knob을 mt-0.5 margin 방식 대신 absolute top-0.5 left-0.5로 위치 지정,
트랙에 relative 추가. 위아래가 잘려 보이던 문제 해결, 4개 토글 모두
X봇 다이얼로그와 동일한 비율로 통일.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
translate-x-5.5(유효하지 않은 Tailwind 클래스)로 예정 일정/고정 멤버
토글의 knob이 움직이지 않고 색만 바뀌던 문제 수정. 모든 토글을
translate-x-5(오른쪽=활성화)로 통일.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
예정 일정 자동 생성 시 봇의 고정 멤버(default_member_ids)를 함께
추가하는 옵션(auto_schedule_config.includeDefaultMembers). 워크돌처럼
멤버가 고정인 채널의 다음 주 예정 일정에 멤버가 자동으로 붙음.
DB/라우트 변경 없이 auto_schedule_config JSON에 플래그 추가 +
관리자 다이얼로그에 토글.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
#root의 min-width(1440)를 스크롤 컨테이너(main) 내부 콘텐츠로 옮기고
main에 OverlayScrollbars 적용. 1440 미만으로 줄여도 세로 스크롤바가
뷰포트 끝에 항상 보이고 가로 스크롤이 생김(maplestory 방식). 헤더 고정
모델 유지, 일정 페이지는 기존 내부 스크롤 유지. 공개/관리자 레이아웃 동일 적용.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
캘린더(최다 호출 공개 엔드포인트) getMonthlySchedules를 getOrSet으로
캐시(TTL 60s). 무효화는 모든 쓰기가 수렴하는 3개 meili 동기화 함수
(addOrUpdateSchedule/syncScheduleById/deleteSchedule)에 redis 전달 시
schedule:monthly:* 무효화. 관리자 라우트·봇 경로에서 redis 전달(즉시
반영), festival/누락 경로는 60s TTL로 자동 치유.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
카드가 onClick(schedule)을 호출하도록 변경(기존 호출부는 인자 무시라
호환), 페이지는 useCallback 안정 핸들러를 전달. 매 렌더 새 인라인
함수로 memo가 깨져 필터/스크롤마다 전체 카드가 리렌더되던 문제 해결.
검색 결과 가상화 카드는 범위에서 제외(이미 최적화됨).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
storedSelectedDate가 없을 때 매 렌더 new Date()가 생성돼 다수
useMemo/useEffect가 재실행되고 날짜 스트립이 반복 scrollIntoView되던
문제를, useMemo로 참조를 안정화(값 의미는 동일).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
업스트림 행 시 봇 동기화/요청 핸들러가 무한 대기하던 문제 방지.
fetchWithTimeout(AbortController, 10s)으로 7개 fetch 호출 일괄 래핑.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
깨진 JSON 값 하나가 getAllBots 전체를 throw시켜 startAll(전체 봇 기동)을
막던 위험 제거. weekly_schedule_config/title_filters/default_member_ids/
auto_schedule_config/text_filters를 safeParse(fallback)로 처리.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
무인증 공개 엔드포인트가 봇 설정(채널/계정/필터)과 에러 내부정보
(errorMessage 등)를 그대로 노출하던 것을 상태 요약 필드만 반환하도록
화이트리스트. getBots() await 누락(잠재 버그)도 함께 수정.
관리자 화면은 인증된 /api/admin/bots 사용(영향 없음).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
POST body 스키마/구조분해에 exclude_managed_channels(default true,
컬럼 기본값과 일치)를 추가. 누락으로 INSERT 시 ReferenceError 크래시.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
TikTok은 OG 이미지를 막지만 oEmbed는 thumbnail_url 제공. 단 서명·만료
URL이라 저장하지 않고, /api/schedules/x-card-thumb/:postId 엔드포인트가
요청 시 oEmbed로 현재 썸네일을 받아 302 리다이렉트(Redis 6h 캐시).
resolveCard는 TikTok 카드 이미지를 이 프록시 경로로 설정.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nitter가 주는 pbs.twimg.com/card_img 이미지는 시간이 지나면 404로
만료됨. resolveCard에서 카드 이미지가 없거나 해당 만료성 URL이면
원본 URL의 OG 이미지(i.ytimg 등 안정적)로 대체.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 스크래퍼가 Nitter HTML 엔티티(/ & 등)를 디코딩하지 않아
본문/카드 제목에 [JP/EN]처럼 노출되던 문제 수정 (extractCard,
extractTextFromHtml에 decodeEntities 적용)
- resolveCard가 Nitter 카드에 제목만 있고 이미지가 없을 때 OG로 이미지를
보강하도록 변경 (YouTube 카드 이미지 누락 복구)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
축제 봇 추가/수정 다이얼로그에 1~12월 토글 그리드 + 전체 선택/해제
추가. 신규 봇은 전체 월(항상 실행) 기본. 전체 선택은 active_months
null로 저장.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- botMonths 유틸(parse/serialize) 추가, 빈배열·전체선택은 NULL 정규화
- 축제 봇 라우트 조회/생성/수정에 active_months 반영 (수정 시 봇 재시작으로 즉시 적용)
- 봇별 설정이 이미 분리돼 있어 x/youtube에는 미적용 — DB 컬럼/스케줄러 매퍼에서 제외
- 스케줄러 isActiveMonth 게이트는 범용 유지(미설정 봇은 항상 실행)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
봇 매퍼에 activeMonths 파싱 추가. cron 콜백·즉시 실행 시 현재 KST
월이 활성 월에 포함될 때만 동기화 실행(매 실행 시점 재평가). NULL/
빈배열/12개 전체는 항상 실행. date.js에 monthKST() 헬퍼 추가.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
bot_festival/bot_x/bot_youtube에 active_months JSON 컬럼 추가.
NULL=전체 월(항상 실행), 정수 배열이면 해당 월에만 동기화.
대학 축제 봇처럼 시즌에만 도는 봇의 불필요한 API 호출 절약.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
모바일 일정 페이지에 date_precision='month' 일정을 점선 "N월 중"
카드(UndatedScheduleListCard)로 표시. 선택 날짜와 무관하게 해당
달이면 확정 일정 아래 "날짜 미정" 구분선과 함께 배치.
캘린더/날짜 점은 1일에 찍지 않도록 PC·모바일 dot 목록에서 제외.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
date_precision='month' 일정을 점선 "N월 중" 카드(UndatedScheduleCard)로
표시. 선택 날짜와 무관하게 해당 월이면 확정 일정 아래에 배치하고
사이에 "날짜 미정" 구분선 추가. 카운트에도 포함.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
월만 확정된 일정을 위해 schedules에 date_precision ENUM('day','month')
추가. 기본값 'day'로 기존 일정/쿼리에 영향 없음. month인 경우
date는 해당 월 1일로 저장하고 확정 시 수정에서 정확한 날짜 입력.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
스크롤 방향 감지 자동 숨김이 sticky 바의 transform 전환과
충돌해 리스트 상단에서 위로 스크롤 시 떨림 발생.
항상 보이는 단순 sticky 칩 바로 변경.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 멤버를 화면 폭 전체 화보 카드로 (한글명 + 생일/나이 + 인스타)
- 스티키 스택: 스크롤 시 다음 카드가 이전 카드를 덮으며 흐려짐/축소
- 스크롤 멈추면 가장 가까운 카드를 상단에 자석처럼 스냅
- 마지막 카드도 상단까지 올라오도록 동적 하단 여유 공간
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 일정 리스트 위에 카테고리 필터 칩 추가 (해당 달 전체 카테고리)
- 스크롤 방향 감지 자동 숨김 (내리면 숨고 올리면 보임), 상단 여백 일관
- 카테고리 선택 시 일정 목록 + 날짜 점 필터링 (공개 PC와 store 공유)
- 일정 리스트 카드: 그림자 제거, 1.5px 테두리로 플랫하게
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 골드 그라데이션 단일 히어로로 통합 (타입/학교/제목/날짜/장소/멤버/링크)
- 장소는 히어로에서 클릭 → 지도 다이얼로그(PC 모달/모바일 바텀시트)
- 칩(타입·학교·멤버·링크)을 흰 배경 + 앰버 글자로, 텍스트 그림자 추가
- 모바일: 포스터를 카드와 분리해 크게 표시 + 스크롤 패럴랙스/페이드
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- PC: 검색 모드에서 결과 0개일 때 아무것도 안 나오던 문제 수정
(돋보기 아이콘 + 검색어 표시), 검색 중 로딩 구분
- 모바일: 검색/날짜별 빈 상태를 아이콘 포함 디자인으로 통일
- 빈 상태/로딩을 일정 영역 기준으로 배치
(PC: 상단 30% 지점, 모바일: 중앙)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
트윗의 외부 링크에 대해 미리보기 카드 표시.
- Nitter가 렌더링한 카드 우선 사용 (extractCard)
- Nitter 카드가 비어있으면 본문 URL로 OG 직접 추출 (og.js)
- YouTube/Instagram 등 복구, HTML 엔티티 디코딩 포함
- TikTok 등 봇 차단 사이트는 Nitter 카드로 커버
- schedule_x.card_data 컬럼 + getScheduleDetail 응답에 card 포함
- 가로 레이아웃 카드 (왼쪽 이미지 + 오른쪽 텍스트)
- CardImage: 이미지 로드 실패 시 fallback 아이콘 (인스타 CDN 만료 대비)
- 자체 영상/이미지가 있으면 OG 카드 숨김 (중복 방지)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nitter는 영상 파일을 제공하지 않으므로 썸네일만 추출해 표시하고,
재생 시 원본 트윗으로 이동.
- scraper: extractVideoThumbnails 추가 (amplify_video_thumb 등)
- schedule_x.video_thumbnails 컬럼 + saveTweet 저장
- getScheduleDetail 응답에 videoThumbnails 포함
- PC/모바일 X 상세: 썸네일 + 재생버튼 + 'X에서 재생' 배지
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
같은 리트윗이 타임라인에서 래퍼 id / 원본 id 두 형태로 번갈아 나타나
post_id 중복 체크를 통과해 두 번 저장되던 문제 수정.
리트윗 저장 시 동일 내용 + (원작자 또는 봇계정) username이 이미 있으면
중복으로 간주해 건너뜀. hydration으로 양쪽 모두 전체 내용을 갖추므로
내용 기반 매칭이 안정적으로 동작.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
X long tweet(280자 초과)을 Nitter 타임라인이 간헐적으로 …로 잘라서
주는 경우, 개별 상태 페이지에서 재요청해 전체 내용으로 교체.
- parseTweets: …로 끝나는 트윗에 truncated 플래그 부여
- hydrateTruncatedTweets: 잘린 트윗 status 페이지 재요청 후 교체 (best-effort)
- fetchTweets/fetchAllTweets에 적용
- fetchSingleTweet을 timeout-safe하게 변경
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- logs API: longtext로 저장된 details를 JSON 객체로 파싱해 반환
- 응답 스키마에 additionalProperties: true 추가 (fast-json-stringify가
스키마 미정의 키를 제거하던 문제 해결)
- scheduler 에러 로그: err.cause / err.code / err.causeCode 함께 기록
(fetch failed 등 모호한 메시지의 진짜 원인 식별 가능)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 카테고리 카운트를 선택 날짜와 무관하게 해당 달 전체 기준으로 변경
- 카테고리 섹션이 길어지면 카드 내부 스크롤 (평소엔 콘텐츠 크기 유지)
- 달력 하단에 오늘 날짜로 이동하는 버튼 추가
- 달력 하단 여백 24px → 20px
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>