약속을 적어 두기는 하는데, 정작 그날 아침에 우산을 챙길지 겉옷을 걸칠지 몇 시에 나갈지는 매번 따로 찾아봅니다. 일정 앱이 시간과 장소를 이미 알고 있는데도 그렇습니다. 그 두 개만 있으면 나머지는 붙일 수 있겠다 싶어서 만들었습니다.

만드는 것 자체는 하루 만에 됐습니다. 정작 오래 붙든 것은 화면에 두지 않기로 한 것들이었습니다. 세 번 지웠고, 셋 다 "그럴듯한데 근거를 못 대는 숫자"였습니다.

첫 번째로 지운 것 — 경로 서버가 주는 소요시간

이동 시간은 이 앱에서 가장 먼저 보이는 숫자입니다. 약속 시각에서 이동 시간을 빼면 출발 시각이 나오고, 일정을 열었을 때 실제로 궁금한 건 그 값이니까요.

거리는 OSRM 공개 서버에서 받습니다. 키가 없어도 되고 실제 도로를 따라 잰 값입니다. 그런데 OSRM 은 거리와 함께 소요시간도 줍니다. 그걸 그냥 쓰면 코드가 한 줄로 끝납니다.

받아 보니 성수동에서 강남역 8.3km 를 9분이라고 했습니다. 정체를 전혀 반영하지 않는 값이기 때문입니다. 평일 저녁에 그 구간을 9분에 가는 일은 없습니다.

그래서 거리만 쓰고 시간은 직접 계산합니다. 거리 구간별 평균 속도에 출퇴근·심야·주말·비 계수를 곱합니다. 이것도 추정이지만, 추정이라고 화면에 쓰고 어떤 속도로 계산했는지 펼쳐 볼 수 있게 해 뒀습니다. 근거를 댈 수 없는 정확한 숫자보다, 근거를 댈 수 있는 어림값이 낫습니다.

다만 이 추정치도 아직 실제 지도앱과 대조해 본 적이 없습니다. 알려진 구간에서 터무니없지 않은 범위인지만 테스트로 묶어 뒀습니다. 쓰면서 카카오맵 결과와 비교해 계수를 손봐야 합니다.

두 번째로 지운 것 — 근처 맛집 목록

"약속 장소 근처 맛집"은 이런 앱이라면 당연히 있을 법한 기능입니다. 만들려고 보니 키 없이 쓸 수 있는 상점 데이터는 OSM 뿐인데 두 군데서 막혔습니다.

  • Overpass 는 CORS 헤더를 주지 않습니다. 브라우저가 직접 부를 수 없습니다.
  • 한국 OSM 상점 데이터가 너무 얇습니다. 강남역 반경 500m 에서 이름 있는 음식점이 21곳 나왔습니다. 실제로는 수백 곳입니다.

그 21곳을 "근처 맛집"이라고 내놓으면 목록이 아니라 거짓말입니다. 사용자는 그게 전부인 줄 알 테니까요.

그래서 규칙은 검색어까지만 정합니다. 시간대와 날씨, 격식으로 "저녁에 비 오고 거래처와 만나는 자리면 조용한 실내, 국물"까지 정해 주고, 실제 가게는 카카오맵 검색으로 넘깁니다. 목록을 만드는 대신 무엇을 찾아야 하는지를 정해 주는 쪽으로 물러난 것입니다.

세 번째로 지운 것 — 예상치로 메운 지출 통계

통계 화면에서 이번 달 지출을 더할 때, 실제 금액을 입력하지 않은 일정이 반드시 섞입니다. 빈칸을 예상치로 메우면 합계가 깔끔하게 나옵니다. 그런데 그 순간 "이번 달 20만원 썼다"가 사실이 아니라 계획이 됩니다.

지금은 실제로 입력한 것만 더하고, 몇 건 중 몇 건을 입력했는지 화면에 같이 씁니다. 위 스크린샷의 "5건 중 2건 입력"이 그것입니다.

한 가지가 더 있었습니다. 예상과 실제를 비교할 때 전체 예상(5건)과 일부 실제(2건)를 나란히 놓으면 항상 "예산보다 덜 썼다"가 나옵니다. 언제 봐도 칭찬해 주는 통계는 통계가 아닙니다. 그래서 실제를 입력한 그 일정들의 예상 합만 견줍니다. 화면에는 둘 다 있습니다 — 같은 모집단끼리 비교한 값이 위에, 전체 예상은 참고로 아래에.

