Tailwind v4로 올렸는데 tailwind.config.js가 무시되고 있었다 — @theme 함정과 Chrome DevTools MCP로 검증한 과정

디자인 시스템 문서에는 Primary #3182F6 Toss Blue라고 분명히 적혀 있었다. 그런데 Submit 버튼은 어쩐지 다른 파란색이었다. 기분 탓이 아니라 조금 더 보라끼가 돌고 푸르스름했다. 처음엔 모니터 캘리브레이션 문제인가 싶었는데, DevTools로 computed style을 찍어보니 background-color: oklch(0.546 0.245 262.881). 예상한 rgb(49, 130, 246) 하고는 확실히 다른 값이었다. 원인을 찾아보니 Tailwind CSS v4에서 tailwind.config.js를 완전히 무시하고 있었다. 프로젝트는 tailwindcss@4.1.18 로 올라가 있는데 설정은 v3 문법(JavaScript object) 그대로였고, @theme 블록에는 주요 컬러 스케일이 일부만 포팅돼 있었다. 결과적으로 커스텀 토큰은 전혀 적용이 안 되고 v4 기본 OKLCH 팔레트로 렌더되던 상태. ...

2026-04-21 09:00 · 8분 소요 · Seunghan

Tauri 데스크톱 앱에 LaTeX 수식 달기 — KaTeX 버리고 pulldown-latex + MathML 선택한 이유

오픈소스로 공개해둔 HWP/HWPX → Markdown 변환기 MDM(seunghan91/markdown-media)에 Tauri 데스크톱 뷰어를 붙여서 쓰고 있다. 0.3.0에서 HWPX 파서가 수식을 $...$ / $$...$$ LaTeX로 뽑도록 바꿨는데, 정작 뷰어에서는 달러 기호가 그대로 문자로 보였다. 수식 렌더링이 빠진 거다. 고치는 건 단순해 보였다. 마크다운 뷰어에 KaTeX 붙이면 끝. Obsidian, Typora, Zettlr 전부 이렇게 한다. 그런데 막상 조사해보니 2026년 기준 Tauri 같은 데스크톱 앱에서는 더 좋은 경로가 있었다. Rust 한 곳만 만지고 JS/CSS/폰트 번들은 0으로 유지하는 방법. 이 글은 그 선택 과정과, 덤으로 rhwp 프로젝트에 테스트 하네스를 기여하게 된 이야기다. ...

2026-04-17 09:00 · 8분 소요 · Seunghan

Inertia.js v2→v3 마이그레이션 — Svelte 5 + Rails 8 실전 삽질 기록

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>을 수동으로 감싸야 했다. 유지보수 악몽이었다. ...

2026-04-04 00:00 · 7분 소요 · Seunghan

iOS 26 Liquid Glass 디자인 시스템을 웹에 적용하기 — Svelte 5 + CSS Custom Properties

WWDC 2025에서 Apple이 발표한 Liquid Glass는 iOS 7 이후 가장 큰 UI 변화였다. 반투명 유리 재질에 빛이 굴절되는 듯한 효과가 핵심인데, 이걸 실제 웹 프로젝트에 적용해봤다. Figma Community Kit에서 디자인 토큰을 추출하고, CSS Custom Properties로 변환한 뒤, Svelte 5 컴포넌트로 만들어서 Rails + Inertia.js 프로젝트의 실제 페이지에 붙이는 전 과정을 정리한다. 결론부터 말하면, iOS 26 디자인 시스템은 웹에서도 충분히 구현 가능하다. 다만 다크모드 셀렉터 불일치 같은 함정이 있어서, 기존 프로젝트에 얹을 때는 CSS 변수 네이밍 컨벤션을 꼼꼼히 맞춰야 한다. ...

2026-03-31 00:00 · 8분 소요 · Seunghan

Rails 8에서 Devise 걷어내기 — has_secure_password로 마이그레이션한 실전 기록

