CLIEN

본문 바로가기 메뉴 바로가기 보기설정 테마설정
톺아보기 공감글
커뮤니티 커뮤니티전체 C 모두의광장 F 모두의공원 I 사진게시판 Q 아무거나질문 D 정보와자료 N 새로운소식 T 유용한사이트 P 자료실 E 강좌/사용기 L 팁과강좌 U 사용기 · 체험단사용기 W 사고팔고 J 알뜰구매 S 회원중고장터 B 직접홍보 · 보험상담실 H 클리앙홈
소모임 소모임전체 ·굴러간당 ·아이포니앙 ·주식한당 ·일본산당 ·MaClien ·방탄소년당 ·개발한당 ·자전거당 ·야구당 ·가상화폐당 ·이륜차당 ·소시당 ·AI당 ·IoT당 ·골프당 ·퐁당퐁당 ·디아블로당 ·DANGER당 ·안드로메당 ·육아당 ·키보드당 ·덕질한당 ·걸그룹당 ·나스당 ·리눅서당 ·클다방 ·레고당 ·PC튜닝한당 ·시계찬당 ·바다건너당 ·3D메이킹 ·X세대당 ·ADHD당 ·AI그림당 ·날아간당 ·사과시계당 ·배드민턴당 ·농구당 ·블랙베리당 ·곰돌이당 ·비어있당 ·FM당구당 ·블록체인당 ·보드게임당 ·활자중독당 ·볼링친당 ·캠핑간당 ·냐옹이당 ·문명하셨당 ·클래시앙 ·콘솔한당 ·요리한당 ·쿠키런당 ·대구당 ·뚝딱뚝당 ·개판이당 ·동숲한당 ·날아올랑 ·전기자전거당 ·e북본당 ·갖고다닌당 ·이브한당 ·패셔니앙 ·물고기당 ·도시어부당 ·FM한당 ·맛있겠당 ·포뮬러당 ·젬워한당 ·안경쓴당 ·차턴당 ·총쏜당 ·땀흘린당 ·하스스톤한당 ·히어로즈한당 ·인스타한당 ·KARA당 ·꼬들한당 ·어학당 ·가죽당 ·LOLien ·Mabinogien ·임시소모임 ·미드당 ·밀리터리당 ·땅판당 ·헌팅한당 ·오른당 ·영화본당 ·MTG한당 ·소리당 ·노키앙 ·적는당 ·방송한당 ·찰칵찍당 ·그림그린당 ·소풍간당 ·심는당 ·패스오브엑자일당 ·라즈베리파이당 ·품앱이당 ·리듬탄당 ·노젓는당 ·달린당 ·Sea마당 ·SimSim하당 ·심야식당 ·윈태블릿당 ·미끄러진당 ·축구당 ·나혼자산당 ·스타한당 ·스팀한당 ·파도탄당 ·테니스친당 ·테스트당 ·빨콩이당 ·공대시계당 ·여행을떠난당 ·터치패드당 ·트윗당 ·창업한당 ·VR당 ·WebOs당 ·소셜게임한당 ·위스키당 ·와인마신당 ·WOW당 ·윈폰이당
임시소모임
고객지원
  • 게시물 삭제 요청
  • 불법촬영물등 신고
  • 쪽지 신고
  • 닉네임 신고
  • 제보 및 기타 제안
© CLIEN.NET
공지[점검] 잠시후 서비스 점검을 위해 약 30분간 접속이 차단됩니다. (금일 18:15 ~ 18:45)

팁과강좌

PDA 클리에 되살리기 - 클라우드 씽크 앱 만드는 방법 2

5
2026-08-15 22:18:29 수정일 : 2026-08-15 22:33:40 218.♡.203.3
그시절그때

아래는 클로드코드가 쓴 글입니다. 

코딩 다 끝내고 방금 개발한 내용을 커뮤니티에 자랑하고 싶어. 포스팅할 내용 써줘...라고 하니 써주네요. 

