feat: implement Research Project PageView (v1.8) with static SSG pre-render and E2E gates
This commit is contained in:
+70
-5
@@ -4,10 +4,10 @@
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
| :--- | :--- |
|
| :--- | :--- |
|
||||||
| **문서 ID** | `REQ-ANL-WEB-001` |
|
| **문서 ID** | `REQ-ANL-WEB-001` |
|
||||||
| **버전** | `1.6` |
|
| **버전** | `1.8` |
|
||||||
| **작성일** | 2026-07-30 |
|
| **작성일** | 2026-07-30 |
|
||||||
| **작성자** | claude (`canary-projects-landing-page-creator-claude`) · Role: planner |
|
| **작성자** | 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), 지도교수 |
|
| **대상 독자** | 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) |
|
| **선행 문서** | [`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 애플리케이션) 전체 |
|
| **적용 범위** | `refer_landing_page/` (Next.js 14 App Router 애플리케이션) 전체 |
|
||||||
@@ -143,7 +143,7 @@ PR-03에 따라 **연구실 소개 · 주요 연구 분야 소개 · 오시는
|
|||||||
| ID | 요구사항 | 수준 |
|
| ID | 요구사항 | 수준 |
|
||||||
| :--- | :--- | :-- |
|
| :--- | :--- | :-- |
|
||||||
| H-01 | `app/page.tsx`를 신규 생성하고, 현행 `app/intro/page.tsx`의 섹션 구성(커버 스프레드 → 지표 카운터 → Mission 풀쿼트 → 핵심 연구 분야 → Focus Areas)을 이관한다 | MUST |
|
| 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-03 | **[연구실 소개]** 연구실 개요·지도교수·비전을 서술하는 블록을 배치하고, 상세는 `/members`로 연결한다 | MUST |
|
||||||
| H-04 | **[주요 연구 분야]** §5의 핵심 연구 분야 전문을 매거진 조판으로 노출한다 | MUST |
|
| H-04 | **[주요 연구 분야]** §5의 핵심 연구 분야 전문을 매거진 조판으로 노출한다 | MUST |
|
||||||
| H-05 | **[오시는 길]** 아래 §6.1.1 요구사항을 충족하는 블록을 배치한다 | MUST |
|
| H-05 | **[오시는 길]** 아래 §6.1.1 요구사항을 충족하는 블록을 배치한다 | MUST |
|
||||||
@@ -168,6 +168,37 @@ PR-03에 따라 **연구실 소개 · 주요 연구 분야 소개 · 오시는
|
|||||||
| H-17 | 주소는 시맨틱 `<address>` 요소로 마크업한다 | MUST |
|
| H-17 | 주소는 시맨틱 `<address>` 요소로 마크업한다 | MUST |
|
||||||
| H-18 | 대중교통·주차 안내는 **레거시에 데이터가 없으므로 이번 범위에서 제외**한다. 필요 시 지도교수 확인 후 별도 잡으로 추가 | MUST |
|
| H-18 | 대중교통·주차 안내는 **레거시에 데이터가 없으므로 이번 범위에서 제외**한다. 필요 시 지도교수 확인 후 별도 잡으로 추가 | MUST |
|
||||||
|
|
||||||
|
#### 6.1.2 Research Project PageView *(v1.7 신설)*
|
||||||
|
|
||||||
|
Home의 슬로건 히어로를 **실제 연구 프로젝트 카드 뷰**로 전환한다(H-02 개정).
|
||||||
|
|
||||||
|
**레거시 실측 — 실재하는 프로젝트 3건**: `ISL-2026/index.html` `<section class="portfolio-block skills">`의 `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()`는 `<Suspense>` 경계를 요구하며 경계 안쪽 콘텐츠가 정적 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"` 로 분리한다. 페이지네이션 인디케이터는 `<a href="#project-…">` 앵커로 두어 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`, 카드 제목은 `<h3>` | 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 적용
|
### 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).
|
**레거시 실측**: 재학 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 }[];
|
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 마이그레이션 절차
|
### 7.3 마이그레이션 절차
|
||||||
@@ -517,6 +568,16 @@ Job `343b5808` 검증에서 본문 좌우 여백·최대 폭 미적용(B1)이,
|
|||||||
- [ ] AC-25 *(v1.6 신설 · R-8.19)* 빌드 산출 `.next/server/app/**/*.html`의 **모든 라우트**에서 `<main>` 하위에 `container-content`가 존재함을 자동 게이트로 확인. `<main>`에는 `max-w-*`/`px-*`가 **부여되지 않았음**을 함께 확인 (R-8.17)
|
- [ ] AC-25 *(v1.6 신설 · R-8.19)* 빌드 산출 `.next/server/app/**/*.html`의 **모든 라우트**에서 `<main>` 하위에 `container-content`가 존재함을 자동 게이트로 확인. `<main>`에는 `max-w-*`/`px-*`가 **부여되지 않았음**을 함께 확인 (R-8.17)
|
||||||
- [ ] AC-26 *(v1.6 신설 · R-8.18·R-8.20)* 소스 전역에 `w-screen`·`-mx-[50vw]` 계열 탈출 기법과 임의값 클래스 `min-[1240px]:` 사용이 **0건**임을 grep으로 확인
|
- [ ] 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)
|
## 11. 구현 계획 (Implementation Plan)
|
||||||
@@ -560,6 +621,8 @@ Publications(P4)는 데이터 규모(223건)와 필터·점진적 노출 요구
|
|||||||
| **D-04** | 졸업생 기본 노출 범위(N년) | 🟢 위임 | 최근 3년 | M-06. 구현자 재량 |
|
| **D-04** | 졸업생 기본 노출 범위(N년) | 🟢 위임 | 최근 3년 | M-06. 구현자 재량 |
|
||||||
| **D-05** | 연구실 GitHub 조직명(`github.com/iot-standards-laboratory`) 개명 여부 | 🟡 미결 | 현행 URL 유지 | 조직명이 구 정체성. 개명 시 기존 링크 무효화 위험 |
|
| **D-05** | 연구실 GitHub 조직명(`github.com/iot-standards-laboratory`) 개명 여부 | 🟡 미결 | 현행 URL 유지 | 조직명이 구 정체성. 개명 시 기존 링크 무효화 위험 |
|
||||||
| **D-06** | 본문 여백·최대 폭 컨테이너를 **어느 계층에 부여**할 것인가 | ✅ **확정** (v1.6, 2026-07-30) | — | **결정: `<main>` 일괄 부여안 기각 → 라우트 래퍼 방식 + 자동 게이트 채택.** Planner 초안(`<main>`에 `container-content`)은 1행 수정·누락 불가라는 장점이 있으나, 개발 팀장 이의제기(Job `2560771b`)대로 **full-bleed 밴드·마퀴 티커를 구조적으로 차단**한다. 래퍼 방식의 유일한 약점(신규 라우트 누락)은 **AC-25 게이트로 상쇄**되므로 래퍼 방식이 우월하다. → R-8.17~R-8.19 |
|
| **D-06** | 본문 여백·최대 폭 컨테이너를 **어느 계층에 부여**할 것인가 | ✅ **확정** (v1.6, 2026-07-30) | — | **결정: `<main>` 일괄 부여안 기각 → 라우트 래퍼 방식 + 자동 게이트 채택.** Planner 초안(`<main>`에 `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 상세
|
#### 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.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/<slug>`)로 갱신**, **F2 §2.1·§2.2 연구 실적 수치 279건 → 223건 정정**, **F3 §10 수용 기준 AC-14~24 순서 및 그룹핑 정돈**. Job `f3e09780` |
|
| 1.2 | 2026-07-30 | agy (creator) | 교차 리뷰 리포트(Job `3820bdc8`) 결함 피드백 전면 반영. **F1 §4.1 사이트맵 쿼리 표기를 라우트 세그먼트(`/publications/<slug>`)로 갱신**, **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.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) — `<main>` 일괄 부여안을 기각하고 3계층 분리(전체 폭 `<main>` / 라우트 래퍼 `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.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) — `<main>` 일괄 부여안을 기각하고 3계층 분리(전체 폭 `<main>` / 라우트 래퍼 `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` |
|
||||||
|
|||||||
@@ -21,30 +21,30 @@
|
|||||||
```
|
```
|
||||||
refer_landing_page/
|
refer_landing_page/
|
||||||
├── app/
|
├── app/
|
||||||
|
│ ├── _components/ # 서버/클라이언트 전용 컴포넌트 (ProjectPageView, ProjectPageViewControls)
|
||||||
│ ├── layout.tsx # 루트 레이아웃 (Header/Footer, 폰트, 메타데이터)
|
│ ├── layout.tsx # 루트 레이아웃 (Header/Footer, 폰트, 메타데이터)
|
||||||
│ ├── globals.css # Tailwind 지시문 + 공통 유틸 클래스
|
│ ├── globals.css # Tailwind 지시문 + 공통 유틸 클래스
|
||||||
│ ├── page.tsx # Home 라우트 (연구실 소개, 5대 기둥 3+2 매거진 조판, 오시는 길)
|
│ ├── page.tsx # Home 라우트 (연구실 소개, Research Project PageView, 4대 분야 조판, 오시는 길)
|
||||||
│ ├── members/page.tsx # 구성원 (16명 재학 연구원, 60명 졸업생 인벤토리)
|
│ ├── members/page.tsx # 구성원 (16명 재학 연구원, 60명 졸업생 인벤토리)
|
||||||
│ ├── publications/
|
│ ├── publications/
|
||||||
│ │ ├── page.tsx # 연구 실적 개요 및 Top 10 하이라이트
|
│ │ ├── page.tsx # 연구 실적 개요 및 Top 10 하이라이트
|
||||||
│ │ └── [category]/page.tsx # 카테고리 4종 정적 사전 생성 라우트 (● SSG)
|
│ │ └── [category]/page.tsx # 카테고리 3종 정적 사전 생성 라우트 (● SSG)
|
||||||
│ ├── lectures/page.tsx # 강의 이력 (2004 Spring ~ 2026 Spring 45개 학기)
|
│ ├── lectures/page.tsx # 강의 이력 (2004 Spring ~ 2026 Spring 45개 학기)
|
||||||
│ ├── standardization/page.tsx# 표준화 활동 (6개 기구 / 44개 규격 문서)
|
│ ├── standardization/page.tsx# 표준화 활동 (6개 기구 / 44개 규격 문서)
|
||||||
│ ├── sitemap.ts # SEO 사이트맵
|
│ ├── sitemap.ts # SEO 사이트맵
|
||||||
│ └── robots.ts # 검색엔진 크롤러 제어
|
│ └── robots.ts # 검색엔진 크롤러 제어
|
||||||
├── content/ # 마이그레이션 실데이터 및 스키마 모듈
|
├── content/ # 마이그레이션 실데이터 및 스키마 모듈 (projects.ts 자동 생성)
|
||||||
├── scripts/ # 레거시 데이터 파싱 및 무손실 대조 검증 파이프라인
|
├── scripts/ # 레거시 데이터 파싱 및 무손실 대조 검증 파이프라인
|
||||||
├── components/
|
├── components/
|
||||||
│ ├── Header.tsx # 반응형 상단 내비게이션
|
│ ├── Header.tsx # 반응형 상단 내비게이션 (1240px desktop: 햄버거 토글)
|
||||||
│ ├── Footer.tsx # 하단 연락처/바로가기
|
│ ├── Footer.tsx # 하단 연락처/바로가기
|
||||||
│ ├── PageHeader.tsx # 페이지 배너 (한글 제목 + 영문 display)
|
|
||||||
│ ├── SectionLabel.tsx # 매거진 섹션 넘버링 (01 / 05)
|
│ ├── SectionLabel.tsx # 매거진 섹션 넘버링 (01 / 05)
|
||||||
│ ├── Reveal.tsx # 스크롤 등장 모션 래퍼
|
│ ├── Reveal.tsx # 스크롤 등장 모션 래퍼
|
||||||
│ ├── Counter.tsx # 0→값 카운트업 숫자
|
│ ├── Counter.tsx # 0→값 카운트업 숫자
|
||||||
│ └── Marquee.tsx # 키워드 러닝 티커
|
│ └── Marquee.tsx # 키워드 러닝 티커
|
||||||
├── docs/
|
├── docs/
|
||||||
│ └── DESIGN.md # 디자인 시스템 문서 (토큰·타입·모션·컴포넌트)
|
│ └── DESIGN.md # 디자인 시스템 문서 (토큰·타입·모션·컴포넌트)
|
||||||
├── tailwind.config.ts # 색상·폰트·타입스케일·모션 토큰
|
├── tailwind.config.ts # 색상·폰트·타입스케일·모션 토큰 (desktop: "1240px")
|
||||||
├── next.config.js
|
├── next.config.js
|
||||||
├── tsconfig.json
|
├── tsconfig.json
|
||||||
├── postcss.config.js
|
├── 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 |
|
| `/intro` | Home (`/`)으로 308 permanent redirect |
|
||||||
| `/members` | 구성원 — 지도교수 및 재학 연구원 16명, 졸업생 60명 이력 인벤토리 |
|
| `/members` | 구성원 — 지도교수 및 재학 연구원 16명, 졸업생 60명 이력 인벤토리 |
|
||||||
| `/publications` | 연구 실적 개요 — 223건 카테고리 분류 및 Top 10 최신 성과 |
|
| `/publications` | 연구 실적 개요 — 223건 카테고리 분류 및 Top 10 최신 성과 |
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<section className="space-y-6">
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4 border-b border-line pb-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-3 mb-1">
|
||||||
|
<span className="h-px w-8 bg-vermillion"></span>
|
||||||
|
<span className="font-mono text-xs tracking-widest uppercase text-vermillion font-bold">
|
||||||
|
Key Initiatives
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="font-serif text-2xl sm:text-3xl font-normal text-ink">
|
||||||
|
주요 연구 프로젝트 (Research Projects)
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-ink-mute font-mono">
|
||||||
|
총 {researchProjects.length}개 핵심 국제/국내 표준화 연구 과제
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scroll-Snap Track: Mobile 1up, Tablet 2up, Desktop Static 3-col Grid */}
|
||||||
|
<div
|
||||||
|
id={trackId}
|
||||||
|
tabIndex={0}
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="carousel"
|
||||||
|
aria-label="연구 프로젝트 목록"
|
||||||
|
className="flex gap-6 overflow-x-auto snap-x snap-mandatory scroll-smooth [scrollbar-width:none] [&::-webkit-scrollbar]:hidden desktop:grid desktop:grid-cols-3 desktop:overflow-visible focus:outline-none focus:ring-1 focus:ring-vermillion/50 rounded"
|
||||||
|
>
|
||||||
|
{researchProjects.map((proj, idx) => (
|
||||||
|
<article
|
||||||
|
key={proj.slug}
|
||||||
|
id={`project-${proj.slug}`}
|
||||||
|
role="group"
|
||||||
|
aria-roledescription="slide"
|
||||||
|
aria-label={`${researchProjects.length}개 중 ${idx + 1}번째 프로젝트`}
|
||||||
|
className="snap-start shrink-0 min-w-0 w-full md:w-[calc(50%-12px)] desktop:w-full flex flex-col justify-between bg-paper p-6 rounded-lg border border-line hover:border-vermillion/60 transition-colors space-y-5 break-words [word-break:keep-all]"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Header Info */}
|
||||||
|
<div className="flex items-start justify-between gap-3 border-b border-line/60 pb-3">
|
||||||
|
<h3 className="font-serif text-xl font-normal text-ink leading-snug">
|
||||||
|
{proj.title}
|
||||||
|
</h3>
|
||||||
|
{proj.period && (
|
||||||
|
<span className="font-mono text-xs text-vermillion bg-ivory border border-line px-2 py-0.5 rounded shrink-0">
|
||||||
|
{proj.period}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Abstract */}
|
||||||
|
<p className="text-sm leading-relaxed text-ink/80">
|
||||||
|
{proj.abstract}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Keywords */}
|
||||||
|
<div className="flex flex-wrap gap-1.5 pt-1">
|
||||||
|
{proj.keywords.map((kw) => (
|
||||||
|
<span
|
||||||
|
key={kw}
|
||||||
|
className="font-mono text-[0.72rem] text-ink-mute bg-ivory border border-line px-2 py-0.5 rounded"
|
||||||
|
>
|
||||||
|
#{kw}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Metadata Footer: Standards Track & Deliverables */}
|
||||||
|
<div className="space-y-3 pt-3 border-t border-line/60 text-xs font-mono">
|
||||||
|
{/* Standards Track (or Funder if present, never placeholder if undefined) */}
|
||||||
|
{proj.funder ? (
|
||||||
|
<div className="flex items-baseline justify-between gap-2">
|
||||||
|
<span className="text-ink-mute">Funder:</span>
|
||||||
|
<span className="text-ink font-semibold">{proj.funder}</span>
|
||||||
|
</div>
|
||||||
|
) : proj.standardsTrack ? (
|
||||||
|
<div className="flex items-baseline justify-between gap-2">
|
||||||
|
<span className="text-ink-mute">Standards Track:</span>
|
||||||
|
<span className="text-cobalt font-bold">{proj.standardsTrack}</span>
|
||||||
|
</div>
|
||||||
|
) : proj.standardsOrg ? (
|
||||||
|
<div className="flex items-baseline justify-between gap-2">
|
||||||
|
<span className="text-ink-mute">Organization:</span>
|
||||||
|
<span className="text-cobalt font-bold">{proj.standardsOrg}</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Key Deliverables */}
|
||||||
|
{proj.deliverables.length > 0 && (
|
||||||
|
<div className="space-y-1.5 pt-1">
|
||||||
|
<span className="text-ink-mute block">Key Deliverables:</span>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{proj.deliverables.slice(0, 3).map((item, dIdx) => (
|
||||||
|
<li
|
||||||
|
key={dIdx}
|
||||||
|
className="flex items-start gap-1.5 text-[0.75rem] text-ink/90"
|
||||||
|
>
|
||||||
|
<span className="text-vermillion font-bold">•</span>
|
||||||
|
<span className="line-clamp-2 leading-tight">
|
||||||
|
<strong className="font-semibold text-ink">{item.ref}</strong> — {item.title}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{proj.deliverables.length > 3 && (
|
||||||
|
<li className="text-[0.7rem] text-ink-mute italic">
|
||||||
|
+외 {proj.deliverables.length - 3}건의 규격 문서
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Interactive Controls (Hidden on desktop) */}
|
||||||
|
<ProjectPageViewControls slugs={slugs} trackId={trackId} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectPageView;
|
||||||
@@ -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 (
|
||||||
|
<div className="flex items-center justify-between mt-6 pt-4 border-t border-line desktop:hidden">
|
||||||
|
{/* Indicator dots using semantic anchor links */}
|
||||||
|
<div className="flex items-center gap-2" role="tablist" aria-label="프로젝트 위치">
|
||||||
|
{slugs.map((slug, idx) => (
|
||||||
|
<a
|
||||||
|
key={slug}
|
||||||
|
href={`#project-${slug}`}
|
||||||
|
role="tab"
|
||||||
|
aria-selected={activeIndex === idx}
|
||||||
|
aria-label={`${slugs.length}개 중 ${idx + 1}번째 프로젝트로 이동`}
|
||||||
|
className={`h-2.5 rounded-full transition-all duration-300 ${
|
||||||
|
activeIndex === idx
|
||||||
|
? "w-8 bg-vermillion"
|
||||||
|
: "w-2.5 bg-line hover:bg-ink-mute"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation Buttons */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => scroll("left")}
|
||||||
|
disabled={!canScrollLeft}
|
||||||
|
aria-label="이전 프로젝트"
|
||||||
|
aria-controls={trackId}
|
||||||
|
className="inline-flex items-center justify-center w-9 h-9 border border-line bg-paper text-ink transition hover:border-vermillion hover:text-vermillion disabled:opacity-30 disabled:pointer-events-none rounded"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => scroll("right")}
|
||||||
|
disabled={!canScrollRight}
|
||||||
|
aria-label="다음 프로젝트"
|
||||||
|
aria-controls={trackId}
|
||||||
|
className="inline-flex items-center justify-center w-9 h-9 border border-line bg-paper text-ink transition hover:border-vermillion hover:text-vermillion disabled:opacity-30 disabled:pointer-events-none rounded"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import HeroComposition from "./_components/HeroComposition";
|
import HeroComposition from "./_components/HeroComposition";
|
||||||
import { DirectionsBlock } from "./_components/DirectionsBlock";
|
import { DirectionsBlock } from "./_components/DirectionsBlock";
|
||||||
|
import { ProjectPageView } from "./_components/ProjectPageView";
|
||||||
import { publications, patents } from "../content/publications";
|
import { publications, patents } from "../content/publications";
|
||||||
|
|
||||||
const PILLARS = [
|
const PILLARS = [
|
||||||
{
|
{
|
||||||
no: "01",
|
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",
|
en: "Extensibility & Protocol",
|
||||||
ko: "AI Agent 기능 확장 프로토콜",
|
ko: "AI Agent 기능 확장 프로토콜",
|
||||||
theme: "vermillion" as const,
|
theme: "cobalt" as const,
|
||||||
topics: [
|
topics: [
|
||||||
"Model Context Protocol (MCP) 서버/클라이언트 아키텍처",
|
"Model Context Protocol (MCP) 서버/클라이언트 아키텍처",
|
||||||
"SKILL 도구 캡슐화 및 재사용 패키징",
|
"SKILL 도구 캡슐화 및 재사용 패키징",
|
||||||
@@ -17,10 +30,10 @@ const PILLARS = [
|
|||||||
desc: "에이전트가 외부 도구 및 시스템과 표준화된 방식으로 연동할 수 있도록 MCP와 SKILL 패키징 기술을 개발합니다.",
|
desc: "에이전트가 외부 도구 및 시스템과 표준화된 방식으로 연동할 수 있도록 MCP와 SKILL 패키징 기술을 개발합니다.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
no: "02",
|
no: "03",
|
||||||
en: "Multi-Agent Systems",
|
en: "Multi-Agent Systems",
|
||||||
ko: "멀티 에이전트 협업 및 오케스트레이션",
|
ko: "멀티 에이전트 협업 및 오케스트레이션",
|
||||||
theme: "cobalt" as const,
|
theme: "vermillion" as const,
|
||||||
topics: [
|
topics: [
|
||||||
"HERDR 백그라운드 프로세스 컨테이너 격리 제어",
|
"HERDR 백그라운드 프로세스 컨테이너 격리 제어",
|
||||||
"crewAI 기반 역할 분담 및 태스크 위임 체계",
|
"crewAI 기반 역할 분담 및 태스크 위임 체계",
|
||||||
@@ -29,10 +42,10 @@ const PILLARS = [
|
|||||||
desc: "다중 에이전트 환경에서 독립적인 역할 분담과 선순환 오케스트레이션을 보장하는 자율 협업 플랫폼을 연구합니다.",
|
desc: "다중 에이전트 환경에서 독립적인 역할 분담과 선순환 오케스트레이션을 보장하는 자율 협업 플랫폼을 연구합니다.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
no: "03",
|
no: "04",
|
||||||
en: "Interoperability Standards",
|
en: "Interoperability Standards",
|
||||||
ko: "에이전트 상호운용성 개방형 표준",
|
ko: "에이전트 상호운용성 개방형 표준",
|
||||||
theme: "vermillion" as const,
|
theme: "cobalt" as const,
|
||||||
topics: [
|
topics: [
|
||||||
"Agent-to-Agent (A2A) 프로토콜 규격 정의 및 검증",
|
"Agent-to-Agent (A2A) 프로토콜 규격 정의 및 검증",
|
||||||
"Agent Communication Protocol (ACP) 메시지 교환 규격",
|
"Agent Communication Protocol (ACP) 메시지 교환 규격",
|
||||||
@@ -41,10 +54,10 @@ const PILLARS = [
|
|||||||
desc: "서로 다른 프레임워크 기반의 에이전트 간의 상호운용성을 보장하는 국제 표준 개방형 통신 프로토콜을 구현합니다.",
|
desc: "서로 다른 프레임워크 기반의 에이전트 간의 상호운용성을 보장하는 국제 표준 개방형 통신 프로토콜을 구현합니다.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
no: "04",
|
no: "05",
|
||||||
en: "High-Performance Backbone",
|
en: "High-Performance Backbone",
|
||||||
ko: "AIoT & 고성능 통신 백본",
|
ko: "AIoT & 고성능 통신 백본",
|
||||||
theme: "cobalt" as const,
|
theme: "vermillion" as const,
|
||||||
topics: [
|
topics: [
|
||||||
"gRPC / QUIC 양방향 스트리밍 에이전트 백본 통신",
|
"gRPC / QUIC 양방향 스트리밍 에이전트 백본 통신",
|
||||||
"Protocol Buffers (Protobuf) 직렬화 통신 최적화",
|
"Protocol Buffers (Protobuf) 직렬화 통신 최적화",
|
||||||
@@ -115,6 +128,9 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Research Project PageView Component */}
|
||||||
|
<ProjectPageView />
|
||||||
|
|
||||||
{/* Core Research Areas Grid */}
|
{/* Core Research Areas Grid */}
|
||||||
<section className="space-y-8 pt-8 border-t border-line">
|
<section className="space-y-8 pt-8 border-t border-line">
|
||||||
<div className="flex justify-between items-end">
|
<div className="flex justify-between items-end">
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -80,3 +80,21 @@ export interface StandardsBody {
|
|||||||
role?: string;
|
role?: string;
|
||||||
projects: StandardProject[];
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -443,6 +443,77 @@ def parse_standards():
|
|||||||
|
|
||||||
return bodies
|
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):
|
def clean_dict(obj):
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
return {k: clean_dict(v) for k, v in obj.items() if v is not None}
|
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()
|
intl_p, dom_p, pat_p = parse_publications()
|
||||||
semesters = parse_lectures()
|
semesters = parse_lectures()
|
||||||
bodies = parse_standards()
|
bodies = parse_standards()
|
||||||
|
projects = parse_projects()
|
||||||
|
|
||||||
os.makedirs(CONTENT_DIR, exist_ok=True)
|
os.makedirs(CONTENT_DIR, exist_ok=True)
|
||||||
|
|
||||||
@@ -481,5 +553,10 @@ def main():
|
|||||||
f.write('import { StandardsBody } from "./types";\n\n')
|
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')
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -14,17 +14,18 @@ MONTH_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def verify():
|
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()
|
active_m, alumni_m = parse_members()
|
||||||
intl_p, dom_p, pat_p = parse_publications()
|
intl_p, dom_p, pat_p = parse_publications()
|
||||||
semesters = parse_lectures()
|
semesters = parse_lectures()
|
||||||
bodies = parse_standards()
|
bodies = parse_standards()
|
||||||
|
projects = parse_projects()
|
||||||
|
|
||||||
total_pubs = len(intl_p) + len(dom_p) + len(pat_p)
|
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"])
|
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"Active Members: {len(active_m)} (Expected: 16)")
|
||||||
print(f"Alumni: {len(alumni_m)} (Expected: 60)")
|
print(f"Alumni: {len(alumni_m)} (Expected: 60)")
|
||||||
print(f"International Papers: {len(intl_p)} (Expected: 68)")
|
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"Lectures Semesters: {len(semesters)} (Expected: 45)")
|
||||||
print(f"Standard Bodies: {len(bodies)} (Expected: 6)")
|
print(f"Standard Bodies: {len(bodies)} (Expected: 6)")
|
||||||
print(f"Standard Documents: {std_docs_count} (Expected: 44)")
|
print(f"Standard Documents: {std_docs_count} (Expected: 44)")
|
||||||
|
print(f"Research Projects: {len(projects)} (Expected: 3)")
|
||||||
|
|
||||||
errors = []
|
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
|
# 1. Count checks
|
||||||
if len(active_m) != 16:
|
if len(active_m) != 16:
|
||||||
errors.append(f"Active Members count mismatch: got {len(active_m)}, expected 16")
|
errors.append(f"Active Members count mismatch: got {len(active_m)}, expected 16")
|
||||||
|
|||||||
@@ -93,5 +93,23 @@ class TestANLHomepageE2E(unittest.TestCase):
|
|||||||
for token in ["--ink", "--ivory", "--paper", "--line", "--vermillion", "--cobalt"]:
|
for token in ["--ink", "--ivory", "--paper", "--line", "--vermillion", "--cobalt"]:
|
||||||
self.assertIn(token, css, f"Missing CSS variable {token} in globals.css")
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user