|
| 1 | +Coding instructions for all programming languages: |
| 2 | + |
| 3 | +- If no language is specified, assume the latest version of python. |
| 4 | +- If tokens or other secrets are needed, pull them from an environment variable |
| 5 | +- Prefer early returns over nested if statements. |
| 6 | +- Prefer `continue` within a loop vs nested if statements. |
| 7 | +- Prefer smaller functions over larger functions. Break up logic into smaller chunks with well-named functions. |
| 8 | +- Only add comments if the code is not self-explanatory. Do not add obvious code comments. |
| 9 | +- Do not remove existing comments. |
| 10 | +- When I ask you to write code, prioritize simplicity and legibility over covering all edge cases, handling all errors, etc. |
| 11 | +- When a particular need can be met with a mature, reasonably adopted and maintained package, I would prefer to use that package rather than engineering my own solution. |
| 12 | +- Never add error handling to recover gracefully from an error without being asked to do so. Fail hard and early with assertions and allowing exceptions to propagate whenever possible |
| 13 | +- When naming variables or functions, use names that describe the effect. For example, instead of `function handleClaimFreeTicket` (a function which opens a dialog box) use `function openClaimFreeTicketDialog`. |
| 14 | + |
| 15 | +Use line breaks to organize code into logical groups. Instead of: |
| 16 | + |
| 17 | +```python |
| 18 | +if not client_secret_id: |
| 19 | + raise HTTPException(status.HTTP_400_BAD_REQUEST) |
| 20 | +session_id = client_secret_id.split("_secret")[0] |
| 21 | +``` |
| 22 | + |
| 23 | +Prefer: |
| 24 | + |
| 25 | +```python |
| 26 | +if not client_secret_id: |
| 27 | + raise HTTPException(status.HTTP_400_BAD_REQUEST) |
| 28 | + |
| 29 | +session_id = client_secret_id.split("_secret")[0] |
| 30 | +``` |
| 31 | + |
| 32 | +**DO NOT FORGET**: keep your responses short, dense, and without fluff. I am a senior, well-educated software engineer, and do not need long explanations. |
| 33 | + |
| 34 | +### Agent instructions |
| 35 | + |
| 36 | +- When running python tests, use an already open terminal and the `pytest` binary. |
| 37 | +- If you added models, generate a migration with `just migration {add,delete,update}_model_other_description` |
| 38 | + |
| 39 | +## Fastapi |
| 40 | + |
| 41 | +- When throwing a `HTTPException`, do not add a `detail=` and use a named status code (`status.HTTP_400_BAD_REQUEST`) |
| 42 | +- Do not return a `dict`, instead create a `class RouteNameResponse` |
| 43 | + |
| 44 | +## Pytest Integration Tests |
| 45 | + |
| 46 | +- Look to tests/factories.py to generate any required database state |
| 47 | + - Here's an example of how to create + persist a factory `DistributionFactory.build(domain=PYTHON_TEST_SERVER_HOST).save()` |
| 48 | +- Add the `server` factory to each test |
| 49 | +- Use the `faker` factory to generate emails, etc. |
| 50 | +- Don't add obvious `assert` descriptions |
| 51 | + |
| 52 | +## Python App |
| 53 | + |
| 54 | +* Files within `app/commands/` should have: |
| 55 | + * Are not designed for CLI execution, but instead are interactor-style internal commands. |
| 56 | + * Should not be used on the queuing system |
| 57 | + * A `perform` function that is the main entry point for the command. |
| 58 | + * Look at existing commands for examples of how to structure the command. |
| 59 | + * Use `TypeIDType` for any parameters that are IDs of models. |
| 60 | +* Files within `app/jobs/` should have: |
| 61 | + * Are designed for use on the queuing system. |
| 62 | + * A `perform` function that is the main entry point for the job. |
| 63 | + * Look at existing jobs for examples of how to structure the job. |
| 64 | + * Use `TypeIDType | str` for any parameters that are IDs of models. |
| 65 | +* When referencing a command, use the full-qualified name, e.g. `app.commands.transcript_deletion.perform`. |
| 66 | +* When queuing a job or `perform`ing it in a test, use the full-qualified name, e.g. `app.jobs.transcript_deletion.perform`. |
| 67 | + |
| 68 | +## Python Route Tests |
| 69 | + |
| 70 | +- Polyfactory is the [factory](tests/factories.py) library in use. `ModelNameFactory.build()` is how you generate factories. |
| 71 | +- Use `assert_status(response)` to check the response of a client |
| 72 | + |
| 73 | +## Python |
| 74 | + |
| 75 | +When writing Python: |
| 76 | + |
| 77 | +* Assume the latest python, version 3.13. |
| 78 | +* Prefer Pathlib methods (including read and write methods, like `read_text`) over `os.path`, `open`, `write`, etc. |
| 79 | +* Prefer modern typing: `list[str]` over `List[str]`, `dict[str, int]` over `Dict[str, int]`, etc. |
| 80 | +* Use Pydantic models over dataclass or a typed dict. |
| 81 | +* Use SQLAlchemy for generating any SQL queries. |
| 82 | +* Use `click` for command line argument parsing. |
| 83 | +* Use `log.info("the message", the_variable=the_variable)` instead of `log.info("The message: %s", the_variable)` or `print` for logging. This object can be found at `from app import log`. |
| 84 | + * Log messages should be lowercase with no leading or trailing whitespace. |
| 85 | + * No variable interpolation in log messages. |
| 86 | + * Do not coerce database IDs or dates to strings |
| 87 | +* Do not fix import ordering or other formatting issues. |
| 88 | + |
| 89 | +### Date & DateTime |
| 90 | + |
| 91 | +* Use the `whenever` library for datetime + time instead of the stdlib date library. `Instant.now().format_common_iso()` |
| 92 | + |
| 93 | +### Database & ORM |
| 94 | + |
| 95 | +When accessing database records: |
| 96 | + |
| 97 | +* SQLModel (wrapping SQLAlchemy) is used |
| 98 | +* `Model.one(primary_key)` or `Model.get(primary_key)` should be used to retrieve a single record |
| 99 | +* Do not manage database sessions, these are managed by a custom tool |
| 100 | + * Use `TheModel(...).save()` to persist a record |
| 101 | + * Use `TheModel.where(...).order_by(...)` to query records. `.where()` returns a SQLAlchemy select object that you can further customize the query. |
| 102 | + |
| 103 | +When writing database models: |
| 104 | + |
| 105 | +* Don't use `Field(...)` unless required (i.e. when specifying a JSON type for a `dict` or pydantic model using `Field(sa_type=JSONB)`). For instance, use `= None` instead of `= Field(default=None)`. |
| 106 | +* Add enum classes close to where they are used, unless they are used across multiple classes (then put them at the top of the file) |
| 107 | +* Use `ModelName.foreign_key()` when generating a foreign key field |
| 108 | +* Store currency as an integer, e.g. $1 = 100. |
| 109 | + |
| 110 | +Example: |
| 111 | + |
| 112 | +```python |
| 113 | +class Distribution( |
| 114 | + BaseModel, TimestampsMixin, SoftDeletionMixin, TypeIDMixin("dst"), table=True |
| 115 | +): |
| 116 | + """Triple-quoted strings for multi-line class docstring""" |
| 117 | + |
| 118 | + date_field_with_comment: datetime | None = None |
| 119 | + "use a string under the field to add a comment about the field" |
| 120 | + |
| 121 | + # no need to add a comment about an obvious field; no need for line breaks if there are no field-level docstrings |
| 122 | + title: str = Field(unique=True) |
| 123 | + state: str |
| 124 | + |
| 125 | + optional_field: str | None = None |
| 126 | + |
| 127 | + # here's how relationships are constructed |
| 128 | + doctor_id: TypeIDType = Doctor.foreign_key() |
| 129 | + doctor: Doctor = Relationship() |
| 130 | + |
| 131 | + @computed_field |
| 132 | + @property |
| 133 | + def order_count(self) -> int: |
| 134 | + return self.where(Order.distribution_id == self.id).count() |
| 135 | +``` |
| 136 | + |
| 137 | +## React Router |
| 138 | + |
| 139 | +- You are using the latest version of React Router (v7). |
| 140 | +- The primary export in a routes file should specify `loaderData` like `export default function RouteNamePage({ loaderData }: Route.ComponentProps)`. `loaderData` is the return value from `clientLoader`. |
| 141 | +- Use `href("/products/:id", { id: "abc123" })` to generate a url path for a route managed by the application. |
| 142 | + - Look at [routes.ts](mdc:web/app/routes.ts) to determine what routes and path parameters exist. |
| 143 | +- Use `export async function clientLoader(loaderArgs: Route.ClientLoaderArgs)` to define a `clientLoader` on a route. |
| 144 | +- Do not define `Route.*` types, these are autogenerated and can be imported from `import type { Route } from "./+types/routeFileName"` |
| 145 | +- If URL parameters or query string values need to be checked before rendering the page, do this in a `clientLoader` and not in a `useEffect` |
| 146 | +- Never worry about generating types using `pnpm` |
| 147 | +- Use [`<AllMeta />`](web/app/components/shared/AllMeta.tsx) instead of MetaFunction or individual `<meta />` tags |
| 148 | +- Use the following pattern to reference query string values (i.e. `?theQueryStringParam=value`) |
| 149 | + |
| 150 | +```typescript |
| 151 | +const [searchParams, _setSearchParams] = useSearchParams(); |
| 152 | +// searchParams contains the value of all query string parameters |
| 153 | +const queryStringValue = searchParams.get("theQueryStringParam") |
| 154 | +``` |
| 155 | + |
| 156 | +### Loading Mock Data |
| 157 | + |
| 158 | +Don't load mock data in the component function with `useEffect`. Instead, load data in a `clientLoader`: |
| 159 | + |
| 160 | +```typescript |
| 161 | + |
| 162 | +// in mock.ts |
| 163 | +export async function getServerData(options: any) { |
| 164 | + // ... |
| 165 | +} |
| 166 | + |
| 167 | +// in web/app/routes/**/*.ts |
| 168 | +export async function clientLoader(loaderArgs: Route.ClientLoaderArgs) { |
| 169 | + // no error reporting is needed, this will be handled by the `getServerData` |
| 170 | + // mock loading functions should return result in a `data` key |
| 171 | + const { data } = await getServerData({ /* ... */ }) |
| 172 | + |
| 173 | + // the return result here is available in `loaderData` |
| 174 | + return data |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +### How to use clientLoader |
| 179 | + |
| 180 | +- `export async function clientLoader(loaderArgs: Route.ClientLoaderArgs) {` |
| 181 | +- Load any server data required for page load here, not in the component function. |
| 182 | +- Use `return redirect(href("/the/url"))` to redirect users |
| 183 | +- Use [getQueryParam](web/app/lib/utils.ts) to get query string variables |
| 184 | +- `throw new Response` if you need to mimic a 400, 500, etc error |
| 185 | +- `loaderArgs` and all sub-objects are all fully typed |
| 186 | +- `loaderArgs.params.id` to get URL parameters |
| 187 | + |
| 188 | +### Using API Data |
| 189 | + |
| 190 | +- `~/configuration/client` re-exports all types and functions from `client/*`. Import from `~/configuration/client` instead of anything you find in the `client/` folder/package. |
| 191 | +- For each API endpoint, there's a fully typed async function that can be used to call it. Never attempt to call an API endpoint directly. |
| 192 | +- When using an import from `~/configuration/client`: |
| 193 | + - use `body:` for request params |
| 194 | + - always `const { data, error } = await theCall()` |
| 195 | + |
| 196 | +## React |
| 197 | + |
| 198 | +- You are using the latest version of React (v19) |
| 199 | +- Do not write any backend code. Just frontend logic. |
| 200 | +- If a complex skeleton is needed, create a component function `LoadingSkeleton` in the same file. |
| 201 | +- Store components for each major page or workflow in `app/components/$WORKFLOW/$COMPONENT.tsx`. |
| 202 | + - If a single page has more than two dedicated components, create a subfolder `app/components/$WORKFLOW/$PAGE/$COMPONENT.tsx` |
| 203 | +- Use lowercase dash separated words for file names. |
| 204 | +- Use React 19, TypeScript, Tailwind CSS, and ShadCN components. |
| 205 | +- Prefer function components, hooks over classes. |
| 206 | +- Use ShadCN components in `web/app/components/ui` as your component library. If you need new components, ask for them. |
| 207 | + - Never edit the `web/components/ui/*.tsx` files. |
| 208 | + - You can find a list of components here https://ui.shadcn.com/docs/components |
| 209 | +- Break up large components into smaller components, but keep them in the same file unless they can be generalized. |
| 210 | +- Put any "magic" strings like API keys, hosts, etc into a "constants.ts" file. |
| 211 | +- For React functional components with three or fewer props, always inline the prop types as an object literal directly in the function signature after the destructured parameters (e.g., `function Component({ prop1, prop2 }: { prop1: string; prop2?: number }) { ... })`. Include default values in destructuring and mark optional props with ? in the type object. Do not use separate interfaces or type aliases; keep types inline. For complex types, add inline comments if needed. |
| 212 | +- Put the interface definition right above the related function |
| 213 | +- Internally, store all currency values as integers and convert them to floats when rendering visually |
| 214 | +- When building forms use React Hook Form. |
| 215 | +- Include a two line breaks between any `useHook()` calls and any `useState()` definitions for a component. |
| 216 | +- When using a function prop inside a `useEffect`, please use a pattern that avoids including the function in the dependency array, like the `useRef` trick.s |
| 217 | +- Use the following pattern to reference query string values (i.e. `?theQueryStringParam=value`): |
| 218 | + |
| 219 | +```typescript |
| 220 | +const [searchParams, _setSearchParams] = useSearchParams(); |
| 221 | +// searchParams contains the value of all query string parameters |
| 222 | +const queryStringValue = searchParams.get("theQueryStringParam") |
| 223 | +``` |
| 224 | + |
| 225 | +### Mock Data |
| 226 | + |
| 227 | +- For any backend communication, create mock responses. Use a async function to return mock data that I will swap out later for a async call to an API. |
| 228 | +- When creating mock data, always specify it in a dedicated `web/app/mock.ts` file |
| 229 | +- Load mock data using a react router `clientLoader`. Use the Skeleton component to present a loading state. |
| 230 | + |
| 231 | +### React Hook Form |
| 232 | + |
| 233 | +Follow this structure when generating a form. |
| 234 | + |
| 235 | +```tsx |
| 236 | + |
| 237 | +// add a mock function simulating server communication |
| 238 | +async function descriptiveServerSendFunction(values: any) { |
| 239 | + const mockData = getMockReturnData(/* ... */) |
| 240 | + return new Promise(resolve => setTimeout(() => resolve(mockData), 500)); |
| 241 | +} |
| 242 | + |
| 243 | +const formSchema = z.object({ |
| 244 | + field_name: z.string(), |
| 245 | + // additional schema definition |
| 246 | +}) |
| 247 | + |
| 248 | +const form = useForm<z.infer<typeof formSchema>>({ |
| 249 | + resolver: zodResolver(formSchema), |
| 250 | +}) |
| 251 | + |
| 252 | +async function onSubmit(values: z.infer<typeof formSchema>) { |
| 253 | + // ... |
| 254 | + await descriptiveSendFunction(values) |
| 255 | + // ... |
| 256 | +} |
| 257 | + |
| 258 | +return ( |
| 259 | + <Form {...form}> |
| 260 | + <form onSubmit={form.handleSubmit(onSubmit)}> |
| 261 | + {/* form fields */} |
| 262 | + </form> |
| 263 | + </Form> |
| 264 | +) |
| 265 | +``` |
| 266 | + |
| 267 | +## Shell |
| 268 | + |
| 269 | +- Assume zsh for any shell scripts. The latest version of modern utilities like ripgrep (rg), fdfind (fd), bat, httpie (http), zq (zed), jq, procs, rsync are installed and you can request I install additional utilities. |
| 270 | + |
| 271 | +## Typescript |
| 272 | + |
| 273 | +- Use `pnpm`, not `npm` |
| 274 | +- Node libraries are not available |
| 275 | +- Use `lib/` for generic code, `utils/` for project utilities, `hooks/` for React hooks, and `helpers/` for page-specific helpers. |
| 276 | +- Prefer `function theName() {` over `const theName = () =>` |
| 277 | +- Use `import { invariant } from @epic-web/invariant` instead of another invariant library |
| 278 | + |
| 279 | +Here's how frontend code is organized in `web/app/`: |
| 280 | + |
| 281 | +- `lib/` not specific to the project. This code could be a separate package at some point. |
| 282 | +- `utils/` project-specific code, but not specific to a particular page. |
| 283 | +- `helpers/` page- or section-specific code that is not a component, hook, etc. |
| 284 | +- `hooks/` react hooks. |
| 285 | +- `configuration/` providers, library configuration, and other setup code. |
| 286 | +- `components/` react components. |
| 287 | + - `ui/` reusable ShadCN UI components (buttons, forms, etc.). |
| 288 | + - `shared/` components shared across multiple pages. |
| 289 | + - create additional folders for route- or section-specific components. |
| 290 | + |
0 commit comments