Vibecode Capacities
track this build5 phases, 15 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Capacities, organize linked notes as typed objects such as people, books, meetings, and ideas. The hard boundary is object-model polish, mobile capture, sync, calendar integration, and ai features, plus sync, collaboration, and capture polish.
You are building a lean indie version of Capacities. 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 ===== # Capacities · indie build A notes application built on typed objects with defined properties, where mentioning a person creates a real entity rather than a link to nothing, and where the daily note is the capture surface that feeds everything else without you filing anything. Estimated effort: **multi-day**. 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 | TypeScript with a local application and SQLite | your notes are yours and a knowledge base is a lifetime artefact | | Objects | Types with declared properties, stored relationally | the difference from a wiki is that a person has a role and a book has an author | | Editor | Block-based, with inline object references | references need to be first-class, not text that looks like a link | | Search | Full text plus property filters | finding every book you rated highly is a query, not a search | | Export | Markdown with front matter, always available | a knowledge base you cannot leave is a knowledge base you should not start | ## 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 local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your first four object types** · free - Why: Types shape everything and are painful to change once you have hundreds of objects. Four is enough to test the model and few enough to change your mind. - Get it: Look at what you already write about. Person, book, idea and meeting is a reasonable start if nothing obvious presents itself. - Verify: Four types are written down with their properties before any code - [ ] **A few hundred existing notes** · free - Why: Search performance, schema migration and export all behave differently at scale, and a knowledge base with twenty notes proves nothing. - Get it: Export from whatever you use now, or generate a realistic corpus with varied references. - Verify: Several hundred objects with references exist to test against - [ ] **A file store you control** (optional) · pennies per month at personal scale - Why: Phase 6 only. Sync goes through storage you own rather than a service that owns your notes. - Get it: Any object storage or a synced folder. The requirement is that you control it. - Verify: Two machines can read and write the same location ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. - object-model polish, mobile capture, sync, calendar integration, and AI features - frictionless mobile capture - real-time team editing - hosted publishing - proprietary AI memory If one of those is essential to you, that is the reason to keep paying for Capacities, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Capacities 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 notes where every person, book and idea is a typed object rather than a page you happened to name to replace Capacities. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · your notes are yours and a knowledge base is a lifetime artefact - Objects: Types with declared properties, stored relationally · the difference from a wiki is that a person has a role and a book has an author - Editor: Block-based, with inline object references · references need to be first-class, not text that looks like a link - Search: Full text plus property filters · finding every book you rated highly is a query, not a search - Export: Markdown with front matter, always available · a knowledge base you cannot leave is a knowledge base you should not start ### Data model (create this before Phase 1) - types(id, name, icon, property_schema_json) · person, book, idea, meeting, whatever you define - objects(id, type_id, title, created_at, updated_at) · every note is an object of some type - properties(object_id, key, value_json) · typed per the type's schema, validated on write - blocks(id, object_id, ordinal, kind, text, children_json) · the body content - references(from_object_id, from_block_id, to_object_id, kind) · kind is mention or property_link - daily(date, object_id) · the daily note is an object too, which is why capture and structure are the same system - A reference is a row, so backlinks are a query rather than a text search across every note. ### Phase 1 · Typed objects with real properties Build: Define types with property schemas. Create objects of a type with properties validated on write. Changing a type's schema must handle existing objects rather than orphaning their data. Trap: Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. Done when: Types define properties with kinds and objects validate against them on write. Adding a property to a type leaves existing objects intact and marked incomplete. Removing a property preserves the values rather than deleting them. A property of the wrong kind is rejected with a clear message. Do not build yet: No references, daily notes or search. ### Phase 2 · Mentions that create the thing mentioned Build: Inline references that resolve to an existing object or offer to create one of a chosen type. Store references as rows. Renaming an object updates every reference because they point at an id rather than a string. Trap: References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. Done when: Mentioning a name offers to create a typed object and the reference is stored as a row. Renaming an object updates every mention, verified across five notes. Backlinks list every referring object and block. Deleting a referenced object is refused or converts references to plain text with a warning rather than leaving dangling rows. Do not build yet: No daily notes or search yet. ### Phase 3 · The daily note as the capture surface Build: A daily note that exists for every date, is itself an object, and is where capture happens. Mentions made there create objects and appear on those objects' pages with the date. No filing step. Trap: A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. Done when: The daily note exists for any date without being created by hand. A mention in today's note appears on that object's page with the date. A backlog of unfiled items is impossible because there is no filing step. Navigating between days is fast on a year of notes. Do not build yet: No search or export yet. ### Phase 4 · Finding things by what they are Build: Full text search across blocks combined with property filters and type filters. Saved queries as objects, so a saved view is itself part of the knowledge base. Trap: Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. Done when: A property filter combined with a type filter returns correct results, verified against a set built by hand. Full text and property filters combine in one query. A saved query is an object with a type. Search across ten thousand blocks returns within a reasonable time. Do not build yet: No export or sync yet. ### Phase 5 · Getting everything out Build: Export every object as Markdown with properties in front matter and references as links. Round trip: re-import an export and confirm objects, properties and references are identical. Run this as a test rather than a promise. Trap: An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. Done when: Every object exports as Markdown with front matter and linked references. Re-importing an export reproduces objects, properties and references identically, asserted in a test. The export is readable in a plain text editor. Export of ten thousand objects completes without exhausting memory. Do not build yet: Sync and backup come last. ### Phase 6 · Two machines, and never losing a note Build: Sync through a file store you control, with per-block conflict handling that keeps both versions rather than choosing. Automated backups with a tested restore. Trap: Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. Done when: Editing different blocks of one object on two machines merges both changes. Editing the same block keeps both versions visibly rather than choosing. A restore from backup reproduces objects, properties and references, verified by comparison. Sync of a large knowledge base completes without transferring everything. Do not build yet: Nothing after this. ### Out of scope (and why) - Collaboration and shared spaces, which is a different product and a much harder synchronisation problem. - Mobile capture, which is where a lot of the value of a daily note actually comes from. - AI features over the knowledge base, which is a natural next thing and needs the graph to be correct first. - Web clipping with content extraction, which is a genuinely hard problem the hosted product has invested in. ### README must contain - That objects are typed with validated properties and what happens when a schema changes - That references are stored by id so renaming is free and nothing dangles - That the daily note is the capture surface and there is no filing step - That the export round trip is tested rather than promised - That sync resolves per block and keeps both sides on a real conflict ===== AGENTS.md ===== # Agent instructions · Capacities indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a local application and SQLite, Types with declared properties, stored relationally, Block-based, with inline object references, Full text plus property filters, Markdown with front matter, always available. 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 a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ===== BUILD_PLAN.md ===== # Build plan · Capacities A notes application built on typed objects with defined properties, where mentioning a person creates a real entity rather than a link to nothing, and where the daily note is the capture surface that feeds everything else without you filing anything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Typed objects with real properties A person has a role and a book has an author, enforced. ### Steps 1. Declare property schemas per type Kind, required, default. This is what separates this from a folder of pages. Files: `src/types.ts` 2. Validate on write A pure function against the schema, unit tested with wrong kinds. Files: `src/validate.ts`, `src/validate.test.ts` 3. Handle schema change explicitly Existing objects keep data and are marked incomplete. Decide this now, not at three hundred objects. Files: `src/migrate.ts` ### Done when - [ ] Objects validate against their type's schema - [ ] Adding a property leaves existing objects intact - [ ] Removing a property preserves values - [ ] A wrong-kind property is rejected clearly ### Watch out - Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. ## Phase 2 · Mentions that create the thing mentioned Typing a person's name in a note produces a person object, not a dead link. ### Steps 1. Reference by id, render by title So renaming is free. Text references are the mistake that shows up months in. Files: `src/reference.ts` 2. Offer creation with a type Choosing the type at mention time is what keeps the graph typed rather than generic. Files: `src/mention.ts` 3. Handle deletion of a referenced object Refuse or convert with a warning. Never leave dangling references. Files: `src/delete.ts` ### Done when - [ ] A mention creates a typed object and a reference row - [ ] Renaming updates every mention - [ ] Backlinks list referring objects and blocks - [ ] Deleting a referenced object is handled explicitly ### Watch out - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. ## Phase 3 · The daily note as the capture surface You write into today and the structure builds itself. ### Steps 1. Daily note as a first-class object So it participates in references like anything else. Files: `src/daily.ts` 2. Make the mention the filing No inbox, no later step. This is the design that survives contact with real use. Files: `src/daily.ts` 3. Show dated backlinks on objects A person's page becomes a timeline of when you mentioned them. Files: `src/backlinks.ts` ### Done when - [ ] A daily note exists for any date - [ ] A mention appears dated on the object's page - [ ] There is no filing step to fall behind on - [ ] Navigating a year of daily notes is fast ### Watch out - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. ## Phase 4 · Finding things by what they are Every book you rated above four, in one query. ### Steps 1. Index blocks for text Full text search over content, scoped by object type. Files: `src/search.ts` 2. Query properties relationally The reason the property model exists. Filters over typed values, not string matching. Files: `src/query.ts` 3. Saved queries as objects So a view is part of the knowledge base rather than application state. Files: `src/saved.ts` ### Done when - [ ] Property and type filters return correct results - [ ] Text and property filters combine - [ ] A saved query is a typed object - [ ] Ten thousand blocks search quickly ### Watch out - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. ## Phase 5 · Getting everything out Your knowledge base leaves in a form you could read in ten years. ### Steps 1. Markdown with front matter Properties in the front matter, body as Markdown. Readable without your application. Files: `src/export.ts` 2. Test the round trip Export, import, compare. A backup untested is not a backup. Files: `tests/roundtrip.test.ts` 3. Stream large exports Ten thousand objects should not need ten thousand objects of memory. Files: `src/export.ts` ### Done when - [ ] Every object exports as Markdown with front matter - [ ] A round trip reproduces objects, properties and references - [ ] The export reads plainly in a text editor - [ ] A large export completes without memory exhaustion ### Watch out - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ## Not in this build - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. ## After v1, if you want it - Object templates, so creating a meeting pre-fills the properties you always want - A graph view, once the references are trustworthy enough to be worth looking at - Scheduled review surfacing objects you have not touched, which is where a base earns its keep ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your entire knowledge base. The single most important file to back up. DB_PATH=~/.local/share/capacities/base.db # Required. Where Markdown exports are written. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/notes-export # Required. When a new day starts, so a note written at one in the morning lands on the right day. DAILY_NOTE_START_HOUR=4 # Optional. Phase 6 only. Absent means single machine, which is a fine way to run. SYNC_REMOTE=s3://my-notes/base # Required. Automated backups. Restore-tested, or they do not count. BACKUP_DIR=~/backups/capacities
You are building a lean indie version of Capacities. 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 ===== # Capacities · indie build A notes application built on typed objects with defined properties, where mentioning a person creates a real entity rather than a link to nothing, and where the daily note is the capture surface that feeds everything else without you filing anything. Estimated effort: **multi-day**. 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 | TypeScript with a local application and SQLite | your notes are yours and a knowledge base is a lifetime artefact | | Objects | Types with declared properties, stored relationally | the difference from a wiki is that a person has a role and a book has an author | | Editor | Block-based, with inline object references | references need to be first-class, not text that looks like a link | | Search | Full text plus property filters | finding every book you rated highly is a query, not a search | | Export | Markdown with front matter, always available | a knowledge base you cannot leave is a knowledge base you should not start | ## 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 local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your first four object types** · free - Why: Types shape everything and are painful to change once you have hundreds of objects. Four is enough to test the model and few enough to change your mind. - Get it: Look at what you already write about. Person, book, idea and meeting is a reasonable start if nothing obvious presents itself. - Verify: Four types are written down with their properties before any code - [ ] **A few hundred existing notes** · free - Why: Search performance, schema migration and export all behave differently at scale, and a knowledge base with twenty notes proves nothing. - Get it: Export from whatever you use now, or generate a realistic corpus with varied references. - Verify: Several hundred objects with references exist to test against - [ ] **A file store you control** (optional) · pennies per month at personal scale - Why: Phase 6 only. Sync goes through storage you own rather than a service that owns your notes. - Get it: Any object storage or a synced folder. The requirement is that you control it. - Verify: Two machines can read and write the same location ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. - object-model polish, mobile capture, sync, calendar integration, and AI features - frictionless mobile capture - real-time team editing - hosted publishing - proprietary AI memory If one of those is essential to you, that is the reason to keep paying for Capacities, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Capacities 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 notes where every person, book and idea is a typed object rather than a page you happened to name to replace Capacities. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · your notes are yours and a knowledge base is a lifetime artefact - Objects: Types with declared properties, stored relationally · the difference from a wiki is that a person has a role and a book has an author - Editor: Block-based, with inline object references · references need to be first-class, not text that looks like a link - Search: Full text plus property filters · finding every book you rated highly is a query, not a search - Export: Markdown with front matter, always available · a knowledge base you cannot leave is a knowledge base you should not start ### Data model (create this before Phase 1) - types(id, name, icon, property_schema_json) · person, book, idea, meeting, whatever you define - objects(id, type_id, title, created_at, updated_at) · every note is an object of some type - properties(object_id, key, value_json) · typed per the type's schema, validated on write - blocks(id, object_id, ordinal, kind, text, children_json) · the body content - references(from_object_id, from_block_id, to_object_id, kind) · kind is mention or property_link - daily(date, object_id) · the daily note is an object too, which is why capture and structure are the same system - A reference is a row, so backlinks are a query rather than a text search across every note. ### Phase 1 · Typed objects with real properties Build: Define types with property schemas. Create objects of a type with properties validated on write. Changing a type's schema must handle existing objects rather than orphaning their data. Trap: Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. Done when: Types define properties with kinds and objects validate against them on write. Adding a property to a type leaves existing objects intact and marked incomplete. Removing a property preserves the values rather than deleting them. A property of the wrong kind is rejected with a clear message. Do not build yet: No references, daily notes or search. ### Phase 2 · Mentions that create the thing mentioned Build: Inline references that resolve to an existing object or offer to create one of a chosen type. Store references as rows. Renaming an object updates every reference because they point at an id rather than a string. Trap: References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. Done when: Mentioning a name offers to create a typed object and the reference is stored as a row. Renaming an object updates every mention, verified across five notes. Backlinks list every referring object and block. Deleting a referenced object is refused or converts references to plain text with a warning rather than leaving dangling rows. Do not build yet: No daily notes or search yet. ### Phase 3 · The daily note as the capture surface Build: A daily note that exists for every date, is itself an object, and is where capture happens. Mentions made there create objects and appear on those objects' pages with the date. No filing step. Trap: A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. Done when: The daily note exists for any date without being created by hand. A mention in today's note appears on that object's page with the date. A backlog of unfiled items is impossible because there is no filing step. Navigating between days is fast on a year of notes. Do not build yet: No search or export yet. ### Phase 4 · Finding things by what they are Build: Full text search across blocks combined with property filters and type filters. Saved queries as objects, so a saved view is itself part of the knowledge base. Trap: Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. Done when: A property filter combined with a type filter returns correct results, verified against a set built by hand. Full text and property filters combine in one query. A saved query is an object with a type. Search across ten thousand blocks returns within a reasonable time. Do not build yet: No export or sync yet. ### Phase 5 · Getting everything out Build: Export every object as Markdown with properties in front matter and references as links. Round trip: re-import an export and confirm objects, properties and references are identical. Run this as a test rather than a promise. Trap: An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. Done when: Every object exports as Markdown with front matter and linked references. Re-importing an export reproduces objects, properties and references identically, asserted in a test. The export is readable in a plain text editor. Export of ten thousand objects completes without exhausting memory. Do not build yet: Sync and backup come last. ### Phase 6 · Two machines, and never losing a note Build: Sync through a file store you control, with per-block conflict handling that keeps both versions rather than choosing. Automated backups with a tested restore. Trap: Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. Done when: Editing different blocks of one object on two machines merges both changes. Editing the same block keeps both versions visibly rather than choosing. A restore from backup reproduces objects, properties and references, verified by comparison. Sync of a large knowledge base completes without transferring everything. Do not build yet: Nothing after this. ### Out of scope (and why) - Collaboration and shared spaces, which is a different product and a much harder synchronisation problem. - Mobile capture, which is where a lot of the value of a daily note actually comes from. - AI features over the knowledge base, which is a natural next thing and needs the graph to be correct first. - Web clipping with content extraction, which is a genuinely hard problem the hosted product has invested in. ### README must contain - That objects are typed with validated properties and what happens when a schema changes - That references are stored by id so renaming is free and nothing dangles - That the daily note is the capture surface and there is no filing step - That the export round trip is tested rather than promised - That sync resolves per block and keeps both sides on a real conflict ===== AGENTS.md ===== # Agent instructions · Capacities indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a local application and SQLite, Types with declared properties, stored relationally, Block-based, with inline object references, Full text plus property filters, Markdown with front matter, always available. 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 a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ===== BUILD_PLAN.md ===== # Build plan · Capacities A notes application built on typed objects with defined properties, where mentioning a person creates a real entity rather than a link to nothing, and where the daily note is the capture surface that feeds everything else without you filing anything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Typed objects with real properties A person has a role and a book has an author, enforced. ### Steps 1. Declare property schemas per type Kind, required, default. This is what separates this from a folder of pages. Files: `src/types.ts` 2. Validate on write A pure function against the schema, unit tested with wrong kinds. Files: `src/validate.ts`, `src/validate.test.ts` 3. Handle schema change explicitly Existing objects keep data and are marked incomplete. Decide this now, not at three hundred objects. Files: `src/migrate.ts` ### Done when - [ ] Objects validate against their type's schema - [ ] Adding a property leaves existing objects intact - [ ] Removing a property preserves values - [ ] A wrong-kind property is rejected clearly ### Watch out - Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. ## Phase 2 · Mentions that create the thing mentioned Typing a person's name in a note produces a person object, not a dead link. ### Steps 1. Reference by id, render by title So renaming is free. Text references are the mistake that shows up months in. Files: `src/reference.ts` 2. Offer creation with a type Choosing the type at mention time is what keeps the graph typed rather than generic. Files: `src/mention.ts` 3. Handle deletion of a referenced object Refuse or convert with a warning. Never leave dangling references. Files: `src/delete.ts` ### Done when - [ ] A mention creates a typed object and a reference row - [ ] Renaming updates every mention - [ ] Backlinks list referring objects and blocks - [ ] Deleting a referenced object is handled explicitly ### Watch out - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. ## Phase 3 · The daily note as the capture surface You write into today and the structure builds itself. ### Steps 1. Daily note as a first-class object So it participates in references like anything else. Files: `src/daily.ts` 2. Make the mention the filing No inbox, no later step. This is the design that survives contact with real use. Files: `src/daily.ts` 3. Show dated backlinks on objects A person's page becomes a timeline of when you mentioned them. Files: `src/backlinks.ts` ### Done when - [ ] A daily note exists for any date - [ ] A mention appears dated on the object's page - [ ] There is no filing step to fall behind on - [ ] Navigating a year of daily notes is fast ### Watch out - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. ## Phase 4 · Finding things by what they are Every book you rated above four, in one query. ### Steps 1. Index blocks for text Full text search over content, scoped by object type. Files: `src/search.ts` 2. Query properties relationally The reason the property model exists. Filters over typed values, not string matching. Files: `src/query.ts` 3. Saved queries as objects So a view is part of the knowledge base rather than application state. Files: `src/saved.ts` ### Done when - [ ] Property and type filters return correct results - [ ] Text and property filters combine - [ ] A saved query is a typed object - [ ] Ten thousand blocks search quickly ### Watch out - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. ## Phase 5 · Getting everything out Your knowledge base leaves in a form you could read in ten years. ### Steps 1. Markdown with front matter Properties in the front matter, body as Markdown. Readable without your application. Files: `src/export.ts` 2. Test the round trip Export, import, compare. A backup untested is not a backup. Files: `tests/roundtrip.test.ts` 3. Stream large exports Ten thousand objects should not need ten thousand objects of memory. Files: `src/export.ts` ### Done when - [ ] Every object exports as Markdown with front matter - [ ] A round trip reproduces objects, properties and references - [ ] The export reads plainly in a text editor - [ ] A large export completes without memory exhaustion ### Watch out - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ## Not in this build - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. ## After v1, if you want it - Object templates, so creating a meeting pre-fills the properties you always want - A graph view, once the references are trustworthy enough to be worth looking at - Scheduled review surfacing objects you have not touched, which is where a base earns its keep ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your entire knowledge base. The single most important file to back up. DB_PATH=~/.local/share/capacities/base.db # Required. Where Markdown exports are written. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/notes-export # Required. When a new day starts, so a note written at one in the morning lands on the right day. DAILY_NOTE_START_HOUR=4 # Optional. Phase 6 only. Absent means single machine, which is a fine way to run. SYNC_REMOTE=s3://my-notes/base # Required. Automated backups. Restore-tested, or they do not count. BACKUP_DIR=~/backups/capacities
You are building a production product version of Capacities. 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 ===== # Capacities · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Capacities, organize linked notes as typed objects such as people, books, meetings, and ideas. The hard boundary is object-model polish, mobile capture, sync, calendar integration, and ai features, plus sync, collaboration, and capture polish. ## Product outcome A knowledge base where a person is a person rather than a page, where writing in today's note is the only filing you do, and where the export round trip is a test rather than a promise. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - macOS, Windows, or Linux - local folder access - optional sync folder chosen by the user ## Explicit non-goals for v1 - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. - object-model polish, mobile capture, sync, calendar integration, and AI features - frictionless mobile capture - real-time team editing - hosted publishing - proprietary AI memory ## Success criteria - Renaming an object is confirmed to update every mention - A schema change is confirmed to leave existing objects intact - The export round trip is confirmed identical in the suite - A block-level sync conflict is confirmed to keep both versions - A restore is confirmed by comparison against the source ===== BRIEF.md ===== # Build brief · Capacities 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 notes where every person, book and idea is a typed object rather than a page you happened to name to replace Capacities. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · your notes are yours and a knowledge base is a lifetime artefact - Objects: Types with declared properties, stored relationally · the difference from a wiki is that a person has a role and a book has an author - Editor: Block-based, with inline object references · references need to be first-class, not text that looks like a link - Search: Full text plus property filters · finding every book you rated highly is a query, not a search - Export: Markdown with front matter, always available · a knowledge base you cannot leave is a knowledge base you should not start ### Data model (create this before Phase 1) - types(id, name, icon, property_schema_json) · person, book, idea, meeting, whatever you define - objects(id, type_id, title, created_at, updated_at) · every note is an object of some type - properties(object_id, key, value_json) · typed per the type's schema, validated on write - blocks(id, object_id, ordinal, kind, text, children_json) · the body content - references(from_object_id, from_block_id, to_object_id, kind) · kind is mention or property_link - daily(date, object_id) · the daily note is an object too, which is why capture and structure are the same system - A reference is a row, so backlinks are a query rather than a text search across every note. ### Phase 1 · Typed objects with real properties Build: Define types with property schemas. Create objects of a type with properties validated on write. Changing a type's schema must handle existing objects rather than orphaning their data. Trap: Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. Done when: Types define properties with kinds and objects validate against them on write. Adding a property to a type leaves existing objects intact and marked incomplete. Removing a property preserves the values rather than deleting them. A property of the wrong kind is rejected with a clear message. Do not build yet: No references, daily notes or search. ### Phase 2 · Mentions that create the thing mentioned Build: Inline references that resolve to an existing object or offer to create one of a chosen type. Store references as rows. Renaming an object updates every reference because they point at an id rather than a string. Trap: References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. Done when: Mentioning a name offers to create a typed object and the reference is stored as a row. Renaming an object updates every mention, verified across five notes. Backlinks list every referring object and block. Deleting a referenced object is refused or converts references to plain text with a warning rather than leaving dangling rows. Do not build yet: No daily notes or search yet. ### Phase 3 · The daily note as the capture surface Build: A daily note that exists for every date, is itself an object, and is where capture happens. Mentions made there create objects and appear on those objects' pages with the date. No filing step. Trap: A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. Done when: The daily note exists for any date without being created by hand. A mention in today's note appears on that object's page with the date. A backlog of unfiled items is impossible because there is no filing step. Navigating between days is fast on a year of notes. Do not build yet: No search or export yet. ### Phase 4 · Finding things by what they are Build: Full text search across blocks combined with property filters and type filters. Saved queries as objects, so a saved view is itself part of the knowledge base. Trap: Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. Done when: A property filter combined with a type filter returns correct results, verified against a set built by hand. Full text and property filters combine in one query. A saved query is an object with a type. Search across ten thousand blocks returns within a reasonable time. Do not build yet: No export or sync yet. ### Phase 5 · Getting everything out Build: Export every object as Markdown with properties in front matter and references as links. Round trip: re-import an export and confirm objects, properties and references are identical. Run this as a test rather than a promise. Trap: An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. Done when: Every object exports as Markdown with front matter and linked references. Re-importing an export reproduces objects, properties and references identically, asserted in a test. The export is readable in a plain text editor. Export of ten thousand objects completes without exhausting memory. Do not build yet: Sync and backup come last. ### Phase 6 · Two machines, and never losing a note Build: Sync through a file store you control, with per-block conflict handling that keeps both versions rather than choosing. Automated backups with a tested restore. Trap: Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. Done when: Editing different blocks of one object on two machines merges both changes. Editing the same block keeps both versions visibly rather than choosing. A restore from backup reproduces objects, properties and references, verified by comparison. Sync of a large knowledge base completes without transferring everything. Do not build yet: Nothing after this. ### Out of scope (and why) - Collaboration and shared spaces, which is a different product and a much harder synchronisation problem. - Mobile capture, which is where a lot of the value of a daily note actually comes from. - AI features over the knowledge base, which is a natural next thing and needs the graph to be correct first. - Web clipping with content extraction, which is a genuinely hard problem the hosted product has invested in. ### README must contain - That objects are typed with validated properties and what happens when a schema changes - That references are stored by id so renaming is free and nothing dangles - That the daily note is the capture surface and there is no filing step - That the export round trip is tested rather than promised - That sync resolves per block and keeps both sides on a real conflict ===== ARCHITECTURE.md ===== # Architecture · Capacities ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | your notes are yours and a knowledge base is a lifetime artefact | | Objects | Types with declared properties, stored relationally | the difference from a wiki is that a person has a role and a book has an author | | Editor | Block-based, with inline object references | references need to be first-class, not text that looks like a link | | Search | Full text plus property filters | finding every book you rated highly is a query, not a search | | Export | Markdown with front matter, always available | a knowledge base you cannot leave is a knowledge base you should not start | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Types | Property schemas and their migration. | Hard to change late. Deciding the migration behaviour early is what makes it survivable. | | References | Id-based mentions and backlinks. | Any storage. Referencing by id rather than text is the requirement. | | Daily | The capture surface with no filing step. | Not swappable for an inbox, which is the design that fails. | | Query | Text and property filtering. | Any engine. The property path is why the schema exists. | | Sync | Block-level merge and conflict retention. | Any transport, provided merge is per block and conflicts keep both sides. | ## 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 · Your entire knowledge base. The single most important file to back up. - `EXPORT_DIR` · required · Where Markdown exports are written. Somewhere your ordinary backups already reach. - `DAILY_NOTE_START_HOUR` · required · When a new day starts, so a note written at one in the morning lands on the right day. - `SYNC_REMOTE` · optional · Phase 6 only. Absent means single machine, which is a fine way to run. - `BACKUP_DIR` · required · Automated backups. Restore-tested, or they do not count. ## 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 · Capacities product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a local application and SQLite, Types with declared properties, stored relationally, Block-based, with inline object references, Full text plus property filters, Markdown with front matter, always available. - 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 a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. - Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. ===== MILESTONES.md ===== # Delivery milestones · Capacities Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Typed objects with real properties A person has a role and a book has an author, enforced. ### Steps 1. Declare property schemas per type Kind, required, default. This is what separates this from a folder of pages. Files: `src/types.ts` 2. Validate on write A pure function against the schema, unit tested with wrong kinds. Files: `src/validate.ts`, `src/validate.test.ts` 3. Handle schema change explicitly Existing objects keep data and are marked incomplete. Decide this now, not at three hundred objects. Files: `src/migrate.ts` ### Done when - [ ] Objects validate against their type's schema - [ ] Adding a property leaves existing objects intact - [ ] Removing a property preserves values - [ ] A wrong-kind property is rejected clearly ### Watch out - Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. ## M2 · Mentions that create the thing mentioned Typing a person's name in a note produces a person object, not a dead link. ### Steps 1. Reference by id, render by title So renaming is free. Text references are the mistake that shows up months in. Files: `src/reference.ts` 2. Offer creation with a type Choosing the type at mention time is what keeps the graph typed rather than generic. Files: `src/mention.ts` 3. Handle deletion of a referenced object Refuse or convert with a warning. Never leave dangling references. Files: `src/delete.ts` ### Done when - [ ] A mention creates a typed object and a reference row - [ ] Renaming updates every mention - [ ] Backlinks list referring objects and blocks - [ ] Deleting a referenced object is handled explicitly ### Watch out - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. ## M3 · The daily note as the capture surface You write into today and the structure builds itself. ### Steps 1. Daily note as a first-class object So it participates in references like anything else. Files: `src/daily.ts` 2. Make the mention the filing No inbox, no later step. This is the design that survives contact with real use. Files: `src/daily.ts` 3. Show dated backlinks on objects A person's page becomes a timeline of when you mentioned them. Files: `src/backlinks.ts` ### Done when - [ ] A daily note exists for any date - [ ] A mention appears dated on the object's page - [ ] There is no filing step to fall behind on - [ ] Navigating a year of daily notes is fast ### Watch out - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. ## M4 · Finding things by what they are Every book you rated above four, in one query. ### Steps 1. Index blocks for text Full text search over content, scoped by object type. Files: `src/search.ts` 2. Query properties relationally The reason the property model exists. Filters over typed values, not string matching. Files: `src/query.ts` 3. Saved queries as objects So a view is part of the knowledge base rather than application state. Files: `src/saved.ts` ### Done when - [ ] Property and type filters return correct results - [ ] Text and property filters combine - [ ] A saved query is a typed object - [ ] Ten thousand blocks search quickly ### Watch out - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. ## M5 · Getting everything out Your knowledge base leaves in a form you could read in ten years. ### Steps 1. Markdown with front matter Properties in the front matter, body as Markdown. Readable without your application. Files: `src/export.ts` 2. Test the round trip Export, import, compare. A backup untested is not a backup. Files: `tests/roundtrip.test.ts` 3. Stream large exports Ten thousand objects should not need ten thousand objects of memory. Files: `src/export.ts` ### Done when - [ ] Every object exports as Markdown with front matter - [ ] A round trip reproduces objects, properties and references - [ ] The export reads plainly in a text editor - [ ] A large export completes without memory exhaustion ### Watch out - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ## M6 · Two machines, and never losing a note (production only) You write on a second machine and nothing is lost or duplicated. ### Steps 1. Sync at block granularity Object-level last-write-wins loses paragraphs silently. Files: `src/sync.ts` 2. Keep both sides on a real conflict Visibly, for you to resolve. Choosing for the user is choosing to lose something. Files: `src/conflict.ts` 3. Test the restore Compare against the source. Backups that have never been restored are hopes. Files: `scripts/verify-restore.sh` ### Done when - [ ] Different blocks on two machines merge - [ ] A true conflict keeps both versions - [ ] A restore reproduces everything by comparison - [ ] Sync does not retransfer the whole base ### Watch out - Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. ===== OPERATIONS.md ===== # Operations · Capacities ## Backup A knowledge base is a lifetime artefact. Automated backups, plus periodic Markdown exports as a format-independent copy, and a restore test that compares rather than merely completing. ## Restore Restore and compare objects, properties and references against the source. Confirm references resolve rather than dangle, since that is what a partial restore breaks first. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Reference integrity is the thing to check: dangling references mean a deletion path is wrong. Also watch export round trip success in the suite, since that is your escape hatch. ## Incident checklist If renaming breaks mentions, references are being stored as text somewhere and that is a data model defect rather than a display bug. If sync loses a paragraph, resolution is happening above block level. 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 - [ ] Renaming an object is confirmed to update every mention - [ ] A schema change is confirmed to leave existing objects intact - [ ] The export round trip is confirmed identical in the suite - [ ] A block-level sync conflict is confirmed to keep both versions - [ ] A restore is confirmed by comparison against the source ## Launch constraint Do not market omitted Capacities 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. Your entire knowledge base. The single most important file to back up. DB_PATH=~/.local/share/capacities/base.db # Required. Where Markdown exports are written. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/notes-export # Required. When a new day starts, so a note written at one in the morning lands on the right day. DAILY_NOTE_START_HOUR=4 # Optional. Phase 6 only. Absent means single machine, which is a fine way to run. SYNC_REMOTE=s3://my-notes/base # Required. Automated backups. Restore-tested, or they do not count. BACKUP_DIR=~/backups/capacities
# Capacities · indie build A notes application built on typed objects with defined properties, where mentioning a person creates a real entity rather than a link to nothing, and where the daily note is the capture surface that feeds everything else without you filing anything. Estimated effort: **multi-day**. 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 | TypeScript with a local application and SQLite | your notes are yours and a knowledge base is a lifetime artefact | | Objects | Types with declared properties, stored relationally | the difference from a wiki is that a person has a role and a book has an author | | Editor | Block-based, with inline object references | references need to be first-class, not text that looks like a link | | Search | Full text plus property filters | finding every book you rated highly is a query, not a search | | Export | Markdown with front matter, always available | a knowledge base you cannot leave is a knowledge base you should not start | ## 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 local application. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Your first four object types** · free - Why: Types shape everything and are painful to change once you have hundreds of objects. Four is enough to test the model and few enough to change your mind. - Get it: Look at what you already write about. Person, book, idea and meeting is a reasonable start if nothing obvious presents itself. - Verify: Four types are written down with their properties before any code - [ ] **A few hundred existing notes** · free - Why: Search performance, schema migration and export all behave differently at scale, and a knowledge base with twenty notes proves nothing. - Get it: Export from whatever you use now, or generate a realistic corpus with varied references. - Verify: Several hundred objects with references exist to test against - [ ] **A file store you control** (optional) · pennies per month at personal scale - Why: Phase 6 only. Sync goes through storage you own rather than a service that owns your notes. - Get it: Any object storage or a synced folder. The requirement is that you control it. - Verify: Two machines can read and write the same location ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. - object-model polish, mobile capture, sync, calendar integration, and AI features - frictionless mobile capture - real-time team editing - hosted publishing - proprietary AI memory If one of those is essential to you, that is the reason to keep paying for Capacities, and the README should say so rather than pretend.
# Build brief · Capacities 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 notes where every person, book and idea is a typed object rather than a page you happened to name to replace Capacities. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · your notes are yours and a knowledge base is a lifetime artefact - Objects: Types with declared properties, stored relationally · the difference from a wiki is that a person has a role and a book has an author - Editor: Block-based, with inline object references · references need to be first-class, not text that looks like a link - Search: Full text plus property filters · finding every book you rated highly is a query, not a search - Export: Markdown with front matter, always available · a knowledge base you cannot leave is a knowledge base you should not start ### Data model (create this before Phase 1) - types(id, name, icon, property_schema_json) · person, book, idea, meeting, whatever you define - objects(id, type_id, title, created_at, updated_at) · every note is an object of some type - properties(object_id, key, value_json) · typed per the type's schema, validated on write - blocks(id, object_id, ordinal, kind, text, children_json) · the body content - references(from_object_id, from_block_id, to_object_id, kind) · kind is mention or property_link - daily(date, object_id) · the daily note is an object too, which is why capture and structure are the same system - A reference is a row, so backlinks are a query rather than a text search across every note. ### Phase 1 · Typed objects with real properties Build: Define types with property schemas. Create objects of a type with properties validated on write. Changing a type's schema must handle existing objects rather than orphaning their data. Trap: Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. Done when: Types define properties with kinds and objects validate against them on write. Adding a property to a type leaves existing objects intact and marked incomplete. Removing a property preserves the values rather than deleting them. A property of the wrong kind is rejected with a clear message. Do not build yet: No references, daily notes or search. ### Phase 2 · Mentions that create the thing mentioned Build: Inline references that resolve to an existing object or offer to create one of a chosen type. Store references as rows. Renaming an object updates every reference because they point at an id rather than a string. Trap: References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. Done when: Mentioning a name offers to create a typed object and the reference is stored as a row. Renaming an object updates every mention, verified across five notes. Backlinks list every referring object and block. Deleting a referenced object is refused or converts references to plain text with a warning rather than leaving dangling rows. Do not build yet: No daily notes or search yet. ### Phase 3 · The daily note as the capture surface Build: A daily note that exists for every date, is itself an object, and is where capture happens. Mentions made there create objects and appear on those objects' pages with the date. No filing step. Trap: A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. Done when: The daily note exists for any date without being created by hand. A mention in today's note appears on that object's page with the date. A backlog of unfiled items is impossible because there is no filing step. Navigating between days is fast on a year of notes. Do not build yet: No search or export yet. ### Phase 4 · Finding things by what they are Build: Full text search across blocks combined with property filters and type filters. Saved queries as objects, so a saved view is itself part of the knowledge base. Trap: Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. Done when: A property filter combined with a type filter returns correct results, verified against a set built by hand. Full text and property filters combine in one query. A saved query is an object with a type. Search across ten thousand blocks returns within a reasonable time. Do not build yet: No export or sync yet. ### Phase 5 · Getting everything out Build: Export every object as Markdown with properties in front matter and references as links. Round trip: re-import an export and confirm objects, properties and references are identical. Run this as a test rather than a promise. Trap: An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. Done when: Every object exports as Markdown with front matter and linked references. Re-importing an export reproduces objects, properties and references identically, asserted in a test. The export is readable in a plain text editor. Export of ten thousand objects completes without exhausting memory. Do not build yet: Sync and backup come last. ### Phase 6 · Two machines, and never losing a note Build: Sync through a file store you control, with per-block conflict handling that keeps both versions rather than choosing. Automated backups with a tested restore. Trap: Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. Done when: Editing different blocks of one object on two machines merges both changes. Editing the same block keeps both versions visibly rather than choosing. A restore from backup reproduces objects, properties and references, verified by comparison. Sync of a large knowledge base completes without transferring everything. Do not build yet: Nothing after this. ### Out of scope (and why) - Collaboration and shared spaces, which is a different product and a much harder synchronisation problem. - Mobile capture, which is where a lot of the value of a daily note actually comes from. - AI features over the knowledge base, which is a natural next thing and needs the graph to be correct first. - Web clipping with content extraction, which is a genuinely hard problem the hosted product has invested in. ### README must contain - That objects are typed with validated properties and what happens when a schema changes - That references are stored by id so renaming is free and nothing dangles - That the daily note is the capture surface and there is no filing step - That the export round trip is tested rather than promised - That sync resolves per block and keeps both sides on a real conflict
# Agent instructions · Capacities indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: TypeScript with a local application and SQLite, Types with declared properties, stored relationally, Block-based, with inline object references, Full text plus property filters, Markdown with front matter, always available. 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 a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import.
# Build plan · Capacities A notes application built on typed objects with defined properties, where mentioning a person creates a real entity rather than a link to nothing, and where the daily note is the capture surface that feeds everything else without you filing anything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Typed objects with real properties A person has a role and a book has an author, enforced. ### Steps 1. Declare property schemas per type Kind, required, default. This is what separates this from a folder of pages. Files: `src/types.ts` 2. Validate on write A pure function against the schema, unit tested with wrong kinds. Files: `src/validate.ts`, `src/validate.test.ts` 3. Handle schema change explicitly Existing objects keep data and are marked incomplete. Decide this now, not at three hundred objects. Files: `src/migrate.ts` ### Done when - [ ] Objects validate against their type's schema - [ ] Adding a property leaves existing objects intact - [ ] Removing a property preserves values - [ ] A wrong-kind property is rejected clearly ### Watch out - Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. ## Phase 2 · Mentions that create the thing mentioned Typing a person's name in a note produces a person object, not a dead link. ### Steps 1. Reference by id, render by title So renaming is free. Text references are the mistake that shows up months in. Files: `src/reference.ts` 2. Offer creation with a type Choosing the type at mention time is what keeps the graph typed rather than generic. Files: `src/mention.ts` 3. Handle deletion of a referenced object Refuse or convert with a warning. Never leave dangling references. Files: `src/delete.ts` ### Done when - [ ] A mention creates a typed object and a reference row - [ ] Renaming updates every mention - [ ] Backlinks list referring objects and blocks - [ ] Deleting a referenced object is handled explicitly ### Watch out - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. ## Phase 3 · The daily note as the capture surface You write into today and the structure builds itself. ### Steps 1. Daily note as a first-class object So it participates in references like anything else. Files: `src/daily.ts` 2. Make the mention the filing No inbox, no later step. This is the design that survives contact with real use. Files: `src/daily.ts` 3. Show dated backlinks on objects A person's page becomes a timeline of when you mentioned them. Files: `src/backlinks.ts` ### Done when - [ ] A daily note exists for any date - [ ] A mention appears dated on the object's page - [ ] There is no filing step to fall behind on - [ ] Navigating a year of daily notes is fast ### Watch out - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. ## Phase 4 · Finding things by what they are Every book you rated above four, in one query. ### Steps 1. Index blocks for text Full text search over content, scoped by object type. Files: `src/search.ts` 2. Query properties relationally The reason the property model exists. Filters over typed values, not string matching. Files: `src/query.ts` 3. Saved queries as objects So a view is part of the knowledge base rather than application state. Files: `src/saved.ts` ### Done when - [ ] Property and type filters return correct results - [ ] Text and property filters combine - [ ] A saved query is a typed object - [ ] Ten thousand blocks search quickly ### Watch out - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. ## Phase 5 · Getting everything out Your knowledge base leaves in a form you could read in ten years. ### Steps 1. Markdown with front matter Properties in the front matter, body as Markdown. Readable without your application. Files: `src/export.ts` 2. Test the round trip Export, import, compare. A backup untested is not a backup. Files: `tests/roundtrip.test.ts` 3. Stream large exports Ten thousand objects should not need ten thousand objects of memory. Files: `src/export.ts` ### Done when - [ ] Every object exports as Markdown with front matter - [ ] A round trip reproduces objects, properties and references - [ ] The export reads plainly in a text editor - [ ] A large export completes without memory exhaustion ### Watch out - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ## Not in this build - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. ## After v1, if you want it - Object templates, so creating a meeting pre-fills the properties you always want - A graph view, once the references are trustworthy enough to be worth looking at - Scheduled review surfacing objects you have not touched, which is where a base earns its keep
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Your entire knowledge base. The single most important file to back up. DB_PATH=~/.local/share/capacities/base.db # Required. Where Markdown exports are written. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/notes-export # Required. When a new day starts, so a note written at one in the morning lands on the right day. DAILY_NOTE_START_HOUR=4 # Optional. Phase 6 only. Absent means single machine, which is a fine way to run. SYNC_REMOTE=s3://my-notes/base # Required. Automated backups. Restore-tested, or they do not count. BACKUP_DIR=~/backups/capacities
# Capacities · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Capacities, organize linked notes as typed objects such as people, books, meetings, and ideas. The hard boundary is object-model polish, mobile capture, sync, calendar integration, and ai features, plus sync, collaboration, and capture polish. ## Product outcome A knowledge base where a person is a person rather than a page, where writing in today's note is the only filing you do, and where the export round trip is a test rather than a promise. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - macOS, Windows, or Linux - local folder access - optional sync folder chosen by the user ## Explicit non-goals for v1 - Collaboration and shared spaces. - Mobile capture. - AI features over the knowledge base. - Web clipping with content extraction. - object-model polish, mobile capture, sync, calendar integration, and AI features - frictionless mobile capture - real-time team editing - hosted publishing - proprietary AI memory ## Success criteria - Renaming an object is confirmed to update every mention - A schema change is confirmed to leave existing objects intact - The export round trip is confirmed identical in the suite - A block-level sync conflict is confirmed to keep both versions - A restore is confirmed by comparison against the source
# Build brief · Capacities 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 notes where every person, book and idea is a typed object rather than a page you happened to name to replace Capacities. 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. ### Stack (fixed, do not substitute) - Runtime: TypeScript with a local application and SQLite · your notes are yours and a knowledge base is a lifetime artefact - Objects: Types with declared properties, stored relationally · the difference from a wiki is that a person has a role and a book has an author - Editor: Block-based, with inline object references · references need to be first-class, not text that looks like a link - Search: Full text plus property filters · finding every book you rated highly is a query, not a search - Export: Markdown with front matter, always available · a knowledge base you cannot leave is a knowledge base you should not start ### Data model (create this before Phase 1) - types(id, name, icon, property_schema_json) · person, book, idea, meeting, whatever you define - objects(id, type_id, title, created_at, updated_at) · every note is an object of some type - properties(object_id, key, value_json) · typed per the type's schema, validated on write - blocks(id, object_id, ordinal, kind, text, children_json) · the body content - references(from_object_id, from_block_id, to_object_id, kind) · kind is mention or property_link - daily(date, object_id) · the daily note is an object too, which is why capture and structure are the same system - A reference is a row, so backlinks are a query rather than a text search across every note. ### Phase 1 · Typed objects with real properties Build: Define types with property schemas. Create objects of a type with properties validated on write. Changing a type's schema must handle existing objects rather than orphaning their data. Trap: Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. Done when: Types define properties with kinds and objects validate against them on write. Adding a property to a type leaves existing objects intact and marked incomplete. Removing a property preserves the values rather than deleting them. A property of the wrong kind is rejected with a clear message. Do not build yet: No references, daily notes or search. ### Phase 2 · Mentions that create the thing mentioned Build: Inline references that resolve to an existing object or offer to create one of a chosen type. Store references as rows. Renaming an object updates every reference because they point at an id rather than a string. Trap: References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. Done when: Mentioning a name offers to create a typed object and the reference is stored as a row. Renaming an object updates every mention, verified across five notes. Backlinks list every referring object and block. Deleting a referenced object is refused or converts references to plain text with a warning rather than leaving dangling rows. Do not build yet: No daily notes or search yet. ### Phase 3 · The daily note as the capture surface Build: A daily note that exists for every date, is itself an object, and is where capture happens. Mentions made there create objects and appear on those objects' pages with the date. No filing step. Trap: A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. Done when: The daily note exists for any date without being created by hand. A mention in today's note appears on that object's page with the date. A backlog of unfiled items is impossible because there is no filing step. Navigating between days is fast on a year of notes. Do not build yet: No search or export yet. ### Phase 4 · Finding things by what they are Build: Full text search across blocks combined with property filters and type filters. Saved queries as objects, so a saved view is itself part of the knowledge base. Trap: Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. Done when: A property filter combined with a type filter returns correct results, verified against a set built by hand. Full text and property filters combine in one query. A saved query is an object with a type. Search across ten thousand blocks returns within a reasonable time. Do not build yet: No export or sync yet. ### Phase 5 · Getting everything out Build: Export every object as Markdown with properties in front matter and references as links. Round trip: re-import an export and confirm objects, properties and references are identical. Run this as a test rather than a promise. Trap: An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. Done when: Every object exports as Markdown with front matter and linked references. Re-importing an export reproduces objects, properties and references identically, asserted in a test. The export is readable in a plain text editor. Export of ten thousand objects completes without exhausting memory. Do not build yet: Sync and backup come last. ### Phase 6 · Two machines, and never losing a note Build: Sync through a file store you control, with per-block conflict handling that keeps both versions rather than choosing. Automated backups with a tested restore. Trap: Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict. Done when: Editing different blocks of one object on two machines merges both changes. Editing the same block keeps both versions visibly rather than choosing. A restore from backup reproduces objects, properties and references, verified by comparison. Sync of a large knowledge base completes without transferring everything. Do not build yet: Nothing after this. ### Out of scope (and why) - Collaboration and shared spaces, which is a different product and a much harder synchronisation problem. - Mobile capture, which is where a lot of the value of a daily note actually comes from. - AI features over the knowledge base, which is a natural next thing and needs the graph to be correct first. - Web clipping with content extraction, which is a genuinely hard problem the hosted product has invested in. ### README must contain - That objects are typed with validated properties and what happens when a schema changes - That references are stored by id so renaming is free and nothing dangles - That the daily note is the capture surface and there is no filing step - That the export round trip is tested rather than promised - That sync resolves per block and keeps both sides on a real conflict
# Architecture · Capacities ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | TypeScript with a local application and SQLite | your notes are yours and a knowledge base is a lifetime artefact | | Objects | Types with declared properties, stored relationally | the difference from a wiki is that a person has a role and a book has an author | | Editor | Block-based, with inline object references | references need to be first-class, not text that looks like a link | | Search | Full text plus property filters | finding every book you rated highly is a query, not a search | | Export | Markdown with front matter, always available | a knowledge base you cannot leave is a knowledge base you should not start | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Types | Property schemas and their migration. | Hard to change late. Deciding the migration behaviour early is what makes it survivable. | | References | Id-based mentions and backlinks. | Any storage. Referencing by id rather than text is the requirement. | | Daily | The capture surface with no filing step. | Not swappable for an inbox, which is the design that fails. | | Query | Text and property filtering. | Any engine. The property path is why the schema exists. | | Sync | Block-level merge and conflict retention. | Any transport, provided merge is per block and conflicts keep both sides. | ## 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 · Your entire knowledge base. The single most important file to back up. - `EXPORT_DIR` · required · Where Markdown exports are written. Somewhere your ordinary backups already reach. - `DAILY_NOTE_START_HOUR` · required · When a new day starts, so a note written at one in the morning lands on the right day. - `SYNC_REMOTE` · optional · Phase 6 only. Absent means single machine, which is a fine way to run. - `BACKUP_DIR` · required · Automated backups. Restore-tested, or they do not count. ## 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 · Capacities product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: TypeScript with a local application and SQLite, Types with declared properties, stored relationally, Block-based, with inline object references, Full text plus property filters, Markdown with front matter, always available. - 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 a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. - Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict.
# Delivery milestones · Capacities Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Typed objects with real properties A person has a role and a book has an author, enforced. ### Steps 1. Declare property schemas per type Kind, required, default. This is what separates this from a folder of pages. Files: `src/types.ts` 2. Validate on write A pure function against the schema, unit tested with wrong kinds. Files: `src/validate.ts`, `src/validate.test.ts` 3. Handle schema change explicitly Existing objects keep data and are marked incomplete. Decide this now, not at three hundred objects. Files: `src/migrate.ts` ### Done when - [ ] Objects validate against their type's schema - [ ] Adding a property leaves existing objects intact - [ ] Removing a property preserves values - [ ] A wrong-kind property is rejected clearly ### Watch out - Adding a required property to a type with three hundred existing objects either fails, silently invalidates all of them, or quietly drops the constraint. Decide the migration behaviour before you need it: existing objects keep their data and are marked incomplete rather than being rejected or rewritten. ## M2 · Mentions that create the thing mentioned Typing a person's name in a note produces a person object, not a dead link. ### Steps 1. Reference by id, render by title So renaming is free. Text references are the mistake that shows up months in. Files: `src/reference.ts` 2. Offer creation with a type Choosing the type at mention time is what keeps the graph typed rather than generic. Files: `src/mention.ts` 3. Handle deletion of a referenced object Refuse or convert with a warning. Never leave dangling references. Files: `src/delete.ts` ### Done when - [ ] A mention creates a typed object and a reference row - [ ] Renaming updates every mention - [ ] Backlinks list referring objects and blocks - [ ] Deleting a referenced object is handled explicitly ### Watch out - References stored as the displayed text mean renaming an object breaks every mention of it, and the breakage is silent because nothing checks. Store the id and render the current title, so renaming is free and a reference can never dangle. ## M3 · The daily note as the capture surface You write into today and the structure builds itself. ### Steps 1. Daily note as a first-class object So it participates in references like anything else. Files: `src/daily.ts` 2. Make the mention the filing No inbox, no later step. This is the design that survives contact with real use. Files: `src/daily.ts` 3. Show dated backlinks on objects A person's page becomes a timeline of when you mentioned them. Files: `src/backlinks.ts` ### Done when - [ ] A daily note exists for any date - [ ] A mention appears dated on the object's page - [ ] There is no filing step to fall behind on - [ ] Navigating a year of daily notes is fast ### Watch out - A capture surface that requires you to file things later accumulates an unfiled backlog and stops being used within a month. Make the mention itself the filing, so writing a person's name in today's note is already the connection and nothing needs to be moved. ## M4 · Finding things by what they are Every book you rated above four, in one query. ### Steps 1. Index blocks for text Full text search over content, scoped by object type. Files: `src/search.ts` 2. Query properties relationally The reason the property model exists. Filters over typed values, not string matching. Files: `src/query.ts` 3. Saved queries as objects So a view is part of the knowledge base rather than application state. Files: `src/saved.ts` ### Done when - [ ] Property and type filters return correct results - [ ] Text and property filters combine - [ ] A saved query is a typed object - [ ] Ten thousand blocks search quickly ### Watch out - Full text search alone cannot answer the questions typed objects exist to make answerable, and building only text search wastes the entire property model. The filter path is the reason for the schema and needs to be there from the start rather than added once the data is large. ## M5 · Getting everything out Your knowledge base leaves in a form you could read in ten years. ### Steps 1. Markdown with front matter Properties in the front matter, body as Markdown. Readable without your application. Files: `src/export.ts` 2. Test the round trip Export, import, compare. A backup untested is not a backup. Files: `tests/roundtrip.test.ts` 3. Stream large exports Ten thousand objects should not need ten thousand objects of memory. Files: `src/export.ts` ### Done when - [ ] Every object exports as Markdown with front matter - [ ] A round trip reproduces objects, properties and references - [ ] The export reads plainly in a text editor - [ ] A large export completes without memory exhaustion ### Watch out - An export that loses references or property types is a backup you cannot actually restore from, and you discover that at the moment you need it. Test the round trip in the suite, comparing objects, properties and references after re-import. ## M6 · Two machines, and never losing a note (production only) You write on a second machine and nothing is lost or duplicated. ### Steps 1. Sync at block granularity Object-level last-write-wins loses paragraphs silently. Files: `src/sync.ts` 2. Keep both sides on a real conflict Visibly, for you to resolve. Choosing for the user is choosing to lose something. Files: `src/conflict.ts` 3. Test the restore Compare against the source. Backups that have never been restored are hopes. Files: `scripts/verify-restore.sh` ### Done when - [ ] Different blocks on two machines merge - [ ] A true conflict keeps both versions - [ ] A restore reproduces everything by comparison - [ ] Sync does not retransfer the whole base ### Watch out - Last-write-wins at the object level silently destroys a paragraph written on the other machine, and because notes are rarely re-read immediately, the loss is discovered long after the version that had it is gone. Resolve at the block level and keep both sides when they genuinely conflict.
# Operations · Capacities ## Backup A knowledge base is a lifetime artefact. Automated backups, plus periodic Markdown exports as a format-independent copy, and a restore test that compares rather than merely completing. ## Restore Restore and compare objects, properties and references against the source. Confirm references resolve rather than dangle, since that is what a partial restore breaks first. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Reference integrity is the thing to check: dangling references mean a deletion path is wrong. Also watch export round trip success in the suite, since that is your escape hatch. ## Incident checklist If renaming breaks mentions, references are being stored as text somewhere and that is a data model defect rather than a display bug. If sync loses a paragraph, resolution is happening above block level. 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 - [ ] Renaming an object is confirmed to update every mention - [ ] A schema change is confirmed to leave existing objects intact - [ ] The export round trip is confirmed identical in the suite - [ ] A block-level sync conflict is confirmed to keep both versions - [ ] A restore is confirmed by comparison against the source ## Launch constraint Do not market omitted Capacities 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. Your entire knowledge base. The single most important file to back up. DB_PATH=~/.local/share/capacities/base.db # Required. Where Markdown exports are written. Somewhere your ordinary backups already reach. EXPORT_DIR=~/Documents/notes-export # Required. When a new day starts, so a note written at one in the morning lands on the right day. DAILY_NOTE_START_HOUR=4 # Optional. Phase 6 only. Absent means single machine, which is a fine way to run. SYNC_REMOTE=s3://my-notes/base # Required. Automated backups. Restore-tested, or they do not count. BACKUP_DIR=~/backups/capacities
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Capacities because people keep paying when the note system is trusted across every device and disappears into their daily capture habits. The recurring cost buys file watching, conflict handling, indexing, mobile apps, encryption, backups, and migrations, not just the visible interface.
xobject-model polish, mobile capture, sync, calendar integration, and AI features
xfrictionless mobile capture
xreal-time team editing
xhosted publishing
xproprietary AI memory
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Capacities →· no votes, no pay-to-list · just what's real
Capacities pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| capacities basic | $0/user | $0/user | Unlimited spaces, objects, blocks, custom object types and devices; 5 GB media total, then 100 MB uploads/month; 100 MB/file |
| capacities pro | $11.99/user | $9.99/user | Unlimited media uploads under fair use; 100 MB/file; AI, smart queries, calendar, tasks, formulas and API |
| capacities believer | $14.99/user | $12.49/user | All Pro limits/features plus beta access to major new features |
free tierUnlimited spaces, objects, blocks, custom object types and devices; 5 GB total media, then 100 MB uploads/month; 100 MB/file
billingmonthly + annual (-16%); free Pro trial in app, no card
hidden costsPaid 'unlimited' media is subject to fair use and a 100 MB/file cap. Paddle applies regional pricing/VAT, and VPN-based regional pricing is not accepted. No team or organization plan is sold.
verified 2026-08-13 · source ↗
Vibecode Capacities
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Capacities replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Capacities cost?
Capacities costs about $11.99/month (Capacities Pro, checked 2026-08-13), which is $143.88 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Capacities?
Honestly: object-model polish, mobile capture, sync, calendar integration, and AI features; frictionless mobile capture; real-time team editing; hosted publishing; proprietary AI memory. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Capacities?
Yes: SiYuan (Typed blocks, relations and database views for people who enjoy seeing the machinery.) Trilium Notes (Notes become objects through attributes, relations and reusable templates.) Anytype (Capacities with the same object obsession, local data and only 100 MB of free relay storage.) All 3 curated free alternatives are at vibecodeit.com/capacities/alternatives. The prompt is for when you want it exactly your way.