My First Vibe Coding Experience: Building lf.nvim
The Itch
I use Lingua Franca -- a polyglot coordination language for concurrent and distributed systems. The official tooling lives in VSCode. I live in Neovim. That gap had been bugging me for a while.
On November 22, 2025, I sat down and decided to do something about it. Not by grinding through documentation for weeks, but by vibing -- just me and Claude, talking through what I wanted, watching code materialize, course-correcting in real time.
I had no idea it would turn into a 4,500+ line plugin in a matter of days.

Day One: The Initial Burst
The first commit landed at 4:48 PM: "Initial commit: LF file manager integration for Neovim."
By 7:39 PM -- less than three hours later -- there were already 8 commits. The plugin had gone from zero to:
- Auto-building diagram dependencies on first use
- Click-to-source navigation in interactive diagrams
- Support for arbitrarily nested reactor hierarchies
- Instant, flicker-free navigation using programmatic LSP requests
- Graceful handling of external/imported reactors
That last one is interesting. My first attempt at diagram-to-source navigation worked, but the cursor visibly flickered -- it would jump to an instance declaration, then to the type name, then trigger go-to-definition. Three hops. I mentioned it felt janky. Within minutes, Claude rewrote the navigation to make a single programmatic LSP request behind the scenes -- one instant jump, no intermediate positions visible to the user. The kind of polish that would normally get deferred to "later."
The Architecture That Emerged
The most ambitious piece was the interactive diagram viewer. The VSCode extension uses KLighD to render reactor diagrams with a Sprotty-based protocol. Getting that same experience in a terminal editor meant building a three-tier system:
Browser (React/KLighD) <-> WebSocket <-> Node.js Sidecar <-> RPC <-> Neovim <-> LSP
A browser renders the diagrams. A Node.js sidecar routes messages. Neovim injects file context and talks to the LF Language Server. Click a reactor in the browser, and your cursor jumps to its definition in Neovim.
We got the protocol wrong at first -- sending diagram/accept as a command instead of a notification. We figured out the bounds computation handshake (requestBounds -> computedBounds -> setModel) by reading the Sprotty source. These are the kinds of protocol details that would take a solo developer hours of trial and error to nail down. With vibe coding, we'd try something, I'd report what happened, and Claude would adjust.
Day Two: Reactive Updates and SSH Support
The next day, the plugin got reactive diagram updates -- switch files in Neovim, and the browser diagram auto-refreshes. We landed on a clean URL architecture: the browser just hits http://localhost:8765/ with no parameters, and Neovim injects the current file URI dynamically.
I work over SSH sometimes, so we added a no_browser mode that just prints the URL for you to port-forward. Made that the default, actually. Small decision, but it shows how vibe coding handles edge cases naturally -- I mentioned my SSH workflow, and the feature just appeared.
Day Five: Tree-Sitter
By November 27, the plugin had a custom tree-sitter grammar. Not just basic highlighting -- full support for embedded language blocks. Write target C in your LF file, and the C code blocks get proper C syntax highlighting. Same for Python, TypeScript, Rust, and C++.
The tree-sitter work also lived in its own repo (tree-sitter-lf), with query files for highlights, locals, text objects, folds, and indents. The :LFTSInstall command handles everything -- detecting your parser directory, compiling if needed, installing queries.
Week Two: Fine-Grained Navigation
On December 1, we pushed further. Diagram navigation could now jump to specific ports and reactions, not just reactor instances. This meant parsing KLighD's element ID format -- things like $$P0 for ports and _reaction_1 for reactions -- and matching them to source locations by index. It required understanding that KLighD uses 0-indexed ports but 1-indexed reactions, and that the LSP doesn't expose reactions as symbols, so we had to search the source text directly.
Months Later: CI/CD
On February 8, 2026, the final session automated the remaining manual steps. A CI workflow now builds the LSP jar from upstream releases. A weekly sync checks the VSCode extension's TextMate grammar for new keywords and opens a PR if anything changes. The :LFLspInstall command downloads a pre-built jar so users never have to clone the full Lingua Franca repository.
The Numbers
- 34 commits over ~11 weeks
- 4,573 lines of Lua across 16 modules
- 3 tiers of architecture (browser, sidecar, Neovim)
- First commit to working diagrams: 3 hours
- First commit to tree-sitter support: 5 days
What Vibe Coding Actually Feels Like
It's not "AI writes code for me." It's more like pair programming with someone who has infinite patience and encyclopedic knowledge of APIs you've never used. I knew what I wanted. I knew the Lingua Franca ecosystem. Claude knew Lua, Neovim APIs, tree-sitter internals, LSP protocol details, and TypeScript/React patterns.
The rhythm was: I describe what I want. Code appears. I try it. I report what happened. Refinements happen immediately. The feedback loop is minutes, not hours.
The .claude/ directory in my workspace tells the story -- session notes, architecture docs, quick references -- all generated during development. It's not just code that emerged from the vibe. It's a knowledge base that makes it possible to pick up right where I left off, even months later.
The thing that surprised me most: the code is good. It's modular. It handles edge cases. The 975-line diagram_klighd.lua has clean separation between ID parsing, LSP navigation, and UI concerns. That's not because I architected it carefully upfront. It's because vibe coding lets you iterate fast enough that good structure emerges naturally from solving real problems.
Would I Do It Again?
I already have. But that first time -- watching a full-featured Neovim plugin come together in an afternoon, with interactive diagrams, LSP integration, tree-sitter highlighting, and CI/CD automation -- that changed how I think about what one person can build.
The plugin is at github.com/remifan/lf.nvim if you're a fellow Lingua Franca + Neovim user. And if you're not -- maybe the real takeaway is: that tool you've been wishing existed? The gap between "I wish" and "I shipped" is shorter than you think.