h-lab/docs/superpowers/plans/2026-05-31-all-pages-design-uplift.md
hehihoho3@gmail.com 15d8ec0b88 docs: add all-pages design uplift implementation plan
Phase 1 (CSS tokens, utility backfill, shared components) with complete
CSS; Phase 2 (dashboard visuals + deep links, collection URL filters,
per-page sweep). Verification via bootRun + browser observation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 00:25:53 +09:00

26 KiB

전체 페이지 디자인/UX 개선 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 공유 CSS(변수+스타일)를 끌어올려 12개 Thymeleaf 페이지를 한 번에 개선하고(Phase 1), 대시보드/수집함 등 페이지별 폴리시와 가벼운 인터랙션을 더한다(Phase 2).

Architecture: Tailwind 없이 인라인/유틸 클래스에 의존하던 SSR 페이지들에서, 실제 사용 중인 미정의 유틸 클래스를 style.css에 백필하고 공유 컴포넌트(카드/버튼/배지/막대/폼/테이블/스켈레톤)를 정련한다. 그 위에 페이지별 마크업을 소폭 손보고, 대시보드 클릭→수집함 필터 딥링크를 추가한다.

Tech Stack: Spring Boot 3.4 + Thymeleaf(SSR), 바닐라 CSS/JS, Lucide 아이콘. 백엔드/새 라이브러리 변경 없음.

검증 방식: 이 프로젝트는 자동 테스트 인프라가 없다(src/test 없음). 각 태스크의 "테스트"는 앱 기동 후 브라우저(Playwright)로 해당 페이지를 열어 특정 요소를 관찰하는 것이다. JAVA_HOME은 D:\Development\app\JDK\jdk-21.0.5, 포트 8088.

앱 기동/재기동 공통 절차 (여러 태스크에서 참조):

# 기동 (백그라운드)
$env:JAVA_HOME="D:\Development\app\JDK\jdk-21.0.5"; .\gradlew.bat bootRun *> bootrun.log
# "Started ...Application in" 로그가 나오면 준비 완료
# CSS/템플릿만 바뀐 경우: 정적 리소스라 재빌드 불필요하나, 확실히 하려면 앱 재기동.
# 종료: 포트 8088 점유 프로세스 Stop-Process

CSS/HTML은 정적 리소스이므로 변경 후 브라우저 강력 새로고침(Ctrl+F5) 또는 Playwright 재네비게이션으로 반영된다. Java 코드 변경이 없으므로 compileJava는 불필요하다.


File Structure

  • src/main/resources/static/css/variables.css — 디자인 토큰. 토큰 추가만(기존 유지).
  • src/main/resources/static/css/style.css — 유틸 클래스 + 공유 컴포넌트. 백필/정련의 주력.
  • src/main/resources/templates/dashboard.html — Phase 2 비주얼+인터랙션.
  • src/main/resources/templates/collection.html — Phase 2 딥링크 수신 + 폴리시.
  • 기타 템플릿(board/publish/discover/channels/videos/production/production_detail/channel_detail/multi_channel_videos/rework) — Phase 2 페이지 스윕(공유 CSS 위 소폭).

페이지 고유 클래스(sortable, filter-sel, pub-in, kb-*, tab, custom-checkbox, st-badge 등)는 각 페이지 <style> 블록 또는 JS 훅이므로 공유 CSS에서 건드리지 않는다.


PHASE 1 — 공유 파운데이션

Task 1: variables.css 토큰 확장

Files:

  • Modify: src/main/resources/static/css/variables.css

  • Step 1: --primary-rgb·--warning·간격/그림자 토큰 추가

