<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shenouda Bertel</title>
    <description>The latest articles on DEV Community by Shenouda Bertel (@shenoudab).</description>
    <link>https://dev.to/shenoudab</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4066163%2Fc386708b-1e66-4f43-beca-ffbf28af32d1.jpg</url>
      <title>DEV Community: Shenouda Bertel</title>
      <link>https://dev.to/shenoudab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shenoudab"/>
    <language>en</language>
    <item>
      <title>I built a Markdown resume builder for the AI-paste workflow — here's everything that broke</title>
      <dc:creator>Shenouda Bertel</dc:creator>
      <pubDate>Thu, 06 Aug 2026 15:53:15 +0000</pubDate>
      <link>https://dev.to/shenoudab/i-built-a-markdown-resume-builder-for-the-ai-paste-workflow-heres-everything-that-broke-32g1</link>
      <guid>https://dev.to/shenoudab/i-built-a-markdown-resume-builder-for-the-ai-paste-workflow-heres-everything-that-broke-32g1</guid>
      <description>&lt;p&gt;There's a workflow that basically didn't exist three years ago and now half the job-seekers I know use it: ask ChatGPT, Claude, Gemini, or any AI to write your resume bullets, get back beautifully structured text… and then spend forty minutes mangling it into Word or a drag-and-drop resume builder, fixing bullet indentation and font sizes by hand.&lt;/p&gt;

&lt;p&gt;Here's the thing that bugged me: &lt;strong&gt;LLMs already speak Markdown.&lt;/strong&gt; Ask any chatbot for a resume and you get &lt;code&gt;## Experience&lt;/code&gt;, &lt;code&gt;**Senior Engineer**&lt;/code&gt;, &lt;code&gt;- Shipped X&lt;/code&gt; — clean, structured Markdown. Then every resume tool on earth makes you throw that structure away and re-enter it into form fields.&lt;/p&gt;

&lt;p&gt;So I built ResumeMD: a split-pane editor where you paste Markdown on the left, see a typeset resume on the right, pick a template, and download a PDF. No signup to start, everything in localStorage by default. This post is about the parts that fought back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision 1: Markdown is the source of truth
&lt;/h3&gt;

&lt;p&gt;Most resume builders store your resume as a proprietary JSON blob mapped to form fields. I wanted the document itself to be portable text. That means the entire product is "just" a Markdown renderer with opinions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;h2&lt;/code&gt; = section headers (Experience, Education) — these get the decorative treatment per template: uppercase, border, background, prefix glyphs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;h3&lt;/code&gt; = job titles — plain, bold, primary color.&lt;/li&gt;
&lt;li&gt;One weird trick I'm genuinely fond of: the sidebar template splits a single Markdown document into main column and sidebar using an HTML comment (&lt;code&gt;&amp;lt;!-- sidebar --&amp;gt;&lt;/code&gt;) as the split marker. Content above the marker is the main column; below is the sidebar. It keeps the document valid Markdown everywhere else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preview is react-markdown + remark-gfm with a 300ms debounce, styled by a template system that turned out to need &lt;strong&gt;three parallel implementations&lt;/strong&gt; of every template: CSS classes for the live preview, inline-style functions shared between preview and template cards, and pure-JS styles for the PDF renderer. Thirty-two templates, three layers each. When I add a template I touch three files in lockstep, and yes, it's as tedious as it sounds — but each rendering context has constraints that made a single abstraction leakier than the duplication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision 2: localStorage first, accounts optional
&lt;/h3&gt;

&lt;p&gt;I didn't want a signup wall in front of a tool whose whole pitch is "paste and go." So the architecture is offline-first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No account:&lt;/strong&gt; everything lives in localStorage. The editor, all 32 templates, PDF export, LinkedIn import — fully functional with zero backend calls. Your resume never leaves the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;With an account:&lt;/strong&gt; the same localStorage state gets a cloud-sync layer on top — 5-second debounce, an offline queue, and conflict resolution for multi-device edits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sync layer produced my favorite bug of the whole project. The Supabase real-time subscription callback captured stale closure values of the current markdown and settings. Change your template while a sync was in flight, and the subscription would "detect" a conflict between your own update and… your own update, then overwrite your state. The fix is the classic React pattern nobody enjoys writing: refs (&lt;code&gt;currentMarkdownRef&lt;/code&gt;, &lt;code&gt;cloudVersionRef&lt;/code&gt;) updated synchronously during render, an &lt;code&gt;isSyncingRef&lt;/code&gt; guard so the subscription ignores our own writes, and a dependency array cut down to identity values only. If you have a useEffect subscription that needs current state but must not re-subscribe on every keystroke — refs, not closures.&lt;/p&gt;

&lt;p&gt;A second race hid behind the first: on first sign-in, &lt;em&gt;two&lt;/em&gt; async paths could each decide "no cloud resume exists yet, better create one," producing duplicate rows five seconds apart. The fix was a creation-specific guard ref plus a re-check inside the create branch: query once more before inserting, and if another path won the race, adopt its row instead of creating a sibling.&lt;/p&gt;

