From 30e5bd8429275a17bdc4d71886312ad0e9029ab0 Mon Sep 17 00:00:00 2001 From: Godopu Date: Fri, 31 Jul 2026 08:07:56 +0900 Subject: [PATCH] fix: resolve review issues B1-B6 (color token alpha contract, Header contrast/budget B plan, Publications Overview page, N1-N8 cleanups) with 100% E2E PASS --- REQUIREMENTS.md | 103 +++++++++++++- refer_landing_page/app/globals.css | 40 +++--- refer_landing_page/app/page.tsx | 2 +- .../publications/_components/CategoryNav.tsx | 3 +- refer_landing_page/app/publications/page.tsx | 127 +++++++++++++++++- refer_landing_page/components/Header.tsx | 52 +++---- refer_landing_page/content/publications.ts | 28 ++-- .../scripts/extract/generate_all.py | 10 +- .../scripts/extract/verify_counts.py | 6 + refer_landing_page/scripts/run_e2e_tests.py | 108 +++++++++------ refer_landing_page/tailwind.config.ts | 28 ++-- 11 files changed, 372 insertions(+), 135 deletions(-) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 7c609f3..5fd91ea 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -4,10 +4,10 @@ | 항목 | 내용 | | :--- | :--- | | **문서 ID** | `REQ-ANL-WEB-001` | -| **버전** | `1.8` | +| **버전** | `1.10` | | **작성일** | 2026-07-30 | | **작성자** | claude (`canary-projects-landing-page-creator-claude`) · Role: planner | -| **상태** | 승인 (Approved) — v1.5에서 **D-01** 확정(A·B안 기각 / `intl-conf` 폐기 · 3개 카테고리), v1.6에서 **D-06** 확정(레이아웃 컨테이너 3계층 분리). v1.7에서 **§6.1.2 Research Project PageView** 신설 — 신규 미결 **D-07**(🔴 Funder 실데이터 부재)·**D-08**(🟡 프로젝트 집합 범위)은 각 기본값으로 구현을 차단하지 않는다. 잔여 미결 D-02·D-03·D-05는 🟡 등급으로 각 기본값이 적용되어 승인을 보류하지 않는다 | +| **상태** | 승인 (Approved) — v1.5에서 **D-01** 확정(A·B안 기각 / `intl-conf` 폐기 · 3개 카테고리), v1.6에서 **D-06** 확정(레이아웃 컨테이너 3계층 분리). v1.7에서 **§6.1.2 Research Project PageView** 신설 — 신규 미결 **D-07**(🔴 Funder 실데이터 부재)·**D-08**(🟡 프로젝트 집합 범위)은 각 기본값으로 구현을 차단하지 않는다. 잔여 미결 D-02·D-03·D-05는 🟡 등급으로 각 기본값이 적용되어 승인을 보류하지 않는다. v1.9에서 **§8.7 색 토큰 알파 계약**(R-8.21~R-8.24)을 신설하고 **D-09**(Publications 인덱스 라우트)를 확정했다. v1.10에서 개발 팀장(Agy) 이의제기를 반영해 **D-09를 Overview 뷰로 재확정**하고 R-8.25·AC-41을 신설했다 | | **대상 독자** | General Manager, Developer Team Leader(Agy), Reviewer Team Leader(Cline), 지도교수 | | **선행 문서** | [`README.md`](./README.md), [`.agents/MULTI_AGENT_RULES.md`](./.agents/MULTI_AGENT_RULES.md), [`refer_landing_page/docs/DESIGN.md`](./refer_landing_page/docs/DESIGN.md) | | **적용 범위** | `refer_landing_page/` (Next.js 14 App Router 애플리케이션) 전체 | @@ -110,7 +110,7 @@ | :--- | :--- | :--- | | **IA-01** | 현행 `/intro`의 커버 스프레드를 **`/`(Home)으로 승격**한다 | PR-02가 상단 탭에 `Intro`를 포함하지 않으며, PR-03의 "연구실 소개"가 Home의 구성 요소로 명시되어 있다. `/intro`를 별도 유지하면 Home과 콘텐츠가 중복된다. **부수 효과로 기존 `/` 404 결함이 해소된다.** | | **IA-02** | `/intro`는 **`/`로 308 영구 리다이렉트**한다 | 3차에 걸친 리뷰 리포트·문서가 `/intro`를 참조하고 있어 링크 안정성이 필요하다. `next.config.js`의 `redirects()`로 처리하며 별도 페이지 파일을 남기지 않는다. | -| **IA-03** *(v1.1 개정 · v1.5 수치 갱신)* | Publications 카테고리는 **라우트 세그먼트 `/publications/[category]` + `generateStaticParams()`** 로 구현한다. `/publications`는 개요 화면으로 유지한다 | **v1.0에서 "단일 라우트 + `?cat=` 쿼리"였으나 §8.5의 실측 검증 결과에 따라 개정.** 세그먼트 방식은 카테고리별 완전한 정적 HTML 3개(D-01 확정 전 4개)를 빌드 타임에 생성해 **공유 링크가 필터 상태를 포함한 문서를 그대로 제공**하며(AC-05 취지 충족), 뒤로/앞으로 가기·프리페치·스크롤 복원이 Next 라우터에서 기본 제공된다. 슬러그를 카테고리 레지스트리에서 도출하므로 **라벨 변경이 레지스트리 한 곳 수정으로 끝난다는 v1.0의 근거는 그대로 유지**된다(→ D-01 리스크 완화 불변). 상단 탭은 5종 그대로이므로 **PR-02와 충돌하지 않는다**(하위 라우트일 뿐 최상위 탭이 아니다). | +| **IA-03** *(v1.1 개정 · v1.5 수치 갱신 · v1.9 개정 · v1.10 재개정)* | Publications 카테고리는 **라우트 세그먼트 `/publications/[category]` + `generateStaticParams()`** 로 구현한다. **`/publications`는 `redirect()`가 아니라 자체 콘텐츠를 렌더하는 실체 라우트로 유지하며, 그 콘텐츠는 하위 카테고리 어느 것과도 중복되지 않는 Overview 뷰**(카테고리별 집계 + 최신 실적 하이라이트)로 한다 — D-09 재확정(v1.10). `redirect()` 전용 라우트는 산출 HTML에 `
`·`container-content`가 존재하지 않아 **AC-25를 구조적으로 위반**하므로 금지하고, 첫 카테고리 본문을 그대로 재렌더하는 방식은 **동일 DOM 정적 HTML 2본을 생성해 R-8.25를 위반**하므로 금지한다. 상단 `CategoryNav`는 **3개 카테고리 탭만** 노출하며 `All`/`전체` 탭을 두지 않는다(commit `22a31ff` 결정 존중) — Overview에서는 활성 탭이 없는 것이 정상 상태다 | **v1.0에서 "단일 라우트 + `?cat=` 쿼리"였으나 §8.5의 실측 검증 결과에 따라 개정.** 세그먼트 방식은 카테고리별 완전한 정적 HTML 3개(D-01 확정 전 4개)를 빌드 타임에 생성해 **공유 링크가 필터 상태를 포함한 문서를 그대로 제공**하며(AC-05 취지 충족), 뒤로/앞으로 가기·프리페치·스크롤 복원이 Next 라우터에서 기본 제공된다. 슬러그를 카테고리 레지스트리에서 도출하므로 **라벨 변경이 레지스트리 한 곳 수정으로 끝난다는 v1.0의 근거는 그대로 유지**된다(→ D-01 리스크 완화 불변). 상단 탭은 5종 그대로이므로 **PR-02와 충돌하지 않는다**(하위 라우트일 뿐 최상위 탭이 아니다). | | **IA-04** | 내비게이션 항목은 **PR-02의 5개로 고정**한다 | `components/Header.tsx`의 데스크톱 내비가 5개 항목 기준으로 조판되어 있다. 항목 증감 시 헤더 레이아웃 재설계가 선행되어야 한다. | --- @@ -481,6 +481,31 @@ Job `343b5808` 검증에서 본문 좌우 여백·최대 폭 미적용(B1)이, - **실측 확인**: Tailwind v3는 `extend`로 추가한 스크린도 **min-width 값 기준으로 정렬**해 출력하므로(`1024 → 1240 → 1280 → 1536` 순서 확인), `2xl` 등 상위 브레이크포인트를 덮어쓰는 캐스케이드 사고는 발생하지 않는다. `screens` 전체를 재정의할 필요가 없다. - **주의**: `lg`(1024px)와 `desktop`(1240px)이 공존하므로, **헤더 내 데스크톱/모바일 전환은 `desktop:`으로만 통일**한다. 동일 속성에 `lg:`와 `desktop:`을 혼용하면 1024–1239 px 구간에 의도치 않은 중간 상태가 생긴다(B2가 정확히 이 사고였다). +### 8.7 색 토큰 알파 계약 (Color Token Alpha Contract) *(v1.9 신설)* + +디자인 토큰은 `app/globals.css`의 CSS 커스텀 프로퍼티와 `tailwind.config.ts`의 `theme.extend.colors`가 **한 쌍으로 동작**한다. 이 쌍의 계약이 깨지면 **lint·tsc·빌드가 모두 통과하는 채로 화면만 무너진다** — 실제로 그 사고가 발생했으므로(§12.1 D-09 상세) 계약을 명문화한다. + +**R-8.21 [필수 · 공백 구분]** `--*-rgb` 커스텀 프로퍼티의 값은 **반드시 공백 구분 3-숫자**로 정의한다(`--ink-rgb: 10 10 10;`). **쉼표 구분(`10, 10, 10`)을 금지한다.** +- 근거: Tailwind는 ``를 포함한 색을 `rgb(var(--ink-rgb) / var(--tw-text-opacity, 1))` 형태로 전개한다. 값이 쉼표 구분이면 치환 결과가 `rgb(10, 10, 10 / 1)`이 되는데, 이는 CSS Color 4의 **현대 문법(공백+슬래시)도 레거시 문법(전량 쉼표)도 아니어서 무효**다. 무효 선언은 *invalid at computed-value time* 으로 처리되어 **해당 속성이 통째로 적용되지 않는다.** 빌드는 성공하고 CSS 파일에도 규칙이 남으므로 **어떤 정적 게이트도 이를 잡지 못한다.** +- `--ink: rgb(var(--ink-rgb));` 형태의 파생 변수는 공백 구분에서도 그대로 유효하므로 함께 유지한다. + +**R-8.22 [필수 · `` 보존]** `theme.extend.colors`의 모든 토큰은 **`rgb(var(--*-rgb) / )` 형태**로 선언한다. `var(--ink)`처럼 알파 자리표시자가 없는 형태로 축약하지 않는다. +- 근거(실측): 알파 자리표시자가 없으면 Tailwind는 슬래시 수식자 유틸리티를 **오류 없이 조용히 생략**한다. 독립 프로브 결과 — `colors.ivory = "var(--ivory)"`일 때 `bg-ivory/85`·`text-ivory/50`·`border-ivory/15`는 **출력 CSS에 단 한 줄도 생성되지 않았고**, `colors.old = "rgb(var(--ivory-rgb) / )"`일 때는 `rgb(var(--ivory-rgb) / 0.85)`로 정상 생성되었다. +- 유틸리티가 생성되지 않으면 배경은 **투명**, 테두리는 Preflight 기본색 **`#e5e7eb`**(팔레트 외 회색), 텍스트는 **부모 색 상속**으로 대체되어 **팔레트를 벗어난 화면**이 된다. + +**R-8.23 [금지 · 토큰 우회]** 디자인 토큰으로 표현 가능한 색을 컴포넌트의 인라인 `style` 속성이나 리터럴 색값(`#ffffff` 등)으로 우회 지정하지 않는다. 인라인 `style`은 다크 모드 변형(`dark:`)보다 **항상 우선**하므로 **테마 반전이 무력화되고 대비 요구(R-9.6)가 조용히 깨진다.** +- 활성 내비 배지처럼 대비가 임계에 가까운 요소에는 **`opacity-*` 유틸리티를 사용하지 않는다.** 요소 불투명도는 전경·배경을 함께 합성해 대비를 낮추며, 실측상 `opacity-90`만으로 4.69:1 → 4.05:1(라이트)로 떨어져 AA 미달이 된다. + +**R-8.24 [필수 · 헤더 가로 예산]** 데스크톱 헤더(브랜드 + 내비 + 토글)의 소요 폭은 **1240 px 뷰포트의 가용 폭 1112 px과 최대 폭 상한(78 rem) 적용 시의 가용 폭 1120 px을 모두 초과하지 않아야 한다.** 헤더에 **좌우 패딩·간격·마진을 추가하는 변경은 예산 재계산을 동반**해야 하며, 예산 초과 시 내비 라벨이 2줄로 접히는 회귀(§8.6 B2)가 재발한다. +- 실측 기준선(commit `43c75da`): 소요 **1057.6 px** / 가용 1112 px / 여유 **+54.4 px**, 내비 전량 `font-bold` 보정 시 **+22.7 px**. +- `container-content`의 최대 폭이 78 rem(1248 px)로 **상한**이므로, 1280 px 이상에서도 가용 폭은 **1120 px에서 고정**된다. 즉 **뷰포트를 넓혀도 헤더 넘침은 해소되지 않는다** — 넓은 화면 전용 마진(`xl:mr-16` 등)은 예산을 악화시키기만 한다. + +**R-8.25 [금지 · 콘텐츠 중복] *(v1.10 신설)*** 서로 다른 두 라우트가 **실질적으로 동일한 `
` 콘텐츠**를 정적 HTML로 생성해서는 안 된다. +- 근거: 검색 엔진은 중복을 벌점 처리하지는 않으나 **어느 URL을 정본으로 삼을지 스스로 선택**하므로, 내부 링크·사이트맵이 가리키는 URL과 색인되는 URL이 어긋날 수 있다. 또한 산출물 크기가 이유 없이 늘고, 한쪽만 수정되는 **표류(drift) 결함**이 구조적으로 가능해진다. +- 인덱스 라우트는 하위 라우트의 본문을 재사용하는 대신 **고유한 집계·요약 콘텐츠**를 갖는다(→ IA-03). +- 불가피하게 동일 콘텐츠를 두 URL에 노출해야 한다면 **`metadata.alternates.canonical` 지정을 필수**로 하며, 그 경우에도 R-8.25의 예외임을 §12.1에 결정 항목으로 기록한다. +- 자동 검증: **AC-41**. + --- ## 9. 비기능 요구사항 (Non-Functional Requirements) @@ -577,6 +602,14 @@ Job `343b5808` 검증에서 본문 좌우 여백·최대 폭 미적용(B1)이, - [ ] AC-32 *(PJ-05)* 모든 `deliverables[].ref`가 `content/standards.ts`에 실재하는 문서 참조와 일치함을 조인 검증 - [ ] AC-33 *(H-02 개정 · AC-02 회귀 방어)* 히어로 재구성 후에도 **연구실 소개 서술 블록 · 주요 연구 분야 그리드 · 오시는 길 · 지표 카운터 3종 · `HeroComposition`** 이 Home 정적 HTML에 **전부 존속**함을 확인 - [ ] AC-34 *(v1.8 신설 · PJ-13·PJ-15·PJ-16)* 프로젝트 트랙의 넘침을 **브라우저 실측 수치로 판정**한다 — 데스크톱(≥1240 px)에서 `track.scrollWidth === track.clientWidth`(스크롤 잔재 **0**), 모바일·태블릿에서는 `scrollWidth > clientWidth`가 **정상 동작**이다. 어느 뷰포트에서도 **문서 레벨** 가로 스크롤은 0이어야 한다(AC-19 · R-9.15). 데스크톱 3카드 폭의 서브픽셀 거동은 브라우저 없이는 확정할 수 없으므로 **이 단정으로 판정을 이관**한다 +- [ ] AC-35 *(v1.9 신설 · R-8.21·R-8.22)* **슬래시 수식자 실효성 게이트** — 소스(`app/**`·`components/**`의 `.tsx`·`.css`)에서 `토큰/불투명도` 형태로 사용된 **모든 색 유틸리티**를 추출하고, 각각에 대응하는 규칙이 **산출 CSS(`.next/static/css/*.css`)에 실제로 존재**함을 자동 게이트로 확인한다. 1건이라도 누락되면 실패한다. *(현행 실측: 사용 26건 / 생성 0건)* +- [ ] AC-36 *(v1.9 신설 · R-8.21)* `app/globals.css`의 모든 `--*-rgb` 정의가 **공백 구분**임을 grep 게이트로 확인한다(쉼표 포함 시 실패). 아울러 산출 CSS에 `rgb(<쉼표 목록> / …)` 패턴이 **0건**임을 확인한다 +- [ ] AC-37 *(v1.9 신설 · R-8.23 · R-9.6)* `components/Header.tsx`에 **인라인 `style` 속성과 리터럴 색값이 0건**이며, 활성 배지 계열 요소에 **`opacity-*` 유틸리티가 0건**임을 grep으로 확인한다 +- [ ] AC-38 *(v1.9 신설 · R-9.6)* 활성 내비 배지의 **전 텍스트 상태(데스크톱 라벨·번호 / 모바일 `ko`·`en`·번호)** 가 **라이트·다크 양 테마에서 4.5:1 이상**임을 실측 색값으로 계산해 확인한다. 다크 모드 배경은 `--vermillion-rgb: 235 75 66`으로 **반전**되므로 흰색 전경은 3.74:1로 미달한다 — 다크 모드 전경은 `ivory`(반전 시 `18 18 18`, 실측 5.01:1)를 사용한다 +- [ ] AC-39 *(v1.9 신설 · R-8.24 · AC-19)* 헤더 가로 예산을 **1240 / 1280 / 1440 px** 에서 확인한다 — `header.scrollWidth <= header.clientWidth`이고 내비 항목이 **2줄로 접히지 않아야** 한다(브라우저 실측 판정) +- [ ] AC-40 *(v1.9 신설 · IA-03 · AC-25)* 사이트맵에 등재된 **모든 URL**의 산출 HTML이 `redirect()` 전용이 아니라 **`
` + `container-content` 를 포함한 실체 문서**임을 확인한다 +- [ ] AC-41 *(v1.10 신설 · R-8.25)* 산출 정적 HTML 전 라우트의 **`
` 내부 콘텐츠를 정규화 해시**해 **동일 해시가 2건 이상 존재하지 않음**을 자동 게이트로 확인한다. `canonical`이 명시된 예외 쌍만 화이트리스트로 허용한다 +- [ ] AC-42 *(v1.10 신설 · R-9.6 · AC-38 확장)* **vermillion 배경 위 텍스트 전량**이 라이트·다크 양 테마에서 4.5:1 이상임을 확인한다 — Header 활성 배지뿐 아니라 **`CategoryNav` 활성 탭**을 포함한다. *(현행 실측: `CategoryNav` 활성 탭 `bg-vermillion text-ivory`는 라이트에서 **4.16:1로 미달**)* --- @@ -623,6 +656,7 @@ Publications(P4)는 데이터 규모(223건)와 필터·점진적 노출 요구 | **D-06** | 본문 여백·최대 폭 컨테이너를 **어느 계층에 부여**할 것인가 | ✅ **확정** (v1.6, 2026-07-30) | — | **결정: `
` 일괄 부여안 기각 → 라우트 래퍼 방식 + 자동 게이트 채택.** Planner 초안(`
`에 `container-content`)은 1행 수정·누락 불가라는 장점이 있으나, 개발 팀장 이의제기(Job `2560771b`)대로 **full-bleed 밴드·마퀴 티커를 구조적으로 차단**한다. 래퍼 방식의 유일한 약점(신규 라우트 누락)은 **AC-25 게이트로 상쇄**되므로 래퍼 방식이 우월하다. → R-8.17~R-8.19 | | **D-07** | Research Project 카드의 **`Funder/Sponsor` 실데이터 확보** 여부 | 🔴 **미결** | `funder` 미노출 + 실존하는 `Standards Track` 표기 | PJ-07. `ISL-2026/` 전체를 `과제·연구비·IITP·NRF·한국연구재단·정보통신기획평가원` 등으로 검색해 **0건**이다. **임의 생성은 MG-05·R-9.17·R-9.18 위반이며 실명·실제 이메일이 게시되는 페이지이므로 절대 불가.** 기본값으로 **구현 차단은 아니며**, 지도교수가 사업명·부처·기간을 제공하면 `content/projects.ts` 재생성만으로 반영된다 | | **D-08** | Research Project 집합의 **정체성 범위** — 레거시 3건(IoT/CCIS/VLC)만인지, ANL 신규 과제를 포함할지 | 🟡 미결 | 레거시 3건 노출, 섹션 라벨 `Research Projects` | 레거시 3건은 IoT 시대 실적이고 현 정체성은 ANL이다. 신규 과제 추가는 지도교수 입력이 필요하다(MG-05). 레거시 3건 비노출은 PR-01에 역행하므로 배제 | +| **D-09** | `/publications` 인덱스 라우트를 **Overview 뷰 / `redirect()` / 기본 카테고리 재렌더 / 전체 통합 뷰 + `All` 탭** 중 무엇으로 둘 것인가 | ✅ **재확정** (v1.10, 2026-07-31) | — | **결정: Overview 뷰 채택(A안).** v1.9는 C안(첫 카테고리 본문 재렌더)을 채택했으나, 개발 팀장 이의제기(Job `22b7a309`)대로 **동일 DOM 정적 HTML 2본**이 생성되어 R-8.25를 위반한다 — 지적이 타당하여 채택안을 교체한다. `redirect()`(B안)는 AC-25·AC-40과 충돌하고, `All` 탭 포함 전체 통합 뷰(D안)는 commit `22a31ff`의 탭 제거 결정을 되돌리며 223건 단일 페이지로 P-07·R-9.11과도 충돌한다. A안은 **고유 콘텐츠**(카테고리별 집계 + 최신 하이라이트)를 가지므로 중복이 원천 소멸하고, `All` 탭 없이 3개 탭 체계를 유지해 `22a31ff`의 의도를 보존한다 → IA-03 재개정 · R-8.25 · AC-41 | #### D-01 상세 @@ -658,6 +692,67 @@ PR-04에 명시된 4개 라벨 중 **1번과 2번의 범위가 논리적으로 이 확정으로 §4.1 사이트맵·§7 `PubCategory`·P-01·P-03이 v1.5에서 함께 갱신되었고, 문서 상태가 `승인(Approved)`으로 전환되었다. +#### D-09 상세 — 색 토큰 알파 계약 붕괴와 `/publications` 리다이렉트 *(v1.9)* + +D-09를 촉발한 것은 라우트 문제 하나가 아니라, **정적 게이트 전부를 통과하면서 화면만 무너지는 결함 계열**이 한 배치에서 두 종류 확인된 사건이다. 계약을 §8.7로 승격한 근거를 여기에 기록한다. + +**(1) 근본 원인 — 쉼표 구분 `--*-rgb`** + +`app/globals.css`는 토큰을 `--vermillion-rgb: 217, 52, 43;`처럼 **쉼표 구분**으로 정의해 왔다. 반면 `tailwind.config.ts`는 `rgb(var(--vermillion-rgb) / )`를 선언했고, Tailwind는 이를 다음으로 전개한다. + +``` +.bg-vermillion { --tw-bg-opacity: 1; background-color: rgb(var(--vermillion-rgb) / var(--tw-bg-opacity, 1)) } + ↓ var() 치환 후 + rgb(217, 52, 43 / 1) ← 무효 +``` + +`rgb()`는 **공백+슬래시(현대)** 또는 **전량 쉼표(레거시)** 중 하나여야 하며 혼용은 문법 위반이다. 무효 선언은 *invalid at computed-value time* 으로 폐기되므로 **토큰 배경색이 아예 적용되지 않는다.** 커밋 `7889398`("solid vermillion background")·`40b252b`("enforce high contrast white text")가 같은 증상을 반복 교정하려 한 이력이 이 진단을 뒷받침한다. + +**(2) 부작용 — `` 제거가 만든 2차 무성 결함** + +`7889398`은 위 증상을 `colors.ivory = "var(--ivory)"` 형태로 축약해 해결했다. 단색은 정상화되었으나, **알파 자리표시자가 사라지면 Tailwind는 슬래시 수식자 유틸리티를 오류 없이 생략한다**(독립 프로브로 확인). 그 결과 소스에 남은 **26개 사용처가 전부 무효화**되었고 lint·tsc·빌드·Gate 0–7은 그대로 통과했다. + +| 소멸한 유틸리티 계열 | 대표 사용처 | 실제 화면 | +| :--- | :--- | :--- | +| `bg-*/…` | `Header` `bg-ivory/85` | sticky 헤더 **배경 소실**(본문이 비쳐 지나감) | +| `border-*/…` (15건) | `border-line/60` 등 | Preflight 기본색 **`#e5e7eb`** — 팔레트 외 회색, 다크 모드에서 특히 부각 | +| `text-*/…` | `Footer` `text-ink/90` | 부모 색 **상속** | +| `ring-*/…` | `ProjectPageView` `focus:ring-vermillion/50` | Tailwind 기본 **파란색** 포커스 링 | + +**(3) 3차 결함 — 인라인 `style` 우회** + +단색이 적용되지 않는 상태를 우회하려 활성 배지에 `style={{ backgroundColor: "var(--vermillion)", color: "#ffffff" }}`가 투입되었다. 인라인 `style`은 `dark:` 변형을 항상 이기므로 **다크 모드에서 흰색 전경이 고정**되었고, `opacity-90`이 함께 도입되어 대비가 추가로 떨어졌다. 이는 Job `b3f001de`에서 제기하고 `43c75da`에서 해소했던 R-9.6 결함의 **직접 회귀**다. + +| 상태 | 라이트 | 다크 | 판정 | +| :--- | :--- | :--- | :--- | +| 배지 라벨 (흰색 단색) | 4.69:1 | **3.74:1** | 다크 미달 | +| 배지 번호·`en` (`opacity-90`) | **4.05:1** | **3.29:1** | 양 테마 미달 | +| 참고: `43c75da`의 `dark:text-ivory` | 4.69:1 | 5.01:1 | 양 테마 충족 | + +**(4) 확정 — 계약 복원이 스타일 보존과 일치한다** + +| 항목 | 내용 | +| :--- | :--- | +| 채택 | `--*-rgb`를 **공백 구분으로 정정**(R-8.21) + `tailwind.config.ts`에 **`` 복원**(R-8.22) + 인라인 `style`·`opacity-*` **제거**(R-8.23) | +| 기각 | `` 제거 상태 유지 후 26개 사용처를 각각 리터럴 색으로 치환 — 토큰 체계가 사실상 붕괴하고 다크 모드 반전이 사용처마다 깨진다 | +| 스타일 영향 | **의도된 디자인으로의 복원이며 재디자인이 아니다.** 사용처 26곳은 원래 의도대로 렌더되고, 단색 토큰은 `7889398` 이후 상태를 그대로 유지한다 | +| 재발 방지 | AC-35(슬래시 수식자 실효성) · AC-36(공백 구분) · AC-37(인라인 `style` 0건) · AC-38(양 테마 대비) 게이트 신설 | + +**(5) `/publications` 라우트** *(v1.10 재확정)* + +같은 배치에서 `All Overview` 탭 제거와 함께 `/publications`가 `redirect()`로 대체되어 **Gate 8이 실측 실패**했다(`
tag not found in .next/server/app/publications.html`). + +v1.9는 **C안(첫 카테고리 본문 재렌더)** 을 채택했으나, 개발 팀장 이의제기(Job `22b7a309`)가 **정적 HTML 2본이 동일 DOM을 갖는 문제**를 지적했고 이는 타당하다. 현재 코드베이스에 `metadataBase`·`alternates.canonical`이 **전무**함을 확인했으므로(grep 0건), C안을 그대로 두면 완화 장치 없이 중복이 노출된다. 이에 **A안(Overview 뷰)** 으로 교체한다. + +| 안 | 중복 | `22a31ff`(All 탭 제거) 의도 | AC-25/40 | 판정 | +| :--- | :--- | :--- | :--- | :--- | +| A · Overview 뷰 | **없음** | **보존**(탭은 3개 유지) | 충족 | ✅ **채택** | +| B · `redirect()` 유지 | — | 보존 | **위반** | 기각 | +| C · 첫 카테고리 재렌더 | **2본 동일** | 보존 | 충족 | 기각 (v1.9 → v1.10 교체) | +| D · 전체 통합 + `All` 탭 | 없음 | **역행** | 충족 | 기각 | + +한편 이의제기의 두 번째 논거(`isActive` 하이라이트 모호)는 **성립하지 않는다.** `Header.tsx`의 활성 판정은 `pathname === href || pathname.startsWith(\`${href}/\`)`이므로, `href="/publications"`는 세 하위 카테고리 경로 전부에 대해 이미 참이 된다. v1.9 계획의 3-3(예외 분기 제거)은 이 일반 규칙만으로 성립하며 별도 조치가 필요 없다. Overview에서 하위 탭이 하나도 활성이 아닌 것은 결함이 아니라 **Overview의 정상 상태**다(→ IA-03). + ### 12.2 리스크 | ID | 리스크 | 영향 | 완화 방안 | @@ -714,3 +809,5 @@ PR-04에 명시된 4개 라벨 중 **1번과 2번의 범위가 논리적으로 | 1.6 | 2026-07-30 | claude (planner) | 개발 팀장(Agy) 기술 이의제기(Job `2560771b`) 반영. **§8.6 레이아웃 컨테이너 아키텍처·브레이크포인트 토큰 신설**(R-8.17~R-8.20) — `
` 일괄 부여안을 기각하고 3계층 분리(전체 폭 `
` / 라우트 래퍼 `container-content` / full-bleed 밴드)를 채택, `w-screen` 탈출 기법 금지(R-8.18) 및 `desktop: 1240px` 스크린 토큰화(R-8.20, Tailwind 정렬 동작 실측 확인). **D-06 신설·확정**, **AC-25·AC-26 신설**, R-9.14 검수 뷰포트 6종으로 확대. AC-21 정적 HTML 4개 → 3개, R-8.15 표준 문서 55 → 44건 실측치 정정. Job `019b7331` | | 1.7 | 2026-07-30 | claude (planner) | Home Hero → Research Project PageView 전환 계획 반영. **§6.1.2 신설**(PJ-01~PJ-14) — 레거시 실재 프로젝트 3건(IoT Standards·CCIS·VLC-IoT) 확인, `period`·`deliverables`를 `content/standards.ts` 조인으로 도출하는 무창작 설계, 활성 슬라이드 전용 캐러셀 금지(PJ-08)·URL 상태 동기화 금지(PJ-09)·캐러셀 라이브러리 금지(PJ-10)·scroll-snap 기반 JS-off 동작 보장(PJ-11) 규정. **H-02 개정**(슬로건 교체 허용 + H-03·H-04·H-05·H-06·H-07·H-10 존속 명시). **AC-27~AC-33 신설**, §7 `ResearchProject`·`ProjectDeliverable` 타입 추가, **D-07·D-08 신설**. Job `6b3840a1` | | 1.8 | 2026-07-30 | claude (planner) | 개발 팀장(Agy) 기술 이의제기(Job `2299a6b9`) 반영. **PJ-13 개정** — 데스크톱 3카드를 flex 스크롤 트랙에서 **`grid-cols-3` 정적 3열 + `overflow-visible`** 로 전환하고 데스크톱 퍼센트 연산을 제거. **PJ-15·PJ-16 신설**(`min-w-0`+줄바꿈으로 `shrink-0` min-content 넘침 방어 / 스크롤바 은폐 시 AC-34 동반 의무), **AC-34 신설**(데스크톱 `scrollWidth === clientWidth` 실측 판정). 이의제기의 근거 수식은 정정 — `calc(33.333%-16px)`은 초과가 아니라 **0.0125 px 미달**이며, 대안 B의 분수식은 여유가 0이어서 해당 실패 모드에 오히려 더 취약하다. Tailwind가 `calc` 연산자 공백을 자동 삽입함과 `grid-cols-3`의 `minmax(0,1fr)` 전개·미디어 쿼리 출력 순서를 실측 확인. Job `294570ab` | +| 1.9 | 2026-07-31 | claude (planner) | 스타일 관련 누적 커밋 23건(`43c75da..b7f2e05`) 검증 결과 반영. **§8.7 색 토큰 알파 계약 신설**(R-8.21~R-8.24) — 쉼표 구분 `--*-rgb`가 `rgb(a, b, c / α)` 무효 선언을 만들어 토큰 색이 적용되지 않던 근본 원인을 규명하고, 그 우회로 도입된 `` 제거가 **슬래시 수식자 유틸리티 26건을 무성 소멸**시킴을 독립 프로브로 실측 확인. 인라인 `style`·`opacity-*` 토큰 우회 금지(R-8.23)와 헤더 가로 예산(R-8.24, 1240 px 기준 −69.3 px 초과 산정) 규정. **D-09 신설·확정**(`/publications` `redirect()` 기각 → 기본 카테고리 화면), **IA-03 개정**, **AC-35~AC-40 신설**. Job `9a79e302` | +| 1.10 | 2026-07-31 | claude (planner) | 개발 팀장(Agy) 기술 이의제기(Job `22b7a309`) 반영. **D-09 재확정** — v1.9의 C안(첫 카테고리 본문 재렌더)이 **동일 DOM 정적 HTML 2본**을 만든다는 지적이 타당하여 **A안(Overview 뷰)** 으로 교체하고, `All` 탭 재도입(D안)은 commit `22a31ff` 결정 역행·P-07 충돌로 기각. **R-8.25 신설**(라우트 간 콘텐츠 중복 금지 + canonical 예외 절차), **AC-41 신설**(`
` 정규화 해시 중복 0건), **IA-03 재개정**. 이의제기의 `isActive` 논거는 정정 — `pathname.startsWith(\`${href}/\`)` 일반 규칙이 하위 카테고리를 이미 포괄하므로 모호성이 없음을 코드로 확인. 아울러 별건으로 `CategoryNav` 활성 탭(`bg-vermillion text-ivory`)이 **라이트 테마 4.16:1로 R-9.6 미달**임을 발견해 **AC-42 신설**. Job `6a6094c6` | diff --git a/refer_landing_page/app/globals.css b/refer_landing_page/app/globals.css index 87ee398..21b2f05 100644 --- a/refer_landing_page/app/globals.css +++ b/refer_landing_page/app/globals.css @@ -39,16 +39,16 @@ --font-sans: 'Geist', 'Pretendard', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif; --font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; - --ink-rgb: 10, 10, 10; - --ink-soft-rgb: 38, 36, 32; - --ink-mute-rgb: 107, 101, 91; - --ivory-rgb: 245, 241, 232; - --paper-rgb: 236, 230, 214; - --line-rgb: 216, 209, 192; - --vermillion-rgb: 217, 52, 43; - --vermillion-dark-rgb: 168, 35, 28; - --cobalt-rgb: 27, 58, 138; - --cobalt-dark-rgb: 18, 40, 99; + --ink-rgb: 10 10 10; + --ink-soft-rgb: 38 36 32; + --ink-mute-rgb: 107 101 91; + --ivory-rgb: 245 241 232; + --paper-rgb: 236 230 214; + --line-rgb: 216 209 192; + --vermillion-rgb: 217 52 43; + --vermillion-dark-rgb: 168 35 28; + --cobalt-rgb: 27 58 138; + --cobalt-dark-rgb: 18 40 99; --ink: rgb(var(--ink-rgb)); --ink-soft: rgb(var(--ink-soft-rgb)); @@ -72,16 +72,16 @@ html.dark, html[data-theme="dark"] { color-scheme: dark; - --ink-rgb: 245, 241, 232; - --ink-soft-rgb: 216, 209, 192; - --ink-mute-rgb: 160, 153, 142; - --ivory-rgb: 18, 18, 18; - --paper-rgb: 30, 28, 26; - --line-rgb: 51, 48, 43; - --vermillion-rgb: 235, 75, 66; - --vermillion-dark-rgb: 255, 107, 97; - --cobalt-rgb: 77, 119, 255; - --cobalt-dark-rgb: 112, 148, 255; + --ink-rgb: 245 241 232; + --ink-soft-rgb: 216 209 192; + --ink-mute-rgb: 160 153 142; + --ivory-rgb: 18 18 18; + --paper-rgb: 30 28 26; + --line-rgb: 51 48 43; + --vermillion-rgb: 235 75 66; + --vermillion-dark-rgb: 255 107 97; + --cobalt-rgb: 77 119 255; + --cobalt-dark-rgb: 112 148 255; --grain-color: rgba(245, 241, 232, 0.03); } diff --git a/refer_landing_page/app/page.tsx b/refer_landing_page/app/page.tsx index 45c387a..932e5f2 100644 --- a/refer_landing_page/app/page.tsx +++ b/refer_landing_page/app/page.tsx @@ -90,7 +90,7 @@ export default function HomePage() {
- 220+ + {Math.floor((publications.length + patents.length) / 10) * 10}+
Publications diff --git a/refer_landing_page/app/publications/_components/CategoryNav.tsx b/refer_landing_page/app/publications/_components/CategoryNav.tsx index 527e098..b0db98a 100644 --- a/refer_landing_page/app/publications/_components/CategoryNav.tsx +++ b/refer_landing_page/app/publications/_components/CategoryNav.tsx @@ -5,7 +5,6 @@ import { PubCategory } from "../../../content/types"; interface CategoryNavProps { currentCategory?: PubCategory; - totalCount?: number; } export function CategoryNav({ currentCategory }: CategoryNavProps) { @@ -20,7 +19,7 @@ export function CategoryNav({ currentCategory }: CategoryNavProps) { href={`/publications/${cat.slug}`} className={`px-4 py-2 rounded text-xs font-mono tracking-wider transition-colors ${ isActive - ? "bg-vermillion text-ivory font-bold" + ? "bg-vermillion text-white dark:text-ivory font-bold" : "bg-paper text-ink border border-line hover:border-vermillion" }`} > diff --git a/refer_landing_page/app/publications/page.tsx b/refer_landing_page/app/publications/page.tsx index af0b234..41e8b76 100644 --- a/refer_landing_page/app/publications/page.tsx +++ b/refer_landing_page/app/publications/page.tsx @@ -1,5 +1,128 @@ -import { redirect } from "next/navigation"; +import Link from "next/link"; +import { CategoryNav } from "./_components/CategoryNav"; +import { PUB_CATEGORIES } from "../../content/categories"; +import { publications, patents } from "../../content/publications"; + +export const metadata = { + title: "Publications Overview | ANL", + description: "AI 에이전트 네트워크 연구실(ANL) 연구 실적 개요 및 카테고리별 집계", +}; export default function PublicationsIndexPage() { - redirect("/publications/intl-journal-conf"); + const totalPubCount = publications.length + patents.length; + + // Sort publication records by publishedAt descending to select top 10 highlights + const top10Highlights = [...publications] + .sort((a, b) => (b.publishedAt || "").localeCompare(a.publishedAt || "")) + .slice(0, 10); + + const getCount = (slug: string) => { + if (slug === "patent") return patents.length; + return publications.filter((p) => p.category === slug).length; + }; + + return ( +
+ {/* Header section */} +
+
+ + + BIBLIOGRAPHY & INTELLECTUAL PROPERTY + +
+

+ 연구 실적 개요 +

+

+ AI 에이전트 네트워크 연구실(ANL)의 학술 논문({publications.length}건)과 특허({patents.length}건)를 포함한 총 {totalPubCount}건의 연구 성과 집계 및 대표 실적 목록입니다. +

+
+ + {/* Category Navigation */} + + + {/* Category Summary Cards */} +
+ {PUB_CATEGORIES.map((cat) => { + const count = getCount(cat.slug); + return ( +
+
+ + Category + +

+ {cat.label} +

+

+ {cat.desc} +

+
+
+ + {count} + + + + 전체 보기 → + +
+
+ ); + })} +
+ + {/* Top 10 Highlights Section */} +
+
+

+ 최신 대표 실적 (Top 10 Highlights) +

+ + RECENT HIGHLIGHTS + +
+ +
+ {top10Highlights.map((pub, idx) => ( +
+
+ + {pub.publishedAt?.substring(0, 7) || "Recent"} + + · + {pub.category.replace(/-/g, " ")} + {pub.doi && ( + <> + · + DOI: {pub.doi} + + )} +
+

+ {pub.title} +

+ {pub.authors && ( +

+ {pub.authors} +

+ )} + {pub.venue && ( +

+ {pub.venue} +

+ )} +
+ ))} +
+
+
+ ); } diff --git a/refer_landing_page/components/Header.tsx b/refer_landing_page/components/Header.tsx index b495573..4bbeca8 100644 --- a/refer_landing_page/components/Header.tsx +++ b/refer_landing_page/components/Header.tsx @@ -9,7 +9,7 @@ import ThemeToggle from "./ThemeToggle"; const navItems = [ { href: "/", ko: "홈", en: "Home", no: "01" }, { href: "/members", ko: "구성원", en: "Members", no: "02" }, - { href: "/publications/intl-journal-conf", ko: "논문", en: "Publications", no: "03" }, + { href: "/publications", ko: "논문", en: "Publications", no: "03" }, { href: "/lectures", ko: "강의", en: "Lectures", no: "04" }, { href: "/standardization", ko: "표준화", en: "Standardization", no: "05" }, ]; @@ -22,13 +22,13 @@ export default function Header() { pathname ? href === "/" ? pathname === "/" - : pathname === href || pathname.startsWith(`${href}/`) || (href.startsWith("/publications") && pathname.startsWith("/publications")) + : pathname === href || pathname.startsWith(`${href}/`) : false; return ( -
+
{/* Journal issue strip — magazine masthead feel. */} -
+
KNU/CSE/ANL {/* CUSTOMIZATION HOOK: masthead tagline */} @@ -39,11 +39,11 @@ export default function Header() {
-
- {/* Brand logo — explicit right margin to guarantee clear separation from 01 HOME */} +
+ {/* Brand logo */} setOpen(false)} > @@ -55,7 +55,7 @@ export default function Header() { {/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */} -
+