Devise가 Inertia.js와 싸우기 시작했다 Rails 8 + Inertia.js + Svelte 5 스택으로 운영하던 프로젝트에서 로그인이 안 되는 버그가 터졌다. 에러 메시지도 없고, 401만 돌아왔다. 로그를 보니 Warden의 database_authenticatable strategy가 valid_for_params_auth? = false를 찍고 있었다. 쿼리가 0개 — DB에 접근조차 안 한 것이다. 원인은 Devise의 Warden 미들웨어가 request.params[:user]를 읽는데, Inertia.js는 {email, password}를 flat하게 보내서 Rails의 ParamsWrapper가 session 키로 감싸버리는 구조적 충돌이었다. # Inertia.js가 보내는 것 { email: "user@example.com", password: "secret" } # Rails ParamsWrapper가 변환한 것 { email: "...", password: "...", session: { email: "...", password: "..." } } # Devise/Warden이 찾는 것 { user: { email: "...", password: "..." } } # ← 이게 없다 normalize_sign_in_params라는 핵으로 params[:user]를 세팅했지만, Warden은 ActionController의 params가 아니라 **Rack 레벨의 request.params**를 따로 읽었다. 두 객체는 완전히 별개다. ...

2026-03-31 00:00 · 7분 소요 · Seunghan

한국 주식 수급분석 대시보드 만들기 — pykrx 사망부터 네이버 크롤링까지

주식 투자에서 “외국인이 샀다”, “기관이 팔았다"는 뉴스를 자주 접하지만, 정작 이 데이터를 프로그래밍으로 가져와서 분석하려면 막막한 경우가 많다. 특히 한국 시장은 KRX가 투자자별 매매동향을 공개하고 있어 데이터 접근성이 좋은 편인데, 문제는 이걸 자동화하는 도구들이 생각보다 불안정하다는 거다. 이번에 수급분석 웹 대시보드를 만들면서 겪은 삽질을 기록한다. pykrx가 완전히 깨져서 네이버 증권 크롤링으로 우회한 과정, lightweight-charts로 누적 순매수 차트를 구현하면서 Y축 포맷팅에서 막힌 부분, 그리고 실제 데이터를 수집해서 대시보드에 연결하기까지의 전체 과정이다. ...

2026-03-24 00:00 · 8분 소요 · Seunghan

Tailwind v4 테마 적용 삽질기 — CSS 변수가 안 먹는 진짜 이유

Rails + Tailwind CSS v4 프로젝트에서 그랜드슬램 테마 시스템을 만들었다. 설정에서 Wimbledon(보라), Roland Garros(오렌지), US Open(네이비), Australian Open(스카이블루)을 고르면 앱 전체 색상이 바뀌는 기능이다. 이틀을 날렸다. 결론부터 말하면 CSS 파일 로드 순서 한 줄이 문제였다. 배경: Tailwind v4의 CSS 변수 컴파일 방식 Tailwind v4는 v3과 완전히 다른 방식으로 색상을 처리한다. 가장 중요한 변화는 모든 유틸리티 클래스가 CSS 변수를 통해 동작한다는 것이다. /* Tailwind v4가 bg-emerald-600을 컴파일한 결과 */ .bg-emerald-600 { background-color: var(--color-emerald-600); } v3에서는 bg-emerald-600이 background-color: #059669 같은 하드코딩 hex로 컴파일됐다. v4에서는 CSS 변수 참조로 바뀌었다. 이 차이가 테마 시스템의 핵심이다. ...

2026-03-19 00:00 · 7분 소요 · Seunghan
Tailwind v4 CSS Variable Theme System

Tailwind v4 CSS 변수 오버라이드로 앱 전체 테마 교체하기

테마 시스템을 구현할 때 흔히 생각하는 방법은 컴포넌트마다 조건부 클래스를 추가하는 것이다. 하지만 기존 코드를 건드리지 않고 CSS 변수 한 블록만으로 앱 전체 색상을 바꿀 수 있다면? Tailwind v4에서는 그게 가능하다. Tailwind v4의 CSS 변수 컴파일 방식 이 패턴 전체를 가능하게 만드는 아키텍처 변화는 미묘하지만 근본적이다. Tailwind v4는 유틸리티 클래스를 하드코딩된 값이 아니라 CSS 변수 참조로 컴파일한다. /* Tailwind v4가 생성하는 CSS */ .bg-emerald-700 { background-color: var(--color-emerald-700); } .text-emerald-600 { color: var(--color-emerald-600); } .border-emerald-500 { border-color: var(--color-emerald-500); } Tailwind v3에서는 bg-emerald-700이 background-color: #047857로 컴파일됐다. 스타일시트에 hex 값이 직접 박혔다. v4에서는 background-color: var(--color-emerald-700)으로 컴파일된다. 실제 색상 값은 :root에 선언된 CSS 커스텀 프로퍼티에 저장된다. ...