크롬플렉스 OS에서는 아예 드라이버 설치도 필요없이 동기화가 바로 되고, 윈11에서도 드라이버만 깔면 한방에 씽크 될거라 합니다. (클로드가)


서랍에 있던 소니 클리에(PalmOS)를 다시 쓰고 싶어서 만들었습니다. 

문제는 HotSync 시절 소프트웨어가 요즘 PC에서 안 돈다는 것이었고, 그래서 브라우저만으로 동작하는 동기화 웹앱을 만들었습니다.

크래들에 꽂고 웹페이지에서 동기화 버튼 한 번, 기기에서 HotSync 한 번.

그러면 메모·일정·연락처가 들어갑니다. 설치할 프로그램도, 드라이버도 없습니다.


1.jpg

3.jpg




되는 것

  • 메모 — PC에서 쓰던 메모가 기기 Memo Pad로. 폴더가 그대로 카테고리가 됩니다
  • 일정 — Google 캘린더에서 앞뒤 90일치를 Date Book으로
  • 연락처 — Google 연락처를 주소록으로
  • 한글 — EUC-KR로 넣어 기기에서 한글이 그대로 보입니다 (기기에 한글 환경 필요)
  • 앱/파일 설치 — .prc/.pdb를 기기에 밀어넣는 기능도 함께

표가 들어간 메모는 화면 폭에 맞춰 자동 변환됩니다. 좁은 표는 칸을 맞춰 정렬하고, PC용 넓은 표는 항목별로 세로로 펼쳐서 320px 화면에서도 읽힙니다. 이미지는 넣을 자리가 없어 [이미지] 표시만 남습니다.


어떻게

브라우저의 WebUSB로 기기와 직접 통신합니다. HotSync 프로토콜(DLP)을 그대로 씁니다. palm-sync, palm-pdb 두 오픈소스 라이브러리 위에 올렸고, 앱 자체는 정적 웹페이지라 서버가 필요 없습니다. 크롬에 앱으로 설치해서 쓰고 있습니다.


일부러 이렇게 만든 것

단방향입니다. 소스 → 기기로만 갑니다. 기기에서 고친 내용은 원본에 반영되지 않고 다음 동기화 때 덮어써집니다. 양방향으로 만들면 충돌 해소가 필요해 난이도가 확 뛰는데, 어차피 원본을 PC에서 관리하니 이쪽이 훨씬 안전하고 단순합니다.

Google 쪽은 읽기 전용 권한만 요청합니다. 코드에 버그가 있어도 원본 캘린더나 연락처를 건드릴 수 없습니다.


삽질 기록도 같이 공개합니다

20년 전 포맷을 다루다 보니 문서에 없는 함정이 많았습니다. 몇 가지만 예를 들면,

  • 레코드가 2개 이상일 때만 기기 데이터가 깨지는 라이브러리 버그
  • 카테고리 이름을 따로 안 보내면 기기에 반영이 안 되는 것
  • 기기 배터리가 빠져 날짜가 2000년으로 돌아가면, 일정을 제대로 넣어도 화면에 아무것도 안 보이는 것

이런 걸 정리한 개발 노트를 함께 올립니다. 같은 걸 만들려는 분이나, AI에게 이런 작업을 시키려는 분이 시행착오를 건너뛰는 데 도움이 되면 좋겠습니다.


....라고 썼는데. md 파일 업로드가 안되네요. 텍스트로 붙이긴 좀 많은데 ㄷㄷㄷ

축약해서 md 파일 내용 복붙하겠습니다. 필요한 분은 바로 아래에서부터 복사해서 쓰심 되겠습니다. 

아래의 힌트 정도만 있어도 클로드에게 붙여넣고 만들어 달라고 하면 삽질없이 한방에 만들어줄 겁니다.



[MD파일 내용]

# PalmOS 기기 동기화 앱 개발 요령

