<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Frankmd | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/frankmd/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:51 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/frankmd/index.xml" rel="self" type="application/rss+xml"/><item><title>Vibe Code: I Built a Markdown Editor From Scratch With Claude Code (FrankMD) PART 2</title><link>https://akitaonrails.github.io/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-2/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-2/</guid><pubDate>Sun, 01 Feb 2026 19:29:18 +0000</pubDate><description>&lt;p&gt;If you haven&amp;rsquo;t read it yet, check out &lt;a href="https://akitaonrails.github.io/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-1/"&gt;Part 1&lt;/a&gt; to understand the app I built. &lt;strong&gt;FrankMD&lt;/strong&gt; is a Markdown editor that I customized to be effective for my personal use case: writing blog posts. It wasn&amp;rsquo;t meant to be a replacement for Obsidian or VS Code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/akitaonrails/FrankMD"target="_blank" rel="noopener"&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_18-49-26.jpg" alt="FrankMD" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;TL;DR&lt;/strong&gt; is this: in the old days, if I were to build this web app from scratch, it would have cost me around &lt;strong&gt;200 hours&lt;/strong&gt;, spread over 1 or 2 months, going slow, back and forth. And it probably wouldn&amp;rsquo;t have ended up as complete as this one.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you haven&rsquo;t read it yet, check out <a href="/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-1/">Part 1</a> to understand the app I built. <strong>FrankMD</strong> is a Markdown editor that I customized to be effective for my personal use case: writing blog posts. It wasn&rsquo;t meant to be a replacement for Obsidian or VS Code.</p>
<p><a href="https://github.com/akitaonrails/FrankMD"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_18-49-26.jpg" alt="FrankMD"  loading="lazy" /></a></p>
<p>The <strong>TL;DR</strong> is this: in the old days, if I were to build this web app from scratch, it would have cost me around <strong>200 hours</strong>, spread over 1 or 2 months, going slow, back and forth. And it probably wouldn&rsquo;t have ended up as complete as this one.</p>
<blockquote>
  <p>With Claude, I did it all in a little over <strong>30 hours</strong>, spread across 3 days of super hyper focus (I don&rsquo;t recommend it; I almost got sick doing it). In the end, this produced a project with roughly 130 pull requests. A total of <strong>18 thousand lines of code</strong> (test coverage around 70%). It was all done in Ruby on Rails 8 and JavaScript (HotWire/Stimulus).</p>

</blockquote>
<p>There are several advantages to this particular app: it&rsquo;s a very well-known and well-documented scope. There are hundreds of text editors of all types and complexities. Surely every LLM has been trained with tons of material on this. So when I ask it to search by filename, it already knows to reach for something like the Levenshtein algorithm for fuzzy matching, for example.</p>
<p>Searching and listing images in a grid, uploading to S3, find and replace, HTML preview, etc. All of this is super well documented and trivial to do. Even without LLMs, any intern would find it in the first Google link and on Stack Overflow. So I&rsquo;d say more than 90% of the scope was easy. An app with a less well-known scope — say, a scientific application — would have been much harder. So this example of mine is only slightly above a Hello World: repeating a well-known scope that already exists.</p>
<p>Unlike what I did in my first Vibe Coding article, this time I won&rsquo;t comment commit by commit; there are more than 130! We&rsquo;d be here all week. Instead, I&rsquo;ll comment on a few themes that catch my attention and that might be relevant to other projects.</p>
<h2>Choose Your LLM Plans Carefully<span class="hx:absolute hx:-mt-20" id="choose-your-llm-plans-carefully"></span>
    <a href="#choose-your-llm-plans-carefully" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Over those 3 days, I was interrupted by Claude Code about 2 or 3 times and had to use something else for 2 or 3 hours. That&rsquo;s because it has some daily token limit and tells me to stop after I use too many tokens.</p>
