Initial import

This commit is contained in:
how2ice
2026-04-21 03:33:23 +09:00
commit 9e4b70f1f1
495 changed files with 94680 additions and 0 deletions

155
docs/README.md Executable file
View File

@@ -0,0 +1,155 @@
# Docs Guide
프로젝트 문서는 작업일지, 기능 문서, 컴포넌트 문서를 기본 축으로 운영합니다. 현재 메인 앱 `Docs` 화면은 `docs/**/*.md`를 동적으로 수집해 폴더별로 노출합니다.
## 0. 임시 로컬 모드
- 현재 저장소는 당분간 로컬 전용으로 운영합니다.
- 문서 정리나 Codex 작업 시 Git 원격 동기화, 브랜치 운영, 자동 merge 흐름은 기본 전제로 사용하지 않습니다.
- `Codex Live`, 일반 채팅, 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 `main` 작업본을 바로 수정**하는 방식으로 처리합니다.
- Git 관련 작업은 사용자가 명시적으로 요청할 때만 수행합니다.
## 1. 작업일지
- 위치: `docs/worklogs`
- 규칙: 날짜별 1개 Markdown 파일 작성
- 파일명 예시: `2026-03-31.md`
- 템플릿: `docs/templates/worklog-template.md`
- 권장 기록 범위: 구현 내용, 구조 변경, 빌드/배포 이슈, Git 작업 내역
- 최근 작업일지는 날짜별로 계속 누적 기록
- 화면 캡처는 `docs/assets/worklogs/YYYY-MM-DD/` 아래에 저장하고 작업일지에서 상대 경로로 연결
- 캡처는 전체 화면보다 작업한 컴포넌트 영역 단위 이미지를 우선 사용
- 메뉴/기능 증적이 필요하면 `capture:menu`, `capture:feature` 스크립트로 화면 단위 캡처를 함께 남김
- 화면 캡처를 남기지 못한 날에도 `## 화면 캡처` 섹션은 유지하고, 미첨부 사유를 한 줄로 기록
- 문서 최신화 작업을 수행한 날에는 어떤 문서를 왜 수정했는지 함께 기록
권장 항목:
- 오늘 작업한 내용
- 이슈 및 해결 과정
- 결정 사항
- 상세 작업 내역
## 2. 기능 문서
- 위치: `docs/features`
- 규칙: 기능 단위로 Markdown 파일 작성
- 파일명 예시: `auth.md`, `dashboard.md`
- 템플릿: `docs/templates/feature-template.md`
- 권장 기록 범위: 기능 목적, 화면 흐름, API/상태, 테스트 포인트
- `docs/features/*.md`를 추가하거나 수정하면 앱 `Docs / 기능문서` 메뉴에 반영됨
- `src/features/**/*.md`는 프로젝트 내부 전용 설명 문서용이며 메인 `Docs` 메뉴의 기본 수집 대상은 아님
권장 항목:
- 기능 목적
- 주요 화면/흐름
- 데이터 구조 및 API
- 예외 처리
- 테스트 포인트
## 3. 컴포넌트 문서
- 위치: `docs/components`
- 규칙: 컴포넌트별 1개 Markdown 파일 작성
- 파일명 예시: `status-badge.md`, `user-card.md`
- 대표 샘플: 각 컴포넌트의 `samples/Sample.tsx`
- 확장 샘플: `samples/*.tsx`
권장 항목:
- 목적
- 폴더 구조
- UI props
- plugin input/output 규칙
- plugin 합성 규칙
- Sample 활용 예시
현재 기준 주요 컴포넌트 구조:
```text
src/components
├─ markdownPreview
├─ navigation
├─ previewer
├─ search
├─ status-badge
└─ window
```
공통 플러그인 타입과 유틸은 `src/types/component-plugin.ts` 에서 관리합니다.
샘플 운영 규칙:
- `samples/Sample.tsx`는 해당 컴포넌트의 가장 기본형만 표현
- plugin/feature 예시는 `samples/*.tsx`로 분리
- 샘플 목록에서는 같은 컴포넌트 기준으로 묶고 `base -> plugin -> feature` 순서로 정렬
## 4. 샘플/위젯 레이아웃
- 컴포넌트 샘플 레이아웃: 좌측 컴포넌트 목록 + 우측 상세 카드
- 상세 카드는 컴포넌트 하나당 1개
- 카드 내부는 `Base Sample` 아래에 `Plugin Samples`, `Feature Samples`를 순차적으로 배치
- 위젯 샘플은 `widgets/**/samples/*.tsx` 기준으로 별도 수집
- 실제 샘플 엔트리 로딩은 `src/app/manifests/samples.manifest.ts`, `src/samples/registry.ts`를 기준으로 동작
## 5. 프로젝트 종속 레이아웃
- 위치: `src/features/layout`
- 대상: 현재 프로젝트 화면에서만 사용하는 레이아웃
- 예시: 컴포넌트 샘플 목록, 위젯 샘플 목록, Docs markdown preview, Plan 게시판
프로젝트 종속 기능 규칙:
- 현재 프로젝트에서만 의미 있는 화면/기능은 `src/features` 아래에 둠
- 예: `Plan 게시판`, 대시보드 feature 샘플, 앱 전용 레이아웃
- 공통 컴포넌트/위젯으로 재사용 가능한 항목은 `src/components`, `src/widgets`에 유지
메인 화면 분리 규칙:
- 위치: `src/app/main`
- 구성: `MainView`, `MainHeader`, `MainSidebar`, `MainContent`
- 목적: 상단 메뉴, 사이드바, 본문, 검색/문서/Plan 흐름을 앱 레벨에서 분리
## 6. Markdown Preview
- 공통 markdown preview는 `src/components/markdownPreview` 아래에서 관리
- `basePath`를 받아 특정 폴더 아래 markdown 문서를 재사용 가능하게 렌더링
- `docs` 문서 영역은 좌측 폴더/문서 트리 + 우측 markdown 카드 목록 구조 사용
- 문서 수집 매니페스트는 `src/app/manifests/docs.manifest.ts`에서 관리
- `docs/features`, `docs/components`, `docs/worklogs`, `docs/templates`는 폴더 단위로 자동 분류됨
- `docs/worklogs`는 최신 날짜가 먼저 보이도록 역순 정렬
## 7. 대시보드 위젯/데이터
- 대시보드 카드 위젯은 `src/widgets/dashboard-report-card`
- 위젯 샘플과 프로젝트 종속 샘플은 분리
- 재사용 가능한 샘플 데이터는 `src/data` 아래에서 관리
- 프로젝트 전용 대시보드 샘플은 `src/features/dashboard`에 둠
## 8. 배포 메모
- Nexus publish 대상 registry는 `package.json``publishConfig.registry`
- alpha 버전 배포는 `npm publish --tag alpha`
- Nexus 인증은 `~/.npmrc``username / _password(base64) / email` 방식으로 확인
## 9. etc 운영 기준
- 부가 서버/DB/타언어 프로젝트는 `etc` 아래에서 분리 관리
- 서버 예시: `etc/servers/work-server`
- DB 예시: `etc/db/work-db`
- `etc` 내부 비밀값과 생성물은 커밋 제외
- `.env`
- `node_modules`
- `dist`
- `*.log`
## 10. Plan 기능 문서 메모
- `Plan` 기능은 `src/features/planBoard`에서 관리
- 현재 앱에는 목록/상세 보드, release 검수, 차트, 스케줄 화면이 함께 포함됨
- 기본 상태는 `등록`, `작업중`, `작업완료`, `릴리즈완료`, `완료`
- 자동화 진행은 `workerStatus`로 별도 관리하며 브랜치 준비, 자동 작업, release/main 반영 상태를 표현
- `작업시작` 이후에는 원본 요청(`작업 ID`, 원본 메모`)을 수정하지 않고 조치 이력으로 누적 기록
- 권한 토큰이 없으면 조회 중심으로 동작하며 민감 메모와 소스 작업 일부는 제한 또는 마스킹
- 관련 기능 문서는 `docs/features/plan-board-review.md`, `plan-automation.md`, `plan-schedule.md`, `plan-usage.md` 참고

View File

@@ -0,0 +1,249 @@
# Chat Frontend Rewrite Plan
## Goal
Rebuild the chat frontend around explicit feature boundaries instead of one large panel. The rewrite keeps the current server contract for now, but removes direct API coupling from UI components and preserves the current mobile visual structure.
## Non-Goals
The first rewrite wave does not change:
1. Work server REST endpoint shapes
2. Work server WebSocket event payload shapes
3. Existing chat database schema
4. Current mobile interaction model and visual hierarchy
## Current Pain Points
The existing frontend mixes too many concerns in the same render tree:
1. Session routing and panel selection
2. Conversation list fetch and sort
3. Conversation detail recovery
4. Composer draft and upload lifecycle
5. WebSocket connection and reconnect
6. Runtime dashboard fetch and live updates
7. Error log loading
8. Notification unread sync
9. Visibility, focus, reconnect, and page restore sync
This creates three classes of failures:
1. Render loops from unstable effect dependencies
2. Request storms from duplicate fetch paths
3. Partial outages where one failing concern blanks the whole chat workspace
## Rewrite Strategy
The rewrite is frontend-first, but not frontend-only in architecture. The new frontend must assume that API latency and WebSocket reconnects can fail, and each feature controller must degrade independently.
### Guiding Rules
1. UI components do not call REST helpers directly
2. UI components do not build WebSocket URLs directly
3. One feature controller owns one feature's network lifecycle
4. Shell state never performs data fetches
5. Mobile layout is preserved while data flow is replaced under it
## Feature Inventory
### 1. Workspace Shell
Responsibilities:
1. Hold `chat | runtime | errors` selection
2. Hold active session id
3. Hold mobile split-pane visibility
4. Compose feature panes
Rules:
1. No direct REST calls
2. No direct socket usage
3. No data caching
### 2. Conversation List
Responsibilities:
1. Load room summaries
2. Search and sort locally
3. Create, rename, delete, and select rooms
4. Expose unread and processing badges
Rules:
1. One fetch source
2. One short-lived in-flight dedupe layer
3. No room detail fetch inside the list controller
### 3. Conversation Room
Responsibilities:
1. Load one room detail
2. Merge server messages with optimistic local state
3. Recover state after reconnect
4. Mark replies as read
Rules:
1. Only one active detail request at a time
2. Detail loading must never fan out into list reloads
3. Loading and recovery state must be local to the active room
### 4. Composer
Responsibilities:
1. Hold draft and attachments
2. Submit queue or direct requests
3. Retry, cancel, and delete pending items
4. Manage optimistic user/request messages
Rules:
1. No list-wide refresh on send
2. No runtime refresh coupled to draft input
### 5. Live Connection
Responsibilities:
1. Open and maintain the shared chat socket
2. Route message, job, runtime, and activity events
3. Reconnect with bounded recovery
4. Publish connection state through a shared adapter
Rules:
1. No duplicate context writes
2. Background transitions are throttled
3. Reconnect only restores the active room by default
### 6. Runtime
Responsibilities:
1. Load runtime snapshot
2. Show queue and active jobs
3. Load per-job detail
4. Support remove and cancel actions
Rules:
1. Runtime refresh is separate from room detail refresh
2. Runtime failure must not blank the chat room UI
### 7. Error Viewer
Responsibilities:
1. Load error log list
2. Render error detail and resource previews
Rules:
1. Fully isolated from chat room state
### 8. Notification Integration
Responsibilities:
1. Unread badges
2. Notification center list/detail
3. Offline room notifications
Rules:
1. No room detail polling from notification badge refresh
2. No direct dependency from notification UI to chat room rendering
## New Frontend Layers
### A. UI Layer
Files under `components/` and pane files.
Responsibilities:
1. Render props only
2. Emit user actions only
### B. Feature Controller Layer
Files under `hooks/`.
Responsibilities:
1. Manage one feature's state machine
2. Translate UI actions to gateway calls
3. Own loading and error state
### C. Gateway Layer
Files under `data/`.
Responsibilities:
1. Wrap all chat REST calls
2. Wrap all chat socket entry points
3. Normalize fallback behavior and timeouts in one place
This is the critical separation that the old frontend does not have.
## Target Folder Shape
```text
src/app/main/chatV2/
ChatWorkspaceV2.tsx
types.ts
data/
chatGateway.ts
chatConnectionGateway.ts
hooks/
useChatWorkspaceState.ts
useConversationListController.ts
useConversationRoomController.ts
useComposerController.ts
useRuntimeController.ts
useNotificationController.ts
components/
ConversationListPane.tsx
ConversationRoomPane.tsx
Composer.tsx
RuntimePane.tsx
ErrorPane.tsx
```
## Migration Waves
### Wave 1
1. Freeze mobile layout
2. Introduce chatV2 gateway layer
3. Move list/detail/runtime access behind the gateway
### Wave 2
1. Replace list controller
2. Replace room controller
3. Replace composer controller
### Wave 3
1. Reconnect runtime and notifications through new controllers
2. Remove old `MainChatPanel` effect chains
### Wave 4
1. Make `MainChatPanel` a thin compatibility wrapper or replace it entirely
## Success Criteria
1. Main load triggers one list fetch
2. Opening one room triggers one detail fetch
3. No direct browser fallback to external `:3100` ports on remote hosts
4. WebSocket and REST routing live in one gateway boundary
5. One pane can fail without blanking the others
6. Mobile layout matches the pre-rewrite visual structure

34
docs/components/check-combo.md Executable file
View File

@@ -0,0 +1,34 @@
# Check Combo Input
## 목적
`code/value` 데이터를 받아 여러 항목을 체크박스 형태로 선택하고, 실제 값은 `code[]`로 유지하는 다중 선택 combo 입력 컴포넌트입니다.
## 폴더 구조
```text
src/components/inputs/checkCombo
├─ CheckComboUI.tsx
├─ index.ts
├─ plugins/
├─ samples/
└─ types/
```
## 주요 props
- `data: { code, value }[]`
- `value`, `defaultValue`
- `onChange(codes, items)`
- `showSearch`
- `allowClear`
- `placeholder`
## plugins
- `createCheckComboPlaceholderPlugin`
- `createCheckComboSortPlugin`
## 샘플
- 대표 샘플: `src/components/inputs/checkCombo/samples/Sample.tsx`

View File

@@ -0,0 +1,35 @@
# Codex Diff Previewer
## 목적
변경 파일의 전체 소스와 raw diff를 codex 스타일 아코디언으로 함께 보여주는 공통 preview 컴포넌트입니다.
## 폴더 구조
```text
src/components/previewer
├─ CodexDiffBlock.tsx
├─ CodexDiffPreviewer.tsx
├─ CodexDiffPreviewer.css
├─ samples/
│ └─ CodexDiffSample.tsx
└─ index.ts
```
## 주요 props
- `files`
- `diffText`
- `title`
- `description`
- `height`
## 공통 사용처
- 작업일지 `## 소스`
- 일반 문서의 ````diff```` 코드 블록
- previewer 샘플 갤러리
## 샘플
- 대표 샘플: `src/components/previewer/samples/CodexDiffSample.tsx`

View File

@@ -0,0 +1,130 @@
# 신규 컴포넌트 후보 2차 정리
## 신규 컴포넌트 후보 7차 제안
### 목적
현재 `release` 브랜치 기준으로 기존 컴포넌트와 겹치지 않는 신규 공통 컴포넌트 후보를 제안합니다.
이 글은 검토용 plan 게시판 작성만 수행하며, 자동화 접수는 하지 않고 미접수 상태로 유지합니다.
### release 기준 확인
- 이미 존재: Dashboard Report Card, Progress/MultiProgress, Search Command, Popup/Select/CheckCombo 입력, Markdown Preview, Previewer/Codex Diff, Status Badge, Window, DataListTable, EmbeddedMap, TextMemo/GPS/API 샘플 위젯
- 제안 방향: Plan/Board/History 화면에서 반복될 가능성이 높지만 아직 공통 컴포넌트로 분리되지 않은 조합형 UI
### 신규 후보
#### 1. Query Filter Builder UI
복수 조건 필터를 행 단위로 추가하고 저장할 수 있는 필터 빌더 컴포넌트입니다.
- 적용 위치: Plan Board 고급 필터, History 검색, Board 검색
- 주요 props: `fields`, `operators`, `value`, `onChange`, `presets`, `compact`
- 기대 효과: 화면마다 흩어질 수 있는 필터 조건 UI를 일관된 패턴으로 정리
#### 2. Timeline Activity Feed UI
작업 상태 변경, 접수, release/main 반영, 오류 이벤트를 시간순으로 보여주는 활동 피드 컴포넌트입니다.
- 적용 위치: Plan 상세, History 상세, 자동화 실행 이력
- 주요 props: `items`, `groupByDate`, `statusResolver`, `dense`, `renderMeta`
- 기대 효과: 로그성 텍스트를 추적 가능한 UI로 전환하고 최근 변경 맥락을 빠르게 파악
#### 3. Evidence Attachment Strip UI
스크린샷, diff, 로그, 링크 같은 증빙 자료를 한 줄 카드 목록으로 노출하는 첨부 스트립 컴포넌트입니다.
- 적용 위치: Plan 검증 증빙, Preview 결과, History 상세
- 주요 props: `attachments`, `onPreview`, `onDownload`, `maxVisible`, `variant`
- 기대 효과: 증빙 자료 표시와 미리보기 진입점을 공통화
### 우선순위 제안
1. Query Filter Builder UI
2. Timeline Activity Feed UI
3. Evidence Attachment Strip UI
우선 1번을 먼저 검토하는 것이 좋습니다. Plan Board와 History에서 필터 조건이 계속 늘어날 가능성이 높아 재사용 효과가 가장 큽니다.
## 목적
기존에 개발 완료된 `FormField`, `StateKit`, `DataListTable`과 이미 개발 접수된 `Action Toolbar UI`, `Detail Inspector Panel`, `Timeline / Activity Log UI`, `Confirm Dialog UI`, `Notification Toast / Action Feedback UI`, `Date Range Input`, `File Attachment List`, `Component Usage Doc Card`, `Split Pane Layout`은 이번 후보에서 제외합니다.
이번 문서는 현재 코드베이스와 기존 Board/Plan 접수 이력에 없는 신규 공통 컴포넌트만 다시 추려 이후 Plan 후속 작업 후보를 만드는 목적입니다.
## 제외 기준
- 이미 구현 완료된 공통 컴포넌트는 중복 후보로 다시 올리지 않음
- 이미 Board/Plan에서 개발 접수된 컴포넌트는 신규 후보에서 제외
- 앱 전용 화면 조합보다 여러 기능에서 재사용 가능한 공통 UI를 우선 선정
## 신규 후보
### 1. Drawer / Side Sheet UI
본문 흐름을 끊지 않고 우측 또는 하단에서 보조 편집 화면을 여는 컴포넌트입니다.
- 적용 위치: Plan 상세 보조 편집, 설정 화면, 모바일 상세 패널
- 주요 props: `open`, `placement`, `width`, `title`, `footer`, `onClose`
- 기대 효과: 전체 화면 전환 없이 보조 작업을 열고 닫는 패턴을 공통화
### 2. Description List / Key Value Summary UI
상세 정보 화면에서 라벨과 값을 읽기 전용으로 정리하는 컴포넌트입니다.
- 적용 위치: Plan 메타 정보, 방문 이력 상세, 앱 설정 요약
- 주요 props: `items`, `columns`, `size`, `labelWidth`, `copyable`
- 기대 효과: 상세 화면마다 반복되는 메타 정보 레이아웃을 줄임
### 3. Stepper / Process Flow UI
등록, 작업중, `release` 반영, `main` 반영 같은 단계를 순서형으로 보여주는 컴포넌트입니다.
- 적용 위치: Plan 상태 흐름, 배포 진행 표시, 자동화 단계 요약
- 주요 props: `steps`, `current`, `status`, `direction`, `compact`
- 기대 효과: 텍스트 상태 나열보다 현재 단계와 다음 단계를 직관적으로 전달
### 4. Tag Input UI
여러 키워드나 라벨을 직접 추가하고 삭제하는 입력 컴포넌트입니다.
- 적용 위치: Board 태그, 검색 조건 저장, 증적 분류, 빠른 필터 조합
- 주요 props: `value`, `suggestions`, `maxTags`, `allowCustom`, `onChange`
- 기대 효과: 다중 조건 입력을 `select`와 별도로 다뤄 반복 필터 구성이 쉬워짐
### 5. Breadcrumb / Context Path UI
현재 위치와 상위 경로를 짧게 보여주는 탐색 보조 컴포넌트입니다.
- 적용 위치: Docs 상세, Components 샘플 상세, History 상세 진입 경로
- 주요 props: `items`, `separator`, `compact`, `onNavigate`
- 기대 효과: 깊은 메뉴 구조에서 현재 위치 파악과 상위 이동 비용을 낮춤
### 6. Property Grid UI
설정값이나 옵션 목록을 2열 또는 다열로 배치해 빠르게 편집하는 설정형 컴포넌트입니다.
- 적용 위치: 앱 설정, 자동화 설정, 위젯 옵션 편집
- 주요 props: `sections`, `fields`, `columns`, `readonly`, `onChange`
- 기대 효과: 설정 폼을 긴 세로 나열 대신 밀도 있게 구성 가능
## 권장 진행 순서
1. `Description List / Key Value Summary UI`
2. `Stepper / Process Flow UI`
3. `Drawer / Side Sheet UI`
4. `Tag Input UI`
5. `Property Grid UI`
6. `Breadcrumb / Context Path UI`
## 검증 기준
- 모바일 폭에서 `drawer`, `stepper`, `property grid`가 가로 넘침 없이 동작하는지 확인
- Plan 상세와 설정 화면에 붙였을 때 기존 `antd` 기본 컴포넌트 조합보다 반복 코드가 줄어드는지 확인
- 읽기 전용 화면과 편집 화면에서 같은 컴포넌트를 무리 없이 재사용할 수 있는지 확인
## 메모
- 다음 후보 구현 시에는 `samples/BaseSample.tsx`, `samples/Sample.tsx`, 필요 시 `plugins/*.plugin.ts`를 같은 묶음으로 준비
- Docs 문서에는 목적, 주요 props, 적용 위치, 확장 포인트를 함께 기록

View File

@@ -0,0 +1,46 @@
# Evidence Attachment Strip UI
## 목적
Plan/Board 계열 화면에서 반복되는 산출물 카드, 링크, 미리보기 진입 UI를 공통 스트립으로 정리하는 컴포넌트입니다.
## 지원 타입
- `image`
- `markdown`
- `code`
- `text`
- `json`
- `preview`
- `video`
- `audio`
- `pdf`
- `empty`
## 주요 props
- `attachments`
- `onPreview`
- `onCopy`
- `maxVisible`
- `compact`
- `emptyText`
- `title`
- `description`
## 기본 액션
- 링크 열기
- 복사
- 미리보기 진입
## 적용 예시
- `PlanBoardPage``WorklogEvidenceTab` 산출물 Preview 영역
- 작업일지/스크린샷/로그/preview 링크 혼합 카드 목록
- 모바일 보조 패널 또는 상세 모달의 compact 첨부 목록
## 확장 포인트
- `EvidenceAttachmentPreviewBody`를 별도 export 하므로 상세 모달 본문에서 같은 렌더러를 재사용할 수 있습니다.
- `copyValue`, `language`, `format`을 항목별로 제어해 코드/텍스트/경로형 산출물 표현을 조정할 수 있습니다.

66
docs/components/input.md Executable file
View File

@@ -0,0 +1,66 @@
# Input
## 목적
Ant Design `Input`을 기반으로 하되, 타이핑 중에는 내부 상태만 변경하고 `Enter` 또는 `blur` 시점에만 외부 `onChange`를 호출하는 입력 컴포넌트입니다.
## 폴더 구조
```text
src/components/inputs/primitives/input
├─ plugins/
│ ├─ index.ts
│ └─ input.plugin.ts
├─ samples/
│ ├─ Sample.tsx
│ └─ ValidInputSample.tsx
├─ types/
│ ├─ index.ts
│ └─ input.ts
├─ InputUI.tsx
└─ index.ts
```
## 동작 방식
- 입력 중에는 내부 `draftValue`만 변경
- `Enter` 시 외부 `onChange` 호출
- `blur` 시 외부 `onChange` 호출
- 검증이나 추가 기능은 `commitPlugins`로 주입
- 나머지 props는 Ant Design `InputProps`를 그대로 전달
## 샘플 규칙
- `samples/Sample.tsx`: 기본형 `InputUI`
- `samples/ValidInputSample.tsx`: validation plugin을 적용한 확장 샘플
## 사용 예시
```tsx
<InputUI
value={value}
placeholder="입력 후 Enter"
onChange={(event) => {
setValue(event.target.value);
}}
/>
```
```tsx
<InputUI
value={value}
placeholder="3글자 이상만 허용"
commitPlugins={[
createValidInputPlugin(({ nextValue }) => nextValue.trim().length >= 3),
]}
onChange={(event) => {
setValue(event.target.value);
}}
/>
```
## 참고
- 외부에서 제어하는 값은 확정 시점에만 변경됩니다.
- `InputUI.tsx` 하나만 두고 기능은 plugin으로 확장하는 구조입니다.
- API 게시판이나 문서 예시는 `samples/Sample.tsx`를 대표 샘플로 사용합니다.

35
docs/components/popup.md Executable file
View File

@@ -0,0 +1,35 @@
# Popup Input
## 목적
`[input][button][readonly input]` 형태로 검색어 입력, 버튼 액션, 선택 결과 표시를 한 줄에서 처리하는 입력 컴포넌트입니다.
## 폴더 구조
```text
src/components/inputs/popup
├─ PopupUI.tsx
├─ index.ts
├─ plugins/
├─ samples/
└─ types/
```
## 주요 props
- `value`, `defaultValue`
- `resultValue`
- `onChange`
- `onButtonClick`
- `buttonText`
- `inputPlaceholder`
- `resultPlaceholder`
## plugins
- `createPopupButtonTextPlugin`
- `createPopupResultPlaceholderPlugin`
## 샘플
- 대표 샘플: `src/components/inputs/popup/samples/Sample.tsx`

30
docs/components/previewer-ui.md Executable file
View File

@@ -0,0 +1,30 @@
# Previewer UI
## 목적
다양한 데이터를 공통 카드 형태로 미리보기할 수 있는 previewer 컴포넌트입니다.
## 지원 타입
- `text`
- `json`
- `code`
- `image`
- `markdown`
- `empty`
## 추가로 유용한 preview 타입
- `markdown`: 작업일지, 문서 미리보기
- `empty`: 파일 미선택, 데이터 없음 상태
## 주요 props
- `type`
- `title`
- `description`
- `value`
- `language`
- `imageAlt`
- `height`
- `toolbar`

View File

@@ -0,0 +1,108 @@
# Process Flow UI
## 목적
Plan, Board, History 화면에서 공통으로 사용할 수 있는 단계형 진행 표시 컴포넌트입니다.
현재 단계, 완료 단계, 실패 단계, 다음 대기 단계를 한 번에 보여주며 가로/세로 배치와 compact 모드를 지원합니다.
## 폴더 구조
```text
src/components/processFlow
├─ samples/
│ └─ BaseSample.tsx
├─ types/
│ ├─ index.ts
│ └─ process-flow.ts
├─ ProcessFlowUI.css
├─ ProcessFlowUI.tsx
└─ index.ts
```
## 기본 Props
```ts
type ProcessFlowStepStatus = 'complete' | 'current' | 'failed' | 'pending';
type ProcessFlowStep = {
key: string;
label: ReactNode;
description?: ReactNode;
status?: ProcessFlowStepStatus;
};
type ProcessFlowUIProps = {
steps: ProcessFlowStep[];
currentStepKey?: string;
direction?: 'horizontal' | 'vertical';
compact?: boolean;
showConnector?: boolean;
onStepClick?: (step: ProcessFlowStep, index: number) => void;
statusIcons?: Partial<Record<ProcessFlowStepStatus, ReactNode>>;
statusStyles?: Partial<Record<ProcessFlowStepStatus, Partial<ProcessFlowStatusAppearance>>>;
statusLabels?: Partial<Record<ProcessFlowStepStatus, string>>;
};
```
## 동작 규칙
- `step.status` 가 있으면 해당 상태를 우선 사용합니다.
- `step.status` 가 없으면 `currentStepKey` 기준으로 이전 단계는 `complete`, 현재 단계는 `current`, 이후 단계는 `pending` 으로 계산합니다.
- `direction="horizontal"` 은 넓은 화면에서 단계 흐름을 한 줄로 보여주고, 모바일 폭에서는 자동으로 세로 스택으로 바뀝니다.
- `direction="vertical"` 은 긴 설명이나 운영 절차처럼 텍스트가 많은 흐름에 적합합니다.
- `compact` 는 카드 안쪽, 테이블 상세, 모바일 요약 영역처럼 밀도가 필요한 구간에 사용합니다.
## 기본 예시
```tsx
import { ProcessFlowUI } from '@/components/processFlow';
const steps = [
{ key: 'created', label: '등록' },
{ key: 'working', label: '작업중' },
{ key: 'done', label: '작업완료' },
{ key: 'released', label: '릴리즈완료' },
{ key: 'completed', label: '완료' },
];
export function PlanStatusFlow() {
return <ProcessFlowUI steps={steps} currentStepKey="working" />;
}
```
## 상태 확장 예시
```tsx
import { CheckOutlined, SyncOutlined } from '@ant-design/icons';
<ProcessFlowUI
steps={steps}
currentStepKey="released"
statusIcons={{
complete: <CheckOutlined />,
current: <SyncOutlined spin />,
}}
statusStyles={{
current: {
accent: '#7c3aed',
accentSoft: 'rgba(124, 58, 237, 0.12)',
border: 'rgba(124, 58, 237, 0.22)',
background: 'linear-gradient(180deg, rgba(245, 243, 255, 0.98) 0%, rgba(237, 233, 254, 0.84) 100%)',
text: '#4c1d95',
connector: 'rgba(124, 58, 237, 0.28)',
},
}}
/>
```
## 적용 예시
- Plan 상세: `등록 -> 작업중 -> 작업완료 -> 릴리즈완료 -> 완료`
- release/main 반영 대기 흐름 표시
- Board 자동화 접수 후 현재 진행 단계 요약
## 재사용 가이드
- 공통 운영 상태를 이미 문자열로 보유하고 있다면 화면 레이어에서 `steps``currentStepKey` 로만 변환해서 바로 사용할 수 있습니다.
- 상태 라벨이 운영 용어와 다르면 `statusLabels` 로 화면별 텍스트만 바꿉니다.
- 프로젝트 고유 색이나 아이콘이 필요하면 `statusStyles`, `statusIcons` 만 덮어쓰고 기본 레이아웃은 유지합니다.

View File

@@ -0,0 +1,23 @@
# Search Command
## 목적
문서, API, 컴포넌트, 위젯을 키워드로 빠르게 찾고 바로 이동할 수 있는 통합 검색 모달입니다.
## 특징
- `AutoComplete` 기반 추천 드롭다운
- 모달 오픈 시 입력창 자동 포커스
- `Enter`, 항목 선택, 바깥 클릭, `Esc`로 닫기/이동
- 모바일 상단 제스처와 연결 가능
## 주요 props
- `open`
- `options`
- `onClose`
## 샘플/연결
- `src/components/search/SearchCommandModal.tsx`
- `src/layer/search/context/SearchLayerContext.tsx`

34
docs/components/select.md Executable file
View File

@@ -0,0 +1,34 @@
# Select Input
## 목적
`code/value` 데이터를 받아 실제 값은 `code`로 유지하고, 드롭다운 표시와 검색은 `value` 기준으로 처리하는 select combo 입력 컴포넌트입니다.
## 폴더 구조
```text
src/components/inputs/select
├─ SelectUI.tsx
├─ index.ts
├─ plugins/
├─ samples/
└─ types/
```
## 주요 props
- `data: { code, value }[]`
- `value`, `defaultValue`
- `onChange(code, item)`
- `showSearch`
- `allowClear`
- `placeholder`
## plugins
- `createSelectPlaceholderPlugin`
- `createSelectSortPlugin`
## 샘플
- 대표 샘플: `src/components/inputs/select/samples/Sample.tsx`

106
docs/components/status-badge.md Executable file
View File

@@ -0,0 +1,106 @@
# StatusBadge
## 목적
상태 값을 간단한 UI 배지로 표현하는 컴포넌트입니다.
## 폴더 구조
```text
src/components/status-badge
├─ plugins/
│ ├─ index.ts
│ └─ status-badge.plugin.ts
├─ samples/
│ └─ Sample.tsx
├─ types/
│ ├─ index.ts
│ └─ status-badge.ts
├─ StatusBadgeUI.tsx
└─ index.ts
```
## 구성 원칙
- `StatusBadgeUI.tsx`: 실제 UI 렌더링
- `types/`: props, plugin input 타입 관리
- `plugins/`: 외부 입력 변환, props 후처리, 커링 플러그인 관리
- `samples/Sample.tsx`: 대표 샘플
공통 플러그인 제네릭은 `src/types/component-plugin.ts` 에서 관리합니다.
## 기본 Props
```ts
type StatusBadgeProps = {
label: string;
tone?: 'success' | 'warning' | 'error' | 'processing' | 'default';
};
```
## Plugin Input 예시
```ts
type StatusBadgePluginInput = {
text: string;
status?: 'ready' | 'working' | 'blocked' | 'done';
};
```
## 공통 Plugin 타입
```ts
type PropsPlugin<TProps> = (props: TProps) => TProps;
type ComponentPlugin<TInput, TProps = TInput> = (input: TInput) => TProps;
type ComponentPluginFactory<TArgs extends unknown[] = [], TInput = unknown, TProps = TInput> =
(...args: TArgs) => ComponentPlugin<TInput, TProps>;
```
`PropsPlugin<TProps>` 는 질문하신 `plugin<T>(T props) => T` 형태를 직접 표현합니다.
`ComponentPlugin``ComponentPluginFactory` 는 입력 타입과 UI props 타입이 다르거나, 커링이 필요한 경우까지 확장하기 위한 타입입니다.
## 여러 Plugin 합성
```ts
function plugins<TProps>(
props: TProps,
pluginList: ReadonlyArray<PropsPlugin<TProps>>,
): TProps
```
`plugins<T>(props, Plugin[])` 형태로 여러 props 플러그인을 순서대로 적용합니다.
## Plugin 구현 예시
```ts
const mapStatusBadgeInputToProps: ComponentPlugin<
StatusBadgePluginInput,
StatusBadgeProps
> = (input) => ({
label: input.text,
tone: input.status ? statusToneMap[input.status] : 'default',
});
const createStatusBadgeTonePlugin: ComponentPluginFactory<
[options?: StatusBadgePluginOptions],
StatusBadgeProps
> = (options) => (props) => ({
...props,
tone: props.tone === 'default' ? options?.fallbackTone ?? 'default' : props.tone,
});
```
## Sample 활용 목적
- API 게시판에서 예제 UI 노출
- 문서 페이지에서 동작 방식 설명
- QA 시 컴포넌트 빠른 확인
## 확장 방향
- `map input -> props``props -> props` 플러그인 체인을 조합
- `plugins(props, pluginList)` 로 여러 후처리 플러그인 적용
- 기본형은 `PropsPlugin<TProps>` 로, 확장형은 `ComponentPluginFactory` 로 표준화
- 샘플 자동 수집 페이지 구성

43
docs/components/stepper.md Executable file
View File

@@ -0,0 +1,43 @@
# Stepper
## 목적
여러 단계를 순서대로 표시하고 현재 진행 위치를 강조하는 stepper 컴포넌트입니다.
## 구현 위치
```text
src/components/stepper
├─ StepperUI.tsx
├─ index.ts
├─ types.ts
└─ samples/
└─ BaseSample.tsx
```
## 특징
- 내부적으로 `ProcessFlowUI`를 재사용해 동일한 상태 표현과 스타일을 유지합니다.
- `horizontal`, `vertical` 두 방향을 지원합니다.
- `currentStepKey`만 넘겨도 현재 단계를 기준으로 완료/진행중/대기 상태를 계산합니다.
- 각 단계에 `status`를 직접 지정하면 수동 상태 제어도 가능합니다.
- `compact`, `showConnector`, `statusLabels`, `statusIcons`, `statusStyles`를 그대로 사용할 수 있습니다.
## 기본 예시
```tsx
<Stepper
steps={[
{ key: 'draft', label: '초안' },
{ key: 'review', label: '검토' },
{ key: 'done', label: '완료' },
]}
currentStepKey="review"
/>
```
## 참고
- `Stepper``StepperUI`를 함께 export하므로 둘 중 하나를 선택해 사용할 수 있습니다.
- `ProcessFlowUI`의 별칭 성격 컴포넌트이므로 동작 규칙과 스타일 계열은 동일합니다.
- 대표 샘플은 `src/components/stepper/samples/BaseSample.tsx`를 사용합니다.

27
docs/components/window-ui.md Executable file
View File

@@ -0,0 +1,27 @@
# Window UI
## 목적
부모 영역 안에서 이동 가능한 모달 스타일 윈도우를 제공합니다.
## 특징
- 헤더 작업줄 드래그 이동
- 부모 영역 내부로 이동 범위 제한
- 모서리/변 리사이즈
- 리사이즈 테두리 더 넓은 히트영역
- 리사이즈 변/모서리 더블클릭 및 더블탭 시 해당 방향으로 즉시 확장
- 최소화 / 최대화 / 복원
- 헤더 더블클릭 최대화 토글
## 주요 props
- `title`
- `subtitle`
- `defaultFrame`
- `minWidth`
- `minHeight`
## 샘플
- `src/components/window/samples/Sample.tsx`

144
docs/features/plan-automation.md Executable file
View File

@@ -0,0 +1,144 @@
# Plan 자동화와 구현 방식
## 목적
Plan 자동화 기능의 데이터 구조, API 연동 방식, release 검수 연계 방식을 정리합니다.
## 구현 위치
- 화면 진입: `src/app/main/MainContent.tsx`
- 메뉴/라우팅: `src/app/main/routes.tsx`
- 메인 보드: `src/features/planBoard/PlanBoardPage.tsx`
- release 검수: `src/features/planBoard/ReleaseReviewPage.tsx`
- 스케줄: `src/features/planBoard/PlanSchedulePage.tsx`
- 차트: `src/features/planBoard/charts.tsx`
- API 클라이언트: `src/features/planBoard/api.ts`
- 빠른 필터: `src/features/planBoard/quickFilters.ts`
- 메모 마스킹: `src/features/planBoard/noteMasking.ts`
## 데이터 모델
핵심 타입은 `src/features/planBoard/types.ts`에 있습니다.
- `PlanItem`: 보드의 기본 작업 항목
- `PlanDraft`: 생성/수정용 초안
- `PlanIssueHistory`: 이슈 이력
- `PlanActionHistory`: 조치 이력
- `PlanSourceWorkHistory`: 브랜치, diff, preview, 파일 스냅샷 등 소스 작업 증적
- `PlanReleaseReview`: release 검수 상태와 메타데이터
- `PlanReleaseReviewBoardItem`: 검수 화면용 합성 모델
자동화 유형 값은 다음 기준으로 사용합니다.
- `plan`: Markdown 스타일 Plan 문서 등록 및 접수용
- `auto_worker`: 실제 Codex 자동 작업 실행용
- `command_execution`, `non_source_work`: 기존 실행 분류 유지
기존 저장값인 `plan_registration`, `general_development`는 서버에서 각각 `plan`, `auto_worker`로 정규화합니다.
## API 연동 방식
기본 API 베이스 URL 규칙:
- `VITE_WORK_SERVER_URL`이 있으면 그 값을 사용
- 없으면 `/api` 사용
- `/api`가 실패하면 브라우저 origin 기준 `3100/api`로 fallback 재시도
요청 공통 규칙:
- GET은 기본 `no-store`
- 타임아웃 8초
- 등록된 토큰이 있으면 `X-Access-Token` 헤더 추가
- 클라이언트 식별 헤더는 `appendClientIdHeader`로 추가
## 주요 엔드포인트
- `POST /plan/setup`: 초기 테이블/구성 준비
- `GET /plan/items`: Plan 목록 조회
- `POST /plan/items`: Plan 등록
- `PATCH /plan/items/:id`: Plan 수정
- `DELETE /plan/items/:id`: Plan 삭제
- `POST /plan/items/:id/actions/:action`: 상태 전이/재처리 액션
- `GET /plan/items/:id/issues`: 이슈 이력
- `POST /plan/items/:id/issues/action`: 이슈 조치
- `GET /plan/items/:id/actions`: 조치 이력
- `POST /plan/items/:id/actions/note`: 추가 조치 메모
- `GET /plan/items/:id/source-works`: 소스 작업 이력
- `GET /plan/items/:id/source-works/:sourceWorkId`: 특정 소스 작업 상세
- `GET /plan/release-reviews`: release 검수 목록
- `PATCH /plan/release-reviews/:planItemId`: release 검수 상태 저장
## 빠른 필터 규칙
`quickFilters.ts`에서 다음 조건을 별도 유틸로 관리합니다.
- `working`: 상태가 `작업중`
- `release-pending-main`: `릴리즈완료`이거나 main 반영 대기/진행/실패 상태
- `automation-failed`: 워커 실패 상태 집합에 포함
이 구조 덕분에 사이드바, 검색, deep link가 같은 기준을 공유합니다.
## release 검수 방식
release 검수 화면은 Plan 본문과 분리되어 있지만 같은 데이터 축을 사용합니다.
- 검수 필터: 전체, main 대기, 검수완료, 수정필요
- 검수 메모 저장
- 관련 컴포넌트/위젯 샘플 바로 열기
- 체크된 페이지/샘플 ID 기반 진행률 계산
- 전체 대상 확인 시 자동으로 `approved`
- 일부 확인 또는 메모 작성 시 `reviewing`
- 수정 요청 상태에서 체크가 덜 끝난 경우 `changes-requested` 유지
검수 메타데이터에는 다음 값이 들어갈 수 있습니다.
- `summary`
- `pageSelectionIds`
- `checkedPageSelectionIds`
- `docIds`
- `componentIds`
- `widgetIds`
## 소스 작업 증적
Plan 상세에서는 최신 자동 작업 결과를 탭 형태로 확인할 수 있습니다.
- 작업란
- 전체소스
- diff
- 커맨드 로그
- 증적 Preview
- 파일 목록
`PlanSourceWorkHistory`에는 브랜치명, 커밋 해시, preview URL, 변경 파일, diff 텍스트, 파일 스냅샷이 포함됩니다.
Codex 실행 로그에 `tokens used`가 잡히면 source work 기록과 상세 상단 상태 영역에 함께 표기합니다.
## 차트 집계 방식
`charts.tsx`는 Plan 전체 목록을 주기적으로 다시 불러와 최근 성과를 집계합니다.
- 일별 최근 7일
- 주별 최근 8주
- 등록 수
- 기능확인완료 수
- 작업완료 수
- main 반영 수
현재 스냅샷은 전체, 등록, 작업중, 기능확인완료, 완료, main반영 개수를 함께 제공합니다.
## 검색/진입 연계
통합 검색 옵션은 `src/app/main/mainView/searchOptions.ts`에서 정의합니다.
- `자동화 / 자동화`
- `자동화 / plan`
- `자동화 / release 검수`
검색에서 선택하면 해당 메뉴로 이동하고 필요한 경우 문서/컴포넌트 위치로 스크롤합니다.
## 유지보수 메모
- 상태 문자열과 워커 상태 문자열은 문자열 상수 기반이라 백엔드와 값이 어긋나지 않게 같이 관리해야 합니다.
- 스케줄 API는 여러 경로 후보를 순차 시도하므로 서버 경로 통합 시 `api.ts` 정리가 필요합니다.
- release 검수 메타데이터 스키마를 늘릴 때는 `ReleaseReviewPage`의 진행률 계산 규칙도 같이 점검해야 합니다.

View File

@@ -0,0 +1,114 @@
# Plan 자동화 보드
## 목적
`src/features/planBoard`는 Plan 등록부터 자동 작업, release 검수, main 동기화 전 단계까지 한 화면 흐름으로 관리하는 기능 묶음입니다.
현재 문서는 기존 개선 제안 메모를 대신해 실제 구현 기준의 운영 문서로 사용합니다.
## 화면 구성
- `all`: 전체 자동화 목록
- `in-progress`: 완료 전 항목 중심 목록
- `error`: 열린 이슈가 있는 항목 중심 목록
- `done`: release 완료 기준 목록
- `board`: 상세 편집과 이력 조회 중심 보드
- `release-review`: release 검수 전용 화면
- `charts`: 최근 작업 추이 차트
- `schedule`: 반복 등록 스케줄 관리
- `history`: 향후 이력 확장 메뉴용 영역
라우팅과 메뉴 정의는 `src/app/main/routes.tsx`, `src/app/main/MainContent.tsx`에서 관리합니다.
## 문서 반영 방식
- 이 문서를 포함한 `docs/features/*.md`는 메인 앱 `Docs / 기능문서` 메뉴에서 자동 수집됩니다.
- 문서 추가만으로 별도 라우트를 만들지는 않으며, `Docs` 화면의 markdown 목록에 합류하는 방식입니다.
- 노출 순서와 폴더 라벨은 `src/app/main/layout/useMainLayoutData.ts`, `src/app/main/routes.tsx`에서 결정합니다.
## 상태 모델
기본 상태는 `src/features/planBoard/types.ts`에 정의되어 있습니다.
- `등록`
- `작업중`
- `작업완료`
- `릴리즈완료`
- `완료`
실제 자동화 진행은 별도 `workerStatus`로 관리합니다. 대표값은 다음과 같습니다.
- 진행: `브랜치생성중`, `자동작업중`, `release반영중`, `main반영중`
- 대기: `브랜치준비`, `release반영대기`, `main반영대기`
- 실패: `브랜치실패`, `자동작업실패`, `release반영실패`, `main반영실패`
즉, 사용자에게 보이는 업무 상태와 자동 워커 상태를 분리해서 표현합니다.
## 주요 사용 흐름
1. 작업을 등록하면 Plan 항목이 생성됩니다.
2. `작업시작` 또는 자동화에 따라 브랜치 준비와 작업이 진행됩니다.
3. 작업이 완료되면 release 반영 상태와 main 반영 대기 여부가 추적됩니다.
4. release 검수 화면에서 샘플/위젯/변경 파일을 확인하고 검수 상태를 갱신합니다.
5. main 반영이 끝나면 최종 완료 흐름으로 정리됩니다.
## 목록 기능
`PlanBoardPage.tsx` 기준으로 다음 기능이 구현되어 있습니다.
- 검색: `workId`, 메모, 상태, 브랜치, 워커 상태, 이슈 태그 검색
- 보조 필터: 작업자 상태, release 상태, main 상태, 이슈 상태
- 빠른 필터: 현재 작업중, 현재 release 상태, 현재 자동화 실패
- 정렬: 진행 목록에서는 자동화 우선순위를 먼저 적용하고, 그 외에는 최근 갱신순 정렬
- 페이지네이션: 목록 10개 단위
## 상세 기능
선택한 Plan 항목은 오버레이에서 상세 확인과 후속 조치를 처리합니다.
- 원본 요청 보기
- 작업 체크리스트
- 릴리즈 준비 요약
- 소스 작업 내역
- 조치 / 이슈 이력
- 추가 조치 기록
- 이슈 조치 기록
- 작업 소스 / diff / command log / 증적 / 파일 뷰어
원본 요청은 `startedAt` 이후 잠기며, 이후 변경은 조치 이력으로 누적합니다.
## 실행 액션
상태와 `workerStatus`에 따라 다음 액션 버튼이 조건부로 노출됩니다.
- `작업시작`
- `작업완료 처리`
- `브랜치 재시도`
- `작업 재처리`
- `release 반영 재시도`
- `작업취소`
- `main 일괄 반영 요청`
세부 액션은 `/plan/items/:id/actions/:action` API로 실행됩니다.
## 자동 새로고침과 알림
- 자동화가 진행/대기 상태일 때만 자동 조회 타이머가 동작합니다.
- 주기는 `appConfig.automation.autoRefreshIntervalSeconds`를 따릅니다.
- 길게 눌러 자동 조회 On/Off를 전환할 수 있습니다.
- 설정값에 따라 자동화 시작/실패 메시지를 toast로 알립니다.
## 권한 처리
권한 토큰이 없는 경우 조회 중심으로 동작합니다.
- 수정/삭제/조치/스케줄 저장 비활성화
- 민감한 요청 메모는 마스킹
- 소스 작업 상세/증적 확인 제한
토큰 처리와 헤더 부착은 `src/app/main/tokenAccess.ts`, `src/features/planBoard/api.ts`에서 담당합니다.
## 연관 문서
- 자동화/구현 방식: `docs/features/plan-automation.md`
- 스케줄 사용법: `docs/features/plan-schedule.md`
- 활용 가이드: `docs/features/plan-usage.md`

98
docs/features/plan-schedule.md Executable file
View File

@@ -0,0 +1,98 @@
# Plan 스케줄 사용법
## 목적
반복적으로 등록되는 자동화 요청을 수동 입력 없이 생성하기 위한 스케줄 화면 사용법과 제약을 정리합니다.
## 구현 위치
- 화면: `src/features/planBoard/PlanSchedulePage.tsx`
- API: `src/features/planBoard/api.ts`
## 제공 기능
- 스케줄 목록 조회
- 신규 스케줄 등록
- 기존 스케줄 수정/삭제
- 즉시 실행 옵션 설정
- 활성/비활성 전환
- 반복 주기 또는 매일 실행 시간 설정
- 다음 실행 예정 시각 계산 표시
## 입력 항목
- `workId`: 반복 등록할 작업 ID
- `note`: 매번 생성될 요청 메모
- `automationType`: 자동화 유형
- `plan`: Markdown 스타일 Plan 문서 등록/접수
- `auto_worker`: 실제 자동 작업 실행
- `command_execution`, `non_source_work`: 기존 분류 유지
- `releaseTarget`: 반영 대상 브랜치
- `jangsingProcessingRequired`: 기능동작확인 필요 여부
- `autoDeployToMain`: main 자동 반영 대상 여부
- `enabled`: 스케줄 사용 여부
- `immediateRunEnabled`: 생성 직후 바로 등록 허용 여부
## 스케줄 모드
### 1. 반복 주기
`interval` 모드입니다.
- 값 + 단위로 반복 주기 설정
- 단위: `minute`, `hour`, `day`, `week`, `month`
- 내부 저장은 `repeatIntervalMinutes` 기준
### 2. 매일 시간
`daily` 모드입니다.
- `HH:mm` 형식 시간 사용
- 시간 계산은 `Asia/Seoul` 기준
- 하루 1회 등록에 적합
## 유효성 규칙
현재 화면에서 다음 검사를 수행합니다.
- 작업 ID 필수
- 메모 필수
- 같은 작업 ID 중복 금지
- `interval` 모드 최소 10분 이상
- 비활성 스케줄은 자동 등록되지 않음을 경고
## 다음 실행 시각 계산
다음 실행 시각은 화면에서 즉석 계산해 보여줍니다.
- 비활성 상태면 `중지`
- 직전 등록 이력이 없고 즉시 실행이 켜져 있으면 현재 시각 기준
- `daily`는 오늘 KST 실행 여부를 보고 오늘 또는 다음 날 시각 결정
- `interval`은 마지막 등록 시각 또는 생성 시각 기준으로 다음 주기를 계산
## 추천 운영 방식
- 자주 반복되는 운영 작업은 고정 `workId`로 등록
- 사람이 검토해야 하는 작업은 `autoDeployToMain`을 끄고 release 검수 단계에서 확인
- 단순 알림성/반복성 작업은 `immediateRunEnabled`를 켜서 누락 없이 시작
- 짧은 주기 스케줄은 10분 이상으로 유지해 중복 생성 위험을 낮춤
## API 경로 메모
스케줄 API는 서버 구현 차이를 흡수하기 위해 여러 경로를 순차 시도합니다.
- `/plan/scheduled-tasks`
- `/plan/schedule/tasks`
- `/plan/schedule`
- `/plan/schedules`
- `/plans/...` 변형 경로
서버 경로가 고정되면 후보 경로를 줄여도 됩니다.
## 테스트 포인트
- interval/daily 전환 시 값 보존이 자연스러운지 확인
- 같은 `workId` 중복 저장이 막히는지 확인
- KST 기준 다음 실행 시각이 기대와 일치하는지 확인
- 비활성 스케줄이 자동 생성되지 않는지 확인
- 토큰 없는 사용자에게 저장/삭제가 제한되는지 확인

58
docs/features/plan-usage.md Executable file
View File

@@ -0,0 +1,58 @@
# Plan 활용 가이드
## 목적
운영자, 검수자, 개발자가 현재 Plan 기능을 어떤 순서로 써야 하는지 정리한 실무 가이드입니다.
## 빠른 진입
- 전체 목록 확인: `자동화 / 자동화`
- 상세 처리 중심: `자동화 / plan`
- release 검수: `자동화 / release 검수`
- 추이 확인: `자동화 / 차트`
- 반복 등록: `자동화 / 스케줄`
통합 검색에서도 `plan`, `release 검수`, `스케줄`로 바로 이동할 수 있습니다.
## 운영자 기준 사용 순서
1. `자동화 / 자동화`에서 작업을 찾습니다.
2. 필요하면 빠른 필터로 `현재 작업중`, `현재 release 상태`, `현재 자동화 실패`를 좁힙니다.
3. 상세 오버레이에서 현재 상태, 이슈, 최근 소스 작업을 확인합니다.
4. 상황에 맞는 액션을 실행합니다.
5. 후속 설명은 조치 기록 또는 이슈 조치 기록으로 남깁니다.
## 검수자 기준 사용 순서
1. `release 검수`에서 `main 대기` 필터를 우선 봅니다.
2. 검수 메모와 변경 파일, 관련 컴포넌트/위젯 샘플을 확인합니다.
3. 확인한 항목은 체크 상태로 반영합니다.
4. 이상 없으면 `검수완료`, 수정이 필요하면 `수정필요`로 저장합니다.
## 개발자 기준 확인 포인트
- 실패 상태면 `workerStatus``lastError`를 먼저 확인
- 브랜치 누락 계열 실패는 `브랜치 재시도` 또는 `작업 재처리` 판단
- release/main 반영 관련 문제는 상세의 소스 작업 내역과 diff를 먼저 확인
- 메모 원문이 잠겨 있으면 추가 조치 기록으로 정정 사유를 남김
## 권한 없는 사용자 동작
- 목록과 검수 현황은 조회 가능
- 민감 메모는 마스킹
- 상세 수정/삭제/조치 실행은 제한
- 소스 작업 상세와 증적 확인도 제한될 수 있음
## 문서와 화면을 함께 운영하는 방법
- 기능 변경 시 `docs/features`를 먼저 갱신
- 새 문서를 만들면 앱 `Docs / 기능문서`에 자동 노출되는지 함께 확인
- 증적이 필요한 변경은 Plan 상세의 Preview, diff, 파일 목록으로 확인
- 작업일지에는 어떤 기능 문서를 왜 수정했는지 함께 남김
## 추천 문서 읽기 순서
1. `docs/features/plan-board-review.md`
2. `docs/features/plan-automation.md`
3. `docs/features/plan-schedule.md`
4. `docs/features/plan-usage.md`

100
docs/features/project-setup.md Executable file
View File

@@ -0,0 +1,100 @@
# 프로젝트 구성 개요
## 목적
현재 저장소의 화면 구조와 문서 체계를 빠르게 파악하기 위한 최신 개요 문서입니다.
## 기술 스택
- React
- Vite
- TypeScript
- Ant Design
- Recharts
- React Router
## 최상위 앱 구조
- `src/app/main`: 메인 앱 프레임, 상단 메뉴, 사이드바, 본문, 검색 연동
- `src/features`: 프로젝트 전용 기능 화면
- `src/components`: 재사용 가능한 UI 컴포넌트
- `src/widgets`: 샘플/위젯 단위 UI
- `docs`: 기능/컴포넌트/작업일지 문서
- `etc/servers/work-server`: Plan API 연동 서버 자산
## 현재 주요 기능 축
### Docs
- `docs/**/*.md`를 수집해 문서 화면에 노출
- 작업일지, 기능 문서, 컴포넌트 문서를 같은 흐름으로 탐색
- `docs/features` 아래 문서는 `Docs / 기능문서` 메뉴에서 동적으로 확인 가능
### APIs
- 컴포넌트 샘플
- 위젯 샘플
### Plans
- Plan 자동화 목록/상세
- release 검수
- 차트
- 스케줄
- 히스토리 확장 영역
### Chat
- Codex Live
- 에러 로그
`Codex Live`는 현재 프로젝트 환경의 `main_project`를 기준 저장소로 사용합니다. 소스 수정이 필요하면 **현재 프로젝트 루트의 로컬 `main` 작업본을 바로 수정**합니다.
일반 채팅 요청과 작업메모 반영 요청도 같은 기준을 따르며, 별도 브랜치 생성 없이 현재 프로젝트 루트에서 바로 수정하는 것을 기본 동작으로 사용합니다.
채팅에서 제공되는 파일/문서/이미지/코드 리소스와 첨부 파일은 세션별로 `public/.codex_chat/<chat-session-id>/resource/...` 아래에 노출됩니다.
### Play
- Layout Editor
- 저장된 레이아웃 기록
## Plan 기능 구조
Plan 관련 코드는 `src/features/planBoard`에 집중되어 있습니다.
- `PlanBoardPage.tsx`: 자동화 목록과 상세 편집
- `ReleaseReviewPage.tsx`: release 검수
- `PlanSchedulePage.tsx`: 반복 등록 스케줄
- `charts.tsx`: 작업 추이 차트
- `api.ts`: API 통신
- `types.ts`: 상태/타입 정의
## 문서 구조
- `docs/worklogs`: 날짜별 작업 기록
- `docs/features`: 기능 설명과 운영 가이드
- `docs/components`: 공통 컴포넌트 설명
- `docs/templates`: 기능/작업일지 템플릿
현재 `docs/features`의 핵심 문서는 다음과 같습니다.
- `project-setup.md`
- `search-layer.md`
- `plan-board-review.md`
- `plan-automation.md`
- `plan-schedule.md`
- `plan-usage.md`
## 검색/문서 연계
- 통합 검색 옵션은 `src/app/main/mainView/searchOptions.ts`에서 구성
- 문서, Plan 화면, 컴포넌트 샘플, 위젯 샘플을 하나의 검색 엔트리로 제공
- 선택 시 해당 메뉴와 포커스 대상으로 바로 이동
## 운영 메모
- 기능 문서는 구현 파일명과 메뉴명을 그대로 써서 찾기 쉽게 유지
- `docs/features` 변경분이 보이지 않으면 현재 선택한 Docs 폴더가 `기능문서`인지 먼저 확인
- Plan 관련 변경은 문서와 라우팅/검색 옵션을 함께 확인
- 스케줄, release 검수, 차트처럼 화면이 분리된 기능은 개별 문서를 유지

22
docs/features/search-layer.md Executable file
View File

@@ -0,0 +1,22 @@
# Search Layer
## 목적
통합 검색 모달의 열기/닫기와 검색 옵션 목록을 전역 레이어에서 관리합니다.
## 구조
- `src/layer/search/context`
- `src/layer/search/hooks`
- `src/layer/search/types`
## 역할 분리
- `layer/search`: 검색 모달 렌더링, open/close, 옵션 등록
- `layer/gesture`: 제스처 감지와 검색 열기 트리거
- `store/appStore`: 현재 페이지와 포커스된 컴포넌트 상태 관리
## 메모
- 검색 UI는 페이지 내부에 직접 두지 않고 레이어에서 렌더링
- 페이지는 검색 옵션만 계산해서 레이어에 전달

25
docs/templates/feature-template.md vendored Executable file
View File

@@ -0,0 +1,25 @@
# 기능명
## 목적
-
## 사용자 시나리오
-
## 화면/동작 설명
-
## 데이터 및 API
-
## 예외 처리
-
## 테스트 포인트
-

53
docs/templates/worklog-template.md vendored Executable file
View File

@@ -0,0 +1,53 @@
# YYYY-MM-DD 작업일지
## 오늘 작업
-
## 이슈 및 해결
-
## 결정 사항
-
## 상세 작업 내역
-
- 이 섹션에는 파일 목록, 경로 나열, raw diff를 직접 풀어쓰지 말고 작업 흐름과 판단만 정리
- 파일 목록은 `## 변경/신규 파일`, raw diff는 `## 소스`에서만 기록
## 스크린샷
- 전체 화면 스크린샷 1장은 필수
- 위젯/컴포넌트 단위 부분 스크린샷은 필요한 만큼 추가
- 저장소 기준 연결된 스크린샷이 없으면 작업 종료 전 반드시 채움
## 소스
### 파일 1: `path/to/file.tsx`
- 변경 또는 신규 추가 목적과 핵심 내용을 한 줄로 정리
- `상세 작업 내역`에는 파일 목록이나 raw diff를 다시 쓰지 않음
- `소스` 탭에서 Codex preview 스타일의 `전체소스 / raw diff` 전환을 제공하므로 여기에는 파일별 raw diff 위주로 남김
```diff
# 이 파일의 raw diff
- before
+ after
```
### 파일 2: `path/to/another-file.ts`
- 필요 없으면 이 섹션은 삭제
## 실행 커맨드
```bash
```
## 변경/신규 파일
- M path/to/file.tsx
- A path/to/new-file.ts

1
docs/test001.md Executable file
View File

@@ -0,0 +1 @@
테스트MD자동 생성 입니다.

126
docs/worklogs/2026-03-30.md Executable file
View File

@@ -0,0 +1,126 @@
# 2026-03-30 작업일지
## 오늘 작업
- Ant Design 기반 프론트엔드 프로젝트 초기 구조 생성
- `docs/worklogs`, `docs/features` 문서 폴더 구성
- 기본 대시보드 스타일의 시작 화면 작성
- React 19.2.4, Vite 8.0.3 최신 안정 버전으로 업데이트
- 컴포넌트 샘플 구조를 `plugins/`, `samples/`, `types/`, `xxxUI.tsx` 형태로 정리
- 공통 플러그인 제네릭과 `plugins<T>(props, pluginList)` 합성 유틸 추가
- `status-badge` 컴포넌트 샘플과 컴포넌트 문서 작성
- `InputUI.tsx` 단일 컴포넌트 구조와 validation plugin 기반 입력 샘플 추가
- 컴포넌트 샘플과 위젯 샘플을 별도 registry로 분리
- `WidgetShell`, `ApiSampleCardWidget`, 위젯 샘플 레이아웃 추가
- 컴포넌트 샘플 레이아웃을 좌측 내비게이션 + 우측 상세 카드 구조로 개편
- 입력 컴포넌트 구조를 `primitives / specialized / composite` 계층으로 재정리
- Git 저장소 초기화, `main` 브랜치 생성, 원격 `origin` 연결, 초기 커밋 생성
## 이슈 및 메모
- 초기 스택은 `React + Vite + TypeScript + Ant Design`으로 결정
- `antd` 포함 번들 특성상 빌드 시 청크 크기 경고가 발생함
- 원격 저장소 `push`는 HTTPS 인증 정보 부재로 실패
- 샘플 목록은 `samples/Sample.tsx` 기본형과 `samples/*.tsx` 확장형을 구분해 관리하기로 함
## 결정 사항
- 작업일지는 날짜별 Markdown 파일로 관리
- 기능 문서는 기능 단위 Markdown 파일로 관리
- 컴포넌트 문서는 `docs/components` 아래에서 관리
- 컴포넌트 샘플은 `samples/Sample.tsx`를 대표 샘플로 사용
- 입력 컴포넌트는 `InputUI.tsx` 하나만 두고 기능은 plugin으로 확장
- 입력 패키지는 `primitives`, `specialized`, `composite` 역할 기준으로 분리
- 플러그인은 기본적으로 공통 제네릭 타입을 사용하고, 필요 시 커링/팩토리 형태로 확장
- 여러 props 후처리 플러그인은 `plugins<T>(props, pluginList)` 형태로 합성
- 샘플 목록은 같은 컴포넌트 기준으로 묶고 `base -> plugin -> feature` 순서로 정렬
## 상세 작업 내역
- 프로젝트 부트스트랩 이후 문서 폴더와 샘플 레지스트리를 함께 정리해 이후 확장 기준을 먼저 마련
- `status-badge`, `InputUI`를 기준 컴포넌트로 삼아 plugin 합성 구조와 샘플 표시 구조를 동시에 검증
- 컴포넌트/위젯 샘플을 분리해 API 성격의 문서 화면으로 확장할 수 있는 기본 형태를 준비
- 원격 저장소 연결까지 완료했지만 인증 이슈로 `push`는 보류 상태로 남음
## 스크린샷
![input](../assets/worklogs/2026-03-30/input.png)
![status-badge](../assets/worklogs/2026-03-30/status-badge.png)
## 소스
- `src/components/status-badge/StatusBadgeUI.tsx`, `src/components/status-badge/plugins/status-badge.plugin.ts`: 상태 배지 기본 UI와 플러그인 합성 구조를 잡아 샘플 갤러리의 기준 컴포넌트로 삼았습니다.
- `src/components/inputs/input/InputUI.tsx`: validation plugin 기반 입력 컴포넌트 초안을 잡아 이후 `primitives / specialized / composite` 확장의 출발점으로 사용했습니다.
- `src/widgets/core/WidgetShell.tsx`: 위젯 본문을 단순 문단이 아니라 자유 레이아웃 컨테이너로 바꿔 카드형 샘플과 대시보드 위젯을 수용하게 했습니다.
- `docs/templates/worklog-template.md`: 작업일지 템플릿에 상세 내역, 스크린샷, 소스, 실행 커맨드, 변경 파일 섹션을 추가해 증적 기록 형식을 고정했습니다.
```diff
diff --git a/src/widgets/core/WidgetShell.tsx b/src/widgets/core/WidgetShell.tsx
-const { Paragraph, Title } = Typography;
+const { Title } = Typography;
...
- <Paragraph className="widget-shell__content">{children}</Paragraph>
+ <div className="widget-shell__content">{children}</div>
diff --git a/docs/templates/worklog-template.md b/docs/templates/worklog-template.md
+## 상세 작업 내역
+## 스크린샷
+## 소스
+
## 변경 파일 (전체, 중복 제거, KST 기준)
- A .gitignore
- A README.md
- A docs/README.md
- A docs/components/status-badge.md
- A docs/features/project-setup.md
- A docs/templates/feature-template.md
- A docs/templates/worklog-template.md
- A docs/worklogs/2026-03-30.md
- A index.html
- A package-lock.json
- A package.json
- A src/App.tsx
- A src/components/status-badge/StatusBadgeUI.tsx
- A src/components/status-badge/index.ts
- A src/components/status-badge/plugins/index.ts
- A src/components/status-badge/plugins/status-badge.plugin.ts
- A src/components/status-badge/samples/Sample.tsx
- A src/components/status-badge/types/index.ts
- A src/components/status-badge/types/status-badge.ts
- A src/main.tsx
- A src/styles.css
- A src/types/component-plugin.ts
- A tsconfig.app.json
- A tsconfig.json
- A tsconfig.node.json
- A vite.config.ts
## 실행 커맨드
+## 변경 파일
```
## 실행 커맨드
```bash
npm install
npm run build
git init
git checkout -b main
git add .
git commit -m "feat: initialize antd app and component plugin structure"
```
## 변경 파일
- `README.md`
- `docs/worklogs/2026-03-30.md`
- `src/App.tsx`
- `src/components/status-badge/StatusBadgeUI.tsx`
- `src/components/status-badge/plugins/status-badge.plugin.ts`
- `src/components/inputs/input/InputUI.tsx`
- `src/widgets/core/WidgetShell.tsx`
- `src/styles.css`
- `package.json`
- `vite.config.ts`

173
docs/worklogs/2026-03-31.md Executable file
View File

@@ -0,0 +1,173 @@
# 2026-03-31 작업일지
## 오늘 작업
- 입력 컴포넌트 구조를 `primitives / specialized / composite` 계층으로 재정리
- 기본 입력을 `src/components/inputs/primitives/input` 아래로 이동
- 이메일 입력을 `src/components/inputs/specialized/emailInput` 구조로 정리
- 3분할 입력을 `src/components/inputs/composite/multiInput` 구조로 정리
- 샘플 및 문서 경로를 새 입력 계층 구조에 맞게 갱신
- 프로젝트 종속 레이아웃을 `src/features/layout` 아래로 이동
- `basePath` 기반 공통 markdown preview 컴포넌트와 리스트 구성 추가
- `docs/**/*.md`까지 읽을 수 있도록 markdown registry 범위 확장
- `docs` 문서를 좌측 폴더/문서 트리 + 우측 markdown 카드 목록 구조로 구성
- 좌측 문서 바로가기를 공통 `FolderTreeNav` 기반 접기/펼치기 트리로 정리
- 구조 변경 내용을 커밋하고 원격 `main` 브랜치로 푸시
## 이슈 및 메모
- 입력 컴포넌트 이름과 패키지명 기준이 중간에 몇 차례 조정됨
- 샘플 레이아웃과 문서 경로는 구조 변경 시 함께 관리해야 함
- `src` 기준 glob만 사용하면 프로젝트 루트 `docs/`는 읽히지 않음
- 빌드는 정상 통과했지만 `antd` 포함 번들로 인해 청크 크기 경고는 계속 발생
## 결정 사항
- 입력 컴포넌트는 역할 기준으로 `primitives`, `specialized`, `composite`로 분리
- `InputUI`는 primitive로 관리
- `EmailInputUI`는 specialized 입력으로 관리
- `MultiInputUI`는 여러 입력을 조합한 composite 입력으로 관리
- 프로젝트 종속적인 레이아웃은 `src/features/layout` 아래에서 관리
- markdown preview는 공통 컴포넌트가 `basePath`를 받아 재사용 가능하게 구성
- 작업일지는 날짜별 파일로 계속 누적 기록
## 상세 작업 내역
- 입력 계층 재정리와 동시에 샘플/문서 경로도 같이 옮겨 구조 변경 시 누락 포인트를 줄임
- `docs` 트리와 markdown preview를 공통화하면서 프로젝트 루트 문서도 읽을 수 있게 범위를 확장
- 좌측 폴더 트리와 우측 카드 목록 구성을 도입해 문서 탐색 화면의 기본 틀을 완성
- 구조 개편 후 빌드와 원격 푸시까지 마쳐 문서 기반 화면을 기준선으로 고정
## 스크린샷
![email-input](../assets/worklogs/2026-03-31/email-input.png)
![multi-input](../assets/worklogs/2026-03-31/multi-input.png)
## 소스
- `src/components/inputs/primitives/input/InputUI.tsx`: 기본 입력을 primitive 계층으로 고정하고 commit plugin 검증 흐름을 공통 엔트리로 정리했습니다.
- `src/components/inputs/specialized/emailInput/EmailInputUI.tsx`: 이메일 전용 입력을 primitive 위에 얹어 specialized 계층 규칙을 검증했습니다.
- `src/components/inputs/composite/multiInput/MultiInputUI.tsx`: 전화번호형 3분할 입력을 추가해 composite 계층과 세그먼트 이동 UX를 구현했습니다.
- `src/components/markdownPreview/MarkdownPreviewCard.tsx`, `src/features/layout/docs-markdown-preview/DocsMarkdownPreviewLayout.tsx`: 루트 `docs/` 문서를 카드와 폴더 트리로 탐색하는 Docs 미리보기 레이아웃을 추가했습니다.
```diff
diff --git a/src/components/inputs/composite/multiInput/MultiInputUI.tsx b/src/components/inputs/composite/multiInput/MultiInputUI.tsx
+function splitValue(value?: string): MultiInputParts {
+ const digits = (value ?? '').replace(/\D/g, '').slice(0, 11);
+ return [digits.slice(0, 3), digits.slice(3, 7), digits.slice(7, 11)];
+}
...
+ if (event.target.value.length === 3) {
+ secondRef.current?.focus();
+ }
diff --git a/src/components/inputs/specialized/emailInput/EmailInputUI.tsx b/src/components/inputs/specialized/emailInput/EmailInputUI.tsx
+ inputMode={inputMode}
+ placeholder={placeholder}
+ autoComplete={autoComplete}
+ commitPlugins={[createEmailValidatorPlugin(), ...commitPlugins]}
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- M README.md
- M docs/README.md
- M docs/features/project-setup.md
- A docs/worklogs/2026-03-31.md
- M src/App.tsx
- A src/components/markdownPreview/MarkdownPreviewCard.tsx
- A src/components/markdownPreview/MarkdownPreviewContent.tsx
- A src/components/markdownPreview/MarkdownPreviewList.tsx
- A src/components/markdownPreview/index.ts
- A src/components/markdownPreview/markdown-document.ts
- A src/components/markdownPreview/registry.ts
- A src/components/navigation/folder-tree-nav.tsx
- A src/components/navigation/index.ts
- A src/features/layout/README.md
- R src/layouts/component-sample-gallery/ComponentSamplesLayout.tsx -> src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx
- R src/layouts/component-sample-gallery/index.ts -> src/features/layout/component-sample-gallery/index.ts
- A src/features/layout/docs-markdown-preview/DocsMarkdownPreviewLayout.tsx
- A src/features/layout/docs-markdown-preview/index.ts
- A src/features/layout/feature-markdown-preview/FeatureMarkdownPreviewListLayout.tsx
- A src/features/layout/feature-markdown-preview/index.ts
- R src/layouts/widget-registry-gallery/WidgetRegistryLayout.tsx -> src/features/layout/widget-registry-gallery/WidgetRegistryLayout.tsx
- R src/layouts/widget-registry-gallery/index.ts -> src/features/layout/widget-registry-gallery/index.ts
- R src/layouts/widget-sample-gallery/SampleWidgetsLayout.tsx -> src/features/layout/widget-sample-gallery/SampleWidgetsLayout.tsx
- R src/layouts/widget-sample-gallery/index.ts -> src/features/layout/widget-sample-gallery/index.ts
- A src/features/markdownPreview/FeatureMarkdownPreviewCard.tsx
- A src/features/markdownPreview/index.ts
- A src/features/overview.md
- M src/styles.css
- M docs/components/input.md
- M docs/worklogs/2026-03-30.md
- A src/components/inputs/composite/multiInput/MultiInputUI.tsx
- A src/components/inputs/composite/multiInput/index.ts
- A src/components/inputs/composite/multiInput/plugins/index.ts
- A src/components/inputs/composite/multiInput/plugins/multi-input.plugin.ts
- A src/components/inputs/composite/multiInput/samples/Sample.tsx
- A src/components/inputs/composite/multiInput/types/index.ts
- A src/components/inputs/composite/multiInput/types/multi-input.ts
- R src/components/inputs/input/InputUI.tsx -> src/components/inputs/primitives/input/InputUI.tsx
- R src/components/inputs/input/index.ts -> src/components/inputs/primitives/input/index.ts
- R src/components/inputs/input/plugins/index.ts -> src/components/inputs/primitives/input/plugins/index.ts
- R src/components/inputs/input/plugins/input.plugin.ts -> src/components/inputs/primitives/input/plugins/input.plugin.ts
- R src/components/inputs/input/samples/Sample.tsx -> src/components/inputs/primitives/input/samples/Sample.tsx
- R src/components/inputs/input/samples/ValidInputSample.tsx -> src/components/inputs/primitives/input/samples/ValidInputSample.tsx
- R src/components/inputs/input/types/index.ts -> src/components/inputs/primitives/input/types/index.ts
- R src/components/inputs/input/types/input.ts -> src/components/inputs/primitives/input/types/input.ts
- A src/components/inputs/specialized/emailInput/EmailInputUI.tsx
- A src/components/inputs/specialized/emailInput/index.ts
- A src/components/inputs/specialized/emailInput/plugins/email-input.plugin.ts
- A src/components/inputs/specialized/emailInput/plugins/index.ts
- A src/components/inputs/specialized/emailInput/samples/Sample.tsx
- A src/components/inputs/specialized/emailInput/types/email-input.ts
- A src/components/inputs/specialized/emailInput/types/index.ts
- A docs/components/input.md
- A src/components/inputs/input/InputUI.tsx
- A src/components/inputs/input/index.ts
- A src/components/inputs/input/plugins/index.ts
- A src/components/inputs/input/plugins/input.plugin.ts
- A src/components/inputs/input/samples/Sample.tsx
- A src/components/inputs/input/samples/ValidInputSample.tsx
- A src/components/inputs/input/types/index.ts
- A src/components/inputs/input/types/input.ts
- M src/components/status-badge/samples/Sample.tsx
- A src/layouts/component-sample-gallery/ComponentSamplesLayout.tsx
- A src/layouts/component-sample-gallery/index.ts
- A src/layouts/widget-registry-gallery/WidgetRegistryLayout.tsx
- A src/layouts/widget-registry-gallery/index.ts
- A src/layouts/widget-sample-gallery/SampleWidgetsLayout.tsx
- A src/layouts/widget-sample-gallery/index.ts
- A src/samples/registry.ts
- A src/vite-env.d.ts
- A src/widgets/api-sample-card/ApiSampleCardWidget.tsx
- A src/widgets/api-sample-card/index.ts
- A src/widgets/api-sample-card/samples/Sample.tsx
- A src/widgets/core/WidgetShell.tsx
- A src/widgets/core/index.ts
- A src/widgets/core/registry/widget-features.ts
- A src/widgets/core/types/widget.ts
- A src/widgets/registry.ts
## 실행 커맨드
```bash
npm run build
git add .
git commit -m "입력 컴포넌트 구조 정리"
git commit -m "문서 미리보기와 레이아웃 정리"
git push origin main
```
## 변경 파일
- `docs/worklogs/2026-03-31.md`
- `src/components/inputs/primitives/input/InputUI.tsx`
- `src/components/inputs/specialized/emailInput/EmailInputUI.tsx`
- `src/components/inputs/composite/multiInput/MultiInputUI.tsx`
- `src/components/markdownPreview/MarkdownPreviewCard.tsx`
- `src/components/markdownPreview/MarkdownPreviewContent.tsx`
- `src/components/markdownPreview/MarkdownPreviewList.tsx`
- `src/components/navigation/folder-tree-nav.tsx`
- `src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx`
- `src/styles.css`

254
docs/worklogs/2026-04-01.md Executable file
View File

@@ -0,0 +1,254 @@
# 2026-04-01 작업일지
## 오늘 작업
- 대시보드 카드 위젯과 샘플 구성을 보강
- WMS/TMS 대시보드 콘텐츠 배치와 차트 표현을 조정
- 대시보드 공통 표현 컴포넌트 `progress`, `multiProgress` 구조를 정리
- `src/data` 아래로 대시보드 프리셋 데이터를 분리
- 메인 화면을 좌측 메뉴 기반 API 스타일 구조로 정리
- `popup` 입력 컴포넌트를 추가하고 UI 스타일을 보강
- `select` 입력 컴포넌트를 추가
- `checkCombo` 입력 컴포넌트를 추가
- `package.json`에 Nexus `publishConfig` 추가
- 버전을 `0.1.0-alpha.0`로 조정하고 publish용 `private` 설정을 해제
- 메인 화면을 `src/views/main` 아래로 임시 분리
- Nexus 인증 방식을 `username / _password(base64)` 형식으로 재구성
- Nexus에 `ai-code-app@0.1.0-alpha.0` 배포 성공
- 메인 화면 구조를 `src/app/main` 기준으로 다시 정리하고 export 경로를 보강
- 콘텐츠 영역 바깥 여백을 줄여 카드 중심 레이아웃으로 정리
- 버튼 클릭 시에만 편집 가능한 `buttonEditableInput` 컴포넌트를 추가
- `buttonEditableInput`의 readonly 음영과 확인 버튼 편집 완료 동작을 보정
## 이슈 및 메모
- `npm publish`는 alpha 버전 배포 시 `--tag alpha`가 필요
- Nexus는 토큰 방식보다 `username / _password(base64)` 방식에서 인증이 정상 동작함
- popup 입력은 스타일 보강 과정에서 입력/버튼/readonly 영역의 경계와 톤을 반복 조정
- 대시보드 카드 높이와 콘텐츠 간격 조정은 과도한 고정값을 피하고 점진적으로 조정하는 편이 안정적
- 버튼 기반 편집 입력은 blur, Enter, 버튼 클릭 확정 타이밍을 함께 맞춰야 UX가 안정적
## 결정 사항
- 재사용 가능한 샘플/프리셋 데이터는 `src/data`에서 관리
- 대시보드 공통 표현은 `src/components/dashboard`, 카드 조합은 `src/widgets/dashboard-report-card`로 분리
- 신규 입력 컴포넌트도 `UI / plugins / samples / types` 구조를 동일하게 적용
- 메인 화면은 임시로 `views/main` 아래에 `header / sidebar / content` 단위로 분리
- 배포는 `npm publish --tag alpha` 기준으로 진행
- 메인 화면 엔트리는 `src/app/main` 기준으로 정리하고 패키지 루트 export를 함께 관리
- 버튼 편집 입력은 기본 readonly 상태를 유지하고 검증 실패 시 이전 값으로 복원
## 상세 작업 내역
- 대시보드 위젯, 입력 컴포넌트, 메인 화면 구조 변경이 동시에 진행되며 앱 성격이 샘플 갤러리 중심으로 이동
- Nexus 배포 설정과 인증 방식을 실사용 기준으로 정리해 alpha 배포 경로를 실제로 검증
- `popup`, `select`, `checkCombo`, `buttonEditableInput`을 추가하며 입력 UI 패키지 확장 패턴을 구체화
- 메인 화면을 `src/app/main`으로 재정리하면서 이후 헤더/사이드바/콘텐츠 분리 작업의 기반을 마련
## 스크린샷
![button-editable-input](../assets/worklogs/2026-04-01/button-editable-input.png)
![check-combo-input](../assets/worklogs/2026-04-01/check-combo-input.png)
![dashboard-multi-progress](../assets/worklogs/2026-04-01/dashboard-multi-progress.png)
![dashboard-progress](../assets/worklogs/2026-04-01/dashboard-progress.png)
![main-content-fullscreen-toggle](../assets/worklogs/2026-04-01/main-content-fullscreen-toggle.png)
![popup-input](../assets/worklogs/2026-04-01/popup-input.png)
![select-input](../assets/worklogs/2026-04-01/select-input.png)
## 소스
- `src/components/dashboard/progress/ProgressUI.tsx`, `src/components/dashboard/multiProgress/MultiProgressUI.tsx`: 대시보드 진행률 표현을 컴포넌트화해 카드/샘플/위젯에서 공통으로 쓰게 정리했습니다.
- `src/components/inputs/popup/PopupUI.tsx`, `src/components/inputs/select/SelectUI.tsx`, `src/components/inputs/checkCombo/CheckComboUI.tsx`: 신규 입력군을 같은 패키지 규칙으로 추가해 선택형 입력 범위를 넓혔습니다.
- `src/components/inputs/specialized/buttonEditableInput/ButtonEditableInputUI.tsx`: readonly 상태와 편집 확정 버튼을 가진 클릭 편집 입력을 도입했습니다.
- `src/app/main/MainContent.tsx`: 메인 콘텐츠를 `Docs / APIs` 기준 카드 레이아웃으로 나눠 현재 앱 성격에 맞는 메인 화면으로 재정리했습니다.
```diff
diff --git a/src/components/inputs/specialized/buttonEditableInput/ButtonEditableInputUI.tsx b/src/components/inputs/specialized/buttonEditableInput/ButtonEditableInputUI.tsx
+ const [isEditing, setIsEditing] = useState(false);
...
+ readOnly={!isEditing}
+ commitPlugins={mergedCommitPlugins}
...
+ {isEditing ? confirmButtonLabel : editButtonLabel}
diff --git a/src/app/main/MainContent.tsx b/src/app/main/MainContent.tsx
+ {activeTopMenu === 'docs' ? (
+ <div className="app-main-panel">
+ <Card title={`Docs / ${selectedDocsMenu}`} ...>
+ {selectedDocs.map((document) => (
+ <MarkdownPreviewCard document={document} />
+ ))}
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- A app-dist/assets/2026-03-30-C4SD1FRx.js
- A app-dist/assets/2026-03-31-DwLJWvh2.js
- A app-dist/assets/2026-04-01-D5gI7Q4h.js
- A app-dist/assets/AntdIcon-Byo_R91X.js
- A app-dist/assets/CloseOutlined-B6nrJF3-.js
- A app-dist/assets/InputUI-DAmC5DJh.js
- A app-dist/assets/MultiProgressUI--uB5kqTr.js
- A app-dist/assets/ProgressUI-C91UL-oJ.js
- A app-dist/assets/README-CI9EVrw_.js
- A app-dist/assets/README-O9_O-4tf2.js
- A app-dist/assets/Sample-6Ml90fMj.js
- A app-dist/assets/Sample-BJxnglT1.js
- A app-dist/assets/Sample-BPCdH5hH.js
- A app-dist/assets/Sample-CLup9Uwo.js
- A app-dist/assets/Sample-CeT4nPqx.js
- A app-dist/assets/Sample-DKoCtyPX.js
- A app-dist/assets/Sample-DMEGMJwT.js
- A app-dist/assets/Sample-Dyso1eHr.js
- A app-dist/assets/Sample-E6V4D3Du.js
- A app-dist/assets/Sample-LB0lRdor.js
- A app-dist/assets/Sample-xgRr-oUd.js
- A app-dist/assets/SearchOutlined-Civ7xtmP.js
- A app-dist/assets/TmsDeliveryFlowSample-BHeS93-n.js
- A app-dist/assets/TmsDeliveryMetricsSample-BQV5az65.js
- A app-dist/assets/ValidInputSample-C9pl9si5.js
- A app-dist/assets/WidgetShell-DhXCYrC8.js
- A app-dist/assets/WmsInboundOutboundSample-BZCM3_0V.js
- A app-dist/assets/WmsInventoryTrendSample-DvxPBjgx.js
- A app-dist/assets/card-BpKFEf6A.js
- A app-dist/assets/check-combo-Bz7kGmN1.js
- A app-dist/assets/clsx-CzIxj0DI.js
- A app-dist/assets/component-plugin-BjxKibxS.js
- A app-dist/assets/dashboard-report-presets-Bh8duNGL.js
- A app-dist/assets/feature-template-D3D0o1kc.js
- A app-dist/assets/index-BQsYfbAI.js
- A app-dist/assets/index-CaXbpawn.css
- A app-dist/assets/input-B6oA1SZJ.js
- A app-dist/assets/input.plugin-ulF_zEvq.js
- A app-dist/assets/jsx-runtime-CNArSbpp.js
- A app-dist/assets/overview-DgYaz2rW.js
- A app-dist/assets/popup-BGFdvx2z.js
- A app-dist/assets/project-setup-jU8Nv-E8.js
- A app-dist/assets/select-DYfkmyn8.js
- A app-dist/assets/select-kIZVYgkF.js
- A app-dist/assets/status-badge-1fx0opaz.js
- A app-dist/assets/wave-DQjt-ubw.js
- A app-dist/assets/worklog-template-DE_f72dx.js
- A app-dist/index.html
- A docker-compose.yml
- M docs/worklogs/2026-04-01.md
- M package-lock.json
- M package.json
- M src/App.tsx
- A src/app/main/MainContent.tsx
- A src/app/main/MainHeader.tsx
- R src/views/main/MainSidebar.tsx -> src/app/main/MainSidebar.tsx
- R src/views/main/MainView.tsx -> src/app/main/MainView.tsx
- R src/views/main/index.ts -> src/app/main/index.ts
- R src/views/main/types.ts -> src/app/main/types.ts
- A src/app/manifests/docs.manifest.ts
- A src/app/manifests/samples.manifest.ts
- M src/components/inputs/primitives/input/index.ts
- A src/components/inputs/specialized/buttonEditableInput/ButtonEditableInputUI.css
- A src/components/inputs/specialized/buttonEditableInput/ButtonEditableInputUI.tsx
- A src/components/inputs/specialized/buttonEditableInput/index.ts
- A src/components/inputs/specialized/buttonEditableInput/samples/Sample.tsx
- M src/components/markdownPreview/MarkdownPreviewList.tsx
- M src/components/markdownPreview/index.ts
- M src/components/markdownPreview/registry.ts
- M src/components/status-badge/index.ts
- M src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx
- M src/features/layout/dashboard-report-gallery/DashboardReportGalleryLayout.tsx
- M src/features/layout/docs-markdown-preview/DocsMarkdownPreviewLayout.tsx
- M src/features/layout/feature-markdown-preview/FeatureMarkdownPreviewListLayout.tsx
- M src/features/layout/widget-sample-gallery/SampleWidgetsLayout.tsx
- A src/index.ts
- M src/samples/registry.ts
- M src/styles.css
- M src/views/main/MainContent.tsx
- D src/views/main/MainHeader.tsx
- A tsconfig.lib.json
- M README.md
- M docs/README.md
- M docs/features/project-setup.md
- A src/views/main/MainContent.tsx
- A src/views/main/MainHeader.tsx
- A src/views/main/MainSidebar.tsx
- A src/views/main/MainView.tsx
- A src/views/main/index.ts
- A src/views/main/types.ts
- A docs/components/check-combo.md
- A docs/components/popup.md
- A docs/components/select.md
- A docs/worklogs/2026-04-01.md
- A src/components/dashboard/multiProgress/MultiProgressUI.tsx
- A src/components/dashboard/multiProgress/index.ts
- A src/components/dashboard/multiProgress/plugins/index.ts
- A src/components/dashboard/multiProgress/plugins/multi-progress.plugin.ts
- A src/components/dashboard/multiProgress/samples/Sample.tsx
- A src/components/dashboard/multiProgress/types/index.ts
- A src/components/dashboard/multiProgress/types/multi-progress.ts
- A src/components/dashboard/progress/ProgressUI.tsx
- A src/components/dashboard/progress/index.ts
- A src/components/dashboard/progress/plugins/index.ts
- A src/components/dashboard/progress/plugins/progress.plugin.ts
- A src/components/dashboard/progress/samples/Sample.tsx
- A src/components/dashboard/progress/types/index.ts
- A src/components/dashboard/progress/types/progress.ts
- A src/components/inputs/checkCombo/CheckComboUI.tsx
- A src/components/inputs/checkCombo/index.ts
- A src/components/inputs/checkCombo/plugins/check-combo.plugin.ts
- A src/components/inputs/checkCombo/plugins/index.ts
- A src/components/inputs/checkCombo/samples/Sample.tsx
- A src/components/inputs/checkCombo/types/check-combo.ts
- A src/components/inputs/checkCombo/types/index.ts
- A src/components/inputs/popup/PopupUI.tsx
- A src/components/inputs/popup/index.ts
- A src/components/inputs/popup/plugins/index.ts
- A src/components/inputs/popup/plugins/popup.plugin.ts
- A src/components/inputs/popup/samples/Sample.tsx
- A src/components/inputs/popup/types/index.ts
- A src/components/inputs/popup/types/popup.ts
- A src/components/inputs/select/SelectUI.tsx
- A src/components/inputs/select/index.ts
- A src/components/inputs/select/plugins/index.ts
- A src/components/inputs/select/plugins/select.plugin.ts
- A src/components/inputs/select/samples/Sample.tsx
- A src/components/inputs/select/types/index.ts
- A src/components/inputs/select/types/select.ts
- A src/components/navigation/SectionMenuLayout.tsx
- M src/components/navigation/index.ts
- A src/data/dashboard-report-presets.ts
- A src/features/dashboard/TmsDashboardFeatureSamples.tsx
- A src/features/dashboard/WmsDashboardFeatureSamples.tsx
- A src/features/layout/dashboard-feature-gallery/DashboardFeatureGalleryLayout.tsx
- A src/features/layout/dashboard-feature-gallery/index.ts
- A src/features/layout/dashboard-report-gallery/DashboardReportGalleryLayout.tsx
- A src/features/layout/dashboard-report-gallery/index.ts
- M src/widgets/core/WidgetShell.tsx
- A src/widgets/dashboard-report-card/DashboardReportCardWidget.tsx
- A src/widgets/dashboard-report-card/index.ts
- A src/widgets/dashboard-report-card/samples/TmsDeliveryFlowSample.tsx
- A src/widgets/dashboard-report-card/samples/TmsDeliveryMetricsSample.tsx
- A src/widgets/dashboard-report-card/samples/WmsInboundOutboundSample.tsx
- A src/widgets/dashboard-report-card/samples/WmsInventoryTrendSample.tsx
- M src/widgets/registry.ts
## 실행 커맨드
```bash
npm run build
npm publish --tag alpha
npm run capture:component -- --date 2026-04-01 --name check-combo-input
npm run capture:component -- --date 2026-04-01 --name select-input
npm run capture:component -- --date 2026-04-01 --name popup-input
npm run capture:fullscreen -- --date 2026-04-01
```
## 변경 파일
- `docs/worklogs/2026-04-01.md`
- `package.json`
- `src/components/dashboard/progress/ProgressUI.tsx`
- `src/components/dashboard/multiProgress/MultiProgressUI.tsx`
- `src/components/inputs/popup/PopupUI.tsx`
- `src/components/inputs/select/SelectUI.tsx`
- `src/components/inputs/checkCombo/CheckComboUI.tsx`
- `src/components/inputs/specialized/buttonEditableInput/ButtonEditableInputUI.tsx`
- `src/app/main/MainContent.tsx`
- `src/styles.css`

396
docs/worklogs/2026-04-02.md Executable file
View File

@@ -0,0 +1,396 @@
# 2026-04-02 작업일지
## 오늘 작업
- 루트 `README.md`를 현재 앱 구조와 스크립트 기준으로 최신화
- `docs/README.md`의 문서 운영 기준을 실제 디렉터리 구조와 Docs 수집 방식에 맞게 정리
- 오늘 작업일지에 문서 최신화 내역을 추가
- `window-ui` 공통 컴포넌트를 추가
- 윈도우 헤더 드래그, 리사이즈, 최소화, 최대화, 복원 동작을 구성
- 모바일 터치 기반 드래그와 버튼 터치 영역을 보정
- 멀티 윈도우 추천 배치, 가로 분할, 세로 분할, 그리드 배치를 추가
- 배치 이후 창 순서를 사용자가 조정할 수 있는 UI를 추가
- 활성화된 윈도우가 항상 최상단으로 오도록 z-index 정렬을 추가
- 전체화면 버튼을 빈 공간 우선 확장 후 부모 전체 확장 방식으로 보정
- `previewer-ui` 공통 컴포넌트를 추가
- `text`, `json`, `code`, `image`, `markdown`, `empty` preview 타입을 구성
- JSON preview에 영역별 컬러 토큰 표시를 추가
- Code preview에 언어 선택과 VSCode 스타일 표현을 추가
- Previewer 샘플에 TypeScript, JSON, HTML, CSS, Bash, SQL, YAML 예제를 추가
- 메인 앱을 PWA 오프라인 캐시 전략으로 보강
- 모바일 헤더/사이드바 동작을 손봐서 메뉴 선택 흐름을 단순화
- 헤더 액션 영역과 전체화면 토글 동작을 재정리
- `src/layer/gesture`를 추가해 모바일 우측 상단 제스처를 레이어로 관리
- `src/store/appStore`를 추가해 현재 페이지와 포커스 컴포넌트 상태를 관리
- `src/layer/search`를 추가해 통합검색 모달과 검색 옵션을 레이어 컨텍스트로 관리
- `SearchCommandModal` 통합 검색 컴포넌트를 추가
- 문서, API, 컴포넌트, 위젯 키워드 자동 추천과 빠른 이동을 연결
- 모바일 입력 확대 방지와 검색 모달 포커스 동작을 보정
- 빌드 산출물 추적 정리를 위해 `.gitignore`를 보강하고 `app-dist`, `dev-dist` 무시 준비를 진행
- `etc/db/work-db` 경로로 PostgreSQL 도커 구성을 분리하고 `.env` 기반 설정을 정리
- `etc/servers/work-server``Fastify + PostgreSQL` 기반 작업 API 서버를 추가
- Plan 게시판용 등록, 수정, 삭제, 테이블 생성 API를 추가
- Plan 게시판이 웹서버 프록시(`/api`)를 통해 `work-server`를 바라보도록 수정
- Plan 항목 등록 시 작업 ID, 요청 메모, 대상 브랜치/릴리즈 흐름을 함께 관리하도록 화면과 서버 모델을 정리
- Plan 항목을 주기적으로 읽어 `등록 -> 작업중(브랜치 준비)``개발완료 -> 완료(release merge)` 흐름을 자동화하는 worker를 추가
- `scripts/run-plan-codex-once.mjs`로 단건 Plan 항목을 읽어 Codex 실행 결과를 조치 이력에 반영하는 자동화 스크립트를 추가
- Plan 자동화 실패 시 상태를 `이슈`로 바꾸지 않고 해시태그/오류 이력으로 관리하도록 변경
- `etc` 내부에서 커밋되면 안 되는 `.env`, `node_modules`, `dist`, 로그 파일을 루트 `.gitignore`에서도 명시적으로 제외
- `작업시작` 이후에는 원본 요청을 수정할 수 없도록 잠금 규칙을 추가
- 작업 이후 변경 사항은 일반 조치 이력과 이슈 조치 이력으로 누적 기록되도록 확장
- 컴포넌트/화면 스크린샷을 생성한 뒤 작업일지 `## 화면 캡처` 섹션에 자동 링크하는 스크립트를 정리
- Plan 게시판 메모 상세의 모바일 높이를 절반 수준으로 줄이도록 조정
- 작업일지 캡처 스크립트를 공통 유틸로 정리하고 누락 방지용 모바일 Plan 상세 캡처를 추가
## 이슈 및 메모
- `vite-plugin-pwa``vite@8`과 peer 경고가 있어 도커 실행 시 `npm install --force`가 필요
- 작업일지 이미지 표시를 위해 markdown preview가 전체 블록을 렌더링하도록 수정
- 컴포넌트 캡처 자동화는 `Playwright` 기반으로 스크린샷을 생성하고 작업일지 링크를 함께 추가
- 캡처 스크립트는 날짜별 폴더를 만들고 동일 날짜 작업일지에 이미지 Markdown 링크를 자동 삽입함
- iOS에서는 제스처 후 키보드 오픈 보장이 제한적이라 강제 포커스 로직보다 레이어 분리와 단순 동작 유지에 우선순위를 둠
- Plan 자동화는 현재 저장소 worktree가 깨끗해야 브랜치 생성이 가능함
- 현재 저장소에는 `release` 브랜치가 아직 없어 실제 자동 merge를 쓰려면 브랜치 생성이 먼저 필요함
- 컨테이너 내부 git은 `safe.directory` 이슈가 있어 자동 처리 로직을 추가함
- Plan 이력 테이블 추가 이후 기존 실패 데이터에는 과거 이력이 없을 수 있어 재시도로 새 이력을 만드는 방식으로 정리함
- `run-plan-codex-once`는 최근 조치 이력/이슈 이력을 프롬프트에 포함해 재작업이나 보완 요청을 우선 반영하도록 구성함
## 결정 사항
- 공통 미리보기 컴포넌트는 `src/components/previewer` 아래에서 관리
- 공통 윈도우 컴포넌트는 `src/components/window` 아래에서 관리
- 작업일지 캡처 이미지는 `docs/assets/worklogs/YYYY-MM-DD/`에 저장
- 컴포넌트 캡처는 전체 화면이 아니라 샘플 컴포넌트 영역 중심으로 생성
- 앱 상태 저장은 `src/store`
- 화면 전역 UI는 `src/layer`
- 통합검색은 store가 아니라 layer context로 관리
- 프로젝트 내부 부가 서버/DB는 `etc/servers/<name>`, `etc/db/<name>` 구조로 관리
- `etc` 내부의 비밀값/생성물은 각 하위 `.gitignore`와 루트 `.gitignore`에서 이중으로 차단
- Plan 게시판은 상태 수기 변경보다 자동화 중심 흐름을 우선하고, 사용자는 상태별 허용 액션 버튼만 사용
- 원본 요청은 `작업시작` 이후 읽기 전용으로 전환하고 추가 조치사항은 이력으로만 남김
- 스크린샷 자산은 날짜별 작업일지와 1:1로 연결하고 캡처 직후 문서 링크까지 자동 반영
## 상세 작업 내역
- 초기 문서에 남아 있던 구형 `src/components/inputs`, `src/views/main` 기준 설명을 현재 `src/app/main`, `src/components/*`, `src/features/planBoard` 구조에 맞춰 교체
- 루트 안내문에서 실행 스크립트, 앱 섹션, 문서 위치를 한 번에 확인할 수 있도록 정리
- Docs 가이드에 실제 Markdown 수집 범위와 문서 최신화 기록 원칙을 반영
- `window-ui`, `previewer-ui` 추가 이후 앱 전반 구조가 컴포넌트 문서/샘플 허브 형태로 더 선명해짐
- PWA 오프라인 캐시 전략과 모바일 레이아웃 보정을 함께 진행해 실제 사용 환경 대응 범위를 넓힘
- `gesture`, `search`, `store/appStore`를 나눠 전역 UI와 상태 책임을 분리하는 기준을 세움
- 통합검색은 문서, API, 컴포넌트, 위젯을 한 흐름에서 찾도록 묶고 모달/제스처/포커스 추적을 연결
- 빌드 산출물 추적을 정리하며 `.gitignore`와 Git 인덱스 운영 기준도 함께 정비
- `work-server`는 Plan 게시판 API, DB 연결, worker 자동화 역할을 함께 가지도록 확장
- Plan 게시판은 단순 상태 보드가 아니라 등록 정보, 조치 이력, 이슈 이력, 원본 요청 잠금 규칙까지 함께 관리하는 작업 큐 성격으로 확장
- 등록 단계에서는 작업 ID와 요청 내용을 남기고, 자동화 단계에서는 브랜치 확보, Codex 실행, 커밋/푸시, release 반영 가능 여부를 순차적으로 판단하도록 흐름을 정리
- 자동화 worker는 현재 저장소에 직접 브랜치를 만들고 `release` 브랜치 머지까지 시도하되, 작업 디렉터리가 dirty면 이슈 태그와 오류 이력을 남기고 안전하게 멈춤
- Plan 화면에서는 브랜치명, 자동화 상태, 최근 오류, 이슈 태그, 조치 이력을 함께 확인할 수 있게 보강
- `작업시작` 이후 서버는 원본 요청 수정 API를 `409`로 차단하고, 화면에서도 메모/작업 ID를 읽기 전용으로 전환
- 스크린샷 처리 스크립트는 `docs/assets/worklogs/YYYY-MM-DD/` 저장, `docs/worklogs/YYYY-MM-DD.md` 링크 반영, 중복 링크 방지까지 한 번에 수행하도록 맞춤
- 검색 모달, 프리뷰어, 윈도우 UI 캡처는 각각 샘플 셀렉터를 기준으로 잘라 저장해 작업일지 증적 자료로 재사용 가능하게 구성
- Plan 게시판은 모바일에서 `새 메모` 상세를 바로 캡처하는 스크립트를 추가해 메모 높이 보정 결과를 작업일지에 바로 남길 수 있게 함
## 스크린샷
![plan-board-mobile-memo-detail](../assets/worklogs/2026-04-02/plan-board-mobile-memo-detail.png)
![previewer-ui](../assets/worklogs/2026-04-02/previewer-ui.png)
![search-command](../assets/worklogs/2026-04-02/search-command.png)
![window-ui](../assets/worklogs/2026-04-02/window-ui.png)
## 소스
- `src/components/window/WindowUI.tsx`: 드래그, 리사이즈, 분할 배치를 지원하는 공통 윈도우 UI를 추가했습니다.
- `src/components/previewer/PreviewerUI.tsx`: `text/json/code/image/markdown` 등 여러 증적 형식을 한 컴포넌트에서 미리보게 만들었습니다.
- `src/components/search/SearchCommandModal.tsx`: 문서, API, 컴포넌트, 위젯을 한 번에 찾는 통합 검색 모달을 추가했습니다.
- `src/features/planBoard/PlanBoardPage.tsx`: Plan 게시판 UI와 상세 흐름을 본격 추가했습니다.
- `etc/servers/work-server/src/workers/plan-worker.ts`, `scripts/run-plan-codex-once.mjs`: Plan 자동화 워커와 Codex 실행기를 도입해 브랜치 준비, 작업 실행, 반영 흐름을 자동화했습니다.
```diff
diff --git a/etc/servers/work-server/src/workers/plan-worker.ts b/etc/servers/work-server/src/workers/plan-worker.ts
+export class PlanWorker {
+ private readonly workerId: string;
+ private timer: NodeJS.Timeout | null = null;
+ private running = false;
...
+ await this.processRegisteredPlans();
+ await this.processExecutablePlans();
+ await this.processReleaseReadyPlans();
diff --git a/scripts/run-plan-codex-once.mjs b/scripts/run-plan-codex-once.mjs
+const planItemId = process.env.PLAN_ITEM_ID ? Number(process.env.PLAN_ITEM_ID) : null;
+const codexBin = process.env.PLAN_CODEX_BIN ?? 'codex';
...
+function requiresSourceChange(note) {
+ return /(?:\/|\\).+\.[a-z0-9]+/i.test(text) || /(생성|만들어|추가|수정|변경|삭제|파일)/.test(text);
+}
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- M scripts/run-plan-codex-once.mjs
- M .gitignore
- M etc/servers/work-server/.env.example
- M etc/servers/work-server/docker-compose.yml
- M etc/servers/work-server/src/services/plan-service.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M src/features/planBoard/PlanBoardPage.tsx
- M src/main.tsx
- M etc/servers/work-server/src/config/env.ts
- M etc/servers/work-server/src/routes/plan.ts
- M etc/servers/work-server/src/services/git-service.ts
- M src/features/planBoard/types.ts
- M package.json
- A scripts/run-plan-codex-once.mjs
- M docs/README.md
- M docs/worklogs/2026-04-02.md
- M src/features/planBoard/api.ts
- M docker-compose.yml
- A etc/db/work-db/.env.example
- A etc/db/work-db/.gitignore
- A etc/db/work-db/README.md
- A etc/db/work-db/docker-compose.yml
- A etc/servers/work-server/.env.example
- A etc/servers/work-server/.gitignore
- A etc/servers/work-server/README.md
- A etc/servers/work-server/docker-compose.yml
- A etc/servers/work-server/package-lock.json
- A etc/servers/work-server/package.json
- A etc/servers/work-server/src/app.ts
- A etc/servers/work-server/src/config/env.ts
- A etc/servers/work-server/src/db/client.ts
- A etc/servers/work-server/src/lib/identifier.ts
- A etc/servers/work-server/src/routes/crud.ts
- A etc/servers/work-server/src/routes/ddl.ts
- A etc/servers/work-server/src/routes/health.ts
- A etc/servers/work-server/src/routes/plan.ts
- A etc/servers/work-server/src/routes/schema.ts
- A etc/servers/work-server/src/server.ts
- A etc/servers/work-server/src/services/git-service.ts
- A etc/servers/work-server/src/services/plan-service.ts
- A etc/servers/work-server/src/workers/plan-worker.ts
- A etc/servers/work-server/tsconfig.json
- M src/app/main/MainContent.tsx
- M src/app/main/MainHeader.tsx
- M src/app/main/MainSidebar.tsx
- M src/app/main/MainView.tsx
- M src/app/main/types.ts
- A src/features/planBoard/PlanBoardPage.tsx
- A src/features/planBoard/api.ts
- A src/features/planBoard/index.ts
- A src/features/planBoard/types.ts
- M src/store/appStore/types/index.ts
- M src/styles.css
- M vite.config.ts
- A docs/assets/worklogs/2026-04-02/search-command.png
- M docs/templates/worklog-template.md
- M docs/worklogs/2026-03-30.md
- M docs/worklogs/2026-03-31.md
- M docs/worklogs/2026-04-01.md
- A scripts/capture-search-command-screenshot.mjs
- D app-dist/apple-touch-icon.svg
- D app-dist/assets/2026-03-30-C9zIPzPv.js
- D app-dist/assets/2026-03-31-DnTx-3Nm.js
- D app-dist/assets/2026-04-01-HPg12E04.js
- D app-dist/assets/AntdIcon-Byo_R91X.js
- D app-dist/assets/CloseOutlined-B6nrJF3-.js
- D app-dist/assets/InputUI-w7gS2eD_.js
- D app-dist/assets/MultiProgressUI-BrtiP5fC.js
- D app-dist/assets/ProgressUI-CcSfL1yk.js
- D app-dist/assets/README-C7FUDFuk.js
- D app-dist/assets/README-CCe9ioJ1.js
- D app-dist/assets/Sample-3CFWfRoz.js
- D app-dist/assets/Sample-AyuQ97sV.css
- D app-dist/assets/Sample-BVdBpN2O.js
- D app-dist/assets/Sample-BX522g-L.js
- D app-dist/assets/Sample-BcLA6P4T.js
- D app-dist/assets/Sample-BdE7za0g.js
- D app-dist/assets/Sample-CJuh1XyL.js
- D app-dist/assets/Sample-CXTqMdzC.js
- D app-dist/assets/Sample-CYEm5q8n.js
- D app-dist/assets/Sample-D8WPIGkO.js
- D app-dist/assets/Sample-DCMU_ANA.js
- D app-dist/assets/Sample-DucAjs70.css
- D app-dist/assets/Sample-H43X1Va0.js
- D app-dist/assets/Sample-H8g2LJhv.js
- D app-dist/assets/Sample-IHgSYgwU.css
- D app-dist/assets/Sample-iMOOVcMn.js
- D app-dist/assets/SearchOutlined-Civ7xtmP.js
- D app-dist/assets/TmsDeliveryFlowSample-BIsYLVLu.js
- D app-dist/assets/TmsDeliveryMetricsSample-Cij5-xON.js
- D app-dist/assets/ValidInputSample-DfYNB-Xp.js
- D app-dist/assets/WidgetShell-BuID0-5g.js
- D app-dist/assets/WmsInboundOutboundSample-D8v85F-k.js
- D app-dist/assets/WmsInventoryTrendSample-BaOj-o94.js
- D app-dist/assets/button-editable-input-TvwRLJmy.png
- D app-dist/assets/card-C6TZ9YUa.js
- D app-dist/assets/check-combo-Bz7kGmN1.js
- D app-dist/assets/clsx-CzIxj0DI.js
- D app-dist/assets/component-plugin-DrEfBYjG.js
- D app-dist/assets/dashboard-report-presets-BmenDHNL.js
- D app-dist/assets/feature-template-C3ggnfNS.js
- D app-dist/assets/index-CR5AbEsh.js
- D app-dist/assets/index-WZ9gt1kR.css
- D app-dist/assets/input-B6oA1SZJ.js
- D app-dist/assets/input.plugin-BigPQCa9.js
- D app-dist/assets/jsx-runtime-CNArSbpp.js
- D app-dist/assets/main-content-fullscreen-toggle-Cppu1H3t.png
- D app-dist/assets/overview-BtWbOP4n.js
- D app-dist/assets/popup-BGFdvx2z.js
- D app-dist/assets/previewer-ui-BheoU6aq.js
- D app-dist/assets/project-setup-B38Eco2m.js
- D app-dist/assets/select-BVXn7KWj.js
- D app-dist/assets/select-oi1Bjv-c.js
- D app-dist/assets/status-badge-C7aul6sS.js
- D app-dist/assets/wave-DQjt-ubw.js
- D app-dist/assets/window-ui-CXDuYQu1.js
- D app-dist/assets/workbox-window.prod.es5-B4qug_J_.js
- D app-dist/assets/worklog-template-Donys780.js
- D app-dist/favicon.svg
- D app-dist/index.html
- D app-dist/manifest.webmanifest
- D app-dist/pwa-192x192.svg
- D app-dist/pwa-512x512.svg
- D app-dist/sw.js
- D app-dist/workbox-8c29f6e4.js
- D dev-dist/sw.js
- D dev-dist/workbox-5a5d9309.js
- A docs/components/search-command.md
- A docs/features/search-layer.md
- M index.html
- A src/components/search/SearchCommandModal.tsx
- A src/components/search/index.ts
- A src/components/search/types.ts
- M src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx
- M src/features/layout/widget-sample-gallery/SampleWidgetsLayout.tsx
- M src/index.ts
- A src/layer/gesture/context/GestureContext.tsx
- A src/layer/gesture/hooks/useGestureLayer.ts
- A src/layer/gesture/index.ts
- A src/layer/gesture/types/index.ts
- A src/layer/index.ts
- A src/layer/search/context/SearchLayerContext.tsx
- A src/layer/search/hooks/useSearchLayer.ts
- A src/layer/search/index.ts
- A src/layer/search/types/index.ts
- A src/store/appStore/context/AppStoreContext.tsx
- A src/store/appStore/hooks/useAppStore.ts
- A src/store/appStore/index.ts
- A src/store/appStore/types/index.ts
- A src/store/index.ts
- A app-dist/apple-touch-icon.svg
- R app-dist/assets/2026-03-30-C4SD1FRx.js -> app-dist/assets/2026-03-30-C9zIPzPv.js
- R app-dist/assets/2026-03-31-DwLJWvh2.js -> app-dist/assets/2026-03-31-DnTx-3Nm.js
- D app-dist/assets/2026-04-01-D5gI7Q4h.js
- A app-dist/assets/2026-04-01-HPg12E04.js
- R app-dist/assets/InputUI-DAmC5DJh.js -> app-dist/assets/InputUI-w7gS2eD_.js
- R app-dist/assets/MultiProgressUI--uB5kqTr.js -> app-dist/assets/MultiProgressUI-BrtiP5fC.js
- R app-dist/assets/ProgressUI-C91UL-oJ.js -> app-dist/assets/ProgressUI-CcSfL1yk.js
- R app-dist/assets/README-CI9EVrw_.js -> app-dist/assets/README-C7FUDFuk.js
- R app-dist/assets/README-O9_O-4tf2.js -> app-dist/assets/README-CCe9ioJ1.js
- R app-dist/assets/Sample-CeT4nPqx.js -> app-dist/assets/Sample-3CFWfRoz.js
- A app-dist/assets/Sample-AyuQ97sV.css
- R app-dist/assets/Sample-Dyso1eHr.js -> app-dist/assets/Sample-BVdBpN2O.js
- A app-dist/assets/Sample-BX522g-L.js
- R app-dist/assets/Sample-DKoCtyPX.js -> app-dist/assets/Sample-BcLA6P4T.js
- R app-dist/assets/Sample-DMEGMJwT.js -> app-dist/assets/Sample-BdE7za0g.js
- R app-dist/assets/Sample-BJxnglT1.js -> app-dist/assets/Sample-CJuh1XyL.js
- R app-dist/assets/Sample-xgRr-oUd.js -> app-dist/assets/Sample-CXTqMdzC.js
- R app-dist/assets/Sample-BPCdH5hH.js -> app-dist/assets/Sample-CYEm5q8n.js
- R app-dist/assets/Sample-LB0lRdor.js -> app-dist/assets/Sample-D8WPIGkO.js
- R app-dist/assets/Sample-6Ml90fMj.js -> app-dist/assets/Sample-DCMU_ANA.js
- A app-dist/assets/Sample-DucAjs70.css
- D app-dist/assets/Sample-E6V4D3Du.js
- A app-dist/assets/Sample-H43X1Va0.js
- R app-dist/assets/Sample-CLup9Uwo.js -> app-dist/assets/Sample-H8g2LJhv.js
- A app-dist/assets/Sample-IHgSYgwU.css
- A app-dist/assets/Sample-iMOOVcMn.js
- R app-dist/assets/TmsDeliveryFlowSample-BHeS93-n.js -> app-dist/assets/TmsDeliveryFlowSample-BIsYLVLu.js
- R app-dist/assets/TmsDeliveryMetricsSample-BQV5az65.js -> app-dist/assets/TmsDeliveryMetricsSample-Cij5-xON.js
- R app-dist/assets/ValidInputSample-C9pl9si5.js -> app-dist/assets/ValidInputSample-DfYNB-Xp.js
- R app-dist/assets/WidgetShell-DhXCYrC8.js -> app-dist/assets/WidgetShell-BuID0-5g.js
- R app-dist/assets/WmsInboundOutboundSample-BZCM3_0V.js -> app-dist/assets/WmsInboundOutboundSample-D8v85F-k.js
- R app-dist/assets/WmsInventoryTrendSample-DvxPBjgx.js -> app-dist/assets/WmsInventoryTrendSample-BaOj-o94.js
- A app-dist/assets/button-editable-input-TvwRLJmy.png
- R app-dist/assets/card-BpKFEf6A.js -> app-dist/assets/card-C6TZ9YUa.js
- R app-dist/assets/component-plugin-BjxKibxS.js -> app-dist/assets/component-plugin-DrEfBYjG.js
- R app-dist/assets/dashboard-report-presets-Bh8duNGL.js -> app-dist/assets/dashboard-report-presets-BmenDHNL.js
- R app-dist/assets/feature-template-D3D0o1kc.js -> app-dist/assets/feature-template-C3ggnfNS.js
- R app-dist/assets/index-BQsYfbAI.js -> app-dist/assets/index-CR5AbEsh.js
- D app-dist/assets/index-CaXbpawn.css
- A app-dist/assets/index-WZ9gt1kR.css
- R app-dist/assets/input.plugin-ulF_zEvq.js -> app-dist/assets/input.plugin-BigPQCa9.js
- A app-dist/assets/main-content-fullscreen-toggle-Cppu1H3t.png
- R app-dist/assets/overview-DgYaz2rW.js -> app-dist/assets/overview-BtWbOP4n.js
- A app-dist/assets/previewer-ui-BheoU6aq.js
- R app-dist/assets/project-setup-jU8Nv-E8.js -> app-dist/assets/project-setup-B38Eco2m.js
- R app-dist/assets/select-DYfkmyn8.js -> app-dist/assets/select-BVXn7KWj.js
- R app-dist/assets/select-kIZVYgkF.js -> app-dist/assets/select-oi1Bjv-c.js
- R app-dist/assets/status-badge-1fx0opaz.js -> app-dist/assets/status-badge-C7aul6sS.js
- A app-dist/assets/window-ui-CXDuYQu1.js
- A app-dist/assets/workbox-window.prod.es5-B4qug_J_.js
- R app-dist/assets/worklog-template-DE_f72dx.js -> app-dist/assets/worklog-template-Donys780.js
- A app-dist/favicon.svg
- M app-dist/index.html
- A app-dist/manifest.webmanifest
- A app-dist/pwa-192x192.svg
- A app-dist/pwa-512x512.svg
- A app-dist/sw.js
- A app-dist/workbox-8c29f6e4.js
- A dev-dist/sw.js
- A dev-dist/workbox-5a5d9309.js
- A docs/assets/worklogs/2026-04-01/.gitkeep
- A docs/assets/worklogs/2026-04-01/button-editable-input.png
- A docs/assets/worklogs/2026-04-01/main-content-fullscreen-toggle.png
- A docs/assets/worklogs/2026-04-02/.gitkeep
- A docs/assets/worklogs/2026-04-02/previewer-ui.png
- A docs/assets/worklogs/2026-04-02/window-ui.png
- A docs/components/previewer-ui.md
- A docs/components/window-ui.md
- A docs/worklogs/2026-04-02.md
- M package-lock.json
- A public/apple-touch-icon.svg
- A public/favicon.svg
- A public/pwa-192x192.svg
- A public/pwa-512x512.svg
- A scripts/capture-component-screenshot.mjs
- A scripts/capture-fullscreen-toggle-screenshot.mjs
- M src/app/manifests/docs.manifest.ts
- M src/components/markdownPreview/MarkdownPreviewCard.tsx
- M src/components/markdownPreview/MarkdownPreviewContent.tsx
- A src/components/previewer/PreviewerUI.css
- A src/components/previewer/PreviewerUI.tsx
- A src/components/previewer/index.ts
- A src/components/previewer/samples/Sample.tsx
- A src/components/previewer/types/index.ts
- A src/components/previewer/types/previewer.ts
- A src/components/window/WindowUI.css
- A src/components/window/WindowUI.tsx
- A src/components/window/index.ts
- A src/components/window/samples/Sample.tsx
- A src/components/window/types/index.ts
- A src/components/window/types/window.ts
- M src/vite-env.d.ts
- M tsconfig.lib.json
## 실행 커맨드
```bash
npm run build
docker compose up -d
npm run plan:codex:once
node scripts/capture-search-command-screenshot.mjs --date 2026-04-02
npm run capture:plan-mobile -- --date 2026-04-02
```
## 변경 파일
- `docs/worklogs/2026-04-02.md`
- `src/components/window/WindowUI.tsx`
- `src/components/previewer/PreviewerUI.tsx`
- `src/components/search/SearchCommandModal.tsx`
- `src/features/planBoard/PlanBoardPage.tsx`
- `src/layer/search/context/SearchLayerContext.tsx`
- `src/store/appStore/context/AppStoreContext.tsx`
- `etc/servers/work-server/src/routes/plan.ts`
- `etc/servers/work-server/src/services/plan-service.ts`
- `scripts/run-plan-codex-once.mjs`

137
docs/worklogs/2026-04-03.md Executable file
View File

@@ -0,0 +1,137 @@
# 2026-04-03 작업일지
## 오늘 작업
- Plan 자동화 재처리 흐름과 main 반영 안정화
- release/main 브랜치 전략을 정리하고 서버 재시작 오류를 수정
- Plan 메모 검색 기능 추가
- Plan 상세에서 소스 작업 이력/미리보기 확인 흐름 보강
- 모바일 설정 아이콘 터치 불가 이슈를 제스처 오버레이 충돌 제거로 해결
- 작업 상세의 불필요한 `내용 조회` 버튼 제거
- Noti 설정의 서버 토큰 등록/삭제 연동 보강
- 작업 ID 기본값(`작업ID`) 처리 및 중복 제약 완화
- 완료/반영 단계에서도 소스 작업 이력이 누락되지 않도록 저장 로직 보강
- Web Push 등록/호환 처리 보강(미지원 API 404 노출 완화)
- 4월 3일 작업 증적용 스크린샷(앱 설정, 알림 설정) 보강
- Plan 상세 증적 탭 하단에 스크린샷/작업일지/preview/source 전체화면 미리보기 추가
## 이슈 및 메모
- release -> main 자동 머지 이후 브랜치 상태 꼬임이 반복되어 브랜치 전략 재정비 필요
- 모바일 터치 이슈는 기능 오류보다 레이어 겹침(오버레이) 영향이 컸음
- 알림 기능은 로컬 UI 상태와 서버 토큰 상태가 분리되면 재현 어려운 장애로 이어짐
- 작업/이슈 이력 누락은 재처리 판단 정확도에 직접 영향
## 결정 사항
- Plan 자동화 브랜치 기준은 `main` 중심으로 정리
- 실패 후 재실행은 명시적 재처리 요청 기반으로 진행
- 알림 설정은 UI 토글만이 아니라 서버 토큰 등록/삭제와 동기화
- 메모/소스 작업 이력은 완료 단계 이후까지 일관 저장
## 상세 작업 내역
- 자동화 워커의 재처리, 반영, 이력 저장 경로를 손봐 동일 이슈 반복 시 추적 가능성을 높임
- release/main 전개 과정에서 발생하던 복구성 작업을 줄이기 위해 브랜치 운영 규칙을 단순화
- Plan 보드 UX는 모바일 접근성(터치/조회/검색) 중심으로 우선 개선
- 알림 관련 API 미지원 상황에서도 사용자에게 과도한 오류를 직접 노출하지 않도록 완충 처리 추가
## 스크린샷
![settings-app](../assets/worklogs/2026-04-03/settings-app.png)
![settings-notification](../assets/worklogs/2026-04-03/settings-notification.png)
## 소스
- `etc/servers/work-server/src/services/git-service.ts`: 새 이슈 브랜치를 `release`가 아니라 `main` 기준으로 만들도록 바꿔 브랜치 꼬임을 줄였습니다.
- `etc/servers/work-server/src/services/plan-service.ts`: 기본 작업 ID 처리, 중복 제약 해제, 완료 시 소스 이력 저장, `main` 반영 요청 단건화 등 자동화 추적 로직을 보강했습니다.
- `src/features/planBoard/PlanBoardPage.tsx`: 소스 작업 이력과 증적 미리보기 흐름을 상세 화면에 반영했습니다.
- `src/app/main/MainHeader.tsx`, `src/app/main/notificationApi.ts`: 알림 설정을 서버 토큰 상태와 동기화하고 미지원 환경의 404를 사용자 오류로 과하게 노출하지 않도록 조정했습니다.
- `etc/servers/work-server/src/workers/plan-worker.ts`: 재처리와 반영 흐름을 `main` 중심으로 다시 맞췄습니다.
```diff
diff --git a/etc/servers/work-server/src/services/git-service.ts b/etc/servers/work-server/src/services/git-service.ts
- const baseBranch = releaseTarget || config.releaseBranch;
+ const baseBranch = config.mainBranch;
diff --git a/etc/servers/work-server/src/services/plan-service.ts b/etc/servers/work-server/src/services/plan-service.ts
- workId: z.string().trim().min(1),
+ workId: z.string().trim().optional().default('작업ID'),
...
+ await createPlanLifecycleSourceWorkHistory(
+ id,
+ '작업완료 처리로 release 반영 대기 상태로 전환했습니다.',
+ currentRow.assigned_branch ?? currentRow.release_target ?? 'release',
+ );
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- M etc/servers/work-server/src/services/plan-service.ts
- M scripts/run-plan-codex-once.mjs
- M etc/servers/work-server/.env.example
- M etc/servers/work-server/README.md
- M etc/servers/work-server/src/config/env.ts
- M etc/servers/work-server/src/services/git-service.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M src/app/main/notificationApi.ts
- M src/app/main/MainHeader.tsx
- M etc/servers/work-server/package-lock.json
- M etc/servers/work-server/package.json
- M etc/servers/work-server/src/routes/notification.ts
- M etc/servers/work-server/src/services/notification-service.ts
- A etc/servers/work-server/src/types/web-push.d.ts
- M src/main.tsx
- A src/sw.ts
- M vite.config.ts
- M src/features/planBoard/PlanBoardPage.tsx
- M etc/servers/work-server/src/routes/plan.ts
- M src/features/planBoard/api.ts
- M src/features/planBoard/types.ts
- A etc/servers/work-server/src/services/plan-notification-service.ts
- A src/app/main/notificationApi.ts
- M src/layer/gesture/context/GestureContext.tsx
- M src/styles.css
- M etc/servers/work-server/src/app.ts
- A etc/servers/work-server/src/routes/notification.ts
- M etc/servers/work-server/src/server.ts
- A etc/servers/work-server/src/services/notification-service.ts
- M README.md
- A docs/assets/worklogs/2026-04-02/plan-board-mobile-memo-detail.png
- M docs/worklogs/2026-04-02.md
- M package.json
- M scripts/capture-component-screenshot.mjs
- M scripts/capture-fullscreen-toggle-screenshot.mjs
- A scripts/capture-plan-board-mobile-screenshot.mjs
- M scripts/capture-search-command-screenshot.mjs
- A scripts/worklog-capture-utils.mjs
- M src/app/main/MainSidebar.tsx
- M docs/README.md
- M index.html
- M src/app/main/MainView.tsx
- M src/app/main/types.ts
- M docker-compose.yml
- M src/features/planBoard/index.ts
- A docs/test001.md
- M etc/servers/work-server/docker-compose.yml
## 실행 커맨드
```bash
npm run plan:codex:once
npm run build
npm run capture:settings -- --date 2026-04-03
```
## 변경 파일
- `docs/worklogs/2026-04-03.md`
- `src/features/planBoard/PlanBoardPage.tsx`
- `etc/servers/work-server/src/workers/plan-worker.ts`
- `etc/servers/work-server/src/services/plan-service.ts`
- `etc/servers/work-server/src/services/git-service.ts`
- `src/app/main/MainHeader.tsx`
- `src/app/main/notificationApi.ts`
- `src/layer/gesture/context/GestureContext.tsx`
- `scripts/run-plan-codex-once.mjs`
- `src/styles.css`

104
docs/worklogs/2026-04-04.md Executable file
View File

@@ -0,0 +1,104 @@
# 2026-04-04 작업일지
## 오늘 작업
- Plan 목록 조회 버튼을 자동조회 중심 UX로 개편(남은 시간 표시/토글)
- 자동조회 버튼 레이아웃 깨짐/문구 중복/선택 드래그 문제 보정
- 앱 업데이트 진행 UI에 단계 표시와 프로그레스 흐름 추가
- 서비스워커 교체 흐름(`SKIP_WAITING`, 등록 확인, 타임아웃) 안정화
- 업데이트 실패 시 상태 복구와 오류 가시성 개선
- `vite-plugin-pwa` 업데이트 경로를 공식 흐름 우선으로 정리
- 자동화 Codex 실행 타임아웃 도입 및 롤백 과정 반영
- Plan 삭제 응답/정리 로직 개선
- work-server 메인 프로젝트 경로/풀 동작 정리
## 이슈 및 메모
- 업데이트 이슈는 단일 원인보다 서비스워커 교체 타이밍/등록 상태/환경 설정이 복합적으로 영향
- 자동조회는 텍스트/배지/오버레이가 겹치면 UX가 빠르게 악화됨
- Codex 타임아웃은 안전장치가 되지만 과도하면 정상 작업도 중단시킬 수 있음
## 결정 사항
- 업데이트 상태는 단순 성공/실패가 아닌 단계 기반으로 노출
- 서비스워커 등록/교체는 실패 복구 경로까지 포함해 설계
- 자동조회 UI는 버튼 본문 최소화 + 보조 배지 방식 유지
- 워커 경로 설정은 환경별 명확한 기준 경로로 통일
## 상세 작업 내역
- Plan 보드에서는 자동조회 중심 운영을 위해 상단 액션을 재설계하고 모바일/데스크톱 표시 균형을 맞춤
- 앱 업데이트 영역은 사용자 체감 기준(지금 무엇을 하는지)으로 문구와 상태 전이를 재구성
- 서비스워커 관련 반복 장애를 줄이기 위해 등록 확인, 교체 메시지 처리, 타임아웃/롤백 로직을 함께 점검
- 서버 측 경로 및 삭제 처리 개선으로 운영 중 발생하던 잔여 데이터/응답 일관성 문제를 줄임
## 스크린샷
- 저장소 기준 연결된 스크린샷 없음
## 소스
- `src/app/main/appUpdate.ts`, `src/app/main/MainHeader.tsx`: 앱 업데이트를 단계형 상태와 프로그레스 UI로 노출하고 서비스워커 등록 대기/재시도 흐름을 추가했습니다.
- `src/features/planBoard/PlanBoardPage.tsx`: 자동조회 버튼 UX와 Plan 목록 상단 액션 구성을 손봤습니다.
- `scripts/run-plan-codex-once.mjs`: Codex 자식 프로세스 종료/에러를 중복 처리하지 않도록 `settled` 가드를 추가했습니다.
- `etc/servers/work-server/src/services/plan-service.ts`: Plan 삭제 시 연관 소스작업/조치/이슈 이력까지 함께 정리하도록 트랜잭션 삭제로 바꿨습니다.
- `etc/servers/work-server/src/workers/plan-worker.ts`: 타임아웃과 실패 처리 흐름을 보강했습니다.
```diff
diff --git a/etc/servers/work-server/src/services/plan-service.ts b/etc/servers/work-server/src/services/plan-service.ts
- await db(PLAN_TABLE).where({ id }).delete();
+ await db.transaction(async (trx) => {
+ await trx(PLAN_SOURCE_WORK_TABLE).where({ plan_item_id: id }).delete();
+ await trx(PLAN_ACTION_TABLE).where({ plan_item_id: id }).delete();
+ await trx(PLAN_ISSUE_TABLE).where({ plan_item_id: id }).delete();
+ await trx(PLAN_TABLE).where({ id }).delete();
+ });
diff --git a/src/app/main/MainHeader.tsx b/src/app/main/MainHeader.tsx
+import { applyAppUpdate, getAppUpdateSnapshot, subscribeAppUpdate, type AppUpdateStatus } from './appUpdate';
...
+ <Button
+ block
+ icon={appUpdateStatus === 'updating' ? <ReloadOutlined spin /> : <DownloadOutlined />}
+ >
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- M src/app/main/appUpdate.ts
- M etc/servers/work-server/src/routes/plan.ts
- M etc/servers/work-server/src/services/plan-service.ts
- M src/features/planBoard/api.ts
- M etc/servers/work-server/src/config/env.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M scripts/run-plan-codex-once.mjs
- M src/sw.ts
- M vite.config.ts
- M src/app/main/MainHeader.tsx
- M src/styles.css
- M etc/servers/work-server/.env.example
- M etc/servers/work-server/docker-compose.yml
- M src/features/planBoard/PlanBoardPage.tsx
- M docker-compose.yml
- A src/app/main/appUpdate.ts
- M src/main.tsx
## 실행 커맨드
```bash
npm run build
npm run plan:codex:once
```
## 변경 파일
- `docs/worklogs/2026-04-04.md`
- `src/app/main/appUpdate.ts`
- `src/app/main/MainHeader.tsx`
- `src/features/planBoard/PlanBoardPage.tsx`
- `src/sw.ts`
- `vite.config.ts`
- `etc/servers/work-server/src/config/env.ts`
- `etc/servers/work-server/src/workers/plan-worker.ts`
- `etc/servers/work-server/src/services/plan-service.ts`
- `scripts/run-plan-codex-once.mjs`

129
docs/worklogs/2026-04-05.md Executable file
View File

@@ -0,0 +1,129 @@
# 2026-04-05 작업일지
## 오늘 작업
- Plan 작업 등록 시 `메인까지 자동등록` 옵션 추가 및 자동 main 반영 흐름 연결
- 대기성 알림을 줄이고 실제 자동화 시작/핵심 단계 중심 알림으로 재정렬
- 기본 진입 화면을 Plans로 조정하고 알림 진입 시 plan 상세 연결 개선
- Plan 성과 차트를 보드 하단에서 별도 사이드 메뉴(차트) 화면으로 분리
- 작업 ID 기본값일 때 알림 제목을 `#채번번호` 기준으로 표기
- Codex Live 채팅 기능 추가 후 별도 Chat 메뉴/사이드바로 분리
- 채팅을 폴링 기반에서 `work-server /ws/chat` WebSocket 기반으로 전환
- Vite 개발 환경에 `/ws/chat` 프록시 추가 및 재연결 안정화
- 채팅 UI를 하단 입력 고정형으로 개편하고 진단/오프라인 폴백 개선
- 진행 알림(1분/3분) 메시지에 현재 처리 단계/요약 반영
- 재처리/재시작 알림 조건 정교화(실제 재실행 예약 시만 노출)
- Web Push 알림 태그 충돌(덮어쓰기) 방지 처리
- Noti 토큰 관리 UI 및 전송 조건(토큰 등록 시) 정리
- Docs 기본 선택을 `worklogs`로 조정
- Plan 목록에서 `정상처리 구분(상/중/하)` 즉시 변경 기능 추가
## 이슈 및 메모
- 채팅 연결 이슈는 프론트 코드보다 프록시/업그레이드 헤더/URL 계산의 영향이 큼
- 진행 알림은 내용 품질이 낮으면 오히려 운영 피로도를 높임
- Web Push는 `tag` 충돌 시 중요한 이벤트가 사라질 수 있어 이벤트 키 설계가 중요
- 재처리 알림은 "실행 예약"과 "단순 응답" 구분이 필요
## 결정 사항
- 채팅은 WebSocket 기본, 실패 시 진단 가능 메시지와 폴백 제공
- 진행 알림은 `1분 후`, 이후 `3분마다` 유지하되 현재 단계 중심 문구 사용
- 알림 제목은 사용자 입력값보다 채번 ID 우선 전략 유지
- 재시작 알림은 실제 재실행이 예약된 경우에만 발송
- Docs 진입 기본 탭은 작업일지 우선
## 상세 작업 내역
- Plan/Chat 네비게이션을 재구성해 상단 메뉴-사이드바-콘텐츠 관계를 명확히 분리
- 워커 알림 체계를 시작/경과/완료/재시작으로 세분화하고 노이즈 이벤트를 제거
- 채팅은 UI 레이아웃, 연결 안정성, 진단 문구, 오프라인 대체 응답까지 함께 다듬어 실사용 대응 범위를 확장
- 푸시 알림은 iOS/Web 공통 이벤트 일관성을 높이고 덮어쓰기 문제를 줄이도록 이벤트 키 전략을 조정
- Plan 보드 운영 기능(정상처리 구분, 상세 이동, 자동화 상태 반영)을 보강해 운영 화면으로서 활용도를 높임
## 스크린샷
![feature-chat-live](../assets/worklogs/2026-04-05/feature-chat-live.png)
![feature-plans-charts](../assets/worklogs/2026-04-05/feature-plans-charts.png)
## 소스
- `etc/servers/work-server/src/services/chat-service.ts`, `src/app/main/MainChatPanel.tsx`: Codex Live 채팅을 WebSocket 기반으로 연결하고, 현재 화면/Plan 상태를 반영한 응답 흐름을 추가했습니다.
- `src/features/planBoard/charts.tsx`, `src/features/planBoard/PlanBoardPage.tsx`: Plan 성과 차트를 별도 메뉴로 분리하고 보드 운영 기능을 확장했습니다.
- `src/features/planBoard/api.ts`: Plan 데이터 조회를 `no-store` 중심으로 바꿔 현재 상태가 바로 반영되게 조정했습니다.
- `etc/servers/work-server/src/services/plan-service.ts`, `etc/servers/work-server/src/workers/plan-worker.ts`: `메인까지 자동등록`, 정상처리 구분, 진행 알림/재시작 조건을 서버 흐름에 연결했습니다.
```diff
diff --git a/etc/servers/work-server/src/services/chat-service.ts b/etc/servers/work-server/src/services/chat-service.ts
+type ChatInboundMessage =
+ | { type: 'context:update'; payload: ChatContext }
+ | { type: 'message:send'; payload: { text: string } };
...
+function isWorklogRequest(input: string) {
+ return mentionsWorklog || (normalized.includes('log') && asksToWrite);
+}
...
+function buildWorklogReply(context: ChatContext | null, snapshot: PlanSnapshot | null) {
+ return [
+ `# ${today} 작업일지`,
+ '## 오늘 작업',
+ ].join('\\n');
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- M etc/servers/work-server/src/services/chat-service.ts
- M etc/servers/work-server/src/routes/plan.ts
- M src/app/main/MainChatPanel.tsx
- M etc/servers/work-server/src/services/plan-notification-service.ts
- M src/app/main/MainHeader.tsx
- A src/app/main/tokenAccess.ts
- M src/features/planBoard/PlanBoardPage.tsx
- M etc/servers/work-server/src/config/env.ts
- M etc/servers/work-server/src/services/plan-service.ts
- M scripts/run-plan-codex-once.mjs
- M src/features/planBoard/types.ts
- M src/styles.css
- M etc/servers/work-server/src/workers/plan-worker.ts
- M src/app/main/MainContent.tsx
- M vite.config.ts
- M etc/servers/work-server/package-lock.json
- M etc/servers/work-server/package.json
- M etc/servers/work-server/src/server.ts
- A etc/servers/work-server/src/services/chat-service.ts
- M src/app/main/MainSidebar.tsx
- M src/app/main/MainView.tsx
- M src/app/main/types.ts
- M src/store/appStore/types/index.ts
- A src/app/main/MainChatPanel.tsx
- A src/features/planBoard/charts.tsx
- M src/features/planBoard/index.ts
- M src/store/appStore/context/AppStoreContext.tsx
- M src/sw.js
- M src/features/planBoard/api.ts
- M docker-compose.yml
- M scripts/serve-app-dist.mjs
- R src/sw.ts -> src/sw.js
- A scripts/serve-app-dist.mjs
- M src/app/main/appUpdate.ts
## 실행 커맨드
```bash
npm run build
npm run capture:feature -- --date 2026-04-05 --target chat-live
npm run capture:feature -- --date 2026-04-05 --target plans-charts
```
## 변경 파일
- `docs/worklogs/2026-04-05.md`
- `src/app/main/MainChatPanel.tsx`
- `src/features/planBoard/PlanBoardPage.tsx`
- `src/features/planBoard/charts.tsx`
- `src/features/planBoard/api.ts`
- `etc/servers/work-server/src/services/plan-service.ts`
- `etc/servers/work-server/src/services/plan-notification-service.ts`
- `etc/servers/work-server/src/services/plan-notification-policy.ts`
- `etc/servers/work-server/src/services/chat-service.ts`
- `etc/servers/work-server/src/workers/plan-worker.ts`

171
docs/worklogs/2026-04-06.md Executable file
View File

@@ -0,0 +1,171 @@
# 2026-04-06 작업일지
## 오늘 작업
- Plan API의 `Load Failed`, 완료건 정상처리, 삭제 요청 실패 같은 기본 동작 오류를 먼저 정리
- Plan 목록/상세에서 작업 ID 표기, 자동화 진행 상태, 로딩/오류 메시지, 복사 버튼 등 사용성 보강
- Chat 화면이 선택된 Plan 문맥을 더 직접적으로 답하도록 서버 응답과 헤더 UX를 계속 보완
- `Codex Live``앱로그 > 에러 로그` 구조를 나누고, 에러 로그 상세 화면을 탭/미리보기 중심으로 확장
- 앱 설정을 드롭다운 구조와 `app config` 저장 방식으로 재구성하고 DB 동기화, 스냅샷 캐시, 즉시 반영 흐름 정리
- Docs 작업일지를 산출물 브라우저와 섹션 탭 기반 미리보기 형태로 키우고, `## 소스`/`## 실행 커맨드`/`## 변경 파일` 포맷을 통일
- 작업일지 스크린샷 자동화, 중복 제거, 날짜별 연결 규칙을 정리하고 3월 30일~4월 6일 문서를 후속 보강
- 토큰 미등록 사용자의 Plan/설정 접근 범위를 readonly와 403 기준으로 제한
## 이슈 및 메모
- Fastify는 빈 바디인데 `Content-Type: application/json`만 있는 `DELETE` 요청을 `400`으로 거절해 프런트 공통 요청 유틸 수정이 필요했음
- 완료건 정상처리, 자동 재처리, 자동화 설정 반영 시점이 서로 어긋나면 사용자 입장에서는 저장 실패처럼 보여 서버/프런트 기준을 같이 맞춰야 했음
- 작업일지 본문만 보여주던 방식으로는 스크린샷, 로그, 코드 diff, 변경 파일을 따라가기 어려워 산출물 브라우저가 필요했음
- 에러 로그/채팅/작업일지 preview가 각각 다른 규칙으로 렌더링돼 fenced code, 커맨드 블록, 파일 목록 표시가 계속 들쭉날쭉했음
- 토큰 미등록 사용자에게는 Plan 상세, 메모, 소스 작업, 설정 일부를 그대로 노출하면 안 돼 화면과 API를 동시에 잠가야 했음
## 결정 사항
- 공통 API 유틸은 HTTP 메서드가 아니라 실제 바디 유무 기준으로 `Content-Type`을 설정
- 자동화 실패 후 메모 저장이나 이슈 조치만으로는 자동 재처리를 다시 켜지 않도록 `retry=false` 기본값을 유지
- 작업일지 preview는 Markdown 본문 외 산출물도 같은 카드 안에서 섹션 단위 탭으로 노출
- 작업일지 `## 소스`는 파일 나열보다 "요약 설명 + 대표 diff" 중심으로 고정
- 앱 설정은 클라이언트 임시 상태가 아니라 DB 저장값과 비교 가능한 `app config` 구조로 관리
- 토큰 미등록 사용자는 Plan 관련 쓰기 작업과 민감 상세 열람을 모두 제한
## 상세 작업 내역
- `src/features/planBoard/api.ts`에서 요청 타임아웃/재시도와 헤더 구성을 다듬어 `Load Failed`, 삭제 실패, 정상처리 갱신 문제를 함께 줄였음
- `src/features/planBoard/PlanBoardPage.tsx`에서 작업 ID 표기, 자동화 상태 설명, 오류 알림 복사, 진행중 우선 정렬, readonly 처리, 작업일지 산출물 브라우저를 순차 반영
- `etc/servers/work-server/src/services/chat-service.ts`는 Chat 화면이어도 `planId`가 있으면 상태/이력/이슈를 같이 답하게 보강했고, 응답 제한 이유도 더 직접적으로 바꿨음
- `src/app/main/MainChatPanel.tsx`와 서버 에러 로그 라우트/서비스를 연결해 `앱로그 > 에러 로그` 화면과 상세 탭/최대화/참조 preview를 정리했음
- `src/app/main/MainHeader.tsx`, `src/app/main/appConfig.ts`, `etc/servers/work-server/src/routes/app-config.ts` 계층에서 앱 설정 드롭다운, DB 저장, 동기화, 캐시, 즉시 반영 흐름을 묶었음
- `src/components/markdownPreview/MarkdownPreviewCard.tsx`, `src/components/markdownPreview/MarkdownPreviewContent.tsx`, `src/components/previewer/*`는 작업일지/산출물 preview를 공통 포맷 레이어로 렌더링하게 바꿨음
- `scripts/capture-menu-screenshot.mjs`, `scripts/capture-feature-screenshot.mjs`, `scripts/worklog-capture-utils.mjs`로 메뉴/기능 캡처와 작업일지 연결을 자동화했고, 불필요한 날짜 참조는 다시 정리했음
- `docs/worklogs/2026-03-30.md`부터 `docs/worklogs/2026-04-06.md`까지 문서 포맷과 스크린샷/소스/커맨드/변경 파일 섹션을 현재 규칙에 맞게 보강했음
## 스크린샷
- 저장소 기준 연결된 스크린샷 없음
## 소스
- Plan 기본 동작 안정화: 완료건 정상처리, 삭제, 재시도, 오류 표시에 걸친 프런트/서버 동작을 정리했습니다.
- `src/features/planBoard/api.ts`, `src/features/planBoard/PlanBoardPage.tsx`, `etc/servers/work-server/src/services/plan-service.ts`: 요청 헤더, 타임아웃, 목록 정렬, readonly, 완료 처리와 자동 재처리 기준을 함께 맞췄습니다.
- Chat/앱로그 확장: 선택된 Plan 문맥 응답과 에러 로그 상세 뷰를 계속 키웠습니다.
- `etc/servers/work-server/src/services/chat-service.ts`, `src/app/main/MainChatPanel.tsx`, `src/app/main/errorLogApi.ts`: Chat 답변 문맥, 앱로그 메뉴 구조, 에러 로그 조회/상세/preview 흐름을 연결했습니다.
- 작업일지/증적 preview 정리: 문서 본문, 코드, 커맨드, 변경 파일을 같은 시각 규칙으로 렌더링하게 통일했습니다.
- `src/components/markdownPreview/MarkdownPreviewContent.tsx`, `src/components/previewer/renderers.tsx`, `docs/templates/worklog-template.md`: 작업일지 포맷과 preview 렌더러를 섹션 중심으로 정리했습니다.
```diff
diff --git a/src/features/planBoard/api.ts b/src/features/planBoard/api.ts
+ const hasBody = init?.body !== undefined && init.body !== null;
...
- headers.set('Content-Type', 'application/json');
+ if (hasBody && !headers.has('Content-Type')) {
+ headers.set('Content-Type', 'application/json');
+ }
```
```diff
diff --git a/src/app/main/MainHeader.tsx b/src/app/main/MainHeader.tsx
- <button>앱 설정</button>
+ <Dropdown menu={{ items: appConfigMenus }}>
+ <button>앱 설정</button>
+ </Dropdown>
```
```diff
diff --git a/src/components/previewer/renderers.tsx b/src/components/previewer/renderers.tsx
+export function renderMarkdownSectionPreview(section, context) {
+ if (section.type === 'code') return <CodeBlockPreview {...context} />;
+ if (section.type === 'command') return <CommandLogPreview {...context} />;
+ return <DefaultSectionPreview {...context} />;
+}
```
## 변경 파일 (전체, 중복 제거, KST 기준)
- M src/app/main/MainChatPanel.tsx
- M src/styles.css
- M etc/servers/work-server/src/services/app-config-service.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M src/app/main/MainHeader.tsx
- M src/components/window/WindowUI.tsx
- M src/app/main/MainSidebar.tsx
- M src/app/main/MainView.tsx
- M etc/servers/work-server/src/services/plan-service.ts
- M src/features/planBoard/PlanBoardPage.tsx
- M src/features/planBoard/api.ts
- M etc/servers/work-server/src/services/plan-policy.test.ts
- M etc/servers/work-server/src/app.ts
- M etc/servers/work-server/src/services/error-log-service.ts
- M src/app/main/errorLogApi.ts
- M src/app/main/MainContent.tsx
- M src/app/main/types.ts
- A etc/servers/work-server/src/routes/error-log.ts
- A etc/servers/work-server/src/services/error-log-service.ts
- M src/App.tsx
- A src/app/main/errorLogApi.ts
- M etc/servers/work-server/src/routes/app-config.ts
- M src/app/main/appConfig.ts
- A etc/servers/work-server/src/routes/app-config.ts
- A etc/servers/work-server/src/services/app-config-service.ts
- M etc/servers/work-server/src/config/env.ts
- M etc/servers/work-server/src/routes/plan.ts
- M etc/servers/work-server/src/services/git-service.ts
- M etc/servers/work-server/src/services/notification-service.ts
- A src/app/main/appConfig.ts
- M etc/servers/work-server/src/services/chat-service.ts
- M src/features/planBoard/charts.tsx
- M src/features/planBoard/types.ts
- A docs/worklogs/2026-04-03.md
- A docs/worklogs/2026-04-04.md
- A docs/worklogs/2026-04-05.md
- A docs/worklogs/2026-04-06.md
- M etc/servers/work-server/src/services/plan-notification-service.ts
- M etc/servers/work-server/package.json
- A etc/servers/work-server/src/services/plan-notification-policy.ts
- A etc/servers/work-server/src/services/plan-policy.test.ts
- A etc/servers/work-server/src/services/plan-retry-policy.ts
- M src/sw.js
## 실행 커맨드
```bash
npx tsc -b
npm run build:app
npm run capture:menu -- --date 2026-04-06
npm run capture:feature -- --date 2026-04-06
npm run plan:codex:once
```
## 변경 파일
- `docs/templates/worklog-template.md`
- `docs/worklogs/2026-03-30.md`
- `docs/worklogs/2026-03-31.md`
- `docs/worklogs/2026-04-01.md`
- `docs/worklogs/2026-04-02.md`
- `docs/worklogs/2026-04-03.md`
- `docs/worklogs/2026-04-04.md`
- `docs/worklogs/2026-04-05.md`
- `docs/worklogs/2026-04-06.md`
- `etc/servers/work-server/src/routes/app-config.ts`
- `etc/servers/work-server/src/routes/error-log.ts`
- `etc/servers/work-server/src/routes/plan.ts`
- `etc/servers/work-server/src/services/app-config-service.ts`
- `etc/servers/work-server/src/services/chat-service.ts`
- `etc/servers/work-server/src/services/error-log-service.ts`
- `etc/servers/work-server/src/services/plan-service.ts`
- `etc/servers/work-server/src/workers/plan-worker.ts`
- `scripts/capture-feature-screenshot.mjs`
- `scripts/capture-menu-screenshot.mjs`
- `scripts/worklog-capture-utils.mjs`
- `src/app/main/MainChatPanel.tsx`
- `src/app/main/MainHeader.tsx`
- `src/app/main/appConfig.ts`
- `src/app/main/errorLogApi.ts`
- `src/components/markdownPreview/MarkdownPreviewCard.tsx`
- `src/components/markdownPreview/MarkdownPreviewContent.tsx`
- `src/components/previewer/PreviewerUI.css`
- `src/components/previewer/PreviewerUI.tsx`
- `src/components/previewer/renderers.tsx`
- `src/features/planBoard/PlanBoardPage.tsx`
- `src/features/planBoard/api.ts`
- `src/styles.css`

159
docs/worklogs/2026-04-07.md Executable file
View File

@@ -0,0 +1,159 @@
# 2026-04-07 작업일지
## 오늘 작업
- 누락된 `2026-04-07` 작업일지 파일을 복구
- Plan 재처리 요청 이력과 자동작업 실패 이슈를 기준으로 오늘 상태를 문서화
- `Docs > 작업일지` 화면 캡처를 생성해 오늘자 스크린샷 자산과 문서 링크를 연결
- `## 소스`, `## 실행 커맨드`, `## 변경/신규 파일` 섹션을 현재 정리 결과 기준으로 갱신
## 이슈 및 해결
- 오늘자 작업일지가 없어 Docs와 Plan 증적에서 `2026-04-07` 기록을 바로 확인할 수 없었음
- 별도 기능 수정 없이 누락된 일자 문서를 추가해 조회 공백을 해소
- 스크린샷 섹션이 비어 있어 오늘 작업 화면을 바로 검증하기 어려웠음
- `vite` 개발 서버와 `capture:feature` 스크립트로 `feature-docs-worklogs.png`를 생성해 문서와 연결
## 결정 사항
- 오늘 조치 범위는 작업일지 정리와 증적 보강으로 한정
- 2026-04-07 증적 대표 화면은 `Docs > 작업일지` 뷰로 통일
- 앱 번들 타입 오류가 남아 있어 이번 캡처는 `build:app` 대신 `vite` 개발 서버 기준으로 수행
## 상세 작업 내역
- `docs/worklogs/2026-04-07.md`를 생성해 오늘자 작업일지 기본 섹션을 채움
- 최근 조치 이력의 재처리 요청과 자동작업 실패 이력을 작업일지 맥락에 맞게 정리
- `docs/assets/worklogs/2026-04-07/feature-docs-worklogs.png`를 생성하고 `## 스크린샷`에 연결
- 실행한 확인/캡처 커맨드와 오늘 기준 변경 파일 증적을 문서 하단 섹션에 정리
## 스크린샷
![feature-docs-worklogs](../assets/worklogs/2026-04-07/feature-docs-worklogs.png)
## 소스
### 파일 1: `docs/worklogs/2026-04-07.md`
- 누락된 작업일지 복구 후 최신 증적까지 포함하도록 오늘자 문서를 다시 정리했습니다.
- 오늘 작업 요약, 스크린샷 링크, 실행 커맨드, 변경/신규 파일 목록을 최신 상태로 반영했습니다.
```diff
diff --git a/docs/worklogs/2026-04-07.md b/docs/worklogs/2026-04-07.md
+## 스크린샷
+![feature-docs-worklogs](../assets/worklogs/2026-04-07/feature-docs-worklogs.png)
+## 실행 커맨드
+npm run dev -- --host 127.0.0.1 --port 4173
```
### 파일 2: `docs/assets/worklogs/2026-04-07/feature-docs-worklogs.png`
- `Docs > 작업일지` 화면을 오늘자 자산 폴더에 저장해 문서 증적에서 바로 미리볼 수 있게 했습니다.
- 2026-04-07 작업일지의 스크린샷 섹션과 1:1로 대응하는 캡처 파일입니다.
```diff
Binary file added: docs/assets/worklogs/2026-04-07/feature-docs-worklogs.png
```
## 변경/신규 파일 (전체, 중복 제거, KST 기준)
- M src/app/main/MainHeader.tsx
- M scripts/run-plan-codex-once.mjs
- M docs/worklogs/2026-04-07.md
- M etc/servers/work-server/src/services/app-config-service.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M src/app/main/appConfig.ts
- A .github/workflows/daily-docs-maintenance.yml
- M README.md
- M docs/README.md
- A docs/assets/worklogs/2026-04-07/.gitkeep
- A docs/assets/worklogs/2026-04-07/feature-docs-worklogs.png
- A docs/daily-maintenance.config.json
- M docs/worklogs/2026-03-30.md
- M docs/worklogs/2026-03-31.md
- M docs/worklogs/2026-04-01.md
- M docs/worklogs/2026-04-02.md
- M docs/worklogs/2026-04-03.md
- M docs/worklogs/2026-04-04.md
- M docs/worklogs/2026-04-05.md
- M docs/worklogs/2026-04-06.md
- M package.json
- A scripts/refresh-daily-docs.mjs
- M src/features/planBoard/PlanBoardPage.tsx
- A etc/servers/work-server/src/services/plan-service.test.ts
- M etc/servers/work-server/src/services/plan-service.ts
- A docs/worklogs/2026-04-07.md
- M docs/templates/worklog-template.md
- M etc/servers/work-server/src/services/chat-service.ts
- M src/components/markdownPreview/MarkdownPreviewCard.tsx
- M scripts/capture-component-screenshot.mjs
- M scripts/capture-feature-screenshot.mjs
- M scripts/capture-fullscreen-toggle-screenshot.mjs
- M scripts/capture-menu-screenshot.mjs
- M scripts/capture-plan-board-mobile-screenshot.mjs
- M scripts/capture-search-command-screenshot.mjs
- M scripts/capture-settings-screenshot.mjs
- M scripts/worklog-capture-utils.mjs
- M src/components/markdownPreview/MarkdownPreviewContent.tsx
- M src/components/previewer/PreviewerUI.css
- M src/components/previewer/PreviewerUI.tsx
- A src/components/previewer/renderers.tsx
- M src/components/previewer/types/index.ts
- M src/components/previewer/types/previewer.ts
- M etc/servers/work-server/src/routes/plan.ts
- M src/features/planBoard/types.ts
- M src/features/planBoard/api.ts
- M src/styles.css
- M src/app/main/MainContent.tsx
- M src/app/main/MainView.tsx
- R docs/assets/worklogs/2026-04-03/feature-chat-live.png -> docs/assets/worklogs/2026-04-05/feature-chat-live.png
- R docs/assets/worklogs/2026-04-03/feature-plans-charts.png -> docs/assets/worklogs/2026-04-05/feature-plans-charts.png
- A docs/assets/worklogs/2026-04-03/feature-chat-live.png
- A docs/assets/worklogs/2026-04-03/feature-plans-charts.png
- A docs/assets/worklogs/2026-04-03/settings-app.png
- A docs/assets/worklogs/2026-04-03/settings-notification.png
- A scripts/capture-settings-screenshot.mjs
- D docs/assets/worklogs/2026-04-06/docs-menu.png
- D docs/assets/worklogs/2026-04-06/feature-apis-components.png
- D docs/assets/worklogs/2026-04-06/feature-docs-worklogs.png
- D docs/assets/worklogs/2026-04-06/feature-plans-board.png
- D docs/assets/worklogs/2026-04-06/feature-plans-charts.png
- D docs/assets/worklogs/2026-04-06/plans-menu.png
- A docs/assets/worklogs/2026-04-06/docs-menu.png
- A docs/assets/worklogs/2026-04-06/feature-apis-components.png
- A docs/assets/worklogs/2026-04-06/feature-docs-worklogs.png
- A docs/assets/worklogs/2026-04-06/feature-plans-board.png
- A docs/assets/worklogs/2026-04-06/feature-plans-charts.png
- A docs/assets/worklogs/2026-04-06/plans-menu.png
- A scripts/capture-feature-screenshot.mjs
- A scripts/capture-menu-screenshot.mjs
- A docs/assets/worklogs/2026-03-30/input.png
- A docs/assets/worklogs/2026-03-30/status-badge.png
- A docs/assets/worklogs/2026-03-31/email-input.png
- A docs/assets/worklogs/2026-03-31/multi-input.png
- A docs/assets/worklogs/2026-04-01/check-combo-input.png
- A docs/assets/worklogs/2026-04-01/dashboard-multi-progress.png
- A docs/assets/worklogs/2026-04-01/dashboard-progress.png
- A docs/assets/worklogs/2026-04-01/popup-input.png
- A docs/assets/worklogs/2026-04-01/select-input.png
- M etc/servers/work-server/src/services/plan-notification-service.ts
- M etc/servers/work-server/src/services/plan-policy.test.ts
- M src/app/main/MainChatPanel.tsx
## 실행 커맨드
```bash
git -c safe.directory=/workspace/auto_codex/repo status --short --branch
sed -n '1,260p' docs/worklogs/2026-04-07.md
sed -n '1,260p' docs/templates/worklog-template.md
npm run dev -- --host 127.0.0.1 --port 4173
CAPTURE_BASE_URL=http://127.0.0.1:4173 npm run capture:feature -- docs-worklogs 2026-04-07
git -c safe.directory=/workspace/auto_codex/repo diff -- docs/worklogs/2026-04-07.md
```
## 자동 정리 상태
- 일일 정리 스크립트 실행일: `2026-04-07`
- 작업일지 파일 점검: `docs/worklogs/2026-04-07.md`
- 스크린샷 폴더 점검: `docs/assets/worklogs/2026-04-07/`
- README / Docs 가이드 자동 요약 갱신 완료

188
docs/worklogs/2026-04-08.md Executable file
View File

@@ -0,0 +1,188 @@
# 2026-04-08 작업일지
## 오늘 작업
- `Play` 상단 메뉴와 `Play > Test` 진입 경로를 추가했습니다.
- 헤더 최상단 메뉴를 `Docs / Plan / Play` 3개 기준으로 정리했습니다.
- `Play > layout` 화면을 만들고 상하/좌우 분할, 재귀 분할, 크기 단위, 최소 크기, 리사이즈 여부를 조절할 수 있게 확장했습니다.
- Plan 상세에서 긴 `issue`/`error` 본문을 기본 접힘으로 바꾸고 아이콘으로만 펼치도록 정리했습니다.
- `release` 환경에서 `main` 미반영 항목만 모아 보여주는 모달을 추가했습니다.
- 자동화 작업 브랜치 기준을 `release`가 아니라 `main`으로 바로잡고, worklog 자동화는 `hotfix/*` 브랜치를 쓰도록 보강했습니다.
- 누락됐던 `docs/worklogs/2026-04-08.md` 초안이 템플릿 상태로만 남아 있던 문제를 실제 작업 내역 기준으로 다시 정리했습니다.
- 누락됐던 `Play > layout` 스크린샷을 `docs/assets/worklogs/2026-04-08/feature-play-layout.png`로 다시 연결했습니다.
## 이슈 및 해결
- 오늘자 작업일지가 템플릿 문구만 남아 있어 `play`, 자동화 브랜치 정책, release 대기 모달 작업 이력을 문서에서 확인할 수 없었습니다.
- Git 기록 기준으로 2026-04-08 UTC/KST 커밋을 다시 확인해 실제 작업 항목과 변경 파일을 복원했습니다.
- 사용자가 언급한 `history` 기능은 Git 이력상 2026-04-07 13:44:06 UTC 커밋(`plan(history)`)에 생성됐고, 2026-04-08에는 `play`/plan 상세/자동화 보강 작업이 집중됐습니다.
- 스크린샷 섹션이 비어 있고 소스 뷰어의 diff 라벨도 일관되지 않아, 증적 확인 흐름이 한 번에 읽히지 않았습니다.
## 결정 사항
- 2026-04-08 문서는 placeholder 템플릿이 아니라 실제 커밋 증적 기준으로 유지합니다.
- `Play``Test``layout` 두 축으로 관리하고, 레이아웃 실험은 전용 playground에서 확장합니다.
- Plan 소스 뷰어 탭 명칭은 사용자가 바로 이해할 수 있게 `작업란 / 전체소스 / diff`로 정리합니다.
- 자동화 작업 브랜치 base는 `main`, 자동 worklog 정리는 `hotfix/*` 흐름을 따릅니다.
- 2026-04-08 대표 스크린샷은 `Play > layout` 화면으로 고정합니다.
## 상세 작업 내역
- `src/app/main/MainHeader.tsx`, `src/app/main/MainView.tsx`, `src/app/main/MainContent.tsx`, `src/app/main/types.ts`, `src/views/play/TestView.tsx`에서 `Play` 메뉴와 `Play > Test` 화면을 연결했습니다.
- `src/views/play/LayoutPlaygroundView.tsx``src/styles.css`에서 레이아웃 playground를 추가하고, 이후 같은 날 재귀 분할과 초기 빈 상태 진입까지 두 차례 확장했습니다.
- `src/features/planBoard/PlanBoardPage.tsx`에서 긴 이슈 본문 접힘 처리와 소스 작업 미리보기 경험을 보강했습니다.
- `src/App.tsx`, `src/app/main/ReleasePendingMainModal.tsx`, `src/styles.css`에서 `release` 접속 시 `main` 미반영 항목 안내 모달을 추가했습니다.
- `scripts/run-plan-codex-once.mjs`, `etc/servers/work-server/src/services/git-service.ts`, `etc/servers/work-server/src/workers/plan-worker.ts`, `etc/servers/work-server/src/services/plan-service.ts`에서 자동화 브랜치/정리 정책을 수정했습니다.
- 오늘 문서 누락 보완으로 `docs/worklogs/2026-04-08.md`를 실제 기록 기준으로 재작성했습니다.
- `scripts/capture-feature-screenshot.mjs``play-layout` 프리셋을 추가해 오늘자 대표 화면을 다시 캡처할 수 있게 했습니다.
- `src/components/previewer/CodexDiffPreviewer.tsx`에서 소스 뷰어 탭 라벨을 `전체소스 / diff`로 맞췄습니다.
## 스크린샷
![feature-play-layout](../assets/worklogs/2026-04-08/feature-play-layout.png)
## 소스
### 파일 1: `src/app/main/MainView.tsx`
- 상단 메뉴 체계를 `Docs / Plan / Play` 중심으로 재정리하고 `Play > Test`, `Play > layout` 탐색을 연결했습니다.
- 검색 커맨드와 초기 URL 파라미터도 `playSection` 기준으로 함께 맞췄습니다.
```diff
const PLAY_BASE_OPEN_KEYS = ['play-group', 'play-test-group', 'play-layout-group'] as const;
...
+ id: 'page:play:test',
+ keywords: ['play', 'test', '테스트', 'TEST메뉴'],
+ },
+ {
+ id: 'page:play:layout',
+ keywords: ['play', 'layout', 'flex', 'split', '분할', '레이아웃', 'preview'],
```
### 파일 2: `src/views/play/LayoutPlaygroundView.tsx`
- `play > layout` 화면을 추가하고, 같은 날 재귀 분할과 빈 상태 시작 플로우까지 확장했습니다.
- 상하/좌우 시작, `px/%` 단위, 최소 크기, 리사이즈 허용 여부를 화면에서 바로 조정할 수 있습니다.
```diff
diff --git a/src/views/play/LayoutPlaygroundView.tsx b/src/views/play/LayoutPlaygroundView.tsx
+const DEFAULT_LAYOUT_DIRECTION = 'row';
+const EMPTY_LAYOUT: LayoutNode | null = null;
...
+<Button onClick={() => handleCreateRoot('row')}>좌우로 시작</Button>
+<Button onClick={() => handleCreateRoot('column')}>상하로 시작</Button>
...
+<Button onClick={() => handleSplitNode(selectedNodeId, 'row')}>좌우 추가</Button>
+<Button onClick={() => handleSplitNode(selectedNodeId, 'column')}>상하 추가</Button>
```
### 파일 3: `src/app/main/ReleasePendingMainModal.tsx`
- `release` 환경에서 아직 `main`에 반영되지 않은 작업만 모아 보여주는 전용 모달을 추가했습니다.
- `릴리즈완료`, `main반영대기/중/실패` 상태를 기준으로 요약과 현황을 표시합니다.
```diff
diff --git a/src/app/main/ReleasePendingMainModal.tsx b/src/app/main/ReleasePendingMainModal.tsx
+export function ReleasePendingMainModal() {
+ // release 서버에서 main 미반영 항목만 조회
+}
```
### 파일 4: `scripts/run-plan-codex-once.mjs`
- `.auto_codex` 작업 브랜치의 base를 `release`가 아니라 `main`으로 맞췄습니다.
- 같은 날 자동 worklog 쪽은 `hotfix/*` 흐름을 따르도록 서버 로직도 함께 보강됐습니다.
```diff
diff --git a/scripts/run-plan-codex-once.mjs b/scripts/run-plan-codex-once.mjs
-const baseBranch = 'release';
+const baseBranch = 'main';
```
### 파일 5: `src/features/planBoard/PlanBoardPage.tsx`
-`issue`/`error` 본문은 기본 접힘으로 바꾸고, 이번 보완에서 소스 뷰어 탭 명칭도 `작업란 / 전체소스 / diff`로 정리했습니다.
```diff
diff --git a/src/features/planBoard/PlanBoardPage.tsx b/src/features/planBoard/PlanBoardPage.tsx
- label: '요약',
+ label: '작업란',
- label: '전체 소스',
+ label: '전체소스',
- label: 'Diff',
+ label: 'diff',
```
### 파일 6: `src/components/previewer/CodexDiffPreviewer.tsx`
- worklog 소스 탭과 Plan 소스 뷰어의 모드 라벨을 `전체소스 / diff`로 통일했습니다.
- 기존 `Raw Diff` 표기를 `diff`로 바꿔 사용자가 탭 목적을 바로 읽을 수 있게 맞췄습니다.
```diff
diff --git a/src/components/previewer/CodexDiffPreviewer.tsx b/src/components/previewer/CodexDiffPreviewer.tsx
- { label: '전체 소스', value: 'source' },
- { label: 'Raw Diff', value: 'diff' },
+ { label: '전체소스', value: 'source' },
+ { label: 'diff', value: 'diff' },
```
### 파일 7: `scripts/capture-feature-screenshot.mjs`
- `Play > layout` 화면도 기존 worklog 캡처 흐름으로 다시 만들 수 있게 프리셋을 추가했습니다.
- 오늘자 누락 스크린샷은 이 프리셋으로 `feature-play-layout.png`를 생성해 연결했습니다.
```diff
diff --git a/scripts/capture-feature-screenshot.mjs b/scripts/capture-feature-screenshot.mjs
+ 'play-layout': {
+ topMenu: 'play',
+ screenshotFileName: 'feature-play-layout.png',
+ targetSelector: '.app-main-card',
+ query: { playSection: 'layout' },
+ },
```
### 파일 8: `docs/assets/worklogs/2026-04-08/feature-play-layout.png`
- `Play > layout` 대표 화면을 다시 캡처해 오늘자 스크린샷 섹션과 바로 연결했습니다.
- 2026-04-08 작업일지에서 비어 있던 시각 증적을 채우는 실제 산출물 파일입니다.
```diff
Binary file added: docs/assets/worklogs/2026-04-08/feature-play-layout.png
```
## 실행 커맨드
```bash
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-08 00:00' --until='2026-04-08 23:59:59' --stat --oneline --decorate
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-08 00:00' --until='2026-04-08 23:59:59' --name-status --format='commit %H%nAuthor: %an%nDate: %ad%nSubject: %s' --date=iso
git -c safe.directory=/workspace/auto_codex/repo log --follow --name-status -- src/features/history/HistoryPage.tsx
npm run dev -- --host 127.0.0.1 --port 4173
CAPTURE_BASE_URL=http://127.0.0.1:4173 node scripts/capture-feature-screenshot.mjs play-layout 2026-04-08
```
## 변경/신규 파일 (전체, 중복 제거, Git 기록 기준)
- M src/app/main/MainHeader.tsx
- M src/app/main/MainContent.tsx
- M src/app/main/MainSidebar.tsx
- M src/app/main/MainView.tsx
- M src/app/main/types.ts
- M src/app/main/clientIdentity.ts
- M src/App.tsx
- A src/app/main/ReleasePendingMainModal.tsx
- A src/views/play/TestView.tsx
- A src/views/play/LayoutPlaygroundView.tsx
- M src/features/planBoard/PlanBoardPage.tsx
- M src/styles.css
- M src/store/appStore/types/index.ts
- M scripts/run-plan-codex-once.mjs
- M etc/servers/work-server/src/services/git-service.ts
- A etc/servers/work-server/src/services/git-service.test.ts
- M etc/servers/work-server/src/services/plan-service.ts
- M etc/servers/work-server/src/services/plan-service.test.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M AGENTS.md
- A docs/worklogs/2026-04-08.md
- M src/components/previewer/CodexDiffPreviewer.tsx
- M scripts/capture-feature-screenshot.mjs
- A docs/assets/worklogs/2026-04-08/feature-play-layout.png

446
docs/worklogs/2026-04-09.md Executable file
View File

@@ -0,0 +1,446 @@
# 2026-04-09 작업일지
## 오늘 작업
- `Play > Layout`을 컴포넌트/위젯 선택형 레이아웃 편집기로 확장하고, 저장 레이아웃 목록/상세 흐름을 여러 차례 다듬었습니다.
- 레이아웃 저장소를 브라우저 `IndexedDB`에서 `work-db` API 기반 서버 저장 방식으로 전환했습니다.
- `Window UI` 검색 중복 열림, 모바일 리사이즈 hit area, 더블탭/더블클릭 확장, 스크롤 처리 문제를 순차적으로 정리했습니다.
- `EmbeddedMapUI`, `GPS Sample Widget`, 서버 경유 푸시 알림 흐름을 추가해 GPS 거점/반경/알림 시나리오를 앱 안에서 직접 다룰 수 있게 만들었습니다.
- `Text Memo Widget`을 신규 추가하고, 이어서 iOS 메모 느낌의 시트형 UI로 재구성했습니다.
- `Plan Board`에 로컬 페이지네이션, `release 반영 상태`/`자동화 실패` 바로가기, `main` 일괄 반영 흐름을 추가했습니다.
- 앱 초기 1.9초 로딩 오버레이, 제스처 단축키 설정, `release 반영 상태` 메뉴/모달, Git flow 안전 규칙 문서 보강까지 반영했습니다.
- 오늘자 작업 증적을 위해 전체 화면 1장과 위젯 단위 부분 캡처 1장을 `docs/assets/worklogs/2026-04-09/`에 저장하고 연결했습니다.
## 이슈 및 해결
- `Play > Layout` 저장본을 클라이언트 로컬 저장소만으로 관리하던 상태라 작업 환경이 바뀌면 레이아웃이 끊겼습니다.
- `src/views/play/layoutStorage.ts``work-db` API 호출 구조로 전환하고, 인증 토큰/클라이언트 ID 헤더를 함께 보내도록 바꿔 저장본을 서버 기준으로 유지했습니다.
- `Window UI` 검색/열기 흐름에서 같은 항목이 짧은 시간 안에 2개씩 열리거나, 전체 부모 기준으로 과도하게 확장되는 재발 이슈가 있었습니다.
- `SearchLayerContext`, `SearchCommandModal`, `WindowUI`를 함께 수정해 선택 잠금, 500ms 중복 차단, 보이는 viewport 기준 확장 로직으로 안정화했습니다.
- 지도 반경 오버레이는 초기에 좌표 기반 렌더가 아니라 CSS 고정 오버레이 성격이 강해 지도를 움직일 때 의미가 깨졌습니다.
- `EmbeddedMapUI`를 실제 좌표/경계 계산 기반 OSM iframe 오버레이로 바꾸고, GPS 위젯에서 선택 거점과 현재 위치를 함께 표시하도록 맞췄습니다.
- 자동화 쪽은 `main` 반영이 단건 기준으로만 움직여 `release`에 쌓인 항목을 한 번에 넘기기 어려웠습니다.
- `plan-service.ts`, `plan-worker.ts`에서 `release_target` 기준 `main` 일괄 반영/재시도 흐름으로 보강했습니다.
## 결정 사항
- `Play > Layout`의 대표 명칭은 `Layout Editor`로 통일하고, 저장된 레이아웃은 별도 메뉴 엔트리로 노출합니다.
- 레이아웃/플레이그라운드 저장은 브라우저 로컬 상태보다 `work-db` 서버 저장을 우선 기준으로 사용합니다.
- `Plan` 상단/설정 영역의 `release 반영 상태`, `자동화 실패`는 별도 빠른 진입점으로 유지합니다.
- `hotfix``feature` 후속 작업은 항상 새 브랜치에서 다시 시작한다는 Git 안전 규칙을 문서에 명시합니다.
- 오늘 대표 스크린샷은 `Play > Layout`, 부분 스크린샷은 실제 위젯 단위로 확인 가능한 `GPS Sample Widget` 카드로 남깁니다.
## 상세 작업 내역
- `src/views/play/LayoutPlaygroundView.tsx`에서 섹션별 `showHideAction`, 컴포넌트/위젯 검색 모달, preview-only 렌더, 저장 레이아웃 목록/상세, 전체보기/빈 상태 플로우를 누적 확장했습니다.
- `src/views/play/layoutStorage.ts`에서 `play_layouts` 테이블을 다루는 서버 API 저장소로 전환하고, fallback URL과 timeout 처리까지 추가했습니다.
- `src/components/search/SearchCommandModal.tsx`, `src/layer/search/context/SearchLayerContext.tsx`, `src/components/window/WindowUI.tsx`, `src/components/window/WindowUI.css`에서 검색 선택 중복, 모바일 hit area, 보이는 화면 기준 확장, 닫기 액션을 정리했습니다.
- `src/components/embeddedMap/*`, `src/layer/gps/*`, `src/widgets/gps-sample-card/*`, `etc/servers/work-server/src/routes/notification.ts`, `src/app/main/notificationApi.ts`에서 지도 내장 UI, GPS 레이어, 웹푸시 알림 경로를 구축했습니다.
- `src/widgets/text-memo-widget/*`, `src/widgets/core/WidgetShell.tsx`, `src/widgets/registry.ts`, `src/index.ts`에서 메모 위젯을 추가하고 위젯 카드 래퍼 옵션과 샘플 연결을 마쳤습니다.
- `src/features/planBoard/PlanBoardPage.tsx`, `src/features/planBoard/quickFilters.ts`, `src/app/main/MainHeader.tsx`, `src/app/main/MainView.tsx`, `src/app/main/MainContent.tsx`에서 plan 빠른 필터/로컬 패이징/윈도우 열기/메뉴 정리를 반영했습니다.
- `etc/servers/work-server/src/services/plan-service.ts`, `etc/servers/work-server/src/workers/plan-worker.ts`에서 `main` 일괄 반영 요청과 배치 처리 로직을 추가했습니다.
- `src/App.tsx`, `src/styles.css`에 앱 시작 로딩 오버레이와 로그 스트림 애니메이션을 넣었습니다.
- `AGENTS.md``release` 반영 직후 같은 브랜치에서 작업을 이어가지 않도록 하는 안전 체크리스트와 꼬임 위험 신호를 추가했습니다.
- 오늘 업무일지 문서와 스크린샷 자산은 현재 날짜 기준 Git 이력과 실제 캡처 결과로 다시 정리했습니다.
## 스크린샷
![feature-play-layout](../assets/worklogs/2026-04-09/feature-play-layout.png)
- `Play > Layout` 전체 화면입니다. 저장 레이아웃/편집/분할/섹션 선택 흐름을 한 번에 확인할 수 있습니다.
![widget-gps-sample](../assets/worklogs/2026-04-09/widget-gps-sample.png)
- `GPS Sample Widget` 부분 캡처입니다. 오늘 추가된 지도/거점/반경 위젯 단위 증적입니다.
## 소스
### 파일 1: `src/views/play/LayoutPlaygroundView.tsx`
- `Layout Editor`의 핵심 화면으로, 컴포넌트/위젯 바인딩과 저장 레이아웃 상세 플로우를 대부분 이 파일에서 확장했습니다.
```diff
+import { SearchCommandModal, type SearchKeywordOption } from '../../components/search';
+import { componentSampleEntries, widgetSampleEntries } from '../../app/manifests/samples.manifest';
+type LayoutComponentBinding = {
+ optionId: string;
+ label: string;
+ description?: string;
+ keywords: string[];
+};
+type LayoutPlaygroundViewProps = {
+ savedLayoutViewId?: string | null;
+ showSavedLayoutsOnly?: boolean;
+ onSavedLayoutsChange?: (layouts: SavedLayoutRecord[]) => void;
+};
- title: string;
- description: string;
+ showHideAction: boolean;
+ componentBinding: LayoutComponentBinding | null;
```
### 파일 2: `src/views/play/layoutStorage.ts`
- 레이아웃 저장소를 `IndexedDB`에서 `work-db` API 기반 서버 저장 방식으로 전환했습니다.
```diff
+import { appendClientIdHeader } from '../../app/main/clientIdentity';
+import { getRegisteredAccessToken } from '../../app/main/tokenAccess';
-const DATABASE_NAME = 'play-layout-db';
-const STORE_NAME = 'saved-layouts';
-const DATABASE_VERSION = 1;
+const WORK_SERVER_TIMEOUT_MS = 8000;
+const PLAY_LAYOUTS_TABLE = 'play_layouts';
+class LayoutStorageError extends Error {
+ status: number;
+}
```
### 파일 3: `src/components/search/SearchCommandModal.tsx`
- 검색 모달 제목/설명 커스터마이징, 모바일 축약 문구, 선택 잠금 로직을 추가했습니다.
```diff
+ title?: string;
+ description?: string;
+ placeholder?: string;
+ submitHint?: string;
+ const selectionLockRef = useRef(false);
+ const [isMobileViewport, setIsMobileViewport] = useState(() => {
+ return window.innerWidth <= 768;
+ });
+ const submitOption = (option: SearchKeywordOption | undefined) => {
+ if (!option || selectionLockRef.current) {
+ return;
+ }
+ selectionLockRef.current = true;
+ onSelectOption(option);
+ onClose();
+ };
```
### 파일 4: `src/components/window/WindowUI.tsx`
- `Window UI`는 닫기 액션, 모바일 리사이즈 개선, 보이는 viewport 기준 더블탭/더블클릭 확장 로직을 받았습니다.
```diff
-import { FullscreenExitOutlined, FullscreenOutlined, MinusOutlined } from '@ant-design/icons';
+import { CloseOutlined, FullscreenExitOutlined, FullscreenOutlined, MinusOutlined } from '@ant-design/icons';
+type ResizeTapRecord = {
+ direction: ResizeDirection;
+ at: number;
+ x: number;
+ y: number;
+};
+const DOUBLE_TAP_DELAY = 320;
+const DOUBLE_TAP_MOVE_TOLERANCE = 18;
+const RESIZE_MOVE_THRESHOLD = 4;
+function getVisibleParentBounds(element: HTMLDivElement | null) {
+ const viewportWidth = window.visualViewport?.width ?? window.innerWidth;
+}
```
### 파일 5: `src/components/embeddedMap/EmbeddedMapUI.tsx`
- 새 내장 지도 UI를 추가하고, 좌표/반경/보조 마커를 OSM iframe 위에 오버레이로 표시하도록 구성했습니다.
```diff
+export type EmbeddedMapUIProps = {
+ latitude: number;
+ longitude: number;
+ radiusMeters?: number;
+ lockViewport?: boolean;
+ secondaryMarker?: {
+ latitude: number;
+ longitude: number;
+ label?: string;
+ } | null;
+ overlay?: ReactNode;
+};
+function createEmbedUrl(bounds: Bounds, latitude: number, longitude: number) {
+ return `https://www.openstreetmap.org/export/embed.html?...`;
+}
```
### 파일 6: `src/widgets/gps-sample-card/GpsSampleWidget.tsx`
- GPS on/off, 현재 좌표, 거점 저장, 반경, In/Out 푸시 알림, 지도 표시를 한 위젯으로 묶었습니다.
```diff
+import {
+ AimOutlined,
+ BellOutlined,
+ EnvironmentOutlined,
+ DeleteOutlined,
+ RadarChartOutlined,
+} from '@ant-design/icons';
+import { EmbeddedMapUI } from '../../components/embeddedMap';
+import { useGpsLayer } from '../../layer';
+const selectedAnchor = anchors.find((anchor) => anchor.id === selectedAnchorId) ?? null;
+const distanceToSelectedAnchor =
+ selectedAnchor && currentPosition
+ ? calculateDistanceMeters(...)
+ : null;
```
### 파일 7: `src/widgets/text-memo-widget/TextMemoWidget.tsx`
- `Text Memo Widget`을 신규 추가하고 최근 6개 메모 저장/불러오기/삭제와 시트형 편집 UI를 구성했습니다.
```diff
+const STORAGE_KEY = 'ai-code-app:text-memo-widget';
+const MAX_SAVED_NOTES = 6;
+type SavedNote = {
+ id: string;
+ body: string;
+ createdAt: string;
+};
+export const TextMemoWidget = forwardRef<WidgetHandle, TextMemoWidgetProps>(function TextMemoWidget(
+ { cardWrapper },
+ ref,
+) {
```
### 파일 8: `src/features/planBoard/PlanBoardPage.tsx`
- plan 목록 로컬 패이징, 빠른 필터, 최대화 뒤 아이콘-only 돌아가기, 긴 본문 접힘 흐름을 오늘 기준으로 누적 반영했습니다.
```diff
+ const [currentPage, setCurrentPage] = useState(1);
+ const pageSize = 10;
+ const pagedItems = filteredItems.slice((currentPage - 1) * pageSize, currentPage * pageSize);
+ useEffect(() => {
+ setCurrentPage(1);
+ }, [filter, searchQuery]);
- label: '요약',
+ label: '작업란',
```
### 파일 9: `src/app/main/MainHeader.tsx`
- 설정 메뉴에 `release 상태 작업`, `자동화 실패` 바로가기를 추가하고 현재 건수를 같이 보여주도록 확장했습니다.
```diff
+import { isAutomationFailedItem, isReleasePendingMainItem } from '../../features/planBoard/quickFilters';
+const [planShortcutCounts, setPlanShortcutCounts] = useState({
+ releasePendingMain: 0,
+ automationFailed: 0,
+});
+void fetchPlanItems('all')
+ .then((items) => {
+ setPlanShortcutCounts({
+ releasePendingMain: items.filter(isReleasePendingMainItem).length,
+ automationFailed: items.filter(isAutomationFailedItem).length,
+ });
+ })
```
### 파일 10: `src/app/main/MainView.tsx`
- 상단/사이드 메뉴 구조를 `release 반영 상태`, `APIs / Widgets`, `Layout Editor`, 저장 레이아웃 메뉴까지 포괄하도록 재정리했습니다.
```diff
- 'in-progress': '작업중',
+ 'in-progress': '자동화 대기 / 작업 중',
- error: '오류',
+ error: '오류 (작업 완료 전)',
+ release: 'release 반영 상태',
+const PLAY_LAYOUT_RECORD_PREFIX = 'layout-record:' as const;
+function resolvePlanQuickFilterMenu(filter: PlanQuickFilter): Extract<PlanSidebarKey, 'release' | 'error'> {
+ return filter === 'release-pending-main' ? 'release' : 'error';
+}
```
### 파일 11: `src/app/main/MainContent.tsx`
- 검색 결과를 `Window UI` 창으로 여는 흐름, 샘플 숨김 목록, `Plan`/저장 레이아웃 렌더 분기를 추가했습니다.
```diff
+import { useMemo, useState, type ReactNode } from 'react';
+import { WindowUI, type WindowFrame } from '../../components/window';
+import { useSearchLayer } from '../../layer';
+const HIDDEN_COMPONENT_IDS = ['search-command-modal', 'window-ui'];
+const { windowSelections, clearWindowSelection } = useSearchLayer();
+const [windowFrames, setWindowFrames] = useState<Record<string, WindowFrame>>({});
+const [windowZIndexes, setWindowZIndexes] = useState<Record<string, number>>({});
```
### 파일 12: `src/App.tsx`
- 앱 시작 직후 1.9초 동안 보여주는 풀스크린 로딩 오버레이와 로그 스트림 UI를 추가했습니다.
```diff
+const INITIAL_LOADING_LOGS = [
+ 'BOOT SEQUENCE :: app shell warmup',
+ 'CONFIG SYNC :: workspace profile applied',
+ 'SESSION LINK :: reconnecting realtime channel',
+ 'MODULE CHECK :: dashboard widgets online',
+ 'READY SIGNAL :: rendering main viewport',
+];
+const [showInitialLoading, setShowInitialLoading] = useState(true);
+window.setTimeout(() => {
+ setShowInitialLoading(false);
+}, 1900);
```
### 파일 13: `etc/servers/work-server/src/services/plan-service.ts`
- `main` 반영 요청/클레임/완료 처리 모두를 `release_target` 기준 배치 처리로 바꿨습니다.
```diff
- isMainRetry ? 'main 반영 재시도를 요청했습니다.' : 'release 반영 재시도를 요청했습니다.',
+ isMainRetry ? 'main 일괄 반영 재시도를 요청했습니다.' : 'release 반영 재시도를 요청했습니다.',
+ const pendingRows = await db(PLAN_TABLE)
+ .select('id')
+ .where({ status: '릴리즈완료', release_target: releaseTarget });
- .where({ id })
+ .whereIn('id', targetIds.length > 0 ? targetIds : [id])
+ message: `${releaseTarget} 브랜치 기준으로 ${Math.max(targetIds.length, 1)}건 main 일괄 반영을 요청했습니다.`,
```
### 파일 14: `AGENTS.md`
- Git 안전 규칙을 보강해 `release` 반영 뒤 같은 브랜치에서 추가 작업을 이어가는 패턴을 금지하고 점검 절차를 명시했습니다.
```diff
+* `release` 반영 상태에서 현재 브랜치 그대로 추가 작업 진행
+* `release` 반영이 끝난 뒤 추가 수정 요청이 들어오면, 반드시 새 브랜치에서 다시 시작한다
+## 안전 점검 체크리스트
+1. 지금 반영할 변경이 `release`에 먼저 들어갔는지 확인
+2. 추가 요청이라면 새 `feature/*` 또는 `hotfix/*` 브랜치에서 작업 중인지 확인
+## 꼬임 위험 신호
+* `release` 반영 직후 추가 요청을 현재 브랜치에서 바로 이어서 처리하는 경우
```
### 파일 15: `docs/assets/worklogs/2026-04-09/feature-play-layout.png`
- 오늘 대표 전체 화면 스크린샷입니다.
```diff
Binary file added: docs/assets/worklogs/2026-04-09/feature-play-layout.png
```
### 파일 16: `docs/assets/worklogs/2026-04-09/widget-gps-sample.png`
- 오늘 위젯 단위 부분 스크린샷입니다.
```diff
Binary file added: docs/assets/worklogs/2026-04-09/widget-gps-sample.png
```
## 실행 커맨드
```bash
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-09 00:00' --until='2026-04-09 23:59:59' --stat --oneline --decorate
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-09 00:00' --until='2026-04-09 23:59:59' --name-status --format='commit %H%nAuthor: %an%nDate: %ad%nSubject: %s%n' --date=iso
git -c safe.directory=/workspace/auto_codex/repo diff --stat b70ce0c2549542703967b73aedf8d249050a40f7..HEAD
npm run dev -- --host 127.0.0.1 --port 4173
CAPTURE_BASE_URL=http://127.0.0.1:4173 node scripts/capture-feature-screenshot.mjs play-layout 2026-04-09
node --input-type=module # GPS Sample Widget 부분 캡처
npm run build:app
```
## 변경/신규 파일 (전체, 중복 제거, Git 기록 기준 + 오늘 업무일지 산출물)
- A src/components/dashboard/multiProgress/samples/BaseSample.tsx
- A src/components/dashboard/progress/samples/BaseSample.tsx
- A src/components/embeddedMap/EmbeddedMapUI.css
- A src/components/embeddedMap/EmbeddedMapUI.tsx
- A src/components/embeddedMap/index.ts
- A src/components/embeddedMap/samples/BaseSample.tsx
- A src/components/embeddedMap/samples/Sample.tsx
- A src/components/inputs/checkCombo/samples/BaseSample.tsx
- A src/components/inputs/composite/multiInput/samples/BaseSample.tsx
- A src/components/inputs/popup/samples/BaseSample.tsx
- A src/components/inputs/primitives/input/samples/BaseSample.tsx
- A src/components/inputs/select/samples/BaseSample.tsx
- A src/components/inputs/specialized/buttonEditableInput/samples/BaseSample.tsx
- A src/components/inputs/specialized/emailInput/samples/BaseSample.tsx
- A src/components/markdownPreview/samples/MarkdownPreviewCardBaseSample.tsx
- A src/components/markdownPreview/samples/MarkdownPreviewContentBaseSample.tsx
- A src/components/markdownPreview/samples/MarkdownPreviewListBaseSample.tsx
- A src/components/navigation/samples/FolderTreeNavBaseSample.tsx
- A src/components/navigation/samples/SectionMenuLayoutBaseSample.tsx
- A src/components/previewer/samples/BaseSample.tsx
- A src/components/previewer/samples/CodexDiffBaseSample.tsx
- A src/components/search/samples/BaseSample.tsx
- A src/components/status-badge/samples/BaseSample.tsx
- A src/components/window/samples/BaseSample.tsx
- A src/features/planBoard/quickFilters.ts
- A src/layer/gps/context/GpsLayerContext.tsx
- A src/layer/gps/hooks/useGpsLayer.ts
- A src/layer/gps/index.ts
- A src/layer/gps/types/index.ts
- A src/widgets/gps-sample-card/GpsSampleWidget.css
- A src/widgets/gps-sample-card/GpsSampleWidget.tsx
- A src/widgets/gps-sample-card/index.ts
- A src/widgets/gps-sample-card/samples/Sample.tsx
- A src/widgets/text-memo-widget/TextMemoWidget.css
- A src/widgets/text-memo-widget/TextMemoWidget.tsx
- A src/widgets/text-memo-widget/index.ts
- A src/widgets/text-memo-widget/samples/Sample.tsx
- M AGENTS.md
- M docs/components/window-ui.md
- M etc/servers/work-server/src/routes/notification.ts
- M etc/servers/work-server/src/services/plan-service.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M package-lock.json
- M package.json
- M src/App.tsx
- M src/app/main/MainContent.tsx
- M src/app/main/MainHeader.tsx
- M src/app/main/MainView.tsx
- M src/app/main/ReleasePendingMainModal.tsx
- M src/app/main/appConfig.ts
- M src/app/main/notificationApi.ts
- M src/app/main/types.ts
- M src/components/dashboard/multiProgress/samples/Sample.tsx
- M src/components/dashboard/progress/samples/Sample.tsx
- M src/components/embeddedMap/EmbeddedMapUI.css
- M src/components/embeddedMap/EmbeddedMapUI.tsx
- M src/components/embeddedMap/samples/Sample.tsx
- M src/components/inputs/checkCombo/samples/Sample.tsx
- M src/components/inputs/composite/multiInput/samples/Sample.tsx
- M src/components/inputs/popup/samples/Sample.tsx
- M src/components/inputs/primitives/input/samples/Sample.tsx
- M src/components/inputs/select/samples/Sample.tsx
- M src/components/inputs/specialized/buttonEditableInput/samples/Sample.tsx
- M src/components/inputs/specialized/emailInput/samples/Sample.tsx
- M src/components/previewer/samples/CodexDiffSample.tsx
- M src/components/previewer/samples/Sample.tsx
- M src/components/search/SearchCommandModal.tsx
- M src/components/status-badge/samples/Sample.tsx
- M src/components/window/WindowUI.css
- M src/components/window/WindowUI.tsx
- M src/components/window/samples/Sample.tsx
- M src/components/window/types/window.ts
- M src/features/layout/component-sample-gallery/ComponentSamplesLayout.tsx
- M src/features/planBoard/PlanBoardPage.tsx
- M src/features/planBoard/index.ts
- M src/index.ts
- M src/layer/gps/context/GpsLayerContext.tsx
- M src/layer/index.ts
- M src/layer/search/context/SearchLayerContext.tsx
- M src/layer/search/types/index.ts
- M src/main.tsx
- M src/styles.css
- M src/views/play/LayoutPlaygroundView.tsx
- M src/views/play/layoutStorage.ts
- M src/widgets/api-sample-card/ApiSampleCardWidget.tsx
- M src/widgets/api-sample-card/samples/Sample.tsx
- M src/widgets/core/WidgetShell.tsx
- M src/widgets/core/index.ts
- M src/widgets/core/types/widget.ts
- M src/widgets/dashboard-report-card/DashboardReportCardWidget.tsx
- M src/widgets/dashboard-report-card/samples/TmsDeliveryFlowSample.tsx
- M src/widgets/dashboard-report-card/samples/TmsDeliveryMetricsSample.tsx
- M src/widgets/dashboard-report-card/samples/WmsInboundOutboundSample.tsx
- M src/widgets/dashboard-report-card/samples/WmsInventoryTrendSample.tsx
- M src/widgets/gps-sample-card/GpsSampleWidget.tsx
- M src/widgets/gps-sample-card/samples/Sample.tsx
- M src/widgets/registry.ts
- M src/widgets/text-memo-widget/TextMemoWidget.css
- M src/widgets/text-memo-widget/TextMemoWidget.tsx
- A docs/worklogs/2026-04-09.md
- A docs/assets/worklogs/2026-04-09/feature-play-layout.png
- A docs/assets/worklogs/2026-04-09/widget-gps-sample.png

357
docs/worklogs/2026-04-10.md Executable file
View File

@@ -0,0 +1,357 @@
# 2026-04-10 작업일지
## 오늘 작업
- `react-router-dom` 기반 앱 셸로 메인 화면을 재구성하고, 상단 메뉴와 사이드바 선택이 URL 경로와 직접 연결되도록 정리했습니다.
- `AppShell`, `MainLayout`, `routes.tsx`를 추가해 `Docs / APIs / Plans / Chat / Play`를 페이지 단위로 분리했습니다.
- `MainContent`는 공통 창 레이어와 콘텐츠 컨테이너 역할만 남기고, 실제 화면 렌더링은 각 페이지 컴포넌트로 이동시켰습니다.
- 저장 레이아웃 상세 화면은 `savedLayoutViewId` 전용 fit 모드로 보정해, 상단 바를 포함한 전체 구성을 한 화면 안에 맞춰 보이도록 수정했습니다.
- `Text Memo Widget`의 삭제 버튼이 실제로 동작하도록 `Modal.useModal()` 기반 확인 플로우를 연결했습니다.
- 오늘 증적용으로 `APIs / Widgets` 전체 화면 1장과 `Text Memo Widget` 부분 화면 1장을 `docs/assets/worklogs/2026-04-10/`에 저장했습니다.
- 오늘자 작업일지 문서를 새로 작성하고, 소스 탭에서 `전체소스 / diff` 전환에 대응할 수 있도록 파일별 raw diff 근거와 전체 변경 파일 목록을 정리했습니다.
## 이슈 및 해결
- 기존 `MainView` 한 파일에 메뉴 상태, 검색 옵션, 문서/플랜/채팅/플레이 렌더링이 모두 얽혀 있어 경로 기반 탐색과 확장이 어려웠습니다.
- `AppShell + MainLayout + pages/* + routes.tsx` 구조로 나누고, 라우팅 파생 상태는 `parseRoute()``MainLayoutContext`에서만 계산하도록 정리했습니다.
- 저장 레이아웃 전용 화면은 내부 콘텐츠 높이와 스크롤 상태에 따라 미리보기가 잘리거나 빈 여백이 생겼습니다.
- `useLayoutEffect`, `ResizeObserver`, `MutationObserver`로 실제 렌더 크기를 다시 재고 `savedLayoutFitScale`을 계산해 fit 렌더를 안정화했습니다.
- 메모 위젯 삭제 버튼은 정적 모달 호출과 컨텍스트 미연결 상태 때문에 클릭해도 확인창이 뜨지 않는 문제가 있었습니다.
- `Modal.useModal()`을 도입하고 `modalContextHolder`를 위젯 루트에 넣어 저장 메모와 작성 중 초안 모두 삭제 가능하게 바꿨습니다.
- 이 작업 환경에서는 Git이 `dubious ownership`으로 차단되고 글로벌 `safe.directory` 등록도 실패했습니다.
- 글로벌 설정 대신 모든 Git 조회에 `git -c safe.directory=/workspace/auto_codex/repo ...`를 사용해 증적 수집을 계속 진행했습니다.
## 결정 사항
- 메인 앱 진입은 상태 기반 조건 분기보다 URL 라우팅을 우선 기준으로 유지합니다.
- 공통 레이아웃 상태는 `MainLayoutContext`로 전달하고, 페이지별 렌더링 책임은 `pages/*`로 분리합니다.
- 저장 레이아웃 상세 뷰는 스크롤보다 fit 우선 미리보기 규칙을 사용합니다.
- 메모 삭제는 즉시 삭제하지 않고 항상 확인 모달을 거치게 유지합니다.
- 오늘 대표 스크린샷은 `APIs / Widgets` 전체 화면, 부분 스크린샷은 `Text Memo Widget`으로 고정합니다.
## 상세 작업 내역
- 오늘 Git 이력은 `WindowUI` 최소화/헤더 정리, 메모 삭제 플로우 복구, 저장 레이아웃 fit 모드 보정, 앱 셸 라우팅 분리 순서로 누적됐고 최종적으로 `release -> main` 동기화까지 진행됐습니다.
- 라우팅 개편은 `MainView`에 모여 있던 메뉴/문서/플랜/채팅/플레이 조건 분기를 걷어내고, `AppShell` 아래에서 `MainLayout`이 공통 상태를 제공한 뒤 각 페이지가 자기 화면만 렌더링하도록 재조정한 작업입니다.
- 검색 옵션 빌드도 같은 라우팅 구조를 따라가도록 재작성해서, 검색 결과가 단순 상태 변경이 아니라 실제 경로 전환과 포커스 이동을 함께 수행하도록 맞췄습니다.
- 저장 레이아웃 보정은 단순 CSS 스케일링이 아니라 실제 콘텐츠 크기를 측정해 viewport 안에 맞추는 방식으로 바꿨고, 관련 overflow 규칙도 같이 조정했습니다.
- 메모 위젯은 삭제 버튼 활성화와 확인 모달 흐름을 분리해, 저장된 메모와 작성 중 초안을 같은 UX 안에서 처리하도록 정리했습니다.
- 오늘 작업일지 작성 단계에서는 Git 로그, diff, 빌드 결과, 실제 Playwright 캡처를 함께 확인해서 문서와 스크린샷 링크를 최신 상태로 맞췄습니다.
## 스크린샷
![feature-apis-widgets-full](../assets/worklogs/2026-04-10/feature-apis-widgets-full.png)
- `APIs / Widgets` 전체 화면 캡처입니다. 라우팅 기반 앱 셸 아래에서 위젯 갤러리 전체 내역이 한 번에 보이도록 full-page로 저장했습니다.
![widget-text-memo](../assets/worklogs/2026-04-10/widget-text-memo.png)
- `Text Memo Widget` 부분 캡처입니다. 오늘 복구한 메모 입력/삭제 흐름을 위젯 단위로 바로 확인할 수 있도록 별도로 남겼습니다.
## 소스
### 파일 1: `src/app/main/AppShell.tsx`
- 라우터 엔트리와 페이지 분리의 기준점입니다.
```diff
+import { Navigate, Route, Routes } from 'react-router-dom';
+import { MainLayout } from './layout/MainLayout';
+import { ApisPage } from './pages/ApisPage';
+import { ChatPage } from './pages/ChatPage';
+import { DocsPage } from './pages/DocsPage';
+import { PlansPage } from './pages/PlansPage';
+import { PlayPage } from './pages/PlayPage';
+<Route path="/" element={<MainLayout />}>
+ <Route path="docs/:folder" element={<DocsPage />} />
+ <Route path="apis/:section" element={<ApisPage />} />
+ <Route path="plans/:section" element={<PlansPage />} />
+ <Route path="chat/:section" element={<ChatPage />} />
+ <Route path="play/layout-record/:layoutId" element={<PlayPage />} />
+</Route>
```
### 파일 2: `src/app/main/layout/MainLayout.tsx`
- 기존 `MainView`의 거대한 상태 조합을 라우팅 기반 공통 레이아웃으로 옮겼습니다.
```diff
+import { Outlet, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
+function parseRoute(pathname: string) {
+ if (top === 'docs') { ... }
+ if (top === 'apis' && (first === 'components' || first === 'widgets')) { ... }
+ if (top === 'play' && first === 'layout-record' && second) { ... }
+}
+const routeState = useMemo(() => parseRoute(location.pathname), [location.pathname]);
+const layoutData = useMainLayoutData();
+<MainLayoutContextProvider value={...}>
+ <MainHeader ... />
+ <MainSidebar ... />
+ <MainContent contentExpanded={contentExpanded} onToggleContentExpanded={...}>
+ <Outlet />
+ </MainContent>
+</MainLayoutContextProvider>
```
### 파일 3: `src/app/main/routes.tsx`
- 메뉴 라벨, 경로 빌더, 사이드바 공개 타입을 한곳에 모았습니다.
```diff
+export type TopMenuKey = 'docs' | 'apis' | 'plans' | 'chat' | 'play';
+export type PlanSectionKey = PlanFilterStatus | 'release' | 'charts' | 'history';
+export const DOCS_DEFAULT_FOLDER = 'worklogs';
+export function buildDocsPath(folder = DOCS_DEFAULT_FOLDER) {
+ return `/docs/${folder}`;
+}
+export function buildPlansPath(section: PlanSectionKey = 'all') {
+ return `/plans/${section}`;
+}
+export function buildPlayMenuItems(savedLayouts: Array<{ id: string; name: string }>): MenuProps['items'] {
+ return [{ key: 'play-group', children: [...] }];
+}
```
### 파일 4: `src/app/main/MainContent.tsx`
- 공통 창 레이어만 남기고 화면별 조건 분기를 페이지 컴포넌트로 밀어냈습니다.
```diff
-import { Button, Card, Layout, Modal, Space, Typography } from 'antd';
+import { Button, Layout, Modal, Space, Typography } from 'antd';
-import { MarkdownPreviewCard } from '../../components/markdownPreview';
+import { useMainLayoutContext } from './layout/MainLayoutContext';
-export function MainContent({ activeTopMenu, selectedApiMenu, selectedDocsMenu, ... }: MainContentProps) {
+export function MainContent({ contentExpanded, onToggleContentExpanded, children }: MainContentProps) {
+ const { componentSampleEntries, widgetSampleEntries, componentSamples, widgetSamples, initialSelectedPlanId, initialSelectedWorkId } =
+ useMainLayoutContext();
- <div className={activeTopMenu === 'chat' ? 'app-main-layout app-main-layout--single' : 'app-main-layout'}>
- {activeTopMenu === 'docs' ? ... : activeTopMenu === 'plans' ? ... : ...}
- </div>
+ <div className="app-main-layout">{children}</div>
```
### 파일 5: `src/app/main/MainSidebar.tsx`
- 소개 영역과 API 메뉴 선택 타입을 공통 라우팅 구조에 맞게 정리했습니다.
```diff
+ introColor,
+ introTag,
+ introDescription,
- const activeTagColor = isDocsGroup ? 'gold' : activeTopMenu === 'play' ? 'cyan' : 'green';
- <Tag color={activeTagColor}>...</Tag>
+ <Tag color={introColor}>{introTag}</Tag>
+ <Text type="secondary">{introDescription}</Text>
- onSelectApiMenu(key);
+ onSelectApiMenu(key as MainSidebarProps['selectedApiMenu']);
```
### 파일 6: `src/app/main/layout/MainLayoutContext.ts`
- 페이지와 공통 레이어가 같은 상태를 공유하도록 컨텍스트를 신설했습니다.
```diff
+export type MainLayoutContextValue = {
+ topMenu: TopMenuKey;
+ selectedDocsMenu: string;
+ selectedApiMenu: ApiSectionKey;
+ selectedPlanMenu: PlanSectionKey;
+ selectedChatMenu: ChatSectionKey;
+ selectedPlayMenu: PlaySidebarKey;
+ searchOptions: SearchKeywordOption[];
+};
+const MainLayoutContext = createContext<MainLayoutContextValue | null>(null);
+export function useMainLayoutContext() { ... }
```
### 파일 7: `src/app/main/layout/buildSearchOptions.ts`
- 검색 결과가 상태 변경이 아니라 실제 라우팅 전환을 수행하도록 재작성했습니다.
```diff
+import { buildApisPath, buildChatPath, buildDocsPath, buildPlansPath, buildPlayPath } from '../routes';
+onSelect: () => {
+ requestPlanQuickFilter(null);
+ navigateTo(buildApisPath('widgets'));
+ setFocusedComponentId(null);
+},
+onSelect: () => {
+ requestPlanQuickFilter('release-pending-main');
+ navigateTo(buildPlansPath('release'));
+ setFocusedComponentId(null);
+},
+onSelect: () => {
+ navigateTo(buildDocsPath(document.folder));
+ setFocusedComponentId(`doc:${document.id}`);
+ scrollToElement(`document-preview-${document.id}`);
+},
```
### 파일 8: `src/app/main/pages/ApisPage.tsx`
- APIs 화면 렌더링 책임을 페이지 단위로 분리했습니다.
```diff
+export function ApisPage() {
+ const { selectedApiMenu, componentSampleEntries, widgetSampleEntries } = useMainLayoutContext();
+ return (
+ <div className="app-main-panel">
+ <Card title={selectedApiMenu === 'components' ? 'APIs / Components' : 'APIs / Widgets'} className="app-main-card" bordered={false}>
+ {selectedApiMenu === 'components' ? (
+ <ComponentSamplesLayout entries={componentSampleEntries} excludeComponentIds={HIDDEN_COMPONENT_IDS} />
+ ) : (
+ <SampleWidgetsLayout entries={widgetSampleEntries} />
+ )}
+ </Card>
+ </div>
+ );
+}
```
### 파일 9: `src/views/play/LayoutPlaygroundView.tsx`
- 저장 레이아웃 전용 fit 모드와 실제 크기 측정 로직을 추가했습니다.
```diff
-import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
+import { useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
+const isSavedLayoutFitMode = Boolean(savedLayoutViewId);
+const [savedLayoutFitScale, setSavedLayoutFitScale] = useState(1);
+const [savedLayoutFitSize, setSavedLayoutFitSize] = useState<{ width: number; height: number } | null>(null);
+const savedLayoutFitViewportRef = useRef<HTMLDivElement | null>(null);
+const savedLayoutFitContentRef = useRef<HTMLDivElement | null>(null);
+useLayoutEffect(() => {
+ const nextScale = Math.min(1, availableWidth / width, availableHeight / height);
+ setSavedLayoutFitScale(...);
+}, [savedLayoutViewId, selectedSavedLayoutRecord, sampleEntries.length]);
+style={{ transform: `scale(${savedLayoutFitScale})`, width: `${savedLayoutFitSize.width}px` }}
+previewSurface: isSavedLayoutFitMode,
```
### 파일 10: `src/styles.css`
- fit 모드에서 overflow와 스크롤바를 억제하는 전용 스타일을 추가했습니다.
```diff
+.layout-playground__fullscreen-shell--saved-fit {
+ min-height: 0;
+ overflow: hidden;
+}
+.layout-playground__saved-fit-viewport {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+}
+.layout-playground__saved-detail--fit * {
+ scrollbar-width: none;
+}
+.layout-playground__saved-detail--fit *::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+}
```
### 파일 11: `src/widgets/text-memo-widget/TextMemoWidget.tsx`
- 삭제 버튼 비활성 상태를 해제하고, 확인 모달을 거쳐 실제 삭제되도록 바꿨습니다.
```diff
-import { Button, Empty, Input, message } from 'antd';
+import { Button, Empty, Input, Modal, message } from 'antd';
+const [modalApi, modalContextHolder] = Modal.useModal();
-const handleDelete = () => {
- void messageApi.info('삭제 기능은 현재 비활성화되어 있습니다.');
-};
+const handleDelete = () => {
+ if (!selectedNote && !hasDraft) {
+ return;
+ }
+ void modalApi.confirm({
+ title: isDraftOnly ? '작성 중인 메모를 삭제할까요?' : '선택한 메모를 삭제할까요?',
+ onOk: () => { ... },
+ });
+};
+{modalContextHolder}
-disabled
+disabled={!selectedNote && !hasDraft}
```
### 파일 12: `src/App.tsx`, `src/main.tsx`, `src/app/main/index.ts`
- 앱 진입점을 라우터 기반 셸로 연결했습니다.
```diff
-import { MainView } from './app/main';
+import { BrowserRouter } from 'react-router-dom';
+import { MainView } from './app/main';
+<BrowserRouter>
+ <MainView />
+</BrowserRouter>
-export { MainView } from './MainView';
+export { AppShell } from './AppShell';
+export { MainView } from './MainView';
-export function MainView() { ...legacy layout... }
+export function MainView() {
+ return <AppShell />;
+}
```
## 실행 커맨드
```bash
find docs/worklogs docs/assets/worklogs -type f | sort
grep -RIn "작업일지\|스크린샷\|소스 탭\|실행 커맨드\|상세형" docs/worklogs --include='*.md'
git -c safe.directory=/workspace/auto_codex/repo status --short --branch
sed -n '1,260p' docs/worklogs/2026-04-09.md
sed -n '1,260p' docs/templates/worklog-template.md
find scripts -maxdepth 1 -type f | sort
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-10 00:00:00' --until='2026-04-10 23:59:59' --stat --oneline
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-10 00:00:00' --until='2026-04-10 23:59:59' --name-only --pretty=format: | sed '/^$/d' | sort | uniq
git -c safe.directory=/workspace/auto_codex/repo diff --stat 0b105f8..HEAD -- src/app/main/MainView.tsx src/app/main/layout/MainLayout.tsx src/app/main/routes.tsx src/app/main/MainContent.tsx src/widgets/text-memo-widget/TextMemoWidget.tsx src/components/window/WindowUI.tsx src/styles.css src/views/play/LayoutPlaygroundView.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/app/main/MainView.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/app/main/layout/MainLayout.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/widgets/text-memo-widget/TextMemoWidget.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/app/main/routes.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/app/main/MainContent.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/views/play/LayoutPlaygroundView.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/app/main/AppShell.tsx src/app/main/MainSidebar.tsx src/app/main/layout/MainLayoutContext.ts src/app/main/layout/buildSearchOptions.ts src/app/main/layout/useMainLayoutData.ts src/app/main/pages/ApisPage.tsx src/app/main/pages/ChatPage.tsx src/app/main/pages/DocsPage.tsx src/app/main/pages/PlansPage.tsx src/main.tsx
git -c safe.directory=/workspace/auto_codex/repo diff 0b105f8..HEAD -- src/components/window/WindowUI.tsx src/components/window/WindowUI.css src/widgets/text-memo-widget/TextMemoWidget.css src/styles.css
git -c safe.directory=/workspace/auto_codex/repo diff --name-status 0b105f8..HEAD
npm run build:app
PORT=4173 node scripts/serve-app-dist.mjs
node --input-type=module <<'EOF'
import { chromium } from 'playwright';
// APIs / Widgets full-page screenshot and text memo widget crop capture
EOF
```
## 변경/신규 파일
- A docs/worklogs/2026-04-10.md
- A docs/assets/worklogs/2026-04-10/feature-apis-widgets-full.png
- A docs/assets/worklogs/2026-04-10/widget-text-memo.png
- M package-lock.json
- M package.json
- M src/App.tsx
- A src/app/main/AppShell.tsx
- M src/app/main/MainContent.tsx
- M src/app/main/MainSidebar.tsx
- M src/app/main/MainView.tsx
- M src/app/main/index.ts
- A src/app/main/layout/MainLayout.tsx
- A src/app/main/layout/MainLayoutContext.ts
- A src/app/main/layout/buildSearchOptions.ts
- A src/app/main/layout/useMainLayoutData.ts
- A src/app/main/pages/ApisPage.tsx
- A src/app/main/pages/ChatPage.tsx
- A src/app/main/pages/DocsPage.tsx
- A src/app/main/pages/PlansPage.tsx
- A src/app/main/pages/PlayPage.tsx
- A src/app/main/routes.tsx
- M src/app/main/types.ts
- M src/main.tsx
- M src/styles.css
- M src/views/play/LayoutPlaygroundView.tsx
- M src/widgets/text-memo-widget/TextMemoWidget.tsx

296
docs/worklogs/2026-04-11.md Executable file
View File

@@ -0,0 +1,296 @@
# 2026-04-11 작업일지
## 오늘 작업
- 게시판 글을 자동화 항목으로 접수하는 흐름을 열고, 중복 접수 상태와 권한 검사를 같이 묶어 게시판에서 바로 후속 작업으로 넘길 수 있게 정리했습니다.
- 게시판 기반 Markdown 자동 등록 자동화는 설정, 워커 스케줄, 대상 폴더 규칙을 한 흐름으로 묶고 주기 계산을 분 단위 기준으로 다시 맞췄습니다.
- Plan 반복 요청은 기존 메모 내부 옵션에서 분리해 전용 스케줄 기능으로 옮기고, 스케줄 화면과 API, 서버 저장 구조를 새로 연결했습니다.
- Plan 화면은 목록 필터, 체크리스트, 릴리즈 요약, 증적 탭, 게시판 연결, 이슈/조치 기록, 본문 최대화, 읽기 전용 제어를 순차 보강했습니다.
- Plans 진입 구조는 불필요한 중간 화면을 걷어내고 `Plan / 게시판 / 차트 / 스케줄 / 히스토리`가 직접 연결되도록 다시 정리했습니다.
- 공통 컴포넌트 묶음으로 `FormField`, `StateKit`, `DataListTable`을 추가해 이후 화면 확장에 재사용할 수 있는 기반을 확보했습니다.
- 오늘 증적용으로 `Plan/자동화` 전체 화면 1장과 상단 개요 영역 1장을 `docs/assets/worklogs/2026-04-11/`에 저장했습니다.
## 이슈 및 해결
- 게시판 초기화가 동시에 들어오면 테이블과 컬럼 생성 경합으로 첫 진입이 실패할 수 있었습니다.
- 생성 전 존재 여부를 다시 확인하는 방식으로 setup 경합을 흡수해 초기 로드 실패를 줄였습니다.
- 게시판 자동 등록은 대기 글이 없을 때 다음 주기까지 완전히 멈춰 추천 문서 Plan이 더 이상 생기지 않는 구간이 있었습니다.
- 워커가 빈 큐에서도 다음 스케줄을 유지하도록 바꾸고, 분/초 단위 혼용 계산도 함께 보정했습니다.
- Plan 반복 요청 옵션을 메모 등록 폼 안에 유지하니 일반 메모 편집과 스케줄성 작업이 한 화면에서 섞여 판단이 어려웠습니다.
- 반복 요청 UI를 걷어내고 전용 스케줄 화면과 서버 테이블로 분리해 책임 경계를 명확히 했습니다.
- Plan 상세는 자동화 접수 항목인데도 일부 상태값이나 이력 입력이 우회 수정될 수 있었습니다.
- 잠금 판정을 공통 함수로 모으고, 권한 토큰이 없는 사용자는 조치 입력과 상태 액션이 모두 읽기 전용으로만 보이게 막았습니다.
- 증적/미리보기 전체화면은 내부 스크롤 대신 부모 스크롤이 따라 움직여 읽기 흐름이 깨졌습니다.
- 전체화면 진입 시 `body/html` 스크롤을 고정하고, 모달 내부 스크롤만 유지하도록 바꿔 사용 흐름을 안정화했습니다.
## 결정 사항
- 게시판에서 만들어진 자동화 접수 건은 기본적으로 `release` 기준으로 처리하고 `main` 자동 반영은 별도 정책이 없는 한 꺼 둡니다.
- 반복성 작업은 일반 메모 옵션이 아니라 전용 스케줄 목록에서 관리합니다.
- Plan 상세에서 자동화로 접수된 원본 요청은 수정 대신 조치 기록과 증적 확인 중심으로 다룹니다.
- Plans 화면은 중간 안내 카드보다 실제 작업 화면을 바로 여는 구성이 우선입니다.
- 오늘 대표 증적 화면은 `Plan/자동화` 전체 화면, 부분 증적은 상단 개요 카드로 통일합니다.
## 상세 작업 내역
- 오전 초반에는 게시판 setup 경합과 자동화 접수 진입점을 먼저 정리해, 게시판 글이 실패 없이 열리고 필요한 경우 바로 자동화 요청으로 이어지게 만들었습니다.
- 이어서 게시판 글을 Markdown 생성 작업으로 돌리는 자동화 흐름을 붙였고, 추천 문서 생성 규칙과 스케줄 조건을 계속 수정하면서 빈 큐에서도 다음 주기를 유지하도록 다듬었습니다.
- 공통 컴포넌트 추가는 이후 화면 정리에 필요한 기반 작업으로 진행했고, 새 입력/상태/목록 컴포넌트를 샘플과 함께 공개해 재사용 가능한 토대를 마련했습니다.
- 중반 작업에서는 반복 요청 옵션을 걷어내고 전용 스케줄 기능으로 치환하는 쪽으로 방향을 바꿨습니다. 이 과정에서 저장 구조, API, 워커, 화면을 함께 움직여 반복 작업 관리의 중심을 스케줄 화면으로 옮겼습니다.
- 이후 Plans 네비게이션은 실제 사용 흐름에 맞게 다시 접었고, 게시판 복구, 차트/스케줄/히스토리 재연결, 용어 정리, 검색 라벨 통일을 거쳐 지금 구조로 수렴시켰습니다.
- 오후 후반에는 Plan 목록과 상세 화면 품질을 집중 보강했습니다. 조합 필터, 체크리스트, 릴리즈 요약, 증적 탭, 게시판 연결, 이슈 조치 기록이 순차적으로 붙었고, 본문 최대화와 전체화면 스크롤 제어도 여기서 함께 정리됐습니다.
- 마감 단계에서는 자동화 접수 건의 잠금 규칙을 다시 점검해 우회 수정 가능성을 줄였고, 비토큰 사용자 읽기 전용 제어와 알림 링크 연결까지 보완한 뒤 오늘 작업일지와 증적 캡처를 최신 상태로 정리했습니다.
## 스크린샷
![feature-plans-automation-full](../assets/worklogs/2026-04-11/feature-plans-automation-full.png)
- `Plan/자동화` 전체 화면 캡처입니다. 오늘 정리한 목록, 상세 진입 흐름, 상단 개요 구성이 한 화면에서 보이도록 전체 페이지 기준으로 저장했습니다.
![plan-board-overview](../assets/worklogs/2026-04-11/plan-board-overview.png)
- 상단 개요 카드 부분 캡처입니다. 자동 새로고침 상태, 작업 개수 요약, 안내 문구처럼 오늘 보강한 운영 문맥을 빠르게 확인할 수 있도록 따로 남겼습니다.
## 소스
### 파일 1: `etc/servers/work-server/src/routes/board.ts`, `etc/servers/work-server/src/services/board-service.ts`, `src/features/board/BoardPage.tsx`, `src/features/board/api.ts`, `src/features/board/types.ts`
- 게시판 글을 즉시 자동화 Plan으로 접수하고, 접수 중복과 권한을 함께 처리하는 흐름입니다.
```diff
+app.post('/api/board/posts/:id/actions/automation-receive', async (request, reply) => {
+ if (!requireBoardAutomationAccess(request, reply)) {
+ return;
+ }
+ const result = await receiveBoardPostAutomation(id);
+ return { ok: true, item: result.item, planItemId: result.planItemId, alreadyReceived: result.alreadyReceived };
+});
+if (currentRow.automation_received_at || currentRow.automation_plan_item_id) {
+ return { item: mapBoardPostRow(currentRow), planItemId: ..., alreadyReceived: true };
+}
+const workId = `board-post-${id}`;
+note: [`게시판 제목: ${title}`, '', content].join('\n'),
+auto_deploy_to_main: false,
+const [automationReceivingId, setAutomationReceivingId] = useState<number | null>(null);
+const [automationReceiveError, setAutomationReceiveError] = useState<string | null>(null);
+icon={<PlayCircleOutlined />}
+Tag color={automationStatus.color}
```
### 파일 2: `etc/servers/work-server/src/services/board-service.ts`, `etc/servers/work-server/src/workers/plan-worker.ts`, `etc/servers/work-server/src/services/app-config-service.ts`, `src/app/main/MainHeader.tsx`, `src/app/main/appConfig.ts`
- 게시판 Markdown 자동 등록 자동화를 설정과 워커 주기 계산까지 포함해 운영 가능한 상태로 묶었습니다.
```diff
+markdownPlanItemId: number | null;
+markdownExportedAt: string | null;
+function buildBoardPostMarkdownPlanNote(row: Record<string, unknown>, targetFolder: string) {
+ return [
+ `게시판 추천 글 #${id}을 Markdown 문서로 등록해 주세요.`,
+ `대상 파일: ${targetPath}`,
+ ].join('\n');
+}
+export async function createNextBoardPostMarkdownAutomationPlan(targetFolder: string, releaseTarget = 'release') {
+ work_id: `board-md-${id}`,
+ auto_deploy_to_main: true,
+}
+private evaluateBoardMarkdownAutomationSchedule(config, now) {
+ const scheduleType = config?.scheduleType ?? 'interval';
+ return { due, nextEligibleAt, scheduleLabel };
+}
+await this.processBoardMarkdownAutomation(appConfig);
+intervalMinutes: z.coerce.number().int().min(1).default(5)
```
### 파일 3: `src/components/formField/FormField.tsx`, `src/components/stateKit/StateKit.tsx`, `src/components/dataListTable/DataListTable.tsx`, `src/index.ts`
- 입력/상태/목록 UI를 공통 컴포넌트로 분리해 이후 Plan, Board, History 화면이 같은 토대 위에서 확장되도록 만들었습니다.
```diff
+export type DataListTableProps<T extends object> = {
+ data: T[];
+ searchFields?: ReadonlyArray<keyof T | ((item: T) => string)>;
+ filters?: ReadonlyArray<DataListFilterOption<T>>;
+ mobileCardRender?: (item: T) => ReactNode;
+};
+export function DataListTable<T extends object>({ ... }: DataListTableProps<T>) {
+ const filteredData = useMemo(() => { ... }, [...]);
+ return <Table<T> ... />;
+}
+export type FormFieldProps = Omit<FormItemProps, 'children' | 'help' | 'required'> & {
+ error?: ReactNode;
+ children: ReactNode | ((state: FormFieldRenderState) => ReactNode);
+};
+export { FormField } from './components/formField';
+export { StateKit } from './components/stateKit';
+export { DataListTable } from './components/dataListTable';
```
### 파일 4: `etc/servers/work-server/src/services/plan-service.ts`, `etc/servers/work-server/src/services/plan-schedule-service.ts`, `etc/servers/work-server/src/routes/plan.ts`, `etc/servers/work-server/src/workers/plan-worker.ts`, `src/features/planBoard/PlanSchedulePage.tsx`, `src/features/planBoard/api.ts`
- 반복 요청을 메모 옵션에서 분리하고 전용 스케줄 저장소와 화면으로 재편했습니다.
```diff
+export const PLAN_SCHEDULED_TASK_TABLE = 'plan_scheduled_tasks';
+export const createPlanScheduledTaskSchema = z.object({
+ workId: z.string().trim().optional().default('반복작업'),
+ repeatIntervalMinutes: z.coerce.number().int().min(1).max(1440).default(60),
+});
+app.get('/api/plan/scheduled-tasks', async (request, reply) => { ... });
+app.post('/api/plan/scheduled-tasks', async (request, reply) => { ... });
+app.patch('/api/plan/scheduled-tasks/:id', async (request, reply) => { ... });
+app.delete('/api/plan/scheduled-tasks/:id', async (request, reply) => { ... });
+await ensurePlanScheduledTaskTable();
+return fetchWithFallback('/api/plan/scheduled-tasks', '/api/plans/scheduled-tasks');
+'/plan/schedule', '/plan/schedules', '/plans/schedule', '/plans/schedules'
```
### 파일 5: `src/features/planBoard/PlanBoardPage.tsx`, `src/features/history/HistoryPage.tsx`, `src/features/board/BoardPage.tsx`, `src/styles.css`
- Plan 목록과 상세는 필터, 요약, 증적, 본문 최대화, 잠금 규칙, 읽기 전용 제어를 중심으로 계속 보강했습니다.
```diff
+function isPlanItemRequestLocked(item: Pick<PlanItem, 'startedAt'> | null | undefined) {
+ return Boolean(item?.startedAt);
+}
+const [noteExpanded, setNoteExpanded] = useState(false);
+const canAppendActionHistory = hasAccess && Boolean(selectedItem) && isPlanItemRequestLocked(selectedItem);
+if (!hasAccess) {
+ messageApi.error('설정 > 토큰 관리에서 권한 토큰을 등록한 사용자만 조치 이력을 추가할 수 있습니다.');
+}
+if (isPlanItemRequestLocked(currentItem)) {
+ messageApi.warning('자동화 접수된 항목은 기능동작확인을 수정할 수 없습니다.');
+}
+className={`plan-board-page__notepad-frame${noteExpanded ? ' plan-board-page__notepad-frame--expanded' : ''}`}
+className={`board-page__editor-frame${contentExpanded ? ' board-page__editor-frame--expanded' : ''}`}
+options={FUNCTION_CHECK_OPTIONS}
+disabled={!hasAccess || jangsingProcessingSavingId === item.id || isPlanItemRequestLocked(item)}
```
### 파일 6: `src/app/main/MainContent.tsx`, `src/app/main/layout/MainLayout.tsx`, `src/app/main/layout/buildSearchOptions.ts`, `src/app/main/mainView/constants.tsx`, `src/app/main/mainView/navigation.ts`, `src/app/main/mainView/searchOptions.ts`, `src/app/main/pages/PlansPage.tsx`, `src/app/main/routes.tsx`, `src/app/main/MainHeader.tsx`
- Plans 진입 구조를 단순화하고, 게시판/차트/스케줄/히스토리 복구와 용어 정리를 같은 축에서 마무리했습니다.
```diff
+type PlanSectionKey = 'all' | 'board' | 'charts' | 'schedule' | 'history' | 'release';
+navigateTo(buildPlansPath('board'));
+navigateTo(buildPlansPath('schedule'));
+navigateTo(buildPlansPath('history'));
+label: '자동화'
+label: 'Plan'
+title: '자동화'
+return <PlanBoardPage />;
+release target link -> '*.sm-home.cloud'
+search label: 'Plan'
```
### 파일 7: `etc/servers/work-server/src/services/plan-notification-service.ts`, `src/sw.js`, `src/components/previewer/PreviewerUI.tsx`, `src/components/previewer/CodexDiffPreviewer.tsx`
- 알림 클릭 이동과 전체화면 미리보기는 마지막 품질 보완으로 정리했습니다.
```diff
+function buildPlanNotificationTargetUrl(planId: number, workId: string | null | undefined, eventType: string) {
+ const baseUrl = eventType.startsWith('release-') ? 'https://rel.sm-home.cloud/' : 'https://sm-home.cloud/';
+ targetUrl.searchParams.set('planId', String(planId));
+}
+targetUrl = notificationData.targetUrl ? new URL(String(notificationData.targetUrl)) : new URL('/', self.location.origin);
+const scrollY = window.scrollY;
+document.body.style.position = 'fixed';
+document.body.style.top = `-${scrollY}px`;
+document.documentElement.style.overflow = 'hidden';
+window.scrollTo(0, scrollY);
```
### 파일 8: `docs/components/component-addition-suggestions.md`
- 게시판 논의 결과를 문서 증적으로 옮겨 오늘 제안 정리 흐름도 문서 세트 안에서 바로 확인할 수 있게 했습니다.
```diff
+## 이번 반영 요약
+- FormField: 폼 레이블, 도움말, 오류 메시지를 같은 포맷으로 묶는 공통 입력 레이어
+- StateKit: 로딩, 비어 있음, 오류 상태를 화면마다 반복 작성하지 않도록 정리한 상태 표현 묶음
+- DataListTable: 검색, 필터, 모바일 카드 대체 렌더를 함께 제공하는 공통 목록 레이어
```
## 실행 커맨드
```bash
find docs/worklogs -maxdepth 2 -type f -name '*.md' | sort
grep -RInE '작업일지|실행 커맨드|소스 탭|상세 작업 내역' docs/worklogs --include='*.md'
git -c safe.directory=/workspace/auto_codex/repo branch --show-current
git -c safe.directory=/workspace/auto_codex/repo status --short --branch
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-11 00:00' --pretty=format:'%h %ad %d %s' --date=iso-local --reverse --all
git -c safe.directory=/workspace/auto_codex/repo log --since='2026-04-11 00:00' --name-status --pretty=format:'commit %h' --all
git -c safe.directory=/workspace/auto_codex/repo diff 6f7a9aa..a6a3c92 -- src/features/planBoard/PlanBoardPage.tsx
git -c safe.directory=/workspace/auto_codex/repo show 818cc2f -- src/features/board/BoardPage.tsx src/features/board/api.ts src/features/board/types.ts etc/servers/work-server/src/routes/board.ts etc/servers/work-server/src/services/board-service.ts
git -c safe.directory=/workspace/auto_codex/repo show d9d124b -- src/features/planBoard/PlanSchedulePage.tsx src/features/planBoard/api.ts etc/servers/work-server/src/routes/plan.ts etc/servers/work-server/src/services/plan-schedule-service.ts etc/servers/work-server/src/workers/plan-worker.ts
git -c safe.directory=/workspace/auto_codex/repo show dd2e975 -- src/components/formField/FormField.tsx src/components/stateKit/StateKit.tsx src/components/dataListTable/DataListTable.tsx src/index.ts
git -c safe.directory=/workspace/auto_codex/repo show 4e48813 -- src/app/main/MainHeader.tsx src/app/main/appConfig.ts etc/servers/work-server/src/workers/plan-worker.ts etc/servers/work-server/src/services/board-service.ts
npm run build:app
PORT=4173 node scripts/serve-app-dist.mjs
curl -I http://127.0.0.1:4173/plans/all
node --input-type=module <<'EOF'
// Playwright로 /plans/all 진입 후 전체 화면과 개요 카드 캡처 저장
EOF
find docs/assets/worklogs/2026-04-11 -maxdepth 1 -type f | sort
```
## 변경/신규 파일
- A docs/worklogs/2026-04-11.md
- A docs/assets/worklogs/2026-04-11/feature-plans-automation-full.png
- A docs/assets/worklogs/2026-04-11/plan-board-overview.png
- M docs/components/component-addition-suggestions.md
- M etc/db/work-db/sql/board-posts.sql
- M etc/servers/work-server/src/routes/board.ts
- M etc/servers/work-server/src/routes/plan.ts
- M etc/servers/work-server/src/routes/visitor-history.ts
- M etc/servers/work-server/src/services/app-config-service.ts
- M etc/servers/work-server/src/services/board-service.ts
- M etc/servers/work-server/src/services/plan-notification-service.ts
- A etc/servers/work-server/src/services/plan-schedule-service.ts
- M etc/servers/work-server/src/services/plan-service.ts
- M etc/servers/work-server/src/services/visitor-history-service.ts
- M etc/servers/work-server/src/services/worklog-automation-service.ts
- M etc/servers/work-server/src/services/worklog-automation-utils.ts
- A etc/servers/work-server/src/workers/plan-worker.test.ts
- M etc/servers/work-server/src/workers/plan-worker.ts
- M src/app/main/MainContent.tsx
- M src/app/main/MainHeader.tsx
- M src/app/main/appConfig.ts
- M src/app/main/clientIdentity.ts
- M src/app/main/layout/MainLayout.tsx
- M src/app/main/layout/buildSearchOptions.ts
- M src/app/main/mainView/constants.tsx
- M src/app/main/mainView/navigation.ts
- M src/app/main/mainView/searchOptions.ts
- M src/app/main/pages/PlansPage.tsx
- M src/app/main/routes.tsx
- A src/components/dataListTable/DataListTable.css
- A src/components/dataListTable/DataListTable.tsx
- A src/components/dataListTable/index.ts
- A src/components/dataListTable/samples/BaseSample.tsx
- A src/components/formField/FormField.css
- A src/components/formField/FormField.tsx
- A src/components/formField/index.ts
- A src/components/formField/samples/BaseSample.tsx
- M src/components/previewer/CodexDiffPreviewer.tsx
- M src/components/previewer/PreviewerUI.tsx
- A src/components/stateKit/StateKit.css
- A src/components/stateKit/StateKit.tsx
- A src/components/stateKit/index.ts
- A src/components/stateKit/samples/BaseSample.tsx
- M src/features/board/BoardPage.tsx
- M src/features/board/api.ts
- M src/features/board/types.ts
- M src/features/history/HistoryPage.tsx
- M src/features/history/api.ts
- M src/features/planBoard/PlanBoardPage.tsx
- A src/features/planBoard/PlanSchedulePage.tsx
- M src/features/planBoard/api.ts
- M src/features/planBoard/index.ts
- M src/features/planBoard/types.ts
- M src/index.ts
- M src/store/appStore/context/AppStoreContext.tsx
- M src/styles.css
- M src/sw.js