DEV Community

Trey Hutcheson
Trey Hutcheson

Posted on

More Cursor Lessons

I have been using this blog to capture things I've learned as I've tried to develop Vibe Coding skills. This is another such post, but with some specifics that hopefully can help someone else.

Supervision Required

I cannot stress this enough - supervise whatever your agent is putting out. That's kind of anthetical to the core concept of "vibe coding", where you aren't even paying attention to the code. But you have to.

I just spent two entire days refactoring code I've teased out of Cursor over the past month. That was not a pleasant experience.

Cursor Rules

I learned this one the hard way - you absolutely need to leverage cursor rules. Spend some time familiarizing yourself with how they function, and browse rules that others have put together. I've come across the following while watching videos or just googling:

  • awesome-cursorrules - Github repo. Lots to choose from, will require time to pick rules that fit your situation. Some of the instructions are out of date.
  • playbooks - Great interface for finding/filtering useful rules.
  • cursor.directory - Searchable directory that also includes MCP servers

I had been putting off configuring meaningful rules, partly due to laziness, but mostly because I wanted to learn the tool (Cursor) and become comfortable with my workflow before I started changing that workflow. A couple of weeks ago I actually selected a set of rules and added them to my environment ... but I didn't do it correctly so the rules were never leveraged.

TypeScript Without Types - Why Bother?

A few days ago I was looking through some of my data access code and I noticed a complete lack of type definitions. Like ... anywhere. I'm embarrassed to admit that it took me several weeks to realize that I was basically just working with javascript. There were literally zero type definitions, anywhere. In my front end, or my backend. Not in function signatures or any variable declarations. There was not a single custom type or interface definition, or any type paraemterized functions. What's the point?

What's when I realized that my cursor rules were not being used. So for the time being, I've implemented a project rule that basically says "this is typescript - do typescript type things." I haven't generated much code since I've added this rule, so I'm not yet sure how effective this specific rule is.

Setup You Toolchain Up Front

This should be obvious, but take the time to properly configure your toolchain up front; that includes setting up your linter/formatter (such as eslint or clippy), if applicable . When I finally got around to configuring eslint for my front end, I had ... over 300 errors. Three. Hundred. Errors.

This project stared as a hobby project, but over the past couple of weeks its become much more serious. Given its roots as a hobby, I didn't invest time in early warnings/validations, or the rules. That was plainly a mistake.

I still don't have any CI/CD set up yet. No CD because there's nothing to deploy and nowhere to deploy it. And no CI just because I've spent 100% of my time coding. CI is much less important as a solo dev; I can run validation locally. But it still needs to happen.

Leverage Cursor for Refactoring

Sure, Cursor can generate code (that's the entire point right?), but use it to iterate on what's already there. For example, my backend had several sections that needed database transactions. Each of these had their own try/catch/finally/BEGIN/ROLLBACK/COMMIT implementation, which was all redundant. Yes I was aware of the redundancy as the code was being generated, but I was favoring feature throughput; I knew I'd need to revsit that.

When I made the decision to de-dupe that functionality, I described my intentions to Cursor and it was able generate a serviceable database transaction pattern. It was also able to refactor all of the existing code to follow this new pattern, so that was cool. However, it really tripped up when attempting to refactor the unit tests. When I say tripped I should say completely fell on its face. I eventually told Cursor to throw out the current unit tests and to reimplement them based on the latest code. That was much smoother.

Manual Refactoring Can Be Painful

My codebase is still really small; about 2,000 lines of typescript in the backend, and 4,000 lines of typescript/tsx in the front end. This is /src only, not including tests. When I realized I needed to fully implement types, and when I saw all those linter errors, I decided to stop implementing functionality and to refactor the entire project.

I am way out of Cursor fast requests, so all my agent requests are taking forever. I decided I needed to do a lot of the type related heavy lifting myself. Cursor's built in agent assistance was really helpful here; it was able to anticipate many of the changes I wanted to make. And even with that assistance, implementing full type safety into the front end's screens and navigation was just such a huge lift. I probably spent 12 hours on that alone yesterday.

One situation in particular was very painful. This is my first real experience with React, and I didn't know how to pass typed arguments through navigation to different screens. Cursor suggested a couple of different options, I selected one, and implemented that across all my screens (~15 as of right now). When I thought I was done, and had worked through the bulk of the linter errors, I ran into a type incompatibility issue I didn't have the expertise to resolve. When I asked Cursor for assistance, it's response was basically "the way you're doing it no longer works after React Navigation 6". I wanted to toss my laptop. When Cursor originally presented the various options, it made no mention of them being version dependent. That was an utter waste of 3-ish hours. Not to mention that my package.json is right there.

Summary

Be an active participant; be an active code reviewer. I know the entire point of "vibe coding" is to ignore the code, but I just don't think that's possible. All of the youtube videos of people doing this stuff gives one the impression that it's fire-and-forget, or that one just accepts whatever is generated. I don't see how that could produce anything sustainable.

Furthermore, do some of the leg work up front. Choose your cursor rules, configure your toolchain, etc. Make sure that the code you're actually getting has some minimum quality (and I'm not talking about testing here).

Additionally, don't abandon fundamental software development principals. Be empirical. Refactor often. Implement good patterns.

Top comments (0)