<p>I&rsquo;m using the Max Plan (5x Pro), which I think costs around USD 100, meaning around USD 6 a day. In this case it was cheap, because 3 days would only cost USD 18.</p>
<p>But during one of those breaks, I switched to using Claude Opus via OpenRouter, and that was WAY more expensive. Outside the Claude Code exclusive Max plan, the per-token cost is absurdly higher. In about 2 or 3 hours, it already burned through more than USD 100 (equivalent to the whole monthly subscription!)</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201165118_screenshot-2026-01-31_10-19-11.png" alt="open router billing"  loading="lazy" /></p>
<p>So, in total, by using the Max plan outside its bounds, I ended up spending more than USD 120. But if I had just stopped to rest between the daily limits, I would have spent one extra day (wouldn&rsquo;t have gotten sick &#x1f605;) and wouldn&rsquo;t have gone much beyond USD 20. So you need to research the plans for each platform carefully before jumping in without thinking, like I did. &#x1f602;</p>
<p>As I&rsquo;ve said over and over: when in doubt, use Claude Code with the Max plan.</p>
<h2>Choose Your Architecture Carefully<span class="hx:absolute hx:-mt-20" id="choose-your-architecture-carefully"></span>
    <a href="#choose-your-architecture-carefully" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>When I started coding (every time I say &ldquo;I coded&rdquo;, read it as &ldquo;I vibe coded&rdquo;, of course), the start was very fast. I decided only a few technical things: I wanted it to be Ruby on Rails 8, I wanted it to use Tailwind CSS, I wanted it to use no database and implement everything based on the filesystem, I wanted it to have no authentication, since it would be self-hosted for my use only.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-01-45.jpg" alt="first commits"  loading="lazy" /></p>
<p>The first 10 commits took a little over an hour and a half. I kept adding features bit by bit to give &ldquo;shape&rdquo; to the editor, and at this point I already had a minimal, good-looking editor, with a dark theme, that edited and displayed an HTML preview. Not bad for a prototype.</p>
<p>As I said in the other article, this would be one of the &ldquo;it already works&rdquo; milestones.</p>
<p>But of course, there are still more than 120 commits to really work, and nearly 30 hours to reach the &ldquo;really works&rdquo; stage.</p>
<p>I kept adding various small features, in this order:</p>
<ul>
<li>Add a Markdown cheat-sheet (help)</li>
<li>Dialog box to add fenced code blocks with language autocomplete</li>
<li>Theme system, already adding around 10 themes (but not yet tuned for Omarchy)</li>
<li>Dialog box to customize fonts</li>
<li>Zoom control on the Preview</li>
<li>First attempt at &ldquo;typewriter mode&rdquo; (this will cause plenty of headaches later)</li>
<li>Dialog box to search files by path with a fuzzy finder</li>
<li>First attempt to sync the editor scroll with the preview (this will also cause plenty of headaches)</li>
<li>Dialog box to search files by content with regex support</li>
<li>Dialog box to search and embed YouTube videos</li>
<li>Dialog box to search images, upload and insert</li>
<li>Initial Docker support</li>
</ul>
<p>That completes the first <a href="https://github.com/akitaonrails/FrankMD/commit/ba42c81b47a6343ba42df8705c6973d9d45506a0"target="_blank" rel="noopener">34 commits</a> and about 4 hours of work. Again, very close to &ldquo;it already works&rdquo;.</p>
<p>But the fun begins at <a href="https://github.com/akitaonrails/FrankMD/commit/7bbf03a4b594203ed1a36c6735f9d23c5ffc2c91"target="_blank" rel="noopener">commit 7bbf03a</a>, titled: &ldquo;Refactor to Restful Architecture&rdquo;.</p>
<blockquote>
  <p>This is a Rails-specific thing, and an architecture thing in general. Claude, without me instructing it, did exactly what I&rsquo;d expect a Rails intern to do: it crammed all the logic for managing directories and files, image uploads and everything else into 2 or 3 service modules.</p>

</blockquote>
<p>It also wrote all the routes manually. That is, it didn&rsquo;t use the most basic thing in Rails: the concept of <a href="https://guides.rubyonrails.org/routing.html"target="_blank" rel="noopener">Restful Resources</a>. Read the linked guide; I won&rsquo;t explain what that is here, just that any experienced Rails person knows it with their eyes closed.</p>
<p>So it created routes like &ldquo;<a href="http://localhost:3000/notes"target="_blank" rel="noopener">http://localhost:3000/notes</a>&rdquo; but didn&rsquo;t enable things like &ldquo;<a href="http://localhost:3000/notes/hello"target="_blank" rel="noopener">http://localhost:3000/notes/hello</a>&rdquo; automatically opening &ldquo;hello.md&rdquo;, and HTTP verbs like PATCH to update the file or DELETE to remove the file. And also, without the GET option, I wouldn&rsquo;t have the option to bookmark a file directly. All of this comes for free if you use RESTful. So that commit was the refactor where I instructed it to redo everything in that architecture. Just that alone was almost <strong>1000 lines</strong> added.</p>
<blockquote>
  <p>The &ldquo;correct&rdquo; thing would have been to set up, right at the start, a CLAUDE.MD or CONTRIBUTING.MD file or something similar with the specific architecture rules and code conventions — but I didn&rsquo;t, so here&rsquo;s a reminder for you. &#x1f605;</p>

</blockquote>
<h3>Refactoring and Fixes<span class="hx:absolute hx:-mt-20" id="refactoring-and-fixes"></span>
    <a href="#refactoring-and-fixes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>After that first big refactor, I went back to making adjustments (fixes) and kept adding new features:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-17-55.jpg" alt="screenshot 2026 02 01 17 17 55"  loading="lazy" /></p>
<p>At this point we already crossed <strong>47 commits</strong> and more than <strong>8 hours</strong> non-stop. And yes, I didn&rsquo;t take my eyes off the monitor. You can&rsquo;t read 100% of everything Claude is thinking and doing; it&rsquo;s a lot, but overall I&rsquo;ve noticed you need to keep glancing at it and, most importantly, <strong>pay very close attention</strong>!</p>
<p>The processing time for multiple agents in parallel, many in <em>reasoning/thinking</em> mode, is really slow. Claude is one of the fastest compared to the others, but in practice I find it painfully slow.</p>
<p>After lunch, I decided to open YouTube on the side while waiting for Claude to finish the tasks. That means I wasn&rsquo;t paying attention and only glanced over to see if it finished with &ldquo;success&rdquo; or asked me for some permission to execute something.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201185351_screenshot-2026-01-30_19-32-51.png" alt="don’t do this lol"  loading="lazy" /></p>
<p>I don&rsquo;t remember if it was at this point, but I went to look at what it had generated, and of course, by this stage it already had an <code>application.css</code> with more than 1000 lines, which I asked it to fix in <a href="https://github.com/akitaonrails/FrankMD/commit/4d3d6a07e572567dbf4ddb59faf54ba5f30aa259#diff-824082aab0a95adb8b632a5c383dd6ffb733b0899d4ba594a8380882467eba82"target="_blank" rel="noopener">this commit</a>, and it broke one huge file into all of these:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-25-55.jpg" alt="application.js refactoring"  loading="lazy" /></p>
<p>Even the themes: everything was in a single file!! This is the level I&rsquo;d call &ldquo;intern&rdquo;. Since everything &ldquo;works&rdquo;, it just keeps piling up code, all in the same place, and leaves worrying about it for later. Every intern does this at the start if nobody teaches them, and apparently the LLM does the same. It won&rsquo;t proactively do anything you didn&rsquo;t explicitly ask for. In fact, I never said in the prompt <em>&ldquo;remember to keep breaking things into small files&rdquo;</em>, but I should have.</p>
<p>But that wasn&rsquo;t the worst. No sir, <a href="https://github.com/akitaonrails/FrankMD/commit/e35f6eb738b218caba1340723d016633d8ead99f"target="_blank" rel="noopener">this commit</a> has all the JavaScript in A SINGLE FILE. I think it was already past <strong>4 or 5 thousand lines</strong>. Look at how many files it broke it into, it doesn&rsquo;t even fit in this picture:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-29-19.jpg" alt="app_controller.js refactoring"  loading="lazy" /></p>
<p>The problem is that I myself fell into what I&rsquo;m criticizing here. I&rsquo;d ask for a new feature, hit &ldquo;reload&rdquo; on the browser and <em>voilà</em>, there was the new feature! &ldquo;WORKING&rdquo;!! (Ruby on Rails is very productive!)</p>
<p>And that&rsquo;s how it went, adding &ldquo;it already works&rdquo;, adding, &ldquo;it already works&rdquo;, and when I finally looked, <strong>horror show</strong>!!</p>
<p>At the last refactoring we were at around 9 hours of work. This is where we wrap up the day; we&rsquo;re on the next day and, not counting the time I slept, we&rsquo;re already at <strong>17 hours</strong>, 70 commits. Roughly HALFWAY through the project.</p>
<blockquote>
  <p>All this to say the following: the BIGGEST part of Vibe Coding will always be REFACTORING, fixing bugs, and adding TESTS!</p>

</blockquote>
<h3>I18n - Internationalization<span class="hx:absolute hx:-mt-20" id="i18n---internationalization"></span>
    <a href="#i18n---internationalization" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I got a little ahead of myself; before the JavaScript mega-refactoring, I had done another mega-refactoring: I asked it to extract all the English strings and already translate a Brazilian Portuguese option for me. Another <a href="https://github.com/akitaonrails/FrankMD/commit/9aee048bcaaafbf83480c5c272032b5bb2941ac5"target="_blank" rel="noopener">gigantic commit</a> with more than <strong>1600 lines</strong> modified.</p>
<p>I wanted to make a separate section just to comment on this because this is one of the points where I think LLMs shine. They were originally built precisely to deal with natural language and translations. LLMs are very good with languages. In the old days I&rsquo;d have to go through Google Translate, which was always pretty bad, and then find a native speaker to adjust the terms Google got wrong.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-38-24.jpg" alt="i18n"  loading="lazy" /></p>
<p>As expected, after extracting everything, asking it to adjust the tests to take i18n into account, and seeing that Portuguese was working, I started adding others just for fun and was very pleased with the result. I18n is definitely something that doesn&rsquo;t make sense to do without an LLM these days. It&rsquo;s orders of magnitude better than the old way.</p>
<h2>The Last HALF<span class="hx:absolute hx:-mt-20" id="the-last-half"></span>
    <a href="#the-last-half" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>From the last big refactor to the end, there are about another 70 commits and roughly 14 hours, the end of the second day and the third day, with a roughly 15-hour break in the middle where I got a fever and had to throw myself into bed and stuff myself with medicine… but all good, the next morning I felt a bit better and finished it!</p>
<p>From there on, I kept adding the rest of the features, like emoji, emoticons, find and replace dialog, jump to line dialog, adjustments to the configuration system (via envs, via .fed), and Docker.</p>
<p>One thing I didn&rsquo;t mention before, but that came before the I18n effort, was a <strong>REBRANDING</strong>. This is another thing LLMs are very good at solving: text substitution, knowing how to tell apart plain text, variables and code in general. I wasn&rsquo;t very inspired and, at the start, I was going to make a MUCH simpler little web editor than what it ended up becoming, and I was just going to call it &ldquo;WebNotes&rdquo;.</p>
<p>But when it grew and I decided it was worth sharing publicly, I decided to give it a real name. That&rsquo;s how &ldquo;FrankMD&rdquo; was born. But it&rsquo;s not enough to just give it a name; now you have to go rename EVERYTHING inside the code, strings, documentation, etc. It was in <a href="https://github.com/akitaonrails/FrankMD/commit/4d370f7cbae145fe6c28c6173c723fccfb818ffe"target="_blank" rel="noopener">this commit</a>: a solid <strong>700 lines</strong> of modifications.</p>
<h3>Now &ldquo;does it work??&rdquo;<span class="hx:absolute hx:-mt-20" id="now-does-it-work"></span>
    <a href="#now-does-it-work" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I&rsquo;m writing this article at 5 PM on Sunday. At 7:25 PM on Saturday, I sent it to some friends on Discord to test, innocently thinking I&rsquo;d already reached version 1.0.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-50-30.jpg" alt="discord"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_17-54-03.jpg" alt="issues"  loading="lazy" /></p>
<p>And there I went, with a fever and needing to lie down, but wanting to reach 1.0!!</p>
<p>The issues themselves I killed quickly, but <strong>Syntax Highlight</strong> I didn&rsquo;t know was going to become hell in my life. The first version came out quickly, in <a href="https://github.com/akitaonrails/FrankMD/commit/e063a0bb3b6f3a1ab927ca335d06fe8eb7946d25"target="_blank" rel="noopener">this commit</a></p>
<h3>Don&rsquo;t Reinvent the Wheel (duh)<span class="hx:absolute hx:-mt-20" id="dont-reinvent-the-wheel-duh"></span>
    <a href="#dont-reinvent-the-wheel-duh" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Yes, it&rsquo;s the most obvious cliché of all, but even I make this mistake. As I said at the start, my expectation for this project was to be little more than a Web &ldquo;Notepad&rdquo; for my notes, and it ended up becoming the main editor for my blog.</p>
<p>The text editor part started with the first thing that works first: just a plain textarea.</p>
<p>But on Saturday I wanted to add a line counter column that takes line wrapping into account, control over that wrapping, typewriter mode always centered in the middle of the screen, and sync between the text editor scroll and the preview.</p>
<p>Not counting all the dialog boxes that add things to the text, like images, videos, code and tables. Not counting the shortcuts for things like bold, italic. Not counting auto-save routines, line count stats, current line, etc.</p>
<p>For those who don&rsquo;t know, the problem with syntax highlight is that you can&rsquo;t edit directly on the colored text (because it has hidden tags that format the text). One way to solve this is to duplicate the text block: one in the background, which is the raw text and you can edit however you want, and another formatted and colored block with HTML exactly on top, which you don&rsquo;t touch, but it updates with whatever is edited. That&rsquo;s how Claude did it.</p>
<p>But there are a LOT of &ldquo;edge cases&rdquo;: alignment problems due to wrapping, problems with size and position to adjust the auto-scroll sync. I spent hours and hours fighting with this. Look at the kind of problem I was dealing with:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201183500_screenshot-2026-02-01_12-14-45.png" alt="syntax offset bug"  loading="lazy" /></p>
<p>Everything got resolved when I decided: enough. I&rsquo;m reinventing the wheel. Obviously this problem has already been solved. So I asked Claude to remove all the logic tied to the custom textarea and replace it with <a href="https://codemirror.net/"target="_blank" rel="noopener"><strong>Code Mirror</strong></a>, which is an open source component for a code editor, with support for line counting, syntax highlight, and much more!</p>
<p><a href="https://github.com/akitaonrails/FrankMD/commit/d0d42c9b57a9d19d904023951e6b46d9a71fcba8"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_18-03-47.jpg" alt="codemirror refactoring"  loading="lazy" /></a></p>
<p>It wasn&rsquo;t just those 5 commits; those were the main ones. They took a solid <strong>3 hours</strong> or more to stabilize that code, plus multiple small adjustments over more commits. A good <strong>6,000 lines of code modified</strong>. It&rsquo;s the kind of refactoring that, in a real project, would have taken the whole week and still would have come out with multiple bugs and side effects. But for this, the LLM is also very good. Big refactorings, <strong>especially if you&rsquo;ve prepared a good automated test suite</strong>, go quite well with LLMs.</p>
<blockquote>
  <p>This is the kind of thing that happens in real projects. I can&rsquo;t blame the LLM, it&rsquo;s entirely my fault for changing the project requirements: originally there wasn&rsquo;t going to be syntax highlight!</p>

</blockquote>
<h3>Tests, tests, tests<span class="hx:absolute hx:-mt-20" id="tests-tests-tests"></span>
    <a href="#tests-tests-tests" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The main recommendations for how to do vibe coding are the same as how to program the old way:</p>
<ul>
<li>Be careful with architecture choices (yes, it takes experience)</li>
<li>Build small features one at a time, each with the right abstractions and immediately with tests</li>
<li>Refactor from time to time to consolidate or abstract code into the right domains, adjusting and adding more tests</li>
</ul>
<p>If you follow those 3 items, it already helps a lot. It&rsquo;s not magic: all of these are tools that give feedback to the LLM so it knows what to do.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201183748_screenshot-2026-01-31_03-33-13.png" alt="tests"  loading="lazy" /></p>
<p>If you&rsquo;re the kind of dumb customer who only says <em>&ldquo;oh, it stopped working&rdquo;</em> — you&rsquo;re no good at talking to a programmer, and I&rsquo;m sorry to say that LLMs won&rsquo;t help you either:</p>
<ul>
<li>WHAT stopped working?</li>
<li>Did everything stop opening?</li>
<li>Did just one element disappear?</li>
<li>Does the element appear but clicking does nothing?</li>
<li>Can you click, but an error message appears?</li>
<li>What message exactly?</li>
<li>What did you do before that?</li>
<li>Did you change anything from the previous test to this one?</li>
<li>etc</li>
</ul>
<p>ALL OF THIS and more has to be in the next prompt for fixes. Including server logs, command lines to reproduce the error, and whatever evidence can be produced, to make it easier to understand the error.</p>
<blockquote>
  <p>There&rsquo;s no such thing as <em>&ldquo;it stopped working&rdquo;</em> — your head is the thing that never worked.</p>

</blockquote>
<p>One thing I really liked about Claude was when a bug appeared that hadn&rsquo;t existed before, but I didn&rsquo;t know if it was from the last commit or from 3 commits back. And instead of just guessing at random things, Claude itself offered to manually do the equivalent of a <a href="https://git-scm.com/docs/git-bisect"target="_blank" rel="noopener">git bisect</a> and kept offering me one commit at a time to test and keep going until we found which one introduced the bug:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201183240_screenshot-2026-02-01_15-05-30.png" alt="bisect"  loading="lazy" /></p>
<h3>Optimizations and Checks<span class="hx:absolute hx:-mt-20" id="optimizations-and-checks"></span>
    <a href="#optimizations-and-checks" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Throughout the project, 2 or 3 times I specifically stopped to check things like memory leaks and security holes. Even being a personal project, I didn&rsquo;t want very obvious holes.</p>
<p>Be warned that Claude is a champion at scattering setTimeout calls in JavaScript and never calling <a href="https://github.com/akitaonrails/FrankMD/commit/039f3f8d72b0bf8facd5a1f85f54523bbfc12b53"target="_blank" rel="noopener">clearTimeout</a> on anything!</p>
<p>It&rsquo;s not much, but I had more than <a href="https://github.com/akitaonrails/FrankMD/commit/afb669f9b96baef05bae02cd76a9731353f6eac4"target="_blank" rel="noopener">one commit</a> to delete <strong>&ldquo;Dead Code&rdquo;</strong>: code that, during a refactor, was moved to another module but forgotten at the origin. LLMs will always leave dead code behind, because they have no way to look at 100% of every file 100% of the time. Eventually it misses one thing or another. And it won&rsquo;t cause any obvious bug. You have to prune it gradually, after each refactoring.</p>
<p>When I started really testing with long texts, I noticed the interface had &ldquo;lag&rdquo;, kind of slow. Two things seemed to be causing it: the statistical count of how many words and characters the text has was very aggressive.</p>
<blockquote>
  <p>Again, &ldquo;it already works&rdquo; … but slow …</p>

</blockquote>
<p>Worse: when I first asked it to create the text editor, I didn&rsquo;t say exactly how I wanted the &ldquo;auto-save&rdquo; to behave. So it did it in the most conservative, aggressive and slow way of all: save everything on every keystroke!!!</p>
<p>There were <a href="https://github.com/akitaonrails/FrankMD/commit/76406744a3e268d7b96648a2a41c415e3781c1a4"target="_blank" rel="noopener">several other things</a> like not updating the HTML Preview if it wasn&rsquo;t open and things like that.</p>
<blockquote>
  <p>LLMs won&rsquo;t make performant code right off the bat; you need to tell them how!</p>

</blockquote>
<p>Performance is always a trade-off. For example, if I go as fast as possible, the rest may end up super insecure. If I crank up security too much, usability collapses. If I over-optimize, maintainability collapses. There&rsquo;s no way to make everything perfect: one will always negatively affect the other, and only an experienced programmer knows how to choose (or goes through trial and error until getting it right for the specific case).</p>
<blockquote>
  <p>Again, it&rsquo;s not the LLM&rsquo;s &ldquo;fault&rdquo;; this is normal in any project and we have to keep adjusting these things all the time.</p>

</blockquote>
<h3>Now really, reaching the end<span class="hx:absolute hx:-mt-20" id="now-really-reaching-the-end"></span>
    <a href="#now-really-reaching-the-end" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There&rsquo;s still plenty of technical debt left. The <a href="https://github.com/akitaonrails/FrankMD/blob/master/app/javascript/controllers/app_controller.js"target="_blank" rel="noopener">app_controller.js</a> file still has more than <strong>2,000 lines</strong>. And this is AFTER I spent a solid 3 hours refactoring just this one. Now there are roughly 28 JavaScript controllers, but before there was only a single file.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201183730_screenshot-2026-01-31_11-05-30.png" alt="app controller"  loading="lazy" /></p>
<p>Besides that, LLMs can&rsquo;t be consistent all the time, especially when the project code grows a lot. There was one type of feature it always did server-side, but later decided to do client-side. I had to interrupt and say: <em>&ldquo;hey, why?&rdquo;</em>, and then it redid it the right way:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201183531_screenshot-2026-02-01_10-49-54.png" alt="interrupt"  loading="lazy" /></p>
<p>Any code written with zero human assistance ALWAYS comes out at the worst possible code quality. With my assistance, I think the code is at least minimally presentable. You can check for yourselves at <a href="https://github.com/akitaonrails/FrankMD"target="_blank" rel="noopener">my GitHub repository</a>.</p>
<p>I recommend digging through the commits: at least one thing LLMs do well is document the history of what was modified in each commit, which makes it much easier to search later.</p>
<p>There was one bug where Claude got stuck in a loop, trying and failing, to figure out when my CSS broke out of nowhere. It changed something and asked me to test; I restarted the server, did a force reload, and nothing: broken.</p>
<p>It was Saturday night; I was tired and a bit feverish, but then the penny dropped. I had done something dumb earlier. I put &ldquo;notes/&rdquo; in the <code>.gitignore</code>, which was the directory where I was creating test Markdown files in the editor and didn&rsquo;t want to commit.</p>
<p>However, that also started ignoring directories like <code>app/views/notes/...</code>, for example. And neither I nor Claude knew this, but when I asked it to search, voilà:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260201184244_screenshot-2026-01-30_19-55-28.png" alt="tailwind error"  loading="lazy" /></p>
<blockquote>
  <p>Tailwind build obeys <code>.gitignore</code>, so it was screwing up the CSS compilation. &#x1f92c;</p>

</blockquote>
<p>When we fixed that, it started working, but by then 2 hours had gone by!</p>
<p>As I said, you have to stay sharp on the errors, watch why the LLM is doing so much trial and error, doing and undoing in the same place and getting nowhere. This isn&rsquo;t uncommon and can eat up half your day if you&rsquo;re not paying attention.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>When I started this Friday morning, I imagined:</p>
<blockquote>
  <p><em>&ldquo;Ah, a full day of work should be enough.&rdquo;</em></p>

</blockquote>
<p>And as always, every estimate is an underestimate. It&rsquo;s already Sunday, end of the day, and only now do I think I&rsquo;ve gotten close to version 1.0. It still isn&rsquo;t perfect, but I think it&rsquo;s at least minimally presentable.</p>
<p>Remember I did this at my pace of <strong>extreme hyperfocus</strong>, when I barely blink, my eyes dry out and I start to see everything blurry, and even my head starts to hurt a bit from staying so tense, concentrated on a single thing. It&rsquo;s not a state I recommend.</p>
<p>Even those 30 hours over 3 days, the right thing would be 30 hours spread over at least 5 days.</p>
<p>If I had let Claude do everything on its own, without checking anything, it would have taken half the time and, in the end, I&rsquo;d have code &ldquo;that works&rdquo;, but bizarre, with a single CSS file of around 2000 lines, a single JS file of around 8 thousand lines, everything with ZERO tests, and everything looking like a house of cards where you touch one small thing and break 10 other things without knowing why.</p>
<p>Overall, it was a good experiment. Knowing how and where to use them, LLMs are exceptional. As I said at the start, without the LLM I would have taken more than 200 hours to do it alone. Even going through several rough patches along the way, I only took 30 hours. So the LLM really did make me work at least 6 to 7 times faster, maybe more.</p>
<p>Finally I feel we&rsquo;ve reached the elusive <strong>&ldquo;10x Developer&rdquo;</strong>. All you need is to be a senior first.</p>
<p>To wrap up, a mini-report from <a href="https://github.com/XAMPPRocky/tokei"target="_blank" rel="noopener">Tokei</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Breakdown by language:
┌─────────────────┬───────┬────────┬────────┐
│    Language     │ Files │ Lines  │  Code  │
├─────────────────┼───────┼────────┼────────┤
│ JavaScript      │ 137   │ 26,621 │ 19,922 │
├─────────────────┼───────┼────────┼────────┤
│ Ruby            │ 60    │ 8,449  │ 5,883  │
├─────────────────┼───────┼────────┼────────┤
│ YAML            │ 10    │ 3,477  │ 2,867  │
├─────────────────┼───────┼────────┼────────┤
│ Ruby HTML (ERB) │ 33    │ 2,927  │ 2,634  │
├─────────────────┼───────┼────────┼────────┤
│ CSS             │ 29    │ 1,619  │ 1,297  │
├─────────────────┼───────┼────────┼────────┤
│ Markdown        │ 1     │ 716    │ -      │
├─────────────────┼───────┼────────┼────────┤
│ HTML            │ 5     │ 369    │ 329    │
├─────────────────┼───────┼────────┼────────┤
│ Dockerfile      │ 1     │ 88     │ 46     │
├─────────────────┼───────┼────────┼────────┤
│ Other           │ 6     │ 145    │ 76     │
└─────────────────┴───────┴────────┴────────┘
The codebase is primarily JavaScript (60%) and Ruby (17%) by code volume.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So, roughly <strong>33 thousand lines of code</strong>. This is a SMALL project! Big projects are 10 times bigger, with more than 300 thousand lines of code. I&rsquo;d guess that at an iFood or Mercado Livre, with all the subsystems, it&rsquo;s more than 1 million lines of code. Adjust your expectations!</p>
]]></content:encoded><category>frankmd</category><category>frank</category><category>llms</category><category>artificial-intelligence</category></item><item><title>Vibe Code: I Built a Markdown Editor From Scratch With Claude Code (FrankMD) PART 1</title><link>https://akitaonrails.github.io/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-1/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-1/</guid><pubDate>Sun, 01 Feb 2026 10:40:43 -0300</pubDate><description>&lt;p&gt;In the last &lt;a href="https://akitaonrails.com/en/2026/01/28/vibe-code-built-a-little-app-fully-with-glm-4-7-tv-clipboard/"target="_blank" rel="noopener"&gt;two&lt;/a&gt; &lt;a href="https://akitaonrails.com/2026/01/29/vibe-code-qual-llm-%c3%a9-a-melhor-vamos-falar-a-real/"target="_blank" rel="noopener"&gt;posts&lt;/a&gt; I just wanted to beat up on LLMs to show how hard it is to build something simple from scratch, let alone &lt;em&gt;&amp;ldquo;build GTA 6 from scratch&amp;rdquo;&lt;/em&gt;, as some idiots believe.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been saying this for years: I LIKE LLMs, I use them a lot since they came out, and I&amp;rsquo;ve never had a problem with them. My problem is calibrating the UNREALISTIC expectations of &amp;ldquo;normies&amp;rdquo;, non-programmers: you&amp;rsquo;re never going to get an app like this with a handful of prompts without knowing exactly which components and techniques to use underneath.&lt;/p&gt;</description><content:encoded><![CDATA[<p>In the last <a href="https://akitaonrails.com/en/2026/01/28/vibe-code-built-a-little-app-fully-with-glm-4-7-tv-clipboard/"target="_blank" rel="noopener">two</a> <a href="https://akitaonrails.com/2026/01/29/vibe-code-qual-llm-%c3%a9-a-melhor-vamos-falar-a-real/"target="_blank" rel="noopener">posts</a> I just wanted to beat up on LLMs to show how hard it is to build something simple from scratch, let alone <em>&ldquo;build GTA 6 from scratch&rdquo;</em>, as some idiots believe.</p>
<p>I&rsquo;ve been saying this for years: I LIKE LLMs, I use them a lot since they came out, and I&rsquo;ve never had a problem with them. My problem is calibrating the UNREALISTIC expectations of &ldquo;normies&rdquo;, non-programmers: you&rsquo;re never going to get an app like this with a handful of prompts without knowing exactly which components and techniques to use underneath.</p>
<p>This time, I decided to tackle another little problem that had been sitting on my todo-list for a while: having a self-hosted note-taking web app (to put on my <a href="/en/tags/homelab/">home-server</a>. Over the years I&rsquo;ve used everything: Evernote, Notion, Obsidian, among others, but none of them really pleased me. So why not build my own exactly the way I want?</p>
<p>I&rsquo;m going to split this topic into 2 articles: in this first one, I&rsquo;ll talk exclusively about my application; <a href="/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-2/">in the second one</a>, I&rsquo;ll talk about the process of vibe coding with Claude. Spoilers: it took 3 full days (roughly <strong>30 hours</strong>). 137 pull requests! Wait for the post-mortem.</p>
<h2>The Problem<span class="hx:absolute hx:-mt-20" id="the-problem"></span>
    <a href="#the-problem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Overall, this is just me being fussy. Keeping notes shouldn&rsquo;t be complicated: create a directory on your machine, open LazyVim, VSCode or whatever, done: edit the Markdown as pure code and be happy.</p>
<p>Anyway, recently I was just using Obsidian on a shared directory in my Dropbox. That way I could access it from my phone if I needed to. But it&rsquo;s a pain because if I&rsquo;m on another PC, I need to have Obsidian or some other editor installed. If I have plugins, I need to install those too.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_11-43-09.jpg" alt="obsidian"  loading="lazy" /></p>
<p>In practice any other text editor would do: they&rsquo;re just text files, but the inconsistency of having something different in each place, all misconfigured, demotivates me from writing.</p>
<p>In particular, I was using Obsidian and, later, LazyVim to edit my blog posts, which are also in Markdown. And here I have a bigger set of problems:</p>
<ul>
<li>there&rsquo;s a specific path and Hugo frontMatter; I have to be careful with the title to generate the right slug, etc.</li>
<li>there are images/screenshots that I keep manually uploading to S3, grabbing the URL and pasting into the post</li>
<li>I avoid using tables because &ldquo;drawing&rdquo; tables directly in Markdown is a pain</li>
<li>I keep Google Images, YouTube, etc. tabs open so I can keep grabbing links, alt-tab, and paste</li>
<li>there&rsquo;s no good preview, so I let a lot of little errors slip through (duplicated image url, for example)</li>
<li>there&rsquo;s no good spellchecker, so lots of little typos get through that I then have to keep fixing</li>
</ul>
<p>I wanted to solve all these problems in one place: a beautiful editor like Obsidian that would fix all these inconveniences and let me focus only on my text.</p>
<h2>FrankMD<span class="hx:absolute hx:-mt-20" id="frankmd"></span>
    <a href="#frankmd" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Frank Rosenblatt was the creator of the Perceptron in 1957, which gave rise to modern neural networks. Since I built this editor entirely with AI, I decided to pay tribute to Frank.</p>
<p>So I named it Frank Markdown or Frank Editor (fed). This editor has all the basics you would expect in any editor:</p>
<ul>
<li>syntax highlighting</li>
<li>HTML preview</li>
<li>markdown formatting helpers (Ctrl-B for bold, for example)</li>
<li>customize font family and size</li>
</ul>
<p>But then I started adding some things specific to my personal taste and/or that would make my life easier for editing my blog posts. Let&rsquo;s go.</p>
<h3>Image Insertion<span class="hx:absolute hx:-mt-20" id="image-insertion"></span>
    <a href="#image-insertion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This is an important part for me. When I write posts about code, I&rsquo;m always taking screenshots of my terminal or editor to illustrate. The process is a print screen that sends it to <code>~/Pictures/Screenhots</code>. I open Nautilus (I&rsquo;m on Linux) and run a little script I made to upload that image to a bucket on my AWS S3. I copy the generated link and add it to my post.</p>
<p>It&rsquo;s &ldquo;a little annoying&rdquo; doing this every time. On top of that, sometimes I&rsquo;d like to resize the image to make it more optimized. But that means going to the terminal and running <code>magick screenshot.png -resize 50%</code>, which is another manual step.</p>
<p>So I decided to make a dialog box with several options for this.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-03-02.jpg" alt="image dialog"  loading="lazy" /></p>
<p>By default, I can configure a main images directory on my PC and it will display a grid limited to the 10 most recent. Or I can filter by name. There&rsquo;s an option to link directly (in case I want to upload the image along with the html) or to serve from S3 and it will copy the generated link automatically (which is what I prefer).</p>
<p>Same thing for images on the Web (Google, Pinterest), where I can choose to download, resize and upload to S3:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-06-07.jpg" alt="bing images example"  loading="lazy" /></p>
<p>And the link will be in the expected format <code>...amazonaws.com/frankmd/...jpg</code>. All pretty, easy and practical! For me, this helps a lot!</p>
<p>It&rsquo;s an extra, since I couldn&rsquo;t skip integrating an AI; the last tab is for <a href="https://gemini.google/br/overview/image-generation/?hl=pt-BR"target="_blank" rel="noopener">Nano Banana</a>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-10-24.jpg" alt="nano banana generator"  loading="lazy" /></p>
<p>Nano Banana is really impressive. Look at what this prompt generated:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/ai_1769965759787.png" alt="nano banana generated image"  loading="lazy" /></p>
<p>Even more important: I can see the preview immediately (ctrl-shift-v) to know whether I inserted the right image (something I used to screw up a lot editing in NeoVim, without seeing it):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-13-29.jpg" alt="real time preview"  loading="lazy" /></p>
<p>For me, these image insertion helpers are the ones that are going to help me the most when editing my blog posts.</p>
<h3>Markdown Helpers<span class="hx:absolute hx:-mt-20" id="markdown-helpers"></span>
    <a href="#markdown-helpers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Simpler than an image is embedding a video player. I can either pass any link with a random .mp4 file, or I can directly search YouTube and embed it properly:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-12-34.jpg" alt="youtube searching"  loading="lazy" /></p>
<p>It will insert the correct HTML, <code>&lt;embed ...</code>, with the right URL and title.</p>
<p>I also built an easy way to add and edit tables:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-28-00.jpg" alt="spreadsheet editor"  loading="lazy" /></p>
<p>And it will generate the Markdown just right. Never again do I have to sit there editing that garbage by hand to readjust columns or rows.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">| Cabeçalho 1 | Cabeçalho 2 |
</span></span><span class="line"><span class="cl">| ----------- | ----------- |
</span></span><span class="line"><span class="cl">| Linguagem   | Nota        |
</span></span><span class="line"><span class="cl">| Ruby        | 10          |
</span></span><span class="line"><span class="cl">| JS          | 5           |
</span></span><span class="line"><span class="cl">| Rust        | 6 😂        |</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But if you want to do it manually, you can also do that:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-28-40.jpg" alt="code editor"  loading="lazy" /></p>
<p>I added this mostly because it would be weird to leave it out, but in general I prefer to write code blocks manually. But there it is for anyone who wants a visual mode.</p>
<p>Then, of course, it had to have an easy way to edit Emojis &#x1f606; (ctrl-shift-e):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-30-19.jpg" alt="emoji panel"  loading="lazy" /></p>
<p>But what nobody would expect is that I also offer support for <strong>Emoticons</strong>!!
☆:.｡.o(≧▽≦)o.｡.:*☆</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-30-40.jpg" alt="emoticon panel"  loading="lazy" /></p>
<h3>Customization<span class="hx:absolute hx:-mt-20" id="customization"></span>
    <a href="#customization" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Speaking of Emoticons, that reminds me that I wanted this editor to be infinitely customizable. For example, of course I added internationalization and it has Japanese!!</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-34-30.jpg" alt="japanese locale"  loading="lazy" /></p>
<p>And other languages too; go poke around later. It has Portuguese too!</p>
<blockquote>
  <p>&ldquo;What&rsquo;s the best font for programming?&rdquo;</p>

</blockquote>
<p>Who knows! That&rsquo;s why I added several to choose from:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_15-22-35.jpg" alt="fonts"  loading="lazy" /></p>
<p>Since I use <a href="/en/tags/omarchy/">Omarchy</a>, I also wanted my editor to support exactly the same themes:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-37-05.jpg" alt="temas"  loading="lazy" /></p>
<p>In the <a href="https://github.com/akitaonrails/FrankMD/blob/master/README.md"target="_blank" rel="noopener">README</a> everything that can be customized is documented. You can customize many things through environment variables and also through a <code>.fed</code> file, automatically created in the directory where you open this editor. There&rsquo;s plenty, so I recommend poking around.</p>
<h3>Focus on the Text<span class="hx:absolute hx:-mt-20" id="focus-on-the-text"></span>
    <a href="#focus-on-the-text" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviously I included a <em>&ldquo;Typewriter Mode&rdquo;</em> that keeps my cursor closer to the middle of the screen and centers the editor, while hiding all distractions.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-17-37.jpg" alt="typewriter mode"  loading="lazy" /></p>
<p>I normally prefer TUIs, but I have to admit that using a browser has the advantage of giving me access to extensions. The most useful one, without a doubt, is <a href="https://app.grammarly.com/"target="_blank" rel="noopener">Grammarly</a>, for any language:</p>
<p><a href="https://app.grammarly.com/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-20-04.jpg" alt="grammarly"  loading="lazy" /></a></p>
<p>But beyond that, I added an &ldquo;AI&rdquo; button in the toolbar (which is configurable via ENVs and a config file — read the <a href="https://github.com/akitaonrails/FrankMD/blob/master/README.md"target="_blank" rel="noopener">README</a>). And it does this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-22-28.jpg" alt="AI grammar check"  loading="lazy" /></p>
<p>It sends to your LLM of choice (local Ollama, OpenAI, Anthropic, OpenRouter, etc.) with a prompt telling it not to rewrite your text, only to check grammar, spelling and errors of that kind. Then I return a &ldquo;diff&rdquo; interface to compare and accept, edit or cancel.</p>
<p>I think Grammarly alone is already enough, but it cost nothing to build this one, so I built it.</p>
<h3>Hugo Blogging<span class="hx:absolute hx:-mt-20" id="hugo-blogging"></span>
    <a href="#hugo-blogging" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This blog is made with Hextra/Hugo. That&rsquo;s why I wanted to already bake in an easy way to create new articles in Hugo. It starts right from the option to create a new document:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-39-53.jpg" alt="new hugo note"  loading="lazy" /></p>
<p>And it already creates it with the frontMatter, automatic slug generation, timestamped subdirectories and all the rest:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_14-38-55.jpg" alt="hugo frontmatter"  loading="lazy" /></p>
<p>Like I said, this editor was built mainly to make my own workflow easier.</p>
<h2>Self-deploy vs CLI<span class="hx:absolute hx:-mt-20" id="self-deploy-vs-cli"></span>
    <a href="#self-deploy-vs-cli" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Originally I thought of this project as a Notion-light or something like that. A web editor that would deploy to my home server and serve only as an organizer, a place to put notes bigger than a post-it (like the history of my Linux configurations, step-by-step commands, etc.).</p>
<p>But as I kept building the app, more and more it grew and started looking like a desktop app. But by then it was too late. I wanted to use Ruby on Rails 8 (to shake off the rust and because I like it). Only halfway through did I think I should have done it in Node, because now I can&rsquo;t embed it in an Electron app anymore.</p>
<p>Fine, the idea is to be a Markdown editor for programmers. Every programmer has Docker. So I wrapped it in a little shell function and I can just land in any directory on my Linux and type:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ~/Projects/akitaonrails-hugo
</span></span><span class="line"><span class="cl">fed .</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Read the <a href="https://github.com/akitaonrails/FrankMD/blob/master/README.md#2-add-the-fed-function"target="_blank" rel="noopener">README</a> of the project to learn how to add it to your <code>.bashrc</code>. And doing it this way, it will work on my Omarchy exactly like any other native app:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_15-16-29.jpg" alt="omarchy web app"  loading="lazy" /></p>
<p>In reality, I&rsquo;ll use both ways: as if it were a native app. And as self-hosted, to organize a central place with my notes — which will be separate from where I organize my blog posts. So it really does have multiple uses.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve summarized the features; there are several little things you&rsquo;ll discover as you start using them. Everything is clickable; if you click on the app title, there&rsquo;s an About:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_15-21-03.jpg" alt="about"  loading="lazy" /></p>
<p>Right-click on top of selected text has a formatting submenu; Ctrl-I works for italic; <code>Ctrl-H</code> has a basic &ldquo;find and replace&rdquo;; there are a bunch of keyboard combinations to access things.</p>
<p>Oh right, I didn&rsquo;t even mention that you can quickly search files by name or by content using <code>Ctrl-P</code> and <code>Ctrl-Shift-F</code>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_15-24-36.jpg" alt="search filename"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-01_15-25-59.jpg" alt="search file content"  loading="lazy" /></p>
<p>To make sure everything is working, this very article was written entirely with my new editor. And I&rsquo;d say that, if it isn&rsquo;t perfect (for me), it&rsquo;s at 99%. Feel free to open Issues with bugs or features that would make sense — remembering: the idea is to stay away from replacing VSCode and having EVERYTHING. I need it to have only ENOUGH.</p>
<p>Considering it cost me <strong>3 days of work</strong> and that I didn&rsquo;t write a single line of code, the result is impressive. Yes, text editors are very well documented applications with tons of training material — that&rsquo;s why Claude managed to build it without trouble. Don&rsquo;t get too impressed. That said, yes, I wanted a custom editor for my personal use, one that would be beautiful to use, and it managed to deliver, so I&rsquo;m satisfied.</p>
<p>Don&rsquo;t miss <a href="/en/2026/02/01/frankmd-markdown-editor-vibe-code-part-2/">PART 2</a> for the details of how this was built.</p>
]]></content:encoded><category>frankmd</category><category>frank</category><category>llms</category><category>artificial-intelligence</category></item></channel></rss>