유스케이스 TDD 16테마 스펙과 접근성 제품 결함 수정

- 지원 티켓 작성 UI 신설(설정)·관리자 해결 노트 입력 신설(Admin): 서버 계약은 있었으나
  웹 진입점이 없던 2결함
- a11y: outline 채널 포커스 링(ui/shell css), 세션바 44px 터치 타깃, 청록 하드코딩
  그라디언트를 테마 토큰으로 교체, 설정 라벨/헤딩/대비·리뷰 44px·모바일 오버플로 수정
- uc-*.spec.ts 16테마 239 시나리오 신규(수집 1109 tests/61 files), 기존 스펙 5종 계약 드리프트 교정
- breakpoint-sweep: widthsFor 솎아내기가 실기기 대표 폭(360/390/1024)을 탈락시키는 테스트 결함 수정
  — keep 시드를 전체 DEVICE_WIDTHS로, 3연폭 예외는 솎아내기 발생 여부 기준으로
- 검증: tsc PASS, 병렬 게이트 1040 passed(데스크톱 밀도 충돌 1건 해소 후 focused 68/68 GREEN),
  직렬 게이트 52/5/4 삼각화 — breakpoint(테스트 결함)·kb(낡은 dev API 재기동)·voice(일시적) 해소,
  교사 재평가 2건은 엔진 구독 한도(resets 3pm)로 skip 후 재검증 대기
- 문서/SSOT: HANDOFF·TODO·대시보드·testing 가이드 동기화, 증거 usecase-tdd-2026-08-18.json,
  SSOT 체커 PASS(59)
This commit is contained in:
Yun Chan 2026-08-18 11:47:58 +09:00
parent 4771b97c3a
commit cf899b7f15
37 changed files with 11989 additions and 67 deletions

View file

@ -175,7 +175,10 @@ function widthsFor(breakpoints: number[]): number[] {
if (merged.length > MAX_WIDTHS_PER_ROUTE) {
// 상한을 넘으면 실기기 폭을 먼저 유지하고(사용자가 실제로 보는 폭),
// 경계 폭은 균등 간격으로 솎아 낸다 — 경계 3연폭 세트는 최대한 함께 남긴다.
const keep = new Set<number>(devices);
// keep 은 "경계에 이미 있다고 제외된 devices" 가 아니라 전체 DEVICE_WIDTHS 로
// 시드한다. 아니면 브레이크포인트와 겹치는 실기기 폭(예: 360)이 솎아내기에서
// 빠져 arrayContaining(DEVICE_WIDTHS) 계약이 깨진다.
const keep = new Set<number>(DEVICE_WIDTHS.map(clampWidth));
const budget = MAX_WIDTHS_PER_ROUTE - keep.size;
const step = Math.max(1, Math.ceil(ordered.length / Math.max(1, budget)));
for (let i = 0; i < ordered.length; i += step) keep.add(ordered[i]);
@ -640,6 +643,15 @@ function widthsForPage(page: keyof typeof PAGE_CSS) {
return widthsFor([...new Set([...index.common, ...(index.byPage[page] ?? [])])]);
}
/** 해당 페이지가 폭 상한을 넘어 솎아내기가 일어났는지 — 3연폭 계약의 예외 조건. */
function isPrunedPage(page: keyof typeof PAGE_CSS) {
const candidate = new Set<number>(DEVICE_WIDTHS.map(clampWidth));
for (const bp of [...new Set([...index.common, ...(index.byPage[page] ?? [])])]) {
for (const delta of [-1, 0, 1]) candidate.add(clampWidth(bp + delta));
}
return candidate.size > MAX_WIDTHS_PER_ROUTE;
}
async function runSweep(page: Page, routes: RouteCase[]) {
const report: string[] = [];
let total = 0;
@ -674,7 +686,7 @@ test.describe("브레이크포인트 경계 스윕 @single-run", () => {
// 브레이크포인트 B 마다 B-1/B/B+1 이 살아 있는지(클램프 경계 제외).
for (const bp of index.byPage[pageKey] ?? []) {
if (bp <= MIN_WIDTH || bp >= MAX_WIDTH) continue;
if (widths.length >= MAX_WIDTHS_PER_ROUTE) continue; // 상한으로 솎아 낸 경우는 예외
if (isPrunedPage(pageKey)) continue; // 상한으로 솎아 낸 경우는 예외
expect(widths, `${pageKey}: 브레이크포인트 ${bp}px 경계 3연폭 누락`).toEqual(
expect.arrayContaining([bp - 1, bp, bp + 1]),
);