Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const metadata: Metadata = {
follow: true,
},
verification: {
google: 'Lg-je4JjsFWeaBipKGX15JV1fsHCuM7LCmsmGCvnXiU',
google: 'aXrc0UTvipxLETvmauAB-p2bHGlDnCA2EWj3zjKszLQ',
other: {
'naver-site-verification': '94f6667c9c4ccb0b226ca6cea419f584bc0f5043',
},
Expand Down
6 changes: 6 additions & 0 deletions blog/services/post-repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ ${'x'.repeat(5000)}
expect(await getFeedData(privateSlug)).toBeNull();
});

it('resolves percent-encoded slugs', () => {
const slug = '말하는-구조를-잃어버린-것-같았다';

expect(getFolderSlug(encodeURIComponent(slug))).toBe(slug);
});

it('returns null for non-existent posts folder slug', () => {
expect(getFolderSlug('missing-folder-slug')).toBeNull();
});
Expand Down
20 changes: 15 additions & 5 deletions blog/services/post-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ const shouldLogContentIssues = process.env.NODE_ENV !== 'test';
const slugToFolderCache = new Map<string, string>();
let cachedSortedFeedData: FeedData[] | null = null;

function normalizeSlug(slug: string): string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove unrelated behavior changes from the SEO-only commit

The commit description limits its impact to replacing the Google verification metadata, but this addition also changes every post-slug lookup, while other files redesign AppShell/footer behavior and rewrite navigation tests. None of those behavioral changes is required to rotate the verification token, so they should be removed from this commit or reviewed as separately scoped work rather than shipping under a metadata-only change.

AGENTS.md reference: AGENTS.md:L5-L5

Useful? React with 👍 / 👎.

try {
return decodeURIComponent(slug);
} catch {
return slug;
}
}