2026-03-17 00:00 · 7분 소요 · Seunghan
ViewComponent Design System Lookbook Migration

ViewComponent 디자인 시스템을 Lookbook으로 이관하면서 만난 삽질들 — Rails 8 + Tailwind CSS 4

Rails 8에서 47개 ViewComponent 기반 디자인 시스템을 warm orange 테마로 전환하고, Lookbook 프리뷰를 전면 구축하면서 만난 삽질들을 정리했다. 배경 기존 프로젝트에는 다음이 갖춰져 있었다: 47개 ViewComponent (input, layout, navigation, card, typography 등 15개 카테고리) CSS Custom Properties 기반 디자인 토큰 (tokens.css) Tailwind CSS 4 + Propshaft 에셋 파이프라인 목표는 BMC(Buy Me a Coffee) 디자인을 레퍼런스로, warm orange 테마 + dark sidebar + stone palette로 전환하고, Lookbook으로 전체 프리뷰를 구축하는 것이었다. ...

2026-03-10 00:00 · 4분 소요 · Seunghan

Chrome 확장 개발 삽질 모음 — 도메인 화이트리스트, 이벤트 리스너 중복, 클로저 함정

Chrome 확장을 유지보수하다 보면 “분명히 동작해야 하는데 왜 안 되지?“라는 상황을 꽤 자주 만난다. 이번에 짧은 시간 안에 4가지 실수를 연달아 저질렀고, 각각 원인이 달랐다. 기록해둔다. 1. 디스패치 블록의 return이 범용 감지를 막는다 Content script 끝부분에는 보통 이런 패턴이 있다. if (isSomeSpecificPage()) { doSomethingSpecific(); return; // ← 여기서 끝 } // 범용 DOM 감지 (MutationObserver 등) const observer = new MutationObserver(() => { ... }); observer.observe(document.body, { childList: true, subtree: true }); 특정 도메인에서만 동작하는 기능을 추가하면서 return으로 빠져나왔더니, 그 도메인의 팝업 창에서 범용 DOM 감지가 아예 실행되지 않았다. ...

2026-03-09 00:00 · 4분 소요 · Seunghan

Flutter + Web 디자인 토큰 동기화 — Storybook 기반 디자인 시스템 구축기

Flutter 앱을 개발하다 보면 항상 부딪히는 문제가 있다. 디자이너는 Figma나 웹 기반 도구로 작업하는데, 개발자는 Dart 코드에 색상을 하드코딩한다. Color(0xFF10B981) 같은 값이 app_colors.dart에만 있고, 웹 쪽 CSS에는 #10B981로 따로 있다. 두 곳을 따로 관리하다 보면 어느 순간 서로 달라져 있다. 이번에 Svelte+Storybook 기반 웹 디자인 키트와 Flutter 앱의 토큰을 하나의 기준으로 맞추는 작업을 했다. 삽질한 내용 위주로 정리한다. 문제: 두 곳에 사는 디자인 토큰 기존 상태는 이랬다. 웹 (CSS) :root { --color-primary: #0000FF; /* 기본값 그대로 */ --radius: 0px; } Flutter (Dart) ...

2026-03-08 00:00 · 4분 소요 · Seunghan
Uxui Review And Fix Svelte Rails

Rails + Svelte 앱 UX/UI 전수 점검 및 개선 기록

Rails 8 + Inertia.js + Svelte 5 조합으로 만든 웹앱을 운영하다가, 기능은 돌아가는데 세부 UX가 들쭉날쭉하다는 걸 느꼈다. 이번 글은 전수 점검 후 우선순위 높은 4가지를 직접 고친 기록이다. 기능을 빠르게 만들다 보면 각 화면이 독립적으로 개발되고, 결과적으로 “같은 기능인데 화면마다 동작이 다른” 상태가 된다. 사용자 입장에서 이런 불일치는 앱이 정돈되지 않은 느낌을 준다. 코드의 버그는 아니지만, 분명한 UX 버그다. 문제 발견: 같은 기능인데 UI가 다르다 가장 먼저 눈에 띈 건 시작일 입력 UI가 화면마다 다르게 동작하는 문제였다. ...

2026-03-06 00:00 · 8분 소요 · Seunghan
Svelte Inertia Design Tokens System

Svelte 5 + Inertia.js 프로젝트 8개에 디자인 토큰 체계 잡기

Rails + Inertia.js + Svelte 5 기반으로 여러 프로젝트를 운영하다 보면 하나의 고질적 문제가 생긴다. 각 프로젝트마다 색상, 타이포그래피, 간격 등의 디자인 기준이 제각각이라는 점이다. 어떤 프로젝트는 tailwind.config.js에 체계적으로 정리되어 있고, 어떤 프로젝트는 bg-[#3182F6] 같은 하드코딩이 넘쳐난다. 이 문제는 프로젝트 수가 늘어날수록 더 심각해진다. 새 프로젝트를 시작할 때마다 색상을 다시 정의하고, 버튼 스타일을 다시 만들고, 폰트 크기 기준을 다시 잡아야 한다. 어느 프로젝트에서 잘 만든 컴포넌트를 다른 프로젝트로 복사하려 해도 디자인 기준이 달라서 그대로 쓸 수가 없다. ...

2026-03-03 00:00 · 11분 소요 · Seunghan
Storybook Reference Design Component System

레퍼런스 디자인을 분석해서 컴포넌트 시스템 확장하기 — Svelte 5 + Storybook 10

디자인 시스템이 어느 정도 잡힌 프로젝트에서 레퍼런스 앱을 받았을 때, “완전히 똑같이"가 아니라 “구성(composition)만 동일하게” 적용하고 싶었다. 색상·폰트·모드를 통째로 바꾸는 건 기존 작업을 날리는 일이고, 그렇다고 레퍼런스를 무시하면 디자이너와의 싱크가 깨진다. 이 글은 그 균형점을 찾는 과정을 정리한 기록이다. 배경 기존 프로젝트에는 이미 다음이 갖춰져 있었다: 23개 공유 컴포넌트 (8개 카테고리: layout, navigation, input, overlay, card, data-display, social, feedback) CSS Custom Properties 기반 디자인 토큰 (colors, typography, spacing, radius, shadows, glassmorphism) Storybook 10 + Svelte 5 환경 (51개 story variants) 다크 테마 글래스모피즘 디자인 여기에 디자이너가 참조용으로 보내준 레퍼런스 앱 이미지를 분석해서, 기존 디자인을 깨지 않으면서 구조적 패턴만 흡수하는 작업을 진행했다. ...

2026-02-27 00:00 · 9분 소요 · Seunghan
Rails Sso One Time Token Between Services

Rails 서비스 간 SSO 직접 구현하기: One-Time Token + HMAC 방식

두 개의 Rails 앱이 있다. 하나는 내부 직원용 앱(OTP 로그인, 특정 도메인 전용), 다른 하나는 심사/관리 시스템으로 Devise + JWT 기반이다. 내부 직원이 심사 시스템에도 접근해야 하는데, 계정을 따로 만들어 관리하기 싫었다. “이미 내부 앱에 로그인돼 있으면, 심사 시스템에서 버튼 하나로 자동 로그인되면 안 되나?” OAuth2를 붙이면 정석이지만, Doorkeeper 설정하고 scope 관리하고… 내부 서비스 두 개 사이에 그게 과할 수 있다. 더 단순한 방법을 택했다. OAuth2를 안 쓴 이유 OAuth2는 서드파티 클라이언트 지원, 세밀한 권한 범위(scope) 관리, 토큰 갱신 흐름, 공개 API 연동이 필요할 때 올바른 선택이다. 하지만 같은 팀이 운영하는 두 내부 서비스 사이에서 OAuth2를 도입하면 다음을 감수해야 한다. ...

2026-02-10 00:00 · 6분 소요 · Seunghan
Calendar Print Browser Print Bug Paper Sizes

웹 캘린더 인쇄 기능의 함정: window.print()는 off-screen 엘리먼트를 무시한다

웹에서 캘린더를 출력하는 기능을 만들었다. PDF와 PNG 다운로드는 완벽한데, 브라우저 인쇄 버튼만 누르면 이미지 위치가 전혀 반영되지 않았다. 같은 데이터를 쓰는데 왜 결과가 다를까? 구조: 프리뷰와 숨겨진 내보내기 타겟 캘린더 출력 페이지의 구조는 이렇다: ┌─ 화면에 보이는 영역 ─────────────────┐ │ [설정 패널] [프리뷰 영역] │ │ - 기간 선택 캘린더 미리보기 │ │ - 테마/색상 │ │ - 이미지 위치 슬라이더 │ └──────────────────────────────────────┘ ┌─ 숨겨진 내보내기 타겟 ───────────────┐ │ <div class="fixed -left-[9999px]"> │ ← 화면 밖 │ <PrintableCalendar ... /> │ │ </div> │ └──────────────────────────────────────┘ 프리뷰는 축소된 미리보기고, 실제 내보내기용 캘린더는 원본 크기로 화면 밖(-left-[9999px])에 렌더링된다. PDF/PNG는 이 숨겨진 엘리먼트를 캡처한다. ...

2026-01-20 00:00 · 4분 소요 · Seunghan
Rails Devise Multistep Signup Resend Email

Rails 8 + Devise 다단계 회원가입 & Resend 이메일 삽질 기록

Rails 8 + Inertia.js + Svelte 5 스택에서 역할별 다단계 회원가입과 Resend 이메일 서비스를 연동하면서 겪은 문제들을 정리한다. 1. 역할별 조건부 다단계 회원가입 폼 요구사항 사용자 역할이 두 종류인 서비스에서 회원가입 플로우를 다르게 가져가야 했다. 역할 A: 기본 정보 → 업무 선택 → 소속 정보 (3단계) 역할 B: 기본 정보 → 업무 선택 (2단계, 소속 정보 불필요) Svelte 5 Runes로 조건부 스텝 구현 $derived로 역할에 따라 전체 스텝 수와 버튼 동작을 동적으로 처리했다. ...

2025-12-20 00:00 · 4분 소요 · Seunghan
Spa Blank Screen Inertia Usepage Url Debugging

SPA 배포 후 빈 화면: Inertia.js usePage().url은 string이다

Rails + Inertia.js + Svelte 앱을 배포한 뒤 접속하면 완전히 빈 화면만 보였다. 서버는 정상이고 에셋도 다 로드되는데 화면이 안 그려지는 상황. 원인 추적부터 해결까지, 그리고 재발 방지를 위한 패턴까지 정리한다. 증상 배포된 URL 접속 시 빈 화면 (흰색 배경만 표시) 로컬 개발 서버에서는 정상 동작 아무런 에러 페이지 없이 그냥 빈 화면 서버 로그에도 이상한 점 없음 (200 응답, 정상적인 요청 처리) 이 상황이 특히 짜증스러운 이유는, 서버 입장에서는 완전히 정상 동작하고 있기 때문이다. HTTP 상태 코드도 200, 에러 로그도 없다. 문제는 브라우저 안에서만 발생한다. ...

2025-11-22 00:00 · 6분 소요 · Seunghan
Rails Inertia Svelte Pet Avatar Image Color

Rails + Inertia + Svelte 5: 아바타 이미지/색상 선택 기능 구현에서 삽질한 것들

Rails 8 + Inertia.js + Svelte 5 스택으로 펫(반려동물) 프로필 아바타를 이미지 또는 색상으로 선택하는 기능을 구현하면서 겪은 문제들을 정리한다. 문제 1: 색상이 DB에 저장되지 않았다 증상 처음 코드를 보니 펫 카드에 색상을 표시할 때 이런 식으로 되어 있었다. const PET_COLORS = ['#f3caa1', '#b7ddf9', '#d3c8ff', '#c5d5f4', '#ffd9aa'] function petColor(index: number): string { return PET_COLORS[index % PET_COLORS.length] } 펫을 생성한 순서(인덱스) 로 색상을 결정하는 구조였다. 색상을 DB에 아예 저장하지 않았으니, 사용자가 색상을 바꿔도 새로고침하면 원래 색상으로 돌아왔다. 해결 마이그레이션으로 avatar_color 컬럼을 추가하고 기본값을 지정했다. ...

2025-11-15 00:00 · 4분 소요 · Seunghan
Chrome Extension Insurance Autofill Playwright Gif

크롬 확장 content script — 한국 보험사 자동입력, HTML 목업 스크린샷, MOV→GIF

브라우저 확장 프로그램에서 form 자동입력 기능을 확장하면서 삽질한 내용들을 정리한다. 1. 다이렉트 자동차보험 사이트 content script 자동입력 문제: JS 렌더링 사이트는 WebFetch로 form 구조를 못 읽는다 한국 보험사 다이렉트 사이트들은 대부분 SPA/RIA 구조다. 삼성화재: SFMI 자체 RIA 프레임워크 현대해상, DB손보: Spring MVC .do URL 패턴 KB손보, 메리츠: 모바일/PC 별도 도메인 WebFetch로 URL을 긁어봤자 form 필드 구조가 나오지 않는다. 직접 접속해서 DevTools로 확인하거나, 업계 공통 패턴으로 커버하는 방법 중 후자를 선택했다. ...

2025-10-28 00:00 · 5분 소요 · Seunghan
Multi Landing Page Netlify Workflow

앱 랜딩 페이지 8개를 하나의 저장소로 관리하는 법

앱을 여러 개 만들다 보면 각각 랜딩 페이지가 필요해진다. 저장소를 8개 따로 만들면 관리 비용이 8배가 된다. 반대로 하나로 완전히 묶으면 배포가 복잡해진다 — 어느 페이지 하나를 수정해도 전체가 재배포되고, 실수 하나가 전체를 망가뜨릴 수 있다. 두 극단 사이에서 찾은 구조가 저장소 1개 + Netlify 사이트 N개다. 코드 관리는 한 곳에서, 배포는 서비스별로 완전히 독립적으로. 이 글은 그 구조의 세부 사항, 각 결정의 이유, 그리고 규모가 커져도 유지보수할 수 있게 만드는 패턴들을 정리한 것이다. ...

2025-10-11 00:00 · 7분 소요 · Seunghan

Rails Hotwire 채팅 리디자인 — 버블 UI, 사이드바 채널 분리, content_for 레이아웃 트릭

Rails로 채팅 기능을 만들다 보면 어느 순간 한계에 부딪힌다. 초기엔 textarea에 리스트로 메시지를 쌓는 구조로 충분하지만, 실제 사용자 앞에 놓으면 카카오톡이나 Slack을 쓰던 사람들 눈엔 영 어색해 보인다. 내 메시지는 오른쪽, 상대 메시지는 왼쪽 — 이 직관적인 패턴을 Rails + Turbo Streams 환경에서 어떻게 구현했는지 기록해둔다. 거기에 채팅방 진입점(DM) vs. 그룹 채널을 사이드바에서 구조적으로 분리한 방법, 그리고 특정 페이지에서만 공통 레이아웃 섹션을 숨기는 content_for 트릭까지 담았다. 채팅 버블 UI: 내 메시지 오른쪽, 상대 메시지 왼쪽 채팅 UI의 핵심은 단순하다. 누구 메시지냐에 따라 정렬 방향과 색상만 바꾸면 된다. Tailwind CSS의 flex-row-reverse와 rounded-br-sm 조합이 핵심이다. ...

2025-03-24 00:00 · 6분 소요 · Seunghan
개인정보처리방침 이용약관 면책조항 문의