Releases: hkjang/ptium
Release list
Ptium v1.69.36
Ptium 1.69.36
이번에는 기능 쪽을 검색했습니다. 어떤 도구가 무엇을 갖췄는지, 그리고 실제로 써 본
사람들이 무엇에 불평하는지. 나온 목록 대부분은 이 제품이 이미 하고 있거나 애초에 전제로
삼은 것이었습니다 — PPTX 로 제대로 나가는 것, 오프라인, 한국어, 슬라이드 다시 쓰기,
출처 표시, 발표자 화면. 남은 두 가지를 이 제품에 대어 보니 둘 다 걸렸습니다.
1. 제목이 문장 한가운데서 잘려 나갑니다
브리프 열 개를 돌아가는 제품에 넣고 나온 제목 60개를 전부 읽었습니다.
| 실제로 나온 제목 | 무엇이 잘못됐나 |
|---|---|
재고 회전율 개선 근거를 담고 |
문장 중간에서 끊김 |
하반기 목표를 제안 (4장) |
목적어만 있고 서술어가 없음 |
책임을 나누고 우선순위를 정 |
낱말 자체가 잘림 |
만들고 적용 일정을 안내 (4장) |
연결어미로 시작 |
60개 중 지적된 것은 1개뿐이었습니다. 나머지는 그려지고, 맞춰지고, 점수가 매겨져
나갔습니다.
왜 못 잡았나
제목이 잘렸는지 보는 곳이 두 군데 — 쓰는 쪽과 재는 쪽 — 있었고, 양쪽 다 손으로 적은
동사 목록을 들고 있었습니다. 담고 가 어느 목록에도 없었습니다.
목록 대신 이 언어가 이미 가지고 있는 규칙을 씁니다. 조사를 단 낱말은 서술어를
기다립니다. 근거를 은 동사를 기다리고 목표를 도 동사를 기다립니다. 그다음 낱말이
끝맺는 말이 아니면 그 구는 하려던 말을 마치기 전에 끊긴 것입니다.
그래서 안전 사고 · 물류 창고 · 분기 매출 보고 는 제목이고 매출을 보고 는 아닙니다 —
같은 고 인데 앞에 기다리는 것이 있느냐 없느냐가 다릅니다.
그리고 애초에 그렇게 만들지 않습니다
제목은 명사구입니다. 잘린 자리에 남은 것은 조사를 단 목적어와, 그 뒤에 오려던 동사
또는 그 잔해입니다. 어느 쪽이냐에 따라 고치는 법이 다릅니다.
하반기 목표를 제안 → 하반기 목표 제안 동사가 명사이기도 하면 조사만 뗀다
재고 회전율 개선 근거를 담고 → 재고 회전율 개선 근거 동사일 뿐이면 떼어 낸다
다음 분기 과제를 정 → 다음 분기 과제 한 음절은 낱말이 아니다
같은 열 개 브리프를 다시 돌린 결과입니다.
| 전 | 후 |
|---|---|
재고 회전율 개선 근거를 담고 |
재고 회전율 개선 근거 |
3분기 안에 시범 도입을 제안 |
3분기 안에 시범 도입 제안 |
하반기 목표를 제안 ×4 |
하반기 목표 제안 ×4 |
이관 일정을 정리 |
이관 일정 정리 |
다음 분기 과제를 정 |
다음 분기 과제 |
책임을 나누고 우선순위를 정 |
부서별 역할 |
만들고 적용 일정을 안내 ×4 |
협력사 평가 기준 ×4 |
마지막 줄은 곁가지로 찾은 것 때문입니다. 브리프의 주어를 찾는 코드가 평가 를 평 + 조사
가 로 읽고 있었습니다. 효과 · 성과 · 결과 · 단가 도 마찬가지입니다. 그래서
"협력사 평가 기준을 …" 에서 주어를 기준 으로 잘라냈고, 슬라이드 넉 장이 그 뒤에
붙었습니다. 조사 밑에 두 음절 이상이 남아야 조사로 읽습니다.
오탐도 하나 나왔습니다
고친 뒤 재 보니 이관 일정 정리 가 잘린 제목이라고 나왔습니다. 멀쩡한 제목입니다.
재는 쪽 정규식이 정리 · 작성 · 준비 를 끊긴 동사로 보고 있었는데 셋 다 명사입니다.
그것들이 잡으려던 …일정을 정리 는 위의 조사 규칙이 잡으므로, 목록에서 뺐습니다.
2. 발표 노트가 슬라이드를 그대로 읽습니다
조사에서 이 분야 전체의 가장 약한 곳으로 발표 노트가 꼽혔습니다. 대부분의 도구가
노트를 건너뛰거나 하나 마나 한 말을 채운다는 것입니다. 이 제품도 그랬습니다.
발표 노트 초안 버튼이 쓰던 것은 제목: 첫 요점 — 슬라이드에 이미 있는 낱말을 쌍점으로
이은 것이었습니다. 발표자가 그걸 소리 내어 읽으면 청중이 눈으로 읽고 있는 문장을 그대로
말하는 셈입니다. 제목만 있는 장은 제목을 두 번 받았습니다.
이제 그 장이 무엇을 담고 있는지 보고 할 일을 적습니다.
| 그 장이 담은 것 | 노트 |
|---|---|
| 표지 | 왜 지금 이 이야기를 하는지 한 문장으로 밝히고, 오늘의 결론을 먼저 말합니다 |
| 단계 · 타임라인 | 각 단계의 완료 조건을 한 문장씩 말하고, 순서를 바꿀 수 없는 이유를 덧붙입니다 |
| 지표 · 대표 숫자 | 숫자를 읽지 말고, 그 숫자가 왜 그렇게 나왔는지부터 말합니다 |
| 비교 | 권고안을 먼저 말하고, 고르지 않은 쪽을 접은 이유를 한 가지만 덧붙입니다 |
| 차트 | 눈이 가야 할 곳 한 군데를 먼저 가리키고, 나머지는 질문이 나오면 말합니다 |
| 표 | 표를 다 읽지 말고, 한 줄만 짚어 그것이 무엇을 뜻하는지 말합니다 |
| 마지막 장 | 무엇을 결정해 달라는 것인지 한 문장으로 남기고 마칩니다 |
그리고 다음 장으로 넘기는 말을 붙입니다 — "그다음 비용과 효과로 넘어갑니다." 조사가
받침에 맞춰집니다. 발표자가 벽에서 읽을 수 없는 유일한 것이 다음에 무엇이 오는가이고,
조사에서 생성된 노트가 가장 많이 빠뜨린다고 지목한 것이 이 넘김말이었습니다.
모델 없이 씁니다. 그래서 논지를 아는 척하지 않고 무엇을 할지만 말합니다 — 그 숫자가
왜 그런지는 쓴 사람 말고 아무도 모르고, 추측한 노트는 하나 마나 한 말보다 나쁩니다.
되풀이하면 지적합니다
발표 노트가 슬라이드와 같은 말 이 새로 생겼습니다. 판정은 낱말 단위입니다 — 노트가
자기 낱말을 하나라도 보태면 보탠 것이므로 아무 말도 하지 않습니다. 얼마나 보탰는지를
두고 쓴 사람과 다투는 검사라면 꺼 버리는 편이 나을 테니까요. 구성 점수에 6점입니다.
이를 뽑아 본 결과
- 잘린 구 26개와 멀쩡한 구 67개로 규칙을 시험했습니다. 두 번째 목록이 이 규칙이 지금
모양인 이유입니다 — 처음 쓴 규칙은결과 보고·협력사 평가 기준·남은 질문·
매출을 늘리는 방법을 전부 잘렸다고 했습니다.과·가·의·로는 조사보다
명사의 마지막 음절인 경우가 훨씬 많고,은·는은 동사가 명사에 붙는 방법이기도
합니다. - 재는 쪽 규칙을 빼고 돌리면 검사 9개 중 6개가 실패합니다(나머지 3개는 기존 규칙 몫).
- 쓰는 쪽 수리를 빼고 돌리면 브리프 13개 중 10개가 잘린 제목이 있는 덱을 냅니다.
- 노트 초안을 예전 것으로 되돌리면 새 검사 5개 중 4개가 실패합니다.
- 전체 훑기: api 888 · edges 33 · race 69,157 요청 · tenancy 101 · flows · deep · ui — 0 실패.
곁가지 하나
codecover.test.ts 가 서버 소스 전체를 훑는데, 검사마다 한 번씩 두 번 훑고 파일마다
statSync 를 부르고 있었습니다. 다른 파일과 함께 돌 때 5초 제한을 넘겼습니다. 한 번만
훑고 디렉터리당 한 번만 물어보게 했습니다 — 3,632ms → 3ms. 검사하는 내용은 그대로입니다.
하지 않은 것
- 브리프가 한 낱말만 준 주제는 여전히
원인·일정처럼 제목이 한 낱말입니다. 잘린 것은
아니고 얇은 것이라 다른 문제입니다. 클라우드로 이전처럼 조사가로인 경우는 잡지 못합니다.경로·자료·재료·
진로가 모두로로 끝나서, 잡으려면 그만큼을 오탐으로 냅니다.USER_GUIDE.pdf는 이번에도 다시 만들지 않았습니다. 그 PDF 를 찍은 도구가 이 저장소에
없습니다. 정본은 마크다운입니다.
Ptium v1.69.35
Ptium 1.69.35
글자가 작아진 것을 아무도 말해 주지 않았습니다
이 서비스와 같은 종류의 도구들이 어떤 기능을 갖추고 무엇으로 불평을 듣는지 인터넷에서
찾아보고, 거기서 나온 규칙들을 이 제품에 하나씩 대어 보았습니다. 대부분은 이미 하고
있는 것이었습니다 — 발표자 화면, 개요 편집기, 단축키, 대비 검사, 한 장에 요점 여섯 개,
템플릿의 색과 글꼴을 그대로 쓰는 것. 브랜드가 어긋난다는 가장 흔한 불평은 이 제품이
애초에 고객사 템플릿 안에 그려 넣기 때문에 해당하지 않습니다.
대지 않고 남은 규칙이 하나 있었습니다: 본문은 뒷자리에서도 읽히게 18pt 이상.
Ptium 은 글이 상자에 넘치면 글자를 줄여서 넣습니다. 이건 사람이 해도 그렇게 할
일이고, 그래서 아무것도 잘못돼 보이지 않았습니다 — 낱말은 전부 있고, 상자 안에 있고,
바르게 그려집니다. 다만 얼마나 작아졌는지는 아무 데도 적히지 않았습니다.
돌아가는 제품에서 재어 본 결과입니다. 18pt 본문을 가진 템플릿에 요점 여섯 개를 직접
써 넣었을 때:
| 요점당 낱말 | 실제로 그려지는 크기 | 그때까지의 지적 |
|---|---|---|
| 10 | 18pt | — |
| 14 | 18pt | — |
| 18 | 15.8pt | 한 장에 너무 많음 |
| 26 | 13.8pt | 한 장에 너무 많음 |
| 34 | 12.3pt | 한 장에 너무 많음 |
12pt 는 문서이지 슬라이드가 아닙니다. 뒷자리에서는 읽히지 않습니다.
이제 이렇게 말합니다
품질 패널에 글자가 너무 작아짐 이 새로 생겼습니다.
글자가 템플릿의 18pt에서 12pt로 줄어, 뒷자리에서 읽히는 18pt보다 작습니다
두 숫자를 함께 말하는 이유는, 쓰는 사람이 손댈 수 있는 것이 그 차이이기 때문입니다.
템플릿이 요구하는 크기는 그대로인데 이 장의 글이 상자보다 많아서 줄어든 것이니,
할 일은 글을 나누거나 줄이는 것입니다. 그래서 이 지적에는 두 장으로 나누기 버튼이
붙습니다 — 넘침·너무 많음과 같은 방식으로, 내용을 하나도 버리지 않고 나눕니다.
생성 중에 나오면 다시 쓰기를 한 번 더 요청하는 근거로도 쓰입니다.
가독성 점수에 10점으로 계산됩니다. 슬라이드에 다 있는데 방 뒤쪽에서 아무도 읽지
못하는 글은, 그 절반의 사람에게는 아예 안 그려진 것과 같기 때문에 잘려 나간 내용과
같은 값을 매겼습니다.
템플릿 자체가 작은 것은 말하지 않습니다
본문을 14pt 로 설계한 템플릿이 있습니다. 그건 그 회사가 모든 슬라이드에 대해 일부러
정한 것이고, 이 덱을 쓰는 사람이 어떻게 할 수 있는 일이 아닙니다. 매 장마다 그렇게
말하면 손댈 수 없는 결정에 대한 잡음이 됩니다. 줄어든 경우에만 말합니다 —
줄어들게 만든 것은 이 사람이 쓴 글의 양이니까요.
같은 이유로, 넘침(텍스트 넘침)으로 이미 지적되는 장에서는 이 지적을 내지 않습니다.
한 영역, 한 원인, 한 줄.
이번에도 이를 뽑아 보았습니다
- 고친 부분을 되돌려 놓고 Go 검사 네 개를 다시 돌렸습니다. 두 개가 실패하고 두 개는
통과합니다 — 통과한 둘은 "안 나와야 할 때 안 나오는가"를 보는 반대 검사입니다. - 한국어 번역이 빠지면 실패하는 기존 검사(
coverage.test.ts, 서버 소스를 직접 읽습니다)에
새 문구를 넣었다 빼 보았고, 뺐을 때 정확히 이 문구를 지목하며 실패했습니다. - 돌아가는 서버에서 미리보기의 실제 글자 크기(15.84 / 13.77 / 12.33)와 지적이 말하는
숫자(16 / 14 / 12pt)가 맞는지 대조했습니다. - 전체 훑기: api 888, edges 33, race 72,100 요청, tenancy 101, flows·deep·ui 모두 0 실패.
하지 않은 것
컴포넌트(단계·표·지표 카드)가 그리는 본문은 Ptium 자신의 크기표에 따라 16pt 이고,
설명·단위 같은 작은 글씨는 12pt 입니다. 16pt 는 위의 기준보다 작지만 이건 모든 덱에
해당해서, 지적으로 내면 쓰는 사람이 손댈 수 없는 잡음이 됩니다. 크기표를 올리는 쪽은
컴포넌트가 더 자주 넘치게 만들 수 있어서, 여러 덱을 실제로 그려 비교해 나아진다는 것을
보이기 전에는 바꾸지 않았습니다. 알고 있고, 하지 않았다는 기록으로 남깁니다.
docs/USER_GUIDE.md 에 이 지적과 그때 할 일을 적었습니다. 같은 폴더의 USER_GUIDE.pdf 는
이번에 다시 만들지 않았습니다 — 그 PDF 를 찍은 도구가 이 저장소에 없어서, 다른 도구로 다시
찍으면 문서 전체 모양이 달라집니다. 정본은 마크다운 쪽이고, PDF 는 이 문단만큼 뒤에 있습니다.
Ptium v1.69.34
Ptium 1.69.34
화면이 없던 안내서
지금까지 안내는 docs/user-guide.md 한 장이었습니다. 글로만 적혀 있어서, 처음 쓰는 사람은
"편집기 오른쪽 패널"이 어느 것인지 화면을 열어 놓고 짐작해야 했고, 서버를 띄우고 지키는
사람을 위한 안내는 README 곳곳에 흩어져 있었습니다.
이번 릴리즈는 안내서를 둘로 나누고, 설명하는 화면마다 실제 화면을 붙였습니다.
| 문서 | 읽는 사람 | 담은 것 |
|---|---|---|
docs/USER_GUIDE.md (+ PDF) |
화면을 쓰는 사람 | 제품이 하는 일, 처음 5분, 화면별 사용법, 자주 하는 작업, 막혔을 때 만나는 메시지와 대처, 용어 |
docs/ADMIN_GUIDE.md (+ PDF) |
서버를 띄우고 지키는 사람 | 구성 요소, 붙여 넣을 수 있는 설치, config.go 와 auth/config.go 가 읽는 환경 변수 전부, 콘솔이 보관하는 설정, 역할, 운영, 장애와 그때 남는 로그 줄, 보안 |
docs/user-guide.md 는 정본을 가리키는 짧은 안내로 남고, README 도 새 문서를 가리킵니다.
사진은 제품에서 찍었습니다
docs/assets/guide/ 의 서른세 장은 손으로 찍은 것이 아닙니다. scripts/guide/screenshots.py 가
v1.69.32 릴리즈 이미지를 일회용 데이터베이스 위에 띄우고, 예시 덱을 채우고,
hong@example.com 으로 들어가 한 번의 실행으로 모든 화면을 찍었습니다. 그래서 사진끼리
서로 어긋나지 않습니다 — 대시보드에 보이는 덱이 목록에도, 편집기에도 같은 이름으로 있습니다.
이 스크립트는 다른 스크립트와 겹치지 않는 자기만의 변수에서 대상과 비밀을 읽고, 그것이
비어 있거나 이 기계 밖을 가리키면 멈춥니다. 서비스 설정은 바꾸지 않고, 자기가 심은 것에 표시를
남겨 두 번째 실행에서는 아무것도 더하지 않습니다.
실패한 덱이 있어야 보이는 화면
대기열, 오류 센터, 편집기의 실패 화면, 공유 목록 — 이 네 화면은 새 배포에는 아무것도 없어서
찍을 수 없었습니다. 빈 목록은 안내서에 싣지 않는 것이 기준이고, 오류 센터 사진 하나는 있었지만
오류가 없는 배포의 것이었습니다. 화면이 제 일을 하지 않는 순간의 사진입니다.
그래서 스크립트가 생성이 실패한 덱 하나를 직접 만듭니다. 모델 호스트가 그냥 꺼져 있으면 되지
않습니다 — 그때는 작성기가 오프라인으로 덱을 완성하고 그렇다고 말합니다. 대신 한 번의 생성에 한해
모델 주소를 배포 자신의 API 로 돌립니다. 그곳은 completions 호출에 401 과 JSON 본문으로 답하므로,
잘못 설정된 모델이라는, 운영자에게 실제로 닿는 종류의 실패가 됩니다. 옮긴 설정 두 개는
먼저 읽어 두었다가 finally 에서 되돌리고(scripts/e2e/withmodel.py 와 같은 방식), 두 번째
실행은 이미 실패한 덱을 찾아 설정에 손대지 않습니다.
그 결과로 다섯 장이 더 생겼습니다 — 실패한 덱과 사유가 보이는 대기열, 401 이 열린 오류 센터,
그 사건의 서랍, 공유 목록, 그리고 같은 실패를 보는 작성자 쪽 화면. 안내서는 이제 그 화면을
앞에 두고 말합니다: 작성자가 보는 사유 그대로의 다시 넣기·취소, 열림에서 해결까지의 상태
흐름(API 는 "조사 중"을 acknowledged 로 적는다는 것까지), 링크 회수. 사용자 안내서의 실패 항목은
버튼을 화면에 적힌 대로 생성 다시 시도라고 부르고, 관리자가 같은 덱을 대기열에서 보고
있다고 말합니다.
지키는 장치
internal/docs/told_test.go 는 제품이 읽는 모든 형식이 제품이 읽는다고 말하는 형식인지
봅니다. 안내서가 옮겨 가면서 이 검사는 정본이 아니라 안내문 한 줄을 읽고 있었고, 형식 이름이
하나도 없으니 제대로 실패했습니다. 이제 올릴 파일을 고르는 사람이 실제로 여는
USER_GUIDE.md 를 읽습니다.
Ptium v1.69.33
Ptium 1.69.33
PDF 가져오기를 다시 만들다시피 고쳤습니다. 아래 다섯 가지는 모두 같은 증상으로
끝났습니다 — 쪽에 글자가 하나도 없다고 나오고, 화면에는
이 PDF에는 글자가 없습니다. 스캔했거나 그림으로 내보낸 파일로 보입니다
라고 적혔습니다. 스캔이 아니라 보고서였고, 글자는 내내 거기에 있었습니다.
1. 두 겹으로 감싼 스트림을 풀지 못했습니다
PDF 는 본문을 압축한 뒤 다시 한 번 인쇄 가능한 글자로 감싸는 일이 흔합니다
(/Filter [ASCII85Decode FlateDecode]). 압축만 풀고 바깥 겹을 벗기지 않아 본문이
통째로 빈 쪽이 됐습니다.
이제 ASCII85 · ASCIIHex · RunLength · LZW 를 함께 풉니다. LZW 는 PDF 가 코드를 한
칸 일찍 늘리는 규칙(early change)을 그대로 지킵니다 — 범용 해제기가 이것을 놓쳐서
앞부분만 멀쩡하고 뒤가 깨지는 일이 많은 자리입니다.
2. 띄어쓰기가 $ 로 나왔습니다
부분집합 폰트는 글자 번호를 자기 마음대로 다시 매깁니다. 그 번호가 폰트의 지도에
없을 때, 읽는 쪽이 그 바이트를 그냥 아스키 글자로 찍고 있었습니다. 띄어쓰기가
0x24 번에 놓인 파일에서는 그것이 $ 였습니다.
매출$이$늘었습니다 → 매출 이 늘었습니다
이제 폰트가 스스로 말하는 /Encoding 과 /Differences 를 읽습니다. 그래도 이름이
없는 번호라면, 부분집합 폰트에서는 바이트를 글자로 찍지 않습니다 — 그 번호는 그
폰트의 것이지 아스키가 아니기 때문입니다.
3. 표가 낱말 하나씩 흩어졌습니다
PDF 에 표는 없습니다. 좌표에 찍힌 칸들이 있을 뿐이고, 칸 사이의 넓은 간격이
"띄어쓰기가 아니라 다음 열"이라는 신호입니다. 읽는 쪽은 그 신호를 제대로 지키고
있었는데, 그것을 다시 표로 모으는 곳이 없었습니다. 4열짜리 표가 요점 열여섯
개가 됐습니다.
이제 같은 기준선에 놓인 칸들을 한 행으로 묶고, 그런 행이 두 줄 이상 이어지면 표로
그립니다.
::table
- 항목 | 작년 | 올해 | 증감
- 매출 | 103억 | 128억 | +24%
두 단으로 짠 본문이 표로 오인되지 않도록, 칸이 짧을 때만 표로 봅니다. 표는 이름과
숫자를 담고, 단은 문장을 담기 때문입니다.
4. 폼 안의 글자를 읽지 않았습니다
워드·리브레오피스는 머리글·바닥글·레터헤드를 폼(XObject) 에 담고 Do 한 번으로
그립니다. 쪽 자신의 스트림만 읽어서 그 글자는 아무 말 없이 사라졌습니다.
이제 폼을 따라 들어가 읽고, 쪽 위 제자리에 놓습니다. 다만 쪽이 위에서 아래로
그려진 경우에만 자리를 맞춥니다 — 그렇지 않은 쪽을 다시 정렬하면 두 단짜리 본문의
좌우가 섞이기 때문입니다.
5. 암호가 걸린 파일을 "스캔"이라고 했습니다
사무실에서 흔한 설정은 소유자 암호만 거는 것입니다. 누구나 열 수 있고 인쇄·복사만
막습니다. 그래도 스트림은 암호화되어 있어서, 풀지 않으면 빈 쪽이 나옵니다.
이제 빈 사용자 암호로 열리는 파일은 풀어서 읽습니다(RC4 40·128비트, AES-128,
AES-256). 그리고 진짜로 암호를 요구하는 파일은 그렇다고 말합니다.
이 PDF는 암호가 걸려 있어 열지 못했습니다. 암호를 푼 파일로 올려주세요
지키는 장치
internal/pdftext/imports_test.go 가 위 다섯 가지를 각각 파일로 만들어 확인합니다.
internal/docs/pdftable_test.go 는 표로 봐야 할 것과 그러면 안 될 것(두 단 본문,
그림 옆 설명)을 함께 봅니다. scripts/e2e/api.py 는 표가 든 덱을 PDF 로 내보낸 뒤
다시 가져와 ::table 로 돌아오는지 봅니다.
다섯 가지 고침을 하나씩 되돌려, 각 검사가 그 자리를 정확히 짚는 것을 확인했습니다.
확인만 하고 고치지 않은 것
- 쪽이 90도 돌아간 파일, 여러 겹 압축, 객체 스트림은 원래 제대로 읽고 있었습니다
- 파일 전체에 글자가 한 줄뿐인 PDF 는 표지 하나로만 보고 "내용을 찾지 못했다"고
답합니다. 쪽마다 제목만 있는 발표 자료(4쪽)는 4장으로 제대로 들어오므로, 한 줄짜리
파일은 표지 그 자체로 보는 편이 맞다고 판단해 그대로 두었습니다
Ptium v1.69.32
Ptium 1.69.32
열두 시트를 올렸는데 "앞 30개를 가져왔습니다"
한 덱은 슬라이드 30장까지 담습니다. 통합 문서는 그보다 많은 시트를 담을 수
있고, 덱이 다 차면 리더는 거기서 멈추면서 한 줄을 남겼습니다.
시트가 많아 앞 30개만 가져왔습니다
이 문장에는 빠진 시트의 이름이 하나도 없습니다. 그리고 30은 시트 수가
아니라 슬라이드 수입니다. 시트마다 표가 커서 세 장씩 차지하는 통합 문서라면
덱은 열 번째 시트에서 다 차는데, 열두 시트를 올린 사람은 "앞 30개를
가져왔다"는 말을 듣습니다 — 올린 적 없는 통합 문서에 대한 이야기입니다.
통합 문서: 시트 12개 · 시트마다 슬라이드 3장
이전: 시트가 많아 앞 30개만 가져왔습니다
지금: 슬라이드가 많아 시트(11월, 12월)는 가져오지 않았습니다.
나눠서 올리면 전부 가져옵니다
무엇이 빠졌는지 모르면 확인할 방법도 없습니다. 덱을 처음부터 끝까지 넘겨 보며
통합 문서와 하나씩 맞춰 보는 것 말고는.
고친 내용
한도를 넘긴 뒤에도 시트를 계속 훑어 덱에 담기지 않은 시트의 이름을 모읍니다.
말투는 숨긴 시트 경고와 같고, 다음에 무엇을 하면 되는지는 긴 워드 문서 경고가
이미 하던 안내를 그대로 씁니다 — 나눠서 올리면 전부 들어옵니다.
이름은 다섯 개까지 대고 나머지는 세어서 말합니다.
빠진 시트 2개 : 시트(11월, 12월)는 가져오지 않았습니다
빠진 시트 10개: 시트(31, 32, 33, 34, 35 외 5개)는 가져오지 않았습니다
경고는 사람이 읽는 한 줄입니다. 마흔 개의 이름은 그 한 줄에 옮겨 적은 통합
문서의 목차이고, 끝에 붙은 이름부터 화면 밖으로 밀려납니다. 한 줌을 넘어가면
몇 개인지가 행동할 수 있는 부분이고, 앞의 몇 개는 통합 문서의 어디쯤에서
잘렸는지를 말해 줍니다. 이 목록 규칙(sheetsNamed)은 숨긴 시트 경고와 "모두
숨겨져 있습니다" 오류도 함께 씁니다 — 숨긴 시트가 마흔 개인 통합 문서도 이제
마흔 개를 다 늘어놓지 않습니다.
빈 시트는 이름을 대지 않습니다. 아무것도 없는 시트는 애초에 슬라이드가 될
수 없었으므로 한도 때문에 빠진 것이 아닙니다.
지키는 장치
internal/docs/sheetlimit_test.go 가 통합 문서 네 가지를 통째로 읽습니다 —
시트 34개, 시트마다 세 장씩 차지하는 12개, 이름이 다섯을 넘는 40개, 한도 뒤에
있는 빈 시트. 각각에 대해 빠진 시트의 이름이 경고에 있는지, 담긴 시트의 이름은
없는지, 옛 문장의 "앞 30개"가 더는 나오지 않는지, 그리고 경고가 빠졌다고 말한
시트가 정말 덱에 없는지를 봅니다. 딱 30장에 맞는 통합 문서가 아무 경고도 하지
않는 것까지 봅니다.
sheetsNamed 자체는 여섯 가지로 — 없음, 하나, 둘, 다섯, 여섯, 여덟 — 다섯을
넘는 자리에서만 세기 시작하는지 확인합니다.
Ptium v1.69.31
Ptium 1.69.31
한 번 저장하면 다시는 볼 수 없던 설정
관리 · 설정 화면에서 API 키 회전 유예(security.api_key_grace)를 저장하면,
그 값이 화면에서 사라졌습니다. 같은 값을 그대로 저장해도 그랬습니다.
저장 전 : value "24h" · sensitive false
저장 후 : (value 없음) · sensitive true
어느 설정을 가려야 하는지 두 곳에서 따로 정하고 있었습니다. 제품이 실어 보내는
선언은 이 값을 가리지 않는다고(sensitive: false) 적어 두었는데, 화면 쪽은 이름을
보고 짐작했습니다 — api_key_grace 안에 api_key 가 들어 있다는 이유로 비밀번호와
같은 취급을 받은 것입니다.
한 번 그렇게 저장되면 값은 암호화되어 들어가고, 다시 시작해도 가려진 채로 남습니다.
관리자는 자기가 넣은 값을 확인할 방법이 없었습니다. 회전 유예 자체는 계속 제대로
동작했기 때문에(2초로 두고 재어 보면 2초 뒤 옛 키가 거절됩니다) 겉으로는 아무 일도
없어 보였습니다.
고친 내용
무엇이 비밀인지는 제품이 실어 보내는 선언 한 곳에서만 정합니다. 화면은 그것을
물어보고, 제품이 모르는 열쇠에 대해서만 예전처럼 이름을 봅니다.
이미 저 규칙으로 봉인된 값도 되찾습니다. 서버는 그 값을 열 수 있으므로, 선언이
"비밀이 아니다"라고 말하는 열쇠는 목록에서 다시 보여 줍니다 — 관리자가 값을 짐작해
다시 입력할 필요가 없습니다. 다음에 저장할 때 데이터베이스에도 평문으로 정리됩니다.
진짜 비밀(ai.api_key, auth.oidc.client_secret)은 그대로 가려집니다.
지키는 장치
internal/httpapi/settingsecret_test.go 가 제품이 싣는 설정 전부에 대해, 화면이
가리는지 여부와 제품이 선언한 값이 같은지 봅니다.
검사가 헛돌지 않도록 두 가지를 먼저 확인합니다 — 읽어 온 설정이 스무 개가 넘는지,
그리고 그중에 가리는 것과 안 가리는 것이 둘 다 있는지. 한쪽뿐이면 "모두 일치"가
아무것도 증명하지 못하기 때문입니다.
이름으로 짐작하던 옛 규칙으로 되돌려 돌리면, 이 검사가 문제의 열쇠를 이름까지
짚어 실패합니다.
릴리즈가 "매니페스트가 잘못됐다"고 말하던 자리
오프라인 묶음을 만들 때 쿠버네티스 매니페스트를 kubectl 로 확인합니다. 클러스터가
없는 기계에서도 넘어가도록 --validate=false 로 한 번 더 시도하게 되어 있었는데,
그 명령도 클러스터에 물어봅니다 — 어떤 종류(kind)가 존재하는지 알아내야 하기
때문입니다.
그래서 클러스터가 잠든 노트북에서는 두 시도가 같은 이유로 실패하고, 릴리즈가
The Kubernetes manifest in this bundle is not valid.
라고 말하며 멈췄습니다. 매니페스트는 멀쩡했고, 아무도 그것을 읽어 보지 못했을
뿐입니다.
이제 클러스터가 없으면 아무것도 필요 없는 확인을 합니다. 매니페스트를 해독해
자원이 하나라도 있는지, 각각 apiVersion · kind · metadata.name 을 갖고 있는지
봅니다. 그리고 둘 중 무엇을 했는지 말합니다.
note: 4 resources decode and are named; no cluster was reachable to
check them against the API schema
kind 를 지운 파일, 이름이 없는 파일, YAML 이 깨진 파일, 빈 파일 네 가지로
되돌려 확인했고 모두 릴리즈를 멈춥니다.
Ptium v1.69.30
Ptium 1.69.30
납기 2025-01-21 이 슬라이드에 "2025-01-21T13:30:00" 으로 나왔습니다
통합 문서를 "Strict Open XML Spreadsheet" 로 저장하면 — 보존용 문서고나
공공기관 제출본이 요구하는 그 형식입니다 — 엑셀은 날짜를 며칠째인지 세는
숫자가 아니라 날짜 그 자체로 씁니다. 셀에 t="d" 가 붙고 값은
2025-01-21T13:30:00 이라는 글자입니다.
리더는 날짜라면 숫자일 것이라고만 알고 있었으므로 이 글자를 숫자로 읽으려다
실패했고, 실패한 값은 손대지 않고 지나갔습니다. 셀에 어떤 날짜 서식이 붙어
있든 상관없이, 저장된 글자가 그대로 슬라이드에 실렸습니다.
통합 문서: 엄격 스키마로 저장한 발주서 (t="d")
서식: yyyy-mm-dd ← 납기 열
시트: 납기 | 2025-01-21
이전: 납기 | 2025-01-21T13:30:00
지금: 납기 | 2025-01-21
가운데의 T 는 사람이 쓰는 표기가 아닙니다. 그것이 붙은 채로 슬라이드에 나오면
발표 자료가 아니라 파일을 내보내다 만 화면처럼 보이고, 게다가 그 슬라이드에는
어느 시트에서 가져왔는지 출처까지 달려 있습니다.
얼마만큼을 보여 줄지는 셀 서식이 정합니다
이제 t="d" 를 순간으로 읽고, 일련번호로 저장된 날짜와 똑같은 규칙으로
서식이 무엇을 보여 줄지 정합니다.
- 날짜 서식(
yyyy-mm-dd) —2025-01-21 - 날짜+시각(
yyyy-mm-dd hh:mm) —2025-01-21 13:30 - 시각(
h:mm) —13:30 - 서식이 초를 적으면(
hh:mm:ss) 초까지 —13:30:00
서식이 없거나(General), 백분율·경과 시간처럼 순간에 맞지 않는 서식이면 저장된
순간 자체를 씁니다 — 자정이면 날짜만, 시각이 있으면 시각까지, 초가 있으면
초까지. 통합 문서가 아무 말도 하지 않았는데 값을 잘라내지는 않습니다.
시간대는 읽되 옮기지 않습니다. 13:30+09:00 으로 적힌 회의는 슬라이드에서도
13:30 입니다. 다른 표준시로 환산하면 발표 자료의 회의 시각이 통째로
움직이는데, 시트가 보여 주는 시각은 적힌 그 시각이기 때문입니다.
순간이 아닌 값은 전과 같이 손대지 않고 그대로 둡니다.
지키는 장치
internal/docs/sheetmoments_test.go 가 서식과 값 19가지를 보고, 순간이 적힐 수
있는 열 가지 모양(오프셋이 붙은 것, Z, 소수점 초, 시각 없는 날짜, 분까지만
적힌 것)을 각각 확인합니다. 그리고 엄격 스키마로 저장한 통합 문서 한 개를
통째로 읽어, 납기 열이 슬라이드에 2025-01-21 로 도착하는지를 봅니다.
Ptium v1.69.29
Ptium 1.69.29
납기 2025-01-21 이 슬라이드에는 2021-01-20 으로 적혔습니다
엑셀은 날짜를 날짜로 저장하지 않고 어느 날부터 며칠째인지로 저장합니다.
그 "어느 날"이 통합 문서마다 다릅니다 — 윈도 엑셀은 1900년부터, 맥 엑셀은
1904년부터 셉니다. 어느 쪽인지는 xl/workbook.xml 의 workbookPr date1904 에
적혀 있고, 리더는 그 스위치를 읽지 않았습니다.
통합 문서: 맥 엑셀이 쓴 계약서 (date1904="1")
시트: 납기 | 2025-01-21
이전: 납기 | 2021-01-20 ← 4년 하고 하루 이른 날
지금: 납기 | 2025-01-21
틀린 티가 나지 않는 것이 이 문제의 나쁜 점입니다. 2021-01-20 은 날짜처럼
생겼고, 게다가 그 슬라이드에는 어느 시트에서 가져왔는지 출처까지 달려 있어
보고서처럼 보입니다. 맥에서 만든 통합 문서는 열어 보는 사람이 윈도든 맥이든
계속 1904년부터 세므로, 한 번 그렇게 쓰인 파일은 어디로 가도 그대로입니다.
이제 date1904 가 날을 세기 시작하는 날을 정하고, 날짜를 적는 일은
dayShown 한 곳에서 두 체계를 함께 다룹니다. 1 과 true 를 스위치로 보고,
아무 말이 없는 통합 문서는 전과 같이 1900년부터 셉니다.
1900년 2월 29일이라는 없는 날
1900년부터 세는 쪽에는 사연이 하나 더 있습니다. 이 체계는 1900-02-29 라는,
달력에 없는 날을 하루 세고 넘어갑니다. 옛 스프레드시트가 윤년을 잘못 잡은 것을
그 뒤로 모든 리더가 그대로 지켜 왔기 때문입니다 — 사람들이 이미 갖고 있던 날
수가 계속 같은 날을 뜻하게 하려고.
리더는 그 없는 날을 1900년 3월부터만 지켰으므로, 그 앞은 어긋나 있었습니다.
일련번호 1: 이전 1899-12-31 지금 1900-01-01
일련번호 59: 이전 1900-02-27 지금 1900-02-28
일련번호 60: 이전 1900-02-28 지금 1900-02-29 ← 59와 같은 날로 나오던 그 날
이제 셋 다 시트가 보여 주는 날을 말합니다.
날짜에 시각이 붙은 셀
날짜+시각(yyyy-mm-dd hh:mm)도 같은 날 계산을 쓰므로 두 체계에서 모두 맞습니다.
덤으로, 초 단위 반올림을 날을 떼어내기 전에 하도록 바꾸어 자정 직전의
한순간이 "24:00" 이 아니라 다음 날 "00:00" 으로 넘어갑니다.
지키는 장치
internal/docs/sheetdays_test.go 가 1904 체계 서식 9가지, 1900 체계 서식
6가지, counts1904 스위치 7가지를 보고, 맥 엑셀이 쓴 통합 문서와 그렇지 않은
통합 문서를 통째로 읽어 같은 일련번호가 각각 어느 날로 나오는지를 봅니다.
옛 기대값을 담고 있던 TestTheDayTheCountRunsFrom 도 시트가 보여 주는 날로
고쳤습니다.
Ptium v1.69.28
Ptium 1.69.28
숨겨 둔 코드표가 분기 실적 다음 장이 되었습니다
통합 문서는 어떤 시트를 숨겼는지를 xl/workbook.xml 의 state 에 적어 둡니다.
리더는 시트 이름과 관계 ID 만 읽고 state 는 아예 보지 않았으므로, 숨긴
시트도 보이는 시트와 똑같이 슬라이드가 되었습니다.
시트: 분기 실적 (보임) / 코드표 (숨김) / 설정 (매우 숨김)
이전: 분기 실적 · 코드표 · 설정 (세 장, 경고 없음)
지금: 분기 실적 (한 장 + "숨겨진 시트(코드표, 설정)는 가져오지 않았습니다")
숨긴 시트는 아무도 보라고 만든 것이 아닙니다 — 수식이 참조하는 코드표, 매크로가
읽는 설정, 지난 분기 작업본입니다. 그것이 발표 자료 한가운데에 들어갔고, 게다가
출처까지 달려 보고서인 양 보였습니다.
이제 state 가 hidden 이거나 veryHidden 인 시트는 건너뜁니다. veryHidden
은 매크로로만 되돌릴 수 있는 상태이므로 발표 자료와 더 멀지 가깝지 않습니다.
대소문자와 앞뒤 공백은 무시하며(Hidden 도 숨김), state 가 적혀 있지 않거나
visible 인 시트는 전과 같이 그대로 가져옵니다.
빠뜨린 것을 말없이 빠뜨리지는 않습니다. 무엇을 두고 왔는지 이름을 들어
경고합니다 — 올린 사람이 발표 자료를 넘겨보다 없는 것을 발견하는 대신, 올린
자리에서 알게 됩니다.
시트가 전부 숨겨진 통합 문서는 "이 통합 문서에는 읽을 표가 없습니다" 라고
답했습니다. 숫자가 눈앞에 보이는 파일을 든 사람을 파일이 잘못된 줄 알고
되돌려 보내는 말입니다. 이제 시트가 모두 숨겨져 있다고, 그 이름과 함께
말합니다.
지키는 장치
internal/docs/sheethidden_test.go 가 통합 문서 다섯 가지(숨김 시트가 섞인 것,
매우 숨김, 숨기지 않은 것, 대문자로 적힌 숨김, 숨김 두 장)를 통째로 읽어 어떤
시트가 슬라이드가 되고 어떤 이름이 경고에 실리는지를 봅니다. 전부 숨겨진 통합
문서가 이름을 들어 답하는지도 따로 보고, sheetHidden 자체를 여덟 가지 값으로
확인합니다.
Ptium v1.69.27
Ptium 1.69.27
회의 시각 13:30 이 0.5625 로 읽혔습니다
엑셀은 시각도 날짜와 같은 수로 저장합니다. 하루가 1이므로 13:30 은 0.5625,
30분은 0.0208 입니다. 리더는 서식 코드에서 y·d(날짜)와 %(백분율)만 찾았고
h·s(시각)는 찾지 않았으므로, 시각 셀은 서식이 없는 숫자처럼 저장된 분수
그대로 나왔습니다.
서식: h:mm ← 회의 순서표의 시각 열
시트: 개회 | 13:30 / 보고 | 14:30
이전: 개회 | 0.5625 / 보고 | 0.6041666667
지금: 개회 | 13:30 / 보고 | 14:30
세 가지가 함께 잘못 읽혔습니다.
- 시각(
h:mm,h:mm:ss, 내장 서식 18–21) — 0과 1 사이의 분수로 나왔고,
게다가 열이 숫자로 보였으므로 순서표가 표가 아니라 0.5 근처 막대 차트가
되었습니다. - 날짜+시각(
yyyy-mm-dd hh:mm, 내장 서식 22) — 날짜만 남고 시각이
잘렸습니다. 그 셀이 있는 이유가 시각인데 사라졌습니다. - 경과 시간(
[h]:mm:ss,[mm]:ss, 내장 서식 45–47) — 근무 시간표의 합계
36시간이1.5로 나왔습니다.
이제 서식이 시각을 말하면 시계로 옮깁니다. 시각은 13:30, 초를 적는 서식이면
13:30:00, 날짜와 함께면 2025-01-21 13:30 입니다. 대괄호 안의 단위([h],
[mm])는 "하루를 넘겨서 세라"는 뜻이므로 경과 시간으로 보고 36:00:00 처럼
통째로 셉니다 — 한나절로 접지 않습니다.
날짜가 서식과 관계없이 2025-01-21 로 적히는 것과 같이, 시각도 오전/오후
서식이든 24시간 서식이든 한 가지 방식으로 적습니다. m 은 월과 분이 같은
글자이므로 그것만으로는 시각으로 보지 않고, [Red] 같은 색·조건·지역은 전과
같이 단위가 아닙니다. 음수와 마지막 날(9999-12-31)을 넘는 수는 시각이 아니므로
적힌 그대로 둡니다.
지키는 장치
internal/docs/celltimes_test.go 가 시각·초 있는 시각·오전오후 서식·날짜+시각·
경과 시간·분 단위 경과·자정·반올림·날짜만·따옴표 안의 h·빨간 시각과 내장 서식
20·21·22·46, 그리고 서식 없는 숫자와 글자까지 19가지를 봅니다. kindOfFormat
과 elapsedUnit 자체도 따로 확인하고, 통합 문서 한 개를 통째로 읽어 순서표가
13:30·36:00:00 로 나오는지를 봅니다.