👋 Hi, I’m Seunghan, an AI-Native Engineer.
I build mobile apps and web services powered by AI. Passionate about improving user experiences and solving real-world problems.
👋 Hi, I’m Seunghan, an AI-Native Engineer.
I build mobile apps and web services powered by AI. Passionate about improving user experiences and solving real-world problems.
When Mitchell Hashimoto gave the practice of building reliable agents a name — “harness engineering” — in February 2026, the industry adopted it within days. OpenAI, Anthropic, LangChain all picked it up. Every team running agents now does the same thing: each time an agent makes a mistake, add a rule, ship a tool, prevent the recurrence. From an engineering standpoint, this is the right direction. But once you stay with this arc for a while, something keeps surfacing: there’s a layer the harness doesn’t cover. ...
If you’re building a mobile app with Hotwire Native and Rails 8, you’ll eventually hit issues that don’t show up in development but break things in production — especially on real iOS devices running inside WKWebView. This post documents 7 real-world pitfalls I encountered while shipping a tournament management app (Turbo + Stimulus + ERB + Tailwind CSS 4) as a Hotwire Native iOS app. Each issue includes the symptom, root cause analysis, fix, and lessons learned. ...
Three real-world problems encountered while building a real-time tournament dashboard with Rails 8 + Hotwire, and the exact steps taken to resolve each one. These issues are not edge cases — they reflect the natural friction points of the Hotwire programming model that become apparent only at runtime. 1. Turbo Stream + Stimulus DnD: Events Vanish After DOM Replacement The Problem The feature: drag a player chip onto a court card, send a POST to the server, and get back a Turbo Stream response that replaces both the court card and the player list. ...
The bigger your codebase gets, the harder it is to answer “why does this feature work like this?” You end up opening 5 files. The design doc was written 3 months ago and nobody knows if it still matches the code. Comments are stale, Slack threads are gone, and the original designer left the company. What if the design doc itself was executable, and you maintained that instead of the code? ...

When you’re designing three features simultaneously, they start bleeding into each other. This time I added the following to a tournament bracket management app: FAB Feedback Button — floating button bottom-right → Telegram notification Role-Based Bracket Edit Permissions — tournament vs. friendly mode determines whether regular participants can edit the bracket Audit Log — records who changed what and when, with before/after data Each is simple on its own, but doing them together forced a lot of decisions: where to check permissions, where to write logs, and how much to expose in the UI. ...

When you think about implementing a theme system, the first instinct is to add conditional classes to every component. But what if you could swap the entire app’s color palette by changing a single CSS block — without touching any HTML? In Tailwind v4, that’s exactly what you can do. How Tailwind v4 Compiles Utility Classes The architectural shift that makes this entire pattern possible is subtle but profound. Tailwind v4 compiles utility classes as CSS variable references rather than hard-coded values: ...

I was adding two features to the dashboard of a sports tournament management app: DnD card reordering — drag cards to rearrange sections (My Matches / Bracket / Match List) Card collapse/expand — fold away sections you don’t need Each sounds simple, but add Turbo Frame lazy loading and a requirement that layout state survives page reloads, and there’s more to think about than it first appears. 1. Choosing a DnD Library My first attempt used the native HTML5 Drag & Drop API directly — dragstart, dragover, drop. It works fine on desktop, but the problem is touch devices. The HTML5 drag API has incomplete support on iOS Safari; touch-dragging simply doesn’t work there. ...
I added click-to-highlight interactivity to an SVG-based tournament bracket built with Rails 8 and ViewComponent. Here’s what I ran into. The goal: click a player’s row in the bracket → all matches featuring that player get a subtle color highlight. Background: SVG-rendered bracket The bracket isn’t HTML divs — it’s a pure SVG rendered by a BracketTreeComponent (ViewComponent). The component calculates coordinates for each match slot and emits <rect>, <text>, <circle>, and connector <path> elements. ...
Two problems solved while building a Rails 8 + Hotwire Native iOS app. Real-time notification badge — instantly update the app icon badge and bell button the moment a notification is created on the server Side menu navigation failure — correctly navigate to URLs that require dynamic parameters like a resource ID 1. Background Notification Badge Without setting the badge field in APNs push notifications, no number appears on the iOS app icon. And even when notifications are read, the badge doesn’t clear. ...
Running an iOS app built with Rails 8 + Hotwire Native, I hit a series of issues in a single day. What started as a small UI distortion spiraled into a full permission system redesign. Here is the complete record. The central appeal of Hotwire Native is being able to serve both a web browser and a native iOS or Android shell from a single Rails application. But that architecture quietly encourages a dangerous assumption: if it looks right in the browser, it will look right in the app. In practice, the WKWebView rendering context, the presence of a native navigation bar, and role-based UI branching introduce concerns that have no equivalent in a standard web browser. This post walks through seven of those issues and the patterns that resolved them. ...
While documenting UX flows with Rails + Lookbook, I hit a moment of “something feels off.” Each Step only showed wireframe fragments, so looking at the Lookbook list gave zero sense of the overall flow. I fixed two things: Add a Mermaid flowchart Overview step to each flow Redesign all Step template structures The Problem: Lookbook Step Previews Feel Like “Context-Free Fragments” # @label Admin UX Flow # @logical_path ux_flows class UxFlows::AdminFlowPreview < ViewComponent::Preview # @label 1. Login -> Admin Dashboard def step_1_login_dashboard render_with_template end # ... end Each step_* method renders an ERB template via render_with_template. The ERB contains a wireframe with a simple step navigation bar at the top. ...

