매일 같은 블록을 칠하는 게 이렇게 귀찮을 줄이야
2화에서 서버 인프라까지 깔끔하게 마무리하고 나니, 드디어 "타임박싱 앱 완성!"이라고 외치고 싶었거든요. PocketBase 서버도 돌아가고, Nginx 리버스 프록시도 잘 붙어 있고, 자동 저장도 되고… 뭐, 기능적으로는 훌륭했어요. 그런데 말이죠, 실제로 매일 쓰기 시작하니까 이야기가 달라지더라고요.
월요일 아침 7시. 눈 비비며 앱을 열어서 출근 시간 블록을 칠하고, 점심 시간 칠하고, 퇴근 시간 칠하고… 화요일 아침 7시. 또 똑같은 블록을 칠하고… 수요일? 네, 또요. 직장인의 하루는 생각보다 꽤 비슷하잖아요. 매일 아침 같은 색깔 블록을 처음부터 다시 칠하는 건 솔직히 시간관리 앱에서 시간을 낭비하는 아이러니한 상황이었습니다.
거기다 한 주가 지나면 "이번 주에 회의에 몇 시간이나 썼지?" 같은 궁금증이 생기는데, 날짜를 하나씩 넘겨가며 확인해야 하니 이것도 영 불편했어요. 그리고 결정적으로… 밤 11시, 애들 다 재우고 조용히 타임박싱 정리를 하려는데 화면이 눈부시게 하얀 거예요. 4명의 아이를 겨우 재워놓고 밝은 화면 켜면 다시 깨울 수도 있다는 공포감(?)까지 더해져서, 이 세 가지 문제를 반드시 해결해야겠다고 마음먹었습니다.
이번 3화에서는 반복 템플릿 시스템, 주간 리뷰 대시보드, 그리고 2026년 웹 트렌드의 필수 요소가 된 다크모드 구현기를 다뤄볼게요. Claude에게 "이거 좀 만들어줘"라고 던지면서 벌어진 삽질과 깨달음, 솔직하게 풀어보겠습니다.

반복 템플릿 시스템 — "어제랑 똑같은 하루"를 원클릭으로
왜 필요했나
타임박싱의 핵심은 "의도적으로 시간을 배분하는 것"이에요. 그런데 배분 자체가 매일 비슷하다면? 출근 시간, 점심 시간, 정기 회의, 퇴근 시간 — 이런 고정 블록은 사실 월요일부터 금요일까지 거의 동일하거든요. 매일 아침 5분씩 같은 블록을 칠하는 건 타임박싱이 아니라 타임 낭비에 가까웠습니다.
요즘 시간관리 앱 시장에서도 "반복 패턴 자동화"는 거의 기본 기능으로 자리 잡았어요. Sunsama나 TimeBlocks 같은 인기 앱들도 템플릿 기능을 핵심으로 내세우고 있죠. 우리 앱에도 당연히 있어야 할 기능이었습니다.
Claude와 함께 구현하기
📋 템플릿 버튼을 누르면 팝업이 뜨는 구조로 만들었어요. 기능은 세 가지입니다.
첫 번째, 저장 기능이에요. 현재 화면의 PLAN 블록뿐 아니라 BIG 3 목표, Brain Dump 항목, 메모까지 통째로 캡처해서 localStorage에 저장합니다. 처음에는 PLAN 블록(타임그리드의 색칠된 슬롯)만 저장하려고 했어요. 그런데 매일 쓰다 보니 Brain Dump에도 "메일 확인", "일일 스탠드업", "코드 리뷰"처럼 반복되는 항목이 있더라고요. 그래서 텍스트 영역까지 모두 포함하도록 확장했습니다.
두 번째는 적용 기능이에요. 저장된 템플릿 목록에서 하나를 선택하면 원클릭으로 타임그리드가 복원됩니다. "평일용", "재택근무용", "금요일 반차용" 이런 식으로 이름을 지어두면 아침에 상황에 맞는 템플릿만 딱 눌러주면 되니까 엄청 편해졌어요.
세 번째는 삭제 기능이고요, 더 이상 안 쓰는 템플릿을 정리할 수 있습니다.
function capturePlan() {
// 타임그리드 슬롯 캡처
const slots = [];
document.querySelectorAll('table .slot').forEach(el => { ... });
// BIG 3 목표
const big3 = [];
document.querySelectorAll('.big3-text').forEach(el => { big3.push(el.innerHTML); });
// Brain Dump 항목
const bdump = [];
document.querySelectorAll('.bd-row .editable').forEach(el => { bdump.push(el.innerHTML); });
// 메모 영역
const memo = document.querySelector('.memo-box').innerHTML;
return { slots, big3, bdump, memo };
}삽질 기록: DOM 인덱스의 함정
여기서 한 가지 뼈아픈 삽질이 있었는데요. 처음에 Claude가 제안한 방식은 contenteditable 요소를 인덱스 번호로 찾는 것이었어요. ce0은 날짜 필드, ce1은 숨겨진 필드, ce2~4는 BIG 3 텍스트… 이런 식으로 순서에 의존하는 방식이었죠.
PC에서는 완벽하게 동작했어요. 그런데 모바일에서 사이드 드로어를 열면 DOM 순서가 바뀌면서 인덱스가 어긋나버리는 거예요. BIG 3에 적어둔 내용이 엉뚱한 곳에 들어가거나 아예 비어버리는 현상이 발생했습니다. 밤 12시에 이 버그를 발견했을 때의 그 허탈함이란… 😇
결국 인덱스 기반 탐색을 버리고, CSS 클래스명(.big3-text, .bd-row .editable, .memo-box)으로 직접 타겟팅하는 방식으로 전면 수정했어요. 교훈: 레이아웃이 반응형이라면 DOM 인덱스에 절대 의존하지 마세요. 클래스명이나 data-* 속성으로 요소를 특정하는 게 훨씬 안전합니다. 이건 프론트엔드 개발의 기본 중 기본인데, 뒤늦게 몸으로 배웠네요.

