kanpic
요약. kanpic: 자율 개선 회차 18회, 릴리즈 15건. 최근 릴리즈 v0.243.0 (자산 2개). 건강 B
- 18회차
- 1프로젝트
- 15배포 준비 완료
- 0릴리즈 진행 중
- 1병합 완료
- 1검토 대기
- 0검증 실패
- 0변경 없음
- 1실행 오류
- $43.18비용
- 1시간 37분에이전트 시간
현황
- 저장소
- https://github.com/hkjang/kanpic
- 마지막 회차
- 2026-09-11 05:42 KST — 🚀 릴리즈 merged PR #17, released v0.243.0
- 최근 릴리즈
- v0.243.0 — released · 자산 2개 (이전 v0.242.0: 2개) 전체 릴리즈 →
회차 이력
| 일시 | 프로젝트 | 결과 |
|---|---|---|
| 2026-09-11 05:42 | kanpic | 배포 준비 완료 merged PR #17, released v0.243.0 |
| 2026-09-10 21:02 | kanpic | 검토 대기 review held, PR open PR #16 |
| 2026-09-10 20:41 | kanpic | 실행 오류 hold: budget |
| 2026-09-10 14:19 | kanpic | 배포 준비 완료 merged PR #15, released v0.242.0 |
| 2026-09-10 00:27 | kanpic | 배포 준비 완료 merged PR #14, released v0.241.0 |
| 2026-09-09 09:10 | kanpic | 배포 준비 완료 merged PR #13, released v0.240.0 |
| 2026-09-09 02:26 | kanpic | 배포 준비 완료 merged PR #12, released v0.239.0 |
| 2026-09-08 21:33 | kanpic | 배포 준비 완료 merged PR #11, released v0.238.0 |
| 2026-09-07 02:17 | kanpic | 배포 준비 완료 merged PR #10, released v0.237.0 |
| 2026-09-05 04:35 | kanpic | 배포 준비 완료 merged PR #9, released v0.236.0 |
| 2026-09-04 09:11 | kanpic | 배포 준비 완료 merged PR #8, released v0.235.0 |
| 2026-09-03 23:21 | kanpic | 병합 완료 merged PR #7, release missing |
| 2026-09-03 15:33 | kanpic | 배포 준비 완료 merged PR #6, released v0.233.0 |
| 2026-09-03 15:10 | kanpic | 배포 준비 완료 merged PR #5, released v0.232.0 |
| 2026-09-03 08:49 | kanpic | 배포 준비 완료 merged PR #4, released v0.231.0 |
| 2026-09-03 03:06 | kanpic | 배포 준비 완료 merged PR #3, released v0.230.0 |
| 2026-09-02 20:56 | kanpic | 배포 준비 완료 merged PR #2, released v0.229.0 |
| 2026-09-02 14:38 | kanpic | 배포 준비 완료 merged PR #1, released v0.227.0 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 05:32 | kanpic | 릴리즈 | 3분 | 27 | $1.09 | 921K / 10K | success |
| 05:26 | kanpic | review | 5분 | 46 | $2.65 | 2.4M / 21K | success |
| 05:21 | kanpic | 개선 | 22분 | 147 | $14.77 | 21.5M / 73K | success |
| 21:02 | kanpic | review | 0분 | 0 | $0.00 | 0 / 0 | unknown |
| 20:46 | kanpic | 개선 | 6분 | 42 | $2.39 | 2.3M / 23K | success |
| 14:10 | kanpic | 릴리즈 | 2분 | 27 | $1.02 | 880K / 9K | success |
| 14:03 | kanpic | review | 4분 | 26 | $1.28 | 1.1M / 14K | success |
| 13:58 | kanpic | 개선 | 8분 | 56 | $3.56 | 3.7M / 32K | success |
| 00:16 | kanpic | 릴리즈 | 2분 | 20 | $0.69 | 456K / 7K | success |
| 00:07 | kanpic | review | 2분 | 13 | $0.70 | 465K / 7K | success |
| 00:05 | kanpic | 개선 | 4분 | 35 | $1.94 | 1.8M / 16K | success |
| 08:59 | kanpic | 릴리즈 | 2분 | 20 | $0.75 | 513K / 7K | success |
| 08:52 | kanpic | review | 4분 | 13 | $0.89 | 455K / 15K | success |
| 08:48 | kanpic | 개선 | 7분 | 45 | $2.61 | 2.4M / 29K | success |
| 02:16 | kanpic | 릴리즈 | 2분 | 20 | $0.80 | 539K / 8K | success |
| 02:08 | kanpic | review | 3분 | 13 | $0.75 | 407K / 11K | success |
| 02:05 | kanpic | 개선 | 4분 | 34 | $1.52 | 1.2M / 18K | success |
| 21:23 | kanpic | 릴리즈 | 2분 | 21 | $0.74 | 484K / 7K | success |
| 21:15 | kanpic | review | 2분 | 11 | $0.70 | 382K / 8K | success |
| 21:12 | kanpic | 개선 | 4분 | 20 | $1.32 | 903K / 14K | success |
| 02:15 | kanpic | 릴리즈 | 1분 | 19 | $0.60 | 402K / 5K | success |
| 02:07 | kanpic | review | 2분 | 12 | $0.74 | 437K / 8K | success |
| 02:05 | kanpic | 개선 | 5분 | 30 | $1.67 | 1.3M / 17K | success |
아이디어 백로그 — 대기 9 / 전체 10
| 아이디어 | 가치/위험/크기 | 상태 | 메모 | 갱신 |
|---|---|---|---|---|
| parseUserCSV 는 쉼표만 보아 세미콜론으로 가른 명단을 한 열로 읽는다 | 3/2/S | 대기 | 워크북 가져오기와 IMPORTDATA 는 delimited.Delimiter 를 쓰는데 관리자 사용자 명단만 csv.NewReader 기본 쉼표다. 소수점을 쉼표로 적는 로케일의 엑셀 명단은 머리글 전체가 한 칸이 되어 '머리글 줄에 user_id 열이 있어야 합니다' 로 되돌아온다. 메모 칸에 세미콜론이 들어갈 수 있어 한 번 재 볼 것. | 2026-09-11 |
| csvNumber 자리가 IMPORTDATA 의 "1,200"·"12%"·통화 기호를 글자로 남긴다 | 3/2/S | 대기 | 이제 formula.DecimalNumber 한 곳이다. valueOfText 의 규칙까지 넓힐지, 가져오기 미리보기의 '글자로 담긴 숫자' 경고와 같은 자리에 둘지 정할 것. | 2026-09-11 |
| 홈 화면 워크북 카드의 미리보기가 값이 있는 워크북에서도 빈 격자다 | 3/2/M | 대기 | 캡처하다 발견했다. 셀을 채워 넣은 워크북 네 개가 모두 빈 격자 섬네일로 그려졌다. 미리보기를 언제 무엇으로 만드는지(생성 시점? 저장 시점?)부터 확인할 것 — 가장 먼저 보이는 화면이라 눈에 띈다. | 2026-09-11 |
| 서버 formatValue 와 격자가 숫자처럼 생긴 글자 값에서 갈린다 | 3/3/M | 대기 | 서버의 formatValue 는 글자 "123" 을 toNumber 로 수로 읽고 격자는 글자로 보아 같은 칸을 두 곳이 다르게 그릴 수 있다. 어느 쪽에 맞출지 먼저 정해야 해서 위험이 낮지 않다. | 2026-09-11 |
| 분수 서식의 ? 자리 맞추기 빈칸을 그리지 않는다 | 2/1/S | 대기 | 엑셀은 '# ??/??' 의 한 자리 분자를 빈칸으로 채워 자릿수를 맞춘다. 고칠 때는 internal/formula 와 web/src/lib/cellFormat.ts 양쪽을 같은 규칙으로. | 2026-09-11 |
| IMPORTDATA 의 CSV 파싱 실패도 cache_seconds 만큼 담긴다 | 2/2/S | 대기 | parseCSV 가 내는 #VALUE! 는 원격 본문의 성질이라 remoteFailure 갈래에 넣지 않았다. 원격이 파일을 고쳤을 때의 회복이 cache_seconds 만큼 늦다. | 2026-09-11 |
| 파이프(|)로 가른 파일을 후보에 넣지 않는다 | 2/2/S | 대기 | 로그와 DB 덤프가 흔히 '|' 로 가르는데 separators 에 없어 한 열로 들어온다. 마크다운·ASCII 표가 함께 걸릴 수 있어 재 볼 것. | 2026-09-11 |
| 가이드 캡처를 CI 나 릴리즈 절차에서 다시 찍게 한다 | 2/2/M | 대기 | 이번에 도구는 생겼지만 화면이 바뀌어도 아무도 다시 찍지 않으면 다시 낡는다. 릴리즈 전에 한 번 돌려 달라진 캡처만 커밋하는 자리를 만들지 검토. | 2026-09-11 |
| detectDelimiter 는 \n 만 줄 끝으로 보아 CR 만 쓰는 옛 파일을 한 줄로 읽는다 | 1/1/S | 대기 | encoding/csv 도 그런 파일을 한 줄로 읽으므로 가르는 것만 고쳐서는 부족하다. 파일 전체를 어떻게 읽을지의 문제라 범위가 넓다. | 2026-09-11 |
| 가이드 두 벌을 실제 화면 캡처가 들어간 완성본으로 (캠페인 guides-2026-09) | 4/1/M | 완료 | v0.18.0 에 멈춰 있던 두 가이드를 v0.242.0 기준으로 보강하고, 실제로 띄운 화면 19장을 찍어 실었다. 캡처 도구는 web/e2e/guide-screenshots.mjs, PDF 는 공용 md2pdf. 옛 HTML 사본을 지우고 사이트 링크를 PDF 로 옮겨 정본을 하나로 두었다. | 2026-09-11 |
원장 (에이전트가 남긴 기록)
2026-09-02
- 선택: 엑셀 규칙과 어긋난 서식 함수(PROPER·FIXED·DOLLAR) 수정 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: PROPER 가 빈칸·탭·붙임표·밑줄 뒤만 낱말의 처음으로 보아
PROPER("o'neil")→”O’neil”,PROPER("76budget")→”76budget” 을 내던 것을 엑셀 규칙대로 “글자가 아닌 것 뒤는 모두 낱말의 처음”(unicode.IsLetter)으로 고쳤고,formatNumber가 음수 자릿수를 0 으로 깎아FIXED(1234.567,-2)→”1,235” 를 내던 것을 소수점 왼쪽 반올림(“1,200”)으로 고쳤다(ROUND 와 답이 어긋나 있었다). 덤으로 ea6fd45 가 잘못 담은 18MBworkbook.test실행 파일을 지우고.gitignore에*.test를 넣었다. 검증:gofmt -l,go build ./...,go test ./...(전체 통과),cd web && npm test(480개 통과) 및npm run build,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 3개(03dda1d, e50238e, c4ae80d). - 보류 아이디어:
- CEILING/FLOOR 가
number*factor < 0이면 무조건 #NUM! 을 내는데, 엑셀·구글 시트는CEILING(-4.5,2)=-4,FLOOR(-4.5,2)=-6 을 낸다(MROUND 에만 맞는 규칙). 참조 구현으로 확인 후 고칠 것. internal/external/fetcher.go의 응답 캐시가 만료 항목을 지우지 않아 무한히 커진다. 만료 정리 + 상한 필요.- 같은 캐시가 실패도 담아, 관리자가 allowed_hosts 를 고쳐도 최대 5분간 “허용되지 않은 호스트” 가 남는다. 설정 변경 시 무효화 필요.
internal/external/fetcher.go의short()가 160바이트에서 자르며 UTF-8 글자를 쪼갠다(한글 오류 메시지가 깨진다).NUMBERVALUE미구현(#NAME?),VALUE("12:00")이 시각을 읽지 못한다.
- CEILING/FLOOR 가
- 릴리즈: v0.227.0 (2026-09-02)
2026-09-02
- 선택: CEILING·FLOOR 의 음수 배수 규칙을 엑셀에 맞춘다 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
evaluateMath가number*factor < 0이면 CEILING·FLOOR·MROUND 를 모두 #NUM! 로 냈는데, 그것은 MROUND 에만 맞는 규칙이라 엑셀·구글 시트가 답을 내는CEILING(-4.5,2)(=-4),FLOOR(-4.5,2)(=-6),CEILING(-2.5)(=-2) 가 오류였다. CEILING·FLOOR 는 “양수 + 음의 배수” 만 #NUM! 로 남기고, 몫이 음수일 때는 0 에서 멀어지는 쪽과 위쪽이 반대이므로 반올림 방식(roundAwayFromZero/roundTowardZero)을 몫의 부호에서 뒤집도록 고쳤다 — 십진 셈(CEILING(-0.1-0.2,0.1)=-0.3)과 부호가 같은 기존 답은 그대로다. 검증: 엑셀 도움말의 부호 표를 옮긴TestCeilingAndFloorFollowExcelSignRules를 더하고gofmt -l,go build ./...,go test ./...(전체 통과),cd web && npm ci && npm test(480개 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(1663756, d98ec44), 릴리즈 노트 v0.229.0 과 README VERSION·USER_GUIDE 갱신. - 보류 아이디어:
internal/external/fetcher.go의 응답 캐시가 만료 항목을 지우지 않아 무한히 커진다. 만료 정리 + 상한 필요.- 같은 캐시가 실패도 담아, 관리자가 allowed_hosts 를 고쳐도 최대 5분간 “허용되지 않은 호스트” 가 남는다. 설정 변경 시 무효화 필요.
internal/external/fetcher.go의short()가 160바이트에서 자르며 UTF-8 글자를 쪼갠다(한글 오류 메시지가 깨진다).NUMBERVALUE미구현(#NAME?),VALUE("12:00")이 시각을 읽지 못한다.TRUNC이math.Pow/이진 실수로 자리를 잘라TRUNC(2.29,1)같은 값에서 어긋날 수 있다. ROUND·CEILING 이 쓰는decimalRound로 통일할 것.
- 릴리즈: v0.229.0 (2026-09-02)
- 릴리즈: v0.229.0 (2026-09-02)
2026-09-03
- 선택: 외부 호출 캐시가 정책 검사를 가로막지 않고 상한을 지킨다 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약:
internal/external/fetcher.go의 답 캐시에 겹쳐 있던 세 가지를 함께 고쳤다 — (1) 허용 목록·https·주소 형식을 보는 정책 검사(CheckURL)를fetch에서one의 맨 앞으로 올려 캐시 바깥에 두어, 관리자가external.allowed_hosts를 고치면cache_seconds(기본 5분)를 기다리지 않고 곧바로 통하고 목록에서 뺀 호스트의 지난 답도 캐시에서 나오지 않는다, (2) 만료 항목을 지우는 자리가 없어 무한히 자라던 지도에store()를 두어 새 답이 들어올 때 만료분을 쓸고 512개 상한에서 가장 먼저 만료될 항목을 내보낸다, (3)short()가 160바이트에서 잘라 한글을 반 글자로 남기던 것을 160글자·글자 경계로 고쳤다. 검증: 새 테스트 3개(TestPolicyIsNotCachedInEitherDirection,TestCacheSweepsExpiredAndStaysBounded,TestShortKeepsKoreanLettersWhole)와gofmt -l,go vet,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(1266384), 릴리즈 노트 v0.230.0 과 README VERSION 갱신. 웹 변경이 없어 npm 검사는 돌리지 않았다. - 보류 아이디어:
NUMBERVALUE미구현(#NAME?),VALUE("12:00")이 시각을 읽지 못한다.TRUNC이math.Pow/이진 실수로 자리를 잘라TRUNC(2.29,1)같은 값에서 어긋날 수 있다. ROUND·CEILING 이 쓰는decimalRound로 통일할 것.csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다. 시트가 읽는 수의 범위와 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다(정책 값도 키에 넣거나 크기 오류는 담지 않기). internal/external의 동시 접근에-race시험이 없다. 한 번의 재계산이 여러 주소를 동시에 부르는 길을 시험으로 고정할 것.
- 릴리즈: v0.230.0 (2026-09-03)
- 릴리즈: v0.230.0 (2026-09-03)
2026-09-03
- 선택: 자리를 잘라 내는 셈의 십진화와 자릿수 상한 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: ROUND·ROUNDUP·ROUNDDOWN 은 이미
decimalRound로 십진 셈을 하는데TRUNC(functions_math.go)와PERCENTRANK(functions_distribution.go)만math.Pow(10,d)로 자리를 밀고 있어,TRUNC(0.29,2)=0.28,TRUNC(1.001,3)=1,PERCENTRANK({0..8},4.6,5)=0.57499 처럼 이미 그 자리에 맞는 값의 마지막 자리를 깎았다(같은 값을 ROUNDDOWN 에 물으면 제대로 나왔다). 둘 다decimalRound(..., roundTowardZero)로 옮겼고, 함께 발견한 자릿수 인수의 구멍도 막았다 —=ROUND(1.5,999999999)한 칸이 10^999999999 을 만드느라 수백 MB·수 초를 물고=FIXED(1.5,999999999)는 기가바이트짜리 글자를 내던 것을, 값을 바꾸지 못하는maxDecimalPlaces=400으로 들이고(boundedDecimalPlaces) float→int 변환이 정의되지 않는1E+300도decimalPlaces가 받게 했다. 검증: 새 테스트 2개(TestTruncCutsDecimalDigitsLikeRoundDown— 3000개 값×자릿수 -2..4 를 ROUNDDOWN 과 맞대 봄,TestOutlandishDigitCountsDoNotStallRounding— 20초 시한)와 PERCENTRANK 표 항목 1개,gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(d4db8a1, 018ea0b), 릴리즈 노트 v0.231.0 과 README VERSION 갱신. 웹에는 TRUNC 구현이 없어 npm 검사는 돌리지 않았다. - 보류 아이디어:
NUMBERVALUE미구현(#NAME?),VALUE("12:00")이 시각을 읽지 못한다.csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다. 시트가 읽는 수의 범위와 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다(정책 값도 키에 넣거나 크기 오류는 담지 않기). internal/external의 동시 접근에-race시험이 없다. 한 번의 재계산이 여러 주소를 동시에 부르는 길을 시험으로 고정할 것.DOLLARDE·DOLLARFR도math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다. 십진 셈으로 맞출지 검토할 것.
- 릴리즈: v0.231.0 (2026-09-03)
- 릴리즈: v0.231.0 (2026-09-03)
2026-09-03
- 선택: VALUE 가 사람이 적는 수의 겉모양을 읽고 NUMBERVALUE 를 더한다 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약: VALUE 가
toNumber만 써서1000같은 맨숫자 글자만 읽고"1,000"·"$1,000"·"(1,000)"·"12%"·"12:00"·"2026-01-05"을 모두 #VALUE! 로 냈다 — 가져온 표의 금액 칸이 다 그 꼴인데 달리 쓸 함수가 없었다. 새 파일internal/formula/text_to_number.go에valueOfText(자릿점·앞뒤 통화 기호·회계 서식 괄호·백분율, 시각·날짜는parseTime·DateSerial을 그대로 써 TIMEVALUE·DATEVALUE 와 답이 갈리지 않게)와numberValueOfText(엑셀 도움말 규칙대로 NUMBERVALUE 새로 구현: 가운데 빈칸 무시, % 하나마다 100 으로 나눔, 빈 글자는 0, 소수 구분 기호 중복·그 뒤의 자릿수 구분 기호·같은 두 기호는 오류)를 두고, 카탈로그·_xlfn.접두사·USER_GUIDE 를 함께 채웠다. 셈에 끼어드는 칸의 값(numberFromText)은 일부러 좁은 채로 두었다. 시험을 쓰다DateSerial이 두 날짜 사이를time.Duration으로 세어 292 년에서 넘치는 것을 발견해(9999-12-31 이 2958465 가 아니라 106751) 초로 세도록 함께 고쳤다. 검증: 새 테스트 6개(TestValueReadsTheShapesPeopleTypeIntoCells외)와TestDateSerialRoundTripsWithSerialDate에 먼 날짜 2개 추가,gofmt -l,go vet,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(9e72a86, c8f8f27), 릴리즈 노트 v0.232.0 과 README VERSION 갱신. 웹은 함수 목록을 API 에서 받아 오고 VALUE 구현도 없어 npm 검사는 돌리지 않았다. - 보류 아이디어:
csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다. 이제valueOfText가 그 규칙을 한 곳에 담고 있으니 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다(정책 값도 키에 넣거나 크기 오류는 담지 않기). internal/external의 동시 접근에-race시험이 없다. 한 번의 재계산이 여러 주소를 동시에 부르는 길을 시험으로 고정할 것.DOLLARDE·DOLLARFR도math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다. 십진 셈으로 맞출지 검토할 것.docs/USER_GUIDE.html·.pdf가.md와 함께 갱신되지 않아 오래되었다(마지막 갱신이 11aa6d8).scripts/generate_pdf.js를 릴리즈 절차나 CI 에 묶을지 검토할 것.
- 릴리즈: v0.232.0 (2026-09-03)
- 릴리즈: v0.232.0 (2026-09-03)
2026-09-03
- 선택: 표시 형식의 구역 규칙과 천 단위 축약을 엑셀에 맞춘다 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 표시 형식을 그리는 두 곳(
internal/formula의formatValue,web/src/lib/cellFormat.ts의formatCellValue)에 같은 구멍이 셋 있었다 — (1) 쉼표가 있기만 하면 자릿점으로 보아, 자리 기호 뒤 의 쉼표로 천 단위를 줄여 적는#,##0,,(재무 자료의 “백만 원 단위”)가1234567을 그대로1,234,567로 그렸다, (2) 구역이 둘뿐인 줄 알고 0 을 늘 양수 구역으로 보내 회계 서식#,##0;(#,##0);"-"의 0 이-가 아니라0이었고 자리 기호가 없는 글자 구역("판매")도 수로 그렸다, (3) 비워 둔 구역을 감추지 않아0;;이 음수를 부호도 없이5로 그렸다. 새 헬퍼formatSections(따옴표·대괄호 안의;는 구역을 가르지 않는다)·sectionForNumber·thousandsScale을 양쪽에 같은 규칙으로 두었고,0.0,,의 뒤쪽 쉼표를 소수 자리로 세던 것도 함께 고쳤다. 분수 서식(# ?/?)은 손대지 않으려고?를 자리 기호로 세어 지금 동작을 그대로 두었다. 검증: 격자와 서버가 함께 읽는testdata/cell-formats.json에 16줄 추가, 새 테스트TestFormatSectionsAndThousandsScaleFollowExcel(18가지)와 웹the grid picks the format section…·shrinks by a thousand…,gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),cd web && npm test(482개 통과)·npm run build·npm run lint,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(ccb2f92, 50edaf4), 릴리즈 노트 v0.233.0 과 README VERSION·USER_GUIDE 갱신. - 보류 아이디어:
- 분수 서식(
# ?/?,?/8,#/#)을 그리지 못한다.TEXT(0.5,"# ?/?")이1/2가 아니라1/이다. 엑셀 기본 서식 12·13번이라 XLSX 로 들어온다. - 넷째 구역(글자 구역)을 쓰지 않는다.
#,##0;;;"["@"]"처럼 글자에 서식을 준 칸이 그대로 나온다. csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다.valueOfText가 그 규칙을 한 곳에 담고 있으니 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다. DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다.
- 분수 서식(
- 릴리즈: v0.233.0 (2026-09-03)
- 릴리즈: v0.233.0 (2026-09-03)
2026-09-03
- 선택: 분수 서식(
# ?/?)이 값을 분수로 그린다 (가치 4 / 위험 2 / 작업량 M) - 결과: 성공
- 요약: 엑셀 기본 표시 형식 12·13 번(
# ?/?,# ??/??)은 값을 분수로 적으라는 뜻인데, 표시 형식을 그리는 두 곳(internal/formula의formatValue,web/src/lib/cellFormat.ts의formatCellValue)이 자리 기호만 세고 빗금을 글자로 흘려 보내0.5가1/2가 아니라1/,2.75가3/,0.3125가0/로 보였다(XLSX 로 그냥 들어오는 서식이다). 새 파일internal/formula/format_fraction.go와cellFormat.ts에 같은 규칙으로parseFractionFormat·renderFraction·bestFraction을 두었다 — 자리 기호 개수가 분모의 상한(?=9,??=99,???=999)이고 가장 덜 어긋나는 분모를 고르되 같으면 작은 쪽, 빈칸으로 가른 정수 자리가 있으면 대분수·없으면 가분수(#/#은 5.25 를 21/4), 분모를 숫자로 못 박으면 약분하지 않음(?/8→ 4/8), 분자가 분모까지 올라가면 정수로 올림(0.99→ 1),#은 0 인 정수 자리를 감추고0은 적음. 날짜 서식의 빗금은 자리 기호 사이에 없어 걸리지 않는다(m/d/yyyy그대로). 검증:testdata/cell-formats.json에 17줄 추가(격자와 서버가 함께 읽는다), 새 테스트TestFractionFormatsFollowExcel(18가지)와 웹the grid draws fraction formats as fractions(3 it),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),npm ci && npm test(485개 통과)·npm run lint·npm run build,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(5ca15fc), 릴리즈 노트 v0.234.0 과 README VERSION·USER_GUIDE 갱신. - 보류 아이디어:
- 넷째 구역(글자 구역)을 쓰지 않는다.
#,##0;;;"["@"]"처럼 글자에 서식을 준 칸이 그대로 나온다. ?의 자리 맞추기 빈칸을 그리지 않는다. 엑셀은# ??/??의 한 자리 분자를 빈칸으로 채워 자릿수를 맞춘다(단조 글꼴 표에서 눈에 띈다).csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다.valueOfText가 그 규칙을 한 곳에 담고 있으니 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다. DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다.
- 넷째 구역(글자 구역)을 쓰지 않는다.
- 릴리즈: v0.234.0 (2026-09-03)
2026-09-04
- 선택: 표시 형식의 넷째 구역(글자 구역)을 그린다 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 엑셀 표시 형식은
;로 나눈 네 구역(양수 ; 음수 ; 0 ; 글자)까지 담고 넷째 구역은 글자가 담긴 칸에만 걸리는데, 서식을 그리는 두 곳(internal/formula의formatValue,web/src/lib/cellFormat.ts의formatCellValue)이 그 구역을 아예 읽지 않아#,##0;(#,##0);"-";"["@"]"가 걸린미정칸이[미정]이 아니라 그대로 나왔다. 엑셀 기본 회계 서식의 마지막 토막_-@_-가 그 꼴이라 XLSX 로 그냥 들어온다. 함께@를 자리 기호가 아닌 그냥 글자로 흘려 보내@" 님"이 “@ 님” 으로 그려지던 것도 고쳤다(@는 “값을 있는 그대로” 라는 뜻이라 수에도 걸려5는 “5 님”). 규칙: 구역이 넷이면 넷째가 글자를 그리고, 비워 두면 감추며, 셋 이하면 글자를 손대지 않고, 구역이 하나뿐인데@가 있으면 그것이 곧 글자 구역이다. 양쪽에 같은 규칙으로textSection·hasTextPlaceholder·renderTextSection·scanFormatSection을 두었다. 검증: 격자와 서버가 함께 읽는testdata/cell-formats.json이 이제 수뿐 아니라 글자 값도 담도록 양쪽 시험의 값 타입을 넓히고 12줄 추가, 새 테스트TestTextSectionsFollowExcel(12가지)와 웹the grid draws text with the fourth section(3 it),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),cd web && npm ci && npm test(488개 통과)·npm run lint·npm run build,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(4ec3778), 릴리즈 노트 v0.235.0 과 README VERSION·USER_GUIDE 갱신. - 보류 아이디어:
?의 자리 맞추기 빈칸을 그리지 않는다. 엑셀은# ??/??의 한 자리 분자를 빈칸으로 채워 자릿수를 맞춘다.csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다.valueOfText가 그 규칙을 한 곳에 담고 있으니 맞출지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다. DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다.- 서버의
formatValue는 글자 “123” 을toNumber로 수로 읽고 격자는 글자로 보아, 숫자처럼 생긴 글자 값에서 두 곳의 답이 갈릴 수 있다(TEXT 의 강제 변환과 칸 그리기의 차이). 어느 쪽에 맞출지 정할 것.
- 릴리즈: v0.235.0 (2026-09-04)
- 릴리즈: v0.235.0 (2026-09-04)
2026-09-05
- 선택: 가져오기가 스프레드시트의 수만 수로 읽는다 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 값을 수로 읽는 자가 두 벌이었다 — 수식 엔진(
decimalText)과 격자(spreadsheetNumber)는 스프레드시트가 수라고 부르는 것만 읽는데, 파일을 읽어 들이는 자리(internal/importexport의parseScalar·parseXLSXValue,internal/external의csvNumber)만 Go 의ParseFloat를 그대로 써NaN·Inf·Infinity·1_000·0x1p4까지 수로 받았다. pandas·R 이 내보낸 CSV 의 빠진 값이 바로NaN이라 그 한 칸이 부동소수점 NaN 이 되었고, 그 값은json.Marshal이 거부하므로parseDelimited가 통째로 실패했다(사람이 본 것은 “json: unsupported value: NaN” 뿐).1_000은 조용히 1000 이 되어 파일에 적힌 것과 다른 값을 칸에 남겼다. 엔진의 자를formula.DecimalNumber로 내어 세 곳이 함께 쓰게 하고csvNumber중복은 지웠다(앞뒤 빈칸 다듬기는 부르는 쪽에 남겨 가져오기가 파일에 적힌 대로 두게 했다). 검증: 새 테스트 3개(TestParseScalarKeepsGoOnlyNumbersAsText,TestOneExportedNaNDoesNotStopTheWholeImport,TestParseXLSXValueKeepsGoOnlyNumbersAsText)와gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(fb0151d, 5eda9c8), 릴리즈 노트 v0.236.0 과 README VERSION·USER_GUIDE 갱신. 웹은 이미 같은 자를 쓰고 있어 손대지 않았고 npm 검사도 돌리지 않았다. - 보류 아이디어:
?의 자리 맞추기 빈칸을 그리지 않는다. 엑셀은# ??/??의 한 자리 분자를 빈칸으로 채워 자릿수를 맞춘다.csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다(이제formula.DecimalNumber). 가져오기 미리보기의 “글자로 담긴 숫자” 경고와 같은 자리에 놓을지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다. DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다(DOLLARDE(1.02,16)이 1.125 가 아니라 1.1250000000000002).- 서버의
formatValue는 글자 “123” 을toNumber로 수로 읽고 격자는 글자로 보아, 숫자처럼 생긴 글자 값에서 두 곳의 답이 갈릴 수 있다.
- 릴리즈: v0.236.0 (2026-09-05)
- 릴리즈: v0.236.0 (2026-09-05)
2026-09-07
- 선택: 외부 호출 캐시가 그때의 크기 상한과 시간 제한을 함께 기억한다 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
internal/external의 답 캐시는 자리 이름을 함수·주소로만 지어, 옛external.max_kb에서 받은 “응답이 허용된 크기를 넘습니다” 를 관리자가 상한을 올린 뒤에도cache_seconds(기본 5분, 최대 하루) 동안 그대로 내주었다 — 관리자에게는 설정이 먹지 않은 것으로 보인다. 허용 호스트 목록은 이미 캐시 바깥에 있어 고치면 곧바로 통하는데(TestPolicyIsNotCachedInEitherDirection), 크기 상한과 시간 제한도 부르는 방법을 정하는 정책이라는 점에서 다르지 않으므로 새cacheKey가MaxBytes·Timeout을 자리 이름에 함께 넣게 했다(엔진이 답을 찾는formula.ExternalKey는 그대로 두었다). 검증: 새 테스트 2개(TestRaisingTheSizeCeilingIsBelievedAtOnce— 고치기 전에 실제로 깨지는 것을 확인했다,TestResolveIsSafeToShare— 여덟 갈래가 같은 주소들을 한꺼번에 부르는 길을-race로 못 박음),gofmt -l,go vet,go build ./...,go test ./...(전체 통과)와go test -race ./internal/external/,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(7cdb124), 릴리즈 노트 v0.237.0 과 README VERSION 갱신. 웹·가이드 문서는 external 설정을 다루지 않아 손대지 않았고 npm 검사도 돌리지 않았다. -
보류 아이디어: 원격 500·타임아웃 같은 실패도 캐시에 담겨 원격이 고쳐져도 cache_seconds 동안 그대로다(정책과 달리 실패는 짧게 담거나 담지 않을지 검토) /
parseCSV가 쉼표·탭만 보아 세미콜론으로 가른 유럽식 CSV 를 한 열로 읽는다 /csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다 /DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어DOLLARDE(1.02,16)이 1.1250000000000002 다 /?의 자리 맞추기 빈칸을 그리지 않아# ??/??의 한 자리 분자가 자릿수를 맞추지 못한다 - 릴리즈: v0.237.0 (2026-09-07, run 2026-09-07-020048-kanpic-improve)
2026-09-08
- 선택: 원격 탓으로 난 외부 호출 실패는 잠깐만 캐시에 담는다 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약:
internal/external의 답 캐시가 성공과 실패를 가리지 않고cache_seconds(기본 5분, 최대 하루) 동안 담아, 배포 중의 500 이나 한 번의 시간 초과가 원격이 다시 멀쩡해진 뒤에도 하루 내내 답인 척 남을 수 있었다 — 허용 호스트와 크기 상한은 이미 캐시 바깥이거나 자리 이름에 들어 있어 관리자가 고치면 곧바로 통하는데, 정작 이쪽에서 고칠 수 없는 저쪽 사정만 가장 오래 남던 셈이다. 원격이 책임지는 실패(오류 상태·닿지 못함·응답을 읽지 못함)를 새remoteFailure로 표시해 최대 30초(failureCacheFor)만 담고, 이 응답의 성질이 정한 거절(크기 상한·리다이렉트·사설망)은 같은 응답이면 언제나 같은 답이라 예전대로 두었다. 아예 담지 않으면 시간 제한을 매 다시 계산마다 처음부터 다시 기다리므로 짧게 담는 쪽을 골랐다. 검증: 새 테스트TestRemoteFailuresAreKeptOnlyBriefly(고치기 전에 실제로 깨지는 것을 확인했다 — 두 갈래를 한 시험에서 갈라 본다),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과)와go test -race ./internal/external/,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(a981f8e). 서버 안쪽 캐시 수명이라 웹·문서·설정 화면은 손대지 않았고 npm 검사도 돌리지 않았다. -
보류 아이디어:
parseCSV가 쉼표·탭만 보아 세미콜론으로 가른 유럽식 CSV 를 한 열로 읽는다(internal/importexport의detectDelimiter는 이미;를 세므로 그 규칙에 맞출 것) /csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다 /DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어DOLLARDE(1.02,16)이 1.1250000000000002 다 /?의 자리 맞추기 빈칸을 그리지 않아# ??/??의 한 자리 분자가 자릿수를 맞추지 못한다 / IMPORTDATA 의 CSV 를 읽지 못했다는#VALUE!도cache_seconds만큼 담기는데, 이것은 원격이 보낸 본문의 성질이라 지금은 그대로 두었다 — 원격이 파일을 고쳤을 때의 회복 속도를 저울질할 것 - 릴리즈: v0.238.0 (2026-09-08, run 2026-09-08-210919-kanpic-improve)
2026-09-09
- 선택: 가져오기와 IMPORTDATA 가 같은 규칙으로 열을 가른다 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 소수점을 쉼표로 적는 로케일의 엑셀이 내보낸 CSV 는 세미콜론으로 갈리는데,
internal/external의parseCSV는 쉼표를 기본으로 두고 첫 줄에 탭이 있고 쉼표가 없을 때만 탭으로 보아 그런 파일을 통째로 한 열짜리 글자 덩어리로 읽었다(=SUM이 아무것도 더하지 못한다) — 같은 파일이 업로드로 들어오면internal/importexport의detectDelimiter가,·;·\t를 세어 이미 제대로 갈라 읽으므로, 문이 어느 쪽이냐에 따라 같은 파일이 다른 표가 되던 셈이다. 그 규칙 하나를 새internal/delimited(Delimiter)에 두어 두 곳이 함께 쓰게 하고, 함께 그 규칙이 따옴표 안을 세지 않게 했다(헤더의 한 칸이 세미콜론을 여럿 담았다는 이유로 쉼표 파일 전체가 세미콜론으로 갈리던 것 — 짝이 맞지 않는 따옴표는 쉼표로 돌아온다). 검증: 새 테스트 3개(TestDelimiterFollowsTheFirstLine11가지,TestImportDataReadsWhicheverSeparatorCutsTheFile— 고치기 전에 실제로 깨지는 갈래다,TestQuotedSeparatorsDoNotDecideHowTheFileIsCut),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(d1e800e, e9b7b5b). 서버 안쪽 파싱 규칙이라 웹·문서는 손대지 않았고 npm 검사도 돌리지 않았다. -
보류 아이디어:
csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다 /DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어DOLLARDE(1.02,16)이 1.1250000000000002 다 /?의 자리 맞추기 빈칸을 그리지 않아# ??/??의 한 자리 분자가 자릿수를 맞추지 못한다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도cache_seconds만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 서버formatValue는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 - 릴리즈: v0.239.0 (2026-09-09, run 2026-09-09-020058-kanpic-improve)
2026-09-09
- 선택: 표 위의 제목 줄이 파일을 어떻게 가를지 정하지 않는다 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 열을 가르는 규칙(
internal/delimited의Delimiter)이 첫 줄만 세어, 보고서 도구가 표 위에 얹는 제목 한 줄이나 빈 줄 — 아무것으로도 갈리지 않아 파일에 대해 아무 말도 하지 않는 줄 — 이 세미콜론으로 가른 파일 전체를 A열 한 줄짜리 글자 덩어리로 만들었다(=SUM이 아무것도 더하지 못한다). 지난 회차에 유럽식 CSV 를 위해 세미콜론을 세게 해 놓고도 그 파일이 제목 줄을 이고 오면 다시 한 열이 되던 셈이라, 이제 머리의 열 줄까지 보아 가장 많은 줄을 같은 열 수로 가르는 후보를 고르고(가르지 못하는 줄은 세지 않는다), 같으면 예전대로 쉼표가 이긴다. 따옴표 안은 여전히 세지 않으며 줄마다 따로 읽어 짝이 맞지 않는 따옴표는 그 줄만 잃는다. 가져오기 쪽detectDelimiter는 첫 줄만 잘라 넘기던 것을 앞 64KiB(마지막 줄 끝까지)로 넓혔다. 검증: 고치기 전에 제목 줄 파일이 실제로 한 열로 들어오는 것을 확인했고, 새 테스트 4개(TestDelimiterFollowsTheLinesThatAgree에 제목·빈 줄·쉼표 낀 제목·탭·본문에 섞인 세미콜론·들쭉날쭉한 열 수 6가지 추가,TestDelimiterReadsOnlyTheHead,TestATitleRowAboveTheHeaderDoesNotDecideHowTheFileIsCut, IMPORTDATA 쪽TestImportDataReadsWhicheverSeparatorCutsTheFile에 제목 줄 갈래),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(c27367c). 서버 안쪽 파싱 규칙이라 웹·문서는 손대지 않았고 npm 검사도 돌리지 않았다(격자의dataCleanup.ts는 셀 글자를 나누는 다른 자리이고 이미 여러 줄을 본다). -
보류 아이디어:
DOLLARDE·DOLLARFR의 이진 실수 어긋남은 실제로 재현되지 않아 rejected — 분모 2·4·8·16·32·64·128 의 모든 분자를 훑어도 어긋남이 0이다(마지막의 정수 더하기가 반 ulp 를 흡수한다) /csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다 / 서버formatValue는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도cache_seconds만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 파이프(|)로 가른 파일을 후보에 넣을지 — 로그·DB 덤프가 흔히 쓰는데 지금은 한 열로 들어온다 /?의 자리 맞추기 빈칸을 그리지 않아# ??/??의 한 자리 분자가 자릿수를 맞추지 못한다 - 릴리즈: v0.240.0 (2026-09-09, run 2026-09-09-084144-kanpic-improve)
2026-09-10
- 선택: 내보낸 CSV·TSV 가 스스로 UTF-8 이라고 밝힌다 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 엑셀은 UTF-8 표시(BOM)가 없는 CSV 를 그 컴퓨터의 기본 코드 페이지로 읽는데, 내려받은 표를 여는 길은 대개 두 번 누르는 것이고 그 길에는 인코딩을 물어보는 자리가 없어 한글 칸이 통째로 깨져 보였다 — 관리자 로그(
/api/v1/admin/logs.csv)와 AI 기록을 내보내는 자리는 이미 같은 이유로 표시를 붙이고 있었으므로, 정작 한글이 가장 많이 담기는 워크북 내보내기만 빠져 같은 제품이 내주는 두 파일이 엑셀에서 다르게 보이던 셈이다.exportDelimited가 csv 와 tsv 모두 앞에 표시를 붙이게 하고, 이미 그것을 떼고 있던parseDelimited와 같은utf8BOM을 쓰게 해 붙이는 자리와 떼는 자리를 한 값으로 묶었다(IMPORTDATA 의internal/external도 이미 뗀다). 검증: 새 테스트TestDelimitedExportMarksItselfAsUTF8(csv·tsv 두 갈래 — 표시가 붙는지와, 되돌려 읽은 첫 칸이 표시를 이고 있지 않은지를 함께 본다)와 표시가 붙으면서 깨진 기존TestCSVExportEscapesFormulaInjection수정,gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(b4eaa51). 서버가 내주는 바이트라 웹·문서는 손대지 않았고(문서는 내보내기의 인코딩을 말한 적이 없다) npm 검사도 돌리지 않았다. -
보류 아이디어:
csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·통화 기호를 글자로 남긴다 / 서버formatValue는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도cache_seconds만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 파이프(|)로 가른 파일을 후보에 넣을지 — 로그·DB 덤프가 흔히 쓰는데 지금은 한 열로 들어온다 / 가져오기가 UTF-16 로 저장한 CSV 를 “CSV must be UTF-8 encoded” 로 거절한다(엑셀의 ‘유니코드 텍스트’ 저장이 그 꼴이고 BOM 으로 알아볼 수 있다) /?의 자리 맞추기 빈칸을 그리지 않아# ??/??의 한 자리 분자가 자릿수를 맞추지 못한다 - 릴리즈: v0.241.0 (2026-09-10, run 2026-09-10-000118-kanpic-improve)
2026-09-10
- 선택: 무엇으로 적혔는지 밝히고 오는 CSV 를 밝힌 대로 읽는다 (가치 3 / 위험 2 / 작업량 S)
- 결과: 성공
- 요약: 윈도우 파워셸의
Export-Csv와 엑셀의 ‘유니코드 텍스트’ 저장은 UTF-16LE 로 쓰고 파일 앞에 그것을 알리는 표시(BOM)를 두는데,parseDelimited는utf8.Valid만 보아 그 두 바이트 때문에 업로드를 통째로 “CSV must be UTF-8 encoded” 로 거절했다 — 짐작할 것이 하나도 없는 파일인데도(한글이든 영문이든 거절한 것은 내용이 아니라 표시였다) 사람에게는 다른 도구로 저장을 다시 하는 것 말고 길이 없었다. 읽는 규칙을 새internal/delimited.ToUTF8하나에 두어 가져오기와 IMPORTDATA 가 함께 쓰게 했다(열을 가르는 규칙이 이미 그 자리에 있고, 같은 파일은 어느 문으로 들어오든 같은 표가 되어야 한다 —parseCSV가 따로 떼던도 그 자리로 합쳤다). 표시가 있는 UTF-8·UTF-16LE·UTF-16BE 만 밝힌 대로 옮겨 읽고, 표시가 없는 파일은 손대지 않으며(짐작은 멀쩡한 파일을 망가뜨리는 쪽으로만 틀린다), UTF-16LE 와 같은 표시로 여는 UTF-32LE 는 반쯤 읽어 글자 아닌 것을 만드는 대신 예전처럼 거절한다. 미리보기 크기는 옮겨 읽기 전에 재 두어 방금 고른 파일보다 작은 수를 말하지 않게 했다(XLSX 는 이미 그렇게 한다). 검증: 고치기 전에 UTF-16 업로드가 실제로 거절되는 것을 확인했고(영문뿐인 파일도 같은 이유로 거절되던 것을 따로 확인), 새 테스트 3개(TestToUTF8ReadsWhatTheFileSaysItIs7가지·TestToUTF8DoesNotMistakeUTF32ForUTF16·TestToUTF8GuessesNothing,TestImportReadsCSVThatSaysItIsUTF16,TestImportDataReadsACSVThatSaysItIsUTF16),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과)와go test -race ./internal/{external,importexport,delimited}/,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(ab56535). 서버가 파일을 읽는 규칙이라 웹은 손대지 않았고 npm 검사도 돌리지 않았다. 사용자 가이드의 가져오기 항목이 있지도 않은 인코딩 선택 자리(UTF-8·EUC-KR 고르기)를 안내하고 있어 지금 읽는 대로 고쳤다. -
보류 아이디어: 관리자 사용자 명단 가져오기(
UserImportDialog)는 브라우저에서file.text()로 읽어 UTF-16 명단이 깨진 글자로 들어온다 — 서버가 이제 밝힌 대로 읽으므로 격자와 같은 규칙을 웹에도 둘지 /csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·통화 기호를 글자로 남긴다 / 서버formatValue는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도cache_seconds만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 파이프(|)로 가른 파일을 후보에 넣을지 — 로그·DB 덤프가 흔히 쓰는데 지금은 한 열로 들어온다 - 릴리즈: v0.242.0 (2026-09-10, run 2026-09-10-135111-kanpic-improve)
2026-09-10
- 선택: 사용자 명단도 무엇으로 적혔는지 밝히면 밝힌 대로 읽는다 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 관리자가 사용자 명단을 뽑는 가장 흔한 길인 윈도우 파워셸의
Export-Csv(AD 에서 바로 뽑는다)와 엑셀의 ‘유니코드 텍스트’ 저장은 UTF-16LE 로 쓰고 앞에 그것을 알리는 표시(BOM)를 두는데, 일괄 등록 화면(UserImportDialog)은 고른 파일을file.text()로 읽고 그것은 무엇이 적혀 있든 언제나 UTF-8 로 푼다 — 명단이 글자 아닌 것이 되어 사람이 받는 것은 “머리글 줄에 user_id 열이 있어야 합니다” 하나뿐이었다. 서버는 지난 회차에 워크북 가져오기와 IMPORTDATA 를delimited.ToUTF8로 밝힌 대로 읽게 했으므로, 같은 명단이 어느 문으로 들어오느냐에 따라 다르게 읽히던 셈이라 같은 규칙을 웹의 새web/src/lib/fileText.ts(decodeAnnounced·readFileText)에 두었다(표시가 있는 UTF-8·UTF-16LE·UTF-16BE 만 옮겨 읽고, 표시가 없는 파일은 손대지 않으며, UTF-16LE 와 같은 표시로 여는 UTF-32LE 는 반쯤 읽어 글자 아닌 명단을 만드는 대신 예전처럼 서버가 거절하게 둔다). 서버의parseUserCSV도 남은 표시를 떼게 했다 — 화면은 이제 풀어서 보내지만 답이 보내는 쪽에 따라 갈릴 이유가 없고,strings.TrimSpace는 U+FEFF 를 빈칸으로 보지 않는다. 검증: 고치기 전에TestUserCSVReadsARosterThatSaysWhatItIs가 실제로 “머리글 줄에 user_id 열이 있어야 합니다” 로 깨지는 것을 확인했고, 새 테스트 5개(웹 4개 — 밝힌 대로 읽기·아무것도 짐작하지 않기·UTF-32 를 UTF-16 으로 오해하지 않기·고른 파일 넘기기, Go 1개),npm run lint,npm test(69파일 492개 통과),gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 1개(fd9c422). 사용자 가이드는 관리자 일괄 등록의 인코딩을 말한 적이 없어 손대지 않았다. - 보류 아이디어:
parseUserCSV는 쉼표만 보아 세미콜론으로 가른 명단을 한 열로 읽는다(워크북 쪽은delimited.Delimiter를 쓴다) /csvNumber자리(formula.DecimalNumber)가 IMPORTDATA 의"1,200"·"12%"·통화 기호를 글자로 남긴다 / 서버formatValue는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도cache_seconds만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 파이프(|)로 가른 파일을 후보에 넣을지 — 로그·DB 덤프가 흔히 쓰는데 지금은 한 열로 들어온다
2026-09-11
- 선택: 가이드 두 벌을 실제 화면 캡처가 들어간 완성본으로 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약: 캠페인 guides-2026-09 목표. 두 가이드가 v0.18.0(2026-08-02)에 멈춰 있었고 화면 캡처가 한 장도 없이 사람이 그린 ASCII 표가 그 자리를 대신하고 있었다 — 표준이 금지하는 목업이다. 로컬 PostgreSQL 과 v0.242.0 로 빌드한 서버(BOOTSTRAP 관리자 로그인)를 띄우고, 새
web/e2e/guide-screenshots.mjs로 가짜 명단·워크북·부서·API 키를 채운 뒤 1440x900 에서 19장을 찍어docs/images/guide/에 넣었다(대상 주소와 자격 증명은 캡처 전용 환경 변수로만 받고, 값이 없으면 멈추며, 전역 설정은 읽기만 한다). 사용자 가이드에는 ‘처음 5분’ 과 화면별 캡처, 화면에 실제로 나오는 오류 문구를 그대로 실은 ‘막혔을 때’, 용어 절을 더했고, 관리자 가이드에는 구성 요소·포트·볼륨 표, docker load 부터 최초 관리자 로그인까지의 설치 절차, 코드에서 읽은 환경 변수 4개(POSTGRES_DSN·BOOTSTRAP_ADMIN_ID·BOOTSTRAP_ADMIN_PASSWORD·PORT)와 설정 키 전수 표, 상태 점검(GET /healthz·GET /api/v1/version — 라우트 등록 자리에서 메서드 확인)·업그레이드와 되돌리기, 증상 → 확인할 곳 → 로그 문구 → 조치로 적은 장애 대응, 보안 기본값 절을 더했다. PDF 는 공용 md2pdf 로 다시 만들었고(사용자 85쪽), 이미지를 버리는 옛 HTML 사본은 지우고 문서 사이트 링크를 PDF 로 옮겨 정본을 하나로 두었다. 검증: 캡처 19장을 눈으로 확인(빈 목록·스피너·실명·비밀값 없음), 문서의 이미지 참조 22곳이 모두 실제 파일을 가리키는지 확인,npm run lint,go build ./...,scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(af17dc6, 56be589). 서버·웹 코드는 손대지 않았다. -
보류 아이디어: parseUserCSV 는 쉼표만 보아 세미콜론으로 가른 명단을 한 열로 읽는다(워크북 쪽은 delimited.Delimiter 를 쓴다) / csvNumber 자리(formula.DecimalNumber)가 IMPORTDATA 의 “1,200”·”12%”·통화 기호를 글자로 남긴다 / 서버 formatValue 는 글자 “123” 을 수로 읽고 격자는 글자로 보아 숫자처럼 생긴 글자 값에서 두 곳이 갈린다 / IMPORTDATA 의 CSV 파싱 실패(#VALUE!)도 cache_seconds 만큼 담겨 원격이 파일을 고쳐도 회복이 늦다 / 홈 화면 워크북 카드의 미리보기가 값이 있는 워크북에서도 빈 격자로 그려진다(캡처하다 발견)
- 릴리즈: v0.243.0 (2026-09-11, run 2026-09-11-050112-kanpic-improve)