111 KiB
의사결정 이력
2026-05-26 v1.5.2 — 페이지 작성 화면을 게시글 작성 화면과 통일
고정 페이지는 랜딩 페이지 작성 용도로 확장되므로 일반 관리자 폼보다 게시글 작성과 같은 집중형 전체 화면 에디터가 더 적합하다. 게시글과 페이지의 작성 화면이 다르면 저장 위치, 설정 위치, 본문 입력 방식이 매번 달라져 운영 피로가 커진다. 따라서 페이지 작성/수정도 상단 툴바와 오른쪽 접이식 설정 패널을 쓰고, 페이지 형식 선택과 URL·대표 이미지·삭제 액션은 설정 패널로 모은다. 기본 콘텐츠 입력도 게시글과 같은 Markdown-first 에디터를 사용해 작성 경험을 통일한다.
2026-05-26 v1.5.1 — 고정 페이지에 원문 HTML 문서 모드 추가
고정 페이지는 아직 운영에서 본격 사용 전이므로 구조를 크게 바꿀 수 있다. 랜딩 페이지처럼 한 주소에서 단일 index.html 문서를 보여주는 목적에는 Nuxt 컴포넌트 안의 v-html보다 서버에서 text/html로 원문을 응답하는 방식이 맞다. 따라서 페이지에는 render_mode를 두고, 기본 Markdown 모드는 기존 경로를 유지하되 html_document 모드만 서버 미들웨어가 /pages/:slug 요청을 가로채 저장된 HTML을 그대로 반환한다. 이 모드는 관리자만 저장하는 신뢰 콘텐츠를 전제로 한다.
2026-05-26 v1.5.0 — 글쓰기 태그 입력을 검색형 선택으로 정리
게시물 태그는 여러 개 저장되지만 관리자 글 목록에서는 운영자가 빠르게 분류를 읽는 것이 우선이므로 첫 번째 태그만 대표 태그로 표시한다. 글쓰기 화면은 직접 입력 흐름을 유지하되, 메인 태그처럼 이미 관리자가 등록한 태그는 다시 타이핑하지 않아도 되도록 오른쪽 트리거를 선택 드롭다운으로 바꾼다. 기존 태그는 이름과 슬러그 부분 일치로 추천해 no 입력만으로 note 같은 태그를 찾고, 방향키와 Enter로 추가할 수 있게 한다. 태그별 색상은 관리 화면에서 이미 운영자가 지정하는 분류 신호이므로 글쓰기 배지와 글 목록 대표 배지에도 같은 색상을 반영한다.
2026-05-22 v1.4.7 — 라이브 모드 인라인 마크다운 직렬화
라이브 편집 영역은 화면에 <strong>·<em> 등으로 표시되지만, blur 시 저장 경로가 textContent만 읽으면 **·* 마커가 빠진다. 문단 이동 시 이전 블록이 blur·commit 되므로 방향키만으로도 서식이 사라진 것처럼 보였다. readEditableTextFromElement가 DOM 인라인 노드를 마크다운으로 다시 직렬화하도록 수정한다.
인용 블록은 표준 > 문법을 유지하되, 첫 줄에 > [!bg=yellow]처럼 옵션 줄을 둘 수 있게 한다. 새 fenced block을 추가하지 않으면 기존 마크다운과 호환되고, 콜아웃에서 이미 쓰는 배경 프리셋을 공유할 수 있다. 소스에서 라이브로 전환할 때는 커서 줄에 포커스만 두고 스크롤하지 않는 경로가 있어 화면 위치가 어긋났으므로, 해당 전환에서는 대상 줄을 중앙에 가깝게 스크롤한다.
2026-05-22 v1.4.6 — 사이트 설정 이미지 저장 흐름 통일
관리자 사이트 설정은 섹션별 편집 후 저장으로 반영되는 컨셉이므로, 로고 업로드도 DB를 즉시 갱신하지 않고 업로드된 파일 URL만 폼에 반영한 뒤 기타 설정 저장 시 함께 저장하도록 정리한다. 홈 커버는 공개 라이트·다크 테마에 따라 이미지 톤이 크게 달라질 수 있어 기존 라이트 이미지를 기본값으로 유지하면서 다크 전용 URL을 별도 컬럼으로 추가한다. 다크 이미지가 없으면 기존 이미지로 fallback해 기존 설정과 공개 화면 동작을 유지한다.
2026-05-22 v1.4.5 — 게시물 작성자 기준 편집 링크
공개 게시글 상세의 편집 버튼은 단순히 “관리자 로그인 여부”가 아니라 실제 글쓴이인지로 판단해야 한다. 현재 운영은 관리자 1인 작성 전제지만, 멤버·권한 구조가 이미 분리되어 있으므로 게시물에 author_id를 명시해 현재 로그인 회원 ID와 비교하는 방식으로 정리한다. 기존 게시물은 owner/admin 계정이 정확히 1개일 때만 backfill해 잘못된 작성자 배정을 피하고, 새 게시물은 관리자 세션의 사용자 ID를 작성자로 저장한다.
2026-05-21 v1.4.2 — 관리자 레이아웃 라이트 테마 격리
공개 사이트의 라이트/다크 테마는 html[data-theme]와 CSS 변수로 전역에 적용된다. 같은 앱 안의 관리자 화면이 이 변수를 그대로 상속하면, 공개 화면을 다크모드로 둔 상태에서 관리자 네비게이션 입력처럼 별도 배경색을 명시하지 않은 폼 컨트롤이 어두운 색으로 바뀌어 관리 UI 가독성이 깨진다. 관리자 로그인은 별도 다크 인증 화면으로 유지하되, 로그인 이후 admin-layout은 운영 도구 성격에 맞춰 라이트 UI로 고정한다. 다만 글쓰기 에디터는 별도 입력 UX가 있으므로, 폼 컨트롤 color-scheme 재정의는 admin-layout--light-controls가 붙은 일반 관리자 화면에만 적용한다.
2026-05-21 v1.4.1 — 라이브 모드 임베드 즉시 프리뷰 전환
단독 URL 붙여넣기가 임베드 작성의 기본 흐름이 되면서 라이브 모드의 별도 URL 입력 카드는 같은 값을 다시 입력하게 만드는 중복 UI가 됐다. 임베드 블록은 즉시 실제 프리뷰로 보여주고, iframe 때문에 일반 텍스트처럼 커서를 둘 수 없는 문제는 프리뷰 래퍼 자체를 포커스 가능한 블록으로 만들어 삭제·아래 줄 추가 키보드 조작을 받도록 했다. 같은 문제가 업로드 비디오·오디오·파일 카드에도 적용되므로 공통 프리뷰 카드 동작으로 확장한다. iframe·audio 컨트롤 등이 내부 포커스를 가져갈 수 있으므로 hover/focus 삭제 버튼을 둔다. 선택된 프리뷰 카드에서 방향키가 페이지 스크롤로 빠지지 않도록 ArrowUp·ArrowDown은 이전/다음 편집 줄 포커스 이동으로 처리한다. 임베드 전용 왼쪽 핸들은 전체 블록 공통 정책이 아니므로 제거한다.
2026-05-21 v1.4.1 — 라이브 모드 제목 Enter 처리 보정
제목 블록에서 Enter를 누를 때 일반 블록 아래 삽입 로직만 타면 현재 제목 편집 값이 먼저 저장되지 않거나 다음 포커스가 원문 줄처럼 보이는 상태로 이어질 수 있었다. 제목 Enter는 제목 마크다운 줄과 아래 빈 줄을 한 번에 반영해, 제목은 유지하고 다음 빈 문단으로 자연스럽게 이동하도록 분리했다.
2026-05-21 v1.4.1 — 임베드 저장 형식 단독 URL 통일
단독 URL 한 줄을 자동 임베드로 해석하게 되면서 같은 외부 링크가 https://...와 :::embed fenced block 두 형식으로 새로 저장될 수 있었다. 작성자가 원본 마크다운을 읽을 때 혼선이 생기지 않도록 새 임베드 삽입, 라이브 편집, 레거시 블록 변환의 저장 형식은 단독 URL 한 줄로 통일하고, 기존 :::embed 콘텐츠는 렌더링 호환만 유지한다.
2026-05-21 v1.4.1 — 미디어 업로드 크기 한도 분리
관리자 게시물 미디어 업로드 API가 MAX_FILE_SIZE(기본 10MB) 하나만 쓰고 있어 동영상 업로드가 413으로 실패했다. 아바타·로고 등 이미지 전용 한도는 유지하고, POST /admin/api/uploads만 비디오·오디오·문서별 환경 변수(MAX_VIDEO_FILE_SIZE 등)로 검사하도록 분리했다. 에디터 미디어 모달에는 최대 용량 안내와 413 토스트를 추가했다.
2026-05-20 v1.4.0
미디어 선택과 단독 URL을 작성 흐름에 연결
비디오·오디오·파일 블록을 템플릿만 삽입하면 작성자가 업로드 URL을 직접 복사해야 하므로 이미지/갤러리와 UX가 맞지 않는다. 기존 미디어 모달을 확장해 파일 종류별 선택·업로드 후 fenced block URL과 표시 메타를 자동으로 채우도록 했다. 외부 임베드는 작성자가 :::embed를 기억하지 않아도 되도록 단독 http(s) URL 한 줄을 같은 임베드 블록으로 해석한다.
외부 임베드를 플랫폼별 표시 정책으로 분리
YouTube는 본문 폭 16:9 영상이 자연스럽지만 X/Twitter 공식 iframe은 내부 카드 최대 폭이 고정되어 있어 전체 폭 iframe으로 두면 오른쪽 공백이 커지고 내용이 잘릴 수 있다. X/Twitter와 Mastodon은 소셜 카드로 보고 좁은 폭 중앙 정렬을 적용한다. Mastodon은 인스턴스별 공개 게시물 URL 뒤에 /embed를 붙이는 표준 경로를 우선 사용하고, 공식 embed 스크립트와 같은 postMessage 높이 요청으로 긴 글이 잘리지 않게 한다. 다만 인스턴스 정책에 따라 iframe 표시가 실패할 수 있으므로 링크 fallback 정책은 유지한다.
미디어 Prose 블록을 fenced block 렌더링으로 연결
비디오·오디오·파일은 기존 이미지/갤러리처럼 업로드 URL을 본문 마크다운에 저장하는 방식이 관리와 이식성이 가장 단순하다. 따라서 :::video, :::audio, :::file fenced block을 추가하고, 표시용 메타는 url=, title= 같은 키값으로 둔다. 공개 화면은 카드형 컴포넌트가 렌더링하고, 관리자 에디터는 슬래시 명령으로 기본 템플릿을 삽입해 이후 업로드/선택 UI 확장과 분리한다.
2026-05-20 v1.3.7
NAS 마이그레이션 명령을 npm 없는 호스트 기준으로 보정
NAS 운영 호스트는 Docker Compose만 있고 Node/npm이 없을 수 있다. 운영 DB 마이그레이션은 앱 빌드 도구가 아니라 배포 호스트에서 실행하는 운영 절차이므로, npm run db:migrate:prod:*만 안내하면 실제 NAS에서 막힌다. Docker Compose와 DB 컨테이너의 psql만 사용하는 scripts/migrate-production-db.sh를 추가해 호스트 npm 설치 여부와 무관하게 상태 확인, baseline, 미적용 SQL 실행을 처리하도록 했다.
2026-05-20 v1.3.6
NAS 운영 마이그레이션 적용 이력 도입
운영 NAS에서 몇 번 SQL까지 적용했는지 파일명만으로 추적하면 누락과 중복 실행을 사람이 기억해야 한다. schema_migrations 테이블에 적용 완료 파일을 기록하고, 운영용 db:migrate:prod는 기록이 없는 파일만 실행하도록 했다. 이미 운영 중인 DB에는 과거 적용 기록이 없으므로, 기존 스키마가 감지되면 001부터 자동 실행하지 않고 baseline 기록을 요구해 데이터 변경 SQL의 의도치 않은 재실행을 막는다.
2026-05-20 v1.3.5
관리자 로그인·대시보드 차트·통계 보관 후속
v1.3.4 통계 확장 이후 운영에서 로그인 쿠키·클라이언트 번들·차트 조회 오류가 겹쳐 후속 정리가 필요했다. 세션 쿠키는 공통 유틸로 묶고, 통계 상수는 analytics-shared로 분리했다. 대시보드는 기간별 trends 차트와 접속자 목록 가독성을 맞췄다. 저장 용량은 일별 집계는 누적 원본으로 두고 방문자 해시만 32일 초과 시 정리한다.
2026-05-15 v1.1.18
에디터 미디어 UX·발행일·수정일 표시 설정
이미지 삽입을 미디어/업로드 버튼으로 나누면 워드프레스 대비 단계가 많아지므로, 툴바는 이미지·갤러리만 두고 모달에서 라이브러리(기본)와 업로드 탭을 전환한다. 본문 이미지 너비 옵션은 렌더·운영 복잡도 대비 사용 빈도가 낮아 툴바·블록 편집 UI에서 제거한다(기존 {width=…} 마크다운은 파싱만 유지). 발행일은 관리자 목록에서 시·분까지 보여 주고, 발행 후 수정이 있으면 site_settings.show_post_updated_at이 true일 때만 보조 줄로 수정 시각을 노출해 공개 상세와 동일한 정책을 쓴다.
2026-05-15 v1.1.16
게시물 상태 단순화·초안 첫 저장·발행 글 편집 UX
접근 권한별 공개 범위는 추후 별도 기능으로 두고, 지금은 private를 없애 모두 초안·발행(published_at으로 예약 여부 판별)만 쓴다. 제목 없이 저장이 막히던 문제는 신규에 임시 슬러그와 서버 측 빈 제목 (제목 없음) 보정으로 푼다. 이미 발행된 글에서 사이드바만 바꾸면 툴바가 Publish로 바뀌던 것은 폼 상태만 본 탓이므로, 서버에 반영된 게시 형태를 따로 두어 Update·자동 저장 여부를 맞춘다.
2026-05-15 v1.1.15
초안 서버 자동 저장·이탈 가드·목록 헤더
신규 글도 초안·비공개면 DB 행 없이 입력만 두면 복구가 불가능하므로 기존 글과 동일하게 디바운스 POST로 첫 행을 만든 뒤 편집 URL로 옮긴다. 초안·비공개는 이후에도 자동 저장되므로 내부 이동 시 미저장 확인 모달은 발행·예약처럼 자동 저장이 없는 경우에만 둔다. 디바운스 직후 이탈로 놓치는 한 번의 변경은 라우트 가드에서 타이머를 취소하고 즉시 POST/PUT 플러시로 보완한다. 글 목록은 Ghost류처럼 필터를 «새 글» 왼쪽에 붙여 한 눈에 조작 단위를 모은다.
2026-05-15 v1.1.14
관리자 글쓰기 툴바·저장 정책을 Ghost에 맞춤
로컬 자동 저장 복원은 저장된 DB 초안과 새 글 작성 UX가 충돌한다. 초안은 서버 디바운스 저장으로 Draft/Saving.../Draft - Saved를 맞추고, 발행·예약 글은 자동 저장 없이 Update 한 번에만 반영한다. 툴바는 상태별 Publish·Update·Unpublish·Unschedule과 Published ↗·Scheduled 툴팁으로 의도를 분명히 한다.
2026-05-15 v1.1.13
상단 메뉴 1뎁스·추천 사이트·파비콘 프록시
사이드바 상단 네비는 운영상 루트와 그 직속 자식만이면 충분하고, 그보다 깊은 트리는 편집·표시 모두 부담이 된다. 저장·공개 트리 조립·관리자 드래그에서 한 단계로 막는다. 우측 Recommended는 하드코딩 대신 location=recommended 평면 행으로 두어 메뉴 관리 한 화면에서 다루게 하고, 외부 링크는 호스트만 추출해 Google Favicon CDN URL을 쓰면 별도 스크래핑 없이 아이콘을 얻을 수 있다(내부 경로는 생략).
2026-05-15 v1.1.12
상단 메뉴 편집: 드롭 구역 시각 구분과 계층형 개요 번호
행 위·중·아래만으로는 형제 순서 이동과 부모 변경(하위 편입)이 한눈에 구분되기 어렵고, 평면 행 번호(2,3,4…)는 부모·자식 관계와 맞지 않아 혼란스럽다. 드래그 중에는 파란 끝선·앰버 링과 짧은 한글 캡션으로 의미를 고정하고, 개요 열은 1, 2.1, 2.2처럼 트리 깊이에 맞춘 표기로 바꾸며 라벨 들여쓰기를 키워 구조를 읽기 쉽게 한다.
2026-05-15 v1.1.11
관리자 상단 네비를 평면 드래그 편집으로 통일
하위 전용 추가 버튼과 중첩 테이블은 트리 깊이가 늘수록 조작이 어색하고 공개 사이드바와 다른 시각 계층을 준다. 항목은 모두 상단 메뉴 추가로 만든 뒤, 한 테이블에서 들여쓰기만으로 깊이를 보여 주고, 행의 위·가운데·아래 드롭 구역으로 형제 사이 끼움과 하위 편입을 나누면 Ghost류 아웃라이너와 비슷한 자유도를 유지하면서 UI는 단순해진다.
2026-05-15 v1.1.10
관리자 사이트 설정을 전용 전체 화면으로 분리
공개 블로그 설정은 항목이 늘어날 예정이라 목록형 관리자 레이아웃 안에 두면 세로 공간과 시선 분산이 커진다. Ghost Admin처럼 설정만 별도 전체 화면으로 열고 좌측 앵커 내비와 우측 긴 스크롤을 두면 확장(타임존·공지·가져오기/보내기·스팸)을 같은 패턴으로 쌓을 수 있다. 따라서 /admin/settings에서는 기본 관리자 사이드바를 숨기고, 닫기·ESC와 문서 스크롤 잠금으로 집중도를 맞춘다.
2026-05-15 v1.1.9
추천 글을 저장 필드로 분리
홈 Featured와 목록의 번개 표시는 최신 글 여부가 아니라 운영자가 명시한 추천 상태여야 한다. 기존처럼 첫 번째 글을 추천처럼 보이게 하면 추천 의도와 최신순 정렬이 섞이므로, 게시물에 is_featured 필드를 추가하고 글쓰기 사이드바 토글로 관리하도록 했다. 추천 글이 없으면 홈 Featured 영역도 숨겨 빈 운영 상태에서 불필요한 섹션이 보이지 않게 한다.
관리자 글 목록 필터를 클라이언트 우선으로 도입
현재 관리자 글 목록은 전체 글을 한 번에 조회하는 구조라 상태·태그·정렬 필터를 클라이언트에서 먼저 적용해 변경 범위를 줄였다. 목록 규모가 커지면 같은 필터 기준을 /admin/api/posts 쿼리 파라미터로 옮길 수 있도록 상태 키와 태그 필터 계산을 별도 함수로 분리했다.
2026-05-15 v1.1.8
태그 순서 저장을 드롭 즉시 자동화
메인 태그 정렬은 드래그 자체가 명확한 저장 의도를 가진 조작이므로 별도의 정렬 저장 버튼을 두면 화면의 책임이 나뉘어 보인다. 태그 추가 버튼도 화면 전체 제목 옆에 있으면 메인 태그 추가처럼 보일 수 있어, 새 태그가 기본적으로 일반 태그로 생성되는 현재 구조에 맞춰 일반 태그 섹션 헤더 오른쪽으로 옮겼다. 순서 저장 중에는 추가 드래그를 잠시 막아 서버 순서와 화면 순서가 어긋나지 않게 한다.
2026-05-15 v1.1.7
사이트 로고 파일명을 교체마다 고유하게 저장
사이트 로고 업로드는 미디어 라이브러리에 시스템 폴더 메타로 남지만, 기존 구현은 항상 /uploads/system/logo.webp와 /uploads/system/favicon.png를 덮어썼다. 운영 브라우저와 파비콘 캐시는 같은 URL의 이미지를 오래 보관할 수 있어 파일이 바뀌어도 이전 이미지처럼 보일 수 있다. 따라서 로고와 파비콘은 업로드마다 고유 파일명으로 저장하고 사이트 설정 URL 자체를 갱신한다. 현재 사이트 설정에서 참조 중인 로고·파비콘은 미디어 라이브러리에서 사용 중인 파일로 표시해 실수로 이름을 바꾸거나 삭제하지 못하게 했다.
2026-05-15 v1.1.6
일반 태그도 검색 없이 보이는 관리 화면
메인 태그는 공개 카테고리 노출용이고 일반 태그는 게시물 분류 보조용이므로, 새 태그를 무조건 메인 태그로 올리면 공개 노출 의도가 섞인다. 다만 일반 태그를 검색해야만 볼 수 있으면 방금 만든 태그가 저장되지 않은 것처럼 느껴진다. 따라서 새 태그는 일반 태그 기본값을 유지하되, 관리자 태그 화면에서 일반 태그 전체 목록을 배지 형태로 항상 보여주고 필요할 때 메인 태그로 전환하도록 정리했다. 배지는 최근 사용순을 기본으로 하되 운영 판단에 따라 많이 사용순·이름순으로 바꿀 수 있게 했다.
2026-05-15 v1.1.5
운영 업로드 파일을 런타임 볼륨에서 직접 제공
Nuxt 운영 빌드는 public/을 빌드 시점에 .output/public으로 복사해 정적 파일로 제공한다. 반면 Docker 운영 업로드는 컨테이너 실행 중 /app/public/uploads 볼륨에 기록되므로, 새 파일이 .output/public 스냅샷에 없으면 업로드 직후 이미지가 깨져 보일 수 있다. 업로드 파일은 사용자 콘텐츠이자 런타임 데이터이므로 빌드 산출물에 의존하지 않고 /uploads/** 요청을 public/uploads에서 직접 스트리밍하도록 결정했다.
2026-05-15 v1.1.4
관리자 멤버 썸네일 업로드 경로 분리
회원 프로필 썸네일은 관리자 계정인지 일반 회원인지와 무관하게 회원 자산이므로 /uploads/members/avatars에 저장해야 한다. 관리자 멤버 편집 화면이 공용 게시물 이미지 업로드 API를 사용하면 /uploads/posts에 저장되어 미디어 분류와 썸네일 생명주기 규칙이 어긋난다. 회원 설정 업로드와 관리자 멤버 업로드가 같은 검증·WebP 변환·1:1 크롭 로직을 쓰도록 공통 유틸로 분리하고, 관리자 멤버 화면은 회원 전용 업로드 API를 사용하도록 정리했다.
2026-05-13 v1.1.3
사이드바 행 호버 배경 분리
전역 site-panel-hover는 패널과 텍스트 색을 color-mix해 라이트에서도 호버가 진하게 느껴진다. 카드·태그 목록 등 다른 패널은 기존 대비를 유지하고, 왼쪽 사이드바 네비·카테고리·테마 점만 site-sidebar-nav-row로 분리해 라이트에서 #F7F4EF로 완화했다. 다크에서는 가독성을 위해 기존과 동일한 color-mix 호버를 유지한다.
2026-05-14 v1.1.2
태그 없을 때 “POST” 더미 표시 제거
태그 배열이 비어 있을 때 UI 폴백으로 POST 문자열을 넣어 두어, 사용자는 실제 태그가 붙은 것으로 오해했다. 저장 데이터와 무관한 표시이므로 슬러그가 있을 때만 첫 태그를 노출하고 없으면 태그 영역을 렌더하지 않는다.
2026-05-14 v1.1.1
공개 문단 행간 기본값으로 복귀
문단 글자 크기만 16px(text-base)로 고정하고 행간은 leading-7 대신 Tailwind·브라우저 기본(leading-normal 계열)에 맡긴다.
2026-05-14 v1.1.0
관리자 제목·공개 본문 타이포 마이너 조정
관리자 글 제목 입력은 전체 화면 폼에서 과도하게 커 보일 수 있어 text-3xl로 낮췄다. 공개 본문 문단은 15px·좁은 행간 조합보다 text-base·leading-7로 읽기 리듬을 맞추고, 제목 블록의 고정 mt-12를 제거해 본문 첫 블록과의 간격을 렌더 트리에 맡긴다.
2026-05-14 v1.0.19
수정 모드에서 보이는 hard break 표식
마크다운 표준의 공백 2개 hard break는 렌더링 결과는 맞지만 textarea 수정 모드에서는 공백이 보이지 않아 일반 Enter와 Shift+Enter를 구분할 수 없다. Markdown-first 에디터가 아직 plaintext textarea 기반인 동안에는 작성자가 줄바꿈 의미를 눈으로 확인할 수 있어야 하므로, Shift+Enter는 줄끝 백슬래시 hard break를 삽입하도록 바꾼다. 렌더러는 새 백슬래시 방식과 이전 공백 2개 방식을 모두 지원해 기존 저장 콘텐츠와 호환한다.
2026-05-14 v1.0.18
빈 줄 공백 보존과 미리보기 집중 모드
일반 Enter를 단일 줄 이동으로 유지하더라도, 작성자가 의도적으로 여러 줄을 비우면 미리보기와 공개 본문에서도 그만큼의 공백이 보여야 한다. 빈 줄을 문단 경계로만 소비하면 제목 아래나 문단 사이에 넣은 여백이 모두 사라지므로, 내용 없는 줄은 다시 spacer 블록으로 렌더링해 줄 수 정보를 보존한다. 다만 기본 문단 간격은 10px로 유지해 일반 문단 흐름과 의도적 공백을 분리한다.
미리보기 모드는 작성 도구가 아니라 결과 확인 화면이므로 툴바와 카드형 패널 외곽을 숨긴다. 작성 모드 줄 번호 거터는 본문 바깥 위치 안내로만 쓰고 스크롤바는 숨겨 편집 화면의 시각 잡음을 줄인다.
2026-05-14 v1.0.17
textarea 기준 문단 입력 재조정
일반 Enter를 \\n\\n으로 저장하면 마크다운 문단 구분은 명확하지만, 작성 화면에서는 커서가 두 줄 내려가 보여 긴 글 작성 리듬이 어색해진다. textarea 기반 편집에서는 Affine처럼 일반 Enter를 단일 줄 이동의 새 문단으로 보고, Shift+Enter만 같은 문단 안 hard break로 구분하는 쪽이 더 편하다. 따라서 일반 Enter는 브라우저 기본 입력을 유지하고, Shift+Enter만 마크다운 hard break(공백 2개 + \\n)를 삽입한다. 렌더러는 hard break가 있는 행만 같은 문단으로 묶고, 일반 줄은 각각 10px 간격의 문단으로 렌더링한다.
작성 영역 카드감 축소
본문 textarea에 외곽 보더와 배경 카드가 있으면 관리자 화면의 다른 패널과 겹쳐 편집 영역이 과하게 박스처럼 보인다. 작성 영역의 보더와 배경을 제거하고, 줄 번호 거터는 본문 흐름 바깥 absolute 영역으로 분리한다. 현재 줄 강조 액센트는 제거하고 줄 번호 자체만 위치 안내로 사용한다.
2026-05-14 v1.0.16
문단과 줄바꿈 의미 분리
Markdown-first 에디터에서 빈 줄을 그대로 spacer로 보존하면 세로 간격은 조절할 수 있지만, 글쓰기 경험이 옵시디언·고스트처럼 “문단”과 “줄바꿈”으로 나뉘지 않는다. 운영 글쓰기에서는 일반 Enter를 새 문단, Shift+Enter를 같은 문단 안 줄바꿈으로 보는 Ghost형 규칙이 더 예측 가능하다. 따라서 에디터는 일반 Enter 입력 시 \\n\\n을 넣고, Shift+Enter는 브라우저 기본 단일 줄바꿈을 유지한다. 렌더러는 빈 줄을 별도 spacer가 아니라 문단 경계로만 쓰며, 연속 텍스트 줄은 한 문단으로 묶어 단일 줄바꿈을 <br>로 표시한다. 문단 간 구분감은 개별 spacer가 아니라 문단 하단 24px 간격으로 통일한다.
미리보기 전환 후 커서 복원
Cmd/Ctrl+E로 미리보기를 확인한 뒤 작성 모드로 돌아왔을 때 포커스가 사라지면 긴 글을 이어 쓰는 흐름이 끊긴다. 작성 textarea의 선택 시작·끝 위치와 스크롤 위치를 기억하고, 미리보기에서 돌아올 때 같은 위치로 포커스와 선택 영역을 복원하도록 했다.
2026-05-14 v1.0.15
마크다운 빈 줄 간격 보존
Markdown-first 에디터에서는 작성자가 빈 줄을 넣어 문단 사이 호흡을 직접 조절할 수 있어야 한다. 기존 ContentMarkdownRenderer는 빈 줄을 파싱 단계에서 건너뛰어 1줄과 2줄의 차이가 모두 사라졌다. 빈 줄과 레거시 빈 문단 마커를 spacer 블록으로 렌더링해 공개 본문과 관리자 미리보기에서 작성자가 넣은 세로 간격을 보존한다. 세부 높이는 이후 본문 스타일 QA에서 조정하되, 우선 줄 수 정보가 사라지지 않는 것을 기준으로 한다.
2026-05-14 v1.0.14
Markdown-first 전환 후 레거시 본문 정규화
Markdown-first 에디터로 전환한 뒤에도 브라우저 자동 저장본이나 이전 블록 에디터 상태가 배열·객체 형태로 남아 있으면, 저장 API의 content: string 검증에서 “게시물 입력 형식” 오류가 날 수 있다. 데이터베이스 저장 형식은 마크다운 문자열로 유지하되, 클라이언트 복원 단계와 서버 입력 검증 단계에 공통 정규화 유틸을 두어 레거시 블록 값을 마크다운으로 변환한다. 이렇게 하면 사용자가 기존 자동 저장본을 복원해도 발행 흐름이 끊기지 않고, 이후 페이지 편집 쪽도 같은 기준을 공유한다.
2026-05-14 v1.0.13
Markdown 에디터 붙여넣기와 미디어 편집 보강
textarea 기반 Markdown-first 편집은 범위 선택과 복사/붙여넣기 문제를 줄였지만, 외부 웹 글을 붙여넣을 때 HTML 구조가 일반 텍스트로 무너지거나 이미지·갤러리 마크다운을 직접 고쳐야 하는 불편이 남았다. 우선 브라우저 클립보드의 text/html을 제목·문단·목록·링크·강조·이미지 중심의 마크다운 조각으로 변환하고, 커서가 이미지 또는 갤러리 블록 안에 있을 때 별도 편집 패널을 보여 alt·URL·너비·갤러리 순서를 수정하도록 했다. 작성과 미리보기 전환은 반복 작업이므로 Cmd/Ctrl+E 단축키로 접근하게 하고, 관리자 미리보기는 공개 렌더러를 쓰되 밝은 관리자 패널에 맞는 색상 변수를 별도로 고정한다. 완전한 옵시디언식 토큰 숨김 Live Preview와 표준 마크다운 파서는 더 큰 편집 엔진 선택이 필요하므로 후속 단계로 둔다.
2026-05-13 v1.0.12
Markdown 에디터 줄 번호 거터
옵시디언·CodeMirror는 편집 줄 왼쪽에 줄 번호와 현재 줄 하이라이트를 둔다. 본문 편집이 textarea 단일 호스트로 바뀐 뒤에도 동일한 방향의 안내가 있으면 긴 본문에서 위치 파악이 쉬워진다. CodeMirror 수준의 시각 줄(줄바꿈 wrap) 단위 번호는 별도 미러 레이아웃 없이는 맞추기 어렵고, 우선 \\n 기준 논리 줄 번호와 캐럿이 속한 논리 줄의 거터 셀 배경 강조, 스크롤 동기화만 구현했다.
2026-05-14 v1.0.11
관리자 글쓰기를 Markdown-first로 전환
블록형 에디터는 이미지·갤러리 같은 카드형 입력에는 편하지만, 여러 문단 범위 선택, 외부 블로그/옵시디언 복사·붙여넣기, 다중 블록 복사 같은 기본 글쓰기 동작이 브라우저 텍스트 편집 모델과 계속 충돌했다. 저장 포맷은 이미 마크다운 문자열이므로 본문 편집의 원본도 마크다운 문자열로 되돌리고, textarea 기반 작성 모드와 공개 렌더러 기반 미리보기를 제공한다. 이미지와 갤러리는 기존 업로드·미디어 라이브러리 API를 유지하고 커서 위치에 마크다운으로 삽입한다. 옵시디언식 토큰 숨김 Live Preview는 이 기반이 안정화된 뒤 별도 단계로 확장한다.
2026-05-13 v1.0.10
관리자 블록 에디터를 v1.0.5 파일 기준으로 복원
v1.0.6부터 적용했던 다중 줄 붙여넣기 분할, Cmd/Ctrl+A로 전체 마크다운 복사, 블록 단위 범위 선택·레인 드래그·복사 가로채기 등이 실제 사용에서 어색하다는 피드백이 있어, AdminBlockEditor.vue는 Git 태그 v1.0.5 시점 내용으로 되돌렸다. Docker·부트스트랩 등 v1.0.5 이후 서버/배포 변경은 유지하고 에디터 파일만 이전 동작으로 맞춘다.
2026-05-14 v1.0.5
Docker 런타임 환경 변수 우선
Nuxt runtimeConfig에 process.env.*를 직접 대입하면 Docker 이미지 빌드 시점에 값이 비어 있는 상태로 번들에 들어갈 수 있다. 운영 컨테이너는 .env.production을 런타임에 주입하므로, 서버 전용 비밀값과 DB 연결값은 useRuntimeConfig()보다 process.env를 우선 조회하도록 공통 유틸을 추가했다. 이 기준은 관리자 최초 로그인, 세션 서명, DB 연결, 이메일 OTP 설정에 적용한다.
2026-05-14 v1.0.4
최초 관리자 기준을 owner/admin 존재 여부로 변경
운영 DB에 일반 회원이 먼저 생성되면 기존의 “사용자 0명” 기준 부트스트랩이 더 이상 동작하지 않아 관리자 계정이 없는 잠금 상태가 생길 수 있다. 최초 관리자 필요 여부를 전체 사용자 수가 아니라 owner/admin 권한 보유자 존재 여부로 판단하고, ADMIN_EMAIL과 같은 일반 회원이 이미 있으면 해당 회원을 owner로 승격하면서 ADMIN_PASSWORD 기준 비밀번호 해시를 갱신해 운영 복구 경로를 보장한다. 이미 owner/admin이 있으면 환경 변수 로그인은 우회 권한으로 쓰지 않는다.
2026-05-14 v1.0.3
NAS에서 Postgres init 디렉터리 Permission denied
Docker가 호스트의 db/migrations를 postgres:16-alpine의 /docker-entrypoint-initdb.d에 마운트할 때, NAS 파일 시스템이나 복사 시 기본 umask 때문에 디렉터리가 700·파일이 600만 되면 컨테이너 내부 postgres UID로는 목록을 읽지 못한다. 엔트리포인트가 ls /docker-entrypoint-initdb.d에서 실패하면 DB가 즉시 종료되고 restart: unless-stopped로 루프에 들어간다. 배포 문서에 chmod -R a+rX db/migrations 및 상위 경로 a+x 절차를 명시하고, compose에 주석으로 동일 원인을 남겨 재발 시 빠르게 대응할 수 있게 했다.
2026-05-14 v1.0.1
Docker Compose 전용 네트워크 대역 명시
NAS에서 이미 실행 중인 Docker 서비스가 많으면 Docker가 자동으로 고르는 기본 브리지 네트워크 주소 풀이 기존 네트워크와 겹쳐 Compose 실행이 실패할 수 있다. 기존 네트워크를 정리할 수 없는 운영 환경을 고려해 이 프로젝트 전용 브리지 네트워크와 기본 subnet을 명시하고, 필요 시 .env.production의 DOCKER_SUBNET으로 다른 사설 대역을 지정할 수 있게 했다.
2026-05-14 v1.0.0
운영 환경의 샘플 콘텐츠 fallback 차단
개발 단계에서는 DB 없이도 화면 구조를 확인할 수 있도록 샘플 게시물 fallback이 유용했지만, 운영 환경에서 DATABASE_URL 누락을 샘플 콘텐츠로 숨기면 잘못된 배포를 정상 서비스처럼 보이게 만든다. 따라서 NODE_ENV=production에서는 DB URL이 없으면 즉시 실패하도록 바꾸고, 샘플 콘텐츠 fallback은 개발 환경의 보조 장치로만 남긴다.
회원 세션 비밀값 분리
회원 세션 서명값이 ADMIN_PASSWORD로 fallback되면 관리자 로그인 비밀번호와 회원 쿠키 서명 책임이 섞인다. 운영에서 키 회전과 사고 대응을 분리할 수 있도록 MEMBER_SESSION_SECRET을 필수값으로 두고, 누락 시 명확한 서버 오류를 반환한다.
최소 회귀 검증 스크립트 추가
현재 프로젝트에는 전용 테스트 프레임워크가 없으므로 먼저 적용 가능한 최소 자동 검증으로 JavaScript 문법 점검과 Nuxt 프로덕션 빌드를 묶었다. npm run verify는 이후 단위 테스트나 E2E 테스트가 추가될 때 같은 진입점으로 확장한다.
2026-05-13 v0.0.121
자동 저장 안내를 툴바로 이동
자동 저장본이 있을 때 본문 상단에 배너를 띄우면 제목·본문 입력 흐름을 가리고 시각적으로도 무겁다. 이미 툴바 상태 영역에 자동 저장 시각 안내 문자열을 표시하고 있으므로, 같은 줄에 복원과 로컬 초안 삭제(무시)만 작은 버튼으로 붙이면 기능은 유지하면서 편집 영역 침범을 없앨 수 있다.
2026-05-13 v0.0.120
발행 모달을 Ghost 설정 행 패턴에 맞춤
첫 구현은 설정 제목과 버튼이 항상 펼쳐져 있어 고스트의 gh-publish-setting처럼 “현재 값만 보이다가 클릭 시 옵션 노출” 흐름과 달랐다. 사용자가 제공한 마크업에 맞춰 종이비행기·시계·펼침 화살표 SVG를 그대로 쓰고, 행 단위 접기/펼침으로 요약 표시를 맞췄다. 설정 블록 외곽의 상하 보더는 제거하고 행 사이 구분선만 두어 시각적 잡음을 줄였다.
2026-05-13 v0.0.119
게시물 저장 전 최종 발행 모달 도입
우측 설정 패널의 상태 셀렉트만으로 발행/초안/비공개를 반복 전환하는 흐름은 저장 전 최종 상태를 한눈에 확인하기 어렵고 조작도 번거롭다. 저장 버튼을 눌렀을 때 고스트 스타일의 전체 화면 발행 모달을 열고, 상태(발행/초안/비공개)와 발행 시점(즉시/예약)을 버튼식으로 빠르게 선택한 뒤 최종 확정하도록 정리했다. 뉴스레터 관련 섹션은 현재 기능 범위에 없으므로 제외했다.
2026-05-13 v0.0.119
콜아웃을 옵션 메타 기반으로 확장
콜아웃은 단순 본문만 저장하면 디자인 옵션(이모지 노출 여부, 이모지 종류, 배경 톤)을 유지할 수 없어, 작성 화면과 공개 화면의 결과를 일치시키기 어렵다. 기존 fenced 문법을 유지하면서 선언부 메타(emoji, bg)를 추가해 저장 포맷 변경 범위를 최소화했다. 이 방식은 기존 :::callout 콘텐츠와 호환되며, 이후 색상/아이콘 프리셋이 늘어나도 본문 포맷을 다시 바꾸지 않고 확장할 수 있다.
편집 UI는 카드 내부에 옵션 컨트롤을 넣으면 실제 공개 결과와 작성 화면이 달라 보이므로, 고스트처럼 콜아웃 카드 자체는 결과 형태를 유지하고 설정은 별도 패널로 분리했다.
콜아웃 카드 자체는 테두리 장식이 과하면 본문 흐름에서 떠 보이므로 보더를 제거하고 배경 톤 중심으로 정리했다. 이모지 선택은 정해진 목록만 강제하지 않고 별도 팝업 입력을 함께 제공해 시스템 이모지 입력 흐름을 수용한다.
2026-05-13 v0.0.118
게시글 저장·삭제 액션 강조도 조정
게시글 편집 화면의 저장 버튼은 변경사항이 없을 때도 활성화되어 있어 실제 저장 필요 상태를 구분하기 어렵다. 이미 미저장 변경사항 감지 기준을 가지고 있으므로 같은 기준으로 저장 버튼 활성 상태를 제어한다. 삭제 버튼은 파괴적 액션이지만 항상 빨간색이면 편집 흐름에서 과하게 눈에 띄므로 기본 상태는 중립 톤으로 두고 hover 시에만 위험 색상으로 전환한다. 태그 삭제는 텍스트 x 대신 SVG 닫기 아이콘을 사용해 배지 안 정렬을 안정화한다.
2026-05-13 v0.0.117
갤러리 선택과 순서 편집 흐름 정리
갤러리는 단일 이미지 블록과 달리 여러 이미지를 한 번에 구성하는 블록이므로, 미디어 클릭 즉시 적용하면 선택을 이어갈 수 없고 실수 수정도 번거롭다. 갤러리 미디어 선택은 모달 안에서 복수 선택 상태를 유지한 뒤 확인 시점에 블록에 반영한다. 이미지 개수별 열 수를 1·2·3열로 제한해 빈 칸을 줄이고, 작성자가 시각 흐름을 직접 정할 수 있도록 갤러리 내부 이미지는 드래그로 재정렬한다. 드래그 중에는 이미지 사이 삽입 위치를 선으로 표시해 어느 위치에 들어갈지 명확히 보여준다.
2026-05-13 v0.0.116
게시글 제목 IME 입력과 목록 태그 표시 보정
게시글 제목 입력에서 한글 조합 중 Enter를 본문 포커스 이동으로 함께 처리하면 마지막 조합 글자가 본문 에디터에 들어갈 수 있다. Enter가 IME 조합 확정인지 일반 이동 명령인지 구분해 조합 중에는 본문 이동을 막는다. 게시글 목록 태그는 편집 가능한 입력과 달리 삭제 액션이 필요 없으므로, 태그 관리와 같은 배지 인식성만 유지한 읽기 전용 형태로 표시한다.
2026-05-13 v0.0.115
사이트 설정과 계정 설정 책임 분리
관리자 사이트 설정에 관리자 프로필과 비밀번호 변경을 함께 두면 사이트 메타데이터와 개인 계정 관리가 섞인다. 계정 정보는 멤버 편집 화면에서 처리하고, 사이트 설정은 이름·설명·URL·로고·저작권처럼 공개 사이트 자체에 영향을 주는 값만 남긴다. 공개 사용자 설정은 중앙 컬럼 폭이 좁아 3분할 구조가 답답하므로 요약을 상단에 둔 세로형 흐름으로 바꾸고, 로고는 텍스트 대신 1:1 이미지를 저장해 공개 로고와 파비콘에 함께 사용한다.
2026-05-13 v0.0.114
멤버 계정 작업과 사용자 설정 화면 정리
관리자 하단의 내 프로필은 공개 사용자 설정으로 이동하면 관리자 컨텍스트가 끊기므로, 같은 계정이라도 관리자 멤버 편집 화면으로 보내 계정 관리 흐름을 유지한다. 비밀번호 직접 변경은 이메일 전송 장애 같은 비상 상황을 위한 관리자 전용 보조 수단으로 두고, 일반 사용자 설정에서는 비밀번호 변경과 회원 탈퇴를 상시 노출하지 않고 설정 메뉴의 모달 액션으로 낮췄다. 마지막 로그인은 현재 세션 조회 때마다 갱신하면 의미가 흐려지므로, 로그인 성공 시 기존 last_seen_* 값을 previous_last_seen_*로 옮긴 뒤 현재 로그인만 갱신한다.
2026-05-13 v0.0.113
멤버 필터와 썸네일 편집 방식 정리
멤버 목록은 검색만으로는 “Gmail 사용자 제외”, “비활성 사용자”, “특정 날짜 이후 접속 없음”처럼 운영자가 자주 쓰는 조건을 표현하기 어렵다. 서버 API를 먼저 확장하지 않고 현재 화면의 회원 목록 데이터를 기준으로 클라이언트 조건 필터를 제공해 UI 흐름을 빠르게 검증한다. 멤버 상세의 썸네일은 URL 문자열보다 이미지 자체를 클릭해 등록·변경·제거하는 방식이 더 자연스러우므로, URL 입력 필드는 숨기고 요약 영역의 원형 썸네일 액션으로 책임을 옮긴다.
2026-05-13 v0.0.112
관리자 편집 화면 이탈 확인 공통화
게시글 작성 화면은 로컬 자동 저장이 있지만 서버 저장 전 변경사항을 사용자가 의식하지 못한 채 목록으로 이동할 수 있었다. 멤버 편집 화면도 같은 편집 맥락을 가지므로, 라우트 이탈은 Ghost형 공통 모달로 한 번 확인하고 브라우저 새로고침·탭 닫기는 브라우저 기본 확인에 맡긴다. 게시글에서 이탈을 승인한 경우에는 임시 자동 저장본도 함께 버려 사용자가 명시적으로 떠난 내용을 다음 진입 때 다시 제안하지 않도록 한다.
2026-05-13 v0.0.111
관리자 멤버 상세와 추가 화면 분리
멤버 목록에서 모든 편집 기능을 처리하면 목록의 스캔성이 떨어지고 권한 변경 같은 민감 액션도 너무 쉽게 노출된다. 목록은 관측과 진입에 집중하고, 개별 회원의 이름·이메일·레이블·관리자 노트는 별도 상세 화면에서 저장한다. 레이블은 아직 공개 기능에 쓰지 않지만 이후 사용자별 칭호나 분류로 확장할 수 있도록 배열 컬럼으로 두고, 신규 회원은 활동 이력이 없으므로 활동 섹션을 렌더링하지 않는다.
2026-05-13 v0.0.110
관리자 멤버 목록 정보 밀도 정리
멤버 목록에서 이름, 이메일, 접속일, 권한 변경 컨트롤을 모두 별도 컬럼으로 두면 한 사람의 정보가 가로로 흩어지고 목록이 지나치게 넓어진다. Ghost 관리자처럼 닉네임 아래 이메일, 가입일 아래 최근 활동을 함께 묶어 읽는 구조로 바꾸고, 권한 변경은 사용자를 선택한 뒤 처리하는 후속 화면의 책임으로 분리한다. 뉴스레터 지표는 이 프로젝트에 없으므로 같은 위치에는 댓글 작성 개수를 표시한다.
2026-05-13 v0.0.109
관리자 사이드바 하단 사용자 영역 정리
상단 메뉴 아래에 로그아웃이 바로 붙어 있으면 Ghost형 관리자 내비게이션의 정보 구조와 달라지고, 주요 메뉴와 세션 액션이 같은 레벨로 보인다. 로그아웃은 하단 사용자 썸네일 드롭다운으로 옮기고, 설정은 하단 아이콘으로 배치해 상단 메뉴는 콘텐츠 관리 항목 중심으로 유지한다. 멤버 항목에는 총 멤버 수를 함께 보여 관리자가 현재 규모를 즉시 확인할 수 있게 한다.
2026-05-13 v0.0.108
관리자 캔버스 높이와 사이드바 폭 정리
관리자 개별 페이지가 각자 section 배경과 여백을 책임하면 콘텐츠가 짧은 화면에서 우측 배경이 끊겨 보인다. Ghost 관리자처럼 레이아웃의 우측 캔버스가 기본 화면 높이와 배경을 먼저 책임지게 하고, 사이드바는 320px 고정 폭으로 맞춰 목록·설정 화면의 기준 여백을 더 여유롭게 잡는다.
2026-05-13 v0.0.107
관리자 사이드바 Ghost형 톤 전환
기능 구현이 어느 정도 갖춰진 뒤에도 관리자 첫 화면의 어두운 사이드바는 오래된 CMS 느낌을 강하게 만들었다. Ghost 관리자처럼 밝은 바탕, 낮은 대비의 활성 행, 아이콘+라벨 내비게이션으로 바꾸고, 게시글 행 우측에 새 글 작성 + 버튼을 두어 목록을 거치지 않고 바로 작성으로 들어가게 했다.
2026-05-12 v0.0.105
네비게이션 기본 시드 중복 방지
017_navigation_hierarchy.sql에서 메뉴 계층 지원을 위해 (location,label,url) 유니크 제약을 제거한 뒤, 기존 005_add_navigation_items.sql의 ON CONFLICT DO NOTHING은 더 이상 동일 라벨·URL 기본 메뉴 중복을 막지 못했다. 개발 DB 마이그레이션은 전체 SQL 파일을 반복 실행하므로 기본 메뉴가 새 UUID로 누적될 수 있어, 시드 삽입을 NOT EXISTS 조건으로 바꾸고 019_dedupe_navigation_items.sql에서 기존 중복을 정리한 뒤 표현식 유니크 인덱스로 재발을 막는다.
2026-05-12 v0.0.104
관리자 권한 재검증과 마지막 소유자 보호
관리자 세션 쿠키는 서명과 만료만으로는 권한 변경·회원 탈퇴 이후 상태를 반영하지 못한다. /admin/api/* 요청마다 DB의 현재 owner/admin 권한을 다시 확인하는 서버 미들웨어를 추가해, 권한이 내려가거나 계정이 삭제된 세션은 즉시 차단한다. 회원 탈퇴는 마지막 owner를 없애지 못하도록 막고, 탈퇴 시 관리자 쿠키도 함께 정리한다.
OTP 발송 실패와 초기 owner 판정 안정화
OTP는 메일 발송에 실패했는데 DB 챌린지만 남으면 사용자가 코드를 받지 못한 채 쿨다운에 걸릴 수 있다. 새 챌린지는 먼저 만들되 발송 실패 시 즉시 삭제하고, 발송 성공 후 이전 pending 챌린지를 정리한다. 첫 회원 생성은 동시에 들어온 요청이 모두 owner로 판정되지 않도록 users 테이블 잠금 안에서 처리한다.
2026-05-12 v0.0.103
map.md와 관리자 메뉴 화면 동기화
본문에서 마이그레이션 안내 블록을 제거했으므로 docs/map.md 설명에서도 해당 문구를 뺐다.
2026-05-12 v0.0.102
관리자 블록 에디터 빈 단락·키보드·슬래시 메뉴
Markdown 직렬화에서 연속 빈 줄은 사라져 중간 빈 단락을 유지하기 어렵다. 편집용으로만 <!--sori:blank-paragraph--> 한 줄을 쓰고 공개 렌더러에서 동일하게 빈 단락으로 복원한다. 슬래시 팔레트는 필터 변경 시 하이라이트를 초기화하고, 긴 목록은 스크롤·scrollIntoView로 따라가며, /가 아닐 때는 위아래 키로 블록 간 커서를 옮긴다.
2026-05-12 v0.0.101
공개 primary 네비 트리 중복 방지
parent_id가 있는 행과 루트로 들어온 동일 id가 공존하거나, 평면 목록에 id가 중복되면 한 루프로는 같은 노드가 roots와 부모 children에 동시에 들어갈 수 있다. 서버에서 id 단위로 한 번만 쓰고, 자식으로 연결된 id는 루트 후보에서 빼서 UI 중복을 제거한다.
2026-05-12 v0.0.100
EMAIL_OTP_PEPPER 문서화
운영자가 짧은 숫자만 넣는 오해를 줄이기 위해, pepper는 세션 비밀과 별도로 OTP 해시에만 쓰이는 긴 난수 문자열임을 배포 문서·예시 env에 명시했다.
2026-05-12 v0.0.99
헤더 검색 중앙·Resend 이메일 OTP
헤더는 좌측 브랜드·우측 계정 사이에서 검색을 시각적 중심에 두는 편이 Ghost/Thred류 기대와 맞다. 트랜잭션 메일은 외부 SMTP 대신 Resend 단일 API로 운영 부담을 줄이고, 키가 없을 때는 기존 가입 흐름을 유지한다.
2026-05-12 v0.0.98
사이드바 푸터 링크 줄바꿈·상단 네비 호버 너비
footer 항목이 한 줄 flex로만 두면 좁은 사이드바에서 가로로 삐져나간다. nav에 flex-wrap과 min-w-0 flex-1을 주고 링크는 줄 단위로 감싸지게 했다. 상단 네비 행은 링크가 콘텐츠 너비만 차지하면 site-panel-hover 배경이 짧게 보이므로 w-full로 행 전체와 맞췄다.
2026-05-12 v0.0.97
상단 네비 장식·현재 페이지 표시
부모 행만 점으로 두면 리프와 시각 언어가 갈린다. before 세로바를 통일하고, 내부 활성 경로는 브랜드 악센트로 구분했다.
2026-05-12 v0.0.96
사이드바 상단 네비 폴더 토글 UX
chevron 전용 버튼과 펼침 배경은 원본 Ghost류 UI와 어긋난다. 부모 한 줄을 단일 컨트롤로 두고 chevron·패널 높이에 전환을 줘서 끊김을 줄였다.
2026-05-12 v0.0.95
메뉴 관리 단순화와 드래그 UX
is_visible 체크는 “숨기기” 용도였으나 목록에 두면 모두 표시해야 한다는 운영 기대와 맞지 않아 제거하고 항상 공개로 저장한다. is_folder는 자식 존재로만 서버가 채운다. 드래그는 태그 관리와 같은 행 단위 시각 피드백으로 맞췄다.
2026-05-12 v0.0.94
메뉴 관리 UX와 상단 네비 트리
위치를 셀렉트로 바꾸면 실수로 상·하단을 오가기 쉽고, 인덱스 입력은 모달·레이아웃과 겹칠 때 피드백이 약하다. 미디어처럼 탭으로 영역을 나누고 순서는 드래그로 통일했다. Ghost형 상단 그룹은 parent_id와 공개 트리 API, 사이드바에서 chevron 접기로 맞췄다.
2026-05-12 v0.0.93
관리자 미디어 오류 표시를 토스트로
상세·폴더 모달이 z-50~z-[60]일 때 본문 상단 배너는 모달 뒤에 깔려 사용자가 API 오류(예: 동일 파일명 409)를 볼 수 없었다. useAdminToast로 우측 상단 고정·높은 z-index에 두어 레이아웃과 무관하게 피드백이 보이게 했다.
2026-05-12 v0.0.92
프로필 썸네일 해제와 다운로드
서버는 이미 디스크를 지우지 않지만, 설정 화면이 PUT /api/auth/profile로만 avatarUrl을 비울 때는 메타 분리가 빠져 관리자 목록과 체감이 어긋날 수 있어 DELETE /api/auth/avatar와 같은 removeManagedAvatarAsset 호출을 맞췄다. 관리자 미디어 모달에 다운로드를 넣어 원본 확인을 쉽게 했다.
2026-05-12 v0.0.91
썸네일 미사용 자산과 업로드 파일명
프로필에서 바뀐 옛 썸네일을 바로 디스크에서 지우면 관리자가 누가 올린 자산인지 추적하기 어렵다. 메타만 끊고 파일은 남겨 썸네일 탭에서 정리하도록 바꿨다. 삭제·이름 변경 차단은 avatar_url이 가리키는 경우로 한정했다. 게시물 업로드는 UUID 접미 대신 원본명과 넘버링으로 검색 가능성을 높였다.
2026-05-12 v0.0.90
관리자 미디어 라이브러리와 썸네일 탭 분리
게시물 이미지는 디스크상 posts/에 두더라도 논리 분류는 미분류로 통일해 posts 트리와 이중 표기를 없앤다. 회원 프로필 이미지는 디스크 경로는 유지하되 논리 폴더를 예약명 썸네일로 고정하고, 관리자 화면에서는 탭을 나눠 검색·탐색 대상을 분리했다. 썸네일 파일은 URL이 회원 콘텐츠와 직결되므로 관리자에서 임의 삭제·이름 변경이 되면 프로필이 깨지기 쉬워 API·UI에서 막는다.
2026-05-12 v0.0.89
미디어 선택 토글 가시성
네이티브 체크박스는 배경·브라우저 기본 스타일 때문에 흰 썸네일 위에서 거의 보이지 않는 사례가 있어, 동일 hit 영역을 유지한 채 명암이 큰 커스텀 토글로 바꿨다. 폴더 트리에 나오는 posts·미분류 등은 디스크 경로와 DB 메타 규칙을 문서에 적어 운영자가 혼동하지 않도록 했다.
2026-05-12 v0.0.88
관리자 미디어 선택·폴더 UX
썸네일 전체 클릭이 선택과 미리보기를 겹쳐 쓰기 어렵다. 본문 클릭은 미리보기 모달, 좌측 체크박스만 선택으로 분리했다. 폴더 추가는 사용 빈도가 낮아 상시 입력 대신 모달로 받고, 비어 있지 않은 분류 트리를 위해 폴더 삭제 API와 행 단위 삭제를 추가했다(물리 파일은 건드리지 않고 메타만 미분류로).
2026-05-12 v0.0.87
저장·로그인 버튼 기본 비활성과 글 목록 삭제 아이콘
정렬 저장·메뉴 저장은 변경이 없을 때 연속 클릭이 의미 없으므로, 서버 스냅샷과 비교해 dirty일 때만 활성화한다. 로그인은 빈 제출을 막기 위해 필수 필드가 채워진 뒤에만 버튼을 켠다. 글 목록 삭제는 보조 액션이므로 텍스트 강조 대신 휴지통 아이콘과 낮은 기본 대비, 호버 시 강조로 시선 부담을 줄였다.
2026-05-12 v0.0.86
게시물 URL 로마자화와 태그 표기 분리
게시물 슬러그는 URL 안정성을 위해 한글 음절을 로마자로 바꾸는 기존 방식을 유지한다. 반면 태그는 사용자가 입력한 한글을 그대로 쓰는 경우가 많고, 동일 로마자화를 적용하면 배지·DB name이 기대와 달라지므로 태그 토큰은 한글·영문·숫자와 하이픈만 정리하는 별도 정규화로 분리했다. 저장소에서는 태그 슬러그에 한글이 포함되면 하이픈을 공백으로 바꾼 문자열을 표시명으로 쓰고, 순수 라틴 하이픈 슬러그는 기존처럼 단어별 이니셜 대문자 규칙을 유지한다.
관리자 글 SEO 입력 단순화
공개 상세는 이미 SEO 필드가 비어 있으면 제목·요약을 메타 기본값으로 쓰므로, 관리자 폼에서 별도 SEO 제목·설명 입력을 두면 중복 편집만 늘어난다. 저장 시점에 제목·요약을 seo_title·seo_description에 동기화하고 폼에서는 noindex만 노출해 입력 부담을 줄였다.
태그 관리 피드백을 토스트로
순서 저장 등 성공 메시지를 본문 위에 블록으로 넣으면 레이아웃이 밀려 체감 품질이 떨어지므로, 네비게이션 저장과 동일하게 우측 상단 고정 토스트로 통일했다.
2026-05-11 v0.0.85
의도한 빈 문단 저장 보존
블록 에디터는 마지막 보조 빈 문단을 자동으로 유지하는 구조라서, 저장 시 모든 빈 문단을 제거하면 사용자가 의도적으로 만든 2~3줄 공백도 함께 사라진다. 이를 구분하기 위해 빈 문단 전용 마커를 저장 포맷에 도입하고, 에디터 파서와 공개 렌더러 파서가 동일하게 해석하도록 맞춰 공백 의도를 보존했다.
2026-05-11 v0.0.84
방향키 문단 이동과 슬래시 메뉴 스크롤 고정
관리자 에디터는 블록 단위 편집이므로 일반 텍스트 에디터처럼 위/아래 방향키로 인접 문단으로 자연스럽게 넘어가야 한다. 커서가 블록 경계에 있을 때만 인접 블록으로 이동하도록 보완해 기존 블록 내부 이동과 충돌을 줄였다. 슬래시 메뉴는 명령 수가 많아도 화면을 넘기지 않도록 최대 높이+내부 스크롤로 제한하고, 방향키 하이라이트 항목을 항상 가시 영역으로 자동 스크롤해 선택 맥락을 유지하도록 정리했다.
2026-05-11 v0.0.83
슬래시 메뉴 방향키 상태 유지
슬래시 메뉴 강조 인덱스를 검색어 동기화 때마다 0으로 초기화하면 방향키를 여러 번 눌러도 체감상 1회만 이동하는 것처럼 보이므로, 검색어가 바뀐 경우에만 초기화하도록 분리했다. 또한 슬래시 입력 상태가 아닌 일반 본문 블록에서는 메뉴 방향키 핸들러를 즉시 빠져나오게 해 기본 커서 이동 동작을 최대한 유지하도록 조정했다.
2026-05-11 v0.0.82
메인 태그는 강등, 일반 태그는 검색 삭제
메인 태그는 홈 카테고리 노출 자산이므로 목록에서 실수 삭제보다 일반 태그로 되돌리는 강등 동작이 안전하다고 판단했다. 반대로 일반 태그는 수량이 많아 전체 목록 노출 대신 검색 중심으로 다루고, 삭제도 검색 결과 문맥에서만 허용해 운영 화면의 복잡도를 줄였다. 태그 수정 화면의 정렬/유형 입력은 목록 액션과 역할이 겹치므로 제거했다.
2026-05-11 v0.0.81
태그 입력 IME 안정화와 메인 태그 전환 흐름
관리자 글 작성 태그 입력은 한글 조합 중 Enter 이벤트가 완성 전/완성 후로 중복 처리될 수 있어, 조합 상태에서는 태그 추가를 막고 조합 완료 후에만 확정하도록 정리했다. 또한 게시물 작성에서 자연스럽게 늘어나는 태그는 기본적으로 일반 태그로 생성하고, 카테고리로 노출할 태그만 별도 검색 후 메인 태그로 승격하도록 운영 흐름을 분리했다.
2026-05-11 v0.0.80
태그를 관리용/일반용으로 분리하고 관리용만 정렬
태그 수가 많아질수록 모든 태그에 순번을 강제하면 운영 비용이 커지므로, 홈페이지 카테고리로 쓰는 태그만 managed로 분리해 순서를 관리하고 나머지는 general로 분리했다. 관리용 태그는 드래그 앤 드롭으로 순서를 바꾸고 일괄 저장 API로 반영해 중복 숫자 입력 문제를 제거했다.
공개 GET /api/tags는 관리용 태그만 반환하도록 바꿔, 카테고리 노출 목적과 일반 배지 태그 목적이 섞이지 않게 했다.
2026-05-11 v0.0.79
최초 사용자 관리자 부트스트랩 전환
관리자 계정을 환경변수로 고정하면 실제 운영에서 초기 세팅 흐름이 불명확하고, 관리자 프로필/권한 관리가 회원 데이터와 분리되어 확장성이 떨어진다. 따라서 최초 사용자가 회원가입을 시도하는 시점에 관리자 등록 모드로 안내하고, 첫 계정에 is_admin=true를 부여하는 부트스트랩 방식으로 전환했다. 관리자 로그인도 동일한 users 인증 체계를 사용하도록 맞춰, 관리자/회원 계정 모델을 일원화했다.
관리자 화면에서 썸네일/이름을 바로 수정할 수 있도록, 관리자 로그인 시 회원 세션도 함께 발급해 기존 회원 프로필 API를 재사용하는 방향을 선택했다.
권한은 향후 기능 확장을 위해 owner/admin/member 3단계로 먼저 분리하고, 현재 단계에서는 관리자 멤버 화면에서 권한 값을 변경할 수 있게 준비했다.
2026-05-11 v0.0.79
댓글 아바타/좋아요/상대시간 표시 정렬
댓글 영역은 텍스트 중심 구조만으로는 SNS형 피드백 흐름이 약해 참여 지표를 확인하기 어려웠다. 작성자 썸네일과 좋아요 토글을 기본 액션으로 배치하고, 시간 표기는 최근 24시간 동안 상대 시간으로 보여 즉시성을 높였다. 24시간 이후에는 날짜로 전환해 장기 글에서도 타임라인 문맥을 유지한다.
2026-05-11 v0.0.78
관리자 미디어에서 회원 썸네일 가시성 복구
회원 썸네일을 미디어 목록에서 완전히 제외하면 운영자가 업로드 결과를 확인하거나 정리할 수 없어 관리성이 떨어진다. 경로 분리는 유지하되 관리자 미디어에서는 회원/썸네일 카테고리로 조회되도록 바꿔, 일반 콘텐츠 미디어와 논리적으로 구분하면서도 관리 화면에서 추적 가능하게 했다.
2026-05-11 v0.0.77
회원 설정 썸네일 표시 방식 전환
설정 화면에서 썸네일 URL 문자열을 직접 보여 주는 방식은 실제 사용자에게 의미가 낮고, 업로드 결과를 직관적으로 확인하기 어려웠다. 프로필 카드에서 아바타 미리보기와 이미지 변경/제거 액션을 바로 제공해 확인-수정 흐름을 단순화했다.
2026-05-11 v0.0.76
회원 썸네일 중앙 1:1 강제 크롭
회원이 세로형/가로형 이미지를 올려도 헤더와 설정 화면의 아바타는 동일한 비율이어야 UI가 안정적이므로, 업로드 시점에 중앙 기준으로 1:1 정사각형 크롭을 강제했다. 이렇게 하면 클라이언트별 개별 크롭 로직 없이 서버 저장본 자체가 일관된 아바타 규격을 가진다.
2026-05-11 v0.0.75
회원 썸네일 최소 해상도/설정 방어 강화
회원 썸네일은 너무 작은 원본이 올라오면 헤더/설정 화면에서 품질 저하가 크게 보이므로 최소 해상도 제한을 추가했다. 또한 운영 환경 변수 오입력으로 리사이즈/품질 값이 비정상이어도 업로드가 깨지지 않도록 서버에서 값 범위를 보정(clamp)해 안정성을 높였다.
2026-05-11 v0.0.74
회원 썸네일 업로드 표준화(WebP + 리사이즈)
회원 아바타는 사용자 입력 원본 포맷과 해상도가 제각각이므로, 업로드 시점에 서버에서 WebP로 통일 변환하고 최대 가로/세로를 제한해 저장하기로 했다. 이 방식은 저장 공간과 전송 용량을 줄이고, 너무 큰 원본 이미지 업로드로 인한 렌더링·트래픽 비용 증가를 줄이면서도 기존 URL 분리 정책(/uploads/members/avatars/)을 유지할 수 있다.
2026-05-11 v0.0.73
회원 썸네일 생명주기 정리
회원 아바타는 교체가 빈번해 파일이 누적되기 쉬우므로, 업로드 성공 후 이전 회원 전용 썸네일 자산을 자동 정리하도록 했다. 또한 회원이 직접 썸네일을 제거하거나 탈퇴할 때도 동일한 정리 로직을 재사용해 고아 파일과 불필요한 media_metadata 레코드가 남지 않게 했다.
2026-05-11 v0.0.72
회원 썸네일 미디어 분리
회원 썸네일은 운영자(작가) 콘텐츠 제작용 미디어와 목적이 다르므로, 업로드 경로를 /uploads/members/avatars/YYYY/MM로 분리했다. 관리자 미디어 목록에서는 해당 경로를 숨겨, 작가용 미디어 라이브러리와 회원 프로필 자산이 섞이지 않도록 했다.
2026-05-11 v0.0.71
회원 UX를 헤더 중심으로 전환
기존 헤더는 로그인 여부와 무관하게 Anonymous 메뉴를 고정으로 보여 실제 로그인 상태가 사용자에게 전달되지 않았다. 구독 버튼 대신 우측 아바타만 남기고, 로그인 상태에서는 설정/로그아웃 메뉴를 제공해 계정 액션을 한 위치로 정리했다. 비로그인 상태에서는 기존 Sign up/Sign in을 유지한다.
회원 설정/활동 추적과 관리자 멤버 관측
회원 기능이 들어오면서 운영 관점에서 사용자 정보와 활동 추적이 필요해졌다. users에 avatar_url, last_seen_at, last_seen_ip를 추가하고 로그인/세션조회/댓글작성 시 최근 활동을 갱신한다. 관리자는 /admin/members에서 닉네임, 이메일, 최근 접속, IP, 댓글 수를 확인해 운영 판단을 할 수 있다.
닉네임 유니크 정책
사용자 설정에서 닉네임 변경 시 중복 체크가 필요하므로 DB 레벨에서 lower(username) 유니크 인덱스를 도입했다. 기존 중복 데이터로 마이그레이션이 막히지 않도록, 인덱스 생성 전 중복 닉네임은 -2, -3 접미사를 붙여 자동 정리한 뒤 인덱스를 생성한다.
2026-05-11 v0.0.65
통합 검색 모달과 GET /api/search
헤더 검색은 장식이 아니라 Ghost류 UX로 / 단축키·모달·태그·게시물 섹션 구분이 필요했다. INPUT/TEXTAREA 등에 포커스가 있을 때는 브라우저 입력과 충돌하지 않도록 /를 무시한다. 검색은 저장소 searchPublicContent에 모아 LIKE 대신 position(lower(q) in lower(column))로 부분 일치를 구현해 %·_ 이스케이프 이슈를 줄였다. 저자(author) 검색은 현재 도메인 모델에 없어 제외했다.
2026-05-11 v0.0.63
Tailwind 엔트리 단일화
@nuxtjs/tailwindcss 기본 cssPath는 assets/css/tailwind.css인데 저장소에 해당 파일이 없으면 모듈이 패키지 내 tailwind.css를 nuxt.options.css 앞에 끼워 넣는다. 프로젝트는 이미 main.css에 @tailwind와 커스텀 @layer를 두고 있어 두 엔트리가 겹치면 유틸·레이어 순서가 기대와 달라질 수 있다. tailwindcss.cssPath를 main.css로 고정하고, JIT content에 composables·modules·plugins를 포함해 클래스 수집을 보강했다.
2026-05-11 v0.0.62
인증 폼 다크 스타일이 안 보이던 현상
layout/page.vue의 text-ink는 본문에 전달되지만, 폼 컨트롤은 UA 스타일로 color를 상속하지 않는 경우가 많아 다크 배경에서 입력 글자와 currentColor SVG가 사실상 사라질 수 있다. 전역 .auth-form-input으로 텍스트·캐럿·placeholder·WebKit autofill 글자색을 고정하고, 토글 버튼은 SFC scoped 스타일로 동일하게 맞췄다. color-scheme: dark는 네이티브 컨트롤 테마를 맞추기 위해 섹션에 추가했다.
2026-05-11 v0.0.61
인증 폼 비밀번호 토글 아이콘화
보기/숨기기 텍스트는 좁은 모바일에서 시각적 잡음이 되고 다국어·아이콘 일관성도 떨어져, Material 스타일 단일 경로 SVG(눈 열림·가림)를 공통 컴포넌트로 두었다. aria-label은 필드명(field-name)을 받아 회원가입의 확인 필드와 구분한다.
2026-05-11 v0.0.60
홈 Featured 모바일 스크롤·화살표 상태
가로 오버플로 트랙은 기본적으로 스크롤 가능하지만, 카드 전체가 링크일 때 브라우저가 세로 제스처에 가깝게 해석하거나 체인 스크롤이 나는 경우가 있어 touch-pan-x와 overscroll-x-contain으로 가로 우선·부모 스크롤 전파를 줄였다. 화살표는 스크롤 한계에서 의미 없는 클릭을 막기 위해 scrollLeft와 scrollWidth - clientWidth 비교로 disabled를 두고, 레이아웃 변화에 맞추기 위해 ResizeObserver를 함께 썼다.
2026-05-11 v0.0.59
Nuxt #internal/nuxt/paths Node 해석 오류
Nuxt 3.21과 @nuxt/vite-builder는 SSR 엔트리에서 #internal/nuxt/paths를 롤업 외부 모듈로 남기는데, 동일 경로의 paths.mjs 템플릿은 기본적으로 VFS에만 있어 디스크 파일이 없다. Node는 프로젝트 루트 package.json의 imports로만 서브패스를 해석하므로, 템플릿을 디스크에 쓰도록(write: true) 훅하는 로컬 모듈과 루트 imports 매핑을 추가했다. nitro-server 경로만으로 브리지하면 nitropack/runtime 쪽 내부 specifier가 끌려와 단독 해석이 깨지므로, Nuxt가 생성하는 paths.mjs 본문을 그대로 두는 방식을 택했다.
2026-05-11 v0.0.58
중앙 본문과 우측 사이드 가로 넘침
그리드에 lg:px-5 등 패딩이 있는데 열 정의가 287px + 720px + 287px로 합이 max-width와 맞춰져 있으면, 패딩을 제외한 실제 가용 폭보다 열 합이 커지고 main에 width: 720px가 걸려 있으면 중앙 열이 줄어들지 못해 오른쪽으로 삐져 나간다. 중앙 트랙을 minmax(0,1fr)로 두고 본문은 max-width: 720px로만 제한했으며, 열 사이 column-gap으로 우측 사이드와의 간격을 명시했다. 좌측 메뉴를 접었을 때는 gap이 왼쪽에 빈 여백을 만들지 않도록 끄고, 대신 본문에만 우측 패딩을 준다.
2026-05-11 v0.0.57
사이드바 하단 푸터 여백
Thred 참고 레이아웃에서 본문 블록은 pl-4 등으로 호흡이 있는데, 좌측 사이드 푸터만 px-1로 두어 푸터 링크가 시각적으로 왼쪽 벽에 붙어 보였다. 푸터는 스크롤 영역과 동일한 체감 밀도가 나오도록 px-4 이상으로 올리고, 우측 사이드 카피라이트 줄에도 소폭 pr을 맞춰 패널 경계와의 간격을 통일했다.
2026-05-11 v0.0.56
헤더 좁은 데스크톱 폭에서의 밀집 완화
lg 직후(약 10241280px)에서 검색창이 고정 470px이면 3단 그리드와 헤더 액션이 같은 뷰포트 안에서 경쟁해 아이콘과 버튼이 시각적으로 붙는다. 검색창은 flex-1과 구간별 max-w로 축소 가능하게 하고, 헤더·본문 그리드에 lgxl 수평 패딩을 복구해 Thred형 3열을 유지하면서도 호흡을 확보했다.
2026-05-11 v0.0.55
공개 레이아웃 모바일 분기
lg 미만에서는 3열 그리드 대신 세로 흐름으로 본문을 먼저 보여 주고, 오른쪽 사이드는 본문 아래로 내린다. 왼쪽 내비는 화면 폭을 줄였을 때 본문을 밀어내지 않도록 고정 슬라이드 패널로 띄우고, 백드롭 클릭·Escape·헤더 토글로 닫을 수 있게 했다. 데스크톱에서는 기존처럼 그리드 3열과 스티키 사이드바를 유지한다.
2026-05-11 v0.0.54
공개 인증 화면 가독성과 입력 피드백 보정
회원가입/로그인은 현재 백엔드 인증 연동 전 단계이므로, 사용자가 실제 동작 상태를 오해하지 않도록 화면 피드백을 더 명확히 보여주는 것이 우선이라고 판단했다. 회원가입은 모바일 우선 여백과 카드 패널 레이아웃으로 읽기 흐름을 정리하고, 로그인 화면은 오류 메시지와 안내 메시지를 분리해 의미가 섞이지 않게 했다.
로그인 입력에서는 비밀번호 보기/숨기기 토글을 추가해 모바일 환경에서도 오입력을 줄일 수 있게 했다. 회원가입 2단계와 로그인 화면에는 상호 이동 링크를 보강해 사용자 흐름이 한 화면에서 끊기지 않도록 정리했다.
2026-05-11 v0.0.53
게시물 공유 모달 UI
게시물 상세의 제목 오른쪽 공유 버튼은 단순 아이콘만 두지 않고, 모달에서 공유 미리보기 카드와 채널별 링크를 제공하도록 확장했다. 사용자가 외부 공유 전 게시물 정보(썸네일·제목·요약)를 한번 확인할 수 있고, 링크 복사까지 같은 컨텍스트에서 끝낼 수 있어 Thred 참고 UX와 운영 편의성을 함께 맞출 수 있기 때문이다.
헤더 사용자 메뉴 단순화
헤더 우측은 Account 텍스트 링크 대신 아바타 아이콘 버튼으로 전환하고, 비로그인 기준 드롭다운 메뉴에서 Sign up/Sign in만 제공한다. 다크 모드나 메뉴 열기 토글은 이미 헤더와 사이드바에 노출되어 기능이 중복되므로 사용자 메뉴에서는 제거해 정보 밀도를 낮췄다.
회원가입/로그인 공개 화면 초안
회원가입은 /signup 단일 화면에서 3단계(환영, 정보 입력, 이메일 확인) 플로우로 처리한다. 초기 단계에서는 실제 메일 인프라 연결 전이므로 3단계에서 인증 메일 재전송과 인증 완료 액션을 시뮬레이션하고, 인증 완료 후 로그인 화면으로 이동시키는 흐름을 먼저 고정한다. 로그인 화면(/signin)은 같은 다크 톤 레이아웃으로 맞춰 인증 화면군의 시각 일관성을 유지한다.
회원가입 스텝 인디케이터는 단계별 콘텐츠 높이에 따라 위치가 바뀌지 않도록, 화면 높이를 기준으로 한 min-h 레이아웃의 하단 고정 영역에 둔다. 회원가입 1단계 환영 문구는 하드코딩 대신 사이트 설정 API의 title, description 값을 사용해 추후 블로그 이름/인사말 관리 화면과 자연스럽게 연결한다.
2026-05-08 v0.0.52
목록 Featured 아이콘 정렬과 상세 메타 구분자
홈/태그 목록에서 Featured 아이콘을 제목 텍스트 라인에 단순 inline-flex+음수 마진으로 올리면, 특정 폰트 렌더링에서 라인박스 높이가 달라져 카드 높이가 미묘하게 흔들릴 수 있다. 아이콘을 h-4 w-4 고정 박스로 만들고 items-center로 정렬해 제목 줄 높이를 안정화했다. 게시물 상세의 제목 아래 메타 정보는 원본 스킨처럼 / 구분자를 매번 수동으로 넣지 않고, 래퍼에서 after 규칙으로 일관되게 출력하도록 통일했다.
2026-05-08 v0.0.51
사이드바 고정 높이와 발행일 포맷
lg+ 그리드에서 items-start 때문에 사이드바 박스 높이가 콘텐츠만큼만 잡히면 내부 flex-1 스크롤 영역이 늘어나지 않아 푸터가 상단 블록 바로 아래에 붙는다. 데스크톱에서 열 높이를 h-[calc(100vh-57px)](및 동일 max-h)로 고정해 flex 컬럼 안에서 푸터를 열 하단에 두었다. 공개 피드·상세의 발행일은 formatPostDate로 YYYY.MM.DD를 통일하고 <time datetime>에 원본 ISO를 넣어 접근성을 맞춘다.
2026-05-08 v0.0.50
문서 스크롤과 스티키 사이드바
Thred에 가깝게 본문 길이에 따른 세로 스크롤은 main의 overflow-y가 아니라 뷰포트(문서) 스크롤로 통일한다. 스크롤바가 중앙 열에만 생기는 체감을 피하고 브라우저 기본 스크롤 위치와 맞추기 위함이다. 좌·우 사이드는 position:sticky와 max-height: calc(100vh - 헤더)로 고정감을 유지하고, 사이드 내용이 넘칠 때는 스크롤바를 숨긴 채 내부만 스크롤한다.
2026-05-08 v0.0.49
데스크톱 3단 레이아웃 스크롤 영역 분리
Thred 참고 화면처럼 긴 본문이 있을 때도 좌·우 사이드 하단(푸터)이 뷰포트 기준으로 고정되도록, 그리드 행 높이를 calc(100vh - 헤더)로 제한하고 grid-template-rows: minmax(0,1fr)로 자식이 넘치지 않게 한다. 세로 스크롤은 중앙 main에만 두어 사이드바는 내부 스크롤 영역과 고정 푸터로 나눈다.
2026-05-08 v0.0.48
Twitter/X 공식 embed iframe과 북마크·회원가입 마크다운 블록
공개 본문에서 트위터 게시물은 platform.twitter.com/embed/Tweet.html iframe으로 표시한다. oEmbed API나 스크립트 삽입에 비해 구현이 단순하고 SSR·테마(useThemeMode)와 theme 쿼리만 맞추면 라이트/다크 일관성을 유지하기 쉽기 때문이다.
북마크·뉴스레터 CTA는 Ghost/Thred 스킨에서 흔한 카드 패턴이므로 :::bookmark·:::signup 확장 블록으로 저장하고 전용 Vue 컴포넌트로 렌더링한다. 메타데이터 풍부한 북마크는 추후 oEmbed나 서버 페치로 보강할 수 있도록 마크다운 키값 형식을 병행한다.
2026-05-07 v0.0.45
사용자 화면 단일 배경과 사이드바 전환 방식 결정
사용자 화면 라이트 모드는 전체 배경을 #fcfcfc로 통일하고, 영역 구분은 색상 차이가 아니라 보더로만 처리한다. Thred 참고 화면처럼 배경 톤 편차를 줄이면 카드, 사이드바, 본문이 하나의 캔버스 안에서 정돈되어 보이고 시선이 콘텐츠와 타이포에 더 집중되기 때문이다.
왼쪽 사이드바는 열림/닫힘 시 DOM을 제거하지 않고 너비를 애니메이션으로 줄인다. 사이드바가 즉시 사라지면 레이아웃이 튀어 보이므로, 그리드 컬럼과 사이드바 폭을 함께 전환해 스르륵 접히는 느낌을 유지한다.
왼쪽 네비게이션 항목은 기본 상태에서 회색 세로 바를 보이고 hover/focus 시 원형 아이콘으로 전환한다. 정적 상태에서는 구분선을 제공하고 상호작용 시 클릭 가능 영역을 명확하게 드러내기 위해서다.
2026-05-07 v0.0.44
사용자 화면 테마 상태 저장과 샘플 폴더 제외 결정
사용자 화면 라이트/다크 모드는 시스템 테마 자동 감지에만 의존하지 않고 수동 전환 상태를 localStorage.SITE_THEME에 저장한다. 공개 화면 헤더와 사이드바에서 같은 테마 상태를 공유해야 하며, 다음 방문에서도 사용자가 마지막으로 선택한 테마를 유지해야 하기 때문이다.
테마 색상 적용은 CSS 변수와 html[data-theme] 조합으로 처리한다. 기존 prefers-color-scheme는 기본 fallback으로 유지하되, 사용자가 명시적으로 라이트를 고른 경우 시스템이 다크여도 의도한 화면이 유지되도록 우선순위를 분리한다.
Thred 참고용 샘플인 ZCF-v1.0.5와 보관 폴더는 레퍼런스 자료로만 사용하고 Git 추적 대상에서 제외한다. 대용량 정적 자산과 외부 테마 원본이 변경 이력에 섞이면 실제 서비스 코드 변경 검토가 어려워지기 때문이다.
2026-05-07 v0.0.43
대표 이미지 액션과 선택 확정 흐름 결정
대표 이미지가 이미 설정된 상태의 변경과 삭제 액션은 이미지 아래 별도 영역이 아니라 이미지 hover 오버레이로 표시한다. 실제 공개 화면에서 보일 이미지 비율과 편집용 버튼 영역이 섞이면 작성자가 레이아웃을 잘못 인식할 수 있기 때문이다.
대표 이미지 선택 모달에서는 미디어 클릭 즉시 값을 바꾸지 않고 선택 상태만 표시한 뒤, 하단 대표 이미지로 적용 버튼으로 확정한다. 변경 작업은 실수했을 때 되돌리기보다 확정 전 확인이 더 안전한 흐름이기 때문이다.
2026-05-07 v0.0.42
태그 입력과 대표 이미지 선택 흐름 결정
관리자 글 설정의 태그 입력은 단순 텍스트 필드가 아니라 배지형 입력으로 처리한다. 태그 입력 중 Enter가 폼 제출로 전파되면 의도치 않게 게시물이 저장 또는 발행될 수 있으므로, Enter와 쉼표는 태그 추가 동작으로만 사용한다.
Canonical URL과 OG 이미지는 별도 입력 항목에서 제외한다. 현재 운영 흐름에서는 기본 글 주소와 대표 이미지가 자연스러운 기본값이며, 별도 OG 이미지를 관리하면 글 설정 패널이 불필요하게 길어지고 대표 이미지와 공유 이미지가 어긋날 수 있기 때문이다.
대표 이미지는 업로드 탭과 미디어 라이브러리 탭을 함께 제공한다. 작성자는 새 이미지를 바로 올릴 수도 있고, 이미 업로드한 이미지를 재사용할 수도 있어야 하기 때문이다.
2026-05-07 v0.0.41
명령 메뉴 계층과 개발 도구 표시 결정
관리자 블록 에디터의 / 명령 메뉴가 열린 행은 다른 블록 행보다 위 stacking 순서로 올린다. 메뉴가 절대 위치로 열릴 때 아래 블록의 텍스트가 같은 레이어에 남아 있으면 메뉴 배경 위로 겹쳐 보일 수 있기 때문이다.
블록 이동은 drop 이벤트뿐 아니라 dragend에서도 현재 삽입선 위치를 기준으로 확정한다. 브라우저와 입력 요소 조합에 따라 contenteditable 주변에서 drop 이벤트가 안정적으로 들어오지 않을 수 있으므로, 사용자가 본 삽입선과 실제 결과가 어긋나지 않게 하기 위해서다.
개발 서버의 Nuxt DevTools는 현재 관리자 글쓰기 전체 화면 QA를 방해하므로 기본 비활성화한다. 하단 검은 도킹 패널은 애플리케이션 UI가 아니라 개발 도구 영역이지만, 편집 화면 높이와 스크롤 문제를 확인할 때 혼동을 만들 수 있기 때문이다.
2026-05-07 v0.0.40
글쓰기 스크롤과 드래그 드롭 피드백 결정
관리자 글 작성/수정 화면은 글쓰기 라우트에서 관리자 레이아웃 자체를 화면 높이로 고정하고, 실제 세로 스크롤은 에디터 작업 영역 내부에서만 처리한다. 작성 화면 아래로 문서 전체가 밀리면 전역 다크 배경이 노출될 수 있고, Ghost 스타일 전체 화면 편집 모드의 집중감도 깨지기 때문이다.
블록 드래그 이동은 대상 블록 위/아래 절반을 기준으로 삽입 위치를 계산하고 같은 위치에 삽입선을 표시한다. 사용자가 놓는 순간의 결과를 드롭 전에 알 수 있어야 블록형 에디터의 이동 조작이 안전하게 느껴지기 때문이다.
2026-05-07 v0.0.39
블록 에디터 줄바꿈과 핸들 표시 보정 결정
관리자 블록 에디터의 Shift+Enter는 브라우저 기본 동작에 맡기지 않고 에디터가 줄바꿈 문자를 직접 삽입한 뒤 커서를 줄바꿈 뒤로 복구한다. contenteditable의 기본 줄바꿈은 브라우저별로 <div>, <br>, 텍스트 노드 처리가 달라 Vue 상태 동기화와 충돌할 수 있고, 특히 문단 끝에서 커서가 문단 앞으로 이동하는 현상을 만들 수 있기 때문이다.
블록 핸들은 문자 아이콘 대신 AFFiNE 참고 스타일의 세로 막대로 표시한다. 작성 중에는 시각 소음을 줄이고, hover 또는 선택 상태에서는 막대가 블록 높이만큼 늘어나 사용자가 선택, 삭제, 드래그할 범위를 바로 인식하게 하기 위해서다.
2026-05-07 v0.0.38
에디터 문단 모델과 설정 패널 액션 배치 결정
관리자 블록 에디터에서 Shift+Enter는 같은 블록 안의 줄바꿈으로, Enter는 새 문단 블록 생성으로 구분한다. Ghost와 AFFiNE 계열 에디터처럼 한 문단 안의 강제 줄바꿈과 문단 종료가 달라야 블록 선택, 삭제, 이동 범위가 사용자가 인식하는 문단 단위와 맞기 때문이다.
블록 간격은 각 구조형 블록의 위아래 margin을 섞지 않고 다음 블록의 margin-top 기준으로 정리한다. 이렇게 하면 갤러리, 코드, 토글 같은 구조형 블록을 선택했을 때 선택 범위 바깥으로 불필요한 여백이 잡히는 일을 줄이고, 인접 블록 사이 간격도 한 방향에서 관리할 수 있다.
글 수정 화면의 보기와 삭제 액션은 편집 본문 위에 띄우지 않고 우측 게시물 설정 패널 안에 배치한다. 보기 액션은 Post URL과 직접 관련되므로 Post URL 라벨 오른쪽에 두고, 삭제는 파괴적 액션이므로 설정 패널 하단의 독립 버튼으로 분리한다.
2026-05-07 v0.0.37
블록 에디터 입력 안정성과 블록 핸들 범위 결정
관리자 블록 에디터의 한글 조합 입력은 compositionend 직후 DOM 텍스트가 완전히 반영된 다음 슬래시 메뉴 필터와 Enter 처리를 갱신한다. 조합 직후 별도 Enter guard로 입력을 막으면 글자가 확정된 뒤에도 사용자가 Enter를 한 번 더 눌러야 하므로, 조합 중 이벤트만 막고 조합 종료 후에는 즉시 일반 입력 흐름으로 돌린다.
블록 핸들은 1차로 선택, Delete/Backspace 삭제, 드래그 이동까지 제공한다. AFFiNE식 잘라내기, 복사, 서식 툴바, 블록 단위 컨텍스트 메뉴는 작성 경험 전반에 영향을 주는 큰 기능이므로 기본 블록 조작이 안정화된 뒤 별도 단계로 확장한다.
2026-05-07 v0.0.35
관리자 글쓰기 전체 화면 모드 보정 결정
관리자 글 작성/수정 화면에서는 좌측 관리자 네비게이션과 공통 내부 패딩을 숨기고, 글쓰기 폼이 브라우저 높이를 직접 사용하는 전체 화면 편집 모드로 동작하게 한다. Ghost와 WordPress류 편집 화면은 작성 중 관리자 메뉴보다 글 본문과 설정 패널의 관계가 더 중요하므로, 네비게이션이 보이면 작성 영역을 불필요하게 압축하고 시선을 분산시키기 때문이다.
글쓰기 화면의 1차 레이아웃은 상단 헤더 전체 폭이 아니라 에디터 작업 영역과 우측 설정 패널의 좌우 분할로 둔다. 설정 패널이 열리거나 닫힐 때 에디터 작업 영역의 상단 도구막대와 본문 폭이 함께 변해야 사용자가 현재 편집 가능한 폭 변화를 자연스럽게 인식할 수 있다.
2026-05-07 v0.0.32
관리자 글 작성 화면 구조 정리 결정
관리자 글 작성/수정 화면은 별도 페이지 제목 영역을 제거하고, 글쓰기 폼 자체의 상단 도구막대와 본문 중심 레이아웃으로 정리한다. 기존 “새 글 작성” 제목은 현재 작업 맥락을 반복해 화면 높이만 차지했고, 실제 작성 흐름에서는 제목 입력과 본문 시작 위치를 아래로 밀어 Ghost 스타일 편집감과 멀어졌기 때문이다.
대표 이미지는 설정 패널의 부가 항목이 아니라 글 제목 위의 본문 흐름에서 바로 추가하도록 둔다. 게시물 설정은 420px 우측 패널로 분리하고 토글할 수 있게 해, 설정을 열었을 때는 Figma 설정 패널 상태를 따르고 닫았을 때는 집중형 작성 화면을 유지한다.
2026-05-03 v0.0.31
글 미리보기 저장 방식 결정
글 미리보기는 데이터베이스에 임시 초안 레코드를 만들지 않고 브라우저 저장소를 통해 현재 작성 폼 값을 전달한다. 저장 전 내용 확인이 목적이므로 DB에 미리보기용 글이 쌓이거나 슬러그 충돌을 만드는 일을 피하기 위해서다.
미리보기 화면은 /admin/posts/preview 관리자 경로에 두고, 공개 게시물 상세와 같은 ContentRenderer, ProseHeaderCard, ContentMarkdownRenderer 조합으로 본문을 렌더링한다. 이렇게 하면 저장 전에도 공개 화면에 가까운 결과를 빠르게 확인할 수 있다.
2026-05-03 v0.0.30
OG 이미지 저장 방식 결정
게시물 OG 이미지는 대표 이미지와 별도 필드인 og_image로 저장한다. 대표 이미지는 화면 카드와 본문 진입 시각 요소에 쓰이고, OG 이미지는 외부 공유 미리보기 비율과 목적이 다를 수 있기 때문이다.
관리자 입력은 대표 이미지와 같은 미디어 선택/업로드 흐름을 재사용한다. OG 이미지가 비어 있으면 공개 상세 화면에서는 대표 이미지를 fallback으로 사용해 기존 글도 기본 공유 이미지를 가질 수 있게 한다.
2026-05-03 v0.0.29
게시물 SEO 설정 범위 결정
게시물 SEO 설정은 우선 검색 결과에 직접 영향을 주는 SEO 제목, SEO 설명, canonical URL, robots noindex 값만 다룬다. OG 이미지는 대표 이미지 재사용 여부와 별도 이미지 선택 흐름이 더 필요하므로 이번 단계에서는 기본 OG 제목/설명/URL만 공개 상세 화면에 연결하고, 전용 OG 이미지는 다음 작업으로 남긴다.
SEO 제목과 설명이 비어 있으면 기존 글 제목과 요약을 fallback으로 사용한다. 이렇게 하면 모든 글에 값을 강제로 입력하지 않아도 공개 화면의 기본 메타 품질을 유지할 수 있다.
2026-05-03 v0.0.28
예약 발행 저장 방식 결정
예약 발행은 별도 scheduled 상태를 추가하지 않고 기존 published 상태와 미래 published_at 값을 조합해 처리한다. 현재 데이터베이스의 게시물 상태 제약은 published, draft, private만 허용하고 있으므로 상태값을 늘리기보다 공개 API의 조회 조건으로 발행 시각을 확인하는 편이 변경 범위가 작다.
관리자 목록에서는 미래 발행 시각을 가진 published 게시물을 예약 상태로 표시한다. 공개 목록과 상세 API는 published_at이 비어 있거나 현재 시각 이전인 발행 글만 노출한다.
2026-05-02 v0.0.27
미디어 폴더 트리 관리 방식 결정
미디어 폴더는 워드프레스 플러그인형 폴더 UX처럼 왼쪽 트리에서 만들고 선택하지만, 실제 업로드 파일 경로는 이동하지 않는다. 이미 게시물과 페이지에 저장된 이미지 URL이 깨지는 일을 막기 위해 폴더 이동은 media_metadata.category 값을 경로 문자열로 갱신하는 방식으로 처리한다.
빈 폴더도 남길 수 있어야 하므로 media_folders 테이블을 별도로 둔다. 다만 미디어 사용 여부와 공개 렌더링은 계속 URL 기준으로 판단하며, Ctrl/Command 및 Shift 복수 선택과 드래그 이동은 선택된 URL 목록의 메타데이터만 일괄 변경한다.
2026-05-02 v0.0.26
미디어 카테고리 저장 방식 결정
미디어 카테고리는 실제 파일 경로나 URL을 변경하지 않고 media_metadata 테이블에 URL별 메타데이터로 저장한다. 업로드 파일을 폴더별로 이동하면 이미 게시물이나 페이지에 저장된 이미지 URL이 깨질 수 있기 때문이다.
파일명 변경은 사용 중인 미디어에서 차단되어 있지만, 미사용 파일명을 변경할 때는 기존 URL의 메타데이터도 새 URL로 옮긴다. 삭제 시에는 남은 메타데이터가 쌓이지 않도록 함께 정리한다.
2026-05-02 v0.0.25
빈 문단 placeholder 표시와 네비게이션 관리 범위 결정
블록 에디터의 / 안내 문구는 첫 빈 화면이거나 마지막 보조 입력 블록일 때만 표시한다. 사용자가 중간에 의도적으로 만든 빈 문단에도 같은 안내가 반복되면 작성 중인 여백이 오류처럼 보이고, 실제 내용보다 placeholder가 더 강하게 눈에 들어오기 때문이다.
네비게이션 관리는 1차로 공개 왼쪽 사이드바의 상단 메뉴와 하단 링크를 대상으로 한다. 기존 화면에서 이미 해당 영역이 메뉴 역할을 하고 있으므로 새 UI 영역을 만들기보다 하드코딩된 항목을 navigation_items 테이블로 옮겨 관리자에서 라벨, URL, 위치, 순서, 표시 여부를 조정할 수 있게 한다.
2026-05-02 v0.0.24
빈 줄 입력 보존과 사이트 설정 범위 결정
관리자 블록 에디터는 마지막에 클릭 가능한 빈 문단을 유지하지만, 사용자가 Enter로 만든 연속 빈 문단은 자동 삭제하지 않는다. 글 작성 중 여러 줄을 띄워 생각의 구간을 나누는 행동이 자연스럽고, 보조 입력 블록 정리 로직이 사용자의 입력 의도를 지우면 안 되기 때문이다.
사이트 설정은 우선 단일 site_settings 레코드로 관리한다. 개인 블로그 초기 단계에서는 여러 사이트나 다국어 설정보다 사이트 이름, 설명, 기본 URL, 텍스트 로고, 저작권 문구를 안정적으로 저장하고 공개 화면에 반영하는 흐름이 더 중요하다. 이미지 기반 로고와 프로필 이미지는 미디어 사용처 추적과 연결해야 하므로 이후 미디어 설정 확장 단계에서 다룬다.
2026-05-02 v0.0.23
고정 페이지 관리 구조 결정
고정 페이지 작성과 수정은 게시물과 같은 블록형 에디터를 공유하되, 별도 AdminPageForm으로 분리한다. 페이지는 상태, 요약, 태그, 발행일이 없는 정적 콘텐츠이므로 게시물 폼을 그대로 재사용하면 불필요한 필드와 저장 조건이 섞이기 때문이다.
관리자 경로는 내부 리소스 컬렉션 기준으로 /admin/pages/:id를 사용하고, 공개 보기 경로는 기존 고정 페이지 공개 구조인 /pages/:slug를 유지한다. 페이지는 목록과 태그 흐름에 노출되지 않는 독립 콘텐츠로 다루기 위해서다.
2026-05-02 v0.0.22
글쓰기 하단 빈 블록과 저장 피드백 보정
이미지, 갤러리, 임베드 같은 비텍스트 블록이 글의 마지막에 오더라도 작성자가 이어서 글을 쓸 수 있도록 에디터 마지막에는 항상 빈 문단 블록을 유지한다. 이 빈 문단은 작성 편의를 위한 입력 지점이므로 내용이 없으면 저장 마크다운에는 포함하지 않는다.
한글 조합 입력 직후 Enter는 IME 확정 동작으로 들어오는 경우가 있으므로 즉시 새 블록 생성으로 처리하지 않는다. 조합 확정 Enter와 문단 이동 Enter를 분리해 마지막 글자가 다음 블록에 중복 입력되는 문제를 줄이기 위해서다.
저장 버튼을 눌렀을 때 동작 여부가 보이지 않으면 작성자가 같은 동작을 반복할 수 있으므로, 저장/수정/삭제 진행과 결과는 우측 상단 토스트로 표시한다. 새 글 저장 후 수정 화면으로 이동하는 경우에도 성공 토스트를 이어서 표시한다.
2026-05-02 v0.0.21
글 작성 중 자동 저장 범위 결정
글 작성 중 자동 저장은 1차로 브라우저 localStorage에 보존한다. 저장 버튼을 누르기 전까지 서버에 게시물을 생성하지 않으면, 의도하지 않은 초안이 DB에 쌓이거나 슬러그 충돌이 발생하는 일을 피할 수 있기 때문이다.
자동 저장본은 새 글과 기존 글을 서로 다른 키로 분리한다. 작성 화면에 다시 들어왔을 때는 자동으로 덮어쓰지 않고 복원/삭제 선택지를 보여준다. 명시적인 저장이 성공하면 해당 자동 저장본을 삭제해 저장 완료 후 오래된 내용이 다시 나타나지 않도록 한다.
2026-05-02 v0.0.20
콜아웃, 토글, 임베드 블록 저장 방식 결정
콜아웃, 토글, 임베드는 기존 content 마크다운 문자열 안에 :::callout, :::toggle, :::embed fenced block으로 저장한다. 이미지 갤러리와 같은 확장 문법을 사용하면 DB 스키마를 바꾸지 않고도 관리자 작성 화면과 공개 렌더러를 함께 확장할 수 있기 때문이다.
임베드는 1차로 YouTube URL만 iframe으로 렌더링하고, 그 외 URL은 외부 링크로 표시한다. Twitter 등 외부 서비스별 스크립트 임베드는 SSR 안정성과 개인정보/스크립트 로딩 정책을 검토한 뒤 별도 단계에서 확장한다.
2026-05-02 v0.0.19
블록 에디터 조합 입력과 이미지 캡션 표시 보정
관리자 블록 에디터는 한글처럼 조합 과정이 있는 입력 중에는 마크다운 단축 변환과 슬래시 메뉴 상태 확정을 미룬다. 조합 중인 DOM 텍스트를 Vue 상태로 다시 덮어쓰면 마지막 글자가 중복되거나 입력 순서가 어긋날 수 있기 때문이다.
이미지 삽입 시 파일명이나 미디어 제목을 자동으로 alt/caption에 채우지 않는다. 파일명은 작성자가 공개 본문에서 보려는 설명이 아니므로 기본 화면에서는 숨기고, 필요한 경우 이미지 hover 또는 focus 상태에서만 alt 입력을 열어 직접 작성하도록 한다.
2026-05-02 v0.0.18
공개 URL 복수형/단수형 기준 결정
게시물과 태그의 전체 목록은 컬렉션이므로 /posts, /tags 복수형을 사용한다. 개별 게시물과 특정 태그 상세는 하나의 리소스를 가리키므로 /post/:slug, /tag/:slug 단수형을 기준 경로로 정한다.
기존에 사용하던 /posts/:slug, /tags/:slug는 외부 링크나 기존 이동 흐름이 깨지지 않도록 새 단수형 경로로 리다이렉트한다. 관리자 API와 관리자 화면 경로는 내부 관리 리소스 컬렉션이므로 기존 /admin/posts/:id, /admin/tags/:id를 유지한다.
2026-05-02 v0.0.17
대표 이미지와 미디어 화면 밀도 개선 결정
대표 이미지는 URL을 직접 입력하지 않고 미디어 라이브러리에서 선택하거나 새로 업로드하는 흐름으로 통일한다. 게시물 작성자가 파일 URL을 다루지 않아도 되고, 이미 업로드된 이미지를 재사용할 수 있어야 하기 때문이다. 대표 이미지가 설정되면 썸네일과 삭제/변경 액션을 바로 보여준다.
미디어 화면은 수백 개 이상의 파일이 쌓이는 전제를 기준으로 카드형 목록에서 고밀도 썸네일 갤러리로 바꾼다. 파일 경로, 용량, 사용 현황, 파일명 변경, 삭제 같은 상세 정보는 워드프레스처럼 선택한 이미지의 상세 모달에서 확인하고 처리한다.
2026-05-01 v0.0.16
미디어 사용처 표시와 삭제 보호 결정
미디어 라이브러리에서 파일명 변경과 삭제를 제공하면, 해당 이미지가 글 본문이나 대표 이미지에 사용 중인지 먼저 보여줘야 한다. 현재 콘텐츠는 이미지 URL을 게시물/페이지의 content와 featuredImage에 직접 저장하므로, 사용 중인 파일을 변경하거나 삭제하면 공개 화면의 이미지가 깨진다.
따라서 1차 사용처 추적은 게시물과 페이지를 대상으로 본문, 대표 이미지 위치를 표시한다. 사용 중인 미디어의 파일명 변경과 삭제는 차단하고, 미사용 파일만 정리할 수 있도록 한다. 프로필이나 사이트 설정 이미지는 아직 해당 데이터 모델이 없으므로 설정 기능 구현 시 사용처 추적에 추가한다.
2026-05-01 v0.0.15
미디어 라이브러리 1차 범위 결정
글쓰기 화면에서 이미지를 매번 로컬 업로드만 하는 흐름은 장기적으로 불편하므로, 먼저 업로드된 파일을 다시 선택할 수 있는 미디어 선택 창을 붙인다. 관리자 사이드바에는 미디어 메뉴를 추가하고, 업로드된 이미지 목록, 파일명 변경, 삭제를 1차 기능으로 제공한다.
미디어 데이터는 아직 별도 DB 테이블을 만들지 않고 public/uploads 아래 실제 파일 시스템을 기준으로 읽는다. 카테고리 분류와 이미지 사용처 추적은 파일만으로 안정적으로 관리하기 어렵기 때문에 이후 미디어 메타데이터 테이블을 만들 때 함께 확장한다.
2026-05-01 v0.0.14
이미지와 갤러리 블록 구현 범위 결정
관리자 글쓰기의 이미지 기능은 기존 content 필드를 유지하면서 마크다운 확장 문법으로 저장한다. 단일 이미지는 기본 마크다운 이미지 문법에 {width=wide} 같은 너비 옵션만 붙이고, 갤러리는 :::gallery fenced block 안에 여러 이미지 행을 넣는다. 이렇게 하면 DB 구조를 바꾸지 않고 공개 렌더러와 관리자 에디터를 함께 확장할 수 있다.
이번 단계에서는 게시물 작성 중 새 이미지를 업로드하고 글에 삽입하는 흐름을 먼저 구현한다. 워드프레스처럼 이미 업로드된 미디어를 선택하거나 파일명 변경, 개별 삭제, 카테고리 분류를 관리하는 기능은 별도 미디어 라이브러리 메뉴에서 다룬다. 글쓰기 화면이 이후 미디어 라이브러리를 호출할 수 있도록 업로드 API와 저장 URL 기준을 먼저 고정한다.
2026-05-01 v0.0.13
개발 서버 로그 요약 방식 결정
Nuxt 개발 서버의 기본 로그는 프레임워크 상태와 빌드 이벤트를 자세히 보여주지만, 일상적인 로컬 개발에서는 접속 링크만 빠르게 확인하는 편이 더 효율적이다. 따라서 npm run dev는 프로젝트 전용 래퍼 스크립트로 Nuxt dev 서버를 실행하고, 터미널에는 Localhost, Local IP, Admin, Tailwind Viewer 링크를 요약 출력한다.
오류나 경고에 가까운 로그는 계속 터미널에 남긴다. 개발 서버 실행 자체는 Nuxt CLI를 그대로 사용하되 출력만 정리해, 프레임워크 동작 방식은 바꾸지 않고 로컬 사용성만 개선한다.
2026-05-01 v0.0.12
제목과 본문 입력 흐름 보정
관리자 글쓰기 화면에서 제목은 별도 데이터 필드로 유지하되, 키보드 흐름은 본문 에디터와 이어지도록 한다. 제목 입력 중 Enter를 누르면 폼 제출이 아니라 본문 첫 블록으로 포커스를 이동해 Ghost류 작성 화면처럼 하나의 글쓰기 흐름으로 느껴지게 한다.
관리자 에디터 본문 색상은 공개 화면용 post-prose 전역 색상 변수를 그대로 따르지 않고 관리자 화면의 ink 색상으로 고정한다. 시스템 다크모드 변수와 관리자 흰 배경이 섞이면 실제 입력 텍스트가 placeholder보다 흐리거나 읽기 어려워질 수 있기 때문이다.
2026-05-01 v0.0.11
블록 에디터 키보드 흐름 보정
빈 문단에서 Enter를 누를 때도 다음 빈 문단 블록을 생성하도록 유지한다. 작성 중 의도적으로 여백을 두거나 다음 입력 위치로 이동하는 행동이 자연스러운 글쓰기 흐름이기 때문이다. 저장 시에는 기존처럼 비어 있는 블록을 마크다운 문자열에 포함하지 않는다.
슬래시 메뉴는 입력 포커스를 본문 블록에 둔 채 키보드로 선택한다. /제목 3처럼 필터링한 뒤 Enter를 누르면 현재 강조된 항목을 적용하고, 위/아래 방향키로 강조 항목을 이동한다. 이렇게 하면 메뉴 항목으로 실제 DOM 포커스를 옮기지 않아도 Ghost류 에디터처럼 연속 입력 흐름을 유지할 수 있다.
2026-05-01 v0.0.10
블록 에디터 입력 안정화 결정
관리자 블록 에디터는 contenteditable 요소 안의 텍스트를 Vue 템플릿 보간으로 직접 렌더링하지 않고 DOM 참조를 통해 동기화한다. Vue의 렌더 패치와 브라우저의 조합 입력이 동시에 같은 텍스트 노드를 수정하면 / 입력이나 한글 필터 입력이 중복되는 문제가 생기기 때문이다.
슬래시 메뉴는 고정적으로 아래에 열지 않고 활성 블록 위치와 화면 높이를 기준으로 위 또는 아래에 표시한다. 글 하단에서 블록을 추가할 때 메뉴가 화면 밖으로 밀리는 문제를 줄이기 위해서다.
제목은 별도 폼 영역이 아니라 에디터 상단의 큰 제목 입력으로 유지한다. Ghost 작성 화면처럼 제목과 본문이 하나의 흐름으로 보이는 편이 글쓰기 집중도와 결과 화면 예측에 더 가깝기 때문이다.
2026-05-01 v0.0.9
관리자 블록형 글쓰기 방식 결정
관리자 글 작성은 순수 마크다운 textarea가 아니라 Ghost 스타일에 가까운 블록형 에디터를 기준으로 전환한다. 사용자가 / 명령으로 블록을 선택하고, ## 같은 마크다운 단축 입력을 즉시 제목 블록으로 변환해 작성 화면과 결과 화면의 차이를 줄이기 위해서다.
다만 현재 데이터베이스와 API의 content 필드는 그대로 유지한다. 블록 에디터 내부에서는 문단, 제목, 인용, 목록, 코드, 구분선을 블록으로 다루고 저장 시 마크다운 문자열로 직렬화한다. 이렇게 하면 기존 게시물 저장 구조를 깨지 않으면서도 이후 이미지, 임베드, 콜아웃 같은 Ghost 카드형 블록을 단계적으로 확장할 수 있다.
공개 게시물과 고정 페이지 본문도 같은 마크다운 렌더러를 사용하도록 연결한다. 작성 화면과 보는 화면을 완전히 동일하게 만드는 것은 이미지 업로드와 전체 콘텐츠 컴포넌트 구현 이후 다시 보정하되, 이번 단계에서는 제목, 목록, 인용, 코드 등 기본 블록의 시각 차이를 먼저 줄인다.
2026-05-01 v0.0.8
관리자 마크다운 미리보기 방식 결정
관리자 글 편집의 미리보기는 저장 형식을 바꾸지 않고 textarea 입력 위에 작성/미리보기 탭을 추가하는 방식으로 시작한다. 현재 공개 게시물 렌더링이 아직 완전한 마크다운 파서를 사용하지 않기 때문에, 관리자 미리보기는 기본 문법 확인용으로 제한하고 원본 마크다운 문자열을 그대로 저장한다.
편집 편의 기능은 제목, 굵게, 목록, 인용, 코드 블록 삽입 버튼으로 시작한다. 별도 에디터 패키지는 이미지 업로드와 공개 렌더링 방향이 확정된 뒤 필요성을 다시 판단한다.
2026-05-01 v0.0.7
관리자 글 작성/수정 구조 결정
관리자 글 작성과 수정은 AdminPostForm 단일 컴포넌트를 공유한다. 현재 단계에서는 별도 위지윅 편집기를 도입하지 않고 마크다운 textarea 입력을 먼저 저장 가능한 형태로 연결한다. 글 관리의 핵심 흐름인 생성, 수정, 상태 변경을 먼저 검증한 뒤 미리보기, 자동 저장, 이미지 업로드를 분리해 확장하기 위해서다.
발행/초안/비공개 전환은 별도 publish API가 아니라 게시물 수정 API의 status 값으로 처리한다. 초기 관리자에서는 버튼과 API를 늘리기보다 저장 모델을 단순하게 유지하고, 추후 목록에서 빠른 발행 전환이 필요해질 때 별도 액션 API를 추가한다.
관리자 태그 관리 방식 결정
태그 관리는 목록 화면에서 생성/수정 입력을 인라인으로 열지 않고 생성/수정 전용 페이지로 분리한다. 태그에 표시 순서와 색상 코드가 추가되면서 입력 항목이 늘었고, 목록 행 안에서 수정 폼을 열면 테이블 레이아웃이 흔들리기 때문이다.
태그 삭제 시 게시물 자체는 삭제하지 않고 post_tags 연결만 외래 키 규칙으로 정리한다. 태그는 분류 메타데이터이고 게시물 본문 데이터와 생명주기가 다르기 때문이다.
태그의 sort_order는 공개 화면 카테고리 정렬 기준으로 사용하고, color는 태그 옆 색상 바와 이후 태그 배지 배경색에 사용할 수 있도록 #RRGGBB 문자열로 저장한다.
초기 관리자 인증 방식 결정
관리자 기능 1차 구현은 별도 사용자 테이블을 만들지 않고 ADMIN_EMAIL, ADMIN_PASSWORD 환경 변수로 시작한다. 개인 블로그/CMS 초기 단계에서는 운영 계정 수가 하나이고, 데이터 모델을 먼저 늘리기보다 글 관리 흐름을 빠르게 검증하는 편이 유지보수에 유리하다.
로그인 성공 시 /admin 경로에만 적용되는 httpOnly 세션 쿠키를 설정한다. 세션 토큰은 ADMIN_PASSWORD 기반 HMAC 서명으로 검증해 쿠키 위조를 막고, 운영 단계에서 사용자 테이블이나 더 강한 인증 방식이 필요해지는 시점에 확장한다.
로컬 개발 컨테이너 실행 환경 결정
새 개발 환경에서 Docker Desktop 없이 터미널 중심으로 PostgreSQL 개발 DB를 실행하기 위해 Homebrew, Docker CLI, Docker Compose, Colima 조합을 사용한다. 이 방식은 Docker daemon을 Colima가 담당하고, 프로젝트는 기존 docker-compose.yml을 그대로 활용할 수 있어 NAS Docker 배포 구조와 로컬 개발 구조를 크게 벌리지 않는다.
로컬 개발 DB는 .env.development만 사용하고, Docker Compose 실행 시 ENV_FILE=.env.development와 --env-file .env.development를 함께 넘긴다. 이렇게 하면 Git에 포함되지 않는 로컬 비밀번호를 사용하면서도 운영 기본값인 .env.production 기준은 유지할 수 있다.
개발 DB 마이그레이션은 npm run db:migrate:dev로 실행한다. Docker entrypoint는 새 볼륨 생성 시에만 SQL을 자동 실행하므로, 이미 생성된 개발 DB에도 반복 적용할 수 있는 별도 실행 명령을 둔다.
2026-04-29 v0.0.6
환경 변수 파일 보안 기준 정리
.env.example은 Git에 포함되는 공유 템플릿이므로 실제 개인 이메일, 관리자 비밀번호, DB 비밀번호를 기록하지 않는다. 공유 파일에는 placeholder만 두고, 실제 값은 Git에서 제외되는 .env.development와 .env.production에서만 관리한다.
로컬 개발 환경은 127.0.0.1:43119로 개발 DB에 연결하고, NAS Docker 운영 환경은 sori-studio-db:5432로 운영 DB에 연결한다. 개발 DB와 운영 DB는 비밀번호도 분리해 한쪽 값이 노출되더라도 다른 환경으로 전파되지 않게 한다.
이미 원격 저장소에 올라간 비밀번호가 실제 사용 값이었다면 Git 이력에서 지워도 안전하다고 볼 수 없으므로, 해당 값은 폐기하고 새 랜덤 값으로 교체하는 것을 전제로 한다.
2026-04-29 v0.0.5
PostgreSQL 기반 데이터 계층 결정
DB 관리 도구로 CloudBeaver를 고려하고 NAS Docker 배포를 전제로 하기 때문에 초기 데이터베이스는 PostgreSQL로 잡는다. SQLite보다 운영/개발 분리, 외부 관리 도구 연결, 향후 확장에 유리하다.
Nuxt 서버 API는 바로 DB에 강결합하지 않고 server/repositories를 통해 콘텐츠를 조회한다. DATABASE_URL이 설정된 환경에서는 PostgreSQL을 사용하고, 설정되지 않은 환경에서는 샘플 데이터를 사용해 화면과 API 개발을 계속할 수 있게 했다.
Docker Compose에는 앱과 PostgreSQL 서비스를 함께 두되, 실제 운영 비밀번호와 연결 문자열은 .env.production에서 관리한다. DB 외부 포트는 기존 사용 포트와 겹치지 않도록 43119를 사용한다.
2026-04-29 v0.0.4
메뉴 토글 구현 방식 결정
원본 Ghost 테마는 Alpine 스타일의 @click, :class, :aria-expanded 바인딩으로 좌측 메뉴 상태를 제어한다. 이 프로젝트는 Nuxt/Vue 기반이므로 Alpine을 추가하지 않고 Vue 상태와 composable로 같은 기능을 구현한다.
메뉴 상태는 useMenuState에서 공유하고, 브라우저 localStorage의 MENU_STATE에 저장한다. 이렇게 하면 헤더 버튼, 공개 레이아웃, 게시물 레이아웃이 같은 상태를 사용하면서도 별도 프론트엔드 상태 라이브러리나 Alpine 의존성을 추가하지 않아도 된다.
2026-04-29 v0.0.3
공개 화면 테마와 패널 구조 보정
참고 화면 기준으로 공개 화면은 단순한 흰색 페이지가 아니라 헤더 아래 좌측 사이드바, 중앙 본문, 우측 사이드바가 각각 전체 화면 높이를 차지하는 패널 구조로 정했다. 사이드바 콘텐츠가 적어도 패널 자체는 calc(100vh - 57px) 높이를 유지한다.
색상은 초반부터 라이트/다크 모드 기준을 잡기 위해 CSS 변수로 관리한다. 기본 배경, 패널, 라인, 텍스트, 보조 텍스트, 입력, 강조 버튼 색상을 분리해 이후 디자인 조정 시 Tailwind 클래스 전체를 갈아엎지 않도록 했다.
2026-04-29 v0.0.2
Nuxt 통합 백엔드 구조 결정
초기 세팅은 별도 백엔드 앱을 만들지 않고 Nuxt/Nitro의 server/api를 사용한다. 개인 블로그와 CMS를 한 서버에서 배포하면 로컬 개발, NAS 운영, 환경 변수 관리가 단순해진다. DB 연결과 API 라우팅은 Nuxt 서버 영역에서 시작하고, 추후 독립 배포나 워커가 필요해질 때 백엔드 분리를 재검토한다.
Nuxt 3, Tailwind CSS, Zod를 실제 의존성으로 추가하고 공개 화면, 관리자 화면, 콘텐츠 컴포넌트의 초기 골격을 만들었다. 현재 API는 샘플 데이터 기반이며 다음 단계에서 개발 DB로 교체한다.
기본 포트와 사용 중인 포트 충돌을 피하기 위해 로컬 개발 서버는 43117, NAS Docker 외부 포트는 43118을 사용한다. 컨테이너 내부 포트는 Nuxt 기본 실행 흐름에 맞춰 3000으로 유지한다.
2026-04-29 v0.0.1
초기 제품 방향 결정
sori.studio는 개인 블로그를 중심으로 운영하되, 기존 포털 역할은 블로그 내부 링크와 고정 페이지로 흡수한다. 글이 계속 쌓이는 공간이 핵심이므로 공개 화면은 읽기 경험과 콘텐츠 확장성을 우선한다.
관리자 화면은 Ghost의 검증된 글쓰기 흐름과 마크다운 기반 위지윅 방식을 기준으로 삼는다. 개인용 프로젝트라서 관리자 기능은 과도한 추상화보다 단순한 유지보수성을 우선한다.
공개 화면은 Thred 테마의 3단 레이아웃과 콘텐츠 카드 스타일을 참고한다. 헤더, 좌우 사이드바, 중앙 본문 폭을 먼저 고정해 전체 화면 구조를 빠르게 파악할 수 있게 한다.
기술 스택은 Nuxt SSR, JavaScript, JSDoc, Zod, Tailwind CSS를 기본값으로 결정했다. SEO가 필요한 공개 페이지는 서버 렌더링을 우선하고, TypeScript는 초기 복잡도를 낮추기 위해 사용하지 않는다.
Posts와 Pages는 분리한다. Posts는 목록과 태그에 노출되는 블로그 글이고, Pages는 고정 페이지와 포털성 링크 정리에 사용한다.
로컬 개발 환경과 NAS 운영 환경은 서로 다른 데이터베이스를 사용한다. 개인 블로그라도 운영 데이터가 글과 미디어의 원본이 되므로, 로컬 개발 과정에서 운영 DB를 직접 연결하지 않는 것을 기본 원칙으로 정했다.
원격 저장소는 https://git.sori.studio/zenn/sori.studio.git을 사용한다. Git 작성자 정보는 zenn <zenn.message@gmail.com>으로 통일해 이후 커밋 이력을 한 계정 기준으로 유지한다.
note.md는 원본 의도 반영이 끝난 뒤 삭제한다. 이후 프로젝트 기준 문서는 AGENTS.md와 docs/ 아래 문서만 사용한다.