👋 안녕하세요, AI-Native Engineer 승한입니다.
AI를 활용해 모바일 앱과 웹 서비스를 빠르게 만들고 있습니다. 사용자 경험을 개선하고 실생활 문제를 해결하는 것에 관심이 있습니다.
👋 안녕하세요, AI-Native Engineer 승한입니다.
AI를 활용해 모바일 앱과 웹 서비스를 빠르게 만들고 있습니다. 사용자 경험을 개선하고 실생활 문제를 해결하는 것에 관심이 있습니다.
오픈소스로 공개해둔 HWP/HWPX → Markdown 변환기 MDM(seunghan91/markdown-media)에 Tauri 데스크톱 뷰어를 붙여서 쓰고 있다. 0.3.0에서 HWPX 파서가 수식을 $...$ / $$...$$ LaTeX로 뽑도록 바꿨는데, 정작 뷰어에서는 달러 기호가 그대로 문자로 보였다. 수식 렌더링이 빠진 거다. 고치는 건 단순해 보였다. 마크다운 뷰어에 KaTeX 붙이면 끝. Obsidian, Typora, Zettlr 전부 이렇게 한다. 그런데 막상 조사해보니 2026년 기준 Tauri 같은 데스크톱 앱에서는 더 좋은 경로가 있었다. Rust 한 곳만 만지고 JS/CSS/폰트 번들은 0으로 유지하는 방법. 이 글은 그 선택 과정과, 덤으로 rhwp 프로젝트에 테스트 하네스를 기여하게 된 이야기다. ...
토너먼트 앱 하나를 Flutter로 전환하고 있는데, 사용자 입장에서 제일 중요한 시나리오가 동작을 안 했다. “로그인 → 알림 수신 → 알림 탭 → 내 코트로 이동 → 대기/경기/결과 확인”. 이게 끊기면 앱이 있으나 마나다. 처음에는 코드 조각은 대부분 있는 것 같았다. FCM 토큰 등록 로직도 있고, ActionCable 클라이언트 클래스도 있고, 스코어 입력 화면도 있다. 그런데 막상 실제로 알림을 탭해도 앱이 홈으로만 열린다. 실시간 업데이트도 안 온다. 왜? 탐색해보니 연결된 것처럼 보이는 부품 사이에 네 군데가 끊겨 있었다. 이 포스트는 그 네 군데를 하루 세션에서 다 고친 기록이다. 각 단절마다 왜 그게 2026년에도 여전히 함정인지 근거도 같이 정리한다. ...
오픈소스로 만든 Rust 문서 변환기 MDM(Markdown-Media)을 AI agent에서 직접 호출할 수 있게 MCP 서버로 노출하는 작업을 했다. 처음엔 @mdm/mcp-server 독립 Node.js 패키지로 만들 생각이었는데, 이미 운영 중인 Korea Law Hub Gateway에 tool 3개 추가하는 쪽이 훨씬 낫다는 결론이 나왔다. 이 글은 그 판단 과정과 실제 구현에서 걸린 지점들을 정리한다. 상황 MDM은 HWP, HWPX, PDF, DOCX, PPTX, XLSX, HTML, CSV, TXT를 Markdown으로 바꾸는 Rust 변환기다. 데스크톱 앱, PyPI 패키지(pip install mdm-parser), CLI 바이너리는 이미 있었다. 남은 건 Claude Code, Cursor, Continue.dev 같은 AI agent에서 MCP 프로토콜로 직접 부르는 경로. ...
법률 AI 서비스를 만들다 보면 이런 순간이 온다. LLM이 자신감 있게 “민법 제103조의2에 따라…” 라고 답변을 줬는데, 확인해보니 제103조의2라는 조문은 존재하지 않는다. 본조인 제103조만 있고 가지조문은 만들어진 것이다. 이게 얼마나 심각한 일인지는 이미 유명한 사건이 증명했다. 2023년 미국 Mata v. Avianca 소송에서 뉴욕의 한 변호사가 ChatGPT가 생성한 판례를 법원 제출서류에 인용했다가 제재를 받았다. 판례가 전부 지어낸 것이었던 거다. 법률 도메인에서 AI 환각은 그냥 버그가 아니라 법률 책임 문제로 번진다. 이번 작업에서 내가 만든 서비스도 같은 위험에 노출돼 있었다. 사용자가 법령 개정 diff를 보면서 “이 개정이 우리 회사에 어떤 영향?” 같은 후속 질문을 하면, LLM이 답변을 돌려주면서 근거 조문을 인용한다. 그 인용이 진짜인지 아닌지를 사용자에게 떠넘길 수는 없었다. ...
Rails 8 + Hotwire 프로젝트에 iOS 26 Liquid Glass 디자인 시스템을 전면 도입했다. 7주간 39개 페이지를 마이그레이션했고, 디자인 가이드 위반 417건이 0건이 됐다. 18개 파일로 구성된 마이그레이션 설계서도 있었고, 단계별(Phase 1-7) 체크리스트도 있었다. 스스로 만족했다. 그 다음에 사용자가 한마디 했다. “토큰/컴포넌트/템플릿/페이지 형태로 반영이되어야하는데 디자인시스템부터 점검해” 그 문장 하나로 전부 무너졌다. 점검해보니 내가 한 건 디자인 시스템이 아니라 CSS 리스킨이었다. 이 글은 그 깨달음과 재설계 과정의 기록이다. AI 코딩의 함정 — 페이지마다 “시스템처럼” 보이게 만들기 처음에는 단계별로 잘 진행했다고 생각했다. 토큰 파일을 만들었고(iOS 26 79개 컬러 × 4모드), 6개 공통 ERB 파셜을 만들었고(toolbar, list_row, button 등), 각 페이지마다 전용 CSS 파일을 분리했다. 39개 페이지가 모두 새 디자인으로 바뀌었고, grep으로 위반을 측정했더니 0건이었다. ...
공개 프로필 페이지를 만들고 있었다. link-in-bio 스타일로, /@username 경로에서 사용자의 소개, 링크, 발표자료를 보여주는 페이지다. 발표자료가 9개 올라가 있었는데, 전부 나열하니까 프로필이 포트폴리오 사이트처럼 변해버렸다. 스크롤이 길어지고, 정작 중요한 링크들이 묻혔다. 사용자가 원하는 3개만 “핀"해서 보여주고, 나머지는 별도 페이지로 유도하는 게 맞았다. 그런데 “더보기"를 어떻게 보여줄지가 문제였다. 별도 버튼? 빈 카드? 결국 Instagram 앨범처럼 마지막 썸네일 위에 반투명 오버레이를 올리는 방식으로 갔다. 이 글은 그 과정의 기록이다. 기존 구조: 전부 보여주기 처음 구현은 단순했다. 컨트롤러에서 published.on_profile 스코프로 가져온 발표자료를 전부 넘기고, 프론트에서 2열 그리드로 렌더링했다. ...
Rails 8 + Svelte 5 프로젝트에서 @inertiajs/svelte를 v2에서 v3로 올렸다. “패키지 버전만 올리면 되겠지"라는 안일한 생각으로 시작했다가 반나절을 날렸다. 이 글은 그 삽질의 기록이다. 왜 업그레이드해야 했나 프로젝트에서 Svelte 5를 쓰고 있었는데, @inertiajs/svelte v2는 Svelte 5를 “대충” 지원했다. 문제는 persistent layout이었다. Svelte 5는 컴포넌트를 함수로 컴파일하는데, Inertia v2는 page.default.layout = AppLayout 처럼 클래스 기반 컴포넌트에 속성을 추가하는 방식을 썼다. Svelte 5에서는 이게 작동하지 않았다. 결과적으로 40개 넘는 페이지에 <AppLayout>을 수동으로 감싸야 했다. 유지보수 악몽이었다. ...
Render에서 배포가 터졌다. 에러 메시지는 짧고 명확했지만 원인은 생각보다 다양했다. ERR_PNPM_OUTDATED_LOCKFILE Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with <ROOT>/apps/legal_audit_web/package.json Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile" Failure reason: specifiers in the lockfile don't match specifiers in package.json: * 1 dependencies were added: @ios26_design_system/svelte-inertia@^1.0.0 로컬에서는 잘 됐는데 CI에서만 죽는 전형적인 패턴이다. 원인과 해결법을 기록해둔다. ...
Flutter로 앱을 만들고 TestFlight에 올리려는데, 아카이브 빌드는 성공하고 IPA export에서 멈췄다. exportArchive Failed to Use Accounts라는 에러가 뜨는데, 구글링해도 명쾌한 답이 없었다. 결국 3가지 다른 에러를 연달아 만나면서 해결했고, 그 과정을 정리한다. 에러 1: exportArchive Failed to Use Accounts flutter build ipa --release --dart-define=ENV=prod --export-options-plist=ios/ExportOptions.plist 아카이브 빌드는 잘 된다: ✓ Built build/ios/archive/Runner.xcarchive (197.5MB) [✓] App Settings Validation • Version Number: 1.0.1 • Build Number: 9 • Display Name: My App • Bundle Identifier: com.example.app 근데 바로 다음 줄에서: ...
프로젝트를 어느 정도 운영하다 보면 브랜치가 쌓인다. feature 브랜치, claude가 만든 자동 브랜치, dependabot 브랜치까지 정리 안 하면 git branch -a 결과가 화면 가득 찬다. 오늘 작업하다 머지된 브랜치들 싹 정리했는데, feature/link-in-bio 삭제하려고 하니까 이런 에러가 났다. error: cannot delete branch 'feature/link-in-bio' used by worktree at '/path/to/.worktrees/link-in-bio' 처음엔 그냥 -D 옵션으로 강제 삭제하면 되지 않나 싶었는데, 그게 올바른 방법이 아니다. worktree를 먼저 제거하는 게 맞다. PR이 이미 머지됐는지 확인하는 법 브랜치 정리 전에 먼저 해야 할 게 있다. 각 브랜치가 main에 이미 포함됐는지 확인하는 것. ...