Releases: hkjang/kanpic
Release list
v0.245.0
kanpic v0.245.0
v0.245.0
엑셀에서 붙여 넣은 사용자 명단을 그대로 올릴 수 있습니다
관리자가 사용자 명단을 일괄 등록… 에 넣는 가장 흔한 길은 파일을 고르는
것이 아닙니다. 엑셀에서 칸을 골라 복사해 대화상자에 그대로 붙여 넣는 것입니다.
그렇게 붙여 넣으면 열은 쉼표가 아니라 탭으로 갈려 옵니다. 소수점을 쉼표로
적는 로케일의 엑셀이 저장한 CSV 는 세미콜론으로 갈려 옵니다. 둘 다 이것이
나왔습니다.
머리글 줄에 user_id 열이 있어야 합니다
머리글 줄은 있었습니다. 첫 열이 user_id 였습니다. 서버의 parseUserCSV 가
쉼표만 보았기 때문에 머리글 전체가 한 칸이 되었고, 그 한 칸은 user_id 가
아니었습니다. 사람이 받는 것은 저 한 줄뿐이었고, 남은 길은 명단을 다른 도구에
넣어 쉼표로 다시 저장하는 것뿐이었습니다. 고치기 전에 두 꼴 모두 실제로 그렇게
되돌아오는 것을 확인했습니다.
같은 명단은 어느 문으로 들어와도 같은 명단입니다
워크북 가져오기와 IMPORTDATA 는 무엇이 열을 가르는지 이미 파일을 보고
정합니다(internal/delimited.Delimiter) — 앞 몇 줄을 가장 고르게 같은 수의
열로 가르는 것이 구분자이고, 아무것도 그러지 못하거나 둘이 동률이면 쉼표입니다.
v0.244.0 에서 사용자 명단의 인코딩을 이 규칙 안으로 들였듯, 이번에는 구분자도
같은 규칙으로 봅니다. 같은 회사에서 나온 같은 파일이 격자로 들어오면 표가 되고
명단으로 들어오면 되돌아올 이유가 없습니다.
메모 칸의 세미콜론은 구분자가 아닙니다
메모 칸에 "영업;서울;신규" 처럼 세미콜론이 든 쉼표 명단이 세미콜론 명단으로
읽히면 도리어 멀쩡한 파일이 망가집니다. 규칙은 따옴표 안을 세지 않고 동률에서
쉼표가 이기므로 그런 일은 없습니다. 테스트로 못 박아 두었습니다.
확인
internal/httpapi/user_import_test.goTestUserCSVReadsWhicheverSeparatorCutsTheRoster— 세미콜론 명단, 탭
명단, 메모 칸에 세미콜론이 든 쉼표 명단 세 가지를 모두 같은 한 사람으로
읽습니다
gofmt·go vet·go build·go test ./...(17 패키지 전체),
scripts/check-release-docs.sh, scripts/check-commit-identities.sh 를
돌렸습니다. 서버 안쪽의 읽기 규칙만 바뀌어 웹은 손대지 않았습니다.
올리실 때
서버가 사용자 명단의 열 구분자를 정하는 규칙만 바뀌었습니다. 마이그레이션은
없고 필수 런타임 환경 변수는 계속 POSTGRES_DSN 하나입니다. 일괄 등록의 파일
고르기는 아직 .csv 만 보이므로, 탭으로 갈린 명단은 파일로 고르는 대신
대화상자에 붙여 넣으시면 됩니다. v0.244.0 에서 올라오시는 분은 이미지만 바꿔
다시 띄우시면 됩니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.245.0.tar.gz.sha256
gzip -dc kanpic-v0.245.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.245.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.245.0.tar.gz.sha256
v0.244.0
kanpic v0.244.0
v0.244.0
윈도우에서 뽑은 사용자 명단을 그대로 올릴 수 있습니다
관리자가 사용자 명단을 뽑는 가장 흔한 길은 둘입니다. 윈도우 파워셸에서
Export-Csv 로 AD 에서 바로 뽑거나, 엑셀에서 "유니코드 텍스트" 로 저장하는
것입니다. 그 파일을 일괄 등록… 에서 고르면 이것이 나왔습니다.
머리글 줄에 user_id 열이 있어야 합니다
머리글 줄은 있었습니다. 첫 열이 user_id 였습니다. 두 도구는 UTF-16LE 로
쓰고 파일 맨 앞에 그것을 알리는 두 바이트(BOM)를 두는데, 화면은 고른
파일을 무엇이 적혀 있든 언제나 UTF-8 로 풀어 읽었습니다. 명단이 글자 아닌 것이
되니 머리글이 user_id 로 보이지 않았고, 사람이 받는 것은 저 한 줄뿐이었습니다.
무엇으로 적혔는지 짐작할 것이 하나도 없는 파일인데도 — 파일이 스스로 적어
두었으니까요 — 남은 길은 다른 도구를 열어 저장을 다시 하는 것뿐이었습니다.
이제 파일이 밝힌 대로 읽습니다. 표시를 두고 오는 UTF-8·UTF-16LE·UTF-16BE
명단은 그대로 올라갑니다.
같은 명단은 어느 문으로 들어와도 같은 명단입니다
v0.242.0 에서 워크북 가져오기와 IMPORTDATA 는 파일이 밝힌 대로 읽게
했습니다(internal/delimited.ToUTF8). 그런데 사용자 명단은 그 규칙 밖에
있었습니다. 같은 회사에서 나온 같은 파일이 격자로 들어오면 표가 되고 명단으로
들어오면 되돌아오는 셈이었습니다.
같은 규칙을 웹에 두었습니다(web/src/lib/fileText.ts). 서버의 parseUserCSV
도 남은 표시를 떼게 했습니다 — 화면은 이제 풀어서 보내지만, 명단을 읽는 답이
보내는 쪽이 무엇이냐에 따라 갈릴 이유는 없습니다. 엑셀의 "CSV UTF-8" 저장이
머리글 앞에 두는 표시도 이제 user_id 의 일부로 보지 않습니다.
짐작은 하지 않습니다
v0.242.0 과 같은 선입니다. 표시가 없는 파일은 손대지 않고 예전처럼 UTF-8 로
풉니다 — 짐작은 멀쩡한 파일을 망가뜨리는 쪽으로만 틀립니다. UTF-16LE 와
똑같은 두 바이트로 여는 UTF-32LE 는 반쯤 읽어 글자 아닌 명단을 만드는 대신
예전처럼 서버가 거절하게 둡니다.
가이드 PDF 의 다이어그램
사용자·관리자 가이드 PDF 에 다이어그램 대신 graph TD 로 시작하는 mermaid
스크립트가 그대로 실려 있었습니다. 문서를 굽는 도구가 mermaid 블록을 여느
코드 블록처럼 넘긴 탓입니다. 도구를 고치고 같은 원본에서 다시 구웠습니다.
본문은 바뀌지 않았습니다.
확인
web/src/lib/fileText.test.ts- 파워셸·엑셀이 쓰는 그대로의 UTF-16LE·UTF-16BE 명단과, 엑셀 "CSV UTF-8" 이
표시를 붙여 둔 명단을 읽어 보고, 브라우저가 스스로 푸는 결과는 명단이
아님을 함께 봅니다 - 아무것도 밝히지 않은 파일은 한 바이트도 손대지 않습니다
- UTF-32LE 를 UTF-16 으로 착각하지 않습니다
- 고른 파일에서 바이트를 받아 같은 규칙에 넘기는지 봅니다
- 파워셸·엑셀이 쓰는 그대로의 UTF-16LE·UTF-16BE 명단과, 엑셀 "CSV UTF-8" 이
internal/httpapi/user_import_test.goTestUserCSVReadsARosterThatSaysWhatItIs— 표시가 머리글에 붙은 채로 와도
첫 열을user_id로 읽습니다
gofmt·go vet·go build·go test ./... 전부와 웹의 npm test(492건)·
npm run lint·npm run build, scripts/check-release-docs.sh 를 돌렸습니다.
올리실 때
일괄 등록 화면이 파일을 읽는 규칙과 서버가 표시를 떼는 규칙만 바뀌었습니다.
마이그레이션은 없고 필수 런타임 환경 변수는 계속 POSTGRES_DSN 하나입니다.
예전에 거절당해 저장을 다시 하셨던 명단이 있다면 이제 원본 그대로 고르셔도
됩니다. v0.243.0 에서 올라오시는 분은 이미지만 바꿔 다시 띄우시면 됩니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.244.0.tar.gz.sha256
gzip -dc kanpic-v0.244.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.244.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.244.0.tar.gz.sha256
v0.243.0
kanpic v0.243.0
v0.243.0
가이드에 실린 화면이 실제로 띄워 찍은 화면입니다
사용자 가이드와 관리자 가이드에는 화면 캡처가 한 장도 없었습니다. 그 자리를
사람이 손으로 그린 표가 대신하고 있었습니다.
┌─────────────────────────────────────────┐
│ [로고] 홈 워크북 공유됨 [프로필] │
├─────────────────────────────────────────┤
│ + 새 워크북 │
└─────────────────────────────────────────┘
이런 그림은 처음 그릴 때부터 실물과 조금씩 다르고, 화면이 바뀌어도 아무도
고치지 않습니다. 단추가 어디 있는지 찾으려고 가이드를 편 사람은 없는 화면
을 보게 됩니다. 두 가이드가 마지막으로 손질된 것은 v0.18.0(2026-08-02)이었고,
그 사이에 홈 화면도 편집기도 관리자 콘솔도 여러 번 바뀌었습니다.
이번 릴리즈의 두 가이드에는 v0.242.0 을 실제로 띄워 1440×900 에서 찍은 19장
이 들어갑니다. 로그인, 홈, 가져오기, 편집기, 차트, 버전 이력, 공유, AI, 개인
설정, 관리자 콘솔의 개요·사용자·명단 올리기·워크북·부서·API 키·로그·설정·시스템
화면입니다.
찍는 일을 저장소가 합니다
캡처를 다시 찍는 도구를 저장소에 두었습니다(web/e2e/guide-screenshots.mjs).
로컬에 띄운 서버에 가짜 명단·워크북·부서·API 키를 채워 넣고 차례로 찍습니다.
다음에 화면이 바뀌면 다시 돌리면 됩니다 — 손으로 그린 그림에는 그런 길이
없었습니다.
찍을 대상과 로그인 정보는 캡처 전용 환경 변수로만 받습니다. 스크립트에는
주소도 아이디도 비밀번호도 적히지 않고, 값이 없으면 그 자리에서 멈춥니다.
버려도 되는 환경임을 사람이 매번 밝혀야 하며, 전역 설정은 읽기만 합니다.
사용자 가이드: 처음 5분과 막혔을 때
가이드를 처음 펴는 사람은 대개 지금 당장 워크북 하나를 만들어 봐야 하는
사람 입니다. 그런데 문서는 시스템 소개부터 시작했습니다.
- 처음 5분 — 로그인부터 워크북 만들기·수식·차트·공유까지 한 번에 지나가는
길을 맨 앞에 두었습니다. - 막혔을 때 — 화면에 실제로 나오는 오류 문구를 그대로 싣고, 그 문구가
나왔을 때 무엇을 하면 되는지 적었습니다. 문구를 그대로 실어야 사람이 화면과
문서를 눈으로 맞춰 볼 수 있습니다. - 용어 — 워크북·시트·범위·필터 뷰처럼 문서가 계속 쓰는 말을 한자리에
모았습니다.
관리자 가이드: 받은 파일에서 첫 로그인까지
관리자 가이드에는 정작 릴리즈 자산을 받아 처음부터 끝까지 설치하는 절차 가
없었습니다. 폐쇄망에 kanpic 을 올리는 사람이 가장 먼저 필요한 것이 그것입니다.
- 구성 요소·포트·볼륨 표
sha256sum -c로 받은 파일을 확인하고docker load부터 최초 관리자 로그인
까지 이어지는 설치 절차- 코드에서 읽어 적은 환경 변수 4개(
POSTGRES_DSN·BOOTSTRAP_ADMIN_ID·
BOOTSTRAP_ADMIN_PASSWORD·PORT)와 관리자 콘솔 설정 키 전수 표 - 상태 점검(
GET /healthz·GET /api/v1/version)과 업그레이드·되돌리기 - 증상 → 확인할 곳 → 로그 문구 → 조치 로 적은 장애 대응. 장애가 났을 때
사람이 손에 쥔 것은 증상뿐이므로 거기서 시작하도록 두었습니다. - 기본값 중 반드시 바꿔야 하는 것과 밖으로 열면 안 되는 포트
정본을 하나로 두었습니다
같은 가이드가 .md·.pdf·.html 세 벌로 있었고, 그중 HTML 사본은 이미지를
싣지 않는 방식으로 만들어져 캡처가 통째로 빠진 가이드 가 되었습니다. 어느
것이 정본인지 알 수 없는 상태로 세 벌을 두는 대신, HTML 사본을 지우고 문서
사이트의 링크를 PDF 로 옮겼습니다. PDF 는 공용 md2pdf 로 다시 만들었습니다
(사용자 가이드 85쪽).
확인
- 캡처 19장을 한 장씩 눈으로 확인했습니다 — 빈 목록이나 아직 도는 스피너를
찍은 것이 없는지, 실명이나 비밀값이 찍히지 않았는지 봤습니다. - 두 문서의 이미지 참조 22곳이 모두 실제 파일을 가리키는지 확인했습니다.
npm run lint,go build ./...,scripts/check-release-docs.sh,
scripts/check-commit-identities.sh
올리실 때
문서만 바뀌었습니다. 서버와 웹 코드는 한 줄도 손대지 않았고 마이그레이션도
없습니다. 필수 런타임 환경 변수는 계속 POSTGRES_DSN 하나입니다. v0.242.0 에서
올라오시는 분은 이미지만 바꿔 다시 띄우시면 됩니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.243.0.tar.gz.sha256
gzip -dc kanpic-v0.243.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.243.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.243.0.tar.gz.sha256
v0.242.0
kanpic v0.242.0
v0.242.0
윈도우에서 저장한 CSV 를 그대로 올릴 수 있습니다
파워셸에서 Export-Csv 로 내보낸 표를, 또는 엑셀에서 "유니코드 텍스트" 로
저장한 표를 가져오기에 끌어다 놓습니다. 그러면 이것이 나왔습니다.
CSV must be UTF-8 encoded
두 도구는 UTF-16LE 로 씁니다. 그리고 파일 맨 앞에 그것을 알리는 두 바이트
(BOM)를 둡니다. 무엇으로 적혔는지 짐작할 것이 하나도 없는 파일입니다 — 파일이
첫 두 바이트에 스스로 적어 두었으니까요. 그런데 가져오기는 그 두 바이트를 보고
"UTF-8 이 아니다" 라고만 판단해 업로드를 통째로 거절했습니다.
거절한 것은 내용이 아니라 표시 였습니다. 안에 든 것이 한글이든 영문 열 개
뿐이든 똑같이 거절되었습니다. 화면에는 인코딩을 고르는 자리가 없으니, 사람에게
남은 길은 다른 도구를 열어 저장을 다시 하는 것뿐이었습니다.
이제 파일이 밝힌 대로 읽습니다. 표시를 두고 오는 UTF-8·UTF-16LE·UTF-16BE 는
그대로 올라갑니다.
같은 파일은 어느 문으로 들어와도 같은 표입니다
읽는 규칙을 새 internal/delimited.ToUTF8 한 곳에 두고, 파일이 들어오는 두 문이
함께 씁니다.
- 올리기 — 파일을 끌어다 놓는 가져오기
- 부르기 —
=IMPORTDATA("https://…/report.csv")
열을 가르는 규칙이 이미 그 자리에 있습니다(v0.239.0). 무엇으로 적혔는지 읽는
일과 무엇으로 갈렸는지 읽는 일은 같은 판단의 앞뒤이므로 함께 두었습니다.
parseCSV 가 따로 떼고 있던 도 그 자리로 합쳤습니다 — 한쪽만 고쳐져
어긋나는 일이 없도록 규칙을 하나로 묶었습니다.
윈도우 도구가 서버에 올려 둔 UTF-16LE CSV 를 IMPORTDATA 로 불러도 이제 표가
됩니다. 예전에는 한 열짜리 글자 아닌 덩어리였습니다.
짐작은 하지 않습니다
표시가 없는 파일은 손대지 않습니다. 표시가 없으면 UTF-16 인지, 그 바이트를
우연히 담은 멀쩡한 파일인지 가릴 길이 없습니다. 짐작은 멀쩡한 파일을
망가뜨리는 쪽으로만 틀립니다. 그래서 EUC-KR 처럼 아무것도 밝히지 않는 파일은
예전처럼 거절합니다 — 저장하실 때 UTF-8 을 골라 주세요.
UTF-32LE 도 마찬가지입니다. 이 인코딩은 UTF-16LE 와 똑같은 두 바이트로
열립니다(뒤에 0 이 둘 더 붙습니다). 앞만 보고 UTF-16 으로 읽으면 반쯤 읽어
글자 아닌 것을 만들어 냅니다. 우리가 읽지 못하는 파일이니, 표를 흉내 내는 대신
예전처럼 거절합니다.
미리보기가 방금 고른 파일보다 작은 수를 말하지 않습니다
UTF-16 을 UTF-8 로 옮기면 한글이 아닌 글자는 대개 절반으로 줄어듭니다. 옮긴
뒤의 크기를 미리보기에 적으면, 탐색기에서 20KB 로 보이던 파일이 대화상자에서는
10KB 라고 나옵니다. 다른 파일을 고른 것처럼 보입니다.
크기는 옮겨 읽기 전에 잽니다. 올리신 파일의 크기 그대로입니다(XLSX 는 이미
그렇게 하고 있었습니다).
확인
internal/delimited/encoding_test.goTestToUTF8ReadsWhatTheFileSaysItIs— 표시가 있는 UTF-8·UTF-16LE·UTF-16BE 와
표시가 없는 파일 등 일곱 가지를 읽어 보고, 표시가 떨어졌는지 함께 봅니다TestToUTF8DoesNotMistakeUTF32ForUTF16— 같은 두 바이트로 여는 UTF-32LE 를
반쯤 읽지 않고 그대로 두는지 못 박습니다TestToUTF8GuessesNothing— 표시가 없는 파일은 한 바이트도 손대지 않습니다
internal/importexport/service_test.goTestImportReadsCSVThatSaysItIsUTF16— 파워셸이 쓰는 그대로, 모든 칸에
따옴표를 두르고 줄 끝을 CRLF 로 쓴 UTF-16LE CSV 를 올려 봅니다. 미리보기의
크기가 올린 파일의 크기인지, 영문뿐인 파일도 같은 이유로 거절되던 것이
풀렸는지 함께 봅니다
internal/external/fetcher_test.goTestImportDataReadsACSVThatSaysItIsUTF16— 같은 파일이 부르기로 들어와도
같은 표가 되는지 봅니다
gofmt·go vet·go build·go test ./... 전부와, 인코딩을 만지는
internal/{external,importexport,delimited} 는 -race 로도 함께 돌렸습니다.
올리실 때
서버가 파일을 읽는 규칙만 바뀌었습니다. 화면도, 이미 저장한 문서도 그대로이고
따로 하실 일은 없습니다. 예전에 거절당해 저장을 다시 하셨던 파일이 있다면 이제
원본 그대로 올리셔도 됩니다.
사용자 가이드의 가져오기 항목이 있지도 않은 인코딩 선택 자리(UTF-8·EUC-KR
고르기)를 안내하고 있어, 지금 읽는 대로 고쳤습니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.242.0.tar.gz.sha256
gzip -dc kanpic-v0.242.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.242.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.242.0.tar.gz.sha256
v0.241.0
kanpic v0.241.0
v0.241.0
내려받은 CSV·TSV 를 엑셀에서 열어도 한글이 깨지지 않습니다
표를 CSV 로 내려받아 두 번 눌러 엽니다. 그러면 이런 것이 나왔습니다.
지점紐� 留ㅼ텧
�쒖슱 媛뺣궓 1200
엑셀은 CSV 를 열 때 이 파일이 무슨 인코딩이냐고 묻지 않습니다. 파일 안에
UTF-8 이라는 표시가 없으면 그 컴퓨터의 기본 코드 페이지(한국어 윈도우라면
CP949)로 읽습니다. kanpic 이 내주는 파일은 UTF-8 이므로, 두 번 눌러 여는
평범한 길에서는 한글 칸이 통째로 깨져 보였습니다.
"데이터 → 텍스트 나누기" 로 들어가 인코딩을 골라 주면 제대로 나옵니다. 다만
내려받은 표를 여는 길은 대개 두 번 누르는 것이고, 그 길에는 인코딩을 고르는
자리가 없습니다.
같은 제품이 내주는 두 파일이 서로 달랐습니다
이 표시(BOM)를 붙이는 자리가 이미 있었습니다.
- 관리자 로그 내려받기(
/api/v1/admin/logs.csv) - AI 기록 내보내기
정작 한글이 가장 많이 담기는 워크북 내보내기만 붙이지 않았습니다. 같은
서버가 내주는 두 CSV 를 나란히 열면 하나는 멀쩡하고 하나는 깨져 보이던
셈입니다.
이제 워크북을 csv 로도 tsv 로도 내보낼 때 파일 앞에 같은 표시가 붙습니다.
되돌려 읽어도 첫 칸에 표시가 남지 않습니다
내보낸 파일을 다시 가져오는 길은 이미 이 표시를 떼고 읽고 있었습니다 —
올리기(parseDelimited)도, =IMPORTDATA(...) 도 그렇습니다. 그래서 내보내고
다시 올려도 첫 칸이 지점명 그대로입니다.
첫 칸이 중요한 이유는 그것이 대개 머리글 이기 때문입니다. 눈에 보이지 않는
표시가 머리에 얹혀 들어오면 그 열을 이름으로 가리키는 수식이 조용히 어긋납니다.
붙이는 자리와 떼는 자리가 이제 같은 utf8BOM 하나를 봅니다 — 한쪽만 고쳐져
어긋나는 일이 없도록 값을 하나로 묶었습니다.
확인
internal/importexport/service_test.goTestDelimitedExportMarksItselfAsUTF8—csv와tsv두 갈래로, 한글이
담긴 표를 내보낸 파일 앞에 표시가 붙는지와, 그 파일을 되돌려 읽은 첫 칸이
표시를 이고 있지 않은지를 함께 봅니다TestCSVExportEscapesFormulaInjection— 표시가 붙은 뒤에도 수식 주입 방어가
그대로인지 보도록 함께 고쳤습니다
올리실 때
서버가 내주는 파일의 첫 세 바이트만 달라집니다. 화면도, 이미 저장한 문서도
그대로이고 따로 하실 일은 없습니다. 이전 릴리즈에서 내려받아 둔 CSV 는 여전히
표시가 없으므로, 그 파일이 엑셀에서 깨져 보인다면 다시 내려받으시면 됩니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.241.0.tar.gz.sha256
gzip -dc kanpic-v0.241.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.241.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.241.0.tar.gz.sha256
v0.240.0
kanpic v0.240.0
v0.240.0
표 위의 제목 한 줄이 파일을 다시 한 열로 만들지 않습니다
지난 릴리즈에서 세미콜론으로 가른 CSV 를 올리기·부르기 두 문이 똑같이 읽게
했습니다. 그런데 현장에서 오는 파일은 표만 담겨 있지 않습니다. 보고서를
내보내는 도구는 대개 표 위에 제목을 한 줄 얹습니다.
2026년 1분기 보고서
지점;매출;비고
서울;1200;
부산;900;
열을 가르는 규칙은 첫 줄만 세었습니다. 첫 줄인 2026년 1분기 보고서 에는
쉼표도 세미콜론도 탭도 없습니다. 아무것으로도 갈리지 않는 줄이니 세어 봐야
전부 0 입니다. 그러면 쉼표가 이깁니다 — 그리고 이 파일에는 쉼표가 없으므로,
아래의 멀쩡한 표까지 통째로 A 열 한 줄짜리 글자 덩어리 가 됩니다.
지점;매출;비고 가 A1 에, 서울;1200; 이 A2 에 통으로 들어앉습니다. 수는
하나도 없으니 =SUM(B:B) 은 0 을 내놓습니다. 유럽식 CSV 를 위해 세미콜론을
세게 해 놓고도, 그 파일이 제목 줄을 이고 오면 고치기 전으로 돌아가던 셈입니다.
빈 줄 하나가 위에 붙어 있어도 마찬가지였습니다.
한 줄이 아니라, 뜻이 맞는 줄들이 정합니다
이제 머리의 열 줄까지 보고, 그 중 가장 많은 줄을 같은 열 수로 가르는
후보를 고릅니다. 위 파일에서 세미콜론은 세 줄을 나란히 3열로 가릅니다.
쉼표는 한 줄도 가르지 못합니다. 그래서 세미콜론이 이깁니다.
핵심은 가르지 못하는 줄은 세지 않는다 는 것입니다. 제목 줄은 어느
후보에 대해서도 아무 말을 하지 않으므로, 아래 줄들의 판단을 뒤집지 못하고
조용히 빠집니다. 제목에 쉼표가 들어 있어도(보고서, 1분기) 그 한 줄은 세
줄을 이기지 못합니다.
같은 수로 갈린다면 예전과 똑같이 쉼표가 이깁니다. 규칙이 바뀌면서 덤으로
얻은 것도 있습니다.
- 본문에 흩어진 세미콜론이 더는 파일을 가로채지 못합니다.
1,김,가;나와
2,박,다;라;마처럼 줄마다 개수가 다르면 세미콜론은 어느 열 수로도 뜻이
맞지 않는 반면, 쉼표는 매 줄을 3열로 가릅니다. - 짝이 맞지 않는 따옴표가 잃는 것은 그 줄 하나 뿐입니다. 예전에는 그
아래 전부가 따옴표 안으로 보여 아무것도 세지 못했습니다. - 따옴표 안은 여전히 세지 않습니다.
이름,"가;나;다;라"는 두 열입니다.
머리의 열 줄만 봅니다. 이십 메가바이트짜리 업로드를 가르는 것을 고르자고
파일 전체를 훑지 않습니다. 가져오기 쪽도 첫 줄만 잘라 넘기던 것을 앞
64 KiB(마지막 줄 끝까지)로 넓혔을 뿐, 본문 전체를 글자로 옮기지는 않습니다.
확인
internal/delimited/delimiter_test.goTestDelimiterFollowsTheLinesThatAgree— 제목 줄, 빈 첫 줄, 쉼표가 낀
제목, 제목 아래의 탭 파일, 본문에 섞인 세미콜론, 열 수가 들쭉날쭉한
파일까지 열일곱 가지를 갈라 봅니다TestDelimiterReadsOnlyTheHead— 머리 뒤에 천 줄을 더 붙여도 판단이
바뀌지 않는다는 것을 못 박습니다
internal/external/fetcher_test.goTestImportDataReadsWhicheverSeparatorCutsTheFile— 제목 줄을 인
세미콜론 표가 2열로 들어오고1200이 수로 읽히는지 봅니다
internal/importexport/service_test.goTestATitleRowAboveTheHeaderDoesNotDecideHowTheFileIsCut— 올리기로
들어온 같은 파일에도 같은 규칙이 걸린다는 것을 못 박습니다
서버 안쪽에서 파일을 읽는 규칙만 바뀌었습니다. 화면과 이미 저장한 문서는
그대로이고, 따로 하실 일은 없습니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.240.0.tar.gz.sha256
gzip -dc kanpic-v0.240.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.240.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.240.0.tar.gz.sha256
v0.239.0
kanpic v0.239.0
v0.239.0
세미콜론으로 가른 CSV 를 IMPORTDATA 도 표로 읽습니다
소수점을 쉼표로 적는 로케일에서 엑셀은 CSV 를 세미콜론 으로 가릅니다.
쉼표 자리는 이미 소수점이 가져갔기 때문입니다. 독일어·프랑스어 환경에서
"CSV(쉼표로 분리)" 로 내보낸 파일이 정작 이름;금액;메모 인 까닭입니다.
kanpic 에 그런 파일이 들어오는 문은 둘입니다.
- 올리기 — 파일을 끌어다 놓는 가져오기
- 부르기 —
=IMPORTDATA("https://…/price.csv")
같은 파일인데 두 문이 서로 다른 표를 내주었습니다.
올리기는 첫 줄에서 ,·;·\t 를 세어 가장 많은 것으로 갈랐으므로 제대로
읽었습니다. 부르기는 쉼표를 기본으로 두고, 첫 줄에 탭이 있고 쉼표가 없을
때만 탭으로 보았습니다. 세미콜론은 아예 후보가 아니었습니다. 그래서
품목;단가;비고
연필;1200;
는 한 열짜리 글자 덩어리 로 들어앉았습니다. 연필;1200; 이 통째로 A 열의
한 칸입니다. 수가 하나도 없으니 =SUM(A:A) 은 0 을 내놓고, 열을 가리키는
수식은 모두 빈손으로 돌아옵니다. 화면에 오류는 뜨지 않습니다 — 표가 들어왔고,
다만 열이 하나일 뿐이니까요. 파일을 내려받아 올려 보면 멀쩡하니, 무엇이
다른지 알아내기도 어렵습니다.
이제 열을 가르는 규칙은 internal/delimited 한 곳에 있고 두 문이 함께
씁니다. 어느 문으로 들어오든 같은 파일은 같은 표가 됩니다.
따옴표 안의 세미콜론은 글자입니다
규칙을 한데 모으면서 올리기 쪽에 있던 흠도 함께 고쳤습니다. 세는 자가
따옴표 안까지 셌습니다.
id,"가;나;다;라"
1,42
첫 줄의 세미콜론은 셋, 쉼표는 하나입니다. 그래서 쉼표로 가른 파일 전체가
세미콜론으로 갈렸습니다. 헤더의 한 칸이 세미콜론을 여럿 담았다는 것이
까닭의 전부입니다. 이제 따옴표 안은 세지 않으므로 이 파일은 두 열입니다.
- 짝이 맞지 않는 따옴표가 있으면 나머지 줄이 따옴표 안으로 보여 아무것도
세지 않게 되고, 그때는 쉼표로 돌아옵니다. - 수가 같으면 쉼표가 이깁니다(
a,b;c는 쉼표). - 첫 줄만 봅니다. 둘째 줄의 내용이 파일을 가르는 방식을 바꾸지 않습니다.
큰 파일을 올릴 때 첫 줄을 보려고 이십 메가바이트를 통째로 글자로 옮기던
자리도 함께 줄였습니다. 줄 하나만 잘라 넘깁니다.
확인
internal/delimited/delimiter_test.goTestDelimiterFollowsTheFirstLine— 쉼표·세미콜론·탭, 가르는 것이 없는
줄, 따옴표 안의 세미콜론과 쉼표, 짝이 맞지 않는 따옴표, 줄 끝의 CR 까지
열한 가지를 갈라 봅니다
internal/external/fetcher_test.goTestImportDataReadsWhicheverSeparatorCutsTheFile— 증상 그대로,
세미콜론으로 가른 표가 3열로 들어오고1200이 수인지, 따옴표 안의
쉼표가 칸을 가르지 않는지 봅니다. 고치기 전에는 깨지는 시험입니다
internal/importexport/service_test.goTestQuotedSeparatorsDoNotDecideHowTheFileIsCut— 헤더의 따옴표 안
세미콜론이 파일 전체를 가르지 못한다는 것을 못 박습니다
서버 안쪽에서 파일을 읽는 규칙만 바뀌었습니다. 화면과 저장한 문서는 그대로이고
따로 하실 일은 없습니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.239.0.tar.gz.sha256
gzip -dc kanpic-v0.239.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.239.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.239.0.tar.gz.sha256
v0.238.0
kanpic v0.238.0
v0.238.0
원격이 다시 멀쩡해지면 칸도 곧바로 따라옵니다
WEBSERVICE·IMPORTDATA 가 가져온 답은 external.cache_seconds 동안 담아
둡니다. 그런데 담아 두는 자리에는 답만이 아니라 실패도 들어갔고, 둘을
같은 시간만큼 붙들었습니다. 그래서 이런 일이 있었습니다.
- 어떤 칸이
=WEBSERVICE("https://api.example.com/…")로 값을 부릅니다. - 마침 저쪽이 배포 중이라
주소가 503 로 답했습니다가 뜹니다. - 1분 뒤 배포가 끝나고 원격은 멀쩡해집니다.
- 그런데 칸은 그대로
주소가 503 로 답했습니다입니다. 기본값이면 5분,
길게 잡아 두었다면 하루 까지 그렇습니다.
한 번의 시간 초과도 같았습니다. 저쪽이 잠깐 느렸다는 사실이 하루치 답으로
굳었습니다.
앞선 릴리즈에서 허용 호스트와 크기 상한·시간 제한은 캐시 바깥으로 나왔습니다
— 관리자가 고치면 그 자리에서 통합니다. 정작 이쪽에서 고칠 수 없는 저쪽
사정 만 가장 오래 남아 있던 셈입니다.
이제 실패를 두 가지로 나눠 봅니다.
- 원격이 책임지는 실패 는 최대 30초 만 담습니다. 오류 상태로
답했거나(주소가 503 로 답했습니다), 아예 닿지 못했거나(시간 초과·연결
실패), 응답을 읽다 끊긴 것입니다. 저쪽이 고쳐지면 곧 통합니다. - 이 응답의 성질이 정한 거절 은 예전 그대로
external.cache_seconds
동안 담습니다. 크기 상한을 넘음, 리다이렉트, 사설망 주소입니다. 같은
응답이면 언제나 같은 답이므로 다시 물어봐야 할 까닭이 없습니다.
external.cache_seconds 가 30초보다 짧으면 그 값을 그대로 씁니다. 실패를
성공보다 오래 붙들지 않습니다.
아예 담지 않는 쪽은 고르지 않았습니다. 닿지 못하는 주소를 부르는 칸이
여럿이면 다시 계산할 때마다 external.timeout_seconds 를 처음부터 새로
기다리게 됩니다. 30초는 그 값이 통째로 되풀이되는 것을 막으면서, 원격이
돌아온 것을 알아채기에는 충분히 짧습니다.
확인
internal/external/fetcher_test.goTestRemoteFailuresAreKeptOnlyBriefly— 증상 그대로, 503 이 30초 안에는
담긴 채로 나오고 그 뒤에는 다시 부르는지, 같은 시험에서 크기 상한 거절은
cache_seconds내내 담긴 채로 있는지 두 갈래를 갈라 봅니다
go test -race ./internal/external/로 캐시를 함께 쓰는 길을 다시 못
박았습니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.238.0.tar.gz.sha256
gzip -dc kanpic-v0.238.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.238.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.238.0.tar.gz.sha256
v0.237.0
kanpic v0.237.0
v0.237.0
외부 호출 크기 상한을 올리면 그 자리에서 통합니다
WEBSERVICE·IMPORTDATA 가 가져온 답은 external.cache_seconds 동안 담아
둡니다. 같은 주소를 여러 칸이 부를 때 남의 서버를 두드리지 않기 위해서입니다.
그런데 담아 두는 자리에 답만이 아니라 거절도 들어갔고, 그 자리를 부른
함수와 주소만으로 이름 지었습니다. 그래서 이런 일이 있었습니다.
- 어떤 칸이
=IMPORTDATA("https://…/big.csv")로 1.5 MB 짜리 표를 부릅니다. external.max_kb가 1024 라서 칸에 "응답이 허용된 크기를 넘습니다" 가
뜹니다.- 관리자가 콘솔에서
external.max_kb를 4096 으로 올립니다. - 그런데 칸은 그대로 "응답이 허용된 크기를 넘습니다 (1024 KB)" 입니다.
기본값이면 5분, 길게 잡아 두었다면 하루까지 그렇습니다.
관리자에게는 설정이 먹지 않은 것으로 보입니다. 실제로는 옛 상한에서 받은
거절을 캐시가 내주고 있을 뿐인데, 그것을 알 길이 없습니다.
허용 호스트 목록은 이미 캐시 바깥에 있었습니다 — external.allowed_hosts 를
고치면 캐시가 살아 있어도 곧바로 통합니다. 크기 상한과 시간 제한도 부르는
방법을 정하는 정책 이라는 점에서 다르지 않습니다. 옛 상한에서 받은 답은 새
상한에 대한 답이 아닙니다.
이제 캐시의 자리 이름에 그때의 external.max_kb 와
external.timeout_seconds 가 함께 들어갑니다.
- 상한을 올리면 담아 둔 거절을 내주지 않고 다시 부릅니다.
- 상한을 내리면 담아 둔 큰 답을 내주지 않고 다시 부릅니다. 새 상한을
넘으면 거절합니다. - 정책이 그대로면 예전과 똑같이 담아 둔 것을 내줍니다. 남의 서버를 두 번
두드리지 않습니다.
확인
internal/external/fetcher_test.goTestRaisingTheSizeCeilingIsBelievedAtOnce— 증상 그대로, 상한을 올리고
내릴 때마다 캐시가 옛 정책의 답을 내주지 않는지, 정책이 그대로면 다시
부르지 않는지TestResolveIsSafeToShare—Fetcher는 함께 써도 안전하다고 적혀
있으므로, 여덟 갈래가 같은 주소들을 한꺼번에 부르는 길을-race로
못 박았습니다
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.237.0.tar.gz.sha256
gzip -dc kanpic-v0.237.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.237.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.237.0.tar.gz.sha256
v0.236.0
kanpic v0.236.0
v0.236.0
"NaN" 한 칸 때문에 파일이 통째로 들어오지 못하던 것을 고쳤습니다
pandas 나 R 로 자료를 다듬어 CSV 로 내보내면 빠진 값이 NaN, 무한대가
Inf·-Inf 라는 글자로 적혀 나옵니다. 흔한 일입니다. 그런데 그런
파일을 kanpic 에 올리면 가져오기가 통째로 실패하고, 화면에는
json: unsupported value: NaN
이라는 말만 떴습니다. 몇 번째 줄 몇 번째 칸이 문제인지도, 무엇을 고쳐야
하는지도 적혀 있지 않았습니다.
까닭은 값을 수로 읽는 자가 두 벌이었기 때문입니다. 수식 엔진과 격자는
스프레드시트가 수라고 부르는 것 만 수로 읽는데, 가져오기만 Go 가 수라고
부르는 것 을 그대로 받았습니다. Go 는 NaN·Inf·Infinity 를 물론이고
1_000(밑줄로 자리를 가른 것)과 0x1p4(16진 실수)까지 수로 읽습니다.
이제 파일을 읽어 들이는 자리도 엔진과 같은 자를 씁니다.
NaN·Inf·Infinity는 글자로 들어옵니다. 파일은 통째로 들어오고,
그 칸에는 파일에 적힌 그대로가 남습니다.1_000은 조용히1000이 되지 않습니다. 예전에는 밑줄이 사라진 값이
칸에 남아, 파일에 적힌 것과 다른 것을 보게 되었습니다.0x1p4도16이 되지 않습니다.- 평범한 수는 그대로 수입니다 —
1000,-2.5,1e3,.5,5.입니다.
CSV·TSV 와 XLSX 가 모두 같습니다. IMPORTDATA 로 가져오는 CSV 도 같은 자를
씁니다.
NaN 이 담긴 칸은 글자이므로 =SUM 이 조용히 빼고 셈합니다. 그것은 예전부터
그랬고 맞는 동작입니다 — 빠진 값을 0 으로 세면 평균이 달라집니다.
확인
internal/importexport/scalar_test.goTestParseScalarKeepsGoOnlyNumbersAsText— Go 만 수로 읽는 열 가지가
글자로 남고 JSON 으로 적히는지TestOneExportedNaNDoesNotStopTheWholeImport— 증상 그대로,NaN과
inf가 섞인 CSV 가 통째로 들어오는지TestParseXLSXValueKeepsGoOnlyNumbersAsText— XLSX 의 형식 없는 칸과
숫자 칸도 같은 자를 쓰는지
- 수로 읽는 자는 이제
internal/formula의DecimalNumber한 곳입니다.
internal/external이 따로 들고 있던 같은 규칙을 지웠습니다.
testdata/numeric-text.json이 엔진과 격자를 함께 붙들어 둡니다.
오프라인 설치
이 릴리즈는 웹 자산과 서버 바이너리를 포함한 Docker 이미지 아카이브로 제공됩니다.
sha256sum -c kanpic-v0.236.0.tar.gz.sha256
gzip -dc kanpic-v0.236.0.tar.gz | docker load
docker run --rm -p 8080:8080 \
-e POSTGRES_DSN='postgres://kanpic:password@postgres.internal:5432/kanpic?sslmode=require' \
kanpic:v0.236.0- 필수 런타임 환경 변수:
POSTGRES_DSN - Redis 및 외부 인터넷 연결: 불필요
- 관리자 초기 로그인을 보호하려면
BOOTSTRAP_ADMIN_ID와BOOTSTRAP_ADMIN_PASSWORD를 함께 지정합니다. - 무결성 검증 파일:
kanpic-v0.236.0.tar.gz.sha256