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` |
|
||||
| **버전** | `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 | 주소는 시맨틱 `<address>` 요소로 마크업한다 | 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 적용
|
||||
|
||||
**레거시 실측**: 재학 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`의 **모든 라우트**에서 `<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으로 확인
|
||||
|
||||
**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) | — | **결정: `<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 상세
|
||||
|
||||
@@ -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/<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.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.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/
|
||||
├── 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 최신 성과 |
|
||||
|
||||
@@ -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 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() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Research Project PageView Component */}
|
||||
<ProjectPageView />
|
||||
|
||||
{/* Core Research Areas Grid */}
|
||||
<section className="space-y-8 pt-8 border-t border-line">
|
||||
<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;
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user