WebUSB로 PalmOS(Sony CLIÉ) 기기에 메모·일정·연락처를 밀어넣는 웹앱을 만들면서

실제로 부딪힌 함정들. **문서에 안 나오거나, 나와도 그냥 지나치기 쉬운 것만** 추렸다.

AI에게 이 작업을 시킬 때 이 문서를 먼저 읽히면 대부분의 시행착오를 건너뛴다.

기술 스택: TypeScript + Vite / `palm-pdb`(포맷) + `palm-sync`(WebUSB 전송) / Python 백엔드.

---

## 0. 관통하는 원칙

**라운드트립으로 검증하라.** 눈으로 보거나 문서를 믿지 말고, 만든 `.pdb`를 다시

역직렬화해서 값을 확인하는 스크립트를 Node에서 돌려라. 아래 함정 중 상당수는 그렇게

잡힌 것들이고, 안 그랬으면 전부 기기 앞에서 원인 모를 증상으로 만났을 것이다.

**기기는 읽기 전용 사본으로 취급하라.** 양방향 동기화는 충돌 해소가 필요해 난이도가

몇 배로 뛴다. "소스가 유일한 진실, 기기는 덮어쓰기"로 정하면 로직이 극단적으로 단순해진다.

**"동기화 성공" 로그를 믿지 마라.** DLP 명령이 성공했다는 건 기기가 명령을 받아들였다는

뜻이지, 사용자가 화면에서 데이터를 본다는 뜻이 아니다. 실제로 이 프로젝트에서 캘린더가

"성공"인데 기기에 아무것도 안 나오는 상태가 **다섯 가지 원인이 겹쳐서** 발생했다:

API 미활성 · 잘못된 캘린더 조회 · 토큰 부재 · 레코드 오프셋 버그 · 기기 날짜가 2003년.

하나 고쳤는데도 여전히 안 보이면 **다음 원인이 뒤에 숨어 있다고 가정하고 계층을 하나씩

끝까지 훑어라.** "고쳤는데 왜 안 되지"에서 멈추면 며칠이 날아간다.

**한 소스가 실패해도 나머지는 진행시켜라.** 메모·일정·연락처를 한 번에 보낼 때 하나가

죽는다고 전부 막으면, 원인 하나 고칠 때마다 처음부터 다시 해야 한다.

---

## 1. 기기 동기화 (WebUSB / DLP)

- **WebUSB는 HTTPS 또는 localhost에서만** 동작한다(secure context).

- **한 HotSync 세션에서 여러 DB를 처리하라.** DB마다 세션을 열면 사용자가 기기에서

  HotSync 버튼을 그 횟수만큼 눌러야 한다. 세션 하나 열고 순차 처리하면 한 번이면 된다.

- **기기 레코드를 읽지 마라.** 레코드가 많은 기기에서 per-record 읽기는 USB 에러를 낸다.

  카테고리 단위 삭제(`DlpDeleteRecordByCategory`)나 전체 삭제(`DlpDeleteAllRecords`) 후

  쓰기만 하면 읽을 일이 없다.

- **교체 범위를 DB마다 정하라.** "전체 삭제 후 재적재"는 단순하지만 기기에서 직접 입력한

  데이터도 지운다. "우리 카테고리만 교체"는 기기 데이터를 보존하지만, **기기에 원래 있던

  항목이 남아 화면에서는 중복처럼 보인다.** 소스가 유일한 진실인 DB는 전체 교체,

  기기에서도 입력하는 DB는 카테고리 교체 — 이렇게 나누면 깔끔하다.

- **카테고리 단위로 교체한다면, 레코드를 반드시 그 카테고리에 다시 넣어라.** 안 그러면

  다음 동기화 때 삭제 대상에서 벗어나 **매번 중복이 쌓인다**. 가장 발견이 늦는 버그다.