I migrated 47 ViewComponents in a Rails 8 project to a warm orange theme and built a full Lookbook preview system. Here are the real issues I ran into. Background The project already had: 47 ViewComponents across 15 categories (input, layout, navigation, card, typography, etc.) CSS Custom Properties based design tokens (tokens.css) Tailwind CSS 4 + Propshaft asset pipeline The goal was to adopt a BMC (Buy Me a Coffee) inspired warm orange theme with a dark sidebar and stone palette, and build comprehensive Lookbook previews. ...
When maintaining a Chrome extension, “this should definitely work, why doesn’t it?” comes up more often than expected. I made four consecutive mistakes in a short period, each with a different root cause. Writing them down. 1. A return in the dispatch block silently kills generic detection Content scripts typically end with a pattern like this: if (isSomeSpecificPage()) { doSomethingSpecific(); return; // ← exits here } // Generic DOM detection (MutationObserver, etc.) const observer = new MutationObserver(() => { ... }); observer.observe(document.body, { childList: true, subtree: true }); After adding a feature for a specific domain with an early return, the generic DOM detection never ran in a popup window on that domain. ...
Using showModalBottomSheet for form input screens feels natural. But when your app has a bottom navigation bar, the sheet slides up and covers the navigation — it works functionally, but looks cluttered. Three issues were fixed in one go: Bottom sheet → centered modal conversion TextButton cancel button rendering in yellow (unreadable) PlatformException when sharing a SQLite file with share_plus Problem 1: BottomSheet Covers the Navigation Bar Symptom An input form built with showModalBottomSheet overlaps the bottom navigation bar when it slides up. Even with isScrollControlled: true, the sheet extends over the navigation area. ...
While building a civic reporting Flutter app, I ran into three UX problems in a row: The photo button only opened the gallery — no camera option Categories kept growing, making the grid scroll-heavy Switching to an emergency report target didn’t change the submit button color Here’s how I fixed each one. Problem 1: image_picker only opens the gallery The issue The photo button called pickImage(source: ImageSource.gallery) directly. Camera permissions were in place, but the UI never offered the option. ...
After building a Flutter app with flutter build ipa --release, TestFlight rejected the upload via altool. Here’s what happened, why it happened, and how I automated the fix with a reusable Makefile target. The Error UPLOAD FAILED with 3 errors Invalid executable. The "Runner.app/Frameworks/objective_c.framework/objective_c" executable references an unsupported platform in the x86_64 slice. Simulator platforms aren't permitted. Invalid executable. The "Runner.app/Frameworks/objective_c.framework/objective_c" executable references an unsupported platform in the arm64 slice. Simulator platforms aren't permitted. Unsupported Architectures. The executable for Runner.app/Frameworks/objective_c.framework contains unsupported architectures '[x86_64]'. The build succeeded and the IPA was generated without any issues — the rejection happened at upload time, not at build time. This distinction matters because it means the problem isn’t in your Xcode settings or your Flutter project configuration per se; it lives inside a third-party framework binary that Flutter embeds silently. ...

While running a web app built with Rails 8 + Inertia.js + Svelte 5, I noticed that even though the features were working correctly, the fine-grained UX felt inconsistent. This post is a record of a full audit and the four highest-priority issues I fixed. When you build features quickly, each screen tends to be developed independently, and you end up with situations where “the same feature works differently depending on where you are.” From the user’s perspective, this inconsistency makes the app feel unpolished. It is not a functional bug in the traditional sense, but it is absolutely a UX bug. ...

When you run multiple projects on a Rails + Inertia.js + Svelte 5 stack, one persistent problem tends to surface: each project ends up with its own inconsistent standards for colors, typography, and spacing. One project has everything neatly organized in tailwind.config.js, while another is littered with hardcoded values like bg-[#3182F6]. The problem compounds as the number of projects grows. Every new project means redefining colors from scratch, rebuilding button styles, and re-establishing font size conventions. Even when a component from one project is worth reusing in another, the differing design foundations make it impossible to drop it in as-is. ...

When you receive a reference app for a project that already has a reasonably mature design system, the temptation is to either copy it pixel-for-pixel or ignore it entirely. Neither option is good. Rewriting everything from scratch discards existing work; ignoring the reference breaks alignment with the designer. This post documents how I found the balance: absorbing the structural patterns from the reference while keeping the existing dark glassmorphism theme intact. ...

Six Rails services deployed on Render were all throwing different errors at the same time. Going through the logs one by one revealed both common patterns and service-specific issues. This post documents how all of them were fixed and deployed within a single session. Overview Instead of SSH-ing into each service individually, I used the Render API to pull logs from all six services at once via a local script. The results: ...

When running two Rails 8 projects in parallel, patterns carefully built in one project often end up missing in the other. When implementing features, you naturally focus on the immediate requirements at hand — and it is easy to overlook a well-crafted solution from a sibling project that would save you significant time and headaches. This time I deliberately placed both projects side by side and did a systematic cross-pollination pass. The focus was on security hardening, progressive web app experience, error tracking, and push notification infrastructure — the kind of foundational plumbing that rarely makes it into sprint planning but matters enormously in production. ...

I have two Rails 8 services. One is the main app acting as the Identity Provider (IdP), and the other is a partner service acting as the Relying Party (RP). I wanted to add a “Sign in with Main App” button to the partner service’s login page, authenticate via SSO, and redirect back. Then I went one step further: if the user has the iOS Hotwire Native app installed, the authentication should open in the native app instead of the browser, via Universal Links. ...

While implementing HMAC-based SSO between two Rails applications, I ran into two unexpected bugs. Both stemmed from how Turbo Drive and ERB handle things under the hood. The error message was identical in both cases — “state mismatch” — but the root causes were completely different. Fixing the first bug left the second one still lurking, which made the debugging process more frustrating than it needed to be. Implementation Overview Structure Two independent Rails apps are connected via SSO: ...

There are two Rails apps. One is an internal staff app — OTP login only, restricted to a specific domain. The other is a review and management system built on Devise + JWT. Internal employees need access to both, but creating and managing separate accounts for each was not a path worth taking. “If a user is already logged into the internal app, can’t they just click a button and get into the review system automatically?” ...

To give an electronic contract storage system legal evidentiary weight, I needed to implement two things simultaneously: Blockchain Merkle Tree anchoring — collect contract hashes, compute a Merkle Root, and record it on an L2 chain RFC 3161 TSA timestamps — cryptographic proof of existence at a specific point in time, certified by a trusted third party It looked straightforward. It was not. Each problem took far longer than expected to resolve, and the issues compounded in unexpected ways — especially where Ruby 4.0 API changes intersected with Rails 8’s multi-database behavior. ...

I decided to do a thorough inspection of a production Rails 8 API server. Most features appeared to be working, but test coverage sat at a mere 3%. This was an exercise in finding out just how dangerous the assumption “it works, so it’s fine” really is. When a project reaches a certain level of maturity, stability becomes more important than shipping new features. In a codebase without tests, every refactor, every dependency upgrade, and every new team member onboarding becomes a gamble. This inspection was not just about raising a coverage number — it was about taking an honest look at the actual state of the codebase. ...

While preparing to release a voice message-based social app, I performed a full audit of unimplemented features. There were quite a few cases where routes existed but controller actions did not, or the Flutter UI was complete but payment logic was blocked with // TODO. Here is the record of cataloging and implementing them one by one. How to Audit Unimplemented Features Backend Audit The fastest method is comparing routes.rb against the actual controllers. ...

After running a project for nearly a year, documents accumulate. Feature design specs, TODOs, debugging records, migration plans, test scenarios… Each was needed at the time, but over time they become noise. One day I ran find docs -name "*.md" | wc -l and got 2,352. Status Check: How Did It Get This Bad find docs -name "*.md" | wc -l # 2352 # File count by directory find docs -maxdepth 1 -type d | while read d; do count=$(find "$d" -name "*.md" | wc -l) echo "$count $(basename $d)" done | sort -rn | head -15 Results: ...

I built a Chrome extension that auto-fills the OACX (simplified authentication) form on Korean government sites. It worked on most sites, but got feedback saying “the name field isn’t being filled” on a specific major site. Symptoms Extension auto-fills name, birthdate, and phone number when the simplified auth popup opens Works correctly on most government sites (Gov24, National Health Insurance, etc.) Only on a specific site, the name field was empty – birthdate and phone number also weren’t filled Investigation: Checking Actual DOM Structure with Playwright Opened the page reported by the user directly using Playwright MCP. ...

I built a calendar printing feature for the web. PDF and PNG downloads worked perfectly, but when hitting the browser print button, image positions weren’t reflected at all. Same data, so why different results? Structure: Preview and Hidden Export Target The calendar print page had this structure: +-- Visible Area ----------------------------+ | [Settings Panel] [Preview Area] | | - Date range Calendar preview | | - Theme/Color | | - Image position slider | +--------------------------------------------+ +-- Hidden Export Target --------------------+ | <div class="fixed -left-[9999px]"> | <- off-screen | <PrintableCalendar ... /> | | </div> | +--------------------------------------------+ The preview is a scaled-down thumbnail, while the actual export calendar is rendered at full size off-screen (-left-[9999px]). PDF/PNG capture this hidden element. ...

After running into a problem where an AI agent grabbed a ticket and abandoned it, I dug into OpenAI’s Symphony project. Symphony is an orchestrator that polls a GitHub issue tracker and automatically runs coding agents (Codex, Claude, etc.). Its core philosophy stuck with me: “Don’t manage agents — manage the Work.” I extracted 7 patterns from that philosophy and applied all of them to a Rails 8 + SolidQueue based ITSM system. Here’s what each pattern solves, how it’s implemented, and why it matters. ...

When running a Rails 8 + Hotwire (Turbo) application in production, broadcast_append_to callbacks can silently throw 500 errors. When that’s compounded by a SolidCable setup issue and Telegram Bot message parsing errors, interpreting the logs becomes genuinely confusing. All three hit at the same time in a recent project — here’s how each one was diagnosed and resolved. These three problems are independent of each other, but in practice they tend to surface together in a freshly deployed Rails 8 app. The key is to isolate each problem and fix them one at a time. ...

While deploying a Rails 8-based ITSM system to Render today, I ran into three consecutive issues that each had different root causes but were connected like links in a chain. I’m documenting the process of reading deployment logs, debugging, patching code, and discovering the next problem. The stack covered in this post: Rails 8.1, SolidQueue 1.3.1, Puma, PostgreSQL, deployed on Render.com. Issue 1 — Application exited early with SolidQueue Symptoms The build succeeds in the Render deployment log, but the application dies immediately on startup. ...

I was building a dispatcher that lets an AI agent call a Rails API server to automatically assign tickets. The logic itself was straightforward, but the integration kept hitting unexpected walls. Over the course of one day, I ran into seven distinct bugs — each small on its own, but exhausting in rapid succession. I’m writing them down in hopes they save someone else the same frustration. 1. Ruby 3.0 kwargs Separation — Why Does render_success(key: val) Blow Up? This one cost the most time. In a Rails controller, I was calling a response helper like this: ...

Here are the problems encountered when switching from a single Navigator to the HotwireTabBarController pattern while wrapping a Rails app with Hotwire Native. Bugs that were invisible in the simulator surfaced on TestFlight, and local dev environment settings got tangled – multiple points where time was wasted. 1. HotwireTabBarController Basic Structure Instead of a single Navigator, each tab has its own independent Navigator and WKWebView. // AppTab.swift enum AppTab: String, CaseIterable { case home, ai, request var systemImage: String { switch self { case .home: return "house" case .ai: return "message" case .request: return "checkmark.circle" } } var selectedSystemImage: String { switch self { case .home: return "house.fill" case .ai: return "message.fill" case .request: return "checkmark.circle.fill" } } var url: URL { let base = AppDelegate.baseURL switch self { case .home: return base.appendingPathComponent("dashboard") case .ai: return base.appendingPathComponent("conversations") case .request: return base.appendingPathComponent("service_requests") } } var hotwireTab: HotwireTab { HotwireTab( title: "", image: UIImage(systemName: systemImage)!, selectedImage: UIImage(systemName: selectedSystemImage)!, url: url ) } } // SceneController.swift core part private lazy var tabBarController: HotwireTabBarController = { let controller = HotwireTabBarController(navigatorDelegate: self) controller.load(AppTab.allCases.map(\.hotwireTab)) // Show only tab icons, remove text controller.viewControllers?.forEach { vc in vc.tabBarItem.title = nil vc.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) (vc as? UINavigationController)?.delegate = self } return controller }() To remove tab titles and keep only icons, both tabBarItem.title = nil and imageInsets adjustment are needed. Setting only title to nil leaves the icon position unchanged, looking awkward. ...

This documents the problems encountered while integrating role-based multi-step signup and the Resend email service on a Rails 8 + Inertia.js + Svelte 5 stack. 1. Role-Based Conditional Multi-Step Signup Form Requirements In a service with two user roles, the signup flow needed to differ: Role A: Basic info -> Work selection -> Organization info (3 steps) Role B: Basic info -> Work selection (2 steps, organization info unnecessary) Implementing Conditional Steps with Svelte 5 Runes Used $derived to dynamically handle total step count and button behavior based on role. ...

I inserted demo seed data directly into a Rails app, and all percentages showed as 0% on the screen. Server logs were clean, the data was clearly in the DB, yet the numbers would not appear. Situation This was a Rails app with a voting feature. There is a screen showing vote counts per choice, calculating percentages against total votes and displaying them with progress bars and numbers. I needed to show a demo, so I fetched real-time data from an external API and inserted it as seed data. The approach was simple: ...

I created categories on the server side using MCP tools. But the new categories were not visible in the mobile app. It seemed like a simple problem, but the deeper I dug, the more layers were tangled together. The Beginning: Categories Created via MCP Not Showing in App I created system categories like dev/, memory on the server through MCP tools. Calling the API directly showed the data. Refreshing the app did not show them. ...

Notes on the problems encountered while building a funding rate collection feature from multiple cryptocurrency exchanges in Ruby on Rails. Each of the 5 exchanges had different API behavior, and in some cases the official documentation didn’t match the actual behavior. Building a Common Base Client for Exchange APIs Before connecting multiple exchanges, I built a common HTTP client first. Used Faraday with retry and Circuit Breaker logic centralized here. ...

The mobile app keeps logging out. It works fine right after login, but when you background the app briefly and reopen it, the login screen appears. Token storage in SecureStorage was verified, and 401 auto-refresh via Dio interceptor was implemented. So why? Reproducing the Symptom Login to app -> works normally Restart app around access token expiration time -> Session restore fails, forced logout Found a hint in the server logs. ...

When wrapping a Rails app with Hotwire Native (Turbo Native) to build iOS/Android native apps, there are quite a few things that work fine in the browser but behave strangely in WebView. Here are the issues encountered during actual development and the fixes applied, all in one place. Most can be resolved with a few lines of CSS or one line in the path configuration JSON. 1. Double-Tap Zoom / 300ms Click Delay Symptoms Double-tapping a button quickly zooms the screen. Even a single tap feels slightly delayed (about 300ms). ...

After deploying a Rails + Inertia.js + Svelte app, visiting the URL showed a completely blank screen. The server was responding normally and all assets were loading fine — but nothing was rendering. This post covers the full investigation, root cause, fix, and patterns to prevent this from happening again. Symptoms Blank screen (white background only) when accessing the deployed URL Works fine on the local development server No error page displayed — just silence No anomalies in the server logs (200 responses, normal request handling) What makes this situation particularly frustrating is that from the server’s perspective, everything is working perfectly. HTTP status code 200, no error logs, normal response times. The problem exists only inside the browser. ...

When building a component library with Rails + ViewComponent + Lookbook, I ran into a situation where all my Stimulus controllers were stubs — empty shells. Out of 13 controllers, only 3 were actually working; the remaining 10 were single-line connect() {} placeholders. This post documents the debugging process and implementation decisions for all 11 controllers I built out. The focus here is not just on the code itself, but on why I chose each approach, what problems came up, and how I solved them. ...

This documents the problems encountered while implementing a pet profile avatar selection feature (image or color) on a Rails 8 + Inertia.js + Svelte 5 stack. Problem 1: Colors Were Not Stored in the DB Symptoms Looking at the initial code, pet card colors were displayed like this: const PET_COLORS = ['#f3caa1', '#b7ddf9', '#d3c8ff', '#c5d5f4', '#ffd9aa'] function petColor(index: number): string { return PET_COLORS[index % PET_COLORS.length] } Colors were determined by the order (index) in which pets were created. Since colors were not stored in the DB at all, even if a user changed a color, it would revert to the original on refresh. ...

During the first cloud deployment of a Rails 8 project, I ran into five separate problems in a single day. Each one looked independent at the time, but the pattern was clear: fix one issue and the next one surfaces. Rails 8’s newly introduced Solid Suite with its multi-database structure added extra unfamiliarity even for seasoned Rails developers. Here is a chronological account of what went wrong and how each issue was resolved. ...

When implementing IAP (In-App Purchase) in a Flutter app and running an open beta, gaps like “it is beta but the store shows paid prices” or “credits are duplicated on Restore” start to surface. Here are the issues I encountered and how they were resolved. 1. The Contradiction Between Beta Mode and the Store Problem // constants.dart static const bool isOpenBeta = true; When isOpenBeta = true, spendCredits() does not deduct credits. This means AI features are free. ...

When managing Flutter iOS apps across multiple Apple accounts, you may find that make testflight works perfectly in one project but the same Makefile fails in another. Here is a case I ran into today. Symptoms Error (Xcode): No signing certificate "iOS Development" found: No "iOS Development" signing certificate matching team ID "XXXXXXXX" with a private key was found. Running flutter build ipa fails with this error. The Distribution certificate is in the Keychain, but it says the Development certificate is missing. ...

I was trying to push a build when multiple issues hit at once. The code generator failed, files were missing, the build number got rejected, UI had hardcoded dark mode colors, and logout wasn’t clearing tokens. Going through them one by one. 1. Retrofit Optional Parameter Syntax Error -> .g.dart Generation Failure Symptoms When running dart run build_runner build, some API service files produce: Expected to find ')' Cause Wrong placement of optional parameters ({}) in Retrofit abstract methods. ...

Notes from extending the form auto-fill feature in a browser extension and the various struggles along the way. 1. Direct Car Insurance Site Content Script Auto-Fill Problem: Can’t Read Form Structure via WebFetch on JS-Rendered Sites Korean insurance company direct sites mostly use SPA/RIA architecture. Samsung Fire: SFMI proprietary RIA framework Hyundai Marine, DB Insurance: Spring MVC .do URL patterns KB Insurance, Meritz: Separate mobile/PC domains Scraping URLs with WebFetch doesn’t yield form field structures. I chose to cover common industry field name patterns instead of manually checking each site via DevTools. ...

Apple Sign-In was failing with 403 Forbidden while Google Sign-In worked perfectly fine. Since Apple login worked correctly in another project using the same stack (Rails 8 + Flutter), I did a comparative analysis to find the root cause. The short answer: three independent bugs existed simultaneously, and all three originated from copy-pasting the Google SSO implementation to create the Apple SSO implementation. Symptoms Apple login: 403 Forbidden Google login: works fine Error message: "Email not verified by Apple" Reproducible in production only, not in development (Apple test account quirk) Background: Apple and Google JWTs Are Not the Same The OAuth 2.0 / OIDC specification says the email_verified claim should be a boolean. In practice, Apple sometimes returns this field as the string "true" instead of the boolean true. This edge case is not clearly documented in Apple’s official developer documentation. ...

I received a report that sign-up and login were completely broken. The app just repeated “An unexpected error occurred.” Symptoms Sign-up attempt -> 500 Internal Server Error Login attempt -> same 500 Health check API -> 200 OK, DB connection normal The server was alive and DB was connected, but all authentication features were dead. Investigation Process Step 1: Check Server Status SSH in and check the Rails environment. rails runner "puts Rails.env" # => production rails runner "puts User.count" # => 13 Server normal, DB connection normal, user data exists. ...

After uploading a test build of the app and running it myself, 4 things broke simultaneously. Google login failure, AI itinerary generation returning wrong results, app crash on tapping the notification button, and the popular destinations section completely empty. Here is the process of finding and fixing each cause. 1. Google SSO Fails While Apple Login Succeeds Symptoms Apple Sign-In works normally but Google Sign-In returns a 500 error. The client only shows a login failure toast. ...

When you are building multiple apps simultaneously, each one eventually needs its own landing page. The naive solution — creating eight separate repositories — multiplies management overhead by eight. The opposite extreme — one monolithic repository with a single Netlify deployment — creates a different problem: every change to any single page triggers a full redeployment, and one mistake can break all the others. The approach I settled on sits between these two extremes: a single repository with N independent Netlify sites, each deployed to its own subdirectory via the Netlify CLI. This post documents the structure, the reasoning behind each decision, and the patterns that make this maintainable at scale. ...

I run 3 Hugo blogs for different purposes. Dev blog — Development debugging logs, technical documentation (this blog) [App] Homepage — App introduction + update blog, multilingual (ko/en) Personal blog — Non-development writing They serve different roles, so they are separated. But I wanted to manage them all from one place. I initially split them into separate repositories, but eventually settled on grouping them under a single directory. This post documents that structure and the configuration choices for each blog. ...

While implementing a Transactional Outbox pattern for offline sync in a mobile app, I discovered that “sync failed” errors were repeatedly shown to users even though synchronization had actually completed successfully. Symptoms The app repeatedly threw the following error: AppException: Failed to push changes: AppException: Push completed with failures; retry count: 2, pending changes remain in queue. Checking server logs confirmed that sync pull was working normally and the data was already synchronized. ...

Login sessions keep dropping in a Flutter BLoC app. Tokens are stored in SecureStorage, automatic renewal on 401 is implemented via Dio interceptors – so why? Starting from server logs, I found 3 causes and fixed all of them. Here is the full process. Tech Stack Mobile: Flutter + BLoC pattern + Dio HTTP + SecureStorage Server: Rails 8 API + ActionCable WebSocket Auth: SHA-256 digest-based access token + JTI refresh token (90 days) Real-time: ActionCable WebSocket (token-based auth) Symptoms Works fine right after login API requests start failing with 401 after some time Token refresh seems to work, but WebSocket disconnects App eventually transitions to unauthenticated state Cause 1: Ghost of Legacy Code - Residual DTA Methods Discovery Server logs showed intermittent user.tokens-related errors during token refresh. The project had migrated from devise_token_auth (DTA) to a custom token system, but token_refresh_service.rb still had DTA-era code. ...

When you build a Flutter app long enough, there are two bugs you inevitably encounter at least once. One is the bottom overflowed by N pixels error, the other is text becoming invisible against the background in light mode. Both have simple causes, but until you do a full audit of all screens, it’s easy to just think “something’s off on a few screens.” It wasn’t until I swept through all 50 pages of the app that the pattern became clear. ...

Loading the entire list upfront is slow. I needed infinite scroll that naturally loads the next batch of data as the user scrolls. Packages like infinite_scroll_pagination exist, but fitting them into an existing BLoC structure sometimes means redesigning your state to match the package’s approach, which can actually make things more complex. Since it’s perfectly achievable with just ScrollController and no external dependencies, I went that route. Why Offset-Based There are two pagination approaches. ...

Overview A guide documenting the structure and deployment workflow for managing multiple static sites (landing pages and blogs) from a single directory. This covers practical problems that arise when operating sites with different characteristics — a personal tech blog, per-app landing pages, and multilingual blogs — along with concrete solutions for each. Static sites have no server overhead, deploy fast, and benefit from highly efficient CDN caching, making them ideal for small personal projects and app marketing pages. However, running multiple sites simultaneously makes it easy to create confusion when each site has a different deployment strategy. This guide clearly separates the deployment approach for each site type and explains how to automate repetitive tasks. ...

Here are the errors repeatedly encountered while uploading multiple Flutter apps to TestFlight. 1. Apple Sign-In Error 1000 SignInWithAppleAuthorizationException(AuthorizationErrorCode.unknown, The operation couldn't be completed. (com.apple.AuthenticationServices.AuthorizationError error 1000.)) Cause This occurs because the Sign in with Apple capability is missing from Runner.entitlements. Solution Both places must be configured. 1. ios/Runner/Runner.entitlements <key>com.apple.developer.applesignin</key> <array> <string>Default</string> </array> 2. Apple Developer Console developer.apple.com -> Identifiers -> Select app Bundle ID -> Check Sign in with Apple -> Save ...

If you maintain a Flutter project long enough, the day comes when flutter analyze spits out hundreds of deprecated warnings. Everything works fine functionally, but when warnings pile up, real problems get buried. Here are the patterns that emerged from cleaning up 200+ deprecated warnings in one session. Leaving deprecated warnings unaddressed eventually leads to three concrete problems. First, when the next major Flutter upgrade lands, deprecations become removals and the build breaks. Second, real bugs and type errors get lost in the noise of info-level warnings, making code review less effective. Third, when a new team member joins and asks “why are there so many warnings?”, the answer requires explaining accumulated technical debt. A single cleanup session dramatically lowers the ongoing maintenance cost. ...

Dealt with two problems back-to-back while working on a Flutter app. One was a UI-level issue – connecting components that were just shells with onTap: () {}. The other was a problem in Xcode 26.2 beta where the app itself wouldn’t install on the simulator due to extensions. 1. Connecting Non-Functional UI Components A common situation during Flutter development: screens are all built, but buttons have onPressed: () {}, cards have onTap: () {}, and there’s no actual behavior. ...

A BLoC that just loads and displays a list is not hard. The challenge comes when you need to manage session-based workflows in a single BLoC – things like “create a session -> add questions -> receive answers -> complete.” This pattern appears constantly in real-world apps: review Q&A systems, multi-step surveys, onboarding flows. If you have ever watched your UI collapse under the weight of a single isLoading boolean, this post is for you. ...

After integrating phone number verification into a Flutter app, I ran into the dreaded “the verification code isn’t arriving” situation. And when pressing the dev bypass button to skip verification and attempt signup, the server returned “Phone number verification not completed.” This post documents both issues: what caused them and how to fix them. Firebase Phone Auth looks straightforward on the surface, but it has several layers where things can break — platform-specific configuration (Android vs. iOS), token verification flow, and behavioral differences between development and production environments. Understanding which layer is failing is the key to diagnosing these problems quickly. ...

While running a Telegram bot that lets users add tasks through natural language input, I ran into three separate bugs and then improved the overall UX by introducing an inline keyboard confirmation step. This post walks through each bug, its root cause, and how it was fixed. The bot’s basic architecture works like this: a user sends a free-form message, the Rails backend first applies a regex-based pre-filter to make a quick guess at intent, and then calls Gemini AI to extract the final intent along with parameters (date, time, content, etc.). This two-stage pipeline is where each bug was hiding, and the results combined in unexpected ways that caused unintended actions for users. ...

This is a summary of reconfiguring code signing settings from scratch while uploading a Flutter app to TestFlight. The process uses manual signing + App Store Connect API Key instead of Xcode automatic signing. Overall Flow [1] Issue Distribution Certificate [2] Issue APNs Certificate (CSR generation required) [3] Enable Push Notifications on App ID [4] Create Provisioning Profile (App Store, Push included) [5] xcodebuild archive + export (API Key authentication) [6] Upload to TestFlight via xcrun altool 1. Distribution Certificate Apple Developer -> Certificates -> + -> Select Apple Distribution. ...

Notes on integrating the DART Open API (Korea’s Financial Supervisory Service disclosure system) with a Rails backend. Implemented 5 areas: disclosure monitoring, audit opinions, governance, financial indexes, and equity reports – each step came with its own struggles. Implementation Structure Created a model and ActiveJob for each data type. Each job calls the DART API and inserts data using upsert_all – simple structure. DartCorpCodeSyncJob -> dart_companies (company master) DartDisclosureSyncJob -> dart_disclosures (disclosure list) DartMajorEventSyncJob -> dart_major_events (delisting trigger events -- DS001) DartAuditOpinionSyncJob -> dart_audit_opinions (audit opinions -- DS002/DS003) DartGovernanceSyncJob -> dart_executives / dart_major_shareholders (DS004/DS005) DartFinancialIndexSyncJob -> dart_financial_indexes (fnlttSinglAcntAll) DartEquityReportSyncJob -> dart_equity_reports (equity disclosures) Struggle 1: upsert_all + update_only + updated_at Duplication The first error to hit. ...