Skip to content

Instantly share code, notes, and snippets.

@Tim-Machine
Last active February 19, 2025 04:33
Show Gist options
  • Save Tim-Machine/80af2be71411fb73c66262c565074fa9 to your computer and use it in GitHub Desktop.
Save Tim-Machine/80af2be71411fb73c66262c565074fa9 to your computer and use it in GitHub Desktop.
Here are some key things that I've found working with Cursor that have really helped take it to the next level.

Lessons learned with Cursor

Here are some key things that I've found working with Cursor that have really helped take it to the next level.

  • Ensure that you have a well written .cursorrules file. Cursor Directory has some great starting points.
  • The @Docs is crazy powerful. Cursor may not have indexed the version of the dependencies required. Using @SomePackageDocs vastly improve the accuarcy of code generation related to a depenency.
  • @web is also a very strong tool, but doesn't get added by default when using Cursor's composer. While you may not always need @web, its very useful for more obscure implementations.

Prompting

The most important thing by far is cearly articulating what you want to do. The more well defined your requirements the less deviation from the request will happen. I've added a prompt example.

Think of talking to the LLM like your talking to a evil genie. It's going to find the best way it can think of to do exactly what you asked it to do, this includes taking short cuts and doing the bare minimum required to solve your problem. Specificity is your friend and ally. While you might have to write longer prompts you are still creating far less keystrokes then writing code typically.

Flow of thought - Ensuring Consistency

Cursor is superpowerful, but that doesn't mean that it knows everything or understands your intent 100% of the time. What I've been doing to combat this is a concept of feature.spec file. These typically live within a directory directly related to a specfic feature and is used to when I want to modify existing functionality across multiple files.

Why is that important?

I've found that only using the composer with no guard rails, LLMs can get a bit agressive in how changes are made and in some cases can remove existing functionality unexpectedly. These guard rails ensure that all requirements are met even when doing complex changes.

How to use the specfile

If you are using the Cursor composer be sure to either add the file or @TwitterFeature.spec to insure that the requirements are there, then clearly defined the changes you'd like to make. Example

After making making changes I highly suggest updating your specfile. This can be done via the composer with @spectfile - update the spec file based on the changes made

Lets say I wanted to modify the existing TwitterUser type to include a new property called lastLogin. First I'd open the composer(full screen) and give it the following prompt @TwitterFeature.spec - modify add {lastLogin:string} to the TwitterUser type. Once I approve the changes that are made the next prompt I'd run would be @TwitterFeature.spec - update the spec file to account for the changes to the TwitterUserType

Warnings/Concerns

  • Sometimes LLMs/cusor like to be a cheeky little fuck. This is very true when your doing very complex changes where the context window is quite large - sometimes you wil LLMs/Cursor replace existing functionality with // exiting functionality - I kid you not. Be aware of this.
  • Writing test seems to be a wild card. I'm still trying to find the best way to write test to do unit testing. E2E testing is even more complex, I don't suggest it right now.

Exmaple prompt to write unit test.

Write a unit test using Jest to test the generateFakeTwitterMessages functionality. You should test all positive and negative test possible. This includes srings, arrays, objects, numbers or anything else you can think of.

  • Testing IMO is much more important now that when writing code by hand. LLMS like to take short cuts and this may alter existing code to implement the changes you have asked it make. Example
  • Review the changes before approving them. image in this next image you can see that it made changes that I didn't directly requestimage And here is a unexpected change where ./index.ts exports have changed image

Testing Prompt

Write a unit test using Jest to test the generateFakeTwitterMessages functionality. You should test all positive and negative test possible. This includes srings, arrays, objects, numbers or anything else you can think of.

import { generateFakeTwitterMessages } from './index';
import { TwitterMessage } from './types';

// Positive Test: Generating a specific number of fake messages
test('generateFakeTwitterMessages generates the correct number of messages', () => {
  const count = 5;
  const messages = generateFakeTwitterMessages(count);
  expect(messages).toHaveLength(count);
});

// Positive Test: Validating the structure of a generated message
test('generateFakeTwitterMessages generates messages with the correct structure', () => {
  const count = 1;
  const messages = generateFakeTwitterMessages(count);
  const message: TwitterMessage = messages[0];

  expect(message).toHaveProperty('user');
  expect(message.user).toHaveProperty('id');
  expect(message.user).toHaveProperty('avatar');
  expect(message.user).toHaveProperty('displayName');
  expect(message.user).toHaveProperty('followers');

  expect(message).toHaveProperty('message');
  expect(message).toHaveProperty('createdAt');
  expect(message).toHaveProperty('id');
  expect(message).toHaveProperty('likes');
  expect(message).toHaveProperty('shares');
  expect(message).toHaveProperty('bookmarks');
});

// Negative Test: Generating messages with non-numeric inputs
test('generateFakeTwitterMessages throws an error for non-numeric input', () => {
  const invalidInputs = ['a', null, undefined, {}, [], true];

  invalidInputs.forEach(input => {
    expect(() => generateFakeTwitterMessages(input as any)).toThrow();
  });
});

// Negative Test: Generating messages with negative numbers
test('generateFakeTwitterMessages returns an empty array for negative input', () => {
  const count = -5;
  const messages = generateFakeTwitterMessages(count);
  expect(messages).toHaveLength(0);
});