- **빈 목록으로 전체 교체를 하지 마라.** 소스가 아직 준비 안 됐거나 조회에 실패했는데

  빈 배열이 그대로 흘러가면 기기 데이터를 전부 지워버린다. "데이터가 있을 때만 작업을

  만든다"는 가드를 명시적으로 넣어라.

- **AppInfo를 명시적으로 써야(`DlpWriteAppBlock`) 카테고리 이름이 기기에 반영된다.**

  레코드만 보내면 카테고리 라벨은 기기의 기존 이름으로 보인다. 한참 헤맬 수 있다.

- 레코드를 쓸 때 `uniqueId = 0`으로 두면 기기가 새 ID를 부여한다.

- `AppInfo.sortOrder = MANUAL`로 두면 **보낸 순서가 기기에서 유지된다.** 정렬을 소스에서

  통제하고 싶으면 필수.

- **동기화할 때 기기 시계를 PC 시각으로 맞춰라**(`DlpSetSysDateTime`). 배터리가 빠지면

  PalmOS는 날짜가 2000년 전후로 돌아가는데, 그 상태로는 일정을 제대로 넣어도 **캘린더

  앱이 엉뚱한 연도를 "오늘"로 보고 열려서 아무것도 안 보인다.** 데이터가 잘못된 줄 알고

  한참 엉뚱한 곳을 파게 되는 증상이라, 매번 맞춰주는 편이 낫다. 실패해도 동기화는

  계속하면 되는 부가 기능이다.

- **카테고리는 최대 16개, 0번은 Unfiled 고정 → 실사용 15개.** 라벨은 **15바이트**(+NUL).

- **레코드 하나는 4096바이트**를 넘을 수 없다.

- **길이 0짜리 일정(시작=종료)을 만들지 마라.** 종일 일정에 기본 시각을 넣을 때

  09:00~09:00처럼 두면 기기에서 표시가 애매해진다. 끝 시각을 1시간 뒤로 두면 안전하다.

- **레코드 수가 수천 건이면 전송이 수 분 걸린다.** DLP는 한 건씩 쓰기 때문이다.

  보낼 필드를 최소한으로 줄이고(화면에서 안 쓰는 주소·메모 등은 빼라), 진행 상황을

  로그로 보여줘라. 안 그러면 사용자가 멈춘 줄 알고 크래들에서 뽑는다.

- 기기가 오래됐다고 얕보지 마라. 기종마다 기본 앱이 다르다(예: 일부 기기는 자체

  조직관리 앱이 표준 DB를 안 보여준다). **표준 앱으로 확인**해야 한다.

### 배포 관련

- `palm-sync`는 npm 미배포라 **벤더링**해서 `dist/browser-bundle.js`를 커밋해야 한다.

- 그때 `.gitignore`의 dist 규칙은 **루트 앵커(`/dist/`)** 여야 한다. `dist/`로 쓰면

  벤더 디렉터리의 dist까지 무시돼 CI가 깨진다.

- **`globalThis.Buffer`는 프로덕션 번들에서 `undefined`일 수 있다.** 반드시

  `import {Buffer} from 'buffer'`로 명시 import. 개발 서버에서만 되고 배포하면 깨지는

  전형적인 함정.

---

## 2. 한글(EUC-KR) 처리

- `palm-pdb` → `serio` → `iconv-lite` 경로라 **`serialize({encoding: 'euc-kr'})`** 한 줄로

  된다. 인코딩 옵션은 문자열 필드 전체에 적용된다.

- **기기에 한글 환경이 설치돼 있어야 보인다**(PalmOS는 한글을 기본 지원하지 않는다).

  없으면 EUC-KR로 보내도 깨져 보이므로, 인코딩 전환 스위치를 두는 편이 안전하다.

- **글자 수가 아니라 바이트로 잘라라.** 한글은 2바이트라 `text.slice(0, 255)`는 최대

  510바이트가 된다. 필드 상한이 바이트 단위인 곳(설명, 카테고리 라벨 등) 전부 해당.

