How DocMind went from browser-only to a proper .NET MAUI client, without touchin' the server
The Situation
DocMind started life as a plain ol' PHP web app — upload a document, let the AI sort it out, chat with your whole library, get your deadlines tracked automatic. Works fine in a browser. But a browser ain't a phone app, and folks expect a phone app to feel like one: native navigation, a share sheet that actually shows up when you hit "share" from another app, offline-friendly upload, the works.
So the question was: rebuild the whole backend to serve a mobile client properly, or leave the server exactly as it is and build a real native front end that talks to the API that's already there?
We went with the second option. No WebView wrapped around the website, no backend rewrite. A genuine .NET MAUI app, callin' the same JSON endpoints the web app already uses, PHP session cookies and all.
The Rule That Shaped Everything
One rule got set at the start and never moved: the PHP app is the boss. Whatever the website does — structure, wording, order of things on a page — the app does too, feature for feature, no exceptions, unless somebody explicitly decided otherwise. A handful of things were explicitly decided otherwise — flyout-style navigation instead of tab bars, a chat screen styled after the big AI assistants everybody's used to by now, an account menu that opens from the bottom instead of buryin' settings three screens deep. Everything else: match the web app, don't get creative.
That discipline matters more than it sounds. It's real easy for a mobile rebuild to drift — "well, on a phone it'd be nicer if..." — until six weeks later the app and the website barely agree on what a document even looks like. Keepin' one system as the single source of truth for behavior kept that from happenin'.
Talking to a Web App That Doesn't Know It's Talking to an App
The trickiest part wasn't the pretty screens. It was login.
The API that handles document uploads, chat, and library management returns clean JSON. Login doesn't. login.php is a classic server-rendered HTML form — it was built for a browser, and it still thinks it's talkin' to one. There's no "success" flag in a JSON body to check.
So the app watches the raw HTML that comes back and looks for one specific, language-independent marker: whether the response contains a particular hidden form field that only shows up when the server's askin' for a two-factor code. If that field's there, the login needs a second step. If it's not, and the session cookie looks right, you're in. No translated error text to parse, no fragile string matching against six different languages — just one structural fact about the page that doesn't change no matter what language the user's got selected.
That's the kind of thing you only find by readin' the actual server code, not by guessin' at what a login page probably does.
Chat, Rendered Native — No Browser Hidin' Inside the App
A lot of "native" mobile apps cheat on rich text by droppin' a WebView in the middle of the screen and lettin' the browser engine render everything. DocMind's chat doesn't do that. Markdown — headers, bold, italics, code blocks, lists, links, block quotes — gets parsed by a plain C# parser and turned into real native labels and layouts. No embedded browser anywhere in the app.
Why bother? Because a WebView is its own little world — different fonts, different scrolling behavior, different theming, and it never quite matches the rest of the screen around it. Parsin' markdown into native views means dark mode, font sizes, and touch scrolling all just work the same way the rest of the app does, because it is the rest of the app.
The chat responses stream in for real too — actual server-sent events, parsed line by line as they arrive, not a fake typing animation played over a response that already finished loadin'. Answers come with source citations shown as file cards you can tap straight through to the original document.
Two-Factor, and the Problem Only a Phone Has
Settin' up two-factor authentication usually means scannin' a QR code with your camera. That works fine when the QR code's on a different device. It falls apart the second you realize the authenticator app and the QR code are both tryin' to live on the same phone — you cannot point your camera at your own screen.
That's not a hypothetical bug, that's just physics. The fix: the app's main path for settin' up two-factor is a direct handoff — tap a button, the phone's own launcher opens straight into whatever authenticator app you've already got installed (Google Authenticator, Authy, whatever), no camera needed. The QR code's still there as a third option, for the case where somebody deliberately keeps their authenticator on a separate device — plenty of security-minded folks do exactly that on purpose, and takin' that option away would've been a step backward.
Things That Bit Us
Every real project's got a few scars, and this one's honest about its own:
- A silent date bug. The web app showed six saved chat conversations. The app showed none. Same account, same server. Turned out the server sends timestamps in plain MySQL format, and the one spot in the whole app that modeled a date as an actual
DateTimeinstead of a plain string couldn't parse it — and the error got swallowed by a try/catch that was supposed to be handlin' somethin' else entirely. Fixed by treatin' every date from the server as a string, full stop, and formattin' it for display only when needed. Lesson learned the hard way, written down so it don't happen twice. - XAML doesn't like double dashes. A comment in the markup used
--good/--warn/--badas shorthand notes — turns out that's flat-out illegal inside an XML comment, and it broke the whole file's parsin'. Small thing, cost real time findin' it. - Android's newer versions stopped movin' the screen out of the way of the keyboard automatically. Had to write our own listener for it.
None of these are dramatic. They're the ordinary cost of connectin' a new client to a server that was never built with that client in mind — and every one of 'em's the kind of thing you only catch by testin' against the real server, not by assumin' the API does what the docs say it does.
Where It Stands
The MAUI client now covers login (password and two-factor), the full document library with chunked resumable uploads, native share-target integration, streamin' chat with citations, deadline tracking with calendar invites, sharing (person-to-person and public links), full document detail and version history, and account/subscription management — deliberately without any admin functionality, which stays web-only on purpose. Six languages, light and dark themes, Android first with Windows as a second target.
It's still catchin' up to the web app feature-for-feature as the web side keeps movin' — and that's fine. That's the whole point of havin' one system be the boss: the app doesn't have to be finished, it just has to know exactly what "finished" looks like.