// Negative Test: Generating messages with zero as input
test('generateFakeTwitterMessages returns an empty array for zero input', () => {
  const count = 0;
  const messages = generateFakeTwitterMessages(count);
  expect(messages).toHaveLength(0);
});```

Prompt Examples

I'm going to provide a couple of exmaples and the results to show the impact that prompting may have.

Low Effort Prompt

Define a type for a twitter message, then write a function to make 10 of them and put them in an array.

// Define the type for a Twitter message
type TwitterMessage = {
    id: number;
    user: string;
    content: string;
    timestamp: Date;
};

// Function to generate 10 Twitter messages
function generateTwitterMessages(): TwitterMessage[] {
    const messages: TwitterMessage[] = [];

    for (let i = 1; i <= 10; i++) {
        messages.push({
            id: i,
            user: `user${i}`,
            content: `This is message number ${i}`,
            timestamp: new Date(),
        });
    }

    return messages;
}

Higher Effort Prompt

Create a new types called "TwitterMessages", "TwitterUser". A twitter message should have the following properties {user:TwitterUser, message:string, createdAt:TimeStamp, id:uuid, likes:number, shares:number, bookmarks:number}. Stub out 10 twittermessages with 3 unique users.

// TypeScript type for Twitter User
interface TwitterUser {
  id: string
  username: string
  displayName: string
  profileImage: string
}

// TypeScript type for Twitter Message
interface TwitterMessage {
  user: TwitterUser
  message: string
  createdAt: Date
  id: string
  likes: number
  shares: number
  bookmarks: number
}

// Stub data
const users: TwitterUser[] = [
  {
    id: uuidv4(),
    username: 'user1',
    displayName: 'User One',
    profileImage: 'https://example.com/user1.jpg',
  },
  {
    id: uuidv4(),
    username: 'user2',
    displayName: 'User Two',
    profileImage: 'https://example.com/user2.jpg',
  },
  {
    id: uuidv4(),
    username: 'user3',
    displayName: 'User Three',
    profileImage: 'https://example.com/user3.jpg',
  },
]

const twitterMessages: TwitterMessage[] = [
  {
    user: users[0],
    message: 'Message from user 1',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 10,
    shares: 2,
    bookmarks: 1,
  },
  {
    user: users[1],
    message: 'Message from user 2',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 5,
    shares: 1,
    bookmarks: 0,
  },
  {
    user: users[2],
    message: 'Message from user 3',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 8,
    shares: 3,
    bookmarks: 2,
  },
  {
    user: users[0],
    message: 'Another message from user 1',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 15,
    shares: 4,
    bookmarks: 2,
  },
  // Add more TwitterMessage objects to satisfy the requirement of 10 messages
  {
    user: users[1],
    message: 'Another message from user 2',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 2,
    shares: 1,
    bookmarks: 0,
  },
  {
    user: users[2],
    message: 'Yet another message from user 3',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 3,
    shares: 0,
    bookmarks: 1,
  },
  {
    user: users[0],
    message: 'More messages from user 1',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 25,
    shares: 7,
    bookmarks: 5,
  },
  {
    user: users[1],
    message: 'More messages from user 2',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 12,
    shares: 3,
    bookmarks: 1,
  },
  {
    user: users[2],
    message: 'More messages from user 3',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 18,
    shares: 5,
    bookmarks: 3,
  },
  {
    user: users[0],
    message: 'Once more a message from user 1',
    createdAt: new Date(),
    id: uuidv4(),
    likes: 30,
    shares: 10,
    bookmarks: 7,
  },
]

Twitter Feature Spec

Below is an example of a possible spec file. The goal is to define the requirements for the feature in isolation.

# Twitter Spec file

* use index.ts at the root to export everything
* Types should be defined in the './types.ts' file
* Code should be broken down into logical units of testable code. 
* Major concepts should be placed into their own file.

# Feature Requirements
* Create the followings types : 
-- TwitterMessage : {user:TwitterUser, message:string, createdAt:TimeStamp, id:uuid, likes:number, shares:number, bookmarks:number}
-- TwitterUser: {id:uuid, avatar:string, displayName:string, followers:number}
* Create a method to generate fake twitter messages, should accept a parameter of number of messages to create and should return and array of TwitterMessages.

Output from the above spec file.

types.ts

// Define the type for a Twitter user
export interface TwitterUser {
  id: string; // UUID
  avatar: string;
  displayName: string;
  followers: number;
}

// Define the type for a Twitter message
export interface TwitterMessage {
  user: TwitterUser;
  message: string;
  createdAt: Date;
  id: string; // UUID
  likes: number;
  shares: number;
  bookmarks: number;
}

index.ts

import { TwitterUser, TwitterMessage } from './types';

// Helper function to generate a UUID
function generateUUID(): string {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
  });
}

// Helper function to generate a random Twitter user
function generateFakeUser(): TwitterUser {
  return {
      id: generateUUID(),
      avatar: `https://randomuser.me/api/portraits/men/${Math.floor(Math.random() * 100)}.jpg`,
      displayName: `User_${Math.floor(Math.random() * 1000)}`,
      followers: Math.floor(Math.random() * 1000),
  }
}

// Function to generate fake Twitter messages
export function generateFakeTwitterMessages(count: number): TwitterMessage[] {
  const messages: TwitterMessage[] = [];
  for (let i = 0; i < count; i++) {
      const user = generateFakeUser();
      messages.push({
          user,
          message: `This is a fake message #${i + 1}`,
          createdAt: new Date(),
          id: generateUUID(),
          likes: Math.floor(Math.random() * 100),
          shares: Math.floor(Math.random() * 100),
          bookmarks: Math.floor(Math.random() * 100),
      });
  }
  return messages;
}

demo.ts

import { generateFakeTwitterMessages } from './index';

// Generate 5 fake Twitter messages
const fakeMessages = generateFakeTwitterMessages(5);

// Print fake messages
console.log(fakeMessages);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment