docs: define TechLog UI migration design
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
# TechLog 전체 UI 이식 설계
|
||||
|
||||
## 상태
|
||||
|
||||
- 승인일: 2026-08-15
|
||||
- 원본: `/home/donghyeon/workspace/techlog-studio-frontend`
|
||||
- 대상: `/home/donghyeon/workspace/desktop-server-git/tech-log-frontend`
|
||||
- 결정: 원본 UI를 보존하고 현재 Vite·React Router·포트/어댑터 구조로 내부 경계만 치환한다.
|
||||
|
||||
## 목적
|
||||
|
||||
원본 TechLog의 Public과 Studio 전체 화면, URL 구조, 표시 콘텐츠, 상호작용과 세션 기반 Mock 동작을 대상 프로젝트로 이식한다. 대상 프로젝트의 부트스트랩, 오류 경계, 진단, 서비스 워커, route registry와 clean architecture 경계는 유지한다.
|
||||
|
||||
이 작업은 새 디자인을 만드는 작업이 아니다. 원본의 DOM 구조, CSS 계산값, 폰트, 자산, 문구, 반응형 동작과 접근성 구조를 기준본으로 삼는다. 구현 편의를 위한 시각적 재해석이나 대상 디자인 시스템에 맞춘 재디자인을 허용하지 않는다.
|
||||
|
||||
## 범위
|
||||
|
||||
### Public
|
||||
|
||||
- 홈과 현재 집중 항목
|
||||
- 통합 탐색과 유형별 탐색
|
||||
- Case, Reference, Question 공개 문서
|
||||
- Topic별 기록
|
||||
- Project 목록, 개요, 기록, 결정, 활동
|
||||
- Release 목록과 상세
|
||||
- Profile
|
||||
- 헤더 검색 dialog와 검색 결과
|
||||
- Public 오류, 빈 상태와 404
|
||||
- 공통 header, footer, document renderer, TOC, 관계, 코드, 표, callout, evidence figure
|
||||
|
||||
### Studio
|
||||
|
||||
- Studio dashboard
|
||||
- 작업본 검색·필터·정렬 목록
|
||||
- Case, Reference, Question 생성
|
||||
- 편집, 즉시 preview와 저장 상태
|
||||
- 검증과 issue 위치 이동
|
||||
- Public preview
|
||||
- 게시·재게시·게시 취소
|
||||
- 게시 이벤트 목록과 불변 snapshot
|
||||
- 충돌, 만료, 요청 실패, 세션 전용 not-found
|
||||
- 미저장 변경의 내부 이동 dialog와 native `beforeunload`
|
||||
|
||||
### 동작 경계
|
||||
|
||||
- 원본의 정적 Public 콘텐츠와 query 동작을 유지한다.
|
||||
- 원본의 `StudioGateway` 계약과 `MockStudioGateway` 상태 전이를 유지한다.
|
||||
- Studio 세션의 게시 결과는 Public 정적 콘텐츠와 검색 결과를 변경하지 않는다.
|
||||
- `localStorage`, 실제 서버 저장, 파일 업로드와 실제 배포 게시를 추가하지 않는다.
|
||||
- Studio 인증은 후속 작업으로 유보한다. 이번 이식에서는 Studio URL에 직접 접근할 수 있고 가짜 로그인 UI를 추가하지 않는다.
|
||||
|
||||
## 선택한 접근
|
||||
|
||||
### 원본 UI 보존형 이식
|
||||
|
||||
원본의 React 마크업, class 이름, CSS, 폰트, 자산과 문구를 유지하고 Next/Vinext 전용 경계만 대상 런타임으로 치환한다.
|
||||
|
||||
- `next/link`는 React Router `Link` 또는 의도된 전체 문서 이동 `<a>`로 바꾼다.
|
||||
- `usePathname`, App Router params와 search params는 React Router route input으로 바꾼다.
|
||||
- Next layout 수명은 React Router 중첩 layout route로 재현한다.
|
||||
- 서버 컴포넌트의 정적 조회는 순수 application query와 동기 projection으로 바꾼다.
|
||||
- Studio의 비동기 요청과 오류는 주입된 `StudioGateway` port를 통해 유지한다.
|
||||
|
||||
별도 legacy SPA 삽입은 라우터·상태·오류 경계를 이중화하므로 사용하지 않는다. 대상을 Next/Vinext로 전환하는 방식은 현재 템플릿과 운영 계약을 폐기하므로 사용하지 않는다.
|
||||
|
||||
## 대상 아키텍처
|
||||
|
||||
TechLog 기능은 하나의 feature boundary 안에서 Public과 Studio 하위 영역을 공유한다. Public renderer를 Studio preview가 함께 사용해야 하므로 두 영역을 서로 독립된 feature로 분리하지 않는다.
|
||||
|
||||
```text
|
||||
src/features/tech-log/
|
||||
├── contracts/ route, content format, gateway DTO와 schema
|
||||
├── domain/ Public content와 Studio document/publication 모델
|
||||
├── application/ Public query, projection, Studio state 계산과 port
|
||||
├── adapters/
|
||||
│ ├── static/ 원본 Public 콘텐츠 catalog
|
||||
│ └── mock/ 세션 수명의 MockStudioGateway
|
||||
└── presentation/
|
||||
├── public/ Public shell, pages와 renderer
|
||||
├── studio/ Studio shell, pages, provider와 editor
|
||||
├── shared/ 양쪽이 공유하는 안전한 render component
|
||||
└── styles/ 원본 CSS와 CSS Module
|
||||
```
|
||||
|
||||
공통 플랫폼은 feature의 route contract, route runtime과 adapter factory만 조립한다. Presentation이 adapter 구현을 직접 import하지 않으며 composition root가 gateway와 Public catalog를 주입한다.
|
||||
|
||||
대상 템플릿의 기존 example navigation, sidebar, theme·locale selector와 인증 예제 UI는 TechLog 제품 화면에서 제거한다. 더 이상 route registry에서 참조되지 않는 sample presentation은 대상의 sample-removal 정책에 따라 제거한다. 부트 오류와 플랫폼 진단 경계는 유지한다.
|
||||
|
||||
## 라우팅과 layout 수명
|
||||
|
||||
공통 router는 플랫폼 provider와 오류 경계를 유지하고 두 개의 시각 layout group을 만든다.
|
||||
|
||||
```text
|
||||
공통 부트스트랩·플랫폼 오류 경계
|
||||
├── PUBLIC layout
|
||||
│ ├── PublicShell
|
||||
│ ├── Public routes
|
||||
│ └── Public 404
|
||||
└── STUDIO layout
|
||||
├── StudioRuntimeBoundary
|
||||
├── StudioProvider
|
||||
├── StudioShell
|
||||
├── Studio routes
|
||||
└── Studio 전용 상태
|
||||
```
|
||||
|
||||
### Public route pattern
|
||||
|
||||
| 경로 | 책임 |
|
||||
| --- | --- |
|
||||
| `/` | 홈 |
|
||||
| `/explore` | 통합 탐색 |
|
||||
| `/explore/:kind` | 유형별 탐색 |
|
||||
| `/cases/:slug` | Case 문서 |
|
||||
| `/references/:slug` | Reference 문서 |
|
||||
| `/questions/:slug` | Question 문서 |
|
||||
| `/topics/:slug` | Topic별 기록 |
|
||||
| `/projects` | Project 목록 |
|
||||
| `/projects/:slug` | Project 개요 |
|
||||
| `/projects/:slug/records` | Project 기록 |
|
||||
| `/projects/:slug/decisions` | Project 결정 |
|
||||
| `/projects/:slug/activity` | Project 활동 |
|
||||
| `/releases` | Release 목록 |
|
||||
| `/releases/:version` | Release 상세 |
|
||||
| `/profile` | Profile |
|
||||
| `/search` | 검색 결과 |
|
||||
| `*` | Public 404 |
|
||||
|
||||
### Studio route pattern
|
||||
|
||||
| 경로 | 책임 |
|
||||
| --- | --- |
|
||||
| `/studio` | dashboard |
|
||||
| `/studio/documents` | 작업본 목록 |
|
||||
| `/studio/documents/new` | 새 문서 생성 |
|
||||
| `/studio/documents/:id/edit` | 편집과 즉시 preview |
|
||||
| `/studio/documents/:id/validation` | 검증 보고서 |
|
||||
| `/studio/documents/:id/preview` | 저장·검증된 Public preview |
|
||||
| `/studio/documents/:id/publish` | 게시·재게시 |
|
||||
| `/studio/publications` | 게시 이벤트 목록 |
|
||||
| `/studio/publications/:publicationEventId/preview` | 불변 snapshot |
|
||||
| 정의되지 않은 `/studio/*` | 실제 route 404 |
|
||||
|
||||
알 수 없는 동적 document ID와 publication event ID는 route에는 일치하지만 Studio shell 안의 전용 찾을 수 없음 상태를 렌더링한다. 선행 검증이나 preview가 부족한 직접 진입은 redirect하지 않고 원본과 같은 차단 이유와 다음 행동을 보여 준다.
|
||||
|
||||
Studio layout의 provider는 Studio 내부 client navigation 동안 유지된다. Studio에서 Public으로 가는 `공개 사이트 보기`는 일반 `<a>`를 사용해 전체 문서 이동, `beforeunload`와 세션 초기화를 보존한다.
|
||||
|
||||
라우트 정의에는 `PUBLIC` 또는 `STUDIO` layout group을 명시한다. Studio 인증을 구현할 때는 `STUDIO` group의 access policy만 `session-required`로 전환할 수 있어야 하며 화면 컴포넌트나 URL을 다시 설계하지 않는다.
|
||||
|
||||
## 컴포넌트 이식 규칙
|
||||
|
||||
- 원본 HTML tag, class 이름, 표시 문구, 요소 순서와 ARIA 관계를 유지한다.
|
||||
- 원본 컴포넌트 경계를 가능한 한 유지하되 Next layout과 router hook에만 필요한 변경을 한다.
|
||||
- 대상 generic design-system primitive로 화면을 다시 그리지 않는다.
|
||||
- Public과 Studio가 공유하는 Public renderer는 하나만 유지한다.
|
||||
- 문자열 HTML과 `dangerouslySetInnerHTML`을 도입하지 않는다.
|
||||
- source의 semantic heading, landmark, tab, dialog, live region과 focus restoration을 유지한다.
|
||||
- 검색 dialog는 하나만 렌더링하고 원본처럼 viewport 중앙에 둔다.
|
||||
- Studio editor 탭 전환은 draft를 잃지 않으며 즉시 preview는 gateway 상태를 바꾸지 않는다.
|
||||
|
||||
## 스타일·폰트·자산 보존
|
||||
|
||||
원본의 다음 파일을 시각 기준으로 사용한다.
|
||||
|
||||
- `app/globals.css`
|
||||
- `app/studio.css`
|
||||
- `app/studio-editor.css`
|
||||
- `components/studio/workflow.module.css`
|
||||
- `components/studio/publication-flow.module.css`
|
||||
- `pretendard/dist/web/variable/pretendardvariable.css`
|
||||
- `@fontsource/ibm-plex-mono/400.css`
|
||||
- `@fontsource/ibm-plex-mono/500.css`
|
||||
- `public/favicon.svg`
|
||||
- `public/media/fetch-strategy-boundary.svg`
|
||||
|
||||
대상 `theme.css`의 Tailwind import와 플랫폼 token은 부트 오류 같은 플랫폼 표면을 위해 유지한다. TechLog 전역 스타일은 그 뒤에 unlayered CSS로 한 번만 로드한다. 원본 `globals.css`의 중복 `@import "tailwindcss"`만 제외하며 그 뒤 rule 순서와 선언값은 유지한다.
|
||||
|
||||
다음 값은 재해석하거나 대상 token 값으로 치환하지 않는다.
|
||||
|
||||
- `--canvas`, `--paper`, `--ink`, `--muted`, `--faint`, `--signal` 등 원본 color token
|
||||
- `--shell: 1180px`, `--body-copy: 42rem`
|
||||
- font size, weight, line-height와 letter-spacing
|
||||
- border, radius, shadow와 transition
|
||||
- `1179`, `1050`, `1024`, `980`, `900`, `767`, `420px` breakpoint
|
||||
- reduced-motion 동작과 최소 `44px` interaction target
|
||||
|
||||
Public과 Studio의 최종 computed style은 원본이 기준이다. 자산은 내용 변경 없이 복사하고 build가 제공하는 동일-origin URL을 사용한다.
|
||||
|
||||
## Public 데이터와 query
|
||||
|
||||
Public 콘텐츠는 immutable static catalog adapter가 소유한다. Application query는 catalog port만 사용해 다음 결과를 파생한다.
|
||||
|
||||
- 홈 focus와 latest index
|
||||
- 유형·topic·project filter
|
||||
- 제목·요약·topic·project 검색
|
||||
- Project별 record, decision, activity
|
||||
- 문서 relation과 related content
|
||||
- Release와 Profile
|
||||
|
||||
페이지 컴포넌트는 URL params와 query를 route codec으로 검증한 뒤 application query를 호출한다. 잘못된 filter 값은 원본의 canonical 상태로 정규화하고 검색 query는 URL에 보존한다. 존재하지 않는 slug와 version은 Public 404로 보낸다.
|
||||
|
||||
## Content Format과 공유 renderer
|
||||
|
||||
Case 본문의 Content Format v1 parser, serializer와 `PublicRenderModel` 판별 union을 유지한다. 지원 block은 heading, paragraph, blockquote, ordered/unordered list, code block, data table, callout와 evidence figure다. 지원 inline은 text, emphasis, strong, inline code, link와 status다.
|
||||
|
||||
Public 문서와 Studio 즉시 preview, 검증 preview, publication snapshot은 같은 typed renderer를 사용한다. raw HTML, script, `javascript:` URL과 임의 asset URL은 계속 거절한다.
|
||||
|
||||
## Studio port, adapter와 상태
|
||||
|
||||
Presentation은 `StudioGateway` port만 사용한다. 모든 method는 `Promise`를 반환하고 선택적인 `AbortSignal`을 받는다. 예상 가능한 실패는 RFC 9457 본문, status, code와 retryable 정보를 가진 `StudioGatewayError`로 정규화한다. `AbortError`만 조용히 무시하고 프로그래밍 오류는 runtime boundary로 전달한다.
|
||||
|
||||
Mock adapter는 원본 seed data, cursor, conflict fixture, preview expiry, validation, publication aggregate와 idempotency 동작을 보존한다.
|
||||
|
||||
Studio 상태 축은 다음 값을 유지한다.
|
||||
|
||||
- Editor: `CLEAN`, `DIRTY`, `SAVING`, `CONFLICT`
|
||||
- Validation result: `NOT_RUN`, `INVALID`, `WARNINGS`, `VALID`
|
||||
- Validation freshness: `NONE`, `CURRENT`, `STALE`
|
||||
- Preview: `NONE`, `CURRENT`, `STALE`, `EXPIRED`
|
||||
- Publication: `NEVER_PUBLISHED`, `PUBLISHED`, `UNPUBLISHED`
|
||||
|
||||
저장은 불완전 draft를 허용하고 version을 증가시킨다. 저장 뒤 validation은 `NOT_RUN`과 `NONE`, 기존 preview는 `STALE`이 된다. 게시 준비 검증과 다음 행동 우선순위, warning acknowledgement, publication idempotency와 unpublish 규칙은 원본 계약을 유지한다.
|
||||
|
||||
Studio 세션은 layout이 유지되는 동안만 살아 있다. 새로고침, Public 전체 이동과 `pageshow.persisted === true`에서 새 Mock adapter를 만들고 draft, request와 dialog를 초기화한다. 고정 fixture ID는 seed 상태로 돌아가고 세션 생성 ID는 Studio 전용 찾을 수 없음 상태가 된다.
|
||||
|
||||
## 오류와 빈 상태
|
||||
|
||||
- Boot failure는 기존 `BootErrorShell`이 담당한다.
|
||||
- Public query·render 실패는 Public shell 안의 원본 fatal error surface를 사용한다.
|
||||
- Public empty, no-result와 not-found는 서로 다른 원본 상태를 유지한다.
|
||||
- Studio request failure는 `StudioGatewayError`의 code와 retryable을 기준으로 원본 메시지와 action을 표시한다.
|
||||
- Studio render failure는 `StudioRuntimeBoundary`가 담당한다.
|
||||
- save conflict는 현재 입력을 보존하고 gateway 최신본과 field path를 비교한다.
|
||||
- auto merge와 auto overwrite를 추가하지 않는다.
|
||||
- dirty 내부 이동은 머무르기, 변경 버리기와 저장 후 이동을 제공하고 trigger focus를 복원한다.
|
||||
|
||||
## 인증 유보
|
||||
|
||||
Studio는 인증이 필요한 제품 영역이지만 이번 이식에서는 인증 구현을 범위 밖으로 둔다.
|
||||
|
||||
- Studio route group을 별도로 유지한다.
|
||||
- access policy 전환 지점을 route contract에 둔다.
|
||||
- 현재는 직접 URL 접근을 허용한다.
|
||||
- 가짜 로그인, 임시 계정과 인증된 것처럼 보이는 UI를 추가하지 않는다.
|
||||
- 후속 인증 작업은 Studio 화면 DOM, URL과 gateway contract를 변경하지 않고 route guard와 session adapter를 연결하는 방식으로 수행한다.
|
||||
|
||||
## 테스트 전략
|
||||
|
||||
모든 production 동작 변경은 TDD로 진행한다. 각 slice는 기대 동작을 표현하는 실패 테스트를 먼저 추가하고 예상한 이유로 실패하는 것을 확인한 다음 최소 구현을 추가한다.
|
||||
|
||||
### 구조·계약 테스트
|
||||
|
||||
- 모든 Public·Studio route ID, path, layout group과 runtime module mapping
|
||||
- Public/Studio layout 수명과 Studio gateway 단일 instance
|
||||
- 원본 heading, landmark, class, element 순서와 ARIA 관계
|
||||
- source CSS color, typography, width, breakpoint와 touch target 계약
|
||||
- Public content graph의 유효한 내부 링크와 404 경계
|
||||
- Content Format parser·serializer round trip과 unsafe input 거절
|
||||
|
||||
### 상호작용·상태 테스트
|
||||
|
||||
- 홈 focus tab과 URL 정규화
|
||||
- 탐색 filter, reset, empty와 no-result
|
||||
- 검색 dialog open/close, focus trap·restore와 query 보존
|
||||
- document TOC, code copy와 evidence dialog
|
||||
- Studio document 생성, 편집, 저장, validation, preview, publish와 unpublish
|
||||
- conflict, stale·expired preview, warning acknowledgement와 idempotency
|
||||
- dirty navigation dialog와 native `beforeunload`
|
||||
- 새로고침·Public 이동·bfcache 복원 뒤 세션 reset
|
||||
|
||||
### 접근성·반응형·시각 검증
|
||||
|
||||
동일한 Chromium, font와 reduced-motion 조건에서 원본과 대상을 캡처한다.
|
||||
|
||||
- 고정 fixture로 접근 가능한 모든 canonical Public·Studio route: `360px`, `1440px`
|
||||
- breakpoint 대표 화면: `390`, `768`, `820`, `1024`, `1180px`
|
||||
- 열린 검색 dialog와 mobile menu
|
||||
- Studio editor, 즉시 preview, warning과 dirty-leave dialog
|
||||
- viewport 전체의 예상하지 않은 가로 overflow
|
||||
- axe, keyboard navigation, focus visibility, single H1과 `aria-current`
|
||||
|
||||
동적 timestamp, caret와 animation을 고정한 뒤 pixel difference는 원칙적으로 `0`을 요구한다. 차이는 개선 여부가 아니라 원본과 동일한지로 판정한다. 브라우저 rasterization처럼 통제할 수 없는 차이가 발견되면 원인을 기록하고 사용자의 별도 승인을 받기 전에는 baseline을 갱신하지 않는다.
|
||||
|
||||
CI는 커밋된 target snapshot과 계약 테스트를 사용하며 외부 원본 경로에 의존하지 않는다. 구현 중 로컬 one-time source/target 비교로 baseline을 만들고 이후 target regression test로 고정한다.
|
||||
|
||||
## 검증 명령과 완료 기준
|
||||
|
||||
구현 완료 전에 다음 범주를 모두 실행한다.
|
||||
|
||||
- TechLog route·component·integration test
|
||||
- TechLog Studio gateway·state·publication test
|
||||
- Playwright visual·accessibility test
|
||||
- TypeScript 전체 project 검사
|
||||
- ESLint
|
||||
- architecture, route registry, design-system과 i18n contract 검사
|
||||
- production build
|
||||
- repository full test suite
|
||||
|
||||
현재 저장소에 이미 기록된 RLIMIT, EMFILE, umask와 `/tmp` 관련 19개 환경 의존 실패는 known baseline으로 분리한다. 그 외 새 실패를 허용하지 않으며 TechLog 이식으로 추가된 테스트는 모두 통과해야 한다. 병합 전에는 known baseline과 새 실패를 구분한 결과를 사용자에게 보고한다.
|
||||
|
||||
완료 조건은 다음과 같다.
|
||||
|
||||
1. 원본 Public과 Studio canonical URL이 대상에서 모두 열리고 정의되지 않은 경로가 올바른 404를 반환한다.
|
||||
2. 원본의 표시 콘텐츠, DOM·ARIA 구조, CSS 계산값, font와 asset이 유지된다.
|
||||
3. Public 검색·탐색·문서 연결과 Studio 전체 Mock workflow가 원본과 동일하게 동작한다.
|
||||
4. Studio 내부 이동 동안 상태가 유지되고 전체 문서 이동·새로고침·bfcache에서 초기화된다.
|
||||
5. 대상의 clean architecture, route registry, 부트·진단·서비스 워커 계약이 유지된다.
|
||||
6. 승인되지 않은 시각 diff와 새 test failure가 없다.
|
||||
|
||||
## 범위 밖
|
||||
|
||||
- Studio 인증과 권한
|
||||
- 실제 HTTP Studio adapter와 백엔드 연결
|
||||
- PostgreSQL, Cloudflare D1·R2와 파일 업로드
|
||||
- Public 콘텐츠 CMS화
|
||||
- 디자인 개선, 문구 수정과 정보 구조 재해석
|
||||
- 원본에 없는 화면이나 기능 추가
|
||||
Reference in New Issue
Block a user