:root { ... } 블록 내 --font-sans 줄 바로 앞에 다음을 추가:

    /* Semantic (added) */
    --warning: #f59e0b;
    --primary-rgb: 59, 130, 246;   /* matches --primary #3b82f6; used by rgba(var(--primary-rgb), …) */
    --success-rgb: 16, 185, 129;
    --danger-rgb: 239, 68, 68;

    /* Spacing scale */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-6: 1.5rem;
    --space-8: 2rem;

    /* Elevation */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
    --shadow-md: 0 4px 12px -2px rgba(0, 0, 0, 0.3);
    --shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.35);
  • Step 2: 본문에 tabular-nums 적용

body { ... } 블록 안 font-family: var(--font-sans); 줄 바로 아래에 추가:

    font-feature-settings: "tnum" 1, "cv01" 1;
  • Step 3: 기동해 회귀 없는지 확인

위 "앱 기동" 절차로 기동(이미 떠 있으면 생략). Playwright로 http://localhost:8088/ 접속 → 콘솔 에러 0건, 레이아웃 깨짐 없는지 스냅샷 확인. Expected: 시각 변화는 미미(토큰 추가뿐). --primary-rgb 사용처(모바일 테이블 Rank 뱃지)가 더 이상 깨지지 않음.

  • Step 4: Commit
git add src/main/resources/static/css/variables.css
git commit -m "style(css): add design tokens (primary-rgb, warning, spacing, shadow, tabular-nums)"

Task 2: style.css 유틸 클래스 백필

템플릿에서 실제 사용되나 미정의인 유틸만 정의한다(인벤토리 기반, 미사용 유틸 양산 금지). 인라인 스타일보다 우선순위가 낮아 회귀 위험이 낮다.

Files:

  • Modify: src/main/resources/static/css/style.css

  • Step 1: 유틸 블록 추가

style.css/* Components */ 주석 바로 앞(기존 유틸 영역 끝, 현재 .p-6 정의 다음 줄)에 아래 블록을 삽입:

/* ===== Utility backfill (classes already used in templates, previously undefined) ===== */

/* Spacing — padding */
.p-0 { padding: 0; }
.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
.p-8 { padding: var(--space-8); }
.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); }
.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
.px-8 { padding-left: var(--space-8); padding-right: var(--space-8); }
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.pr-4 { padding-right: var(--space-4); }

