diff --git a/docs/history.md b/docs/history.md index d841512..3de9509 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,11 @@ # 의사결정 이력 +## 2026-04-03 v1.4.71 +- 모바일에서 공통 본문 하단이 딱 붙어 보이는 문제는 로그인 화면 하나만 고치는 것보다 `workspaceBody` 공통 하단 여백을 safe-area까지 포함해 보강하는 편이 이후 모든 본문 화면에 일괄 적용되어 유지보수상 낫다고 판단했다. +- 모바일 왼쪽 네비게이션은 데스크톱의 폭 축소형 접기와 목적이 다르므로, 기존 `leftRailCollapsed`를 억지로 재사용하기보다 `mobileLeftNavOpen` 상태를 분리하고 유저 카드 우측 버튼으로 검색/메뉴 묶음만 접는 방식이 더 자연스럽다고 정리했다. +- 오른쪽 레일은 모바일에서 기본 자동 열림이 실제 조작 공간을 빼앗는 경우가 많으므로, 모바일 진입과 라우트 이동 시 기본 닫힘으로 두되 PC 레이아웃으로 돌아오면 다시 기본 열림을 복원하는 쪽으로 맞췄다. +- 모바일 터치에서는 짧은 탭 선택과 드래그 시작이 같은 포인터 입력에서 충돌하기 쉬우므로, Sortable에 터치 전용 지연과 threshold를 둬 탭은 선택, 길게 누르고 움직이면 드래그가 되도록 의도를 분리했다. + ## 2026-04-03 v1.4.70 - 카카오톡/디스코드/X 공유 미리보기는 대개 프런트 SPA 자바스크립트를 실행하기 전에 HTML 메타를 먼저 읽으므로, 기존 `index.html` 고정 메타를 프런트 런타임에서 바꾸는 방식만으로는 티어표별 썸네일/제목/설명을 안정적으로 보여주기 어렵다고 판단했다. - 현재 운영 구조가 프런트 Nginx 정적 서빙 + 백엔드 API 분리 형태이므로, 모든 SPA 경로를 SSR로 바꾸기보다 공유 버튼만 `/share/editor/...` 서버 렌더링 경로를 사용하게 하고, 이 경로에서 OG 메타를 만든 뒤 기존 `preview=1` 화면으로 넘기는 방식이 가장 작은 변경이라고 정리했다. diff --git a/docs/update.md b/docs/update.md index 30277e6..ba3996d 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,12 @@ # 업데이트 로그 +## 2026-04-03 v1.4.71 +- 모바일에서 본문 페이지나 로그인 화면 하단이 카드/버튼 바로 아래에서 끊겨 보여 답답했던 부분을 줄이기 위해, 공통 워크스페이스 본문 하단에 모바일 safe-area 기반 여백을 추가했다. +- 모바일 왼쪽 네비게이션은 유저 프로필 카드 오른쪽 토글 버튼으로 접고 펼칠 수 있게 바꾸고, 닫힘/열림 전환 시 검색창과 메뉴가 위아래로 부드럽게 스르륵 접히는 애니메이션을 추가했다. +- 모바일 진입 시 오른쪽 레일은 기본 닫힘으로 시작하고, 모바일에서 직접 오른쪽 레일을 열었을 때도 레일 하단 컨텐츠가 화면 바닥에 붙지 않도록 safe-area 여백을 더했다. +- 티어표 편집기 모바일 터치 조작에서 아이템을 짧게 탭하면 선택만 하고, 길게 누른 뒤 움직일 때 드래그가 시작되도록 Sortable 터치 시작 지연과 이동 임계값을 추가했다. +- 서버 점검 안내 문구는 `서비스 내부 점검이 필요합니다.` 대신 `서비스 내부 점검중입니다.`로 다듬었고, 프런트 프로덕션 빌드(`npm run build`) 통과를 확인했다. + ## 2026-04-03 v1.4.70 - 저장된 티어표의 `공유하기` 버튼이 기존 `preview=1` 편집기 주소 대신 `/share/editor/:topicId/:tierListId` 공유 전용 주소를 복사하도록 바꿨다. - 이 공유 전용 주소는 공개 티어표인 경우 해당 티어표의 제목, 설명, 썸네일을 기반으로 Open Graph/Twitter 메타 태그를 서버에서 동적으로 생성한 뒤, 실제 뷰어 화면 `/editor/:topicId/:tierListId?preview=1`로 즉시 이동시킨다. diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 54b6450..11fe690 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -27,6 +27,7 @@ const RIGHT_RAIL_COPYRIGHT_URL = 'https://x.com/zennbox' const currentTopicId = computed(() => route.params.topicId || '') const leftRailCollapsed = ref(false) +const mobileLeftNavOpen = ref(false) const rightRailOpen = ref(true) const searchQuery = ref('') const leftRailSearchPlaceholder = '주제 템플릿 검색' @@ -312,6 +313,12 @@ onMounted(async () => { const saved = window.localStorage.getItem('tier-maker:right-rail-open') if (saved === '0') rightRailOpen.value = false } + if (isMobileLayout.value) { + mobileLeftNavOpen.value = false + rightRailOpen.value = false + } else { + rightRailOpen.value = true + } searchQuery.value = typeof route.query.q === 'string' ? route.query.q : '' }) @@ -339,13 +346,27 @@ watch( searchQuery.value = typeof route.query.q === 'string' ? route.query.q : '' isCollapsedSearchOpen.value = false isGuideModalOpen.value = false + if (isMobileLayout.value) { + mobileLeftNavOpen.value = false + rightRailOpen.value = false + } } ) watch( isMobileLayout, (mobile) => { - if (mobile) leftRailCollapsed.value = false + if (mobile) { + leftRailCollapsed.value = false + mobileLeftNavOpen.value = false + rightRailOpen.value = false + return + } + mobileLeftNavOpen.value = false + rightRailOpen.value = true + if (typeof window !== 'undefined') { + window.localStorage.setItem('tier-maker:right-rail-open', '1') + } }, { immediate: true } ) @@ -353,7 +374,7 @@ watch( watch( usesLocalRightRail, (needed) => { - if (!needed || rightRailOpen.value) return + if (!needed || rightRailOpen.value || isMobileLayout.value) return rightRailOpen.value = true if (typeof window !== 'undefined') { window.localStorage.setItem('tier-maker:right-rail-open', '1') @@ -368,7 +389,10 @@ function isRouteActive(path) { } function toggleLeftRail() { - if (isMobileLayout.value) return + if (isMobileLayout.value) { + mobileLeftNavOpen.value = !mobileLeftNavOpen.value + return + } leftRailCollapsed.value = !leftRailCollapsed.value if (typeof window !== 'undefined') { window.localStorage.setItem('tier-maker:left-rail-collapsed', leftRailCollapsed.value ? '1' : '0') @@ -449,6 +473,7 @@ function reloadApp() { class="appShell" :class="{ 'appShell--leftCollapsed': leftRailCollapsed, + 'appShell--mobileNavClosed': isMobileLayout && !mobileLeftNavOpen, 'appShell--rightClosed': !rightRailOpen, 'appShell--rightOverlay': isRightRailOverlay, }" @@ -483,7 +508,7 @@ function reloadApp() {