주간 리뷰 대시보드 — 진짜 가치는 "돌아보기"에 있다
하루는 기록이고, 한 주는 패턴이다
타임박싱을 2주 정도 꾸준히 하다 보니 자연스럽게 궁금해지더라고요. "이번 주에 회의에 대체 몇 시간을 쓴 거지?", "계획 대비 실제 달성률은 얼마나 되나?", "어떤 요일에 생산성이 높았나?" 같은 질문들이요.
하루 단위로는 그냥 기록이에요. 하지만 일주일치를 모아서 보면 패턴이 보이거든요. 화요일에 유독 회의가 몰린다든지, 금요일 오후에는 거의 아무것도 못 한다든지… 이런 패턴을 시각화해주는 주간 리뷰 기능이 필요했습니다.
review.html — 별도 페이지로 분리
📊 리뷰 버튼을 메인 화면에 추가하고, 누르면 별도의 review.html 페이지로 이동하도록 만들었어요. 일주일치 데이터를 PocketBase 서버에서 한꺼번에 가져와서 세 가지 뷰를 제공합니다.
색상별 시간 통계가 첫 번째예요. 파란색 블록(업무)에 40시간, 노란색 블록(점심)에 5시간, 빨간색 블록(회의)에 12시간… 이런 식으로 색상 카테고리별 누적 시간을 보여줍니다.
달성률이 두 번째인데, PLAN(계획) 대비 DONE(실행) 비율을 퍼센트로 보여줘요. 이게 은근히 동기부여가 됩니다. "이번 주 달성률 78%"라고 뜨면 "다음 주는 80% 넘겨야지!" 하는 욕심이 생기거든요.
일별 현황이 세 번째고요, 요일별로 계획 시간과 실행 시간을 막대 차트로 비교합니다.
데이터 파싱의 현실: 버전 호환성 문제
여기서 가장 까다로웠던 부분은 데이터 파싱이었어요. 우리 앱은 개발 과정에서 데이터 구조가 변했거든요. v2 버전에서는 PLAN과 DONE이 별도 칸으로 분리된 레이아웃이었고, v3에서는 하나의 통합 칸으로 바뀌었습니다. 그러니까 저장된 HTML payload에서 td(테이블 셀) 인덱스를 계산하는 로직이 버전마다 완전히 달라지는 거예요.
if (s.v === 3) {
// v3: 통합 레이아웃 — 행당 5칸
var OFF = 5, CPR = 5;
} else {
// v2: 분리 레이아웃 — 행당 9칸
var OFF = 11, CPR = 9;
}이걸 해결하기 위해 파서를 두 벌로 만들었습니다. 처음부터 데이터에 버전 필드(v)를 넣어둔 게 정말 다행이었어요. 만약 버전 정보 없이 저장했다면? 생각만 해도 아찔하네요. 앱을 만들 때 데이터 스키마에 버전 필드를 넣는 건 선택이 아니라 필수라는 걸 이번에 확실히 깨달았습니다.
다크모드 — 2026년엔 선택이 아니라 표준
왜 다크모드가 필수인가
2026년 웹 디자인 트렌드를 보면 다크모드는 이제 단순한 옵션이 아니에요. 사용자 기기의 시스템 설정에 따라 자동으로 라이트/다크가 전환되는 "적응형 다크모드"가 사실상 표준으로 자리 잡았거든요. CSS의 prefers-color-scheme 미디어 쿼리를 활용하면 운영체제 설정을 감지해서 웹사이트도 그에 맞춰 테마를 자동 전환할 수 있습니다.
하지만 저의 경우엔 트렌드보다 훨씬 절실한 이유가 있었어요. 밤 11시, 네 아이를 전부 재우고 나서 겨우 얻는 조용한 시간. 그때 새하얀 화면이 번쩍 켜지면… 네, 가장 가까이 자고 있던 막내가 뒤척이기 시작합니다. 다크모드는 저에게 생존 기술이었습니다. 😂
CSS 변수 기반 구현
구현 자체는 CSS 변수(Custom Properties)를 활용해서 깔끔하게 처리했어요. :root에 라이트모드 기본 색상을 정의하고, body.dark 클래스가 추가되면 해당 변수들을 다크 색상으로 오버라이드하는 방식입니다.
:root {
--bg: #f1f5f9;
--surface: #fff;
--text: #0f172a;
--border: #e2e8f0;
}
body.dark {
--bg: #0f172a;
--surface: #1e293b;
--text: #e2e8f0;
--border: #334155;
}상단 바에 🌙 토글 버튼을 넣고, 클릭하면 body.classList.toggle('dark')를 호출합니다. 그리고 선택한 테마 설정은 localStorage에 저장해서 새로고침하거나 다음 날 다시 접속해도 유지되도록 했어요.
이 방식의 장점은 CSS 변수만 바꿔주면 전체 UI 색상이 일괄 변경되기 때문에 유지보수가 매우 쉽다는 거예요. 새로운 컴포넌트를 추가할 때도 var(--bg), var(--text) 같은 변수만 쓰면 자동으로 다크모드가 적용되니까요.
함정: 파스텔 블록 위의 텍스트
그런데 다크모드를 켜고 나니 예상치 못한 문제가 생겼어요. 타임그리드에는 파스텔 톤의 색상 블록이 있잖아요. 연한 파란색, 연한 노란색, 연한 분홍색… 이런 파스텔 블록 안에 있는 라벨 텍스트가 다크모드에서 안 보이는 거예요.
왜냐하면 다크모드에서 텍스트 색상을 밝은 색(#e2e8f0)으로 바꿨는데, 파스텔 배경도 밝은 색이니까 밝은 글씨 + 밝은 배경 = 안 보임, 이라는 아주 당연한 결과가 나온 거죠. 해결책은 간단했어요. 파스텔 블록 안의 라벨 텍스트는 라이트/다크 모드에 관계없이 항상 어두운 색(검은색 계열)으로 고정했습니다. 파스텔 배경 자체가 언제나 밝으니까요.
한 가지 더, 인쇄 모드도 고려했어요. 다크모드 상태에서 주간 리뷰를 인쇄하면 검은 배경에 잉크를 잔뜩 쓰게 되니까, @media print 미디어 쿼리에서는 강제로 라이트 변수를 적용하도록 처리했습니다. 작은 디테일이지만 실제로 인쇄해본 사람이라면 이 배려가 얼마나 고마운지 알 거예요.

어제 PLAN 복사 — 템플릿의 가벼운 동생
템플릿까지는 필요 없는데, "어제랑 거의 비슷한 하루"인 경우도 꽤 많거든요. 그래서 📥 어제PLAN이라는 간편 버튼도 하나 만들었어요.
동작은 단순합니다. 버튼을 누르면 어제 날짜로 PocketBase를 조회하고, 거기서 PLAN 슬롯만 추출(DONE은 빼고)해서 오늘 타임그리드에 적용합니다. 템플릿처럼 이름을 지을 필요도 없고, 원클릭이에요.
v2/v3 데이터 모두 호환되도록 처리했고, 어제 데이터가 없으면 "어제 데이터가 없어요!" 라는 안내 메시지만 띄웁니다. 간단한 기능이지만 체감 편의성은 템플릿 못지않더라고요. 아침에 급할 때 특히 빛을 발합니다.
이번 편에서 배운 것들
이번 3화를 작업하면서 몇 가지 확실하게 깨달은 점이 있어요.
먼저 템플릿은 매일 쓰는 앱의 필수 기능이라는 것. 반복되는 패턴을 저장하고 불러오는 기능은 사용자 만족도에 직접적인 영향을 미쳐요. 직접 매일 쓰면서 만들어보니까 이게 없으면 진짜 불편하더라고요.
두 번째로 데이터 스키마에 버전 필드를 반드시 넣으라는 거예요. 앱이 진화하면 데이터 구조도 바뀌기 마련인데, 버전 정보 없이는 과거 데이터를 파싱할 방법이 없습니다. 처음부터 버전 필드를 넣어둔 과거의 저에게 감사 인사를 전합니다.
세 번째, CSS 변수 기반 다크모드는 구현이 쉽지만 색상 위에 색상이 겹치는 케이스를 놓치기 쉽다는 점이에요. 파스텔 배경 + 텍스트 조합처럼, 특정 색상 위에 올라가는 요소는 다크모드 변수를 따르지 않도록 별도로 고정해줘야 합니다.
마지막으로 DOM 인덱스 대신 클래스명으로 요소를 찾자는 교훈도 남겼어요. 반응형 레이아웃에서는 DOM 순서가 언제든 바뀔 수 있기 때문에, 인덱스에 의존하면 십중팔구 모바일에서 깨집니다.

마무리
이번 편에서는 매일 반복되는 귀찮음(템플릿), 한 주를 돌아보는 인사이트(주간 리뷰), 밤에 눈이 편한 환경(다크모드)을 해결했어요. 솔직히 기능 하나하나는 대단하지 않은데, 이 세 가지가 합쳐지니까 "매일 쓰고 싶은 앱"에 한 발짝 더 가까워진 느낌이 들어요.
특히 다크모드는 개발자이면서 동시에 아빠인 분들에게 강력 추천합니다. 밤에 애들 재우고 나서 조용히 하루를 정리하는 그 시간, 화면이 부드럽게 어두우면 눈도 편하고 마음도 편하거든요. 🌙
다음 편에서는 이 앱의 전체 레이아웃을 오버레이형 통합 구조로 전면 리디자인한 이야기를 다룰 예정이에요. 기대해 주세요!
👉 #1화 — 타임박싱이란? 앱 만들게 된 이유
👉 #2화 — PocketBase + Nginx + Proxmox 서버 인프라 구축
👉 #3화 — 멀티유저 인증과 데이터 동기화
👉 #4화 — 전면 리디자인: 오버레이형 통합 레이아웃
👉 #5화 — 모바일 최적화: 터치와의 전쟁
👉 #6화 — PWA: 웹앱을 앱처럼 쓰는 마법
👉 #7화 — 가족끼리 일정 공유
👉 #8화 — 가족끼리 일정 공유
'IT > AI' 카테고리의 다른 글
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #6 — PWA: 웹앱을 앱처럼 쓰는 마법 🪄 (0) | 2026.03.24 |
|---|---|
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #5 — 모바일 최적화: 터치와의 전쟁 (0) | 2026.03.23 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #4 — 전면 리디자인: 오버레이형 통합 레이아웃 (1) | 2026.03.22 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #2 — Nginx 리버스 프록시 + PocketBase로 셀프호스팅 서버 구축하기 (0) | 2026.03.19 |
| [AI] Claude와 함께 타임박싱 웹앱 만들기 #1 — 종이 플래너를 웹으로 옮긴 인프라 엔지니어의 바이브 코딩 도전기 (1) | 2026.03.18 |
| [AI] 월 구독료 0원의 반란: 홈랩 엔지니어가 Proxmox에 Ollama + Open WebUI로 나만의 ChatGPT를 만든 이유 (0) | 2026.03.15 |