- **4KB 레코드 제한에 두 배로 빨리 걸린다.** 긴 텍스트는 줄 경계 우선으로 분할하고,

  제목에 `(1/n)` 같은 표시를 붙여라. 조각 수가 제목에 들어가면 헤더 크기가 조각 수에

  의존하는 순환이 생기니 자릿수를 재계산하는 루프가 필요하다.

- **표현 불가 문자 검출은 라운드트립으로.** `decode(encode(s, enc), enc) === s`.

  코드포인트 범위 휴리스틱(`> 0xFF` 같은)은 인코딩이 바뀌면 바로 틀린다.

- **카테고리 라벨 15바이트 = 한글 7자.** 폴더명이 넘칠 때는 자르기 전에 **공백부터

  제거**하면 대부분 들어간다(`"주간 업무 보고서"` 16B → `"주간업무보고서"` 14B).

- **구분자로 특수기호를 쓰지 마라.** `▸`, `·` 같은 문자는 EUC-KR에 없을 수 있다.

  ASCII(`>`, `-`)를 써라.

- 표 정렬 등 **화면 폭 계산은 "표시 칸" 기준**으로. 한글·CJK·전각은 2칸이다.

---

## 3. Google 연동 (Calendar / Contacts)

- **Google Keep은 개인 계정용 공식 API가 없다.** Keep API는 Workspace 전용이다.

  메모 소스로 Keep을 쓰려는 계획은 여기서 막힌다. 시작 전에 확인할 것.

- Contacts는 **People API**를 쓴다. 옛 "Contacts API"(GData)는 지원 종료됐다.

- **API를 프로젝트마다 개별로 사용 설정**해야 한다. 다른 프로젝트에서 되던 API도

  새 프로젝트에서는 403이 난다.

- **OAuth 동의 화면이 "테스트" 상태면 refresh token이 7일마다 만료된다.**

  "프로덕션"으로 게시하면 해결된다. 개인용이면 **심사(verification)는 받을 필요 없다** —

  "확인되지 않은 앱" 경고를 한 번 넘기면 되고, 미검증 상태로도 100명까지 쓸 수 있다.

- 서버에서 돌릴 토큰은 **"데스크톱 앱" 유형** 클라이언트로 발급해야 `InstalledAppFlow`가

  동작한다. JSON 최상위 키가 `installed`면 데스크톱, `web`이면 웹이다.

- **`access_type=offline` + `prompt=consent`를 명시하라.** 이미 동의한 계정에는 Google이

  refresh token을 다시 주지 않아, 1시간 뒤 갱신 실패로 조용히 죽는다.

- **스코프를 추가하면 기존 토큰은 못 쓴다.** 재발급 필요.

- **읽기 전용 스코프만 요청하라.** 원본을 건드릴 수 없게 구조적으로 보장된다 —

  코드에 버그가 있어도 안전하다.

- **`calendarId="primary"`만 보면 안 된다.** 실제 일정이 보조 캘린더에 있는 경우가 흔하다

  (기본 캘린더가 완전히 비어 있는 계정도 많다). `calendarList`를 조회해

  **`selected && !hidden`** 인 것만 쓰면 사용자가 화면에서 체크한 것과 일치한다.

- 캘린더별로 조회한 뒤 **합쳐서 다시 시간순 정렬**해야 한다.

### 연락처 중복 — 여기가 제일 지저분하다

- **전화번호 표기가 제각각이다.** `010-1234-5678`, `01012345678`, `+82 10 1234 5678`이

  한 사람 안에 같이 들어 있다. **숫자만 남겨 정규화한 뒤** 비교해야 중복이 잡힌다.

- **같은 사람이 이름 표기를 달리해 여러 건으로 흩어져 있다.** 한글명/영문명/직함 포함

  표기가 따로 저장된다. 이름 기준 병합으로는 못 잡는다.

  → **개인 휴대폰 번호가 같으면 동일인**으로 보는 편이 훨씬 정확하다.

  회사명이 달라도(이직, 한/영 표기) 병합하는 게 맞는 경우가 대부분이다.

