21 KiB
Executable File
21 KiB
Executable File
2026-04-09 작업일지
오늘 작업
Play > Layout을 컴포넌트/위젯 선택형 레이아웃 편집기로 확장하고, 저장 레이아웃 목록/상세 흐름을 여러 차례 다듬었습니다.- 레이아웃 저장소를 브라우저
IndexedDB에서work-dbAPI 기반 서버 저장 방식으로 전환했습니다. 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-dbAPI 호출 구조로 전환하고, 인증 토큰/클라이언트 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 이력과 실제 캡처 결과로 다시 정리했습니다.
스크린샷
Play > Layout전체 화면입니다. 저장 레이아웃/편집/분할/섹션 선택 흐름을 한 번에 확인할 수 있습니다.
GPS Sample Widget부분 캡처입니다. 오늘 추가된 지도/거점/반경 위젯 단위 증적입니다.
소스
파일 1: src/views/play/LayoutPlaygroundView.tsx
Layout Editor의 핵심 화면으로, 컴포넌트/위젯 바인딩과 저장 레이아웃 상세 플로우를 대부분 이 파일에서 확장했습니다.
+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-dbAPI 기반 서버 저장 방식으로 전환했습니다.
+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
- 검색 모달 제목/설명 커스터마이징, 모바일 축약 문구, 선택 잠금 로직을 추가했습니다.
+ 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 기준 더블탭/더블클릭 확장 로직을 받았습니다.
-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 위에 오버레이로 표시하도록 구성했습니다.
+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 푸시 알림, 지도 표시를 한 위젯으로 묶었습니다.
+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를 구성했습니다.
+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 돌아가기, 긴 본문 접힘 흐름을 오늘 기준으로 누적 반영했습니다.
+ 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 상태 작업,자동화 실패바로가기를 추가하고 현재 건수를 같이 보여주도록 확장했습니다.
+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, 저장 레이아웃 메뉴까지 포괄하도록 재정리했습니다.
- '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/저장 레이아웃 렌더 분기를 추가했습니다.
+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를 추가했습니다.
+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기준 배치 처리로 바꿨습니다.
- 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반영 뒤 같은 브랜치에서 추가 작업을 이어가는 패턴을 금지하고 점검 절차를 명시했습니다.
+* `release` 반영 상태에서 현재 브랜치 그대로 추가 작업 진행
+* `release` 반영이 끝난 뒤 추가 수정 요청이 들어오면, 반드시 새 브랜치에서 다시 시작한다
+## 안전 점검 체크리스트
+1. 지금 반영할 변경이 `release`에 먼저 들어갔는지 확인
+2. 추가 요청이라면 새 `feature/*` 또는 `hotfix/*` 브랜치에서 작업 중인지 확인
+## 꼬임 위험 신호
+* `release` 반영 직후 추가 요청을 현재 브랜치에서 바로 이어서 처리하는 경우
파일 15: docs/assets/worklogs/2026-04-09/feature-play-layout.png
- 오늘 대표 전체 화면 스크린샷입니다.
Binary file added: docs/assets/worklogs/2026-04-09/feature-play-layout.png
파일 16: docs/assets/worklogs/2026-04-09/widget-gps-sample.png
- 오늘 위젯 단위 부분 스크린샷입니다.
Binary file added: docs/assets/worklogs/2026-04-09/widget-gps-sample.png
실행 커맨드
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

