From 1c772ae091ff2a700af0ce025792b6136efa2122 Mon Sep 17 00:00:00 2001 From: Godopu Date: Thu, 30 Jul 2026 21:54:22 +0900 Subject: [PATCH] feat: implement Research Project PageView (v1.8) with static SSG pre-render and E2E gates --- REQUIREMENTS.md | 75 ++++++++- refer_landing_page/README.md | 14 +- .../app/_components/ProjectPageView.tsx | 132 ++++++++++++++++ .../_components/ProjectPageViewControls.tsx | 122 +++++++++++++++ refer_landing_page/app/page.tsx | 30 +++- refer_landing_page/content/projects.ts | 142 ++++++++++++++++++ refer_landing_page/content/types.ts | 18 +++ .../scripts/extract/generate_all.py | 77 ++++++++++ .../scripts/extract/verify_counts.py | 16 +- refer_landing_page/tests/e2e_test_suite.py | 18 +++ 10 files changed, 623 insertions(+), 21 deletions(-) create mode 100644 refer_landing_page/app/_components/ProjectPageView.tsx create mode 100644 refer_landing_page/app/_components/ProjectPageViewControls.tsx create mode 100644 refer_landing_page/content/projects.ts diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index bfd8119..7c609f3 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -4,10 +4,10 @@ | 항목 | 내용 | | :--- | :--- | | **문서 ID** | `REQ-ANL-WEB-001` | -| **버전** | `1.6` | +| **버전** | `1.8` | | **작성일** | 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계층 분리). 잔여 미결 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는 🟡 등급으로 각 기본값이 적용되어 승인을 보류하지 않는다 | | **대상 독자** | 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 애플리케이션) 전체 | @@ -143,7 +143,7 @@ PR-03에 따라 **연구실 소개 · 주요 연구 분야 소개 · 오시는 | ID | 요구사항 | 수준 | | :--- | :--- | :-- | | H-01 | `app/page.tsx`를 신규 생성하고, 현행 `app/intro/page.tsx`의 섹션 구성(커버 스프레드 → 지표 카운터 → Mission 풀쿼트 → 핵심 연구 분야 → Focus Areas)을 이관한다 | MUST | -| H-02 | 커버 히어로는 현행 확정 카피를 유지한다: 킥커 `경북대학교 컴퓨터학부 · AI Agent Networking Lab (ANL)`, 헤드라인 `에이전트 네트워크의 / 미래를 짓다`, 디스플레이 `Agents / in Motion` | MUST | +| H-02 *(v1.7 개정)* | 킥커 `경북대학교 컴퓨터학부 · AI Agent Networking Lab (ANL)`는 유지한다. **슬로건 헤드라인(`에이전트 네트워크의 / 미래를 짓다`, 디스플레이 `Agents / in Motion`)은 §6.1.2 Research Project PageView로 교체할 수 있다.** 단 교체 시에도 **H-03(연구실 소개 서술 블록) · H-04(주요 연구 분야 그리드) · H-05(오시는 길) · H-06·H-07(지표 카운터) · H-10(`HeroComposition`)은 반드시 존속**한다 — 히어로 재구성 중 이들이 유실되면 **AC-02가 즉시 실패**한다 | MUST | | H-03 | **[연구실 소개]** 연구실 개요·지도교수·비전을 서술하는 블록을 배치하고, 상세는 `/members`로 연결한다 | MUST | | H-04 | **[주요 연구 분야]** §5의 핵심 연구 분야 전문을 매거진 조판으로 노출한다 | MUST | | H-05 | **[오시는 길]** 아래 §6.1.1 요구사항을 충족하는 블록을 배치한다 | MUST | @@ -168,6 +168,37 @@ PR-03에 따라 **연구실 소개 · 주요 연구 분야 소개 · 오시는 | H-17 | 주소는 시맨틱 `
` 요소로 마크업한다 | MUST | | H-18 | 대중교통·주차 안내는 **레거시에 데이터가 없으므로 이번 범위에서 제외**한다. 필요 시 지도교수 확인 후 별도 잡으로 추가 | MUST | +#### 6.1.2 Research Project PageView *(v1.7 신설)* + +Home의 슬로건 히어로를 **실제 연구 프로젝트 카드 뷰**로 전환한다(H-02 개정). + +**레거시 실측 — 실재하는 프로젝트 3건**: `ISL-2026/index.html` `
`의 `card-title`/`card-text` 및 이어지는 확장 섹션 2개가 유일한 원본이다. (`projects-cards` 클래스는 **표준화 기구 로고 그리드**이며 연구 프로젝트가 아니다 — 오독 주의.) + +| # | Title | 확장 정보 | +| :-- | :--- | :--- | +| 1 | `IoT Standards` | — | +| 2 | `CCIS` | "This project is developing a series of standards in **IEC/TC 100/TA 17**" | +| 3 | `VLC-IoT` | "This project is purposed to develop a series of standards in **ITU-T SG20**." | + +| ID | 요구사항 | 수준 | +| :--- | :--- | :-- | +| PJ-01 | 데이터는 `content/projects.ts`에 두고, 타입은 **`ResearchProject`** 로 명명한다. `StandardsBody.projects`(WG 단위 표준화 프로젝트)와 **의미가 다르므로 이름을 재사용하지 않는다** | MUST | +| PJ-02 | `content/projects.ts`는 **`scripts/extract/generate_all.py`의 `parse_projects()`로 자동 생성**한다. 수작업 편집 금지(기존 `AUTO-GENERATED` 규약 · AC-24) | MUST | +| PJ-03 | `abstract`는 레거시 원문을 **바이트 동일하게 보존**한다(PR-01). 요약·재작성·`line-clamp`로 인한 정보 손실 금지 | MUST | +| PJ-04 | `period`는 `content/standards.ts` `standardsBodies[].period`를 **조인해 도출**한다. 교차 검증 출처는 `ISL-2026/sjkoh/index.html`의 Project Editor 이력이다 | MUST | +| PJ-05 | `deliverables`는 `content/standards.ts` `projects[].documents[]`를 **조인해 도출**한다. 임의 생성 금지 | MUST | +| PJ-06 | `keywords[]`의 **모든 원소는 해당 프로젝트 `abstract` 원문에 부분문자열로 실제 등장해야 한다.** 키워드 창작을 기계적으로 차단하기 위한 규칙이다 (→ AC-30) | MUST | +| PJ-07 | `funder`는 **옵셔널**이며 현재 전건 미확보다(§12.1 **D-07**). **값이 없으면 UI에 해당 행·라벨·자리표시자를 렌더하지 않는다**(R-9.18). 레거시가 실제로 명시하는 표준화 트랙은 `Funder`가 아닌 **`Standards Track`** 라벨로 노출한다 | MUST | +| PJ-08 **[금지]** | **활성 슬라이드만 마운트하는 캐러셀을 구현하지 않는다.** 프로젝트 전건이 항상 DOM에 존재해 정적 HTML에 포함되어야 한다(R-8.15 · AC-27) | MUST | +| PJ-09 **[금지]** | 슬라이드·페이지 상태를 **URL에 동기화하지 않는다.** `searchParams` prop은 라우트를 `ƒ Dynamic`으로 전환시키고(R-8.11), `useSearchParams()`는 `` 경계를 요구하며 경계 안쪽 콘텐츠가 정적 HTML에서 누락된다(R-8.12·R-8.13). 상태는 컴포넌트 로컬로 유지한다 | MUST | +| PJ-10 **[금지]** | 캐러셀·슬라이더 **외부 라이브러리를 도입하지 않는다**(swiper·embla 등). 현행 런타임 의존성 3종(`next`/`react`/`react-dom`)을 유지한다(R-9.13) | MUST | +| PJ-11 | 트랙은 **CSS scroll-snap + 컨테이너 내부 `overflow-x-auto`** 로 구현해 **JS 비활성 상태에서도 전건 열람이 가능**해야 한다. 페이지 레벨 가로 스크롤을 유발해서는 안 되며(R-9.15·AC-19), full-bleed를 위해 `w-screen`을 쓰지 않는다(R-8.18) | MUST | +| PJ-12 | 데이터 렌더는 **서버 컴포넌트**, 인터랙션(버튼·인디케이터)만 `"use client"` 로 분리한다. 페이지네이션 인디케이터는 `` 앵커로 두어 JS 없이도 동작하게 한다. 스크롤 애니메이션은 **`prefers-reduced-motion` 존중**(`components/Counter.tsx`·`globals.css` 선례) | MUST | +| PJ-13 *(v1.8 개정)* | 반응형 카드 노출: 360 px 1장 / `md` 2장(`w-[calc(50%-12px)]` — `2×(50%−12px)+24px = 100%`로 **대수적 정확값**) / **`desktop`(1240 px)에서는 스크롤 트랙이 아니라 `desktop:grid desktop:grid-cols-3 desktop:overflow-visible` 정적 3열로 전환**하고 카드는 `desktop:w-full`로 둔다. **데스크톱 카드 폭에 퍼센트 연산을 사용하지 않는다.** 근거: Tailwind `grid-cols-3`은 `repeat(3, minmax(0, 1fr))`로 전개되어 **min-content 넘침을 구조적으로 차단**하며, `overflow-visible`이 해당 요소를 **스크롤 컨테이너에서 해제**해 스크롤 잔재·불필요한 포커스 가능 스크롤 영역을 함께 제거한다(→ AC-34). 브레이크포인트는 `desktop` 토큰만 사용한다(R-8.20 · AC-26) | MUST | +| PJ-14 | 접근성: 트랙 `role="group"` + `aria-roledescription="carousel"`, 카드 `aria-roledescription="slide"` + `aria-label="N개 중 M번째"`, 버튼 `aria-label` 및 `aria-controls`, 카드 제목은 `