- 병합할 때 **한글명과 영문명을 둘 다 남겨라**(예: 성=한글명, 이름=영문명).

  어느 쪽으로 검색해도 찾히게 된다.

- **Palm 주소록은 전화 슬롯이 5개뿐이고 이메일도 그 슬롯을 쓴다.** 중복을 먼저 제거하면

  잘려나가는 항목이 크게 준다. 잘릴 때를 대비해 `모바일 > 직장 > 집 > 이메일` 순으로

  우선순위를 두어라.

- 기기로 보낼 필드는 최소한으로. 레코드가 작아야 수천 건 USB 전송이 견딜 만하다.

### 비밀 관리

- `token.json`(refresh token)과 `credentials.json`(client secret)은 **반드시 gitignore**.

  한 번 커밋되면 히스토리에 영원히 남아, 지워도 토큰 폐기 말고는 답이 없다.

- 그래서 **코드는 git으로, 비밀 파일은 scp로** 나른다. 서버에서 `git pull` 해도

  토큰은 안 내려온다는 걸 처음에 명확히 해두면 혼란이 없다.

### CORS

- 프론트가 다른 도메인이면 백엔드 허용 오리진에 넣어야 한다. 증상은 `Failed to fetch`인데

  브라우저가 원인을 안 알려줘서 엉뚱한 곳을 파기 쉽다.

  **`curl -X OPTIONS`로 프리플라이트를 직접 찔러 `Access-Control-Allow-Origin` 유무를 확인**하면

  즉시 판별된다.

- 허용 오리진을 환경변수로만 두면, 그 변수가 설정된 서버에서는 코드의 기본값이 통째로

  무시된다. 고정 오리진은 **합집합으로 더하는** 편이 안전하다.

---

## 4. 리치 텍스트 → 메모패드

- **Memo Pad은 첫 줄을 목록의 제목처럼 보여준다.** 제목을 본문 맨 앞 줄로 합쳐라.

- **MemoDB는 순수 텍스트다. 이미지를 담을 필드가 아예 없다.** "작게 줄여서 넣는다"가

  아니라 넣을 자리가 없다. `[이미지]` 같은 자리표시자로 대체하고, 이미지가 꼭 필요하면

  별도 뷰어 앱과 그 앱의 전용 포맷이 필요하다(= 별개 프로젝트).

- **표 변환에 AI를 쓰지 마라.** 폭 계산과 정렬이라 결정론적으로 처리된다. AI를 쓰면

  ① 같은 입력이 매번 다르게 변환돼 증분 동기화 판정이 깨지고 ② 항목 수만큼 호출 비용이

  들며 ③ 내용이 왜곡될 위험이 있다.

- 표는 폭에 따라 두 갈래로:

  - **좁은 표** → 고정폭으로 열 맞춰 정렬

  - **넓은 표** → 행마다 세로로 전치 (`항목: 값` 나열). 좁은 화면에서 넓은 표를 읽히게

    하는 사실상 유일한 방법이고 정보 손실이 없다.

  - 임계 폭은 기기 화면 기준 40칸 안팎(영문 40자 / 한글 20자)에서 시작해 조정.

- HTML 파싱은 **브라우저 `DOMParser`**를 써라. 엔티티 처리와 깨진 마크업 복구가 공짜다.

  - 단, **블록 경계의 공백-only 텍스트 노드를 버려야** 한다. 소스 HTML의 들여쓰기가

    줄 앞 공백으로 그대로 남는다.

  - Node에서 테스트할 때 `linkedom`은 브라우저와 달리 **`<html>/<body>`를 자동 생성하지

    않는다.** 조각 HTML을 주면 body가 비어버리니 테스트 하네스에서 감싸야 한다.

- 빈 본문 메모는 제외하라(기기에서 빈 줄로 보인다).

