SillageFragrance Vault
What it does for you
A fragrance collection manager with note-derived bottle cards, a wear diary, cost-per-wear stats, and an explainable what-to-wear-today picker.

Screenshots

In more detail
Sillage is a private, in-browser vault for a fragrance collection. Every bottle gets a card colored by its own notes, a wear diary, and real cost-per-wear numbers. A "what to wear today" picker ranks your shelf by season, occasion, and weather, and shows the reason behind every point.
Honest limits
Everything lives in localStorage, which is the wrong place for photos, so there are none. The weather-to-family table is hard-coded, not learned. Hemisphere and currency are fixed. It runs and it is tested, but it has not been lived with.
How I built it
The project list asked for a CRUD app, and I wanted to prove the basics (create, edit, filter, save, import, export) on a subject I actually care about. So the record had to be richer than a to-do item: a top, heart, and base notes pyramid, longevity, sillage, bottle size with a fill level, price, status, and a wear log.
It was one of the parallel Claude Code builders from the September 19 one-shot night. I set the scope, the five views, the gradient-from-notes idea, and one rule: a suggestion must never be unexplained. Claude Code wrote vault.js first, then 158 assertions, then the UI that imports the same module. It took headless Chrome screenshots and fixed what they exposed: stat labels running inline, a donut caption wider than the hole, and low-contrast words in the dark theme. The builder was interrupted before its log entry, so a finishing pass re-ran the tests and completed the write-up. Logged time was about half an hour.
What I learned
- Visuals made from data are only testable if the function is pure and has a stable tie-break.
familyProfile()weights the pyramid (top 1, heart 1.5, base 2), sorts by weight and then alphabetically, and only then can the tests assert the exact gradient CSS string. - The weighting matters. On the sample shelf, equal counting gave woody 23 percent and amber 17; weighting by layer gave woody 27 and amber 21. Modest, but it moves toward the base notes you smell most of the day.
- A ranking function is easier to trust when it returns its reasons. Testing that a specific reason with a specific point value is present pins each rule on its own, so tuning one later cannot silently break another.
- Nested lists fit in CSV if each layer gets its own delimiter (commas, pipes, colons). That only works because wear dates are trimmed to date-only strings with no colons.
- Demo data should live in memory. One flag checked inside
persist()means a screenshot run can never overwrite a real collection.
What carried forward
This is one of only three projects in my first 30 whose code got imported by a later build (the others are #11, whose QR code module #75 uses, and #16, whose bullet scorer #86 uses). On September 23, project #92 (Fragrance Recommender, Drydown) imports vault.js directly from this folder: the note vocabulary, the record shape, familyProfile, and the wear-today scorer. Its server even serves this folder read-only so the browser can load it. A small T0 core written on day one of the comeback became the foundation for a bigger build four days later. It also used the standard serve.mjs on port 5126 and the tested-core pattern from #9 through #11.
If I took it further
I would learn the weather table from the wear log itself, based on what I actually rated well in the heat, and add photo upload per bottle stored in IndexedDB.
#026 Built with Claude Code September 20, 2026 158 automated checks