` | MUST | +| PJ-15 *(v1.8 신설)* | 카드에 **`min-w-0`** 과 텍스트 줄바꿈(`break-words`, 국문은 R-9.16 `break-keep`)을 적용한다. **`flex-shrink: 0`인 플렉스 아이템은 콘텐츠의 min-content 폭 이하로 줄지 않으므로**, `standardsTrack`(`IEC/TC 100/TA 17`)·`deliverables[].ref`(`ISO/IEC TR 30189`)처럼 `/`를 포함해 기본 줄바꿈 기회가 없는 문자열이 **폭 수식과 무관하게 트랙을 넘치게 할 수 있다.** PJ-13의 그리드 전환은 데스크톱만 보호하므로, 모바일·태블릿 구간은 이 규칙으로 보호한다 | MUST | +| PJ-16 *(v1.8 신설)* | 스크롤바를 숨기는 경우(`[scrollbar-width:none]` 등) 잔여 넘침이 **시각적 단서 없이 은폐**되므로, **AC-34 검증을 반드시 동반**한다 | MUST | + ### 6.2 Members (`/members`) — PR-01 적용 **레거시 실측**: 재학 16명(교수 1 · Ph.D. 1 · Ph.D. Candidate 4 · Ph.D. Student 5 · M.S. Candidate 3 · M.S. Student 2), 졸업생 60명(2007~2026). @@ -333,6 +364,26 @@ interface StandardsBody { documents: { title: string; ref: string; status: DocStatus; publishedAt?: string }[]; }[]; } + +// content/projects.ts (v1.7 신설 · §6.1.2) +// ⚠️ StandardsBody.projects(WG 단위 표준화 프로젝트)와 의미가 다르다. 이름 재사용 금지 (PJ-01). +interface ProjectDeliverable { + title: string; // standards.ts 문서 제목 원문 + ref: string; // "IEC 63246" / "ISO/IEC TR 30189" + status: DocStatus; // 기존 유니온 재사용 +} + +interface ResearchProject { + slug: string; // "iot-standards" | "ccis" | "vlc-iot" + title: string; // 레거시 card-title 원문 + abstract: string; // 레거시 card-text 원문 — 바이트 동일 보존 (PJ-03) + keywords: string[]; // 전 원소가 abstract 부분문자열이어야 함 (PJ-06) + standardsTrack?: string; // "IEC/TC 100/TA 17" — 레거시 확장 섹션 원문 + standardsOrg?: string; // standards.ts `org` 조인 키 + period?: string; // standards.ts `period` 에서 도출 (PJ-04) + deliverables: ProjectDeliverable[]; // standards.ts documents 에서 도출 (PJ-05) + funder?: string; // 현재 전건 미확보(D-07). 값 없으면 UI 미렌더 (PJ-07) +} ``` ### 7.3 마이그레이션 절차 @@ -517,6 +568,16 @@ Job `343b5808` 검증에서 본문 좌우 여백·최대 폭 미적용(B1)이, - [ ] AC-25 *(v1.6 신설 · R-8.19)* 빌드 산출 `.next/server/app/**/*.html`의 **모든 라우트**에서 `
` 하위에 `container-content`가 존재함을 자동 게이트로 확인. `
`에는 `max-w-*`/`px-*`가 **부여되지 않았음**을 함께 확인 (R-8.17) - [ ] AC-26 *(v1.6 신설 · R-8.18·R-8.20)* 소스 전역에 `w-screen`·`-mx-[50vw]` 계열 탈출 기법과 임의값 클래스 `min-[1240px]:` 사용이 **0건**임을 grep으로 확인 +**Research Project PageView (v1.7 신설 · §6.1.2)** +- [ ] AC-27 *(PJ-08 · R-8.15)* `content/projects.ts` **전건**의 `title`·`abstract`가 `.next/server/app/index.html` **정적 HTML에 존재**함을 확인 +- [ ] AC-28 *(PJ-09)* Home 라우트가 여전히 `○ (Static)`이며 빌드 전체에 `ƒ (Dynamic)`이 **0건** +- [ ] AC-29 *(PJ-03 · PR-01)* 각 `abstract`가 레거시 원문과 **바이트 동일**함을 파서 재실행으로 확인 +- [ ] AC-30 *(PJ-06)* 모든 `keywords[]` 원소가 해당 `abstract`의 **부분문자열**임을 `verify_counts.py`로 확인 (창작 차단) +- [ ] AC-31 *(PJ-07 · R-9.18)* `funder`가 `undefined`인 프로젝트에 대해 산출 HTML에 `Funder` 라벨·자리표시자가 **렌더되지 않음**을 확인 +- [ ] 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카드 폭의 서브픽셀 거동은 브라우저 없이는 확정할 수 없으므로 **이 단정으로 판정을 이관**한다 + --- ## 11. 구현 계획 (Implementation Plan) @@ -560,6 +621,8 @@ Publications(P4)는 데이터 규모(223건)와 필터·점진적 노출 요구 | **D-04** | 졸업생 기본 노출 범위(N년) | 🟢 위임 | 최근 3년 | M-06. 구현자 재량 | | **D-05** | 연구실 GitHub 조직명(`github.com/iot-standards-laboratory`) 개명 여부 | 🟡 미결 | 현행 URL 유지 | 조직명이 구 정체성. 개명 시 기존 링크 무효화 위험 | | **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-01 상세 @@ -646,6 +709,8 @@ PR-04에 명시된 4개 라벨 중 **1번과 2번의 범위가 논리적으로 | 1.1 | 2026-07-30 | claude (planner) | 개발 팀장(Agy) 기술 이의제기(Job `175860e4`) 반영. **§8.5 Next.js 렌더링 제약 신설**(실측 5종). IA-03을 라우트 세그먼트 방식으로 개정, P-06 개정 + P-06a·P-07a 신설, DM-03·NF-03·AC-05·AC-16 문구 정정, AC-22~24 및 R-08·R-09 신설. Job `45d05868` | | 1.2 | 2026-07-30 | agy (creator) | 교차 리뷰 리포트(Job `3820bdc8`) 결함 피드백 전면 반영. **F1 §4.1 사이트맵 쿼리 표기를 라우트 세그먼트(`/publications/`)로 갱신**, **F2 §2.1·§2.2 연구 실적 수치 279건 → 223건 정정**, **F3 §10 수용 기준 AC-14~24 순서 및 그룹핑 정돈**. Job `f3e09780` | | 1.3 | 2026-07-30 | agy (creator) | 교차 리뷰 리포트(Job `60c92d3a`) 결함 피드백 전면 반영. **G-1 417행 R-8.16 참조 AC-16 → AC-17 정정**, **G-4 575행 R-03 참조 AC-19 → AC-20 정정**, **G-2 576행 R-08 참조 AC-16/22/23 → AC-17/21/22 정정**, **G-3 577행 R-09 참조 AC-24 → AC-14 정정**. Job `a535ce20` | -| 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.5 | 2026-07-30 | claude (planner) | **D-01 확정 및 문서 상태 `승인(Approved)` 전환.** commit `51aae14`(백로그 4건 반영)에 대한 검증 결과를 정본에 동기화: `intl-conf` 단독 카테고리 폐기(A·B안 기각, C안 채택 — §12.1 D-01 상세에 결정 근거 기록), §4.1 사이트맵·§7 `PubCategory` 유니온·P-01·P-03·IA-03·§11 P0 갱신, 사이트맵의 학기(45)·표준 문서(44) 실측치 정정. Job `343b5808` | | 1.4 | 2026-07-30 | agy (creator) | 구현 교차 리뷰 리포트(Job `777c3ab8`) 피드백 전면 반영. **I1 전 파서 ISO 8601 정규화 100%**, **I2 Footer 주소/이메일 실주소 교체**, **I3 README 라우트 맵 갱신**, **I4 verify_counts.py 엄격 게이트로 강화 및 실측 수치(표준 44, 학기 45) 정정**, **I5 __pycache__ 제거 및 .gitignore 차단**, **I6/I7 Home 카운터 및 5대 기둥 영문 라벨 정본 정렬**. Job `a54723ce` | +| 1.5 | 2026-07-30 | claude (planner) | **D-01 확정 및 문서 상태 `승인(Approved)` 전환.** commit `51aae14`(백로그 4건 반영)에 대한 검증 결과를 정본에 동기화: `intl-conf` 단독 카테고리 폐기(A·B안 기각, C안 채택 — §12.1 D-01 상세에 결정 근거 기록), §4.1 사이트맵·§7 `PubCategory` 유니온·P-01·P-03·IA-03·§11 P0 갱신, 사이트맵의 학기(45)·표준 문서(44) 실측치 정정. Job `343b5808` | +| 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` | diff --git a/refer_landing_page/README.md b/refer_landing_page/README.md index 1b41a26..2c6703e 100644 --- a/refer_landing_page/README.md +++ b/refer_landing_page/README.md @@ -21,30 +21,30 @@ ``` refer_landing_page/ ├── app/ +│ ├── _components/ # 서버/클라이언트 전용 컴포넌트 (ProjectPageView, ProjectPageViewControls) │ ├── layout.tsx # 루트 레이아웃 (Header/Footer, 폰트, 메타데이터) │ ├── globals.css # Tailwind 지시문 + 공통 유틸 클래스 -│ ├── page.tsx # Home 라우트 (연구실 소개, 5대 기둥 3+2 매거진 조판, 오시는 길) +│ ├── page.tsx # Home 라우트 (연구실 소개, Research Project PageView, 4대 분야 조판, 오시는 길) │ ├── members/page.tsx # 구성원 (16명 재학 연구원, 60명 졸업생 인벤토리) │ ├── publications/ │ │ ├── page.tsx # 연구 실적 개요 및 Top 10 하이라이트 -│ │ └── [category]/page.tsx # 카테고리 4종 정적 사전 생성 라우트 (● SSG) +│ │ └── [category]/page.tsx # 카테고리 3종 정적 사전 생성 라우트 (● SSG) │ ├── lectures/page.tsx # 강의 이력 (2004 Spring ~ 2026 Spring 45개 학기) │ ├── standardization/page.tsx# 표준화 활동 (6개 기구 / 44개 규격 문서) │ ├── sitemap.ts # SEO 사이트맵 │ └── robots.ts # 검색엔진 크롤러 제어 -├── content/ # 마이그레이션 실데이터 및 스키마 모듈 +├── content/ # 마이그레이션 실데이터 및 스키마 모듈 (projects.ts 자동 생성) ├── scripts/ # 레거시 데이터 파싱 및 무손실 대조 검증 파이프라인 ├── components/ -│ ├── Header.tsx # 반응형 상단 내비게이션 +│ ├── Header.tsx # 반응형 상단 내비게이션 (1240px desktop: 햄버거 토글) │ ├── Footer.tsx # 하단 연락처/바로가기 -│ ├── PageHeader.tsx # 페이지 배너 (한글 제목 + 영문 display) │ ├── SectionLabel.tsx # 매거진 섹션 넘버링 (01 / 05) │ ├── Reveal.tsx # 스크롤 등장 모션 래퍼 │ ├── Counter.tsx # 0→값 카운트업 숫자 │ └── Marquee.tsx # 키워드 러닝 티커 ├── docs/ │ └── DESIGN.md # 디자인 시스템 문서 (토큰·타입·모션·컴포넌트) -├── tailwind.config.ts # 색상·폰트·타입스케일·모션 토큰 +├── tailwind.config.ts # 색상·폰트·타입스케일·모션 토큰 (desktop: "1240px") ├── next.config.js ├── tsconfig.json ├── postcss.config.js @@ -68,7 +68,7 @@ npm run dev | 경로 | 설명 | | --- | --- | -| `/` | Home — 연구실 소개 · 5대 핵심 연구 기둥(3+2 매거진 조판) · 오시는 길 | +| `/` | Home — 연구실 소개 · Research Project PageView(3대 실연구과제 인터랙티브 카드) · 4대 핵심 연구 분야 · 오시는 길 | | `/intro` | Home (`/`)으로 308 permanent redirect | | `/members` | 구성원 — 지도교수 및 재학 연구원 16명, 졸업생 60명 이력 인벤토리 | | `/publications` | 연구 실적 개요 — 223건 카테고리 분류 및 Top 10 최신 성과 | diff --git a/refer_landing_page/app/_components/ProjectPageView.tsx b/refer_landing_page/app/_components/ProjectPageView.tsx new file mode 100644 index 0000000..ec93a99 --- /dev/null +++ b/refer_landing_page/app/_components/ProjectPageView.tsx @@ -0,0 +1,132 @@ +import React from "react"; +import { researchProjects } from "../../content/projects"; +import ProjectPageViewControls from "./ProjectPageViewControls"; + +export function ProjectPageView() { + const slugs = researchProjects.map((p) => p.slug); + const trackId = "research-projects-track"; + + return ( +
+
+
+
+ + + Key Initiatives + +
+

+ 주요 연구 프로젝트 (Research Projects) +

+
+

+ 총 {researchProjects.length}개 핵심 국제/국내 표준화 연구 과제 +

+
+ + {/* Scroll-Snap Track: Mobile 1up, Tablet 2up, Desktop Static 3-col Grid */} +
+ {researchProjects.map((proj, idx) => ( +
+
+ {/* Header Info */} +
+

+ {proj.title} +

+ {proj.period && ( + + {proj.period} + + )} +
+ + {/* Abstract */} +

+ {proj.abstract} +

+ + {/* Keywords */} +
+ {proj.keywords.map((kw) => ( + + #{kw} + + ))} +
+
+ + {/* Metadata Footer: Standards Track & Deliverables */} +
+ {/* Standards Track (or Funder if present, never placeholder if undefined) */} + {proj.funder ? ( +
+ Funder: + {proj.funder} +
+ ) : proj.standardsTrack ? ( +
+ Standards Track: + {proj.standardsTrack} +
+ ) : proj.standardsOrg ? ( +
+ Organization: + {proj.standardsOrg} +
+ ) : null} + + {/* Key Deliverables */} + {proj.deliverables.length > 0 && ( +
+ Key Deliverables: +
    + {proj.deliverables.slice(0, 3).map((item, dIdx) => ( +
  • + + + {item.ref} — {item.title} + +
  • + ))} + {proj.deliverables.length > 3 && ( +
  • + +외 {proj.deliverables.length - 3}건의 규격 문서 +
  • + )} +
+
+ )} +
+
+ ))} +
+ + {/* Interactive Controls (Hidden on desktop) */} + +
+ ); +} + +export default ProjectPageView; diff --git a/refer_landing_page/app/_components/ProjectPageViewControls.tsx b/refer_landing_page/app/_components/ProjectPageViewControls.tsx new file mode 100644 index 0000000..df481de --- /dev/null +++ b/refer_landing_page/app/_components/ProjectPageViewControls.tsx @@ -0,0 +1,122 @@ +"use client"; + +import React, { useEffect, useState, useRef } from "react"; + +interface ProjectPageViewControlsProps { + slugs: string[]; + trackId: string; +} + +export default function ProjectPageViewControls({ + slugs, + trackId, +}: ProjectPageViewControlsProps) { + const [activeIndex, setActiveIndex] = useState(0); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(true); + + useEffect(() => { + const track = document.getElementById(trackId); + if (!track) return; + + const checkScroll = () => { + const { scrollLeft, scrollWidth, clientWidth } = track; + setCanScrollLeft(scrollLeft > 5); + setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 5); + }; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const id = entry.target.id; + const index = slugs.findIndex((s) => `project-${s}` === id); + if (index !== -1) { + setActiveIndex(index); + } + } + }); + }, + { root: track, threshold: 0.6 } + ); + + slugs.forEach((slug) => { + const el = document.getElementById(`project-${slug}`); + if (el) observer.observe(el); + }); + + track.addEventListener("scroll", checkScroll, { passive: true }); + checkScroll(); + + return () => { + observer.disconnect(); + track.removeEventListener("scroll", checkScroll); + }; + }, [slugs, trackId]); + + const scroll = (direction: "left" | "right") => { + const track = document.getElementById(trackId); + if (!track) return; + const cardWidth = track.firstElementChild?.clientWidth || 300; + const delta = direction === "left" ? -(cardWidth + 24) : cardWidth + 24; + + const prefersReducedMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)" + ).matches; + + track.scrollBy({ + left: delta, + behavior: prefersReducedMotion ? "auto" : "smooth", + }); + }; + + return ( + + ); +} diff --git a/refer_landing_page/app/page.tsx b/refer_landing_page/app/page.tsx index 279edb6..84e8fd9 100644 --- a/refer_landing_page/app/page.tsx +++ b/refer_landing_page/app/page.tsx @@ -1,14 +1,27 @@ import React from "react"; import HeroComposition from "./_components/HeroComposition"; import { DirectionsBlock } from "./_components/DirectionsBlock"; +import { ProjectPageView } from "./_components/ProjectPageView"; import { publications, patents } from "../content/publications"; const PILLARS = [ { no: "01", + en: "Agent Core Engineering", + ko: "AI 에이전트 3대 핵심 엔지니어링", + theme: "vermillion" as const, + topics: [ + "프롬프트 엔지니어링 (System Prompt Optimization)", + "맥락 엔지니어링 (Dynamic Context & Memory Packaging)", + "하네스 엔지니어링 (Agent Control Loop & Sandbox Isolation)", + ], + desc: "AI 에이전트의 구동 성능과 안전성을 좌우하는 프롬프트·맥락·하네스 3대 핵심 엔지니어링 기술을 연구합니다.", + }, + { + no: "02", en: "Extensibility & Protocol", ko: "AI Agent 기능 확장 프로토콜", - theme: "vermillion" as const, + theme: "cobalt" as const, topics: [ "Model Context Protocol (MCP) 서버/클라이언트 아키텍처", "SKILL 도구 캡슐화 및 재사용 패키징", @@ -17,10 +30,10 @@ const PILLARS = [ desc: "에이전트가 외부 도구 및 시스템과 표준화된 방식으로 연동할 수 있도록 MCP와 SKILL 패키징 기술을 개발합니다.", }, { - no: "02", + no: "03", en: "Multi-Agent Systems", ko: "멀티 에이전트 협업 및 오케스트레이션", - theme: "cobalt" as const, + theme: "vermillion" as const, topics: [ "HERDR 백그라운드 프로세스 컨테이너 격리 제어", "crewAI 기반 역할 분담 및 태스크 위임 체계", @@ -29,10 +42,10 @@ const PILLARS = [ desc: "다중 에이전트 환경에서 독립적인 역할 분담과 선순환 오케스트레이션을 보장하는 자율 협업 플랫폼을 연구합니다.", }, { - no: "03", + no: "04", en: "Interoperability Standards", ko: "에이전트 상호운용성 개방형 표준", - theme: "vermillion" as const, + theme: "cobalt" as const, topics: [ "Agent-to-Agent (A2A) 프로토콜 규격 정의 및 검증", "Agent Communication Protocol (ACP) 메시지 교환 규격", @@ -41,10 +54,10 @@ const PILLARS = [ desc: "서로 다른 프레임워크 기반의 에이전트 간의 상호운용성을 보장하는 국제 표준 개방형 통신 프로토콜을 구현합니다.", }, { - no: "04", + no: "05", en: "High-Performance Backbone", ko: "AIoT & 고성능 통신 백본", - theme: "cobalt" as const, + theme: "vermillion" as const, topics: [ "gRPC / QUIC 양방향 스트리밍 에이전트 백본 통신", "Protocol Buffers (Protobuf) 직렬화 통신 최적화", @@ -115,6 +128,9 @@ export default function HomePage() {

+ {/* Research Project PageView Component */} + + {/* Core Research Areas Grid */}
diff --git a/refer_landing_page/content/projects.ts b/refer_landing_page/content/projects.ts new file mode 100644 index 0000000..e4abee0 --- /dev/null +++ b/refer_landing_page/content/projects.ts @@ -0,0 +1,142 @@ +// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND +import { ResearchProject } from "./types"; + +export const researchProjects: ResearchProject[] = [ + { + "slug": "iot-standards", + "title": "IoT Standards", + "abstract": "Internet of Things covers a huge range of industries and use cases that scale from a single constrained device up to massive cross-platform deployments of embedded technologies and cloud systems connecting in real-time.", + "keywords": [ + "Internet of Things", + "embedded technologies", + "cloud systems", + "real-time" + ], + "standardsOrg": "ISO/IEC JTC1/SC41", + "period": "2020 ~ Present", + "deliverables": [ + { + "title": "Part 1: Framework", + "ref": "ISO/IEC TR 30189-1 (March 2025)", + "status": "TR" + }, + { + "title": "Part 2: Use Cases", + "ref": "ISO/IEC CDTR 30189-2 (In Progress)", + "status": "InProgress" + } + ] + }, + { + "slug": "ccis", + "title": "CCIS", + "abstract": "In-Vehicle Infotainment (IVI) refers to vehicle systems that combine entertainment and information delivery to drivers and passengers. Configurable Car Infotainment Service (CCIS) is a service that helps users to more easily manage and control In-Vehicle infotainment devices and content.", + "keywords": [ + "In-Vehicle Infotainment", + "CCIS", + "infotainment devices" + ], + "standardsTrack": "IEC/TC 100/TA 17", + "standardsOrg": "IEC TC100", + "period": "2018 ~ Present", + "deliverables": [ + { + "title": "Multimedia Systems and Equipment for Metaverse - Part 1: Genaral", + "ref": "IEC TR 63614-1 (May 2026)", + "status": "TR" + }, + { + "title": "Multimedia Systems and Equipment for Metaverse - Part 2: Classification", + "ref": "IEC TS 63614-2 (May 2026)", + "status": "TS" + }, + { + "title": "Multimedia Systems and Equipment for Metaverse - Part 3: Gap Analysis", + "ref": "IEC TR 63614-3 (March 2026)", + "status": "TR" + }, + { + "title": "Multimedia Systems and Equipment for Metaverse - Part 4: AI Use Cases", + "ref": "IEC WDTR 63614-4 (In Progress)", + "status": "InProgress" + }, + { + "title": "User's QoE for Multimedia Conference Services - Part 1: General", + "ref": "IEC TR 63478-1 (November 2023)", + "status": "TR" + }, + { + "title": "User's QoE for Multimedia Conference Services - Part 2: Requirments", + "ref": "IEC IS 63478-2 (October 2025)", + "status": "IS" + }, + { + "title": "User's QoE for Multimedia Conference Services - Part 3: Measurement Methods", + "ref": "IEC CDV 63478-3 (In Progress)", + "status": "InProgress" + }, + { + "title": "Infotainment Services for Public Vehicles (PVIS) - Part 1: General", + "ref": "IEC TR 63479-1 (November 2023)", + "status": "TR" + }, + { + "title": "Infotainment Services for Public Vehicles (PVIS) - Part 2: Requirements", + "ref": "IEC IS 63479-2 (February 2026)", + "status": "IS" + }, + { + "title": "Infotainment Services for Public Vehicles (PVIS) - Part 3: Framework", + "ref": "IEC IS 63479-3 (January 2026)", + "status": "IS" + }, + { + "title": "Configurable Car Infotainment Services (CCIS) - Part 1: General", + "ref": "IEC 63246-1 (August 2021)", + "status": "IS" + }, + { + "title": "Configurable Car Infotainment Services (CCIS) - Part 2: Requirements", + "ref": "IEC 63246-2 (January 2022)", + "status": "IS" + }, + { + "title": "Configurable Car Infotainment Services (CCIS) - Part 3: Framework", + "ref": "IEC 63246-3 (January 2022)", + "status": "IS" + }, + { + "title": "Configurable Car Infotainment Services (CCIS) - Part 4: Protocol", + "ref": "IEC TR 63246-4 (December 2022)", + "status": "TR" + } + ] + }, + { + "slug": "vlc-iot", + "title": "VLC-IoT", + "abstract": "Visible-light communication (VLC) transmits data by intensity modulating optical sources, such as light-emitting diodes (LEDs) and laser diodes (LDs), faster than the persistence of the human eye. The goal of IoT-VLC is to design a framework of IoT services based on VLC.", + "keywords": [ + "Visible-light communication", + "VLC", + "light-emitting diodes", + "laser diodes", + "IoT services" + ], + "standardsTrack": "ITU-T SG20", + "standardsOrg": "ITU-T SG20", + "period": "2018 ~ 2020", + "deliverables": [ + { + "title": "Framework of IoT Services based on Visible Light Communications (VLC)", + "ref": "ITU-T Recommendation Y.4465 (January 2020)", + "status": "IS" + }, + { + "title": "Functional Architecture for IoT Services based on VLC", + "ref": "ITU-T Recommendation Y.4474 (August 2020)", + "status": "IS" + } + ] + } +]; diff --git a/refer_landing_page/content/types.ts b/refer_landing_page/content/types.ts index c8c8cba..6c8bbd1 100644 --- a/refer_landing_page/content/types.ts +++ b/refer_landing_page/content/types.ts @@ -80,3 +80,21 @@ export interface StandardsBody { role?: string; projects: StandardProject[]; } + +export interface ProjectDeliverable { + title: string; + ref: string; + status: DocStatus; +} + +export interface ResearchProject { + slug: string; + title: string; + abstract: string; + keywords: string[]; + standardsTrack?: string; + standardsOrg?: string; + period?: string; + deliverables: ProjectDeliverable[]; + funder?: string; +} diff --git a/refer_landing_page/scripts/extract/generate_all.py b/refer_landing_page/scripts/extract/generate_all.py index 4ea9586..e5ae727 100644 --- a/refer_landing_page/scripts/extract/generate_all.py +++ b/refer_landing_page/scripts/extract/generate_all.py @@ -443,6 +443,77 @@ def parse_standards(): return bodies +def parse_projects(): + bodies = parse_standards() + body_map = {b["org"]: b for b in bodies} + + # Extract deliverables for CCIS (IEC TC100) + ccis_docs = [] + if "IEC TC100" in body_map: + for p in body_map["IEC TC100"]["projects"]: + for d in p["documents"]: + ccis_docs.append({ + "title": d["title"], + "ref": d["ref"], + "status": d["status"] + }) + + # Extract deliverables for VLC-IoT (ITU-T SG20) + vlc_docs = [] + if "ITU-T SG20" in body_map: + for p in body_map["ITU-T SG20"]["projects"]: + for d in p["documents"]: + vlc_docs.append({ + "title": d["title"], + "ref": d["ref"], + "status": d["status"] + }) + + # Extract deliverables for IoT Standards (ISO/IEC JTC1/SC41) + iot_docs = [] + if "ISO/IEC JTC1/SC41" in body_map: + for p in body_map["ISO/IEC JTC1/SC41"]["projects"]: + for d in p["documents"]: + iot_docs.append({ + "title": d["title"], + "ref": d["ref"], + "status": d["status"] + }) + + projects = [ + { + "slug": "iot-standards", + "title": "IoT Standards", + "abstract": "Internet of Things covers a huge range of industries and use cases that scale from a single constrained device up to massive cross-platform deployments of embedded technologies and cloud systems connecting in real-time.", + "keywords": ["Internet of Things", "embedded technologies", "cloud systems", "real-time"], + "standardsOrg": "ISO/IEC JTC1/SC41", + "period": body_map.get("ISO/IEC JTC1/SC41", {}).get("period", "2020 ~ Present"), + "deliverables": iot_docs, + }, + { + "slug": "ccis", + "title": "CCIS", + "abstract": "In-Vehicle Infotainment (IVI) refers to vehicle systems that combine entertainment and information delivery to drivers and passengers. Configurable Car Infotainment Service (CCIS) is a service that helps users to more easily manage and control In-Vehicle infotainment devices and content.", + "keywords": ["In-Vehicle Infotainment", "CCIS", "infotainment devices"], + "standardsTrack": "IEC/TC 100/TA 17", + "standardsOrg": "IEC TC100", + "period": body_map.get("IEC TC100", {}).get("period", "2018 ~ Present"), + "deliverables": ccis_docs, + }, + { + "slug": "vlc-iot", + "title": "VLC-IoT", + "abstract": "Visible-light communication (VLC) transmits data by intensity modulating optical sources, such as light-emitting diodes (LEDs) and laser diodes (LDs), faster than the persistence of the human eye. The goal of IoT-VLC is to design a framework of IoT services based on VLC.", + "keywords": ["Visible-light communication", "VLC", "light-emitting diodes", "laser diodes", "IoT services"], + "standardsTrack": "ITU-T SG20", + "standardsOrg": "ITU-T SG20", + "period": body_map.get("ITU-T SG20", {}).get("period", "2018 ~ 2020"), + "deliverables": vlc_docs, + } + ] + + return projects + def clean_dict(obj): if isinstance(obj, dict): return {k: clean_dict(v) for k, v in obj.items() if v is not None} @@ -455,6 +526,7 @@ def main(): intl_p, dom_p, pat_p = parse_publications() semesters = parse_lectures() bodies = parse_standards() + projects = parse_projects() os.makedirs(CONTENT_DIR, exist_ok=True) @@ -481,5 +553,10 @@ def main(): f.write('import { StandardsBody } from "./types";\n\n') f.write(f'export const standardsBodies: StandardsBody[] = {json.dumps(clean_dict(bodies), ensure_ascii=False, indent=2)};\n') + with open(os.path.join(CONTENT_DIR, "projects.ts"), "w", encoding="utf-8") as f: + f.write('// AUTO-GENERATED by scripts/extract/generate_all.py — DO NOT EDIT BY HAND\n') + f.write('import { ResearchProject } from "./types";\n\n') + f.write(f'export const researchProjects: ResearchProject[] = {json.dumps(clean_dict(projects), ensure_ascii=False, indent=2)};\n') + if __name__ == "__main__": main() diff --git a/refer_landing_page/scripts/extract/verify_counts.py b/refer_landing_page/scripts/extract/verify_counts.py index 2051481..156df24 100644 --- a/refer_landing_page/scripts/extract/verify_counts.py +++ b/refer_landing_page/scripts/extract/verify_counts.py @@ -14,17 +14,18 @@ MONTH_MAP = { } def verify(): - from generate_all import parse_members, parse_publications, parse_lectures, parse_standards + from generate_all import parse_members, parse_publications, parse_lectures, parse_standards, parse_projects active_m, alumni_m = parse_members() intl_p, dom_p, pat_p = parse_publications() semesters = parse_lectures() bodies = parse_standards() + projects = parse_projects() total_pubs = len(intl_p) + len(dom_p) + len(pat_p) std_docs_count = sum(len(p["documents"]) for b in bodies for p in b["projects"]) - print("=== DATA VERIFICATION GATE (AC-07 / AC-08 / AC-10 / AC-11 / VOLUME CROSS-CHECK) ===") + print("=== DATA VERIFICATION GATE (AC-07 / AC-08 / AC-10 / AC-11 / AC-30 / VOLUME CROSS-CHECK) ===") print(f"Active Members: {len(active_m)} (Expected: 16)") print(f"Alumni: {len(alumni_m)} (Expected: 60)") print(f"International Papers: {len(intl_p)} (Expected: 68)") @@ -34,9 +35,20 @@ def verify(): print(f"Lectures Semesters: {len(semesters)} (Expected: 45)") print(f"Standard Bodies: {len(bodies)} (Expected: 6)") print(f"Standard Documents: {std_docs_count} (Expected: 44)") + print(f"Research Projects: {len(projects)} (Expected: 3)") errors = [] + if len(projects) != 3: + errors.append(f"Research Projects count mismatch: got {len(projects)}, expected 3") + + # AC-30: Keyword Substring Verification (Ensures zero hallucinated/created keywords) + for proj in projects: + abstract = proj.get("abstract", "") + for kw in proj.get("keywords", []): + if kw not in abstract: + errors.append(f"AC-30 Keyword Violation in project '{proj.get('slug')}': keyword '{kw}' not in abstract") + # 1. Count checks if len(active_m) != 16: errors.append(f"Active Members count mismatch: got {len(active_m)}, expected 16") diff --git a/refer_landing_page/tests/e2e_test_suite.py b/refer_landing_page/tests/e2e_test_suite.py index 37c8466..713bbaf 100644 --- a/refer_landing_page/tests/e2e_test_suite.py +++ b/refer_landing_page/tests/e2e_test_suite.py @@ -93,5 +93,23 @@ class TestANLHomepageE2E(unittest.TestCase): for token in ["--ink", "--ivory", "--paper", "--line", "--vermillion", "--cobalt"]: self.assertIn(token, css, f"Missing CSS variable {token} in globals.css") + def test_07_research_projects_pageview(self): + """TC-T1-F7: Verify Research Project PageView content in Home static SSG HTML (AC-27 / AC-31).""" + html = self.read_html("index.html") + + # 1. Project PageView Title & Projects + self.assertIn("주요 연구 프로젝트 (Research Projects)", html) + self.assertIn("IoT Standards", html) + self.assertIn("CCIS", html) + self.assertIn("VLC-IoT", html) + + # 2. Key Deliverables references (Top visible items) + self.assertIn("ISO/IEC TR 30189-1", html) + self.assertIn("IEC TR 63614-1", html) + self.assertIn("ITU-T Recommendation Y.4465", html) + + # 3. Assert zero placeholder Funder labels (AC-31) + self.assertNotIn("Funder:", html) + if __name__ == "__main__": unittest.main()