- **양이 많으면 최근 N건만 보내는 옵션을 둬라.** 수백 건을 흑백 320px 화면에서 훑는 건

  현실적이지 않다. 만들고 나서야 깨닫게 된다.

---

## 5. 라이브러리별 함정 (palm-pdb)

- `AddressRecord.set()`은 **호출 시점의 `phoneNumberTypeMapping`을 셀에 굽는다.**

  매핑을 값보다 **먼저** 지정하지 않으면 직렬화가 예외로 실패한다. 문서에 없다.

- `AddressAppInfo`는 **22개 필드 라벨을 전부** 채워야 직렬화된다. 기기 기본 라벨과 같은

  값을 넣으면 기기 주소록이 낯설어지지 않는다.

- `AppInfoType.categories`는 **16칸을 다 채워야** 한다(빈 칸은 빈 문자열).

- 각 레코드에 **서로 다른 `uniqueId`**를 부여하라. 0으로 중복되면 기기가 같은 레코드로

  착각할 수 있다.

- ⚠️ **레코드의 `getSerializedLength()`가 실제 직렬화 길이와 다를 수 있다.**

  일정(Datebook) 레코드에서 실제 14바이트인데 12를, 메모가 붙으면 23인데 17을 돌려주는

  것을 확인했다. PDB는 레코드 목록에 **오프셋**을 담고 그 값이 이 함수로 계산되므로,

  **레코드가 2개 이상이면 두 번째부터 위치가 어긋나 기기가 깨진 데이터를 받는다.**

  1건일 때는 멀쩡해서 더 헷갈린다.

  ```ts

  class OffsetSafeRecord extends TheRecord {

    getSerializedLength(opts) { return this.serialize(opts).length; }

  }

  ```

  **DB 종류마다 레코드 1·2·50건으로 라운드트립을 돌려보라.** 같은 라이브러리라도

  레코드 타입별로 다르다(메모·주소록·할일은 멀쩡한데 일정만 틀린 식).

---

## 6. 검증 환경 만들기

기기에 꽂기 전에 Node에서 실소스를 돌리는 게 핵심인데, 여기에도 함정이 있다.

- **번들은 CJS로 뽑아라.** 인코딩 라이브러리가 `require('buffer')`를 동적으로 호출해서

  ESM 번들로는 실행이 안 된다(`Dynamic require of "buffer" is not supported`).

  `esbuild --bundle --format=cjs --platform=node` 조합이 무난하다.

- **테스트 파일을 프로젝트 디렉터리 안에 두고 번들해라.** Node/번들러는 파일 위치를

  기준으로 `node_modules`를 찾는다. 임시 폴더에 두면 의존성을 못 찾는다.

- HTML을 다루면 **`linkedom`은 브라우저 `DOMParser`와 달리 `<html>/<body>`를 자동 생성하지

  않는다.** 조각 HTML을 주면 body가 비어 결과가 통째로 빈 문자열이 된다. 테스트에서

  래핑하는 얇은 shim을 씌워야 실제 동작과 같아진다.

- 검증 스크립트는 **"기대값 대 실제값"을 한 줄씩 PASS/FAIL로 찍게** 만들어라. 한 번에

  수십 개를 돌려 회귀를 즉시 잡을 수 있고, 기기 앞에서 추측할 일이 없어진다.

## 7. 배포·운영에서 걸린 것

- **서버 파이썬은 `python3`이다.** `python`은 없는 배포판이 많다.

- **서비스가 쓰는 가상환경 경로를 먼저 확인하라**(`systemctl show <svc> -p ExecStart`).

  전역 파이썬에 라이브러리를 깔아봐야 서비스는 못 본다.

- **가상환경은 만들 때의 절대경로가 박힌다.** 사용자명이 바뀌거나 다른 PC에서 만든 폴더가

  클라우드 동기화로 넘어오면 실행 자체가 안 된다. 고치려 하지 말고 지우고 새로 만들어라.