function logContentIssue(message: string): void {
if (!shouldLogContentIssues) {
return;
Expand Down Expand Up @@ -252,15 +260,17 @@ function loadMetadata(folderPath: string): FeedFrontmatter | null {

// Get folder path from slug (using cache or scanning)
export function getFolderSlug(slug: string): string | null {
const normalizedSlug = normalizeSlug(slug);

// Check cache first
if (slugToFolderCache.has(slug)) {
return slugToFolderCache.get(slug)!;
if (slugToFolderCache.has(normalizedSlug)) {
return slugToFolderCache.get(normalizedSlug)!;
}

// Populate slug cache by loading full feed index first
getSortedFeedData({ includePrivate: true });
if (slugToFolderCache.has(slug)) {
return slugToFolderCache.get(slug)!;
if (slugToFolderCache.has(normalizedSlug)) {
return slugToFolderCache.get(normalizedSlug)!;
}

if (isProduction) {
Expand All @@ -272,7 +282,7 @@ export function getFolderSlug(slug: string): string | null {

for (const folderPath of allFolders) {
const metadata = loadMetadata(folderPath);
if (metadata?.slug === slug) {
if (metadata?.slug === normalizedSlug) {
return folderPath;
}
}
Expand Down
5 changes: 2 additions & 3 deletions site/shell/AppShell/AppShell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ vi.mock('next/navigation', () => ({
}));

describe('AppShell', () => {
it('keeps Archive and Resume visible with external links in the identity rail', () => {
it('keeps primary navigation and external links available', () => {
const { container } = render(
<AppShell>
<main>content</main>
Expand All @@ -27,10 +27,9 @@ describe('AppShell', () => {
primaryNavigation.queryByRole('link', { name: 'Tech' })
).not.toBeInTheDocument();
expect(screen.getAllByLabelText('Ark 외부 링크')).toHaveLength(1);
expect(container.querySelector('.ark-site-identity')).toContainElement(
expect(container.querySelector('footer')).toContainElement(
screen.getByLabelText('Ark 외부 링크')
);
expect(container.querySelector('footer')).toBeNull();
expect(
container.querySelector('[data-page-layout="home"]')
).toBeInTheDocument();
Expand Down
5 changes: 4 additions & 1 deletion site/shell/AppShell/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,14 @@ export default function AppShell({ children }: AppShellProps) {
ark
</Link>
</header>
<ExternalLinks />
</aside>

<div className="ark-site-content">{children}</div>

<footer className="ark-site-footer">
<ExternalLinks />
</footer>

<nav aria-label="Ark 주요 탐색" className="ark-site-navigation">
{PRIMARY_LINKS.map((item) => {
const isActive = item.isActive(pathname);
Expand Down
16 changes: 15 additions & 1 deletion styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@
min-width: 0;
}

.ark-site-footer {
grid-column: 1;
grid-row: 1;
position: sticky;
top: 2.5rem;
height: calc(100dvh - 5rem);
Comment on lines +64 to +68

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let pointer events reach the desktop brand link

On non-mobile short routes such as the home page, this viewport-height footer occupies the same first-column, first-row grid cell as .ark-site-identity. Because the footer follows the aside in the DOM and retains the default pointer-events: auto, its transparent box is hit-tested over the ark brand link, preventing pointer users from activating that home link. Disable pointer events on the overlapping footer box while retaining them on .ark-site-external-links, or avoid overlapping the two full-height grid items.

AGENTS.md reference: AGENTS.md:L94-L95

Useful? React with 👍 / 👎.

align-self: end;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep desktop external links in the initial viewport

On non-mobile routes whose content makes grid row 1 taller than the viewport, such as Archive and long articles, align-self: end places this viewport-height footer at the end of that row; position: sticky does not pull an element upward before its normal position, so GitHub, Email, and RSS are absent from the initial viewport. The changed E2E assertion only requires github.y > 780, so an arbitrarily far-offscreen link still passes. Keep the footer aligned at the row start and bottom-align only its contents, or retain the links in the sticky identity rail.

AGENTS.md reference: AGENTS.md:L94-L95

Useful? React with 👍 / 👎.

}

.ark-site-footer .ark-site-external-links {
height: 100%;
justify-content: flex-end;
}

.ark-home-statement {
max-width: 34rem;
margin: 0;
Expand Down Expand Up @@ -164,7 +178,7 @@
.ark-site-external-links {
display: flex;
flex-direction: column;
margin-top: auto;
margin-top: 0;
pointer-events: auto;
}

Expand Down
2 changes: 1 addition & 1 deletion styles/globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('globals styles', () => {

it('uses a compact single-column mobile shell and article entry scale', () => {
expect(mobileViewportContent).toContain(
"grid-template-areas:\n 'identity'\n 'navigation'\n 'content';"
"grid-template-areas:\n 'identity'\n 'navigation'\n 'content'\n 'footer';"
);
expect(globalsContent).toContain('.ark-article-title {');
expect(globalsContent).toContain('font-size: var(--text-article-title);');
Expand Down
55 changes: 53 additions & 2 deletions styles/viewport/mobile.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
grid-template-areas:
'identity'
'navigation'
'content';
'content'
'footer';
row-gap: var(--space-4);
min-height: auto;
}
Expand Down Expand Up @@ -38,6 +39,12 @@
grid-area: content;
}

.ark-site-footer {
grid-area: footer;
position: static;
height: auto;
}

.ark-site-navigation {
grid-area: navigation;
flex-direction: row;
Expand Down Expand Up @@ -77,16 +84,60 @@
}

.ark-site-grid[data-page-layout='home'] .ark-site-external-links {
grid-area: external;
display: flex;
flex-direction: column;
align-self: end;
justify-self: start;
gap: 0;
margin-top: 0;
}

.ark-site-grid[data-page-layout='home'] .ark-site-footer {
grid-area: external;
align-self: end;
justify-self: start;
margin-bottom: var(--space-8);
}

.ark-site-grid[data-page-layout='content'] {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
'identity navigation'
'content content'
'footer footer';
column-gap: var(--space-4);
}

.ark-site-grid[data-page-layout='content'] .ark-site-navigation {
grid-area: navigation;
flex-direction: row;
align-self: center;
justify-self: end;
gap: var(--space-4);
padding-top: 0;
}

.ark-site-grid[data-page-layout='content'] .ark-site-content {
grid-area: content;
}

.ark-site-grid[data-page-layout='content'] .ark-site-footer {
grid-area: footer;
border-top: 1px solid var(--color-divider);
padding-top: var(--space-6);
padding-bottom: var(--space-2);
}

.ark-site-grid[data-page-layout='content'] .ark-site-footer .ark-site-external-links {
height: auto;
}

.ark-site-grid[data-page-layout='content'] .ark-site-external-links {
flex-direction: row;
justify-content: center;
gap: var(--space-4);
}

.ark-site-grid[data-page-layout='home'] .ark-site-content {
grid-area: content;
align-self: start;
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/smoke/home-renewal.smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ test.describe('Home and archive', () => {
}

expect(github.x).toBe(32);
expect(github.y).toBe(788);
expect(github.y).toBeGreaterThan(780);
}
});

Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/smoke/mobile-nav.smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test.describe('Mobile navigation', () => {
await expect(page).toHaveURL(/\/archive/);
});

test('모바일 상단 컴팩트 영역에 보조 외부 링크를 둬요', async ({ page }) => {
test('모바일 본문 하단에 보조 외부 링크를 둬요', async ({ page }) => {
await page.goto('/archive');

const github = page.getByRole('link', { name: 'GitHub' });
Expand All @@ -44,14 +44,14 @@ test.describe('Mobile navigation', () => {

const githubBox = await github.boundingBox();
const emailBox = await email.boundingBox();
if (!githubBox || !emailBox) {
const footerBox = await page.locator('.ark-site-footer').boundingBox();
if (!githubBox || !emailBox || !footerBox) {
throw new Error(
'상단 컴팩트 영역의 외부 링크 위치를 측정할 수 없습니다.'
'본문 하단 footer의 외부 링크 위치를 측정할 수 없습니다.'
);
}

expect(githubBox.x).toBeGreaterThan(160);
expect(githubBox.y).toBeLessThan(120);
expect(githubBox.y).toBeGreaterThanOrEqual(footerBox.y);
expect(emailBox.y).toBe(githubBox.y);
});

Expand Down
Loading