/* Spacing — margin */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mb-1 { margin-bottom: var(--space-1); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-6 { margin-bottom: var(--space-6); }
.ml-2 { margin-left: var(--space-2); }

/* Spacing — gap */
.gap-1 { gap: var(--space-1); }
.gap-3 { gap: var(--space-3); }

/* Fl/grid helpers */
.flex-wrap { flex-wrap: wrap; }
.flex-shrink-0 { flex-shrink: 0; }
.justify-center { justify-content: center; }
.justify-end { justify-content: flex-end; }
.items-start { align-items: flex-start; }
.block { display: block; }
.hidden { display: none; }
.relative { position: relative; }
.absolute { position: absolute; }
.inset-0 { inset: 0; }
.top-4 { top: var(--space-4); }
.left-4 { left: var(--space-4); }
.z-10 { z-index: 10; }
.z-20 { z-index: 20; }
.w-6 { width: 1.5rem; }
.h-6 { height: 1.5rem; }
.h-full { height: 100%; }
.rounded-lg { border-radius: var(--radius-md); }
.opacity-0 { opacity: 0; }
.cursor-pointer { cursor: pointer; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-center { text-align: center; }

/* Typography */
.text-xs { font-size: 0.75rem; }
.text-2xl { font-size: 1.875rem; font-weight: 700; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-normal { font-weight: 400; }

/* Colors */
.text-white { color: #fff; }
.text-secondary { color: var(--text-secondary); }
.text-danger { color: var(--danger); }
.text-success { color: var(--success); }
.text-blue-400 { color: #60a5fa; }
.text-cyan-400 { color: #22d3ee; }
.text-pink-400 { color: #f472b6; }
.text-purple-400 { color: #c084fc; }
.text-orange-400 { color: #fb923c; }
.text-yellow-400 { color: #facc15; }
.text-emerald-400 { color: #34d399; }

/* Transitions */
.transition-colors { transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; }
.transition-opacity { transition: opacity 0.2s ease; }
.transition-all { transition: all 0.2s ease; }

/* Hover utilities (escaped selectors) */
.hover\:text-white:hover { color: #fff; }
.hover\:underline:hover { text-decoration: underline; }
.hover\:text-\[var\(--primary\)\]:hover { color: var(--primary); }
.hover\:border-\[var\(--primary\)\]:hover { border-color: var(--primary); }
.border-\[var\(--glass-border\)\] { border: 1px solid var(--glass-border); }
.hover\:bg-blue-400\/10:hover { background: rgba(96, 165, 250, 0.1); }
.hover\:bg-cyan-400\/10:hover { background: rgba(34, 211, 238, 0.1); }
.hover\:bg-pink-400\/10:hover { background: rgba(244, 114, 182, 0.1); }
.hover\:bg-purple-400\/10:hover { background: rgba(192, 132, 252, 0.1); }
.hover\:bg-orange-400\/10:hover { background: rgba(251, 146, 60, 0.1); }
.hover\:bg-emerald-400\/10:hover { background: rgba(52, 211, 153, 0.1); }
.hover\:bg-yellow-400\/10:hover { background: rgba(250, 204, 21, 0.1); }

/* Group hover reveal */
.group:hover .group-hover\:opacity-100 { opacity: 1; }

/* Spin animation */
@keyframes spin { to { transform: rotate(360deg); } }
.animate-spin { animation: spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) {
    .animate-spin { animation: none; }
}
  • Step 2: 기동 후 대표 페이지 4곳 관찰

앱 기동(또는 떠 있으면 Ctrl+F5). Playwright로 순서대로 접속·스크린샷:

  • http://localhost:8088/ (dashboard)
  • http://localhost:8088/collection
  • http://localhost:8088/board
  • http://localhost:8088/publish

Expected: 텍스트 색(text-danger 등)·정렬(text-center)·말줄임(truncate)·여백(p-3/p-8)이 의도대로 적용되어 정돈됨. 깨짐/겹침 없음. 콘솔 에러 0.

  • Step 3: Commit
git add src/main/resources/static/css/style.css
git commit -m "style(css): backfill utility classes used across templates"

Task 3: style.css 공유 컴포넌트 정련

Files:

  • Modify: src/main/resources/static/css/style.css

  • Step 1: 버튼 변형 보강

.btn-ghost:hover { ... } 정의 다음 줄에 추가(btn-secondary 23곳, btn-outline 2곳에서 사용되나 미정의):

.btn-secondary {
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-primary);
    border: 1px solid var(--glass-border);
}

.btn-secondary:hover {
    background: rgba(255, 255, 255, 0.1);
    border-color: var(--glass-highlight);
    transform: translateY(-1px);
}

.btn-outline {
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--glass-border);
}

.btn-outline:hover {
    color: var(--text-primary);
    border-color: var(--primary);
}
  • Step 2: 공통 막대·배지·스켈레톤 추가

style.css 맨 끝(파일 마지막 줄 뒤)에 추가:

/* ===== Shared components (added) ===== */

/* Progress bar (dashboard funnel, distributions, …) */
.bar-track {
    flex: 1;
    height: 8px;
    background: rgba(255, 255, 255, 0.06);
    border-radius: var(--radius-full);
    overflow: hidden;
}
.bar-fill {
    height: 100%;
    border-radius: var(--radius-full);
    transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
    .bar-fill { transition: none; }
}

/* Badge */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.7rem;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    border: 1px solid transparent;
    line-height: 1.4;
}
.badge-muted   { background: rgba(255,255,255,0.06); color: var(--text-secondary); }
.badge-primary { background: rgba(var(--primary-rgb), 0.15); color: var(--primary); }
.badge-success { background: rgba(var(--success-rgb), 0.15); color: var(--success); }
.badge-warning { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
.badge-danger  { background: rgba(var(--danger-rgb), 0.15); color: var(--danger); }

/* Skeleton shimmer */
.skeleton {
    position: relative;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.05);
    border-radius: var(--radius-md);
}
.skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
    animation: skeleton-shimmer 1.4s infinite;
}
@keyframes skeleton-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
    .skeleton::after { animation: none; }
}

/* Dark form controls (unify select/input across pages) */
select, input[type="text"], input[type="search"], input[type="number"], input[type="date"], input[type="datetime-local"], textarea {
    background: rgba(255, 255, 255, 0.04);
    color: var(--text-primary);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-md);
    padding: 0.5rem 0.75rem;
    font-family: inherit;
    font-size: 0.875rem;
    transition: border-color 0.2s ease, background 0.2s ease;
}
select:focus, input:focus, textarea:focus {
    outline: none;
    border-color: var(--primary);
    background: rgba(255, 255, 255, 0.06);
}
select option { background: #0f172a; color: var(--text-primary); }

/* Table polish */
table { width: 100%; border-collapse: collapse; }
thead th {
    text-align: left;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--glass-border);
}
tbody td { padding: 0.6rem 0.75rem; border-bottom: 1px solid rgba(255, 255, 255, 0.04); }
tbody tr { transition: background 0.15s ease; }
tbody tr:hover { background: rgba(255, 255, 255, 0.03); }

주의: 위 table/select 전역 스타일은 회귀 위험이 상대적으로 높다(페이지별 기존 스타일과 겹칠 수 있음). Step 3에서 테이블·폼이 있는 페이지(collection, videos, production, discover)를 반드시 확인한다.

  • Step 3: 기동 후 테이블/폼 페이지 관찰

앱 기동(또는 Ctrl+F5). Playwright 접속·스크린샷:

  • http://localhost:8088/collection (테이블 + 필터 select)
  • http://localhost:8088/videos
  • http://localhost:8088/production
  • http://localhost:8088/discover

Expected: 테이블 헤더 대문자·구분선, 행 hover 음영, select/input 다크 통일. 모바일 카드 테이블(≤768px) 회귀 없는지 Playwright browser_resize로 폭 375 설정해 collection 재확인.

  • Step 4: Commit
git add src/main/resources/static/css/style.css
git commit -m "style(css): refine shared components (buttons, badge, bar, forms, table, skeleton)"

PHASE 2 — 페이지별 폴리시 & 인터랙션

Task 4: dashboard.html — 비주얼 + 클릭 딥링크

Files:

  • Modify: src/main/resources/templates/dashboard.html

현재 dashboard.html은 KPI 카드, 깔때기, 떡상 TOP5, 발행 현황, 카테고리/출처 막대를 JS로 렌더한다. 인라인 <style>.bar-track/.bar-fill/.st-badge는 Task 3에서 공유로 옮겼으므로 인라인 정의를 제거하고 공유 클래스를 쓴다. 막대 색은 진행도 3톤으로 통일한다.

  • Step 1: KPI 카드를 클릭 가능한 링크로 + 카운트업 준비

<div class="card flex flex-col justify-between"> 5개를 각각 <a> 래퍼로 감싸 딥링크를 건다. 5개 카드의 href와 대상:

  • "수집 영상" → th:href="@{/collection}"
  • "미검토 (NEW)" → th:href="@{/collection(status='NEW')}"
  • "작업대상 (TARGET)" → th:href="@{/collection(status='TARGET')}"
  • "완료 (DONE)" → th:href="@{/collection(status='DONE')}"
  • "발행완료" → th:href="@{/publish}"

각 카드 루트 요소에 class="card ... cursor-pointer" 추가. 숫자를 표시하는 <h3 id="kTotal"> 등에는 카운트업을 위해 data-count 속성을 JS에서 세팅(아래 Step 3).

예: "미검토" 카드:

<a th:href="@{/collection(status='NEW')}" class="card flex flex-col justify-between cursor-pointer" style="text-decoration:none;">
    <div class="flex justify-between items-start mb-3">
        <div><p class="text-sm text-muted">미검토 (NEW)</p><h3 class="text-2xl font-bold" id="kNew">-</h3></div>
        <div style="padding:0.5rem; border-radius:8px; background:var(--bg-hover, rgba(255,255,255,0.06));"><i data-lucide="inbox" color="var(--primary)"></i></div>
    </div>
    <div class="text-sm text-muted" id="kReviewCap">검토중 -</div>
</a>

나머지 4개도 동일 패턴으로 <a>화. (아이콘/캡션 id는 기존 유지)

  • Step 2: 인라인 <style> 정리 + 깔때기/막대 색 3톤화

dashboard.html 내 <style> 블록에서 .bar-track, .bar-fill, .st-badge 정의를 삭제(공유 CSS로 이동됨). 빈 <style>이면 블록 자체 삭제.

  • Step 3: JS — 카운트업·딥링크 막대·배지 색 통일

<script th:inline="javascript"> 내부를 다음 규칙으로 수정:

  1. 카운트업 헬퍼 추가(esc/fmt 근처):
function countUp(el, target){
    target = Number(target||0);
    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){ el.textContent = fmt(target); return; }
    const dur = 600, t0 = performance.now();
    function tick(now){
        const p = Math.min(1, (now - t0) / dur);
        const eased = 1 - Math.pow(1 - p, 3);
        el.textContent = fmt(Math.round(target * eased));
        if (p < 1) requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);
}
  1. KPI 세팅에서 textContent = fmt(...) 5곳을 countUp(el, value)로 교체(kTotal, kNew, kTarget, kDone, kPublished). 캡션은 그대로 textContent.
  2. 깔때기·분포 막대 색을 진행도 3톤으로:
    • 미검토 = var(--text-muted) 톤 → #64748b 유지
    • 검토중·작업대상·출처·카테고리 = var(--primary)
    • 완료·발행완료·롱폼 = var(--success)
    • Shorts·제외 강조 = var(--danger) 기존 bar('검토중', …, '#38bdf8') 등 인라인 hex를 위 토큰값(getComputedStyle 불필요, 직접 'var(--primary)' 문자열을 background에 넣어도 됨)으로 교체.
  3. 떡상 목록: 각 행 앞에 순위 번호 추가(op.map((v,i)=> ... '<span class="badge badge-muted">'+(i+1)+'</span>' ...)), 배수 표시를 badge로:
function ratioBadgeClass(r){ const v=Number(r); return v>=10?'badge-danger':(v>=2?'badge-warning':'badge-success'); }

기존 빨강 고정 배수 스팬을 '<span class="badge '+ratioBadgeClass(v.viewsPerSubRatio)+'">'+ratio+'</span>'로 교체. 5) 발행 최근 목록의 PUB_ST 인라인 색 배지를 badge badge-muted/primary/success로 매핑(DRAFT=muted, READY=warning, PUBLISHED=success). 6) 카테고리 막대 클릭 딥링크: 카테고리 행을 <a href="/collection?categoryId=ID">로 감싼다. 미분류는 /collection(파라미터 없이). 출처 막대 → /collection?source=CHANNEL|SEARCH, Shorts → /collection?shortsOnly=true, 롱폼 → /collection(롱폼 전용 필터 비범위). 7) 로딩 상태: 초기 '로딩 중...' 텍스트를 스켈레톤으로 — funnel/opList/catList/sourceFormat의 초기 innerHTML을 '<div class="skeleton" style="height:48px;"></div>' 류로 교체(선택, 깔끔하면 적용). 8) 새로고침 버튼: loadDashboard() 시작 시 새로고침 아이콘에 animate-spin 추가, 완료(finally) 시 제거.

  • Step 4: 기동 후 대시보드 관찰 + 클릭 검증

앱 기동(또는 Ctrl+F5). Playwright http://localhost:8088/:

  • 스크린샷: KPI 숫자 카운트업(정적 캡처는 최종값), 막대 3톤, 떡상 순위·배수 배지, 발행 배지.
  • 클릭 검증: KPI "미검토" 카드 클릭 → URL이 /collection?status=NEW로 이동하는지 확인(이 시점엔 collection이 아직 파라미터 미수신이라 필터 적용은 Task 5 후 검증).

Expected: 콘솔 에러 0, 모든 섹션 정상, 미검토 카드 클릭 시 /collection?status=NEW로 네비게이션.

  • Step 5: Commit
git add src/main/resources/templates/dashboard.html
git commit -m "feat(dashboard): count-up, 3-tone bars, rank/ratio badges, click-through deep links"

Task 5: collection.html — URL 초기 필터 수신 + 폴리시

Files:

  • Modify: src/main/resources/templates/collection.html

collection.html은 폼 컨트롤(fStatus, fCategory, fSource, fShorts)로 필터를 만들어 loadVideos()를 호출한다(이미 URLSearchParams로 쿼리 빌드). 페이지 진입 시 URL 파라미터를 읽어 폼에 세팅하면 대시보드 딥링크가 실제 필터된다.

  • Step 1: 진입 시 URL 파라미터 → 폼 세팅 함수 추가

loadVideos() 정의 근처(혹은 초기화 IIFE/DOMContentLoaded)에 추가하고, 초기 loadVideos() 호출 직전에 1회 실행:

function applyUrlFilters(){
    const q = new URLSearchParams(window.location.search);
    const setSel = (id, val) => {
        if(val == null) return;
        const el = document.getElementById(id);
        if(!el) return;
        // <select>에 해당 option이 있을 때만 적용(없으면 무시 → 전체 폴백)
        if(el.tagName === 'SELECT'){
            if([...el.options].some(o => o.value === String(val))) el.value = String(val);
        } else { el.value = String(val); }
    };
    setSel('fStatus', q.get('status'));
    setSel('fCategory', q.get('categoryId'));
    setSel('fSource', q.get('source'));
    if(q.get('shortsOnly') === 'true'){ const c = document.getElementById('fShorts'); if(c) c.checked = true; }
    if(q.get('bookmarkedOnly') === 'true'){ const c = document.getElementById('fBookmarked'); if(c) c.checked = true; }
}

fCategory select의 option들이 카테고리 로드 후 채워진다면, applyUrlFilters()카테고리 option을 채운 직후 + 첫 loadVideos() 전에 호출해야 한다. 현재 초기화 순서를 읽고 그 위치에 삽입한다. (카테고리가 비동기 로드면 그 await 다음 줄)

  • Step 2: 로딩 상태 스켈레톤(선택) + 필터바 정렬 점검

기존 '<tr><td colspan="10" ... >로딩 중...</td></tr>'는 유지 가능. 필터바는 Task 3의 폼 통일로 자동 개선됨 — 정렬만 점검(필요 시 flex items-center gap-2 flex-wrap).

  • Step 3: 기동 후 딥링크 end-to-end 검증

앱 기동(또는 Ctrl+F5). Playwright:

  • http://localhost:8088/collection?status=NEW 직접 접속 → 상태 select가 NEW로 세팅되고 결과가 NEW만 표시되는지 스냅샷 확인.
  • http://localhost:8088/ → "미검토" KPI 클릭 → collection으로 이동 후 NEW 필터 적용 확인(Task 4+5 통합 동작).
  • http://localhost:8088/collection?categoryId=999(존재X) → 무시되고 전체 표시(폴백) 확인.

Expected: 유효 파라미터는 필터 적용, 무효 값은 전체 폴백, 콘솔 에러 0.

  • Step 4: Commit
git add src/main/resources/templates/collection.html
git commit -m "feat(collection): apply initial filters from URL params (dashboard deep-link target)"

Task 6: 나머지 페이지 스윕

공유 CSS(Phase 1) 적용 후 대부분 자동 개선되므로, 이 태스크는 페이지별로 육안 확인 → 남은 거슬림만 소폭 수정하는 가이드 스윕이다. 각 페이지는 독립 커밋.

Files (해당되는 것만 Modify):

  • board.html, publish.html, discover.html, channels.html, videos.html, production.html, production_detail.html, channel_detail.html, multi_channel_videos.html, rework.html

페이지별 절차(각 페이지 반복):

  • Step A: 페이지 열어 관찰 — 앱 기동 상태에서 Playwright로 해당 URL 접속, 스크린샷.
    • URL 매핑: /board, /publish, /discover, /channels, /videos, /production. 상세 페이지는 목록에서 진입(존재하는 id로).
  • Step B: 거슬림 식별 — 다음만 본다: ① 인라인 hex 색이 토큰과 안 맞는 곳 ② 로딩 "로딩 중…" 텍스트(→ .skeleton 적용 여부 판단) ③ 클릭 가능한데 hover/cursor 없는 요소 ④ 막대/배지를 공유 컴포넌트(bar-track/badge)로 바꿀 수 있는 인라인 ⑤ 간격/정렬 불일치.
  • Step C: 최소 수정 — 식별된 것만 공유 클래스로 치환(새 구조 변경 금지, 동작 보존). 페이지 고유 컴포넌트(kb-*, pub-in 등)는 색/여백 토큰화 정도만.
  • Step D: 재관찰 — Ctrl+F5/재네비게이션 후 스크린샷으로 개선 확인, 회귀 없는지 확인.
  • Step E: 페이지별 Commit — 예: git add src/main/resources/templates/board.html && git commit -m "style(board): tokenize colors, add hover/skeleton polish"

우선순위(영향도 순): board → publish → discover → channels → videos → production → 상세 4종(production_detail/channel_detail/multi_channel_videos/rework). 시간/거슬림 없으면 일부 페이지는 "변경 없음(공유 CSS로 충분)"으로 스킵 가능 — 스킵한 페이지는 최종 보고에 명시.

  • Step F: 전체 마무리 검증 — 모든 페이지를 한 번씩 순회하며 콘솔 에러 0·레이아웃 정상 확인. 모바일 폭(375)으로 dashboard/collection/board 재확인.

Self-Review (작성자 점검 결과)

  • Spec 커버리지: Phase 1 토큰(Task 1)·유틸 백필(Task 2)·공유 컴포넌트(Task 3) ✓. Phase 2 dashboard 비주얼+딥링크(Task 4)·collection 딥링크 수신(Task 5)·기타 페이지 스윕(Task 6) ✓. 비목표(백엔드/라이브러리/시계열) 준수 ✓.
  • 플레이스홀더: Phase 1·Task 4·5는 완전한 코드 제공. Task 6은 코드가 아니라 가이드 스윕(각 페이지 현 마크업은 실행 시 읽어야 정확)이며, 추측 diff를 지어내지 않기 위해 의도적으로 절차+수용기준으로 구성. 이는 "공유 CSS가 대부분 자동 개선"이라는 설계 전제의 결과.
  • 타입/이름 일관성: .bar-track/.bar-fill(Task 3 정의 → Task 4 사용), .badge*(Task 3 → Task 4), --primary-rgb(Task 1 → Task 3 badge), applyUrlFilters/폼 id fStatus(collection 기존)·딥링크 파라미터명 status/categoryId/source/shortsOnly(Task 4 생성 ↔ Task 5 수신) 일치 ✓.
  • 위험: Task 3의 전역 table/select 스타일이 최고 회귀 위험 → Step 3에서 테이블·폼 페이지 + 모바일 폭 확인으로 가드.