slate
A private place to save what I want to watch and find the right thing when the question is, “what now?”
About slate
I had five years of Letterboxd history, a Notes app full of recommendations, and no good answer when I actually sat down to watch something. Letterboxd was good at movies and the social layer around them. I wanted movies and TV in one quiet place. I wanted a shelf, not another feed.
Slate started with a smaller question: what is the least this product has to remember? A title, whether I want to watch it, whether I am watching it, whether I finished it, and how I felt afterward. No follower graph. No reviews to perform for strangers.
The first usable version took two weekends. Then I kept using it. That is where the real project began.

A living project
Two weekends describes the prototype, not the product that is live now. The first version was single-user and self-hosted. The current version has hosted accounts, isolated libraries, public profiles, custom lists, direct ordering, richer discovery, and the original self-host path.
I still use Slate, so the product keeps changing when I hit a rough edge. This case study is a record of the decisions that survived, not a promise that every screen will stay put.
- 01Personal prototypeOne library, three states, built over two weekends.
- 02Portable productCSV import, Docker self-hosting, and a real data path.
- 03Hosted slateAccounts, optional sharing, and a product other people can use.
A proper walkthrough is next
I am recording a new walkthrough for the current build. It will cover the shelf, AI discovery, imports, profiles, and the parts of the workflow that are hard to explain with still images.
The rules I started with
The shelf comes first
Up next, Watching, and Watched are the three places I need when I open the app. They describe what I can do with a title right now. Everything else has to earn space around them.
Search should end with a saved title
The fastest path is still Cmd+K, a few letters, and Enter. Discovery can get as clever as it wants, but adding a title should stay boring and quick.
Private is the starting state
A library belongs to the person making it. Hosted accounts keep each library separate, public profiles are opt-in, and the self-hosted version still lets someone run the whole thing on their own machine.
Bring the old library with you
A new tracker is useless if it asks me to abandon the last five years. Import was not a launch extra. It was part of making the first session worth having.
The work changed as the product did
AI belongs where search already lives
The model can interpret the request. It cannot invent the shelf.
The first AI version was a natural-language filter. You could ask for "cozy autumn mysteries" or "A24 horror after 2020" and get a useful grid. That part worked. The trouble started when the model tried to sound helpful and named titles from memory that were not in the returned catalogue results.
I kept the AI mode inside the same command palette as regular search, then tightened what happens behind it. There are now three real paths: browse by description, find titles similar to a named film or show, or recommend from the person's watched library. Every title the assistant mentions has to come back from that turn's catalogue result. If it is not in the result, the assistant cannot name it.
That rule mattered more than making the reply sound clever. It also made the interface easier to trust because the words and the poster rail cannot drift apart.


The library should look like the main event
An early home screen mixed saved titles with trending rows and recommendations. The content was useful, but the hierarchy was wrong. My own list looked like one module among several.
The current page lets the library finish before discovery begins. Saved titles, filters, and direct ordering sit together. A clear break introduces recommendations, trending titles, and films in theaters. The product can help me find something without making my shelf feel rented from an algorithm.
Sentiment beat another rating scale
I remember loving a film. I do not remember giving it 4.2 stars.
Personal star ratings felt like homework, and I never went back to read them. Slate uses Loved, Liked, and Disliked because that is closer to how I recall something months later.

The numbers come from somewhere else. IMDb, Rotten Tomatoes, and Metacritic scores are fetched and cached when a title is saved. They give me a reference point without asking me to grade every film with fake precision.
Import came before polish
My own library was the test case: five years of Letterboxd data that I was not going to type again. Slate accepts Letterboxd and Trakt CSV exports, maps their slightly different columns, matches rows against TMDB, skips duplicates, and keeps the watched date when it exists.
Trakt exports added a messier case. A show can arrive as one row per episode, so the importer has to collapse "Breaking Bad: Season 1: Pilot" and the rest of the season back into a single show before matching it. Migration work is not glamorous, but it is the reason the product had real data on day one.

The episode tracker got polished, then deleted
For TV shows I built a small episode chip, a season picker, progress math, optimistic updates, and a shortcut for moving to the next episode. I kept refining the interaction until it was tidy. It was also becoming a second product inside Slate.
In May I removed the UI and 496 lines of code. The database columns are still there, unused. Shipping a feature is not a reason to keep it. The delete was one of the clearer product decisions in the whole project.
Single-user became private by default
Slate began as a tool I could run for myself. When other people wanted their own copy, I first made the whole stack self-hostable with Docker. Later I added the hosted path: Google sign-in, one isolated Postgres library per account, editable profiles, and a read-only public shelf when someone chooses to share it.
I kept both paths. The hosted product is easier to enter. The self-hosted product keeps the original promise that the data can stay on infrastructure the owner controls.
How I worked with AI
I started with a short PRD written with Claude as a thinking partner. It named the three states, the search behavior, the import path, and the things Slate would not do. I designed the main screens in Figma and pulled references for unfamiliar patterns such as command palettes and dense title pages.
That preparation made the coding agents useful. They were fast when the task had a hard edge: wire this Server Action, move the data layer from one Postgres client to another, keep the loading state the same height as the real view. They were much less useful when the problem was product judgment.
The repository history makes that split obvious. The AI search needed several rounds because the model ignored required tool calls, returned unreleased titles, or narrated a plausible answer instead of using the catalogue. I framed the failure, tightened the contract, tested it again, and kept the parts that behaved.
The same thing happened with the episode tracker. AI made it cheap to build and cheap to polish. It did not make the feature worth keeping. That call was still mine.
AI shortened the distance between a framed decision and working software. It did not decide what Slate should become.
What stuck with me
A personal tool becomes a product at the edges
Search and poster grids were the obvious work. Imports, account isolation, empty states, stale deploys, and public links were what made Slate usable beyond my laptop.
Grounding matters more than personality in AI features
A charming recommendation is worthless if the title is made up or missing from the result set. The reliable version came from constraining the model, not giving it a better voice prompt.
Code has no right to stay because it was expensive
The episode tracker survived several polish passes and still got cut. The product got clearer the moment I stopped treating effort as evidence.
What is next
Slate is live at s1ate.space. It is free, private by default, and still MIT licensed. I am also laying the shared API and native foundations for iOS and Android, without turning the web app into a waiting room for them.
The new walkthrough will land in the space above. The product will probably change again before the video does.