Vibecode Mymind
track this build5 phases, 15 steps, beginner friendly0%The mechanic is honestly simple: capture a thing, extract text and metadata, ask a model for tags, embed it, then search across everything. An agent can build that in a weekend with SQLite, a headless browser for page snapshots and one model API call per item. What you cannot one-shot is the capture surface, which is the entire product in practice: browser extensions for three browsers, an iOS and Android share sheet, and the reliability that makes you trust it with the thought you had in a queue. The taste also matters more than it should here, because the pitch is that you never organize anything, and a DIY version that mis-tags half your library quietly becomes a junk drawer. Fine build, real gaps.
You are building a lean indie version of Mymind. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Mymind · indie build A local library that swallows links, images, screenshots and notes in one action, extracts what it can from each, derives tags and a one-line description, and serves a visual grid with search that covers extracted text, derived tags and the text inside images. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite and full text search | one person's library, where retrieval quality matters far more than scale | | Extraction | Readability for pages, text recognition for images | a screenshot is mostly text and useless without it | | Enrichment | A model producing tags and a description, applied once per item | derived rather than asked for, which is what removes the filing step | | Capture | A bookmarklet, a share endpoint and a watched folder | three routes because the friction of capture decides whether the tool survives | | Interface | A masonry grid with lazy loading | you recognise a saved image faster than you recall its title, which is why this is visual | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the capture endpoints, extraction and search. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A text recognition engine** · free - Why: Screenshots are the majority of what a library like this holds and are invisible to search without it. - Get it: Install Tesseract with the language data you need, or use a platform recognition interface if one is available to you. - Verify: A screenshot of text returns its content - [ ] **A few hundred real things you have saved** · free - Why: Search quality and grid performance are both meaningless against generated data, and the vague queries that matter only exist against your own material. - Get it: Import from wherever you currently save things: browser bookmarks, a screenshots folder, a notes application. - Verify: At least three hundred real items are imported - [ ] **A model for tags and descriptions, hosted or local** (optional) · pay per token; tagging an item is a fraction of a cent - Why: Phase 3 only. Capture, extraction and search all work with no model, which is a legitimate configuration. - Get it: A provider key with a spend limit, or a local model if you would rather nothing left the machine. - Verify: A captured item receives tags and a description - [ ] **Ten things you remember but cannot name precisely** · free - Why: Phase 4's real benchmark. Exact-match testing tells you nothing about the retrieval this tool exists for. - Get it: Write down ten items you know are in your library described the way you would actually search for them. - Verify: The ten queries are recorded with their expected items ## Quick start ```sh npm install @mozilla/readability jsdom ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. - Real browser extensions and a mobile share sheet, so capture friction goes way up - Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality - Sync across devices, plus offline capable native apps - Serendipity features: the everything-search, the surfacing of old cards, spaced resurfacing - Someone else's ongoing judgment about what a good tag actually is If one of those is essential to you, that is the reason to keep paying for Mymind, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Mymind The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private place to dump links, images and notes that organises itself so you never file anything to replace Mymind. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The premise is that you never organise. No folders, no required tags, no filing step at capture. Every tool in this category dies the same way: it asks for a decision at the moment of capture, capture becomes work, and within a month you stop. So capture is one action with no fields, and everything that makes an item findable is derived afterwards. Tags are suggested and correctable but never required, and search must work well enough that a thing you saved a year ago and cannot describe still comes back. That puts the whole burden on retrieval, which is where the effort belongs. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite and full text search · one person's library, where retrieval quality matters far more than scale - Extraction: Readability for pages, text recognition for images · a screenshot is mostly text and useless without it - Enrichment: A model producing tags and a description, applied once per item · derived rather than asked for, which is what removes the filing step - Capture: A bookmarklet, a share endpoint and a watched folder · three routes because the friction of capture decides whether the tool survives - Interface: A masonry grid with lazy loading · you recognise a saved image faster than you recall its title, which is why this is visual ### Data model (create this before Phase 1) - items(id, kind, url, title, description, captured_at, source, colour_hex) · kind is link, image, note or highlight - content(item_id, extracted_text, ocr_text, extracted_at, extractor_version) - assets(item_id, path, kind, width, height, bytes, thumb_path) - tags(id, name), item_tags(item_id, tag_id, source, confirmed) · source is derived or manual - items_fts · full text over title, description, extracted text and recognised text - A derived tag is a suggestion until confirmed or corrected, and a correction is never overwritten by a later derivation. ### Phase 1 · Capture in one action Build: Three capture routes: a bookmarklet posting the current page, an endpoint accepting a shared URL or image from a phone, and a watched folder for screenshots. None of them asks for a title, a tag or a folder. Trap: Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. Done when: Saving from the bookmarklet takes one click with no dialogue. Dropping a screenshot into the watched folder captures it without any prompt. A shared URL from a phone captures through the endpoint. No capture route asks for a title, tag or folder. Do not build yet: No extraction, tagging or search. ### Phase 2 · Getting text out of everything Build: Extract article text from pages and run text recognition over images and screenshots. Store both separately with the extractor version. Record failures rather than storing an empty extraction. Trap: A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. Done when: A screenshot of a page of text is findable by a phrase inside it. An article saved from the bookmarklet is findable by a phrase in its body. An image with no readable text is marked rather than silently unsearchable. The extractor version is recorded so a later improvement can re-run. Do not build yet: No tagging, grid or search interface yet. ### Phase 3 · Tags derived, never demanded Build: Send extracted text to a model once per item and store suggested tags and a one-line description. Let a tag be confirmed, corrected or removed. A correction is permanent and is never overwritten by a later derivation. Trap: Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. Done when: Saving an item produces tags and a description without any input. Correcting a tag persists through a later derivation run. Removing a derived tag keeps it removed. An item whose extraction failed gets no invented tags rather than plausible guesses. Do not build yet: No grid or search interface yet. ### Phase 4 · Retrieval, which is where the effort belongs Build: Search across title, description, extracted text, recognised text and tags together. Filter by kind, by colour and by date. Test against a library of thousands of items including things you genuinely cannot name. Trap: Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. Done when: A query matching only text inside an image returns that image. Search across ten thousand items returns in well under a hundred milliseconds. Filtering by kind and date narrows correctly. Ten queries describing content you remember vaguely return the right item in the first screen for most of them. Do not build yet: No visual grid polish or export yet. ### Phase 5 · A grid you actually browse Build: A masonry grid with generated thumbnails, lazy loading and stable layout so nothing jumps as images load. Group by capture date. Make opening an item instant from the stored copy rather than fetching the original. Trap: Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. Done when: Scrolling a library of ten thousand items stays smooth with stable memory. Nothing shifts position as images load, because dimensions are known in advance. Opening an item is instant from the stored copy with no network request. Thumbnails are generated at capture rather than on scroll. Do not build yet: No export or sharing yet. ### Phase 6 · Privacy, export and keeping it yours Build: Export everything as a folder of files with a Markdown index per item. Authenticate any hosted deployment. State clearly whether extracted text is sent to a model and offer a local option. Trap: A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. Done when: Export produces a folder of original files with a Markdown index per item carrying tags and description. A hosted deployment requires authentication. The interface states whether text leaves the machine for enrichment. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - The tagging quality of the hosted product, which is tuned against many users' libraries and corrections. - Mobile applications with a native share sheet, which is where most capture actually happens and where the endpoint is a poorer substitute. - Visual similarity search, which needs image embeddings and is a substantially larger piece of work than text search. - Anything social. This is deliberately single user, which is also what makes the privacy story simple. ### README must contain - That capture asks for nothing and everything findable is derived afterwards - That text is recognised in images, because screenshots are most of what gets saved - That corrected tags are permanent and never overwritten by derivation - That search covers recognised text, so an image is findable by its content - Whether extracted text is sent to a model, and how to run enrichment locally ===== AGENTS.md ===== # Agent instructions · Mymind indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with SQLite and full text search, Readability for pages, text recognition for images, A model producing tags and a description, applied once per item, A bookmarklet, a share endpoint and a watched folder, A masonry grid with lazy loading. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ===== BUILD_PLAN.md ===== # Build plan · Mymind A local library that swallows links, images, screenshots and notes in one action, extracts what it can from each, derives tags and a one-line description, and serves a visual grid with search that covers extracted text, derived tags and the text inside images. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture in one action Saving anything takes one action and asks for nothing. ### Steps 1. Write the bookmarklet Posts the URL, title and any selection. No form, no confirmation dialogue. Files: `public/bookmarklet.js` 2. Watch a screenshot folder With a settle check, so a file still being written is not captured half formed. Files: `src/watch.js` 3. Accept shares from a phone An endpoint taking a URL or an image. The phone is where most captures happen. Files: `src/capture.js` ### Done when - [ ] The bookmarklet saves in one click with no dialogue - [ ] A dropped screenshot captures without a prompt - [ ] A shared URL from a phone captures - [ ] No route asks for a title, tag or folder ### Watch out - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. ## Phase 2 · Getting text out of everything A screenshot is as findable as a note, because its text was read. ### Steps 1. Extract page text Readability, sanitised so the stored copy makes no outbound requests when viewed. Files: `src/extract.js` ```sh npm install @mozilla/readability jsdom ``` 2. Recognise text in images Every image, not only ones that look like screenshots. The distinction is not reliable. Files: `src/ocr.js` 3. Mark unreadable items Rather than letting them vanish from search. They are the ones you will look for and not find. Files: `src/extract.js` ### Done when - [ ] A screenshot is findable by a phrase inside it - [ ] An article is findable by a phrase in its body - [ ] An image with no readable text is marked - [ ] The extractor version is recorded ### Watch out - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. ## Phase 3 · Tags derived, never demanded Items acquire tags without you filing anything, and your corrections stick. ### Steps 1. Derive once per item On capture, from extracted text. Not on a schedule over the whole library. Files: `src/enrich.js` 2. Mark manual edits Corrected and removed tags are permanent decisions and are excluded from derivation. Files: `src/tags.js` 3. Refuse to guess without text An item with no extraction gets no tags. Invented tags are worse than none. Files: `src/enrich.js` ### Done when - [ ] Saving produces tags and a description with no input - [ ] A corrected tag survives a later derivation - [ ] A removed tag stays removed - [ ] An item without extraction gets no invented tags ### Watch out - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. ## Phase 4 · Retrieval, which is where the effort belongs Something you saved a year ago and cannot describe still comes back. ### Steps 1. Index everything extracted Title, description, page text, recognised text and tags in one index. Files: `src/search.js` 2. Add non-text filters Kind, date and dominant colour. Colour is surprisingly effective for images. Files: `src/filters.js` 3. Test with vague queries Ten things you half remember. This is the real benchmark rather than exact matches. Files: `eval/queries.json` ### Done when - [ ] A phrase inside an image returns that image - [ ] Search over ten thousand items is under a hundred milliseconds - [ ] Kind and date filters narrow correctly - [ ] Most vague queries return the right item on the first screen ### Watch out - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. ## Phase 5 · A grid you actually browse Scrolling the library is fast and you recognise things by sight. ### Steps 1. Generate thumbnails at capture Not on scroll. Doing it lazily is why these grids stutter. Files: `src/thumbs.js` 2. Give tiles known dimensions Stored at capture so the layout is stable before images load. Files: `src/grid.js` 3. Open from the stored copy No network request. The archive is the point and it must work offline. Files: `src/item.js` ### Done when - [ ] Ten thousand items scroll smoothly with stable memory - [ ] Nothing shifts as images load - [ ] Opening an item makes no network request - [ ] Thumbnails exist at capture time ### Watch out - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ## Not in this build - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. ## After v1, if you want it - Visual similarity search using image embeddings, which is the natural next retrieval mode - A weekly resurfacing of older items, since a library nobody revisits is a landfill - Deduplication of the same page saved twice from different sources ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Items, extracted text and tags. A revealing dataset, so treat backups accordingly. DB_PATH=./data/library.db # Required. Original images and stored page copies, plus thumbnails. ASSETS_DIR=./data/assets # Required. Folder watched for screenshots. Files here are copied rather than moved. WATCH_DIR=~/Desktop/Screenshots # Required · secret. Used by the bookmarklet and the share endpoint. Scoped to capture only. CAPTURE_TOKEN=replace-with-a-random-token # Required. Which model derives tags. Local means nothing leaves the machine. ENRICH_PROVIDER=local # Required. Thumbnail size. Large enough to recognise, small enough to scroll thousands. THUMB_LONG_EDGE=600
You are building a lean indie version of Mymind. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Mymind · indie build A local library that swallows links, images, screenshots and notes in one action, extracts what it can from each, derives tags and a one-line description, and serves a visual grid with search that covers extracted text, derived tags and the text inside images. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite and full text search | one person's library, where retrieval quality matters far more than scale | | Extraction | Readability for pages, text recognition for images | a screenshot is mostly text and useless without it | | Enrichment | A model producing tags and a description, applied once per item | derived rather than asked for, which is what removes the filing step | | Capture | A bookmarklet, a share endpoint and a watched folder | three routes because the friction of capture decides whether the tool survives | | Interface | A masonry grid with lazy loading | you recognise a saved image faster than you recall its title, which is why this is visual | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the capture endpoints, extraction and search. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A text recognition engine** · free - Why: Screenshots are the majority of what a library like this holds and are invisible to search without it. - Get it: Install Tesseract with the language data you need, or use a platform recognition interface if one is available to you. - Verify: A screenshot of text returns its content - [ ] **A few hundred real things you have saved** · free - Why: Search quality and grid performance are both meaningless against generated data, and the vague queries that matter only exist against your own material. - Get it: Import from wherever you currently save things: browser bookmarks, a screenshots folder, a notes application. - Verify: At least three hundred real items are imported - [ ] **A model for tags and descriptions, hosted or local** (optional) · pay per token; tagging an item is a fraction of a cent - Why: Phase 3 only. Capture, extraction and search all work with no model, which is a legitimate configuration. - Get it: A provider key with a spend limit, or a local model if you would rather nothing left the machine. - Verify: A captured item receives tags and a description - [ ] **Ten things you remember but cannot name precisely** · free - Why: Phase 4's real benchmark. Exact-match testing tells you nothing about the retrieval this tool exists for. - Get it: Write down ten items you know are in your library described the way you would actually search for them. - Verify: The ten queries are recorded with their expected items ## Quick start ```sh npm install @mozilla/readability jsdom ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. - Real browser extensions and a mobile share sheet, so capture friction goes way up - Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality - Sync across devices, plus offline capable native apps - Serendipity features: the everything-search, the surfacing of old cards, spaced resurfacing - Someone else's ongoing judgment about what a good tag actually is If one of those is essential to you, that is the reason to keep paying for Mymind, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Mymind The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private place to dump links, images and notes that organises itself so you never file anything to replace Mymind. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The premise is that you never organise. No folders, no required tags, no filing step at capture. Every tool in this category dies the same way: it asks for a decision at the moment of capture, capture becomes work, and within a month you stop. So capture is one action with no fields, and everything that makes an item findable is derived afterwards. Tags are suggested and correctable but never required, and search must work well enough that a thing you saved a year ago and cannot describe still comes back. That puts the whole burden on retrieval, which is where the effort belongs. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite and full text search · one person's library, where retrieval quality matters far more than scale - Extraction: Readability for pages, text recognition for images · a screenshot is mostly text and useless without it - Enrichment: A model producing tags and a description, applied once per item · derived rather than asked for, which is what removes the filing step - Capture: A bookmarklet, a share endpoint and a watched folder · three routes because the friction of capture decides whether the tool survives - Interface: A masonry grid with lazy loading · you recognise a saved image faster than you recall its title, which is why this is visual ### Data model (create this before Phase 1) - items(id, kind, url, title, description, captured_at, source, colour_hex) · kind is link, image, note or highlight - content(item_id, extracted_text, ocr_text, extracted_at, extractor_version) - assets(item_id, path, kind, width, height, bytes, thumb_path) - tags(id, name), item_tags(item_id, tag_id, source, confirmed) · source is derived or manual - items_fts · full text over title, description, extracted text and recognised text - A derived tag is a suggestion until confirmed or corrected, and a correction is never overwritten by a later derivation. ### Phase 1 · Capture in one action Build: Three capture routes: a bookmarklet posting the current page, an endpoint accepting a shared URL or image from a phone, and a watched folder for screenshots. None of them asks for a title, a tag or a folder. Trap: Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. Done when: Saving from the bookmarklet takes one click with no dialogue. Dropping a screenshot into the watched folder captures it without any prompt. A shared URL from a phone captures through the endpoint. No capture route asks for a title, tag or folder. Do not build yet: No extraction, tagging or search. ### Phase 2 · Getting text out of everything Build: Extract article text from pages and run text recognition over images and screenshots. Store both separately with the extractor version. Record failures rather than storing an empty extraction. Trap: A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. Done when: A screenshot of a page of text is findable by a phrase inside it. An article saved from the bookmarklet is findable by a phrase in its body. An image with no readable text is marked rather than silently unsearchable. The extractor version is recorded so a later improvement can re-run. Do not build yet: No tagging, grid or search interface yet. ### Phase 3 · Tags derived, never demanded Build: Send extracted text to a model once per item and store suggested tags and a one-line description. Let a tag be confirmed, corrected or removed. A correction is permanent and is never overwritten by a later derivation. Trap: Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. Done when: Saving an item produces tags and a description without any input. Correcting a tag persists through a later derivation run. Removing a derived tag keeps it removed. An item whose extraction failed gets no invented tags rather than plausible guesses. Do not build yet: No grid or search interface yet. ### Phase 4 · Retrieval, which is where the effort belongs Build: Search across title, description, extracted text, recognised text and tags together. Filter by kind, by colour and by date. Test against a library of thousands of items including things you genuinely cannot name. Trap: Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. Done when: A query matching only text inside an image returns that image. Search across ten thousand items returns in well under a hundred milliseconds. Filtering by kind and date narrows correctly. Ten queries describing content you remember vaguely return the right item in the first screen for most of them. Do not build yet: No visual grid polish or export yet. ### Phase 5 · A grid you actually browse Build: A masonry grid with generated thumbnails, lazy loading and stable layout so nothing jumps as images load. Group by capture date. Make opening an item instant from the stored copy rather than fetching the original. Trap: Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. Done when: Scrolling a library of ten thousand items stays smooth with stable memory. Nothing shifts position as images load, because dimensions are known in advance. Opening an item is instant from the stored copy with no network request. Thumbnails are generated at capture rather than on scroll. Do not build yet: No export or sharing yet. ### Phase 6 · Privacy, export and keeping it yours Build: Export everything as a folder of files with a Markdown index per item. Authenticate any hosted deployment. State clearly whether extracted text is sent to a model and offer a local option. Trap: A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. Done when: Export produces a folder of original files with a Markdown index per item carrying tags and description. A hosted deployment requires authentication. The interface states whether text leaves the machine for enrichment. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - The tagging quality of the hosted product, which is tuned against many users' libraries and corrections. - Mobile applications with a native share sheet, which is where most capture actually happens and where the endpoint is a poorer substitute. - Visual similarity search, which needs image embeddings and is a substantially larger piece of work than text search. - Anything social. This is deliberately single user, which is also what makes the privacy story simple. ### README must contain - That capture asks for nothing and everything findable is derived afterwards - That text is recognised in images, because screenshots are most of what gets saved - That corrected tags are permanent and never overwritten by derivation - That search covers recognised text, so an image is findable by its content - Whether extracted text is sent to a model, and how to run enrichment locally ===== AGENTS.md ===== # Agent instructions · Mymind indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with SQLite and full text search, Readability for pages, text recognition for images, A model producing tags and a description, applied once per item, A bookmarklet, a share endpoint and a watched folder, A masonry grid with lazy loading. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ===== BUILD_PLAN.md ===== # Build plan · Mymind A local library that swallows links, images, screenshots and notes in one action, extracts what it can from each, derives tags and a one-line description, and serves a visual grid with search that covers extracted text, derived tags and the text inside images. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture in one action Saving anything takes one action and asks for nothing. ### Steps 1. Write the bookmarklet Posts the URL, title and any selection. No form, no confirmation dialogue. Files: `public/bookmarklet.js` 2. Watch a screenshot folder With a settle check, so a file still being written is not captured half formed. Files: `src/watch.js` 3. Accept shares from a phone An endpoint taking a URL or an image. The phone is where most captures happen. Files: `src/capture.js` ### Done when - [ ] The bookmarklet saves in one click with no dialogue - [ ] A dropped screenshot captures without a prompt - [ ] A shared URL from a phone captures - [ ] No route asks for a title, tag or folder ### Watch out - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. ## Phase 2 · Getting text out of everything A screenshot is as findable as a note, because its text was read. ### Steps 1. Extract page text Readability, sanitised so the stored copy makes no outbound requests when viewed. Files: `src/extract.js` ```sh npm install @mozilla/readability jsdom ``` 2. Recognise text in images Every image, not only ones that look like screenshots. The distinction is not reliable. Files: `src/ocr.js` 3. Mark unreadable items Rather than letting them vanish from search. They are the ones you will look for and not find. Files: `src/extract.js` ### Done when - [ ] A screenshot is findable by a phrase inside it - [ ] An article is findable by a phrase in its body - [ ] An image with no readable text is marked - [ ] The extractor version is recorded ### Watch out - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. ## Phase 3 · Tags derived, never demanded Items acquire tags without you filing anything, and your corrections stick. ### Steps 1. Derive once per item On capture, from extracted text. Not on a schedule over the whole library. Files: `src/enrich.js` 2. Mark manual edits Corrected and removed tags are permanent decisions and are excluded from derivation. Files: `src/tags.js` 3. Refuse to guess without text An item with no extraction gets no tags. Invented tags are worse than none. Files: `src/enrich.js` ### Done when - [ ] Saving produces tags and a description with no input - [ ] A corrected tag survives a later derivation - [ ] A removed tag stays removed - [ ] An item without extraction gets no invented tags ### Watch out - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. ## Phase 4 · Retrieval, which is where the effort belongs Something you saved a year ago and cannot describe still comes back. ### Steps 1. Index everything extracted Title, description, page text, recognised text and tags in one index. Files: `src/search.js` 2. Add non-text filters Kind, date and dominant colour. Colour is surprisingly effective for images. Files: `src/filters.js` 3. Test with vague queries Ten things you half remember. This is the real benchmark rather than exact matches. Files: `eval/queries.json` ### Done when - [ ] A phrase inside an image returns that image - [ ] Search over ten thousand items is under a hundred milliseconds - [ ] Kind and date filters narrow correctly - [ ] Most vague queries return the right item on the first screen ### Watch out - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. ## Phase 5 · A grid you actually browse Scrolling the library is fast and you recognise things by sight. ### Steps 1. Generate thumbnails at capture Not on scroll. Doing it lazily is why these grids stutter. Files: `src/thumbs.js` 2. Give tiles known dimensions Stored at capture so the layout is stable before images load. Files: `src/grid.js` 3. Open from the stored copy No network request. The archive is the point and it must work offline. Files: `src/item.js` ### Done when - [ ] Ten thousand items scroll smoothly with stable memory - [ ] Nothing shifts as images load - [ ] Opening an item makes no network request - [ ] Thumbnails exist at capture time ### Watch out - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ## Not in this build - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. ## After v1, if you want it - Visual similarity search using image embeddings, which is the natural next retrieval mode - A weekly resurfacing of older items, since a library nobody revisits is a landfill - Deduplication of the same page saved twice from different sources ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Items, extracted text and tags. A revealing dataset, so treat backups accordingly. DB_PATH=./data/library.db # Required. Original images and stored page copies, plus thumbnails. ASSETS_DIR=./data/assets # Required. Folder watched for screenshots. Files here are copied rather than moved. WATCH_DIR=~/Desktop/Screenshots # Required · secret. Used by the bookmarklet and the share endpoint. Scoped to capture only. CAPTURE_TOKEN=replace-with-a-random-token # Required. Which model derives tags. Local means nothing leaves the machine. ENRICH_PROVIDER=local # Required. Thumbnail size. Large enough to recognise, small enough to scroll thousands. THUMB_LONG_EDGE=600
You are building a production product version of Mymind. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Mymind · product brief ## Problem The mechanic is honestly simple: capture a thing, extract text and metadata, ask a model for tags, embed it, then search across everything. An agent can build that in a weekend with SQLite, a headless browser for page snapshots and one model API call per item. What you cannot one-shot is the capture surface, which is the entire product in practice: browser extensions for three browsers, an iOS and Android share sheet, and the reliability that makes you trust it with the thought you had in a queue. The taste also matters more than it should here, because the pitch is that you never organize anything, and a DIY version that mis-tags half your library quietly becomes a junk drawer. Fine build, real gaps. ## Product outcome A library that costs nothing to add to, finds things by what is inside them rather than what you named them, keeps your corrections, and exports to a folder you could read without it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node 20 and pnpm, runs locally - An LLM API key for tagging and embeddings, in .env - Playwright with Chromium for page snapshots and screenshots - Local disk for original images and thumbnails - Optional: Tesseract for OCR on screenshots - A browser extension loaded unpacked in dev mode ## Explicit non-goals for v1 - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. - Real browser extensions and a mobile share sheet, so capture friction goes way up - Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality - Sync across devices, plus offline capable native apps - Serendipity features: the everything-search, the surfacing of old cards, spaced resurfacing - Someone else's ongoing judgment about what a good tag actually is ## Success criteria - A phrase inside an image is confirmed to return that image - A corrected tag is confirmed to survive derivation - Search latency on the large library has not regressed - The grid is confirmed not to shift as images load - Export is confirmed readable without the application ===== BRIEF.md ===== # Build brief · Mymind The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private place to dump links, images and notes that organises itself so you never file anything to replace Mymind. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The premise is that you never organise. No folders, no required tags, no filing step at capture. Every tool in this category dies the same way: it asks for a decision at the moment of capture, capture becomes work, and within a month you stop. So capture is one action with no fields, and everything that makes an item findable is derived afterwards. Tags are suggested and correctable but never required, and search must work well enough that a thing you saved a year ago and cannot describe still comes back. That puts the whole burden on retrieval, which is where the effort belongs. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite and full text search · one person's library, where retrieval quality matters far more than scale - Extraction: Readability for pages, text recognition for images · a screenshot is mostly text and useless without it - Enrichment: A model producing tags and a description, applied once per item · derived rather than asked for, which is what removes the filing step - Capture: A bookmarklet, a share endpoint and a watched folder · three routes because the friction of capture decides whether the tool survives - Interface: A masonry grid with lazy loading · you recognise a saved image faster than you recall its title, which is why this is visual ### Data model (create this before Phase 1) - items(id, kind, url, title, description, captured_at, source, colour_hex) · kind is link, image, note or highlight - content(item_id, extracted_text, ocr_text, extracted_at, extractor_version) - assets(item_id, path, kind, width, height, bytes, thumb_path) - tags(id, name), item_tags(item_id, tag_id, source, confirmed) · source is derived or manual - items_fts · full text over title, description, extracted text and recognised text - A derived tag is a suggestion until confirmed or corrected, and a correction is never overwritten by a later derivation. ### Phase 1 · Capture in one action Build: Three capture routes: a bookmarklet posting the current page, an endpoint accepting a shared URL or image from a phone, and a watched folder for screenshots. None of them asks for a title, a tag or a folder. Trap: Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. Done when: Saving from the bookmarklet takes one click with no dialogue. Dropping a screenshot into the watched folder captures it without any prompt. A shared URL from a phone captures through the endpoint. No capture route asks for a title, tag or folder. Do not build yet: No extraction, tagging or search. ### Phase 2 · Getting text out of everything Build: Extract article text from pages and run text recognition over images and screenshots. Store both separately with the extractor version. Record failures rather than storing an empty extraction. Trap: A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. Done when: A screenshot of a page of text is findable by a phrase inside it. An article saved from the bookmarklet is findable by a phrase in its body. An image with no readable text is marked rather than silently unsearchable. The extractor version is recorded so a later improvement can re-run. Do not build yet: No tagging, grid or search interface yet. ### Phase 3 · Tags derived, never demanded Build: Send extracted text to a model once per item and store suggested tags and a one-line description. Let a tag be confirmed, corrected or removed. A correction is permanent and is never overwritten by a later derivation. Trap: Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. Done when: Saving an item produces tags and a description without any input. Correcting a tag persists through a later derivation run. Removing a derived tag keeps it removed. An item whose extraction failed gets no invented tags rather than plausible guesses. Do not build yet: No grid or search interface yet. ### Phase 4 · Retrieval, which is where the effort belongs Build: Search across title, description, extracted text, recognised text and tags together. Filter by kind, by colour and by date. Test against a library of thousands of items including things you genuinely cannot name. Trap: Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. Done when: A query matching only text inside an image returns that image. Search across ten thousand items returns in well under a hundred milliseconds. Filtering by kind and date narrows correctly. Ten queries describing content you remember vaguely return the right item in the first screen for most of them. Do not build yet: No visual grid polish or export yet. ### Phase 5 · A grid you actually browse Build: A masonry grid with generated thumbnails, lazy loading and stable layout so nothing jumps as images load. Group by capture date. Make opening an item instant from the stored copy rather than fetching the original. Trap: Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. Done when: Scrolling a library of ten thousand items stays smooth with stable memory. Nothing shifts position as images load, because dimensions are known in advance. Opening an item is instant from the stored copy with no network request. Thumbnails are generated at capture rather than on scroll. Do not build yet: No export or sharing yet. ### Phase 6 · Privacy, export and keeping it yours Build: Export everything as a folder of files with a Markdown index per item. Authenticate any hosted deployment. State clearly whether extracted text is sent to a model and offer a local option. Trap: A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. Done when: Export produces a folder of original files with a Markdown index per item carrying tags and description. A hosted deployment requires authentication. The interface states whether text leaves the machine for enrichment. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - The tagging quality of the hosted product, which is tuned against many users' libraries and corrections. - Mobile applications with a native share sheet, which is where most capture actually happens and where the endpoint is a poorer substitute. - Visual similarity search, which needs image embeddings and is a substantially larger piece of work than text search. - Anything social. This is deliberately single user, which is also what makes the privacy story simple. ### README must contain - That capture asks for nothing and everything findable is derived afterwards - That text is recognised in images, because screenshots are most of what gets saved - That corrected tags are permanent and never overwritten by derivation - That search covers recognised text, so an image is findable by its content - Whether extracted text is sent to a model, and how to run enrichment locally ===== ARCHITECTURE.md ===== # Architecture · Mymind ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite and full text search | one person's library, where retrieval quality matters far more than scale | | Extraction | Readability for pages, text recognition for images | a screenshot is mostly text and useless without it | | Enrichment | A model producing tags and a description, applied once per item | derived rather than asked for, which is what removes the filing step | | Capture | A bookmarklet, a share endpoint and a watched folder | three routes because the friction of capture decides whether the tool survives | | Interface | A masonry grid with lazy loading | you recognise a saved image faster than you recall its title, which is why this is visual | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Capture | Getting things in with no friction. | Routes can change; asking for nothing at capture is the premise and cannot. | | Extraction | Turning content into searchable text. | Any extractor and recogniser. Marking failures rather than silently losing items is the property to keep. | | Enrichment | Derived tags and descriptions. | Any model or none. Never overwriting a correction is the rule that keeps corrections happening. | | Search | Retrieval across everything extracted. | Any index, provided it covers recognised text as well as titles. | | Grid | Visual browsing at scale. | Presentation, provided thumbnails are generated at capture and dimensions are known. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Items, extracted text and tags. A revealing dataset, so treat backups accordingly. - `ASSETS_DIR` · required · Original images and stored page copies, plus thumbnails. - `WATCH_DIR` · required · Folder watched for screenshots. Files here are copied rather than moved. - `CAPTURE_TOKEN` · required, secret · Used by the bookmarklet and the share endpoint. Scoped to capture only. - `ENRICH_PROVIDER` · required · Which model derives tags. Local means nothing leaves the machine. - `THUMB_LONG_EDGE` · required · Thumbnail size. Large enough to recognise, small enough to scroll thousands. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Mymind product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with SQLite and full text search, Readability for pages, text recognition for images, A model producing tags and a description, applied once per item, A bookmarklet, a share endpoint and a watched folder, A masonry grid with lazy loading. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. - A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. ===== MILESTONES.md ===== # Delivery milestones · Mymind Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Capture in one action Saving anything takes one action and asks for nothing. ### Steps 1. Write the bookmarklet Posts the URL, title and any selection. No form, no confirmation dialogue. Files: `public/bookmarklet.js` 2. Watch a screenshot folder With a settle check, so a file still being written is not captured half formed. Files: `src/watch.js` 3. Accept shares from a phone An endpoint taking a URL or an image. The phone is where most captures happen. Files: `src/capture.js` ### Done when - [ ] The bookmarklet saves in one click with no dialogue - [ ] A dropped screenshot captures without a prompt - [ ] A shared URL from a phone captures - [ ] No route asks for a title, tag or folder ### Watch out - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. ## M2 · Getting text out of everything A screenshot is as findable as a note, because its text was read. ### Steps 1. Extract page text Readability, sanitised so the stored copy makes no outbound requests when viewed. Files: `src/extract.js` ```sh npm install @mozilla/readability jsdom ``` 2. Recognise text in images Every image, not only ones that look like screenshots. The distinction is not reliable. Files: `src/ocr.js` 3. Mark unreadable items Rather than letting them vanish from search. They are the ones you will look for and not find. Files: `src/extract.js` ### Done when - [ ] A screenshot is findable by a phrase inside it - [ ] An article is findable by a phrase in its body - [ ] An image with no readable text is marked - [ ] The extractor version is recorded ### Watch out - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. ## M3 · Tags derived, never demanded Items acquire tags without you filing anything, and your corrections stick. ### Steps 1. Derive once per item On capture, from extracted text. Not on a schedule over the whole library. Files: `src/enrich.js` 2. Mark manual edits Corrected and removed tags are permanent decisions and are excluded from derivation. Files: `src/tags.js` 3. Refuse to guess without text An item with no extraction gets no tags. Invented tags are worse than none. Files: `src/enrich.js` ### Done when - [ ] Saving produces tags and a description with no input - [ ] A corrected tag survives a later derivation - [ ] A removed tag stays removed - [ ] An item without extraction gets no invented tags ### Watch out - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. ## M4 · Retrieval, which is where the effort belongs Something you saved a year ago and cannot describe still comes back. ### Steps 1. Index everything extracted Title, description, page text, recognised text and tags in one index. Files: `src/search.js` 2. Add non-text filters Kind, date and dominant colour. Colour is surprisingly effective for images. Files: `src/filters.js` 3. Test with vague queries Ten things you half remember. This is the real benchmark rather than exact matches. Files: `eval/queries.json` ### Done when - [ ] A phrase inside an image returns that image - [ ] Search over ten thousand items is under a hundred milliseconds - [ ] Kind and date filters narrow correctly - [ ] Most vague queries return the right item on the first screen ### Watch out - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. ## M5 · A grid you actually browse Scrolling the library is fast and you recognise things by sight. ### Steps 1. Generate thumbnails at capture Not on scroll. Doing it lazily is why these grids stutter. Files: `src/thumbs.js` 2. Give tiles known dimensions Stored at capture so the layout is stable before images load. Files: `src/grid.js` 3. Open from the stored copy No network request. The archive is the point and it must work offline. Files: `src/item.js` ### Done when - [ ] Ten thousand items scroll smoothly with stable memory - [ ] Nothing shifts as images load - [ ] Opening an item makes no network request - [ ] Thumbnails exist at capture time ### Watch out - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ## M6 · Privacy, export and keeping it yours (production only) The library is portable and nobody but you can reach it. ### Steps 1. Export as files plus Markdown One index file per item. Readable without this application, which is the ownership claim. Files: `src/export.js` 2. Authenticate anything hosted Before it leaves your machine. This dataset deserves it. Files: `src/auth.js` 3. Offer a local enrichment path So nothing needs to be sent. State which is active in the interface. Files: `src/enrich.js` ### Done when - [ ] Export produces files plus a Markdown index per item - [ ] Hosted deployment requires authentication - [ ] The interface states whether text leaves the machine - [ ] A local configuration works with networking disabled ### Watch out - A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. ===== OPERATIONS.md ===== # Operations · Mymind ## Backup The database and the assets directory together. Assets are the irreplaceable half, since a database entry pointing at a missing image is worse than nothing. ## Restore Restore both and open an item whose original source is gone. That is the test that the archive is real rather than a bookmark list. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Extraction failure rate and unrecognised image count. A rising unrecognised count usually means the recognition step broke rather than that your screenshots changed. ## Incident checklist The realistic failure is items becoming unfindable because extraction failed silently. Marking failures at capture is the control, and a periodic report of unreadable items is the backstop. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A phrase inside an image is confirmed to return that image - [ ] A corrected tag is confirmed to survive derivation - [ ] Search latency on the large library has not regressed - [ ] The grid is confirmed not to shift as images load - [ ] Export is confirmed readable without the application ## Launch constraint Do not market omitted Mymind capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Items, extracted text and tags. A revealing dataset, so treat backups accordingly. DB_PATH=./data/library.db # Required. Original images and stored page copies, plus thumbnails. ASSETS_DIR=./data/assets # Required. Folder watched for screenshots. Files here are copied rather than moved. WATCH_DIR=~/Desktop/Screenshots # Required · secret. Used by the bookmarklet and the share endpoint. Scoped to capture only. CAPTURE_TOKEN=replace-with-a-random-token # Required. Which model derives tags. Local means nothing leaves the machine. ENRICH_PROVIDER=local # Required. Thumbnail size. Large enough to recognise, small enough to scroll thousands. THUMB_LONG_EDGE=600
# Mymind · indie build A local library that swallows links, images, screenshots and notes in one action, extracts what it can from each, derives tags and a one-line description, and serves a visual grid with search that covers extracted text, derived tags and the text inside images. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite and full text search | one person's library, where retrieval quality matters far more than scale | | Extraction | Readability for pages, text recognition for images | a screenshot is mostly text and useless without it | | Enrichment | A model producing tags and a description, applied once per item | derived rather than asked for, which is what removes the filing step | | Capture | A bookmarklet, a share endpoint and a watched folder | three routes because the friction of capture decides whether the tool survives | | Interface | A masonry grid with lazy loading | you recognise a saved image faster than you recall its title, which is why this is visual | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the capture endpoints, extraction and search. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A text recognition engine** · free - Why: Screenshots are the majority of what a library like this holds and are invisible to search without it. - Get it: Install Tesseract with the language data you need, or use a platform recognition interface if one is available to you. - Verify: A screenshot of text returns its content - [ ] **A few hundred real things you have saved** · free - Why: Search quality and grid performance are both meaningless against generated data, and the vague queries that matter only exist against your own material. - Get it: Import from wherever you currently save things: browser bookmarks, a screenshots folder, a notes application. - Verify: At least three hundred real items are imported - [ ] **A model for tags and descriptions, hosted or local** (optional) · pay per token; tagging an item is a fraction of a cent - Why: Phase 3 only. Capture, extraction and search all work with no model, which is a legitimate configuration. - Get it: A provider key with a spend limit, or a local model if you would rather nothing left the machine. - Verify: A captured item receives tags and a description - [ ] **Ten things you remember but cannot name precisely** · free - Why: Phase 4's real benchmark. Exact-match testing tells you nothing about the retrieval this tool exists for. - Get it: Write down ten items you know are in your library described the way you would actually search for them. - Verify: The ten queries are recorded with their expected items ## Quick start ```sh npm install @mozilla/readability jsdom ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. - Real browser extensions and a mobile share sheet, so capture friction goes way up - Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality - Sync across devices, plus offline capable native apps - Serendipity features: the everything-search, the surfacing of old cards, spaced resurfacing - Someone else's ongoing judgment about what a good tag actually is If one of those is essential to you, that is the reason to keep paying for Mymind, and the README should say so rather than pretend.
# Build brief · Mymind The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private place to dump links, images and notes that organises itself so you never file anything to replace Mymind. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The premise is that you never organise. No folders, no required tags, no filing step at capture. Every tool in this category dies the same way: it asks for a decision at the moment of capture, capture becomes work, and within a month you stop. So capture is one action with no fields, and everything that makes an item findable is derived afterwards. Tags are suggested and correctable but never required, and search must work well enough that a thing you saved a year ago and cannot describe still comes back. That puts the whole burden on retrieval, which is where the effort belongs. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite and full text search · one person's library, where retrieval quality matters far more than scale - Extraction: Readability for pages, text recognition for images · a screenshot is mostly text and useless without it - Enrichment: A model producing tags and a description, applied once per item · derived rather than asked for, which is what removes the filing step - Capture: A bookmarklet, a share endpoint and a watched folder · three routes because the friction of capture decides whether the tool survives - Interface: A masonry grid with lazy loading · you recognise a saved image faster than you recall its title, which is why this is visual ### Data model (create this before Phase 1) - items(id, kind, url, title, description, captured_at, source, colour_hex) · kind is link, image, note or highlight - content(item_id, extracted_text, ocr_text, extracted_at, extractor_version) - assets(item_id, path, kind, width, height, bytes, thumb_path) - tags(id, name), item_tags(item_id, tag_id, source, confirmed) · source is derived or manual - items_fts · full text over title, description, extracted text and recognised text - A derived tag is a suggestion until confirmed or corrected, and a correction is never overwritten by a later derivation. ### Phase 1 · Capture in one action Build: Three capture routes: a bookmarklet posting the current page, an endpoint accepting a shared URL or image from a phone, and a watched folder for screenshots. None of them asks for a title, a tag or a folder. Trap: Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. Done when: Saving from the bookmarklet takes one click with no dialogue. Dropping a screenshot into the watched folder captures it without any prompt. A shared URL from a phone captures through the endpoint. No capture route asks for a title, tag or folder. Do not build yet: No extraction, tagging or search. ### Phase 2 · Getting text out of everything Build: Extract article text from pages and run text recognition over images and screenshots. Store both separately with the extractor version. Record failures rather than storing an empty extraction. Trap: A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. Done when: A screenshot of a page of text is findable by a phrase inside it. An article saved from the bookmarklet is findable by a phrase in its body. An image with no readable text is marked rather than silently unsearchable. The extractor version is recorded so a later improvement can re-run. Do not build yet: No tagging, grid or search interface yet. ### Phase 3 · Tags derived, never demanded Build: Send extracted text to a model once per item and store suggested tags and a one-line description. Let a tag be confirmed, corrected or removed. A correction is permanent and is never overwritten by a later derivation. Trap: Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. Done when: Saving an item produces tags and a description without any input. Correcting a tag persists through a later derivation run. Removing a derived tag keeps it removed. An item whose extraction failed gets no invented tags rather than plausible guesses. Do not build yet: No grid or search interface yet. ### Phase 4 · Retrieval, which is where the effort belongs Build: Search across title, description, extracted text, recognised text and tags together. Filter by kind, by colour and by date. Test against a library of thousands of items including things you genuinely cannot name. Trap: Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. Done when: A query matching only text inside an image returns that image. Search across ten thousand items returns in well under a hundred milliseconds. Filtering by kind and date narrows correctly. Ten queries describing content you remember vaguely return the right item in the first screen for most of them. Do not build yet: No visual grid polish or export yet. ### Phase 5 · A grid you actually browse Build: A masonry grid with generated thumbnails, lazy loading and stable layout so nothing jumps as images load. Group by capture date. Make opening an item instant from the stored copy rather than fetching the original. Trap: Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. Done when: Scrolling a library of ten thousand items stays smooth with stable memory. Nothing shifts position as images load, because dimensions are known in advance. Opening an item is instant from the stored copy with no network request. Thumbnails are generated at capture rather than on scroll. Do not build yet: No export or sharing yet. ### Phase 6 · Privacy, export and keeping it yours Build: Export everything as a folder of files with a Markdown index per item. Authenticate any hosted deployment. State clearly whether extracted text is sent to a model and offer a local option. Trap: A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. Done when: Export produces a folder of original files with a Markdown index per item carrying tags and description. A hosted deployment requires authentication. The interface states whether text leaves the machine for enrichment. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - The tagging quality of the hosted product, which is tuned against many users' libraries and corrections. - Mobile applications with a native share sheet, which is where most capture actually happens and where the endpoint is a poorer substitute. - Visual similarity search, which needs image embeddings and is a substantially larger piece of work than text search. - Anything social. This is deliberately single user, which is also what makes the privacy story simple. ### README must contain - That capture asks for nothing and everything findable is derived afterwards - That text is recognised in images, because screenshots are most of what gets saved - That corrected tags are permanent and never overwritten by derivation - That search covers recognised text, so an image is findable by its content - Whether extracted text is sent to a model, and how to run enrichment locally
# Agent instructions · Mymind indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22 with SQLite and full text search, Readability for pages, text recognition for images, A model producing tags and a description, applied once per item, A bookmarklet, a share endpoint and a watched folder, A masonry grid with lazy loading. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened.
# Build plan · Mymind A local library that swallows links, images, screenshots and notes in one action, extracts what it can from each, derives tags and a one-line description, and serves a visual grid with search that covers extracted text, derived tags and the text inside images. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Capture in one action Saving anything takes one action and asks for nothing. ### Steps 1. Write the bookmarklet Posts the URL, title and any selection. No form, no confirmation dialogue. Files: `public/bookmarklet.js` 2. Watch a screenshot folder With a settle check, so a file still being written is not captured half formed. Files: `src/watch.js` 3. Accept shares from a phone An endpoint taking a URL or an image. The phone is where most captures happen. Files: `src/capture.js` ### Done when - [ ] The bookmarklet saves in one click with no dialogue - [ ] A dropped screenshot captures without a prompt - [ ] A shared URL from a phone captures - [ ] No route asks for a title, tag or folder ### Watch out - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. ## Phase 2 · Getting text out of everything A screenshot is as findable as a note, because its text was read. ### Steps 1. Extract page text Readability, sanitised so the stored copy makes no outbound requests when viewed. Files: `src/extract.js` ```sh npm install @mozilla/readability jsdom ``` 2. Recognise text in images Every image, not only ones that look like screenshots. The distinction is not reliable. Files: `src/ocr.js` 3. Mark unreadable items Rather than letting them vanish from search. They are the ones you will look for and not find. Files: `src/extract.js` ### Done when - [ ] A screenshot is findable by a phrase inside it - [ ] An article is findable by a phrase in its body - [ ] An image with no readable text is marked - [ ] The extractor version is recorded ### Watch out - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. ## Phase 3 · Tags derived, never demanded Items acquire tags without you filing anything, and your corrections stick. ### Steps 1. Derive once per item On capture, from extracted text. Not on a schedule over the whole library. Files: `src/enrich.js` 2. Mark manual edits Corrected and removed tags are permanent decisions and are excluded from derivation. Files: `src/tags.js` 3. Refuse to guess without text An item with no extraction gets no tags. Invented tags are worse than none. Files: `src/enrich.js` ### Done when - [ ] Saving produces tags and a description with no input - [ ] A corrected tag survives a later derivation - [ ] A removed tag stays removed - [ ] An item without extraction gets no invented tags ### Watch out - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. ## Phase 4 · Retrieval, which is where the effort belongs Something you saved a year ago and cannot describe still comes back. ### Steps 1. Index everything extracted Title, description, page text, recognised text and tags in one index. Files: `src/search.js` 2. Add non-text filters Kind, date and dominant colour. Colour is surprisingly effective for images. Files: `src/filters.js` 3. Test with vague queries Ten things you half remember. This is the real benchmark rather than exact matches. Files: `eval/queries.json` ### Done when - [ ] A phrase inside an image returns that image - [ ] Search over ten thousand items is under a hundred milliseconds - [ ] Kind and date filters narrow correctly - [ ] Most vague queries return the right item on the first screen ### Watch out - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. ## Phase 5 · A grid you actually browse Scrolling the library is fast and you recognise things by sight. ### Steps 1. Generate thumbnails at capture Not on scroll. Doing it lazily is why these grids stutter. Files: `src/thumbs.js` 2. Give tiles known dimensions Stored at capture so the layout is stable before images load. Files: `src/grid.js` 3. Open from the stored copy No network request. The archive is the point and it must work offline. Files: `src/item.js` ### Done when - [ ] Ten thousand items scroll smoothly with stable memory - [ ] Nothing shifts as images load - [ ] Opening an item makes no network request - [ ] Thumbnails exist at capture time ### Watch out - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ## Not in this build - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. ## After v1, if you want it - Visual similarity search using image embeddings, which is the natural next retrieval mode - A weekly resurfacing of older items, since a library nobody revisits is a landfill - Deduplication of the same page saved twice from different sources
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Items, extracted text and tags. A revealing dataset, so treat backups accordingly. DB_PATH=./data/library.db # Required. Original images and stored page copies, plus thumbnails. ASSETS_DIR=./data/assets # Required. Folder watched for screenshots. Files here are copied rather than moved. WATCH_DIR=~/Desktop/Screenshots # Required · secret. Used by the bookmarklet and the share endpoint. Scoped to capture only. CAPTURE_TOKEN=replace-with-a-random-token # Required. Which model derives tags. Local means nothing leaves the machine. ENRICH_PROVIDER=local # Required. Thumbnail size. Large enough to recognise, small enough to scroll thousands. THUMB_LONG_EDGE=600
# Mymind · product brief ## Problem The mechanic is honestly simple: capture a thing, extract text and metadata, ask a model for tags, embed it, then search across everything. An agent can build that in a weekend with SQLite, a headless browser for page snapshots and one model API call per item. What you cannot one-shot is the capture surface, which is the entire product in practice: browser extensions for three browsers, an iOS and Android share sheet, and the reliability that makes you trust it with the thought you had in a queue. The taste also matters more than it should here, because the pitch is that you never organize anything, and a DIY version that mis-tags half your library quietly becomes a junk drawer. Fine build, real gaps. ## Product outcome A library that costs nothing to add to, finds things by what is inside them rather than what you named them, keeps your corrections, and exports to a folder you could read without it. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node 20 and pnpm, runs locally - An LLM API key for tagging and embeddings, in .env - Playwright with Chromium for page snapshots and screenshots - Local disk for original images and thumbnails - Optional: Tesseract for OCR on screenshots - A browser extension loaded unpacked in dev mode ## Explicit non-goals for v1 - Tagging quality tuned across many libraries. - Native mobile applications with a share sheet. - Visual similarity search. - Anything social or shared. - Real browser extensions and a mobile share sheet, so capture friction goes way up - Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality - Sync across devices, plus offline capable native apps - Serendipity features: the everything-search, the surfacing of old cards, spaced resurfacing - Someone else's ongoing judgment about what a good tag actually is ## Success criteria - A phrase inside an image is confirmed to return that image - A corrected tag is confirmed to survive derivation - Search latency on the large library has not regressed - The grid is confirmed not to shift as images load - Export is confirmed readable without the application
# Build brief · Mymind The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private place to dump links, images and notes that organises itself so you never file anything to replace Mymind. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero The premise is that you never organise. No folders, no required tags, no filing step at capture. Every tool in this category dies the same way: it asks for a decision at the moment of capture, capture becomes work, and within a month you stop. So capture is one action with no fields, and everything that makes an item findable is derived afterwards. Tags are suggested and correctable but never required, and search must work well enough that a thing you saved a year ago and cannot describe still comes back. That puts the whole burden on retrieval, which is where the effort belongs. ### Stack (fixed, do not substitute) - Runtime: Node 22 with SQLite and full text search · one person's library, where retrieval quality matters far more than scale - Extraction: Readability for pages, text recognition for images · a screenshot is mostly text and useless without it - Enrichment: A model producing tags and a description, applied once per item · derived rather than asked for, which is what removes the filing step - Capture: A bookmarklet, a share endpoint and a watched folder · three routes because the friction of capture decides whether the tool survives - Interface: A masonry grid with lazy loading · you recognise a saved image faster than you recall its title, which is why this is visual ### Data model (create this before Phase 1) - items(id, kind, url, title, description, captured_at, source, colour_hex) · kind is link, image, note or highlight - content(item_id, extracted_text, ocr_text, extracted_at, extractor_version) - assets(item_id, path, kind, width, height, bytes, thumb_path) - tags(id, name), item_tags(item_id, tag_id, source, confirmed) · source is derived or manual - items_fts · full text over title, description, extracted text and recognised text - A derived tag is a suggestion until confirmed or corrected, and a correction is never overwritten by a later derivation. ### Phase 1 · Capture in one action Build: Three capture routes: a bookmarklet posting the current page, an endpoint accepting a shared URL or image from a phone, and a watched folder for screenshots. None of them asks for a title, a tag or a folder. Trap: Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. Done when: Saving from the bookmarklet takes one click with no dialogue. Dropping a screenshot into the watched folder captures it without any prompt. A shared URL from a phone captures through the endpoint. No capture route asks for a title, tag or folder. Do not build yet: No extraction, tagging or search. ### Phase 2 · Getting text out of everything Build: Extract article text from pages and run text recognition over images and screenshots. Store both separately with the extractor version. Record failures rather than storing an empty extraction. Trap: A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. Done when: A screenshot of a page of text is findable by a phrase inside it. An article saved from the bookmarklet is findable by a phrase in its body. An image with no readable text is marked rather than silently unsearchable. The extractor version is recorded so a later improvement can re-run. Do not build yet: No tagging, grid or search interface yet. ### Phase 3 · Tags derived, never demanded Build: Send extracted text to a model once per item and store suggested tags and a one-line description. Let a tag be confirmed, corrected or removed. A correction is permanent and is never overwritten by a later derivation. Trap: Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. Done when: Saving an item produces tags and a description without any input. Correcting a tag persists through a later derivation run. Removing a derived tag keeps it removed. An item whose extraction failed gets no invented tags rather than plausible guesses. Do not build yet: No grid or search interface yet. ### Phase 4 · Retrieval, which is where the effort belongs Build: Search across title, description, extracted text, recognised text and tags together. Filter by kind, by colour and by date. Test against a library of thousands of items including things you genuinely cannot name. Trap: Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. Done when: A query matching only text inside an image returns that image. Search across ten thousand items returns in well under a hundred milliseconds. Filtering by kind and date narrows correctly. Ten queries describing content you remember vaguely return the right item in the first screen for most of them. Do not build yet: No visual grid polish or export yet. ### Phase 5 · A grid you actually browse Build: A masonry grid with generated thumbnails, lazy loading and stable layout so nothing jumps as images load. Group by capture date. Make opening an item instant from the stored copy rather than fetching the original. Trap: Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. Done when: Scrolling a library of ten thousand items stays smooth with stable memory. Nothing shifts position as images load, because dimensions are known in advance. Opening an item is instant from the stored copy with no network request. Thumbnails are generated at capture rather than on scroll. Do not build yet: No export or sharing yet. ### Phase 6 · Privacy, export and keeping it yours Build: Export everything as a folder of files with a Markdown index per item. Authenticate any hosted deployment. State clearly whether extracted text is sent to a model and offer a local option. Trap: A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider. Done when: Export produces a folder of original files with a Markdown index per item carrying tags and description. A hosted deployment requires authentication. The interface states whether text leaves the machine for enrichment. A local model configuration works with networking disabled. Do not build yet: Nothing after this. ### Out of scope (and why) - The tagging quality of the hosted product, which is tuned against many users' libraries and corrections. - Mobile applications with a native share sheet, which is where most capture actually happens and where the endpoint is a poorer substitute. - Visual similarity search, which needs image embeddings and is a substantially larger piece of work than text search. - Anything social. This is deliberately single user, which is also what makes the privacy story simple. ### README must contain - That capture asks for nothing and everything findable is derived afterwards - That text is recognised in images, because screenshots are most of what gets saved - That corrected tags are permanent and never overwritten by derivation - That search covers recognised text, so an image is findable by its content - Whether extracted text is sent to a model, and how to run enrichment locally
# Architecture · Mymind ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22 with SQLite and full text search | one person's library, where retrieval quality matters far more than scale | | Extraction | Readability for pages, text recognition for images | a screenshot is mostly text and useless without it | | Enrichment | A model producing tags and a description, applied once per item | derived rather than asked for, which is what removes the filing step | | Capture | A bookmarklet, a share endpoint and a watched folder | three routes because the friction of capture decides whether the tool survives | | Interface | A masonry grid with lazy loading | you recognise a saved image faster than you recall its title, which is why this is visual | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Capture | Getting things in with no friction. | Routes can change; asking for nothing at capture is the premise and cannot. | | Extraction | Turning content into searchable text. | Any extractor and recogniser. Marking failures rather than silently losing items is the property to keep. | | Enrichment | Derived tags and descriptions. | Any model or none. Never overwriting a correction is the rule that keeps corrections happening. | | Search | Retrieval across everything extracted. | Any index, provided it covers recognised text as well as titles. | | Grid | Visual browsing at scale. | Presentation, provided thumbnails are generated at capture and dimensions are known. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Items, extracted text and tags. A revealing dataset, so treat backups accordingly. - `ASSETS_DIR` · required · Original images and stored page copies, plus thumbnails. - `WATCH_DIR` · required · Folder watched for screenshots. Files here are copied rather than moved. - `CAPTURE_TOKEN` · required, secret · Used by the bookmarklet and the share endpoint. Scoped to capture only. - `ENRICH_PROVIDER` · required · Which model derives tags. Local means nothing leaves the machine. - `THUMB_LONG_EDGE` · required · Thumbnail size. Large enough to recognise, small enough to scroll thousands. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Mymind product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22 with SQLite and full text search, Readability for pages, text recognition for images, A model producing tags and a description, applied once per item, A bookmarklet, a share endpoint and a watched folder, A masonry grid with lazy loading. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. - A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider.
# Delivery milestones · Mymind Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Capture in one action Saving anything takes one action and asks for nothing. ### Steps 1. Write the bookmarklet Posts the URL, title and any selection. No form, no confirmation dialogue. Files: `public/bookmarklet.js` 2. Watch a screenshot folder With a settle check, so a file still being written is not captured half formed. Files: `src/watch.js` 3. Accept shares from a phone An endpoint taking a URL or an image. The phone is where most captures happen. Files: `src/capture.js` ### Done when - [ ] The bookmarklet saves in one click with no dialogue - [ ] A dropped screenshot captures without a prompt - [ ] A shared URL from a phone captures - [ ] No route asks for a title, tag or folder ### Watch out - Adding one optional field at capture is enough to break the premise, because an optional field still asks for a decision and most people will stop to fill it. Capture nothing but the content and the source; everything else is derived or edited later. ## M2 · Getting text out of everything A screenshot is as findable as a note, because its text was read. ### Steps 1. Extract page text Readability, sanitised so the stored copy makes no outbound requests when viewed. Files: `src/extract.js` ```sh npm install @mozilla/readability jsdom ``` 2. Recognise text in images Every image, not only ones that look like screenshots. The distinction is not reliable. Files: `src/ocr.js` 3. Mark unreadable items Rather than letting them vanish from search. They are the ones you will look for and not find. Files: `src/extract.js` ### Done when - [ ] A screenshot is findable by a phrase inside it - [ ] An article is findable by a phrase in its body - [ ] An image with no readable text is marked - [ ] The extractor version is recorded ### Watch out - A screenshot with no recognised text is invisible to search, and screenshots are usually the majority of what people save in a tool like this. Run recognition on every image, and where it produces nothing, mark the item as needing attention rather than letting it disappear into the grid. ## M3 · Tags derived, never demanded Items acquire tags without you filing anything, and your corrections stick. ### Steps 1. Derive once per item On capture, from extracted text. Not on a schedule over the whole library. Files: `src/enrich.js` 2. Mark manual edits Corrected and removed tags are permanent decisions and are excluded from derivation. Files: `src/tags.js` 3. Refuse to guess without text An item with no extraction gets no tags. Invented tags are worse than none. Files: `src/enrich.js` ### Done when - [ ] Saving produces tags and a description with no input - [ ] A corrected tag survives a later derivation - [ ] A removed tag stays removed - [ ] An item without extraction gets no invented tags ### Watch out - Re-deriving tags on a schedule and overwriting user corrections teaches people their edits do not matter, and they stop correcting, and the tags degrade. Mark corrected tags and leave them alone, deriving only for items that have none. ## M4 · Retrieval, which is where the effort belongs Something you saved a year ago and cannot describe still comes back. ### Steps 1. Index everything extracted Title, description, page text, recognised text and tags in one index. Files: `src/search.js` 2. Add non-text filters Kind, date and dominant colour. Colour is surprisingly effective for images. Files: `src/filters.js` 3. Test with vague queries Ten things you half remember. This is the real benchmark rather than exact matches. Files: `eval/queries.json` ### Done when - [ ] A phrase inside an image returns that image - [ ] Search over ten thousand items is under a hundred milliseconds - [ ] Kind and date filters narrow correctly - [ ] Most vague queries return the right item on the first screen ### Watch out - Searching only titles and tags fails for exactly the items this tool exists to hold: the screenshot with no title and the image whose content you remember but cannot name. Index everything extracted, and test with queries describing content rather than metadata. ## M5 · A grid you actually browse Scrolling the library is fast and you recognise things by sight. ### Steps 1. Generate thumbnails at capture Not on scroll. Doing it lazily is why these grids stutter. Files: `src/thumbs.js` 2. Give tiles known dimensions Stored at capture so the layout is stable before images load. Files: `src/grid.js` 3. Open from the stored copy No network request. The archive is the point and it must work offline. Files: `src/item.js` ### Done when - [ ] Ten thousand items scroll smoothly with stable memory - [ ] Nothing shifts as images load - [ ] Opening an item makes no network request - [ ] Thumbnails exist at capture time ### Watch out - Loading full images into a grid makes scrolling stutter and consumes memory, and it gets worse as the library grows so it feels fine for the first month. Generate thumbnails at capture, give every tile explicit dimensions so the layout does not shift, and load full images only when an item is opened. ## M6 · Privacy, export and keeping it yours (production only) The library is portable and nobody but you can reach it. ### Steps 1. Export as files plus Markdown One index file per item. Readable without this application, which is the ownership claim. Files: `src/export.js` 2. Authenticate anything hosted Before it leaves your machine. This dataset deserves it. Files: `src/auth.js` 3. Offer a local enrichment path So nothing needs to be sent. State which is active in the interface. Files: `src/enrich.js` ### Done when - [ ] Export produces files plus a Markdown index per item - [ ] Hosted deployment requires authentication - [ ] The interface states whether text leaves the machine - [ ] A local configuration works with networking disabled ### Watch out - A library of everything you found interesting is an unusually revealing dataset, more so than a browser history because you chose to keep each item. Authenticate before exposing it anywhere, and be explicit about whether the enrichment step sends your content to a provider.
# Operations · Mymind ## Backup The database and the assets directory together. Assets are the irreplaceable half, since a database entry pointing at a missing image is worse than nothing. ## Restore Restore both and open an item whose original source is gone. That is the test that the archive is real rather than a bookmark list. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Extraction failure rate and unrecognised image count. A rising unrecognised count usually means the recognition step broke rather than that your screenshots changed. ## Incident checklist The realistic failure is items becoming unfindable because extraction failed silently. Marking failures at capture is the control, and a periodic report of unreadable items is the backstop. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A phrase inside an image is confirmed to return that image - [ ] A corrected tag is confirmed to survive derivation - [ ] Search latency on the large library has not regressed - [ ] The grid is confirmed not to shift as images load - [ ] Export is confirmed readable without the application ## Launch constraint Do not market omitted Mymind capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Items, extracted text and tags. A revealing dataset, so treat backups accordingly. DB_PATH=./data/library.db # Required. Original images and stored page copies, plus thumbnails. ASSETS_DIR=./data/assets # Required. Folder watched for screenshots. Files here are copied rather than moved. WATCH_DIR=~/Desktop/Screenshots # Required · secret. Used by the bookmarklet and the share endpoint. Scoped to capture only. CAPTURE_TOKEN=replace-with-a-random-token # Required. Which model derives tags. Local means nothing leaves the machine. ENRICH_PROVIDER=local # Required. Thumbnail size. Large enough to recognise, small enough to scroll thousands. THUMB_LONG_EDGE=600
$ choose a build depth, inspect the files, then open the complete pack in your agent
Because the value of a second brain is proportional to how little effort each save costs, and Mymind spent years shaving that cost down to one click from any device. A self-hosted clone tags almost as well now that models are cheap, but it lives on your laptop behind a localhost URL, which means the thing you saw on your phone at 11pm never makes it in. People also pay for the privacy stance and the promise of no social features, no sharing, no feed, which is easy to replicate technically and hard to replicate as a trust relationship.
xReal browser extensions and a mobile share sheet, so capture friction goes way up
xImage understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality
xSync across devices, plus offline capable native apps
xSerendipity features: the everything-search, the surfacing of old cards, spaced resurfacing
xSomeone else's ongoing judgment about what a good tag actually is
Nothing worth pointing at. That's why the prompt exists.
Vibecode Mymind
Kinda. The core of Mymind is buildable in a weekend with the prompt on this page, but there are real gaps: Real browser extensions and a mobile share sheet, so capture friction goes way up, Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality. Read the honest list above before committing.
How much does Mymind cost?
Mymind costs about $12.99/month (Mastermind, checked 2026-08-18), which is $155.88 per year.
What do I lose by replacing Mymind?
Honestly: Real browser extensions and a mobile share sheet, so capture friction goes way up; Image understanding that recognizes objects, art, color palettes and text in photos at Mymind's quality; Sync across devices, plus offline capable native apps; Serendipity features: the everything-search, the surfacing of old cards, spaced resurfacing; Someone else's ongoing judgment about what a good tag actually is. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Mymind?
No mature open-source alternative worth pointing at, which is exactly why the one-shot prompt on this page exists.