- **정적 호스팅 + 별도 API 서버 구조면 CORS를 먼저 확인하라.** 증상이 `Failed to fetch`

  하나뿐이라 엉뚱한 곳을 판다.

- 앱처럼 설치(PWA)하려면 manifest + 서비스 워커가 필요한데, **서비스 워커가 API 응답을

  캐시하지 않게 오리진으로 걸러야 한다.** 안 그러면 낡은 데이터로 동기화되고, 토큰이

  붙은 응답이 디스크에 남는다.

---

## 8. 순서 체크리스트

처음부터 이 순서로 가면 헛걸음이 거의 없다.

1. **소스 API가 실제로 되는지부터 확인.** 개인 계정으로 접근 불가한 서비스가 있다.

   여기서 막히면 설계가 통째로 바뀐다.

2. 인증 먼저 뚫고(스코프·게시 상태·토큰 갱신), **읽기 전용 스코프**로 고정.

3. 소스 데이터를 **실제로 한 번 덤프해 보라.** 건수·중복·형식 편차가 예상과 다르다.

   여기서 필터링·병합 규칙이 정해진다.

4. 변환 계층(소스 → 기기 포맷)을 만들고 **라운드트립 검증**. 1·2·50건 전부.

5. 전송 계층. 한 세션 다중 DB, 시계 맞추기, 교체 범위 결정.

6. **그다음에** 실기기. 여기까지 오면 기기에서 볼 문제는 거의 없다.

7. 화면 폭·글자 수·건수는 실기기를 보고 조정한다. 이건 미리 정할 수 없다.

---

이 문서는 특정 프로젝트에 매인 내용 없이, 같은 작업을 하려는 사람이 바로 쓸 수 있게

정리한 것이다. 자유롭게 인용·수정·재배포해도 된다.



그시절그때 님의 게시글 댓글
  • 주소복사
  • Facebook
  • X(Twitter)
댓글 • [2]
돌체비타
IP 175.♡.102.142
03:19 2026-08-16 03:19:15
·
아이고 클리에 글이라니... ㅠㅠ UX50 불 들어오나 확인해봐야겠습니다. 어디 잘 넣어놓긴 했는데...
가물가물한 기억이긴한데, 태*전기였지 싶네요, 방문해서 배터리 갈았던 기억이 함께 떠오르네요.
grounder
IP 211.♡.59.212
12:00 2026-08-16 12:00:52
·
우와. 반갑네요. 안 그래도 클리에 몇개 구매를 최근에 했는데. 해보고 싶네요.
새로운 댓글이 없습니다.
이미지 최대 업로드 용량 15 MB / 업로드 가능 확장자 jpg,gif,png,jpeg,webp
지나치게 큰 이미지의 크기는 조정될 수 있습니다.
목록으로
글쓰기
글쓰기
목록으로 댓글보기 이전글 다음글
아이디  ·  비밀번호 찾기 회원가입
이용규칙 운영알림판 운영소통 재검토요청 도움말 버그신고
개인정보처리방침 이용약관 책임의 한계와 법적고지 청소년 보호정책
©   •  CLIEN.NET
보안 강화를 위한 이메일 인증
안전한 서비스 이용을 위해 이메일 인증을 완료해 주세요. 현재 회원님은 이메일 인증이 완료되지 않은 상태입니다.
최근 급증하는 해킹 및 도용 시도로부터 계정을 보호하기 위해 인증 절차가 강화되었습니다.

  • 이메일 미인증 시 글쓰기, 댓글 작성 등 게시판 활동이 제한됩니다.
  • 이후 새로운 기기에서 로그인할 때마다 반드시 이메일 인증을 거쳐야 합니다.
  • 2단계 인증 사용 회원도 최초 1회는 반드시 인증하여야 합니다.
  • 개인정보에서도 이메일 인증을 할 수 있습니다.
지금 이메일 인증하기
등록된 이메일 주소를 확인하고 인증번호를 입력하여
인증을 완료해 주세요.