거리도 같습니다. 좌표가 있는 일정만 세고, 빠진 건수를 밝힙니다. 좌표 없는 일정을 0km 로 세면 합계가 조용히 줄어듭니다.

다 만들고 숨긴 것 — 서버 동기화

PIN 하나로 기기 사이에 일정을 맞추는 동기화를 먼저 만들었습니다. 혼자 쓸 때는 맞는 설계였습니다. 그런데 앱을 공개하기로 하니 세 가지가 한꺼번에 무너졌습니다.

  1. 다른 사람은 PIN 을 모르니 동기화가 아예 안 됩니다. 있는 척하면 거짓말이 됩니다.
  2. 저장소에 사용자 구분 컬럼이 없습니다. 누가 PIN 을 알아내면 그 안의 모든 일정을 읽습니다.
  3. 잠금 카운터가 전역이라, 아무나 스무 번 틀려서 주인을 잠글 수 있었습니다.

(3)은 IP 별 잠금으로 고쳤습니다. (1)과 (2)는 코드로 못 고칩니다 — 사용자별 공간 분리를 새로 만들어야 하는 일입니다. 그래서 동기화 화면 자체를 숨겼습니다(설정 맨 아래 버전 줄을 일곱 번 누르면 나옵니다).

대신 정식 방법은 파일 하나입니다. 일정을 내보내 메신저나 메일로 보내고, 받는 기기에서 가져옵니다. 가져오기는 덮어쓰지 않고 합칩니다 — 같은 일정은 나중에 고친 쪽이 남고, 한쪽에서 지운 일정은 툼스톤으로 전해집니다. 서버 동기화와 같은 병합 규칙을 씁니다. 규칙을 두 벌로 두면 한쪽만 고치게 되니까요.

남의 일정을 제 계정에 보관하지 않겠다는 결정이고, 제대로 된 다중 사용자 동기화가 필요해지기 전까지는 이 상태를 유지합니다.

설명서가 뒤처지지 않게 막아 둔 것

사용설명서는 앱 안에 있습니다. 오프라인에서도 열려야 하고, 사이트가 없어도 동작해야 하니까요. 문제는 설명서가 반드시 뒤처진다는 것입니다.

그래서 설명서에 GUIDE_FOR_VERSION 을 두고, 그 값이 앱 버전과 다르면 테스트가 실패하게 했습니다. 설명서를 손대지 않고 버전만 올리면 빌드가 멈춥니다. 사람의 의지에 맡기면 안 되는 일은 기계가 막게 하는 편이 낫습니다.

화면 길이도 재서 나눴습니다

폰 한 화면이 844px 입니다. 그 2배(1,688px)를 넘는 화면은 나눕니다. 나누는 방법은 둘이고 쓰임이 다릅니다 — 배타적인 관점 중 하나를 고를 때는 탭(상세·통계), 제목 목록에서 찾을 때는 접기(설정·설명서)입니다.

여기서 한 번 틀렸습니다. 상세 화면만 고치고 나머지를 재지 않아서 설정·통계·설명서가 2.3~4.4화면으로 남아 있었습니다. 지금은 모든 화면의 길이를 한 번에 재는 스크립트를 돌리고 그 결과를 파일로 남깁니다.

지금 상태

  • 웹판은 /plan/ 에서 바로 쓸 수 있습니다. 설치도 로그인도 없습니다.
  • 안드로이드 앱은 구글 플레이 비공개 테스트 중입니다(2026-09-04). 테스터로 등록된 계정이면 스토어에서 받을 수 있습니다.
  • 일정은 기기 안에만 있습니다. 서버로 올라가지 않습니다.
  • 이동 시간·비용은 추정치이고, 실제 지도앱과 대조한 검증은 아직 없습니다.

키(카카오·ODsay)가 들어오면 추정이 실제 경로 조회로 바뀝니다. 그때가 되면 이 글의 첫 번째 항목은 지워질 것입니다. 그 전까지는 화면에 "추정"이라고 적힌 채로 둡니다.