feat: implement layout 3-tier container architecture, desktop screen token, Gate 8 no-escape rule, and clean dead code
This commit is contained in:
+6
-6
@@ -7,16 +7,16 @@
|
|||||||
### ✅ 완료된 추후 작업 (Completed Tasks)
|
### ✅ 완료된 추후 작업 (Completed Tasks)
|
||||||
|
|
||||||
#### 1. 전 모드 좌우 여유 공백(Side Margins/Padding) 추가
|
#### 1. 전 모드 좌우 여유 공백(Side Margins/Padding) 추가
|
||||||
- **적용 위치**: `refer_landing_page/app/globals.css` (`.container-content`)
|
- **적용 위치**: `refer_landing_page/app/globals.css` (`.container-content`) & `app/page.tsx` 등 6개 라우트 최상위 래퍼
|
||||||
- **내용**: 모바일, 태블릿, 데스크톱 전 모드에서 시각적 답답함을 해소하고 여유로운 에디토리얼 여백을 제공하도록 `px-6 sm:px-10 md:px-12 lg:px-16` 패딩 시스템 반영
|
- **내용**: 모바일, 태블릿, 데스크톱 전 모드에서 시각적 답답함을 해소하고 여유로운 에디토리얼 여백을 제공하도록 `<main>` 태그의 full-width 확장성은 보존하면서 6개 라우트 래퍼에 `container-content` (`px-6 sm:px-10 md:px-12 lg:px-16`) 적용
|
||||||
|
|
||||||
#### 2. Desktop 모드 `max_width` 비율 고정
|
#### 2. Desktop 모드 `max_width` 비율 고정
|
||||||
- **적용 위치**: `refer_landing_page/tailwind.config.ts` (`maxWidth.content: "78rem"`) & `.container-content`
|
- **적용 위치**: `refer_landing_page/tailwind.config.ts` (`maxWidth.content: "78rem"`) & 6개 라우트 래퍼 `.container-content`
|
||||||
- **내용**: 초고해상도(Ultrawide / 4K) 모니터에서 페이지 가로 폭이 지나치게 넓어져 레이아웃 비율이 깨지는 현상을 방지하기 위해 최대 폭(1248px) 고정 및 중앙 정렬
|
- **내용**: 초고해상도(Ultrawide / 4K) 모니터에서 페이지 가로 폭이 지나치게 넓어져 레이아웃 비율이 깨지는 현상을 방지하기 위해 6개 라우트 본문 최대 폭(78rem / 1248px) 고정 및 중앙 정렬
|
||||||
|
|
||||||
#### 3. 헤더 1240px 반응형 햄버거 메뉴 전환
|
#### 3. 헤더 1240px 반응형 햄버거 메뉴 전환
|
||||||
- **적용 위치**: `refer_landing_page/components/Header.tsx`
|
- **적용 위치**: `refer_landing_page/tailwind.config.ts` (`screens: { desktop: "1240px" }`) & `refer_landing_page/components/Header.tsx` (4곳 `desktop:` 토클)
|
||||||
- **내용**: 윈도우 폭이 1240px 미만일 때 "AI 에이전트 네트워크 연구실" 및 "AI Agent Networking Lab (ANL)" 타이틀 카피가 2줄로 줄바꿈되어 깨지는 현상을 방지하도록 `min-[1240px]` 브레이크포인트 적용. 1240px 미만에서는 햄버거 토글 메뉴로 자동 전환
|
- **내용**: 윈도우 폭이 1240px 미만일 때 "AI 에이전트 네트워크 연구실" 및 "AI Agent Networking Lab (ANL)" 타이틀 카피가 2줄로 줄바꿈되어 깨지는 현상을 방지하도록 `desktop:` (1240px) 스크린 토큰 적용. 1240px 미만에서는 햄버거 토글 메뉴 및 헤더 스트립으로 100% 통일 전환
|
||||||
|
|
||||||
#### 4. Publications 페이지 "International Conference" 카테고리 정리
|
#### 4. Publications 페이지 "International Conference" 카테고리 정리
|
||||||
- **적용 위치**: `refer_landing_page/content/categories.ts`, `content/types.ts`, `app/publications/`
|
- **적용 위치**: `refer_landing_page/content/categories.ts`, `content/types.ts`, `app/publications/`
|
||||||
|
|||||||
+54
-22
@@ -4,10 +4,10 @@
|
|||||||
| 항목 | 내용 |
|
| 항목 | 내용 |
|
||||||
| :--- | :--- |
|
| :--- | :--- |
|
||||||
| **문서 ID** | `REQ-ANL-WEB-001` |
|
| **문서 ID** | `REQ-ANL-WEB-001` |
|
||||||
| **버전** | `1.4` |
|
| **버전** | `1.6` |
|
||||||
| **작성일** | 2026-07-30 |
|
| **작성일** | 2026-07-30 |
|
||||||
| **작성자** | claude (`canary-projects-landing-page-creator-claude`) · Role: planner |
|
| **작성자** | claude (`canary-projects-landing-page-creator-claude`) · Role: planner |
|
||||||
| **상태** | 초안 (Draft) — §12 미결 결정(D-01) 확정 후 `승인(Approved)`으로 전환 |
|
| **상태** | 승인 (Approved) — v1.5에서 **D-01** 확정(A·B안 기각 / `intl-conf` 폐기 · 3개 카테고리), v1.6에서 **D-06** 확정(레이아웃 컨테이너 3계층 분리). 잔여 미결 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 애플리케이션) 전체 |
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
| **PR-01** | **무손실 보존 (Lossless Preservation)** — 레거시 `ISL-2026/`의 레코드는 **한 건도 누락되지 않고**, 문구·표기·순서가 임의로 변형되지 않은 채 이관되어야 한다 | "기존 정보 그대로 보존되어야 함" (4회 반복) |
|
| **PR-01** | **무손실 보존 (Lossless Preservation)** — 레거시 `ISL-2026/`의 레코드는 **한 건도 누락되지 않고**, 문구·표기·순서가 임의로 변형되지 않은 채 이관되어야 한다 | "기존 정보 그대로 보존되어야 함" (4회 반복) |
|
||||||
| **PR-02** | **상단 탭 5종 고정** — Home / Members / Publications / Lectures / Standardization | "홈페이지 상단 탭은 다음과 같음" |
|
| **PR-02** | **상단 탭 5종 고정** — Home / Members / Publications / Lectures / Standardization | "홈페이지 상단 탭은 다음과 같음" |
|
||||||
| **PR-03** | **Home 3구성** — 연구실 소개 · 주요 연구 분야 소개 · 오시는 길 | Home 항목 |
|
| **PR-03** | **Home 3구성** — 연구실 소개 · 주요 연구 분야 소개 · 오시는 길 | Home 항목 |
|
||||||
| **PR-04** | **Publications 4분류** — 원문에 명시된 4개 카테고리 라벨을 그대로 사용한다 | Publications 항목 |
|
| **PR-04** | **Publications 분류** — 원문에 명시된 4개 카테고리 라벨을 출발점으로 삼는다. *(v1.5: 원문 1번 `International Conference`는 §12.1 D-01 확정에 따라 폐기되어 **최종 3분류**로 확정. 원문 문구 자체는 §1에 정본 그대로 보존하며, 이 축소는 해당 라벨에 배정된 레코드가 0건이므로 PR-01 무손실 원칙과 충돌하지 않는다)* | Publications 항목 |
|
||||||
|
|
||||||
> ⚠️ **PR-01의 해석**: "그대로 보존"은 **데이터의 무손실**을 의미하며, *레거시의 시각적 표현을 그대로 복제하라는 뜻이 아니다.* 표현은 신규 디자인 시스템(§8)을 따르되, **레코드 단위의 정보량은 100% 보존**한다. 점진적 노출(더 보기·접기)은 전체 레코드가 접근 가능한 한 허용된다(§9.4 참조).
|
> ⚠️ **PR-01의 해석**: "그대로 보존"은 **데이터의 무손실**을 의미하며, *레거시의 시각적 표현을 그대로 복제하라는 뜻이 아니다.* 표현은 신규 디자인 시스템(§8)을 따르되, **레코드 단위의 정보량은 100% 보존**한다. 점진적 노출(더 보기·접기)은 전체 레코드가 접근 가능한 한 허용된다(§9.4 참조).
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
| 주체 | 역할 | 본 명세서에 대한 책임 |
|
| 주체 | 역할 | 본 명세서에 대한 책임 |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| 지도교수 (고석주 교수) | 콘텐츠 소유자 | 실데이터 정확성 최종 확인, §12 D-01·D-02·D-03 결정 |
|
| 지도교수 (고석주 교수) | 콘텐츠 소유자 | 실데이터 정확성 최종 확인, §12 D-02·D-03·D-05 결정 *(D-01은 v1.5에서 확정)* |
|
||||||
| General Manager | 오케스트레이터 | 잡 분배, 마일스톤 게이팅, 미결 결정 에스컬레이션 |
|
| General Manager | 오케스트레이터 | 잡 분배, 마일스톤 게이팅, 미결 결정 에스컬레이션 |
|
||||||
| **Agy** (`creator`) | 개발 팀장 | §6·§7 구현, 데이터 추출·이관, 컴포넌트 개발 |
|
| **Agy** (`creator`) | 개발 팀장 | §6·§7 구현, 데이터 추출·이관, 컴포넌트 개발 |
|
||||||
| **Cline** (`reviewer`) | 리뷰어 팀장 | §9 비기능 요구사항 검수 (UI/UX 반응형, SEO, 접근성) |
|
| **Cline** (`reviewer`) | 리뷰어 팀장 | §9 비기능 요구사항 검수 (UI/UX 반응형, SEO, 접근성) |
|
||||||
@@ -96,13 +96,12 @@
|
|||||||
```
|
```
|
||||||
/ Home [🆕 신규] 연구실 소개 · 주요 연구 분야(5대 기둥) · 오시는 길
|
/ Home [🆕 신규] 연구실 소개 · 주요 연구 분야(5대 기둥) · 오시는 길
|
||||||
├── /members Members 지도교수 · 재학 연구원 16명 · 졸업생 60명
|
├── /members Members 지도교수 · 재학 연구원 16명 · 졸업생 60명
|
||||||
├── /publications Publications 전체 목록 (총 223건)
|
├── /publications Publications 개요 · 카테고리 분류 · Top 10 (총 223건)
|
||||||
│ ├── /publications/intl-conf International Conference
|
│ ├── /publications/intl-journal-conf International Journal & Conference (68)
|
||||||
│ ├── /publications/intl-journal-conf International Journal & Conference
|
│ ├── /publications/domestic-journal-conf Domestic Journal & Conference (80)
|
||||||
│ ├── /publications/domestic-journal-conf Domestic Journal & Conference
|
│ └── /publications/patent Patent (75)
|
||||||
│ └── /publications/patent Patent
|
├── /lectures Lectures 2004 Spring ~ 2026 Spring, 45개 학기
|
||||||
├── /lectures Lectures 2016 Fall ~ 2026 Spring, 약 20개 학기
|
└── /standardization Standardization 표준화 기구 6곳 · 표준 문서 44건
|
||||||
└── /standardization Standardization 표준화 기구 6곳 · 표준 문서 약 55건
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4.2 IA 설계 결정
|
### 4.2 IA 설계 결정
|
||||||
@@ -111,7 +110,7 @@
|
|||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| **IA-01** | 현행 `/intro`의 커버 스프레드를 **`/`(Home)으로 승격**한다 | PR-02가 상단 탭에 `Intro`를 포함하지 않으며, PR-03의 "연구실 소개"가 Home의 구성 요소로 명시되어 있다. `/intro`를 별도 유지하면 Home과 콘텐츠가 중복된다. **부수 효과로 기존 `/` 404 결함이 해소된다.** |
|
| **IA-01** | 현행 `/intro`의 커버 스프레드를 **`/`(Home)으로 승격**한다 | PR-02가 상단 탭에 `Intro`를 포함하지 않으며, PR-03의 "연구실 소개"가 Home의 구성 요소로 명시되어 있다. `/intro`를 별도 유지하면 Home과 콘텐츠가 중복된다. **부수 효과로 기존 `/` 404 결함이 해소된다.** |
|
||||||
| **IA-02** | `/intro`는 **`/`로 308 영구 리다이렉트**한다 | 3차에 걸친 리뷰 리포트·문서가 `/intro`를 참조하고 있어 링크 안정성이 필요하다. `next.config.js`의 `redirects()`로 처리하며 별도 페이지 파일을 남기지 않는다. |
|
| **IA-02** | `/intro`는 **`/`로 308 영구 리다이렉트**한다 | 3차에 걸친 리뷰 리포트·문서가 `/intro`를 참조하고 있어 링크 안정성이 필요하다. `next.config.js`의 `redirects()`로 처리하며 별도 페이지 파일을 남기지 않는다. |
|
||||||
| **IA-03** *(v1.1 개정)* | Publications 4개 카테고리는 **라우트 세그먼트 `/publications/[category]` + `generateStaticParams()`** 로 구현한다. `/publications`는 전체 목록 기본 화면으로 유지한다 | **v1.0에서 "단일 라우트 + `?cat=` 쿼리"였으나 §8.5의 실측 검증 결과에 따라 개정.** 세그먼트 방식은 카테고리별 완전한 정적 HTML 4개를 빌드 타임에 생성해 **공유 링크가 필터 상태를 포함한 문서를 그대로 제공**하며(AC-05 취지 충족), 뒤로/앞으로 가기·프리페치·스크롤 복원이 Next 라우터에서 기본 제공된다. 슬러그를 카테고리 레지스트리에서 도출하므로 **라벨 변경이 레지스트리 한 곳 수정으로 끝난다는 v1.0의 근거는 그대로 유지**된다(→ D-01 리스크 완화 불변). 상단 탭은 5종 그대로이므로 **PR-02와 충돌하지 않는다**(하위 라우트일 뿐 최상위 탭이 아니다). |
|
| **IA-03** *(v1.1 개정 · v1.5 수치 갱신)* | Publications 카테고리는 **라우트 세그먼트 `/publications/[category]` + `generateStaticParams()`** 로 구현한다. `/publications`는 개요 화면으로 유지한다 | **v1.0에서 "단일 라우트 + `?cat=` 쿼리"였으나 §8.5의 실측 검증 결과에 따라 개정.** 세그먼트 방식은 카테고리별 완전한 정적 HTML 3개(D-01 확정 전 4개)를 빌드 타임에 생성해 **공유 링크가 필터 상태를 포함한 문서를 그대로 제공**하며(AC-05 취지 충족), 뒤로/앞으로 가기·프리페치·스크롤 복원이 Next 라우터에서 기본 제공된다. 슬러그를 카테고리 레지스트리에서 도출하므로 **라벨 변경이 레지스트리 한 곳 수정으로 끝난다는 v1.0의 근거는 그대로 유지**된다(→ D-01 리스크 완화 불변). 상단 탭은 5종 그대로이므로 **PR-02와 충돌하지 않는다**(하위 라우트일 뿐 최상위 탭이 아니다). |
|
||||||
| **IA-04** | 내비게이션 항목은 **PR-02의 5개로 고정**한다 | `components/Header.tsx`의 데스크톱 내비가 5개 항목 기준으로 조판되어 있다. 항목 증감 시 헤더 레이아웃 재설계가 선행되어야 한다. |
|
| **IA-04** | 내비게이션 항목은 **PR-02의 5개로 고정**한다 | `components/Header.tsx`의 데스크톱 내비가 5개 항목 기준으로 조판되어 있다. 항목 증감 시 헤더 레이아웃 재설계가 선행되어야 한다. |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -192,9 +191,9 @@ PR-03에 따라 **연구실 소개 · 주요 연구 분야 소개 · 오시는
|
|||||||
|
|
||||||
| ID | 요구사항 | 수준 |
|
| ID | 요구사항 | 수준 |
|
||||||
| :--- | :--- | :-- |
|
| :--- | :--- | :-- |
|
||||||
| P-01 | PR-04의 **4개 카테고리 라벨을 그대로** 사용한다: `International Conference` / `International Journal & Conference` / `Domestic Journal & Conference` / `Patent` **(→ §12 D-01: 카테고리 1·2 범위 중첩 확인 필요)** | MUST |
|
| P-01 *(v1.5 개정)* | **3개 카테고리**를 사용한다: `International Journal & Conference` / `Domestic Journal & Conference` / `Patent`. **D-01 확정(v1.5)**: PR-04의 1번 라벨 `International Conference`는 대응 레거시 원본이 없어 실측 0건이었고, 2번 라벨의 부분집합으로 범위가 중첩되므로 **단독 카테고리를 폐기**한다. 국제 학술대회 논문(실측 1건)은 `International Journal & Conference`에 포함해 보존한다 | MUST |
|
||||||
| P-02 | 카테고리는 **레지스트리 상수 1곳**에 정의하고, 개별 레코드는 `category` 판별 필드만 갖는다. 라벨 변경·교체가 레지스트리 수정만으로 완결되어야 한다 | MUST |
|
| P-02 | 카테고리는 **레지스트리 상수 1곳**에 정의하고, 개별 레코드는 `category` 판별 필드만 갖는다. 라벨 변경·교체가 레지스트리 수정만으로 완결되어야 한다 | MUST |
|
||||||
| P-03 | **223건 전량 보존** — 어떤 카테고리에도 배정되지 않아 누락되는 레코드가 있어서는 안 된다. 분류 판정이 모호한 레코드는 임의 폐기하지 말고 D-01 확정 시까지 보류 목록으로 보고한다 | MUST |
|
| P-03 | **223건 전량 보존** — 어떤 카테고리에도 배정되지 않아 누락되는 레코드가 있어서는 안 된다. *(v1.5 확인: D-01 확정으로 카테고리가 3개로 축소되었으나 `intl-conf` 배정 레코드가 0건이었으므로 축소는 무손실이다. 실측 68 + 80 + 75 = 223)* | MUST |
|
||||||
| P-04 | 레거시 `paper-international.html`은 저널과 학술대회 논문이 **혼재**되어 있다(학술대회 1건 확인: `International Conference on ICTC`, 2023). 이관 시 게재처 문자열로 판별해 분류한다 | MUST |
|
| P-04 | 레거시 `paper-international.html`은 저널과 학술대회 논문이 **혼재**되어 있다(학술대회 1건 확인: `International Conference on ICTC`, 2023). 이관 시 게재처 문자열로 판별해 분류한다 | MUST |
|
||||||
| P-05 | 기본 정렬은 **발행 연월 내림차순**, 동일 연월 내에서는 레거시 게시 순서를 보존한다 | MUST |
|
| P-05 | 기본 정렬은 **발행 연월 내림차순**, 동일 연월 내에서는 레거시 게시 순서를 보존한다 | MUST |
|
||||||
| P-06 *(v1.1 개정)* | 카테고리 필터 UI를 제공한다. 필터 상태는 **URL 경로**(`/publications/<slug>`)에 반영되어 **공유 가능한 링크**가 되어야 한다. 슬러그는 §7.2 `PubCategory` 값을 그대로 사용한다. 구현 방식은 **§8.5의 렌더링 제약을 반드시 따른다** | MUST |
|
| P-06 *(v1.1 개정)* | 카테고리 필터 UI를 제공한다. 필터 상태는 **URL 경로**(`/publications/<slug>`)에 반영되어 **공유 가능한 링크**가 되어야 한다. 슬러그는 §7.2 `PubCategory` 값을 그대로 사용한다. 구현 방식은 **§8.5의 렌더링 제약을 반드시 따른다** | MUST |
|
||||||
@@ -283,9 +282,8 @@ interface Alumnus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// content/publications.ts
|
// content/publications.ts
|
||||||
// PR-04의 4개 카테고리. 라벨 문자열은 레지스트리에서만 정의한다 (P-02).
|
// D-01 확정(v1.5) 후 3개 카테고리. 라벨 문자열은 레지스트리에서만 정의한다 (P-02).
|
||||||
type PubCategory =
|
type PubCategory =
|
||||||
| "intl-conf" // International Conference
|
|
||||||
| "intl-journal-conf" // International Journal & Conference
|
| "intl-journal-conf" // International Journal & Conference
|
||||||
| "domestic-journal-conf" // Domestic Journal & Conference
|
| "domestic-journal-conf" // Domestic Journal & Conference
|
||||||
| "patent"; // Patent
|
| "patent"; // Patent
|
||||||
@@ -411,10 +409,27 @@ interface StandardsBody {
|
|||||||
|
|
||||||
**R-8.14 [금지]** 라우팅 목적의 `window.history.pushState` 직접 호출을 하지 않는다. Next 라우터를 우회하므로 **뒤로/앞으로 가기 시 URL만 바뀌고 UI가 갱신되지 않으며**, 이를 보정하려면 `popstate` 리스너를 직접 등록·해제해야 한다. 카테고리 이동은 `next/link`로 처리한다(P-06a).
|
**R-8.14 [금지]** 라우팅 목적의 `window.history.pushState` 직접 호출을 하지 않는다. Next 라우터를 우회하므로 **뒤로/앞으로 가기 시 URL만 바뀌고 UI가 갱신되지 않으며**, 이를 보정하려면 `popstate` 리스너를 직접 등록·해제해야 한다. 카테고리 이동은 `next/link`로 처리한다(P-06a).
|
||||||
|
|
||||||
**R-8.15 [필수]** 대량 레코드(Publications 223건, Alumni 60명, 표준 문서 55건)는 **서버 컴포넌트에서 정적 HTML로 렌더**한다. 클라이언트 상태나 `<Suspense>` 경계 안쪽에 넣어 정적 문서에서 누락시키지 않는다. 실측 #2에서 **경계 바깥 서버 렌더 콘텐츠는 프리렌더된다**는 점이 확인되었으므로, 목록은 바깥에 두고 인터랙션만 안쪽에 두는 분리가 가능하다.
|
**R-8.15 [필수]** 대량 레코드(Publications 223건, Alumni 60명, 표준 문서 44건)는 **서버 컴포넌트에서 정적 HTML로 렌더**한다. 클라이언트 상태나 `<Suspense>` 경계 안쪽에 넣어 정적 문서에서 누락시키지 않는다. 실측 #2에서 **경계 바깥 서버 렌더 콘텐츠는 프리렌더된다**는 점이 확인되었으므로, 목록은 바깥에 두고 인터랙션만 안쪽에 두는 분리가 가능하다.
|
||||||
|
|
||||||
**R-8.16 [표기]** `generateStaticParams()`를 사용하는 라우트는 빌드 리포트에서 `○ (Static)`이 아니라 **`● (SSG)`** 로 표기된다. **두 표기 모두 "빌드 타임 정적 프리렌더"이며 요구사항을 충족한다.** 수용 기준의 판정은 §10 AC-17의 개정된 문구를 따른다.
|
**R-8.16 [표기]** `generateStaticParams()`를 사용하는 라우트는 빌드 리포트에서 `○ (Static)`이 아니라 **`● (SSG)`** 로 표기된다. **두 표기 모두 "빌드 타임 정적 프리렌더"이며 요구사항을 충족한다.** 수용 기준의 판정은 §10 AC-17의 개정된 문구를 따른다.
|
||||||
|
|
||||||
|
### 8.6 레이아웃 컨테이너 아키텍처 · 브레이크포인트 토큰 *(v1.6 신설)*
|
||||||
|
|
||||||
|
Job `343b5808` 검증에서 본문 좌우 여백·최대 폭 미적용(B1)이, 개발 팀장 이의제기(Job `2560771b`)에서 그 수정 방식의 확장성 제약이 각각 확인되었다. 아래 규칙은 **D-06 확정 사항**이다.
|
||||||
|
|
||||||
|
**R-8.17 [계층 분리 · 필수]** 레이아웃은 **3계층**으로 분리한다.
|
||||||
|
1. `<main>` (`app/layout.tsx`) — **뷰포트 전체 폭을 유지**한다. `max-w-*` / `px-*` / `container-content`를 **부여하지 않는다.**
|
||||||
|
2. **라우트 최상위 래퍼** — 각 페이지 파일의 루트 `<div>`에 `container-content`를 부여해 본문 여백(24/40/48/64 px)과 최대 폭(78 rem = 1248 px) 중앙 정렬을 확보한다.
|
||||||
|
3. **Full-bleed 밴드** — 전체 폭 배경 밴드·구획선·마퀴 티커는 2계층 래퍼 **바깥**(즉 `<main>` 직속)에 배치한다.
|
||||||
|
|
||||||
|
**R-8.18 [금지]** Full-bleed 연출을 위해 `container-content` 자식에서 `w-screen` 또는 `-mx-[50vw]` 계열 **탈출 기법을 사용하지 않는다.** 현재 `html`·`body`에 `overflow-x` 가드가 **전무**하므로(실측 확인), 폭 제한 컨테이너 내부의 `w-screen`은 좌측 기준점이 부모 콘텐츠 박스여서 우측으로 넘쳐 **가로 스크롤을 유발하고 R-9.15/AC-19를 위반**한다. Full-bleed가 필요하면 R-8.17의 3계층으로 올린다.
|
||||||
|
|
||||||
|
**R-8.19 [필수]** 라우트 래퍼 방식은 **신규 라우트 추가 시 누락될 수 있으므로**, 빌드 산출 HTML의 모든 라우트에서 `<main>` 하위에 `container-content`가 존재함을 **자동 게이트로 검증**한다(→ AC-25). 이 게이트 없이는 본 아키텍처를 채택하지 않는다.
|
||||||
|
|
||||||
|
**R-8.20 [토큰]** 데스크톱 내비 전환 임계값은 `tailwind.config.ts`의 `theme.extend.screens`에 **`desktop: "1240px"` 단일 토큰**으로 등록하고 `desktop:` 변형으로만 사용한다. 임의값 클래스(`min-[1240px]:`)를 소스에 직접 쓰지 않는다.
|
||||||
|
- **실측 확인**: Tailwind v3는 `extend`로 추가한 스크린도 **min-width 값 기준으로 정렬**해 출력하므로(`1024 → 1240 → 1280 → 1536` 순서 확인), `2xl` 등 상위 브레이크포인트를 덮어쓰는 캐스케이드 사고는 발생하지 않는다. `screens` 전체를 재정의할 필요가 없다.
|
||||||
|
- **주의**: `lg`(1024px)와 `desktop`(1240px)이 공존하므로, **헤더 내 데스크톱/모바일 전환은 `desktop:`으로만 통일**한다. 동일 속성에 `lg:`와 `desktop:`을 혼용하면 1024–1239 px 구간에 의도치 않은 중간 상태가 생긴다(B2가 정확히 이 사고였다).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 비기능 요구사항 (Non-Functional Requirements)
|
## 9. 비기능 요구사항 (Non-Functional Requirements)
|
||||||
@@ -452,7 +467,7 @@ interface StandardsBody {
|
|||||||
|
|
||||||
### 9.5 반응형
|
### 9.5 반응형
|
||||||
|
|
||||||
**R-9.14** 지원 브레이크포인트는 Tailwind 기본값 `sm`(640px) / `lg`(1024px)이며, 검수 기준 뷰포트는 **360 / 768 / 1024 / 1440 px** 4종이다.
|
**R-9.14** *(v1.6 개정)* 지원 브레이크포인트는 Tailwind 기본값 `sm`(640px) / `md`(768px) / `lg`(1024px)에 **커스텀 토큰 `desktop`(1240px)** 을 더한 구성이다(R-8.20). 검수 기준 뷰포트는 **360 / 768 / 1024 / 1440 px** 4종에 **1239 px과 1240 px**을 추가한 6종이다 — 데스크톱 내비 전환 경계의 양쪽을 반드시 확인해야 한다.
|
||||||
**R-9.15** 어느 뷰포트에서도 가로 스크롤이 발생하지 않아야 한다.
|
**R-9.15** 어느 뷰포트에서도 가로 스크롤이 발생하지 않아야 한다.
|
||||||
**R-9.16** 긴 국문 제목의 줄바꿈은 `break-keep`을 적용한다.
|
**R-9.16** 긴 국문 제목의 줄바꿈은 `break-keep`을 적용한다.
|
||||||
|
|
||||||
@@ -473,7 +488,7 @@ interface StandardsBody {
|
|||||||
- [ ] AC-02 Home에 **연구실 소개 · 주요 연구 분야 · 오시는 길** 3개 블록이 모두 존재한다
|
- [ ] AC-02 Home에 **연구실 소개 · 주요 연구 분야 · 오시는 길** 3개 블록이 모두 존재한다
|
||||||
- [ ] AC-03 `/intro` 접근 시 `/`로 308 리다이렉트된다
|
- [ ] AC-03 `/intro` 접근 시 `/`로 308 리다이렉트된다
|
||||||
- [ ] AC-04 상단 탭 5종(Home/Members/Publications/Lectures/Standardization)이 전부 정상 라우팅된다
|
- [ ] AC-04 상단 탭 5종(Home/Members/Publications/Lectures/Standardization)이 전부 정상 라우팅된다
|
||||||
- [ ] AC-05 *(v1.1 개정)* Publications 4개 카테고리가 각각 고유 URL(`/publications/<slug>`)을 가지며, **해당 URL로 직접 진입했을 때 정적 HTML이 이미 해당 카테고리 상태를 담고 있다**(비필터 상태가 먼저 보였다가 바뀌는 현상이 없어야 한다)
|
- [ ] AC-05 *(v1.1 개정 · v1.5 수치 갱신)* Publications 3개 카테고리가 각각 고유 URL(`/publications/<slug>`)을 가지며, **해당 URL로 직접 진입했을 때 정적 HTML이 이미 해당 카테고리 상태를 담고 있다**(비필터 상태가 먼저 보였다가 바뀌는 현상이 없어야 한다)
|
||||||
- [ ] AC-06 Lectures가 학기별 그룹으로 렌더되고 최근 3개 학기가 기본 노출된다
|
- [ ] AC-06 Lectures가 학기별 그룹으로 렌더되고 최근 3개 학기가 기본 노출된다
|
||||||
|
|
||||||
**데이터 무손실 (PR-01)**
|
**데이터 무손실 (PR-01)**
|
||||||
@@ -493,12 +508,14 @@ interface StandardsBody {
|
|||||||
- [ ] AC-18 First Load JS 증가분 ≤ 30 kB
|
- [ ] AC-18 First Load JS 증가분 ≤ 30 kB
|
||||||
- [ ] AC-19 4종 뷰포트에서 가로 스크롤 없음
|
- [ ] AC-19 4종 뷰포트에서 가로 스크롤 없음
|
||||||
- [ ] AC-20 신규 Tailwind 반응형 유틸리티가 빌드 CSS에 실제 생성됨을 확인 (JIT 누락 시 빌드는 성공하나 조판이 무너짐)
|
- [ ] AC-20 신규 Tailwind 반응형 유틸리티가 빌드 CSS에 실제 생성됨을 확인 (JIT 누락 시 빌드는 성공하나 조판이 무너짐)
|
||||||
- [ ] AC-21 *(v1.1 신설)* `generateStaticParams()`로 카테고리별 정적 HTML 4개가 실제 생성됨을 `.next/server/app/publications/` 파일 목록으로 확인
|
- [ ] AC-21 *(v1.1 신설)* `generateStaticParams()`로 카테고리별 정적 HTML 3개가 실제 생성됨을 `.next/server/app/publications/` 파일 목록으로 확인
|
||||||
- [ ] AC-22 *(v1.1 신설)* 소스 전역에 `searchParams` prop 사용·`useSearchParams()` 호출·라우팅 목적 `pushState` 호출이 **0건**임을 grep으로 확인 (§8.5 R-8.11·8.12·8.14)
|
- [ ] AC-22 *(v1.1 신설)* 소스 전역에 `searchParams` prop 사용·`useSearchParams()` 호출·라우팅 목적 `pushState` 호출이 **0건**임을 grep으로 확인 (§8.5 R-8.11·8.12·8.14)
|
||||||
|
|
||||||
**문서**
|
**문서**
|
||||||
- [ ] AC-23 `README.md`·`docs/DESIGN.md` 서술이 구현 결과와 일치
|
- [ ] AC-23 `README.md`·`docs/DESIGN.md` 서술이 구현 결과와 일치
|
||||||
- [ ] AC-24 마이그레이션 스크립트가 `scripts/`에 커밋됨
|
- [ ] AC-24 마이그레이션 스크립트가 `scripts/`에 커밋됨
|
||||||
|
- [ ] 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으로 확인
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -508,7 +525,7 @@ interface StandardsBody {
|
|||||||
|
|
||||||
| Phase | 작업 | 산출물 | 담당 | 선행 조건 |
|
| Phase | 작업 | 산출물 | 담당 | 선행 조건 |
|
||||||
| :-- | :--- | :--- | :--- | :--- |
|
| :-- | :--- | :--- | :--- | :--- |
|
||||||
| **P0** | D-01(Publications 카테고리 1·2 중첩) 확정 | 결정 기록 | GM ← 지도교수 | — |
|
| **P0** | D-01(Publications 카테고리 1·2 중첩) 확정 | 결정 기록 | GM ← 지도교수 | ✅ 완료 (v1.5 · `51aae14`) |
|
||||||
| **P1** | 레거시 데이터 추출 스크립트 작성 및 `content/*.ts` 생성 | 4개 데이터 모듈 + 추출 스크립트 | Agy | P0 |
|
| **P1** | 레거시 데이터 추출 스크립트 작성 및 `content/*.ts` 생성 | 4개 데이터 모듈 + 추출 스크립트 | Agy | P0 |
|
||||||
| **P1-R** | 건수·인코딩·날짜 정규화 교차 검증 (AC-07~11) | 검증 리포트 | Claude | P1 |
|
| **P1-R** | 건수·인코딩·날짜 정규화 교차 검증 (AC-07~11) | 검증 리포트 | Claude | P1 |
|
||||||
| **P2** | Home 신설(소개·연구 분야·오시는 길) + `/intro` 리다이렉트 + Header 내비 갱신 | `app/page.tsx`, `next.config.js` | Agy | P1-R |
|
| **P2** | Home 신설(소개·연구 분야·오시는 길) + `/intro` 리다이렉트 + Header 내비 갱신 | `app/page.tsx`, `next.config.js` | Agy | P1-R |
|
||||||
@@ -537,11 +554,12 @@ Publications(P4)는 데이터 규모(223건)와 필터·점진적 노출 요구
|
|||||||
|
|
||||||
| ID | 결정 사항 | 상태 | 기본값(잠정) | 영향 |
|
| ID | 결정 사항 | 상태 | 기본값(잠정) | 영향 |
|
||||||
| :--- | :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- | :--- |
|
||||||
| **D-01** | Publications 카테고리 1(`International Conference`)과 2(`International Journal & Conference`)의 **범위 중첩** 해소 | 🔴 **미결** | 원문 라벨 그대로 사용 | P-01. IA-03·P-02 설계에 의해 **구현 차단 요인은 아니며**, 레지스트리 상수 1곳 수정으로 교체 가능. *(v1.1 보완: 라우트 세그먼트 방식에서도 슬러그를 레지스트리에서 도출하므로 동일하게 1곳 수정으로 끝난다. 단 슬러그 변경은 URL 변경을 수반하므로 **확정은 P4 착수 전이 바람직**하다.)* |
|
| **D-01** | Publications 카테고리 1(`International Conference`)과 2(`International Journal & Conference`)의 **범위 중첩** 해소 | ✅ **확정** (v1.5, 2026-07-30) | — | **결정: 1번 단독 카테고리 폐기 → 3개 카테고리 확정.** A안(저널/학술대회 분리)은 학술대회가 1건에 불과해 기각, B안(미수록 논문 확보)은 신규 데이터 유입 없이 실행 불가하여 기각. 레지스트리 1곳(`content/categories.ts`) + `PubCategory` 유니온 수정으로 완결되었고 **223건 무손실**을 유지했다(P-03). 근거: `intl-conf` 배정 레코드 실측 0건 |
|
||||||
| **D-02** | 강의 과목명 한글 병기 여부 | 🟡 미결 | 영문 원문만 노출 (L-05) | Lectures 표기. 병기 시 지도교수 확인 필요 |
|
| **D-02** | 강의 과목명 한글 병기 여부 | 🟡 미결 | 영문 원문만 노출 (L-05) | Lectures 표기. 병기 시 지도교수 확인 필요 |
|
||||||
| **D-03** | 오시는 길의 지도 표현 방식 | 🟡 미결 | 주소 텍스트 + 캠퍼스 링크 (지도 임베드 없음) | H-16. 외부 지도 스크립트는 R-9.13 최소 의존성 원칙과 충돌 |
|
| **D-03** | 오시는 길의 지도 표현 방식 | 🟡 미결 | 주소 텍스트 + 캠퍼스 링크 (지도 임베드 없음) | H-16. 외부 지도 스크립트는 R-9.13 최소 의존성 원칙과 충돌 |
|
||||||
| **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-01 상세
|
#### D-01 상세
|
||||||
|
|
||||||
@@ -565,6 +583,18 @@ PR-04에 명시된 4개 라벨 중 **1번과 2번의 범위가 논리적으로
|
|||||||
|
|
||||||
**리뷰어 판단**: 어느 안이든 **P-02의 레지스트리 설계로 인해 구현 착수를 막지 않는다.** P1 단계에서는 레거시 3개 원본을 그대로 3개 카테고리에 적재하고, D-01 확정 후 4번째 카테고리 분리를 적용하는 순서를 권고한다. 단 **P-03(레코드 무손실)** 은 어느 안에서도 반드시 지켜져야 한다.
|
**리뷰어 판단**: 어느 안이든 **P-02의 레지스트리 설계로 인해 구현 착수를 막지 않는다.** P1 단계에서는 레거시 3개 원본을 그대로 3개 카테고리에 적재하고, D-01 확정 후 4번째 카테고리 분리를 적용하는 순서를 권고한다. 단 **P-03(레코드 무손실)** 은 어느 안에서도 반드시 지켜져야 한다.
|
||||||
|
|
||||||
|
**✅ 확정 결과 (v1.5, 2026-07-30 · commit `51aae14`)** — **C안: 1번 단독 카테고리 폐기**로 결정되었다.
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| 기각된 A안 | 국제 실적을 저널/학술대회로 분리 → 학술대회가 **실측 1건**뿐이어서 단독 카테고리로 성립하지 않음 |
|
||||||
|
| 기각된 B안 | 미수록 학술대회 논문을 추가 확보 → **MG-05(`ISL-2026/`에 없는 데이터 생성 금지)** 에 저촉되며 신규 원본 유입 없이는 실행 불가 |
|
||||||
|
| 채택된 C안 | `intl-conf` 슬러그·라벨·타입 유니온을 **제거**하고 3개 카테고리로 확정 |
|
||||||
|
| 무손실 검증 | `intl-conf` 배정 레코드 **0건** → 축소가 정의상 무손실. 실측 68 + 80 + 75 = **223건** 유지 |
|
||||||
|
| 변경 범위 | `content/categories.ts`, `content/types.ts`(유니온), E2E 기대 라우트 목록, `README.md` 라우트 맵 — **P-02 설계 예측대로 레지스트리 중심 수정으로 완결** |
|
||||||
|
|
||||||
|
이 확정으로 §4.1 사이트맵·§7 `PubCategory`·P-01·P-03이 v1.5에서 함께 갱신되었고, 문서 상태가 `승인(Approved)`으로 전환되었다.
|
||||||
|
|
||||||
### 12.2 리스크
|
### 12.2 리스크
|
||||||
|
|
||||||
| ID | 리스크 | 영향 | 완화 방안 |
|
| ID | 리스크 | 영향 | 완화 방안 |
|
||||||
@@ -616,4 +646,6 @@ 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` |
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body className="flex min-h-screen flex-col">
|
<body className="flex min-h-screen flex-col">
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1">{children}</main>
|
<main className="w-full flex-1">{children}</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default function LecturesPage() {
|
|||||||
const olderSemesters = semesters.slice(3);
|
const olderSemesters = semesters.slice(3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-16 py-6">
|
<div className="container-content space-y-16 py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default function MembersPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-16 py-6">
|
<div className="container-content space-y-16 py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function HomePage() {
|
|||||||
const pubCount = publications.length + patents.length;
|
const pubCount = publications.length + patents.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-16 py-6">
|
<div className="container-content space-y-16 py-6">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative">
|
<section className="relative">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function PublicationCategoryPage({ params }: PageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-16 py-6">
|
<div className="container-content space-y-16 py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function PublicationsIndexPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-16 py-6">
|
<div className="container-content space-y-16 py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default function StandardizationPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-16 py-6">
|
<div className="container-content space-y-16 py-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<section className="space-y-3">
|
<section className="space-y-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default function Header() {
|
|||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 border-b border-ink bg-ivory/85 backdrop-blur">
|
<header className="sticky top-0 z-50 border-b border-ink bg-ivory/85 backdrop-blur">
|
||||||
{/* Journal issue strip — magazine masthead feel. */}
|
{/* Journal issue strip — magazine masthead feel. */}
|
||||||
<div className="hidden border-b border-line lg:block">
|
<div className="hidden border-b border-line desktop:block">
|
||||||
<div className="container-content flex items-center justify-between py-1.5">
|
<div className="container-content flex items-center justify-between py-1.5">
|
||||||
<span className="kicker">Issue 01 — 2026</span>
|
<span className="kicker">Issue 01 — 2026</span>
|
||||||
{/* CUSTOMIZATION HOOK: masthead tagline */}
|
{/* CUSTOMIZATION HOOK: masthead tagline */}
|
||||||
@@ -55,7 +55,7 @@ export default function Header() {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */}
|
{/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */}
|
||||||
<div className="hidden items-center gap-7 min-[1240px]:flex">
|
<div className="hidden items-center gap-7 desktop:flex">
|
||||||
<nav className="flex items-center gap-7" aria-label="주 메뉴">
|
<nav className="flex items-center gap-7" aria-label="주 메뉴">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
@@ -85,7 +85,7 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile controls (visible under 1240px) */}
|
{/* Mobile controls (visible under 1240px) */}
|
||||||
<div className="flex items-center gap-2 min-[1240px]:hidden">
|
<div className="flex items-center gap-2 desktop:hidden">
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -110,7 +110,7 @@ export default function Header() {
|
|||||||
{open && (
|
{open && (
|
||||||
<nav
|
<nav
|
||||||
id="mobile-menu"
|
id="mobile-menu"
|
||||||
className="border-t border-ink bg-ivory min-[1240px]:hidden"
|
className="border-t border-ink bg-ivory desktop:hidden"
|
||||||
aria-label="모바일 메뉴"
|
aria-label="모바일 메뉴"
|
||||||
>
|
>
|
||||||
<ul className="container-content flex flex-col divide-y divide-line py-2">
|
<ul className="container-content flex flex-col divide-y divide-line py-2">
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import SectionLabel from "@/components/SectionLabel";
|
|
||||||
import Reveal from "@/components/Reveal";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Editorial page masthead. Korean serif headline + huge English display
|
|
||||||
* word, with magazine "NN / 05" spread numbering.
|
|
||||||
*
|
|
||||||
* CUSTOMIZATION HOOK — CONTENT: ko / en / display / description / index.
|
|
||||||
*/
|
|
||||||
export default function PageHeader({
|
|
||||||
ko,
|
|
||||||
en,
|
|
||||||
display,
|
|
||||||
description,
|
|
||||||
index,
|
|
||||||
}: {
|
|
||||||
ko: string;
|
|
||||||
en: string;
|
|
||||||
display?: string;
|
|
||||||
description?: string;
|
|
||||||
index: number;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<section className="relative overflow-hidden border-b border-ink bg-ivory">
|
|
||||||
<div className="container-content pb-12 pt-14 sm:pb-16 sm:pt-20">
|
|
||||||
<Reveal>
|
|
||||||
<SectionLabel index={index} label={en} />
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
<div className="mt-8 grid items-end gap-6 lg:grid-cols-12">
|
|
||||||
<Reveal className="lg:col-span-8" delay={80}>
|
|
||||||
<h1 className="headline-ko break-keep text-balance text-3xl leading-[1.05] text-ink sm:text-4xl lg:text-5xl">
|
|
||||||
{ko}
|
|
||||||
</h1>
|
|
||||||
{/* When a custom `display` word is provided, render it at the full
|
|
||||||
magazine scale. Otherwise fall back to `en` at the smaller
|
|
||||||
`display-sm` scale so long words like "Standardization" never
|
|
||||||
overflow the col-span-8 column. See LAYOUT_AUDIT #6 + #10. */}
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
display
|
|
||||||
? "display mt-2 break-keep text-vermillion"
|
|
||||||
: "display-sm mt-2 break-keep text-vermillion"
|
|
||||||
}
|
|
||||||
aria-hidden
|
|
||||||
>
|
|
||||||
{display ?? en}
|
|
||||||
</p>
|
|
||||||
</Reveal>
|
|
||||||
|
|
||||||
{description && (
|
|
||||||
<Reveal className="lg:col-span-4" variant="right" delay={160}>
|
|
||||||
<p className="border-l-2 border-ink pl-5 text-sm leading-relaxed text-ink-soft">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
</Reveal>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -158,6 +158,68 @@ def test_unittest_suite():
|
|||||||
print("✅ Gate 7 PASSED: All 6 DOM & static HTML content assertion tests passed")
|
print("✅ Gate 7 PASSED: All 6 DOM & static HTML content assertion tests passed")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def test_layout_architecture():
|
||||||
|
print("\n--- Gate 8: Layout Architecture & No-Escape Gate (AC-25 / AC-26 / R-8.17) ---")
|
||||||
|
|
||||||
|
# 1) Check HTML SSG pages for container-content inside main
|
||||||
|
routes_to_check = [
|
||||||
|
os.path.join(".next", "server", "app", "index.html"),
|
||||||
|
os.path.join(".next", "server", "app", "members.html"),
|
||||||
|
os.path.join(".next", "server", "app", "publications.html"),
|
||||||
|
os.path.join(".next", "server", "app", "lectures.html"),
|
||||||
|
os.path.join(".next", "server", "app", "standardization.html"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for rel_p in routes_to_check:
|
||||||
|
full_p = os.path.join(REFER_DIR, rel_p)
|
||||||
|
if not os.path.exists(full_p):
|
||||||
|
print(f"❌ Missing SSG route HTML: {rel_p}")
|
||||||
|
return False
|
||||||
|
with open(full_p, "r", encoding="utf-8") as f:
|
||||||
|
html = f.read()
|
||||||
|
|
||||||
|
# Verify <main tag does NOT contain max-w- or px-
|
||||||
|
main_match = re.search(r'<main\s+class="([^"]*)"', html)
|
||||||
|
if not main_match:
|
||||||
|
print(f"❌ <main> tag not found in {rel_p}")
|
||||||
|
return False
|
||||||
|
main_cls = main_match.group(1)
|
||||||
|
if "max-w-" in main_cls or "px-" in main_cls or "container-content" in main_cls:
|
||||||
|
print(f"❌ <main> tag in {rel_p} restricts width or padding ({main_cls}). Should be w-full flex-1 only.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Verify child wrapper has container-content
|
||||||
|
if "container-content" not in html:
|
||||||
|
print(f"❌ container-content missing in {rel_p}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 2) Check source for forbidden escaped arbitrary classes (AC-26)
|
||||||
|
forbidden_terms = ["w-screen", "-mx-[50vw]", "min-[1240px]:"]
|
||||||
|
for root, _, files in os.walk(os.path.join(REFER_DIR, "app")):
|
||||||
|
for fname in files:
|
||||||
|
if fname.endswith((".ts", ".tsx")):
|
||||||
|
fpath = os.path.join(root, fname)
|
||||||
|
with open(fpath, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
for term in forbidden_terms:
|
||||||
|
if term in content:
|
||||||
|
print(f"❌ Forbidden term '{term}' found in {fpath}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for root, _, files in os.walk(os.path.join(REFER_DIR, "components")):
|
||||||
|
for fname in files:
|
||||||
|
if fname.endswith((".ts", ".tsx")):
|
||||||
|
fpath = os.path.join(root, fname)
|
||||||
|
with open(fpath, "r", encoding="utf-8") as f:
|
||||||
|
content = f.read()
|
||||||
|
for term in forbidden_terms:
|
||||||
|
if term in content:
|
||||||
|
print(f"❌ Forbidden term '{term}' found in {fpath}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("✅ Gate 8 PASSED: Layout 3-tier architecture and AC-25/AC-26 no-escape rule verified")
|
||||||
|
return True
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("=" * 70)
|
print("=" * 70)
|
||||||
print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4")
|
print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4")
|
||||||
@@ -172,6 +234,7 @@ def main():
|
|||||||
success &= test_data_counts()
|
success &= test_data_counts()
|
||||||
success &= test_theme_tokens()
|
success &= test_theme_tokens()
|
||||||
success &= test_unittest_suite()
|
success &= test_unittest_suite()
|
||||||
|
success &= test_layout_architecture()
|
||||||
|
|
||||||
print("\n" + "-" * 70)
|
print("\n" + "-" * 70)
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ const config: Config = {
|
|||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
screens: {
|
||||||
|
desktop: "1240px",
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
ink: {
|
ink: {
|
||||||
DEFAULT: "rgb(var(--ink-rgb) / <alpha-value>)",
|
DEFAULT: "rgb(var(--ink-rgb) / <alpha-value>)",
|
||||||
|
|||||||
Reference in New Issue
Block a user