9148c358d0
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
4.4 KiB
4.4 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| frontend-a11y-testing | A11y Testing — axe / 키보드 / 스크린리더 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
A11y Testing
자동화는 30% — 나머지 70% 는 키보드 + 스크린리더 + 색대비.
axe-core(개발) +@axe-core/playwright(E2E) 가 기본. ARIA 보다 native HTML 우선.
📖 핵심 개념
- WCAG 2.1 AA: 일반 기준.
- ARIA: HTML 으로 표현 안 되는 의미 추가. 그러나 native > ARIA.
- Semantic HTML: button, nav, main, header, footer.
- Focus management: 모달 열림 → 모달 안 / 닫힘 → 트리거.
💻 코드 패턴
axe-core dev — 자동 검사
// react app, dev only
if (process.env.NODE_ENV !== 'production') {
import('@axe-core/react').then(({ default: axe }) => {
axe(React, ReactDOM, 1000);
});
}
Playwright E2E
import { expect, test } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('home page is accessible', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
Storybook + a11y addon
// .storybook/main.ts
addons: ['@storybook/addon-a11y'],
스토리에 자동 panel — violations 보여줌.
React Testing Library + jest-axe
import { axe } from 'jest-axe';
import { render } from '@testing-library/react';
test('Button has no a11y violations', async () => {
const { container } = render(<Button>Save</Button>);
expect(await axe(container)).toHaveNoViolations();
});
키보드 — 모든 interactive 가능?
test('navigates with keyboard', async ({ page }) => {
await page.goto('/');
await page.keyboard.press('Tab');
await expect(page.locator(':focus')).toHaveText('Skip to main content');
await page.keyboard.press('Enter');
await expect(page.locator('main')).toBeFocused();
});
Native HTML 우선
<!-- ❌ button 처럼 만들지 마 -->
<div onclick="..." role="button">Save</div>
<!-- ✅ -->
<button>Save</button>
Form — label 연결
<label for="email">Email</label>
<input id="email" type="email" required aria-describedby="email-error">
<p id="email-error" role="alert">잘못된 이메일</p>
모달 focus trap
import { FocusTrap } from 'focus-trap-react';
{open && (
<FocusTrap focusTrapOptions={{ initialFocus: '#confirm', returnFocusOnDeactivate: true }}>
<div role="dialog" aria-modal="true" aria-labelledby="t">
<h2 id="t">정말 삭제?</h2>
<button id="confirm">삭제</button>
<button onClick={() => setOpen(false)}>취소</button>
</div>
</FocusTrap>
)}
또는 Radix UI / Headless UI — focus trap 자동.
Live region
<div role="status" aria-live="polite" className="sr-only">
{savedAt && `Saved at ${savedAt}`}
</div>
Skip link
<a href="#main" className="sr-only focus:not-sr-only">Skip to main content</a>
<main id="main">...</main>
색대비
/* WCAG AA: text 4.5:1, large text 3:1 */
.text-on-bg { color: #1a1a1a; background: #fff; } /* 16:1 */
도구: Chrome DevTools color picker, contrast-ratio.com.
🤔 의사결정 기준
| 검사 | 도구 |
|---|---|
| 자동 (CI) | axe-playwright / axe-react |
| 컴포넌트 단위 | jest-axe + RTL |
| Storybook | addon-a11y |
| 키보드만 | manual test |
| 스크린리더 | macOS VoiceOver / NVDA / JAWS |
| 색대비 | DevTools / Stark |
❌ 안티패턴
<div onClick>모든 것을: 키보드/스크린리더 없음.- Tabindex 양수: tab 순서 깨짐.
0또는-1만. outline: none만: focus-visible 대체.- 모달
<div>body 끝에 — focus 안 trap: focus trap 라이브러리. - Image alt 없음: alt 빈 문자열도 의도. 의미 있는 글로.
- placeholder 만 — label 없음: 스크린리더 사람 못 들음.
- Color only 의미 (빨강 = 에러): 아이콘 / 텍스트 같이.
🤖 LLM 활용 힌트
- Native HTML > ARIA.
- axe + jest-axe + Playwright 3종.
- Radix UI / Headless UI 가 a11y 잘 처리.