Freedom AI
실시간 협업 무한 캔버스 툴 — 브라우저만 있으면 바로 시작, 링크 공유로 팀과 함께 그립니다.
🔗 https://freedomai-app.vercel.app/
주요 기능
| 기능 | 설명 |
|---|
| 무한 캔버스 | 마우스 휠·핀치 줌, 패닝으로 끝없이 펼쳐지는 작업 공간 |
| 펜 & 지우개 | 펜 3가지 굵기·5가지 색상 / 3가지 크기 소프트 브러시 지우개 |
| 포스트잇 | 5가지 색상, 이동·리사이즈·텍스트 편집, 글자 크기 4단계, 가장자리 핸들로 화살표 연결, 자석 스냅 |
| 도형 | 사각형, 원, 삼각형, 화살표(3가지 굵기) |
| 이미지 삽입 | Ctrl+V 붙여넣기 또는 드래그 & 드롭으로 이미지 추가·이동·리사이즈, 우클릭으로 앞뒤 순서 조절 |
| 배경 제거 | 이미지 위 가위 버튼 또는 꾹 누르기로 실행. 인물 사진은 AI로, 로고·텍스트 같은 그래픽은 명도 분석으로(흰/검은 배경 자동 판별) 처리, Ctrl+Z로 원본 복구 |
| 선택 툴 | 드래그로 여러 항목 범위 선택 후 한 번에 이동, Backspace/Delete로 일괄 삭제, 선택 박스 모서리로 한꺼번에 크기 조절(비율 유지) |
| 그룹 | 여러 항목을 선택해 그룹으로 묶으면 하나만 클릭해도 전체가 함께 선택·이동 (그룹 해제 가능) |
| 협업 To-Do | 툴바 캘린더에서 날짜를 골라 할 일 작성·체크·삭제, 팀원과 실시간 동기화 |
| 실시간 협업 | Pusher 기반 커서·획·포스트잇 실시간 동기화, 현재 접속자 표시 |
| 팀 초대 & 비공개 방 | 공유 버튼 → 초대 링크 복사(7일 유효), 비공개로 전환하면 초대된 멤버만 입장, 멤버 목록·내보내기 |
| Google 로그인 / 게스트 입장 | Google 계정으로 즉시 입장, 로그아웃 시 계정 초기화 / 로그인 없이 게스트로 체험 (게스트가 만든 방은 마지막 편집 24시간 후 자동 삭제) |
| 프로젝트 사이드바 | 좌측 사이드바에서 프로젝트 전환·추가·삭제 (× 버튼 + 확인 창), 우클릭으로 이름 변경 |
| 휴지통 | 삭제한 프로젝트 목록을 이 브라우저에 보관(최대 20개), 클릭으로 내용 열람, ×로 목록·로컬 사본 삭제 (서버의 방 데이터는 남음) |
| 자유 이동 패널 | 상단 툴바·좌측 사이드바를 드래그로 자유롭게 이동 (PC), 위치 자동 저장 |
| 모바일 최적화 | 터치 드로잉·핀치 줌·이미지 터치 이동/리사이즈, 기종별 safe-area 대응 |
| 데이터 보존 | 방 상태는 서버(Vercel KV)에 저장, 브라우저(localStorage)에도 사본을 남겨 서버가 비어 있을 때 복원 |
| 한/영/일 전환 | UI 언어를 한국어·영어·일본어로 전환 (랜딩·앱 공통, 선택이 유지됨) |
AI 이미지 변환(이미지 더블클릭 → 프롬프트로 스타일 변환)은 UI에 남아 있지만, Hugging Face 무료 프로바이더(hf-inference)가 GPU 이미지 생성을 더 이상 지원하지 않아 현재 동작하지 않습니다.
기술 스택
| 분류 | 기술 |
|---|
| 배포 | Vercel (Serverless Functions) |
| 실시간 | Pusher Channels (Presence Channel + Client Events) |
| 저장소 | Vercel KV (Redis) + localStorage 사본 |
| 인증 | Google Identity Services (GIS) OAuth 팝업 |
| 프론트엔드 | Vanilla JS, Canvas API |
| 백엔드 | Node.js Serverless (/api/*.js) |
| AI 이미지 처리 | Hugging Face, 별도 Express 서버(hf-server/, Render 배포) |
프로젝트 구조
Freedom-AI/
├── api/
│ ├── action.js # 캔버스 액션 처리 (획·지우개·포스트잇·이미지·도형·To-Do)
│ ├── join.js # 방 입장 & 초기 상태 반환
│ ├── room.js # 방 상태 조회
│ ├── rename-room.js # 방(프로젝트) 이름 변경
│ ├── team.js # 팀원 목록·비공개 방 멤버 관리
│ ├── projects.js # 프로젝트 목록 동기화 (Google 토큰 검증)
│ ├── pusher-auth.js # Pusher Presence 채널 인증
│ ├── contact.js # 문의 접수 / 문의 목록 조회 (ADMIN_STATS_KEY)
│ └── stats.js # 가입자/방문 통계 기록·조회
├── lib/
│ └── auth.js # API 공통: Google 토큰 검증, 비공개 방 접근 확인, roomId·관리자 키 검증
├── hf-server/ # 배경 제거·AI 이미지 변환 전용 Express 서버 (Render, HF_TOKEN 필요)
├── tests/ # node:test 기반 API 테스트 (인메모리 KV·Pusher 가짜 구현)
├── admin.html # 통계·문의 조회 페이지 (ADMIN_STATS_KEY 필요)
├── app.html # 캔버스 앱 (메인)
├── index.html # 랜딩 페이지
├── fonts.css # 폰트 정의 (두 페이지 공용) — 폰트를 바꾸려면 여기부터
├── vercel.json # Vercel 배포 설정 (보안 헤더·캐시)
├── .vercelignore # 배포에서 제외 (테스트·서버 코드·README가 공개 URL로 새지 않게)
└── package.json
로컬 개발
1. 설치
npm install
2. 환경변수 설정
.env 파일 생성:
PUSHER_APP_ID=your_app_id
PUSHER_KEY=your_key
PUSHER_SECRET=your_secret
PUSHER_CLUSTER=your_cluster
# 선택 — 없으면 방 상태가 서버에 저장되지 않고(브라우저 사본만 남음),
# 팀원 관리·방 이름 변경은 동작하지 않음
KV_REST_API_URL=your_kv_url
KV_REST_API_TOKEN=your_kv_token
# 선택 — /admin.html 통계·문의 조회 키 (없으면 그 페이지만 못 쓰고 캔버스는 정상 동작)
ADMIN_STATS_KEY=추측하기_어려운_긴_무작위_문자열
# 선택 — 본인 Google OAuth 클라이언트를 쓸 때만. 없으면 app.html에 하드코딩된 기본값을 쓴다.
# app.html의 GOOGLE_CLIENT_ID와 반드시 같아야 하며, 다르면 비공개 방 인증이 전부 실패한다.
GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
3. 서버 실행
npm run dev # 또는 npx vercel dev (/api/* 서버리스 함수까지 실행, .env 사용)
npm run dev 또는 npm start를 실행하면 /api/* 서버리스 함수와 프론트엔드가 함께 실행됩니다.
4. 테스트
npm test
api/*.js의 핵심 동작(획·노트·이미지·도형 삭제/이동, 지우개 결과, To-Do, 게스트 방
만료, 비공개 방 멤버 검증, 방 입장·조회, 프로젝트 이름 변경, 일괄 이동·그룹, 관리자
통계, 보안 회귀 — roomId 키 충돌·KV 실패·락·이름 변경 경합·소유권 백필·다른 앱 토큰 거부 등)을
실제 KV·Pusher 없이 인메모리 가짜 구현으로 검증한다 (tests/,
Node 내장 테스트 러너 사용, 별도 설치 불필요 — @vercel/kv·pusher는 npm install로
이미 받는 의존성을 그대로 재사용). 소유권 정책처럼 한 번 조용히 깨졌던 적 있는 동작은
회귀 테스트로 고정해뒀다.
Vercel 배포
환경변수
| 변수명 | 필수 | 설명 |
|---|
PUSHER_APP_ID | ✅ | Pusher 앱 ID |
PUSHER_KEY | ✅ | Pusher 키 |
PUSHER_SECRET | ✅ | Pusher 시크릿 |
PUSHER_CLUSTER | ✅ | Pusher 클러스터 (예: ap3) |
KV_REST_API_URL | 선택 | Vercel KV URL (없으면 서버 저장·팀원 관리·이름 변경 불가) |
KV_REST_API_TOKEN | 선택 | Vercel KV 토큰 |
ADMIN_STATS_KEY | 선택 | /admin.html 통계·문의 조회 키 (없으면 그 페이지만 못 쓴다 — 캔버스는 정상) |
GOOGLE_CLIENT_ID | 선택 | 본인 OAuth 클라이언트를 쓸 때만. app.html의 GOOGLE_CLIENT_ID와 같아야 한다 (쉼표로 여러 개 가능) |
Pusher 설정
- Pusher 대시보드 App Settings → Enable client events 활성화 필수 (커서·그리는 중인 획 공유에 사용)
Google OAuth 설정
Google 클라이언트 ID는 app.html의 GOOGLE_CLIENT_ID에 하드코딩돼 있고, 서버는 토큰이 이
클라이언트로 발급된 것인지 확인한다(lib/auth.js). 본인 OAuth 클라이언트를 쓰려면 app.html
값을 바꾸고 Vercel 환경변수 GOOGLE_CLIENT_ID에도 같은 값을 넣은 뒤, Google Cloud Console → 사용자 인증 정보 →
Authorized JavaScript origins 에 배포 URL 추가:
https://your-domain.vercel.app
AI 서버 (배경 제거)
hf-server/를 Render에 배포하고(render.yaml 포함) 환경변수 HF_TOKEN(Hugging Face
토큰)을 설정한 뒤, app.html의 HF_SERVER_BASE를 배포 주소로 바꾼다. AI 서버는 허용된
출처(ALLOWED_ORIGINS, 기본값 https://freedomai-app.vercel.app과 localhost)에서 온 요청만
받고, IP당 분당 5회·서버 전체 분당 60회(GLOBAL_RATE_LIMIT)로 Hugging Face 호출을 제한한다.
다른 주소로 배포하면 ALLOWED_ORIGINS에 그 주소를 넣는다.
단축키 (PC)
| 키 | 기능 |
|---|
P | 펜 도구 |
E | 지우개 |
S | 포스트잇 |
R | 도형 |
H | 이동(핸드) 도구 |
V | 선택 툴 |
Space (누르고 있기) | 임시 이동(패닝) |
Backspace / Delete | 선택 항목 삭제 (선택 툴) |
Ctrl/Cmd + G | 선택 항목 그룹으로 묶기 (Shift를 같이 누르면 그룹 해제) |
Ctrl/Cmd + Z | 실행 취소 — 한 순서대로 되돌림 (내 획, 삭제한 노트·이미지·도형·획, 배경 제거·AI 편집, 한꺼번에 지운 항목은 한 번에), 최대 100단계 |
Ctrl/Cmd + V | 이미지 붙여넣기 |
+ / - | 줌 인/아웃 (마우스 휠도 가능) |
0 | 화면 위치·확대 초기화 |
모바일 사용법
| 동작 | 기능 |
|---|
| 한 손가락 드래그 | 현재 툴 동작 (그리기·이동·범위 선택 등). 선택 툴에서 항목을 누르고 끌면 그 항목(그룹이면 그룹 전체)이 함께 이동 |
| 한 번 탭 (포스트잇 툴) | 포스트잇 추가 |
| 두 손가락 핀치 | 줌 인/아웃 |
| ☰ 버튼 | 프로젝트 사이드바 열기/닫기 (좁은 화면·로그인 사용자만) |
| 사이드바 외부 터치 | 사이드바 닫기 |
| 이미지 드래그 | 이미지 이동 (핸드 툴에서는 캔버스 이동) |
| 이미지 우하단 핸들 드래그 | 이미지 리사이즈 (비율 유지) |
| 이미지 꾹 누르기 | 배경 제거 |
주요 정책
- 룸당 최대 획 1,000개 (획당 점 5,000개)
- 포스트잇: 룸당 최대 1,000개, 텍스트 최대 10,000자 / 너비 100
3,000px, 높이 803,000px / 글자 크기 10~32px
- 이미지: 룸당 최대 20개, 붙여넣기·드롭 시 최대 1,200px로 자동 압축 (JPEG 82%, 투명 배경은 흰색으로)
- 도형: 룸당 최대 300개
- To-Do: 날짜당 최대 50개, 항목당 200자
- 프로젝트 이름 최대 32자, 사이드바 프로젝트 목록 최대 30개
- 비공개 전환·프로젝트 이름 변경: 방 주인(로그인 사용자)만 가능. 주인은 방이 만들어질 때 기록되고, 게스트가 만든 방처럼 주인이 비어 있던 방은 로그인 사용자가 처음 작업하는 시점에 그 사람으로 기록된다 (그 이전에 만들어져 아직 아무 로그인 사용자도 쓰지 않은 방은 예전처럼 누구나)
- 지우개: 클라이언트에서 직접 계산 후 결과 전송 (KV 없이도 동작)
- 삭제·이동(포스트잇/이미지/도형/획): 방에 입장할 수 있는 사람이면 작성자 구분 없이 가능 (지우개와 동일 정책)
- 실행 취소(Ctrl+Z): 내가 이 탭에서 한 작업을 한 순서대로 최대 100단계 되돌린다 (삭제한 포스트잇·이미지·도형·획, 배경 제거, 일괄 삭제 포함). 다른 사람이 이미 바꿔놓은 항목은 건너뛴다
- 게스트가 만든 방: 마지막 편집 24시간 후 자동 삭제. 로그인 사용자가 그 방에서 한 번이라도 작업하면 만료가 해제된다
- 데이터: 방 상태는 Vercel KV에 저장, 브라우저 localStorage에도 사본 저장 (서버가 비어 있을 때 복원)
- 패널 위치(툴바·사이드바): PC에서 드래그 이동, localStorage에 저장
로드맵
완료 ✅
- 무한 캔버스, 펜·소프트 지우개, 포스트잇 (화살표 연결·자석 스냅)
- 이미지 삽입 (붙여넣기·드래그 드롭·이동·리사이즈·삭제·앞뒤 순서)
- 선택 툴 (범위 드래그 → 다중 항목 일괄 이동·삭제·크기 조절)
- 그룹 묶기 / 해제
- Pusher 실시간 협업 (커서·획·포스트잇 동기화)
- Google 로그인 / 로그아웃 (매번 계정 선택 강제)
- 게스트 입장
- 팀 초대 링크 & 비공개 방
- 프로젝트 사이드바 (전환·추가·삭제·이름 변경·휴지통)
- 상단 툴바·좌측 사이드바 자유 이동 (PC, 위치 저장)
- 모바일 터치 최적화 (핀치 줌, 터치 드로잉, 이미지 터치, safe-area 대응)
- Vercel 서버리스 배포
- 도형 도구 (사각형, 원, 삼각형, 화살표)
- 배경 제거 (인물 AI + 그래픽 명도 분석)
- 캘린더 기반 협업 To-Do
- 한/영/일 UI 전환
예정 🔜
변경 이력
변경할 때마다 이 목록에 새 날짜로 추가합니다 (최신순). 2026-09-13 이전 이력은
git log로 확인할 수 있습니다.
2026-10-15
저장소 정리 — 쓰지 않는 파일을 GitHub에서 제외 (로컬에는 그대로 둠)
- 디자인 원본(
*.af)·로고 사본·스크린샷 6개를 저장소에서 빼고 .gitignore에 추가
- 현재 앱과 연동되지 않던 구버전 WebSocket 서버(
server.js)와 그것만 쓰던 ws 패키지·server:legacy 스크립트 제거
전체 점검 1단계 — 그린 획 유실·좌표 검증·서버 코드 노출
- 방금 그린 획이 사라지던 문제 — 획과 포스트잇을 연달아 만들면 한쪽이 방 잠금을 못 잡고 503(busy)을 받았고, 앱은 이를 실패로 보고 서버 상태로 덮어써 획이 사라졌다(라이브에서 재현). 서버는 응답을 보내기 전에 잠금을 풀고, 앱은 busy를 받으면 잠시 뒤 최대 3번 다시 보낸다
- 이동·화살표 수정에서 Infinity·NaN이 저장되던 문제 — 추가(add)만 검사하고 이동은
typeof만 봐서 항목 위치가 깨질 수 있었고, 크기를 문자열로 보내면 NaN이 저장됐다. 모두 유한한 숫자만 받도록 통일
- 서버 공용 코드(
lib/auth.js)가 공개 URL로 열려 있던 것을 막음 (/lib/* 접근 시 첫 화면으로 이동)
전체 점검 2단계 — 앱 동작 버그
- 이미지 내보내기에서 포스트잇 글자가 10px 기본 폰트로 나오던 문제 — 캔버스 글꼴에 CSS 변수(
var(--font-body))를 넣어 값 전체가 무시됐음. 실제 폰트 이름을 읽어 넣음
- 휴지통에서 "내용 보기"를 하면 그 프로젝트가 목록에도 다시 생기던 문제 — 휴지통에서 들어온 경우엔 최근 프로젝트에 기록하지 않음
- 휴지통
× 확인 문구를 실제 동작에 맞게 수정 — 이 브라우저의 사본만 지우고 서버 데이터는 남는데 "되돌릴 수 없다"고만 안내했음
- 이름 변경 직후 옛 이름의 브라우저 사본이 되살아나던 문제 — 새로고침 직전 저장이 옛 이름으로 일어났음. 나중에 같은 이름으로 새 프로젝트를 만들면 예전 내용이 복원됐다
- 모바일에서 선택한 획을 끌어 옮길 수 없던 문제 — 터치하면 선택이 풀렸음. 마우스처럼 선택된 획 위를 누르면 바로 이동
전체 점검 3단계 — 남은 안정성·표시 문제
- 동시에 초대 링크를 쓰면 한 명이 멤버에서 빠지던 문제 — 멤버 목록을 락 없이 고쳐 마지막 쓰기만 남았음. 멤버 목록에 락을 걸고, 팀 API의 저장소 오류는 JSON 오류로 응답
- 저장소가 막힌 브라우저에서 앱이 멈추던 문제 — 이때는 이 탭에서만 쓰는 메모리 저장소로 대신하고, 손상된 저장 값은 기본값으로 읽음. 이름 변경 중 저장 용량 초과로 화면이 멈추던 것도 수정
- 다른 사람이 배경을 제거한 이미지에 새로고침 전까지 사각 테두리가 남던 문제
- 입장 알림에서 이름의 특수문자가
&처럼 깨져 보이던 문제
- AI 변환의 "모델 불러오는 중" 안내가 영어·일본어 화면에서도 한국어로 나오던 문제
- 문의 폼 실패 안내를 원인별(이메일 오류·입력 오류·일시 중단)로 구분
fonts.css 캐시를 7일에서 1시간으로 줄임 — 고쳐도 재방문자에게 최대 7일간 반영되지 않았음
- 화살표 굵기를 접속할 때마다 "얇게"(3px)로 시작 (기존 기본값은 "보통" 6px)
- 사이드바 우클릭 메뉴의 "저장"을 모든 프로젝트에 표시 — 열려 있지 않은 프로젝트는 서버의 최신 내용을 받아 이 브라우저에 백업 (서버가 비어 있으면 기존 백업을 덮어쓰지 않음)
- 로그인·프로젝트 이름 창을 랜딩 페이지 스타일로 통일 — 제목을 랜딩 헤더와 같은 F 마크 + KOTRA Bold "Freedom Ai"로, Google 로그인 버튼을 검은 테두리·그림자·굵은 글씨로 (본문 글꼴은 원래 같은 Noto Sans KR/JP)
2026-10-14
본문 폰트 실험 후 Noto Sans로 복귀
- 본문 폰트를 Do Hyeon → Black Han Sans → GS혜자로운체 → 태백체 순으로 바꿔 봤다가, 10/07의 Noto Sans KR / JP(제목 900 굵기)로 되돌림. 로고(KOTRA Bold)는 그대로
- 실험 중 저장소에 넣었던 GS혜자로운체 파일(
fonts/, 약 1MB)을 삭제 — 더 이상 참조하지 않는데 공개 URL로 받아갈 수 있었음
- 어두운 푸터에서 로고 뒤에 흰 사각형이 보이던 문제 —
logo.png에 투명 배경이 없었음. 바깥 여백만 투명하게 한 logo-mark.png를 만들어 헤더·푸터·앱 상단바에 사용 (파비콘·공유 미리보기는 기존 logo.png 유지)
- 랜딩 제목의 반짝이는 별(✦)이 영어 화면에서 안 보이던 문제 — 별이 제목 줄 기준 고정 위치라, 언어마다 라임 박스 위치가 달라지면서 영어에서는 같은 라임색 박스 뒤에 가려졌음. 별을 라임 박스 오른쪽 위 모서리에 붙여 세 언어 모두 같은 자리에 보이게 함
2026-10-08
재점검 후속 — 남아 있던 검증 공백과 번역·공유 문제 처리
- 만료에 기대는 동작을 처음으로 검증 — 가짜 KV가 TTL을 전부 무시해서, 문의 레이트리밋(1분)·초대 링크(7일)·게스트 방(24시간)·룸 락(10초)이 구조적으로 테스트 불가능했음(한도를 5→99999로 바꿔도 전체가 초록이었음). 가상 시계를 넣어 실제 타이머 없이 만료를 재현하고, 5개 동작 모두 "되돌리면 테스트가 깨지는지" 확인함
- 토큰 연장 시 다른 계정을 골라도 성공 처리되던 문제 — 받은 토큰이 정말 그 계정 것인지 확인한 뒤에만 연장으로 본다
- 이탈 시 저장이 거부되던 문제 — keepalive 요청은 본문 합계 64KiB를 넘으면 브라우저가 거부한다. 한도 안에서만 keepalive를 쓰고 넘치는 건 일반 요청으로 보냄
- 브라우저 사본 저장 실패를 조용히 삼키던 문제 — 이미지가 많은 보드에서 용량이 모자라면 사본이 안 남는데 사용자는 백업이 있다고 믿었음. 한 번 안내함
- 되돌리기가 방금 한 삭제를 건너뛰던 문제 — 이미지 예산을 넘기면 방금 만든 기록을 통째로 버려 Ctrl+Z가 엉뚱한 과거 동작을 되돌렸음. 가장 최근 항목은 보호함
- 일본어 용어 통일 — 「協業」(기업 제휴를 뜻하는 B2B 표현)을 「共同編集」으로 바꾸고, 한 페이지에 섞여 있던 표기를 정리. 내비 "소개"도 「紹介」→「概要」로 (푸터의 会社概要와 이어짐)
- 스크린리더가 읽는 이름(
aria-label)이 영어·일본어 화면에서도 한국어로 읽히던 문제 — 랜딩의 번역 적용에 aria-label·title 분기가 없었음
- 검색·링크 미리보기용 메타 추가 — 설명·OG·트위터 카드가 하나도 없어서 공유해도 제목만 떴음
api/projects.js 테스트 신규(인증 두 줄을 지워도 전체가 통과하던 공백), 변이 테스트 잔재 감지 테스트 추가
- 좌표가
null로 저장돼 항목이 깨지던 문제 — 가짜 KV를 실제 KV처럼 JSON으로 직렬화하게 바꾸자 드러났음. 좌표·크기를 typeof로만 검사하던 7곳이 Infinity·NaN을 통과시켰고, 그 값은 저장되는 순간 null이 되어 포스트잇·이미지·도형이 위치를 잃었음. 전부 유한한 수인지 검사하도록 고치고 회귀 테스트로 고정
- 랜딩 섹션 머리말(Features/Roadmap)이 영어로 고정돼 있던 것 번역
- 팀 기능이 토큰 검증 없이도 통과하는지 아무도 확인하지 않고 있었음 —
api/team.js의 verifyOwner 네 군데를 모두 지워도 전체 테스트가 초록이었다. 즉 "이메일만 알면 남의 방을 잠그고 초대를 받고 멤버를 내보낼 수 있는지"를 증명하는 테스트가 없었음. 모든 가드를 하나씩 무력화해 보는 변이 스윕으로 찾았고(26건 살아남음 → 9건), 팀 권한·내보내기 규칙·초대 토큰·roomId 검증·주인 없는 방의 이름 변경 권한에 테스트를 붙임. 남은 9건은 "방어 코드를 지우면 400 대신 500이 날 뿐"이거나 도달 불가능한 코드라 그대로 둠
- 캔버스 앱에서 스크린리더가 읽을 이름이 없던 버튼 5개(펜·지우개 굵기 3개, 캘린더 이전·다음 달)에 3개 언어 설명 추가. 이제 두 페이지 모두 이름 없는 버튼 0개
- 폰트 CSS가 렌더를 막던 문제 — 두 폰트를 한 요청으로 받느라 스타일시트가 268KB(gzip,
@font-face 1,240개)였고 그동안 화면이 비어 있었음. Google Fonts가 CJK를 문자 범위별로 쪼개기 때문. 한국어·영어 화면이 쓰는 폰트만 먼저 받고 일본어용은 비동기로 미뤄 렌더 블로킹을 268KB → 116KB로 줄임 (일본어 표시는 그대로 확인)
vercel.json 정리 — 전역 X-Frame-Options: SAMEORIGIN과 /admin.html의 DENY가 같은 경로에 함께 실려 브라우저가 둘 다 무시할 수 있었음. 경로별 frame-ancestors로 바꾸고, fonts.css에 1주일 캐시를 줌(공용 파일로 뺀 이유가 캐시였는데 헤더가 없어 매번 재검증했음)
재점검(병렬 에이전트 4개)에서 드러난, 직전 수정의 허점 보완
- 주인이 없는 방을 제3자가 차지할 수 있던 문제 — 직전 수정이 "주인이 비어 있으면 먼저 쓴 로그인 사용자를 주인으로 기록"하게 했는데, 그러면 방 이름만 아는 사람이 한 글자 쓰고 주인이 되어 원래 쓰던 사람을 비공개 전환으로 쫓아낼 수 있었음(이 기능 이전에 만들어진 방·게스트가 만든 방 전부 해당). 이제 주인을 추측하지 않고, 대신 그 방을 쓴 로그인 사용자 목록을 남겨 비공개로 바뀌어도 함께 쓰던 사람이 모두 멤버로 들어가게 함. 멤버 순서도 쓴 순서라 먼저 쓴 사람이 소유자가 되고, 나중에 끼어든 사람이 기존 사용자를 내보낼 수 없음. 이 목록은 이메일이라 방에 들어온 사람에게 내려보내지 않음
- 휴지통 "영구 삭제"가 아무 반응 없던 문제 — 일본어 추가 때 번역 호출을 일괄 치환하면서, 지역 변수
t가 이미 있는 블록에 번역 함수 t() 호출이 들어가 초기화 전 접근 오류가 났음. 번역 함수 이름을 tr로 바꿔 같은 사고가 재발하지 않게 함
- 이름 변경이 옮겨갈 쪽 방을 잠그지 않던 문제 — 출발지만 잠가서, 바꾸려는 이름으로 이미 작업 중인 사람이 있으면 그 사람의 저장이 방금 옮겨온 내용을 덮어썼음. 두 방을 모두(이름순으로) 잠그고, 옮기는 중간에 실패하면 원래대로 되돌리게 함
- 토큰이 만료되면 방이 하루 뒤 사라지던 문제 — 게스트 만료 해제를 토큰 검증 성공에 묶어둬서, 로그인 1시간 뒤부터는 검증이 실패해 24시간 만료가 매번 다시 걸렸음
- 모바일에서 의도하지 않은 도형이 생기던 문제 — 시스템 제스처로 터치가 취소되면 그리던 획은 버리면서 만들던 도형·화살표는 "확정"해 팀원에게까지 전송했음
- 일본어 화면의 한국어, 한국어 화면의 일본어가 시스템 폰트로 빠지던 문제 — Noto Sans JP에는 한글이 한 글자도 없고(0/11,172), Noto Sans KR에는
国 会 学 体 같은 일본 신자체가 없음. 두 폰트를 체인으로 묶어 서로 없는 글자를 받게 함. 쓰이지만 받아오지 않던 300·500 굵기도 추가(29곳이 400으로 뭉개지고 있었음)
- 문의 모달 제목의 굵기 지정이 바로 아래 줄에 덮여 적용되지 않던 것 수정
- 방 이름 한도가 파일마다 32자/64자로 달랐던 것을
lib/auth.js 한 곳에서 정하도록 통일 (직전 이력의 "32자로 통일"은 실제로는 적용되지 않았음)
- 배포에서
README.md·package.json 제외 — 변경 이력이 취약점과 환경변수 이름을 그대로 열거하므로, 테스트 코드를 제외한 것과 같은 이유
- 회귀 테스트 6개 추가 (64 → 70개). 추가·수정한 항목은 모두 고친 부분을 되돌리면 테스트가 실패하는지 변이 주입으로 확인함
2026-10-07
일본어(JP) 추가 — 랜딩 페이지와 캔버스 앱 전체
- 언어 토글이 한국어 → 영어 → 일본어 순으로 돌아간다 (버튼에는 다음에 바뀔 언어를 표시). 두 화면이 같은 저장 값을 쓰므로 랜딩에서 고른 언어가 앱에서도 이어진다
- 랜딩 91개 항목(본문·푸터·문의 폼), 앱 61개 항목(툴바 툴팁·공유·캘린더·로그인) 전부 번역. 앱 스크립트 안의 안내 문구 85곳도 한/영 2개 분기에서 3개 언어 공용 헬퍼로 정리
- 본문 폰트(BM Dohyeon)가 한자와 장음(
ー)을 빈 글리프로 가지고 있어 일본어가 통째로 사라져 보였음 — 글자별 폰트 폴백이 동작하지 않으므로, 일본어일 때만 Noto Sans JP로 교체(한국어·영어 모양은 그대로)
- 앱은 본문 폰트를 CSS 변수(
--font-body)로 모아 한 곳에서 바꾸게 정리
- 본문 폰트 교체 (Noto Sans KR / 일본어는 Noto Sans JP) — 위 두 문제를
unicode-range로 막아 글자는 다 보이게 됐지만, 없는 글자만 다른 폰트가 받다 보니 한 문장 안에서 굵기가 들쭉날쭉했음(무한 캔버스 · 똠 핤 쑀 짘). 한글·한자·가나를 모두 담은 폰트를 언어별로 하나씩 쓰도록 바꿔 전부 균일하게 맞춤. 제목은 900 굵기로 기존 무게감에 맞췄고, 로고(KOTRA Bold)는 그대로. 더 이상 쓰지 않는 BM Dohyeon 선언을 지워 방문자마다 받던 206KB 폰트 다운로드가 사라짐
- 드문 한글 음절이 빈칸으로 보이던 문제 — 같은 원인. BM Dohyeon은 한글 11,172자 중 자주 쓰는 2,437자만 글자를 갖고 있고 나머지 8,735자(예: 똠·핤·쑀·짘)는 빈 글리프였음. 글자가 있는 음절만 폰트에 맡기고 나머지는 Noto Sans KR이 받게 함
- 폰트 정의를
fonts.css로 분리 — 두 페이지가 함께 쓰고 브라우저가 캐시한다 (한글 구간이 1,664개라 HTML에 두면 양쪽이 비대해짐)
- UI 언어와 무관하게 입력한 일본어가 보이도록 — 폰트 파일을 열어 확인해보니 BM Dohyeon은 한자(
U+4E00-9FFF 4,620자)·CJK 호환한자(U+F900-FAFF 268자)·장음(U+30FC)을 글자 목록에는 등록해 두고 윤곽선이 0개인 빈 글리프로 갖고 있었음(= 브라우저가 "지원한다"고 판단해 대체 폰트로 안 넘어감). @font-face의 unicode-range에서 그 세 구간만 빼서, 한국어·영어 화면에서 포스트잇에 적은 일본어도 제대로 보이게 함
점검에서 남았던 항목 처리
- 오픈 방 권한 — 방 이름만 알면 누구나 그 방을 비공개로 잠그거나(함께 쓰던 사람 차단) 이름을 바꿔 옮길 수 있었음. 로그인한 사용자가 방을 처음 만들면 만든 사람으로 기록하고, 비공개 전환·이름 변경은 그 사람만 할 수 있게 함. 만든 사람 이메일은 다른 사용자에게 내려보내지 않음
- 배경 제거 결과가 저장되지 않던 문제 — 사진의 배경 제거 결과(투명 PNG)가 서버 한도(약 2MB)를 넘으면 서버가 조용히 버리고 성공으로 응답해서, 새로고침하면 원본으로 돌아왔음. 한도를 넘으면 투명도를 유지하는 WebP로 줄이고(미지원 브라우저는 해상도를 줄인 PNG), 그래도 서버가 거부하면 화면을 원래대로 되돌리고 안내함. 테스트에서 6.9MB 결과가 1.6MB로 줄어 저장됨
- 실행 취소를 시간 순서대로 — "마지막 삭제 1건 / 마지막 이미지 편집 1건 / 내 획"이 따로 저장되고 고정 우선순위로 꺼내져서, 노트를 지운 뒤 획을 그리고 Ctrl+Z를 누르면 노트가 먼저 돌아오는 식으로 순서가 뒤섞였음. 한 일을 순서대로 쌓아 최근 것부터 되돌리고(최대 100단계), 여러 개를 한꺼번에 지운 건 한 번에 복원. 선택 툴로 지운 펜 획도 되돌릴 수 있고, 되살린 항목이 글자 크기·그룹·앞뒤 순서·배경 제거 상태를 잃지 않음
- 모바일에서 그룹 이동 — 터치로는 그룹 선택이 안 되고 누른 항목 하나만 그룹에서 빠져나갔음. 선택 툴에서 항목을 누르면 그 항목(그룹이면 그룹 전체)이 선택되고 그대로 끌어 함께 옮겨짐(포스트잇·이미지·도형·화살표)
- 두 손가락으로 확대/축소할 때 펜·도형 툴이면 작은 점이나 기본 크기 도형이 생기던 문제 — 두 번째 손가락이 닿으면 막 시작한 획·도형을 저장하지 않고 취소(다른 사람 화면의 미리보기도 지움)
- 노트에 연결된 화살표의 끝이나 몸통을 끌어 떼어내도 연결이 남아, 노트가 움직이거나 새로고침하면 화살표가 원래 자리로 튀어 돌아가던 문제 — 끌어낸 끝은 연결을 풀고 서버에도 저장
- 사이드바 프로젝트가 5개를 넘으면 가장 오래된 것이 휴지통도 거치지 않고 목록에서 사라지던 문제 — 곳마다 5개/10개로 달랐던 한도를 30개로 통일
- 랜딩 페이지 푸터 링크 정리 — 기능 소개·로드맵은 페이지 안 섹션으로, 시작하기는 앱으로, 회사 → 소개는 회사 사이트(felpenfriends)로 연결하고, 존재하지 않는 페이지(요금제·FAQ·약관·쿠키 설정 등) 링크는 제거. 업데이트·GitHub·블로그·채용은 누르면 "현재 준비중입니다" 안내만 띄우고 이동하지 않음(주소가 생기면 연결 예정)
- 관련 테스트 2개 추가(만든 사람 권한, 이미지 크기 초과 거부)
전체 점검(병렬 에이전트 4개: 백엔드 보안 / 프론트엔드 / 랜딩·관리자·배포 / 테스트·문서) 후 수정
데이터 유실
- 게스트가 만든 방을 아무나 차지할 수 있던 문제 — 방 주인은 "방이 처음 생길 때"만 기록돼서, 첫 쓰기가 게스트였거나 그 순간 토큰이 만료 상태였던 방은 주인이 영원히 비어 있었음. 그 방은 이름만 아는 사람이 비공개로 잠가 원래 쓰던 사람을 영구히 쫓아낼 수 있었음(캔버스 전체 상실). 로그인 사용자가 작업하는 시점에 주인이 비어 있으면 그 사람으로 뒤늦게라도 기록함
- 게스트 방을 이어 쓰면 하루 뒤 사라지던 문제 — 게스트 표시가 한 번 붙으면 떨어지지 않아, 로그인 사용자가 그 방을 계속 써도 쓰기마다 24시간 만료가 새로 걸렸음(게스트로 체험 → 로그인해서 계속 쓰는 흔한 흐름에서 하루만 쉬면 작업 전체 삭제). 로그인 사용자가 작업하면 만료를 해제함
- 이름 변경이 방을 통째로 날리던 문제 — 이름 변경이 "읽어서 비었는지 확인 → 쓰기"였고 방 락도 잡지 않았음. 서로 다른 두 방을 같은 이름으로 동시에 바꾸면 둘 다 성공으로 응답하고 한쪽 방이 오류 없이 사라졌고, 변경 중에 들어온 그리기는 이미 지워진 옛 이름에 되살아나 방이 두 이름으로 갈라졌음. 이름 선점을 원자적으로 바꾸고 캔버스 저장과 같은 락을 쓰게 함
- 입력 중 페이지를 떠나면 마지막 글자가 사라지던 문제 — 포스트잇 입력은 0.4초, 로컬 사본은 0.6초 묶어서 저장하는데 떠나는 시점에 확정하는 코드가 전혀 없었음. 사이드바에서 다른 프로젝트를 누르면 즉시 새로고침이 돌아 서버·브라우저 양쪽에서 유실됐음. 탭을 닫거나 백그라운드로 보낼 때도 확정하고, 떠나는 중에도 취소되지 않는 방식으로 전송함
- 방 입장이 실패를 숨기고 백지를 주던 문제 — 서버 읽기가 실패하면 "저장소 미설정"과 같이 묶어 삼키고 빈 캔버스를 정상 응답으로 돌려줬음. 처음 들어오는 팀원은 오류 한 줄 없이 백지를 받아 그게 방의 전부라고 믿게 됐음. 이제 오류로 알려 다시 시도하게 함
- 프로젝트 목록도 같은 경로 — 목록 읽기 실패 시 빈 목록을 정상 응답으로 돌려줘서, 사이드바가 비어 보이고 거기서 하나라도 추가하면 그 한 개가 원래 목록을 덮어쓸 수 있었음. 이메일 대소문자에 따라 다른 칸을 읽고 쓰던 문제도 함께 고침
- 모바일에서 엉뚱한 좌표가 저장되던 문제 — 드래그 중 알림을 끌어내리는 등 시스템 제스처가 끼면 브라우저가 touchend 대신 touchcancel을 보내는데 이걸 듣지 않아 리스너가 영구히 남았음. 이후 화면 어디를 터치해도 선택 항목이 순간이동하고, 손을 뗄 때마다 그 좌표가 방 전체에 저장됐음
- 브라우저 사본에서 이미지 앞뒤 순서·배경 제거가 사라지던 문제 — 로컬 사본에 순서(z)와 배경 제거 여부를 저장하지 않아, 서버가 비어 이 사본으로 복원되면 순서가 전부 뭉개지고 잘라낸 이미지에 사각 테두리가 돌아왔음
- 팀원이 바꾼 할 일이 브라우저 사본에 빠지던 문제 — 원격 할 일 추가·체크·삭제만 로컬 저장을 호출하지 않았음
동작 오류
- 포스트잇 삭제 ×가 터치에서 안 먹던 문제 — 선택 툴에서 ×를 누르면 삭제 대신 드래그가 시작돼, 삭제는 안 되고 노트가 1px 밀린 것만 팀원에게 전송됐음(마우스에는 있던 예외 처리가 터치 경로에만 빠져 있었음)
- 이미지·도형 한도 초과를 조용히 버리던 문제 — 이미지 20장·도형 300개를 넘기면 서버가 저장하지 않고 성공으로 응답해서, 화면에는 그려진 채 남았다가 새로고침하면 그 항목만 사라졌음. 사유를 돌려주고 화면에서도 되돌린 뒤 이유를 알려줌
- 노트를 지우면 화살표 연결이 새로고침 전후로 달라지던 문제 — 연결을 로컬에서만 풀고 서버에는 알리지 않아, 새로고침하면 화살표가 사라진 노트 자리로 다시 붙어 두 사람 화면이 어긋났음. 서버에도 반영하고, 되돌리기로 노트를 살리면 연결까지 복원함
- 로그인 연장 버튼이 안 눌리던 경우 — Google 스크립트가 차단된 상태면 아무 반응이 없었고, 브라우저에 Google 세션이 없거나 다른 계정이면 같은 버튼만 반복해 눌러도 벗어날 수 없었음(새로고침밖에 답이 없었음). 이유를 알려주고, 한 번 실패하면 계정 선택 창으로 올라감
- AI 프롬프트 입력창에 이미지를 붙여넣으면 캔버스로 가던 문제 — 붙여넣기 처리가
textarea를 예외로 두지 않았음 (키보드 단축키 쪽은 이미 처리돼 있었음)
- 되돌리기가 사진 원본을 계속 붙잡던 문제 — 되돌리기를 100단계로 늘리면서 배경 제거·삭제한 사진의 원본(장당 최대 1.9MB)이 전부 메모리에 남아, 모바일에서는 탭이 죽고 아직 전송 안 된 변경까지 같이 날아갈 수 있었음. 이미지 용량 예산을 두고 오래된 것부터 버림
보안·배포
- 테스트가 데이터 유실을 못 잡던 문제(가장 중요) — 테스트용 가짜 저장소가 값을 복사하지 않고 참조를 그대로 내줘서, 저장 호출이 아예 빠져도 "저장됐다"는 검증이 통과했음. 실제 저장소는 매번 새로 읽어오므로 운영에서는 그게 곧 데이터 유실. 복사해서 끊고 확인해 보니, 방 저장을 일부러 누락시켰을 때 전에는 통과했던 테스트 14개가 제대로 실패함
- 테스트 간에 공유되던 토큰 검증 캐시 때문에 앞선 테스트가 뒤 테스트 결과를 바꿀 수 있던 문제 정리
- 위 수정들에 대한 회귀 테스트 7개 추가 (57 → 64개)
userId를 빈 값만 걸러서 10만 자도 모든 객체에 그대로 저장되던 문제 — 방 상태를 부풀려 결국 그 방 사용자 전원이 오류를 받게 만들 수 있었음
- 관리자 키 헤더를 모든 출처에 허용하던 CORS 설정 제거 — 아무 제3자 페이지가 방문자 브라우저로 키를 추측하고 적중 시 가입 이메일·문의 전문까지 읽을 수 있었음
- 구버전 로컬 서버(
server.js)가 .env와 저장 파일을 그대로 서빙하던 문제 — 점파일·비공개 파일 차단
- 배포 시 레포 루트를 전부 공개 서빙해서 테스트 코드·AI 서버 코드까지 URL로 받아갈 수 있던 문제 —
.vercelignore 추가. 보안 헤더(X-Content-Type-Options, X-Frame-Options, Referrer-Policy)와 관리자 페이지 no-store·noindex 추가
- 프로젝트 이름 한도가 곳마다 32자/64자로 달라, 33자 이상 프로젝트는 만들 수는 있는데 이름 변경이 영원히 거절되던 문제 — 32자로 통일
.gitignore에 .DS_Store 추가
- README를 코드와 맞춤: 실행 취소 설명이 표와 서로 모순됐던 것 정정, 코드가 읽는
GOOGLE_CLIENT_ID를 환경변수 목록에 추가, ADMIN_STATS_KEY를 필수 → 선택으로 정정, 포스트잇 개수 한도 추가, 삭제된 Hugging Face 프록시 관련 설명 정리
2026-10-01
로그인 토큰 만료 처리
- 로그인 만료 처리 — Google 로그인 토큰(약 1시간 유효)을 갱신하지 않아, 1시간이 지나면 비공개 방의 모든 저장이 조용히 실패하고 사이드바로 이동하면 멤버인데도 "비공개 프로젝트" 안내가 떴음. 이제 만료 5분 전(또는 서버가 인증 실패로 거절하면) 툴바 아래에 "로그인 연장" 배너가 뜨고, 클릭 한 번으로 계정 선택 없이 새 토큰을 받은 뒤 서버 상태로 다시 맞춤. 사이드바 이동 시에도 토큰이 곧 만료되면 로그인을 건너뛰지 않음. 실시간 연결 인증도 매번 최신 토큰을 쓰도록 변경
전체 점검(병렬 에이전트 4개: 그룹 기능 / 프론트엔드 / 백엔드 보안 / 랜딩·관리자·AI 서버) 후 수정
- 보안: 방 이름 키 충돌 차단 — 방 이름을
"다른방:members"처럼 보내면 그 방의 멤버 이메일을 읽거나, 멤버 목록을 덮어써 아무도 못 들어오게 잠그거나, 이름 변경으로 옮겨 비공개를 풀 수 있었음. 모든 API에서 :members·:lock으로 끝나는 방 이름을 거부하고, 깨진 멤버 목록이 있어도 500으로 죽지 않게 함
- 보안: 다른 앱에서 발급된 Google 토큰 거부 — 사용자가 로그인했던 다른 서비스가 그 토큰으로 이 앱의 비공개 방·프로젝트 목록에 접근할 수 있었음. 토큰이 이 앱의 클라이언트 ID로 발급됐는지 확인
- 보안: 팀 멤버 관리 — 새로 초대받은 멤버가 방을 비공개로 만든 사람을 포함해 다른 멤버를 전부 내보낼 수 있었음. 이제 방 소유자(처음 비공개로 전환한 사람)만 다른 멤버를 내보낼 수 있고, 나머지는 자기 자신만 나갈 수 있음. 초대 토큰을 추측 불가능한 난수로 바꾸고, 비공개가 풀린 방의 옛 초대 링크로 방을 차지하지 못하게 함
- 보안: AI 서버 남용 방지 — 누구나 이 서버로 Hugging Face 할당량을 쓸 수 있었음(IP별 한도도 헤더 조작으로 우회 가능). 이 앱 주소에서 온 요청만 받고, 실제 접속 IP 기준 한도 + 서버 전체 한도를 둠. 입력 검증·HF 호출 시간 제한·JSON 에러 응답 추가, 내부 에러 메시지 노출 제거
- 보안: 관리자 키를 URL 대신 헤더로 전송 —
?key=가 Vercel 요청 로그·브라우저 기록에 남았음. 비교도 시간차 공격에 안전하게 바꿈
- 쓰이지 않던 Vercel AI 프록시(
api/remove-bg.js, api/ai-transform.js)와 그 테스트 삭제 — 실제 호출은 Render의 hf-server로 가는데, 이 둘은 인증 없이 공개돼 있었음
- Pusher 채널 인증이 이 앱의 방 채널(
presence-room-*)만 서명하도록 제한
- 데이터 유실: KV 읽기가 한 번 실패하면 방 전체가 지워지던 문제 — 읽기 실패를 "빈 방"으로 착각해 빈 상태로 덮어썼음. 이제 500으로 응답하고 방은 그대로 둠. 저장 실패도 더 이상 성공으로 응답하지 않음
- 데이터 유실: 동시에 들어온 요청이 서로의 변경을 덮어쓰던 문제 — 방 락을 못 잡으면 락 없이 진행했었음. 이제 503으로 거부하고 클라이언트가 서버 상태로 다시 맞춤. 락 해제도 내가 건 락만 풀도록 함
- 데이터 유실: 여러 항목 이동·그룹 크기 조절이 일부 저장 안 되던 문제 — 항목마다 요청을 따로 보내 락을 다투다 유실될 수 있었음. 한 번의
items_update 요청으로 묶고, 그냥 클릭만 했을 땐 아무것도 보내지 않음. 크기가 바뀐 노트에 연결된 화살표도 함께 저장
- 긴 펜 획·긴 포스트잇 글이 다른 사람에게 전달되지 않던 문제 — Pusher 이벤트 한도(10KB)를 넘으면 요청이 500으로 실패했음. 넘으면 "다시 읽기" 신호를 보내고, 획 좌표는 소수점 1자리로 줄여 크기 자체를 줄임
- 펜 획 저장 시 검증 강화(임의 필드·무제한 점·저장 거부된 획 전파 차단), 크기 조절 값에 숫자 검증 추가, 포스트잇 개수 상한(1,000개)
- 그룹 크기 조절 시 일부 항목만 최소/최대 크기에 걸려 비율이 깨지고 겹치던 문제 — 배율 자체를 모든 항목이 허용하는 범위로 제한
- 지우개로 잘린 획 조각·실행 취소로 되살린 항목이 그룹 정보를 잃던 문제, 다른 사람이 지운 항목이 선택 박스에 계속 남던 문제 수정
- 할 일 체크를 두 사람이 동시에 하면 서로 다른 값으로 어긋나던 문제(서버가 뒤집기만 했음) — 원하는 값을 보내 그대로 설정
- 지우개 직후 Ctrl+Z가 지운 걸 되살리는 대신 남은 조각을 더 지우던 문제 수정
- "R&D"처럼
&·#가 들어간 프로젝트의 공유 링크가 다른 방으로 열리던 문제 수정
- 프로젝트 이름을 비운 채 한 번 Enter를 누르면 다음 입력부터 페이지가 새로고침되던 문제 수정
- 방 입장 API가 실패하면 앱이 빈 화면으로 멈추던 문제 — 안내 후 중단
2026-09-28
- 선택 툴로 여러 항목을 고르면 전체를 감싸는 점선 박스가 뜨고, 모서리 4곳 핸들을 끌어 한꺼번에 크기 조절 가능 — 반대쪽 모서리를 기준으로 모든 항목의 위치·크기가 같은 비율로 바뀜(이미지 비율 유지). 이미지·포스트잇·도형·화살표·펜 획 모두 지원
- 그룹 기능 추가 — 여러 항목을 선택한 뒤 박스 위 "그룹" 버튼(또는 Ctrl/Cmd+G)으로 묶으면, 이후 그중 하나만 클릭해도 그룹 전체가 선택·이동됨. "그룹 해제"(또는 Ctrl/Cmd+Shift+G)로 풀 수 있고, 그룹 정보는 서버에 저장돼 팀원과 실시간 동기화·새로고침 후에도 유지
- 위 기능을 위해
image_resize·note_resize·shape_resize가 위치(x·y)도 함께 받도록 확장하고 group_set 액션 추가, 관련 테스트 2개 추가
- 캘린더 할 일·프로젝트 이름·AI 프롬프트 입력칸에서 글을 쓸 때 캔버스 단축키가 같이 먹던 버그 수정 — 스페이스가 입력되지 않고(패닝으로 가로채짐), p·e·s 등을 치면 도구가 바뀌고, Backspace가 선택된 캔버스 항목을 지울 수 있었음. 이제 모든 입력칸에서 단축키를 무시함
- Cmd/Ctrl을 누른 채 누르는 키(Cmd+S, Cmd+0 등)가 캔버스 단축키로도 처리되던 문제 수정 — 이제 Cmd/Ctrl 조합은 실행 취소(Z)만 처리하고 나머지는 브라우저에 맡김. Caps Lock이 켜져 있어도 Ctrl/Cmd+Z가 동작
- 배경 제거가 검은 배경을 지운 경우에도 "밝은 배경만 제거했습니다"라고 안내하던 문구를 "단색 배경만 제거했습니다"로 정정
- README 전체 점검(병렬 에이전트 3개) 후 실제 코드와 다른 내용 정리 — 로컬 실행 방법(
npm start는 구버전 서버라 /api가 동작하지 않음 → npx vercel dev 안내), 프로젝트 구조에 빠진 hf-server/·tests/·contact.js·rename-room.js·team.js 추가, 누락된 단축키(R·Space·Delete)·팀 초대/비공개 방·한영 전환·정책 한도 추가, 포스트잇 높이 최소값(80px)·휴지통 영구 삭제 범위 등 틀린 설명 정정
- 변경 이력에서 9/17로 잘못 적혀 있던 히어로 포스트잇·검은 배경 지원·자석 스냅 항목을 실제 날짜(9/18)로 이동
2026-09-26
- README를 실제 기능에 맞게 정리 — 주요 기능 표에 빠져 있던 도형·AI 배경 제거·협업 To-Do·게스트 입장·포스트잇 자석 스냅 등을 추가하고, 기술 스택에 AI 이미지 처리(Hugging Face·Render)를 추가. 로드맵에서 현재 동작하지 않는 AI 이미지 변환을 "완료"에서 빼고 사유를 명시
2026-09-23
- 관리자 통계 월별 방문 수를 더 이상 별도 카운터로 관리하지 않고, 일별 카운터를 월 단위로 합산해서 계산하도록 구조 변경 — 예전 방식(일별·월별을 각각 따로 증가)은 둘 중 하나만 실패해도 두 값이 어긋날 수 있었는데(실제로 1건 차이가 발생했던 적 있음), 합산 방식이면 애초에 어긋날 수가 없음
- 관리자 통계 일별 방문 수 목록에 오늘 날짜를 항상 포함하도록 함 — 오늘 아직 방문이 0건이면 날짜 자체가 목록에 안 잡혀서 "카운터가 고장났나" 오해하기 쉬웠음. 실제로 테스트 방문을 남겨서 원인이 버그가 아니라 단순히 그 시점까지 방문이 없었던 것임을 확인
- 관련 테스트 2개 추가(월별 합산 검증, 오늘 0건 포함 검증)
2026-09-20
- 관리자 통계(
/api/stats) 페이지뷰 기록 시 KV 쓰기 에러를 조용히 삼키던 것을 수정 — pageviews 카운터와 일별/월별 카운터를 각각 별도 try/catch로 분리하고 실패 시 Vercel 함수 로그에 남기도록 함. 오늘 날짜(9/20) 방문 카운팅이 안 되는 것 같다는 제보로 점검하던 중 발견 — 겉에서는 항상 204(성공)로 응답해서 실제로 어느 카운터가 실패하는지 알 수 없던 상태였음
- 전체 점검(병렬 에이전트 4개)으로 발견한 버그 10개 수정:
todo_toggle/todo_delete에 날짜 형식 검증이 빠져있어 date:"__proto__" 같은 입력으로 서버가 500 크래시 나던 문제 수정
- 날짜당 할 일 50개 한도 초과 시 서버가 조용히 거부하면서도 항상 성공 응답을 보내, 클라이언트가 이미 화면에 추가해버린 항목이 동기화 안 된 채 새로고침하면 사라지던 문제 수정 — 서버가
rejected 플래그를 보내고 클라이언트가 낙관적 추가를 되돌리도록 함
- todos 기능 이전에 만들어진 방(레거시 방)이
todos 필드 없이 내려와 프론트가 깨질 수 있던 문제를 api/join.js·api/room.js 양쪽에서 수정
- 포스트잇 자석 스냅 거리가 확대/축소(줌) 배율을 반영 안 해서 많이 확대하면 너무 쉽게 붙고 축소하면 거의 안 붙던 문제 수정
- 게스트가 모바일에서 햄버거(☰) 버튼을 눌러도 아무 반응 없던 죽은 버튼 문제 수정 — 게스트는 사이드바가 없으므로 버튼 자체를 숨김
- 랜딩 페이지 "소개" 메뉴가 존재하지 않는
#about 앵커를 가리키던 죽은 링크 수정 — 푸터로 연결되도록 함
- "색상 팔레트" 카드가 실제로 없는 "HEX 직접 입력" 기능을 광고하던 문구 정정
- 히어로 목업에 남아있던 장식용 포스트잇 3개(보라·민트·로즈) 정리 — "포스트잇 5가지 색상" 카드 문구와 모순되던 것을 해소하고, 그걸 위해 넓혔던 히어로 폭도 원래대로 되돌림
- "협업 To-Do"가 기능 카드에서는 Phase 1(완성)인데 로드맵 섹션에서는 여전히 Phase 2(예정)로 남아 자기모순이던 것을 로드맵도 Phase 1로 옮겨 정리
- 관리자 통계 페이지가 재조회 실패 시 이전 조회 결과(그래프·목록)를 그대로 남겨둬서 최신 데이터로 오해할 수 있던 것을 수정 — 실패 시 전부 숨기도록 함
- 관련 회귀 테스트 4개 추가 (날짜 검증, 한도 초과 응답, 레거시 방 todos 백필 2건)
- 위 수정 사항들을 재점검(병렬 에이전트 3개)하는 과정에서 추가로 발견한 문제 수정 — 관리자 페이지 연락처(문의) 목록도 통계 그래프와 같은 패턴으로, 재조회 실패 시 이전 문의 목록이 그대로 남아있던 것을 수정
2026-09-19
- 랜딩 페이지 "협업 To-Do" 카드 문구를 실제 기능(캘린더에서 날짜 선택 후 할 일 작성)에 맞게 정정하고 Phase 태그를 "Phase 1 · 핵심"으로 변경 — 이전엔 "캔버스 안에" 만든다고 돼있어 실제 구현(캘린더 모달)과 달랐음
2026-09-18
- 랜딩 페이지 히어로 캔버스 목업의 빈 공간에 포스트잇 2개 추가 (링크 공유, 모바일 지원)
- 위 변경을 되돌림 — 포스트잇 2개 제거
- 히어로 문단(.hero-desc) 최대 폭을 560px에서 780px로 넓혀 캔버스 목업이 실제로 780px까지 커지도록 수정하고, 오른쪽에 생긴 공간에 포스트잇 3개(링크 공유, 모바일, 구글 로그인) 추가
- "구글 로그인" 포스트잇 위치를 오른쪽 끝에서 왼쪽 빈 공간으로 이동
- 이미지에서 글씨(로고·타이포그래피)만 추출하는 배경제거가 흰 배경만 지원하던 것을 검은 배경도 지원하도록 확장 — 이미지 가장자리 밝기로 배경이 밝은지 어두운지 먼저 판별해서 방향을 자동으로 맞춤
- 검은 배경 판별이 모서리 몇 점만 보다가, 글씨가 가장자리까지 꽉 찬 타이포그래피(예: "FONT" 같은 큰 글자가 모서리를 침범하는 경우)에서 틀리던 문제 수정 — 이미지 전체의 어두운/밝은 픽셀 개수를 세어 다수결로 판별하도록 변경
- 포스트잇을 드래그해서 다른 포스트잇 가장자리 가까이 가져가면 자석처럼 딱 붙는 스냅 기능 추가 — 같은 의견을 보완하는 포스트잇끼리 깔끔하게 이어붙일 수 있음
- 랜딩 페이지 "AI 이미지 편집" 카드 문구를 "AI 배경 제거"로 변경 — AI 프롬프트 변환·생성 기능은 Hugging Face 무료 프로바이더(hf-inference)가 GPU 이미지 생성을 더 이상 지원하지 않아 현재 작동하지 않는 상태라, 실제로 되는 배경 제거 기능만 안내하도록 문구를 정정함
- 랜딩 페이지 "포스트잇" 카드 문구를 실제 기능(색상 5개, 크기 조절, 화살표 연결, 자석 스냅)을 반영하도록 다시 작성
- 툴바에 "템플릿" 버튼 추가 — 클릭하면 뜨는 팝오버에서 칸반보드(할 일·진행 중·완료 3열, 각 열에 안내용 포스트잇 2장)를 클릭 한 번으로 캔버스에 삽입. 기존 있던 실체 없는 "협업 To-Do" 카드를 실제로 동작하는 "보드 템플릿" 기능으로 대체하고 랜딩 페이지 카드 문구도 정정
- 위 보드 템플릿 기능을 되돌림
- 툴바에 캘린더 아이콘 추가 — 클릭하면 월간 캘린더 모달이 뜨고, 이전/다음 달 이동과 날짜 선택 가능
- 캘린더에서 날짜를 클릭하면 그 날짜의 To-Do 목록을 작성·체크·삭제할 수 있게 함 — 팀원들과 실시간으로 동기화되고 새로고침해도 유지됨(room state에 저장). 할 일이 있는 날짜는 캘린더에 작은 점으로 표시
2026-09-17
- 로그인 모달에 "게스트로 입장" 버튼 추가 — Google 로그인 없이도 캔버스를 바로 체험할 수 있게 함 (안내 문구로 작업 내용이 저장되지 않음을 고지)
- 랜딩 페이지 헤더의 "로그인" 버튼이 별도 Google 팝업을 바로 띄우던 것을 수정 — app.html로 이동해 게스트 입장 옵션이 있는 로그인 모달이 먼저 뜨도록 함
- 게스트로 입장 시 이 브라우저에 남아있던 (이전 로그인 계정의) 최근 프로젝트 목록·사이드바가 그대로 노출되던 문제 수정 — 게스트는 프로젝트 목록·사이드바를 아예 볼 수 없도록 함
- 게스트로 입장 시 "로그아웃" 버튼이 노출되던 문제 수정 — 로그인한 적이 없으므로 게스트에게는 표시하지 않음
- 게스트가 입력한 프로젝트 이름이 이 브라우저에 저장되어, 나중에 같은 브라우저로 실제 로그인하면 "최근 프로젝트"에 섞여 나오던 문제 수정 — 게스트 세션은 로컬 기록을 아예 남기지 않음
- 게스트가 새로 만든 방(캔버스 데이터)이 서버에 영구히 남던 것을 수정 — 마지막 활동 24시간 후 자동 삭제되도록 함 (기존에 있던 진짜 프로젝트와 이름이 겹쳐도 만료가 걸리지 않도록 안전장치 포함)
- 게스트 입장 확인 문구에서 "전부" 표현 삭제
- 작은 크기로 빠르게 드래그하면 지우개가 획을 못 지우는 문제 수정 — 지우개 경로를 점이 아니라 선분으로 판정하도록 해서, 드래그가 빨라 마우스 이동 샘플 간격이 벌어져도 지나간 자리는 확실히 지워지게 함
- 관리자 통계 페이지(
/admin.html)에 일별 방문 수 막대그래프 추가 (최근 30일, KST 기준 날짜별 집계)
- 관리자 통계 페이지에 월별 방문 수 막대그래프도 추가 (최근 12개월, KST 기준 월별 집계)
- 로고에서 F 글자를 감싸던 흰색 둥근 사각형 테두리 제거 — F가 검은 배경에 바로 놓이도록 단순화
- 로고를 F 글자만 남기고 둥근 사각형 배경(폴더 모양) 전체를 제거 — F 모양은 그대로 두고 투명 배경 위 검은 F만 표시
- 위 변경들을 모두 되돌리고 로고를 원래 디자인(흰색 둥근 사각형 테두리 + 검은 F)으로 최종 복원
- 상단바 "Freedom AI" 글씨를 굵게 변경 (font-weight 500 → 700)
- 랜딩 페이지 헤더 "Freedom AI" 글씨도 굵게 변경 (누락됐던 부분 추가)
- 로고에서 바깥 검은 폴더 모양(둥근 사각형 + 접힌 모서리)만 투명 처리해 제거 — 안쪽 흰색 둥근 사각형과 F는 그대로 유지
- 위 변경을 되돌리고 로고를 원래 디자인(검은 폴더 배경 + 흰 테두리 + 검은 F)으로 복원
- "Freedom AI" 로고 글씨체를 KOTRA 볼드체로 변경 (상단바·랜딩 페이지 헤더)
- 로고 옆 "Freedom AI" 글씨를 "FREEDOM AI" 대문자로 변경 (상단바·랜딩 페이지 헤더·푸터)
- 위 대문자 표기를 "freedom ai" 소문자로 변경
- 표기를 "Freedom Ai"로 최종 변경
- 랜딩 페이지 헤더/푸터 로고 아이콘을 감싸던 흰 배경·검은 테두리 박스 제거 — 아이콘이 바로 보이도록 단순화
2026-09-16
- 이미지에 배경 제거 전용 버튼 추가 (호버 시 좌상단에 가위 아이콘, 꾹 누르기 없이도 바로 실행 가능)
- 로고·텍스트 이미지에 배경제거를 쓰면 AI가 글자를 엉뚱하게 잘라내던 문제 수정 — 명도 분포로 사진인지 그래픽인지 미리 판별해서, 그래픽이면 AI 없이 밝은 배경만 제거
- 처리 중 토스트의 깨진 이모지 아이콘을 점선 원 SVG로 교체
- README에 날짜별 변경 이력(changelog) 섹션 추가
- 선택 툴에서 이미지를 클릭만 해도 선택되도록 수정 (이전엔 범위 드래그로만 선택되어, 클릭 한 번으로는 Backspace/Delete 삭제가 안 됐음)
- 이미지 우클릭으로 "맨 앞으로 가져오기"/"맨 뒤로 보내기" 추가 — 배경 제거한 이미지끼리 겹칠 때 순서 조절 가능, 새로고침 후에도 순서 유지
- 관리자 통계 페이지(
/admin.html)에 가입자 수뿐 아니라 실제 가입 이메일 목록도 표시
2026-09-15
- 배경제거 처리 중에만 실루엣 주변에 점선 컷라인을 보여주고, 완료되면 사라지도록 함
- AI 배경제거·이미지변환 결과가 마음에 안 들면 Ctrl+Z로 원본으로 되돌릴 수 있게 함
- 배경제거 후 이미지 주변에 남아있던 사각형 그림자 제거
- 랜딩 페이지를 Neo-Brutalism 스타일로 리디자인
- 코드 전체 점검(프론트엔드/백엔드/신규 서버로 나눠 병렬 점검) 후 발견된 크래시·보안·동기화 버그 수정
- 배경제거·AI이미지변환 요청을 별도 서버(Render)로 분리해, Vercel 계정 문제와 무관하게 항상 동작하도록 함
2026-09-14
- 랜딩 페이지 히어로 문단의 한글 줄바꿈(단어 중간 끊김) 버그 수정
- 복사해서 붙여넣은 글자가 폰트 크기 조절 안 되던 문제 수정
- 포스트잇 핸들 확대 + 상단 툴바에서도 폰트 크기 조절 가능하게 개선
- 포스트잇 글자 크기 조절 기능 최초 추가
- 스페이스바로 패닝 중 다른 요소가 대신 움직이거나 손 커서가 안 보이던 문제 수정
- 배경 제거를 모달 버튼 대신 이미지 꾹 누르기(long-press)로 단순화, 관련 호출 실패/시간초과 버그 수정
- AI 배경 제거 기능 최초 추가
- 핵심 백엔드 동작(획·포스트잇·이미지·도형 삭제/이동, 방 입장)에 자동 테스트 추가
- 삭제·이동이 서버에 반영되지 않고 재접속하면 되살아나던 문제 근본 수정 (userId 고정 + 소유권 정책 정리)
- 사이드바 프로젝트 우클릭 메뉴에 이름 변경·저장 기능 추가
- 툴바에 실행취소(Undo) 버튼 추가
© 2025 Freedom AI