&lt;h3&gt;
  
  
  The PDF pipeline, or: three fights I didn't expect
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Fight 1: Tailwind v4's Lightning CSS.&lt;/strong&gt; This one cost me weeks. Lightning CSS (via &lt;code&gt;@tailwindcss/postcss&lt;/code&gt;) aggressively optimizes selectors, and for my template CSS it would split combined &lt;code&gt;h2, h3&lt;/code&gt; rules and generate h3 declarations that &lt;em&gt;were not in my source&lt;/em&gt;. It fabricated &lt;code&gt;::before&lt;/code&gt; pseudo-element rules I never wrote. It stripped &lt;code&gt;h2 strong { color: inherit }&lt;/code&gt; entirely. My mitigations, in escalating order of resignation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Move all template-specific h2/h3 styling to inline &lt;code&gt;style&lt;/code&gt; props.&lt;/li&gt;
&lt;li&gt;Render decorative prefixes (the &lt;code&gt;&amp;gt;&lt;/code&gt; on the Tech template, the dash bar on Swiss) as actual React elements instead of CSS pseudo-elements.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; tag in the server layout's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that nukes every &lt;code&gt;.resume-preview h2/h3 ::before/::after&lt;/code&gt; with &lt;code&gt;content: none !important&lt;/code&gt; — because compiled pseudo-element rules kept resurrecting from Turbopack's cache.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;React.cloneElement&lt;/code&gt; on heading children to force &lt;code&gt;color: inherit&lt;/code&gt; where the CSS rule got stripped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm not saying Lightning CSS is wrong for your project. I'm saying: if your product &lt;em&gt;is&lt;/em&gt; precise typography, audit what your CSS processor actually emits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fight 2: fonts, at scale of "the whole world writes resumes."&lt;/strong&gt; PDF generation uses @react-pdf/renderer, which requires you to embed fonts for any glyph you render — there's no OS font fallback inside a generated PDF. Latin was easy. Then a Chinese sample resume rendered as tofu boxes, and I learned that Noto Sans SC — the font that covers Simplified Chinese — is a &lt;strong&gt;17MB&lt;/strong&gt; TTF. That's the file you have to load to render one CJK résumé. Arabic added another 825KB (plus RTL handling in the preview), Devanagari another file for Hindi. The system that emerged is a script-detection module (Unicode-range regexes classifying CJK, Arabic, Devanagari, Thai, Bengali, Tamil) feeding a font registry that maps script family → registered font, applied consistently across PDF, DOCX, and HTML export so a Japanese resume doesn't silently degrade in one format. Six languages supported end-to-end so far. Adding a script is now: drop a TTF, add a registry entry, add a sample.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fight 3: color spaces — a fight I eventually won by deleting the combatant.&lt;/strong&gt; Tailwind v4 emits &lt;code&gt;lab()&lt;/code&gt; and &lt;code&gt;oklch()&lt;/code&gt; colors. html2canvas — which I used to rasterize the little resume thumbnails on the dashboard — understands neither, so for months there was an &lt;code&gt;onclone&lt;/code&gt; callback walking a cloned DOM converting modern color functions to RGB before rasterization, guarded by the loudest comment in the codebase. The real fix shipped this month: the whole thumbnail pipeline is gone. Dashboard previews now render live from the Markdown itself — same react-markdown pipeline, scaled down — so they're never a stale snapshot, and html2canvas left the dependency tree entirely. Sometimes the best patch is &lt;code&gt;pnpm remove&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's free, honestly
&lt;/h3&gt;

&lt;p&gt;The free tier isn't a demo. Without even creating an account: full editor, live preview, all 32 templates, un-watermarked PDF export, LinkedIn import, and it works in all six languages. A free account adds cloud sync for one resume and a public profile page. The paid tier ($9/month, with $24/quarter and $89/year options) is the workflow layer — unlimited synced resumes, DOCX/HTML/TXT export, AI job tailoring, version history. I gate convenience and AI compute, not the core tool. That's partly principle and partly architecture: the localStorage-first design means the free tier literally costs me almost nothing to serve — and free, un-watermarked PDF export is a published commitment on the site now, not a growth experiment I might quietly walk back.&lt;/p&gt;

&lt;p&gt;Also, a confession that doubles as a warning about naming things: after launch I discovered "ResumeMD" collides with an unrelated open-source project, a &lt;code&gt;.org&lt;/code&gt;, and at least one other hosted app — and the GitHub repo outranks me for my own name. Check the SERP &lt;em&gt;before&lt;/em&gt; you fall in love with a name. I now write "ResumeMD (resumemd.pro)" everywhere, including here.&lt;/p&gt;

&lt;p&gt;The test suite is at 2,978 tests across 105 files, most of which exist because a template, script, or sync bug got past me once. If you've fought @react-pdf font embedding or Lightning CSS selector rewriting, I'd genuinely like to compare notes in the comments.&lt;/p&gt;

&lt;p&gt;And if you have AI-generated resume text sitting in a chat window right now — &lt;a href="https://resumemd.pro" rel="noopener noreferrer"&gt;paste it into resumemd.pro&lt;/a&gt; and see what it looks like typeset. No signup, it runs in your browser.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>react</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
