Vibecode TextCortex
track this build3 phases, 9 steps, beginner friendly0%TextCortex's solo core is compact: build a private AI writing workspace workspace that sends user text to one chosen model, stores versions, applies reusable instructions, and exports Markdown. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on model, data, workflow.
You are building a lean indie version of TextCortex. 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 ===== # TextCortex · indie build A local writing workspace whose hard problems are never losing the paragraph a rewrite replaced, tracking which spans a model wrote through every subsequent edit, and exporting in a way that carries that record with the text. Estimated effort: **one sitting**. 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 | Next.js 15 with TypeScript, running locally, single user | this is your drafts folder and it should live where your drafts folder lives | | Storage | SQLite beside the application, with every version retained | the version you are about to overwrite is the one you will want back | | Editor | A plain text or rich text editor with offset-addressable content | provenance is stored as ranges and needs offsets that survive editing | | Model | One call per instruction run, with the run recorded | the rewrite you liked was produced by a specific instruction and a specific model | | Provenance | Spans recorded at insertion, remapped on every edit, exported alongside the text | a single boolean saying the document is AI generated is useless by the second paragraph | ## 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 application and the test suite. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per run; drafting a few thousand words a week is small change - Why: One call per instruction run, and the only part of this build that leaves the machine or costs anything. - Get it: Create a key with your preferred provider and set a spend limit in their console as well as in the application. - Verify: Running an instruction produces a new version and a run row with token counts - [ ] **Whether you are publishing what this produces** · free - Why: The disclosure obligation attaches to publication rather than to generation, and the editorial review exemption only helps if somebody actually reviews it. - Get it: Read the current transparency article, decide which side of it your use sits on, and write that decision into the README. - Verify: The README states the decision and the date it was made - [ ] **A real document with mixed authorship** · free - Why: Provenance remapping only breaks on real editing, where text is moved, pasted over and partly rewritten, and a synthetic test document never does any of that. - Get it: Take something you actually drafted with a model and rebuild it in the application. - Verify: Every paragraph shows an origin and none of them is unknown by accident ## 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: - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows If one of those is essential to you, that is the reason to keep paying for TextCortex, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · TextCortex The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private writing workspace that keeps every version and records which spans of a document a model wrote to replace TextCortex. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two things this build does that a chat window does not, and the first is now an obligation rather than a nicety. Article 50 of the EU AI Act requires providers of systems generating synthetic text to mark the outputs in a machine-readable format and make them detectable as artificially generated, and requires deployers who publish artificially generated text to inform the public on matters of public interest to disclose that, with an exemption where the content has had human review or editorial control and a person holds editorial responsibility for it. Those obligations apply from 2 August 2026. If you publish what this produces you are the deployer, and if you run it for anyone else you are closer to the provider than you would like. So provenance is a first-class part of the data model rather than a footer. Every span of every document records whether it was typed, generated, or generated and then edited, along with the model and the date, and that record survives edits, versions and export. It is also the only honest way to answer the question in six months. Second, a refusal. There is no humanising mode, no detector-evasion setting and no claim anywhere that the output is undetectable or original. A feature whose selling point is that a machine wrote it and nobody can tell is the exact opposite of the paragraph above, and this build does not have one. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, running locally, single user · this is your drafts folder and it should live where your drafts folder lives - Storage: SQLite beside the application, with every version retained · the version you are about to overwrite is the one you will want back - Editor: A plain text or rich text editor with offset-addressable content · provenance is stored as ranges and needs offsets that survive editing - Model: One call per instruction run, with the run recorded · the rewrite you liked was produced by a specific instruction and a specific model - Provenance: Spans recorded at insertion, remapped on every edit, exported alongside the text · a single boolean saying the document is AI generated is useless by the second paragraph ### Data model (create this before Phase 1) - documents(id, title, created_at, updated_at, current_version_id) - versions(id, document_id, ordinal, body, created_at, reason, parent_version_id) · every version is kept and reason says why it exists - spans(id, version_id, start_offset, end_offset, origin, model_id, instruction_id, at) · origin is typed, generated, edited or unknown - instructions(id, name, body, created_at, archived_at) · reusable prompts, archived rather than edited in place - runs(id, version_id, instruction_id, model_id, input_hash, output_hash, tokens_in, tokens_out, cost_minor, status, at) - snippets(id, name, body) · the boilerplate you paste often, kept out of the prompt library so it is not confused with instructions - No row in this schema is ever overwritten with generated text. A generation creates a new version whose parent is the one you had. ### Phase 1 · A document that cannot lose the paragraph you liked Build: An editor with autosave writing versions rather than overwriting a body. Running an instruction creates a new version with a reason and a parent. A version list showing reason, parent and time. Restoring an older version creates a further new version rather than deleting anything. Trap: Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. Done when: Running an instruction creates a new version and the previous version's body is byte-identical to what it was, tested. Killing the process during a generation leaves the previous version current and no partial version stored, tested. The version list shows reason, parent and time for every entry. Restoring an old version creates a new version and deletes nothing, verified in the database. Do not build yet: No provenance spans, instruction library, cost tracking or export yet. ### Phase 2 · Knowing which words are yours Build: Record a span whenever text is inserted, with its origin, the model and the time. Remap offsets on every edit so spans follow the text they describe. When a person edits inside a generated span, the overlapping part becomes edited. A span that cannot be remapped becomes unknown and is displayed as unknown rather than quietly counted as typed. Trap: Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. Done when: Typed text is recorded as typed and generated text as generated, tested at insertion. Editing inside a generated span marks the overlapping range as edited and leaves the remainder generated, tested. Inserting text before a span shifts that span's offsets and the span still covers the same words, tested. A span that cannot be remapped becomes unknown and the interface shows it as unknown. Do not build yet: No instruction library, cost ceiling or export yet. ### Phase 3 · Instructions you can reproduce, and a bill that stops Build: Named instructions stored as rows, applied to a selection or the whole document. Archive rather than edit, so existing spans keep pointing at the text that produced them. Record every run with its hashes, token counts and cost. Cap the monthly spend and leave editing working when the cap is reached. Trap: Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. Done when: Editing an instruction creates a new row and archives the old one, and existing spans still resolve to the version that produced them, tested. Every run records input and output hashes, token counts and cost. Reaching the monthly ceiling blocks generation with a clear message and leaves typing, versioning and export working, tested. A failed run creates no version and no span. Do not build yet: No export or backup yet. ### Phase 4 · Export that carries the record with the text Build: Markdown export accompanied by a machine-readable provenance file naming each span, its origin, the model and the date. A JSON backup covering documents, versions, spans, instructions and runs, which reimports into an empty installation. A README section stating the marking and disclosure obligations and the date they apply from. Trap: Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. Done when: Markdown export writes a companion provenance file listing every span with origin, model and date, tested against a document containing all four origins. The JSON backup reimports into an empty installation with identical documents, versions, spans and runs, tested. No route, instruction template or setting offers to disguise generated text, asserted by a test over the route table and the shipped instruction library. The README states the obligations, their application date and where the text is published. Do not build yet: Nothing after this. ### Out of scope (and why) - A browser extension that rewrites text in other people's applications, which is a permission and maintenance burden far larger than the workspace itself. - Team workspaces, shared prompt libraries and approvals, which is what the subscription sells and which turns you into the provider rather than the deployer. - Any humanising or detection-evasion feature, refused rather than deferred. - A knowledge base with retrieval, which is a genuinely separate build and should not be bolted onto a drafts folder. ### README must contain - That every generation creates a new version and nothing is overwritten, with the restore behaviour spelled out - How provenance is recorded, what the four origins mean, and what unknown means when you see it - The marking and disclosure obligations for artificially generated text, their application date, and where the current text is published - That there is no humanising or detection-evasion feature and that a test asserts its absence - Where the database lives, what the backup contains, and how to reimport it into an empty installation ===== AGENTS.md ===== # Agent instructions · TextCortex indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, running locally, single user, SQLite beside the application, with every version retained, A plain text or rich text editor with offset-addressable content, One call per instruction run, with the run recorded, Spans recorded at insertion, remapped on every edit, exported alongside the text. 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 - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ===== BUILD_PLAN.md ===== # Build plan · TextCortex A local writing workspace whose hard problems are never losing the paragraph a rewrite replaced, tracking which spans a model wrote through every subsequent edit, and exporting in a way that carries that record with the text. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A document that cannot lose the paragraph you liked Every change that a model makes creates a new version and the previous body survives byte for byte. ### Steps 1. Make the save path incapable of overwriting a version Append only, at the storage layer rather than by convention. Files: `src/lib/versions.ts`, `tests/versions.test.ts` 2. Give every version a reason and a parent So the history reads as a story rather than a stack. Files: `src/lib/schema.sql` 3. Make restore a forward operation Going back should not destroy what you are going back from. Files: `src/lib/restore.ts`, `tests/restore.test.ts` ### Done when - [ ] A generation leaves the previous body byte-identical - [ ] A crash mid-generation stores no partial version - [ ] Versions carry reason and parent - [ ] Restore creates a version and deletes nothing ### Watch out - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. ## Phase 2 · Knowing which words are yours Provenance is recorded at insertion and survives every subsequent edit. ### Steps 1. Record origin at insertion, never by diffing later A diff between versions is a guess about who typed what. Files: `src/lib/spans.ts`, `tests/spans.test.ts` 2. Remap offsets on every edit The span has to follow the words, not the position. Files: `src/lib/remap.ts`, `tests/remap.test.ts` 3. Make unknown a visible state Better a gap you can see than a claim you cannot support. Files: `src/components/Provenance.tsx` ### Done when - [ ] Origin is recorded at insertion - [ ] Editing generated text marks it edited - [ ] Offsets survive insertions before a span - [ ] Unmappable spans become visible unknowns ### Watch out - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. ## Phase 3 · Instructions you can reproduce, and a bill that stops A rewrite that worked last week can be run again with the same instruction and the same model. ### Steps 1. Archive instructions rather than editing them An instruction referenced by old spans is history, not configuration. Files: `src/lib/instructions.ts`, `tests/instructions.test.ts` 2. Record every run with hashes and cost Reproducibility and the bill come from the same table. Files: `src/lib/runs.ts` 3. Cap the spend, keep the editor working A writing tool should not stop being a text editor. Files: `src/lib/cost.ts`, `tests/cost.test.ts` ### Done when - [ ] Editing an instruction archives the old one - [ ] Old spans still resolve to their instruction - [ ] Runs record hashes, tokens and cost - [ ] The ceiling blocks generation, not editing ### Watch out - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ## Not in this build - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. ## After v1, if you want it - A side-by-side version diff that colours by origin rather than by change - A per-document provenance summary, which is the sentence you would put under a published piece - A second model configured alongside the first, so an instruction can be compared rather than trusted ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. One call per instruction run. Set a provider-side spend limit as well. MODEL_API_KEY=sk-...redacted # Required. Recorded on every run and every generated span, because output differs by model. MODEL_ID=a text model identifier # Required. Documents, versions, spans, instructions and runs. Back this up. DATABASE_PATH=./data/writing.db # Required. Generation stops here and the editor keeps working. MONTHLY_COST_CEILING=4.00 # Required. Markdown plus the companion provenance file for each export. EXPORT_DIR=./exports
You are building a lean indie version of TextCortex. 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 ===== # TextCortex · indie build A local writing workspace whose hard problems are never losing the paragraph a rewrite replaced, tracking which spans a model wrote through every subsequent edit, and exporting in a way that carries that record with the text. Estimated effort: **one sitting**. 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 | Next.js 15 with TypeScript, running locally, single user | this is your drafts folder and it should live where your drafts folder lives | | Storage | SQLite beside the application, with every version retained | the version you are about to overwrite is the one you will want back | | Editor | A plain text or rich text editor with offset-addressable content | provenance is stored as ranges and needs offsets that survive editing | | Model | One call per instruction run, with the run recorded | the rewrite you liked was produced by a specific instruction and a specific model | | Provenance | Spans recorded at insertion, remapped on every edit, exported alongside the text | a single boolean saying the document is AI generated is useless by the second paragraph | ## 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 application and the test suite. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per run; drafting a few thousand words a week is small change - Why: One call per instruction run, and the only part of this build that leaves the machine or costs anything. - Get it: Create a key with your preferred provider and set a spend limit in their console as well as in the application. - Verify: Running an instruction produces a new version and a run row with token counts - [ ] **Whether you are publishing what this produces** · free - Why: The disclosure obligation attaches to publication rather than to generation, and the editorial review exemption only helps if somebody actually reviews it. - Get it: Read the current transparency article, decide which side of it your use sits on, and write that decision into the README. - Verify: The README states the decision and the date it was made - [ ] **A real document with mixed authorship** · free - Why: Provenance remapping only breaks on real editing, where text is moved, pasted over and partly rewritten, and a synthetic test document never does any of that. - Get it: Take something you actually drafted with a model and rebuild it in the application. - Verify: Every paragraph shows an origin and none of them is unknown by accident ## 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: - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows If one of those is essential to you, that is the reason to keep paying for TextCortex, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · TextCortex The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private writing workspace that keeps every version and records which spans of a document a model wrote to replace TextCortex. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two things this build does that a chat window does not, and the first is now an obligation rather than a nicety. Article 50 of the EU AI Act requires providers of systems generating synthetic text to mark the outputs in a machine-readable format and make them detectable as artificially generated, and requires deployers who publish artificially generated text to inform the public on matters of public interest to disclose that, with an exemption where the content has had human review or editorial control and a person holds editorial responsibility for it. Those obligations apply from 2 August 2026. If you publish what this produces you are the deployer, and if you run it for anyone else you are closer to the provider than you would like. So provenance is a first-class part of the data model rather than a footer. Every span of every document records whether it was typed, generated, or generated and then edited, along with the model and the date, and that record survives edits, versions and export. It is also the only honest way to answer the question in six months. Second, a refusal. There is no humanising mode, no detector-evasion setting and no claim anywhere that the output is undetectable or original. A feature whose selling point is that a machine wrote it and nobody can tell is the exact opposite of the paragraph above, and this build does not have one. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, running locally, single user · this is your drafts folder and it should live where your drafts folder lives - Storage: SQLite beside the application, with every version retained · the version you are about to overwrite is the one you will want back - Editor: A plain text or rich text editor with offset-addressable content · provenance is stored as ranges and needs offsets that survive editing - Model: One call per instruction run, with the run recorded · the rewrite you liked was produced by a specific instruction and a specific model - Provenance: Spans recorded at insertion, remapped on every edit, exported alongside the text · a single boolean saying the document is AI generated is useless by the second paragraph ### Data model (create this before Phase 1) - documents(id, title, created_at, updated_at, current_version_id) - versions(id, document_id, ordinal, body, created_at, reason, parent_version_id) · every version is kept and reason says why it exists - spans(id, version_id, start_offset, end_offset, origin, model_id, instruction_id, at) · origin is typed, generated, edited or unknown - instructions(id, name, body, created_at, archived_at) · reusable prompts, archived rather than edited in place - runs(id, version_id, instruction_id, model_id, input_hash, output_hash, tokens_in, tokens_out, cost_minor, status, at) - snippets(id, name, body) · the boilerplate you paste often, kept out of the prompt library so it is not confused with instructions - No row in this schema is ever overwritten with generated text. A generation creates a new version whose parent is the one you had. ### Phase 1 · A document that cannot lose the paragraph you liked Build: An editor with autosave writing versions rather than overwriting a body. Running an instruction creates a new version with a reason and a parent. A version list showing reason, parent and time. Restoring an older version creates a further new version rather than deleting anything. Trap: Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. Done when: Running an instruction creates a new version and the previous version's body is byte-identical to what it was, tested. Killing the process during a generation leaves the previous version current and no partial version stored, tested. The version list shows reason, parent and time for every entry. Restoring an old version creates a new version and deletes nothing, verified in the database. Do not build yet: No provenance spans, instruction library, cost tracking or export yet. ### Phase 2 · Knowing which words are yours Build: Record a span whenever text is inserted, with its origin, the model and the time. Remap offsets on every edit so spans follow the text they describe. When a person edits inside a generated span, the overlapping part becomes edited. A span that cannot be remapped becomes unknown and is displayed as unknown rather than quietly counted as typed. Trap: Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. Done when: Typed text is recorded as typed and generated text as generated, tested at insertion. Editing inside a generated span marks the overlapping range as edited and leaves the remainder generated, tested. Inserting text before a span shifts that span's offsets and the span still covers the same words, tested. A span that cannot be remapped becomes unknown and the interface shows it as unknown. Do not build yet: No instruction library, cost ceiling or export yet. ### Phase 3 · Instructions you can reproduce, and a bill that stops Build: Named instructions stored as rows, applied to a selection or the whole document. Archive rather than edit, so existing spans keep pointing at the text that produced them. Record every run with its hashes, token counts and cost. Cap the monthly spend and leave editing working when the cap is reached. Trap: Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. Done when: Editing an instruction creates a new row and archives the old one, and existing spans still resolve to the version that produced them, tested. Every run records input and output hashes, token counts and cost. Reaching the monthly ceiling blocks generation with a clear message and leaves typing, versioning and export working, tested. A failed run creates no version and no span. Do not build yet: No export or backup yet. ### Phase 4 · Export that carries the record with the text Build: Markdown export accompanied by a machine-readable provenance file naming each span, its origin, the model and the date. A JSON backup covering documents, versions, spans, instructions and runs, which reimports into an empty installation. A README section stating the marking and disclosure obligations and the date they apply from. Trap: Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. Done when: Markdown export writes a companion provenance file listing every span with origin, model and date, tested against a document containing all four origins. The JSON backup reimports into an empty installation with identical documents, versions, spans and runs, tested. No route, instruction template or setting offers to disguise generated text, asserted by a test over the route table and the shipped instruction library. The README states the obligations, their application date and where the text is published. Do not build yet: Nothing after this. ### Out of scope (and why) - A browser extension that rewrites text in other people's applications, which is a permission and maintenance burden far larger than the workspace itself. - Team workspaces, shared prompt libraries and approvals, which is what the subscription sells and which turns you into the provider rather than the deployer. - Any humanising or detection-evasion feature, refused rather than deferred. - A knowledge base with retrieval, which is a genuinely separate build and should not be bolted onto a drafts folder. ### README must contain - That every generation creates a new version and nothing is overwritten, with the restore behaviour spelled out - How provenance is recorded, what the four origins mean, and what unknown means when you see it - The marking and disclosure obligations for artificially generated text, their application date, and where the current text is published - That there is no humanising or detection-evasion feature and that a test asserts its absence - Where the database lives, what the backup contains, and how to reimport it into an empty installation ===== AGENTS.md ===== # Agent instructions · TextCortex indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, running locally, single user, SQLite beside the application, with every version retained, A plain text or rich text editor with offset-addressable content, One call per instruction run, with the run recorded, Spans recorded at insertion, remapped on every edit, exported alongside the text. 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 - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ===== BUILD_PLAN.md ===== # Build plan · TextCortex A local writing workspace whose hard problems are never losing the paragraph a rewrite replaced, tracking which spans a model wrote through every subsequent edit, and exporting in a way that carries that record with the text. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A document that cannot lose the paragraph you liked Every change that a model makes creates a new version and the previous body survives byte for byte. ### Steps 1. Make the save path incapable of overwriting a version Append only, at the storage layer rather than by convention. Files: `src/lib/versions.ts`, `tests/versions.test.ts` 2. Give every version a reason and a parent So the history reads as a story rather than a stack. Files: `src/lib/schema.sql` 3. Make restore a forward operation Going back should not destroy what you are going back from. Files: `src/lib/restore.ts`, `tests/restore.test.ts` ### Done when - [ ] A generation leaves the previous body byte-identical - [ ] A crash mid-generation stores no partial version - [ ] Versions carry reason and parent - [ ] Restore creates a version and deletes nothing ### Watch out - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. ## Phase 2 · Knowing which words are yours Provenance is recorded at insertion and survives every subsequent edit. ### Steps 1. Record origin at insertion, never by diffing later A diff between versions is a guess about who typed what. Files: `src/lib/spans.ts`, `tests/spans.test.ts` 2. Remap offsets on every edit The span has to follow the words, not the position. Files: `src/lib/remap.ts`, `tests/remap.test.ts` 3. Make unknown a visible state Better a gap you can see than a claim you cannot support. Files: `src/components/Provenance.tsx` ### Done when - [ ] Origin is recorded at insertion - [ ] Editing generated text marks it edited - [ ] Offsets survive insertions before a span - [ ] Unmappable spans become visible unknowns ### Watch out - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. ## Phase 3 · Instructions you can reproduce, and a bill that stops A rewrite that worked last week can be run again with the same instruction and the same model. ### Steps 1. Archive instructions rather than editing them An instruction referenced by old spans is history, not configuration. Files: `src/lib/instructions.ts`, `tests/instructions.test.ts` 2. Record every run with hashes and cost Reproducibility and the bill come from the same table. Files: `src/lib/runs.ts` 3. Cap the spend, keep the editor working A writing tool should not stop being a text editor. Files: `src/lib/cost.ts`, `tests/cost.test.ts` ### Done when - [ ] Editing an instruction archives the old one - [ ] Old spans still resolve to their instruction - [ ] Runs record hashes, tokens and cost - [ ] The ceiling blocks generation, not editing ### Watch out - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ## Not in this build - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. ## After v1, if you want it - A side-by-side version diff that colours by origin rather than by change - A per-document provenance summary, which is the sentence you would put under a published piece - A second model configured alongside the first, so an instruction can be compared rather than trusted ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. One call per instruction run. Set a provider-side spend limit as well. MODEL_API_KEY=sk-...redacted # Required. Recorded on every run and every generated span, because output differs by model. MODEL_ID=a text model identifier # Required. Documents, versions, spans, instructions and runs. Back this up. DATABASE_PATH=./data/writing.db # Required. Generation stops here and the editor keeps working. MONTHLY_COST_CEILING=4.00 # Required. Markdown plus the companion provenance file for each export. EXPORT_DIR=./exports
You are building a production product version of TextCortex. 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 ===== # TextCortex · product brief ## Problem TextCortex's solo core is compact: build a private AI writing workspace workspace that sends user text to one chosen model, stores versions, applies reusable instructions, and exports Markdown. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on model, data, workflow. ## Product outcome A drafts folder with a model attached that never overwrites what you had, knows which words it wrote, can reproduce the instruction that wrote them, and exports the record alongside the text. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - OpenAI API key in .env - Node.js 22 - SQLite database ## Explicit non-goals for v1 - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows ## Success criteria - A generation is confirmed to leave the previous body byte-identical - Editing generated text is confirmed to mark it edited - Span offsets are confirmed to survive insertions before them - The export is confirmed to carry a provenance companion file - No evasion or humanising feature is confirmed to exist ===== BRIEF.md ===== # Build brief · TextCortex The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private writing workspace that keeps every version and records which spans of a document a model wrote to replace TextCortex. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two things this build does that a chat window does not, and the first is now an obligation rather than a nicety. Article 50 of the EU AI Act requires providers of systems generating synthetic text to mark the outputs in a machine-readable format and make them detectable as artificially generated, and requires deployers who publish artificially generated text to inform the public on matters of public interest to disclose that, with an exemption where the content has had human review or editorial control and a person holds editorial responsibility for it. Those obligations apply from 2 August 2026. If you publish what this produces you are the deployer, and if you run it for anyone else you are closer to the provider than you would like. So provenance is a first-class part of the data model rather than a footer. Every span of every document records whether it was typed, generated, or generated and then edited, along with the model and the date, and that record survives edits, versions and export. It is also the only honest way to answer the question in six months. Second, a refusal. There is no humanising mode, no detector-evasion setting and no claim anywhere that the output is undetectable or original. A feature whose selling point is that a machine wrote it and nobody can tell is the exact opposite of the paragraph above, and this build does not have one. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, running locally, single user · this is your drafts folder and it should live where your drafts folder lives - Storage: SQLite beside the application, with every version retained · the version you are about to overwrite is the one you will want back - Editor: A plain text or rich text editor with offset-addressable content · provenance is stored as ranges and needs offsets that survive editing - Model: One call per instruction run, with the run recorded · the rewrite you liked was produced by a specific instruction and a specific model - Provenance: Spans recorded at insertion, remapped on every edit, exported alongside the text · a single boolean saying the document is AI generated is useless by the second paragraph ### Data model (create this before Phase 1) - documents(id, title, created_at, updated_at, current_version_id) - versions(id, document_id, ordinal, body, created_at, reason, parent_version_id) · every version is kept and reason says why it exists - spans(id, version_id, start_offset, end_offset, origin, model_id, instruction_id, at) · origin is typed, generated, edited or unknown - instructions(id, name, body, created_at, archived_at) · reusable prompts, archived rather than edited in place - runs(id, version_id, instruction_id, model_id, input_hash, output_hash, tokens_in, tokens_out, cost_minor, status, at) - snippets(id, name, body) · the boilerplate you paste often, kept out of the prompt library so it is not confused with instructions - No row in this schema is ever overwritten with generated text. A generation creates a new version whose parent is the one you had. ### Phase 1 · A document that cannot lose the paragraph you liked Build: An editor with autosave writing versions rather than overwriting a body. Running an instruction creates a new version with a reason and a parent. A version list showing reason, parent and time. Restoring an older version creates a further new version rather than deleting anything. Trap: Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. Done when: Running an instruction creates a new version and the previous version's body is byte-identical to what it was, tested. Killing the process during a generation leaves the previous version current and no partial version stored, tested. The version list shows reason, parent and time for every entry. Restoring an old version creates a new version and deletes nothing, verified in the database. Do not build yet: No provenance spans, instruction library, cost tracking or export yet. ### Phase 2 · Knowing which words are yours Build: Record a span whenever text is inserted, with its origin, the model and the time. Remap offsets on every edit so spans follow the text they describe. When a person edits inside a generated span, the overlapping part becomes edited. A span that cannot be remapped becomes unknown and is displayed as unknown rather than quietly counted as typed. Trap: Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. Done when: Typed text is recorded as typed and generated text as generated, tested at insertion. Editing inside a generated span marks the overlapping range as edited and leaves the remainder generated, tested. Inserting text before a span shifts that span's offsets and the span still covers the same words, tested. A span that cannot be remapped becomes unknown and the interface shows it as unknown. Do not build yet: No instruction library, cost ceiling or export yet. ### Phase 3 · Instructions you can reproduce, and a bill that stops Build: Named instructions stored as rows, applied to a selection or the whole document. Archive rather than edit, so existing spans keep pointing at the text that produced them. Record every run with its hashes, token counts and cost. Cap the monthly spend and leave editing working when the cap is reached. Trap: Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. Done when: Editing an instruction creates a new row and archives the old one, and existing spans still resolve to the version that produced them, tested. Every run records input and output hashes, token counts and cost. Reaching the monthly ceiling blocks generation with a clear message and leaves typing, versioning and export working, tested. A failed run creates no version and no span. Do not build yet: No export or backup yet. ### Phase 4 · Export that carries the record with the text Build: Markdown export accompanied by a machine-readable provenance file naming each span, its origin, the model and the date. A JSON backup covering documents, versions, spans, instructions and runs, which reimports into an empty installation. A README section stating the marking and disclosure obligations and the date they apply from. Trap: Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. Done when: Markdown export writes a companion provenance file listing every span with origin, model and date, tested against a document containing all four origins. The JSON backup reimports into an empty installation with identical documents, versions, spans and runs, tested. No route, instruction template or setting offers to disguise generated text, asserted by a test over the route table and the shipped instruction library. The README states the obligations, their application date and where the text is published. Do not build yet: Nothing after this. ### Out of scope (and why) - A browser extension that rewrites text in other people's applications, which is a permission and maintenance burden far larger than the workspace itself. - Team workspaces, shared prompt libraries and approvals, which is what the subscription sells and which turns you into the provider rather than the deployer. - Any humanising or detection-evasion feature, refused rather than deferred. - A knowledge base with retrieval, which is a genuinely separate build and should not be bolted onto a drafts folder. ### README must contain - That every generation creates a new version and nothing is overwritten, with the restore behaviour spelled out - How provenance is recorded, what the four origins mean, and what unknown means when you see it - The marking and disclosure obligations for artificially generated text, their application date, and where the current text is published - That there is no humanising or detection-evasion feature and that a test asserts its absence - Where the database lives, what the backup contains, and how to reimport it into an empty installation ===== ARCHITECTURE.md ===== # Architecture · TextCortex ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 with TypeScript, running locally, single user | this is your drafts folder and it should live where your drafts folder lives | | Storage | SQLite beside the application, with every version retained | the version you are about to overwrite is the one you will want back | | Editor | A plain text or rich text editor with offset-addressable content | provenance is stored as ranges and needs offsets that survive editing | | Model | One call per instruction run, with the run recorded | the rewrite you liked was produced by a specific instruction and a specific model | | Provenance | Spans recorded at insertion, remapped on every edit, exported alongside the text | a single boolean saying the document is AI generated is useless by the second paragraph | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Versions | Append-only bodies, parents and restore. | Any store, provided no path overwrites a version. | | Spans | Origin at insertion and offset remapping. | Any editor, provided offsets are addressable and remappable. | | Instructions | Named prompts, archiving and run records. | Any prompt store, provided editing archives rather than mutates. | | Cost | Token accounting and the ceiling. | Any accounting, provided the editor survives the ceiling. | | Export | Markdown, the provenance companion and the backup. | Any format, provided provenance leaves with the text. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MODEL_API_KEY` · required, secret · One call per instruction run. Set a provider-side spend limit as well. - `MODEL_ID` · required · Recorded on every run and every generated span, because output differs by model. - `DATABASE_PATH` · required · Documents, versions, spans, instructions and runs. Back this up. - `MONTHLY_COST_CEILING` · required · Generation stops here and the editor keeps working. - `EXPORT_DIR` · required · Markdown plus the companion provenance file for each export. ## 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 · TextCortex product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 with TypeScript, running locally, single user, SQLite beside the application, with every version retained, A plain text or rich text editor with offset-addressable content, One call per instruction run, with the run recorded, Spans recorded at insertion, remapped on every edit, exported alongside the text. - 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 - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. - Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. ===== MILESTONES.md ===== # Delivery milestones · TextCortex Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A document that cannot lose the paragraph you liked Every change that a model makes creates a new version and the previous body survives byte for byte. ### Steps 1. Make the save path incapable of overwriting a version Append only, at the storage layer rather than by convention. Files: `src/lib/versions.ts`, `tests/versions.test.ts` 2. Give every version a reason and a parent So the history reads as a story rather than a stack. Files: `src/lib/schema.sql` 3. Make restore a forward operation Going back should not destroy what you are going back from. Files: `src/lib/restore.ts`, `tests/restore.test.ts` ### Done when - [ ] A generation leaves the previous body byte-identical - [ ] A crash mid-generation stores no partial version - [ ] Versions carry reason and parent - [ ] Restore creates a version and deletes nothing ### Watch out - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. ## M2 · Knowing which words are yours Provenance is recorded at insertion and survives every subsequent edit. ### Steps 1. Record origin at insertion, never by diffing later A diff between versions is a guess about who typed what. Files: `src/lib/spans.ts`, `tests/spans.test.ts` 2. Remap offsets on every edit The span has to follow the words, not the position. Files: `src/lib/remap.ts`, `tests/remap.test.ts` 3. Make unknown a visible state Better a gap you can see than a claim you cannot support. Files: `src/components/Provenance.tsx` ### Done when - [ ] Origin is recorded at insertion - [ ] Editing generated text marks it edited - [ ] Offsets survive insertions before a span - [ ] Unmappable spans become visible unknowns ### Watch out - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. ## M3 · Instructions you can reproduce, and a bill that stops A rewrite that worked last week can be run again with the same instruction and the same model. ### Steps 1. Archive instructions rather than editing them An instruction referenced by old spans is history, not configuration. Files: `src/lib/instructions.ts`, `tests/instructions.test.ts` 2. Record every run with hashes and cost Reproducibility and the bill come from the same table. Files: `src/lib/runs.ts` 3. Cap the spend, keep the editor working A writing tool should not stop being a text editor. Files: `src/lib/cost.ts`, `tests/cost.test.ts` ### Done when - [ ] Editing an instruction archives the old one - [ ] Old spans still resolve to their instruction - [ ] Runs record hashes, tokens and cost - [ ] The ceiling blocks generation, not editing ### Watch out - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ## M4 · Export that carries the record with the text (production only) What leaves this application says which parts a model wrote. ### Steps 1. Write provenance as a companion file Named in the export dialogue so nobody exports half of it. Files: `src/lib/export.ts`, `tests/export.test.ts` 2. Make the JSON backup complete and tested Every table, round-tripped, or it is not a backup. Files: `src/lib/backup.ts`, `tests/roundtrip.test.ts` 3. Assert that no evasion feature exists A test is a better commitment than a promise in a README. Files: `tests/no-evasion.test.ts`, `README.md` ### Done when - [ ] Markdown export has a provenance companion file - [ ] The JSON backup round-trips every table - [ ] No evasion or humanising feature exists - [ ] The README states the obligations and their date ### Watch out - Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. ===== OPERATIONS.md ===== # Operations · TextCortex ## Backup One SQLite file holding drafts, versions and the provenance record. Copy it on a schedule, and test the reimport once rather than assuming it, because the provenance is the part that cannot be reconstructed from the text. ## Restore Restore the file, confirm the document and version counts, then open a document with mixed authorship and confirm the spans still line up with the words they describe. Spans that have drifted mean the remapping state did not survive. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Monthly spend against the ceiling, failed run rate, and the proportion of spans marked unknown. A rising unknown proportion means the remapping is losing track during ordinary editing, which is the failure that matters here. ## Incident checklist The incident is a published document whose provenance was wrong. Every version, span and run is retained, so the question of what the model produced and what you changed has an answer rather than a recollection. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A generation is confirmed to leave the previous body byte-identical - [ ] Editing generated text is confirmed to mark it edited - [ ] Span offsets are confirmed to survive insertions before them - [ ] The export is confirmed to carry a provenance companion file - [ ] No evasion or humanising feature is confirmed to exist ## Launch constraint Do not market omitted TextCortex 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 · secret. One call per instruction run. Set a provider-side spend limit as well. MODEL_API_KEY=sk-...redacted # Required. Recorded on every run and every generated span, because output differs by model. MODEL_ID=a text model identifier # Required. Documents, versions, spans, instructions and runs. Back this up. DATABASE_PATH=./data/writing.db # Required. Generation stops here and the editor keeps working. MONTHLY_COST_CEILING=4.00 # Required. Markdown plus the companion provenance file for each export. EXPORT_DIR=./exports
# TextCortex · indie build A local writing workspace whose hard problems are never losing the paragraph a rewrite replaced, tracking which spans a model wrote through every subsequent edit, and exporting in a way that carries that record with the text. Estimated effort: **one sitting**. 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 | Next.js 15 with TypeScript, running locally, single user | this is your drafts folder and it should live where your drafts folder lives | | Storage | SQLite beside the application, with every version retained | the version you are about to overwrite is the one you will want back | | Editor | A plain text or rich text editor with offset-addressable content | provenance is stored as ranges and needs offsets that survive editing | | Model | One call per instruction run, with the run recorded | the rewrite you liked was produced by a specific instruction and a specific model | | Provenance | Spans recorded at insertion, remapped on every edit, exported alongside the text | a single boolean saying the document is AI generated is useless by the second paragraph | ## 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 application and the test suite. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **An API key for a text model** · pay per run; drafting a few thousand words a week is small change - Why: One call per instruction run, and the only part of this build that leaves the machine or costs anything. - Get it: Create a key with your preferred provider and set a spend limit in their console as well as in the application. - Verify: Running an instruction produces a new version and a run row with token counts - [ ] **Whether you are publishing what this produces** · free - Why: The disclosure obligation attaches to publication rather than to generation, and the editorial review exemption only helps if somebody actually reviews it. - Get it: Read the current transparency article, decide which side of it your use sits on, and write that decision into the README. - Verify: The README states the decision and the date it was made - [ ] **A real document with mixed authorship** · free - Why: Provenance remapping only breaks on real editing, where text is moved, pasted over and partly rewritten, and a synthetic test document never does any of that. - Get it: Take something you actually drafted with a model and rebuild it in the application. - Verify: Every paragraph shows an origin and none of them is unknown by accident ## 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: - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows If one of those is essential to you, that is the reason to keep paying for TextCortex, and the README should say so rather than pretend.
# Build brief · TextCortex The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private writing workspace that keeps every version and records which spans of a document a model wrote to replace TextCortex. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two things this build does that a chat window does not, and the first is now an obligation rather than a nicety. Article 50 of the EU AI Act requires providers of systems generating synthetic text to mark the outputs in a machine-readable format and make them detectable as artificially generated, and requires deployers who publish artificially generated text to inform the public on matters of public interest to disclose that, with an exemption where the content has had human review or editorial control and a person holds editorial responsibility for it. Those obligations apply from 2 August 2026. If you publish what this produces you are the deployer, and if you run it for anyone else you are closer to the provider than you would like. So provenance is a first-class part of the data model rather than a footer. Every span of every document records whether it was typed, generated, or generated and then edited, along with the model and the date, and that record survives edits, versions and export. It is also the only honest way to answer the question in six months. Second, a refusal. There is no humanising mode, no detector-evasion setting and no claim anywhere that the output is undetectable or original. A feature whose selling point is that a machine wrote it and nobody can tell is the exact opposite of the paragraph above, and this build does not have one. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, running locally, single user · this is your drafts folder and it should live where your drafts folder lives - Storage: SQLite beside the application, with every version retained · the version you are about to overwrite is the one you will want back - Editor: A plain text or rich text editor with offset-addressable content · provenance is stored as ranges and needs offsets that survive editing - Model: One call per instruction run, with the run recorded · the rewrite you liked was produced by a specific instruction and a specific model - Provenance: Spans recorded at insertion, remapped on every edit, exported alongside the text · a single boolean saying the document is AI generated is useless by the second paragraph ### Data model (create this before Phase 1) - documents(id, title, created_at, updated_at, current_version_id) - versions(id, document_id, ordinal, body, created_at, reason, parent_version_id) · every version is kept and reason says why it exists - spans(id, version_id, start_offset, end_offset, origin, model_id, instruction_id, at) · origin is typed, generated, edited or unknown - instructions(id, name, body, created_at, archived_at) · reusable prompts, archived rather than edited in place - runs(id, version_id, instruction_id, model_id, input_hash, output_hash, tokens_in, tokens_out, cost_minor, status, at) - snippets(id, name, body) · the boilerplate you paste often, kept out of the prompt library so it is not confused with instructions - No row in this schema is ever overwritten with generated text. A generation creates a new version whose parent is the one you had. ### Phase 1 · A document that cannot lose the paragraph you liked Build: An editor with autosave writing versions rather than overwriting a body. Running an instruction creates a new version with a reason and a parent. A version list showing reason, parent and time. Restoring an older version creates a further new version rather than deleting anything. Trap: Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. Done when: Running an instruction creates a new version and the previous version's body is byte-identical to what it was, tested. Killing the process during a generation leaves the previous version current and no partial version stored, tested. The version list shows reason, parent and time for every entry. Restoring an old version creates a new version and deletes nothing, verified in the database. Do not build yet: No provenance spans, instruction library, cost tracking or export yet. ### Phase 2 · Knowing which words are yours Build: Record a span whenever text is inserted, with its origin, the model and the time. Remap offsets on every edit so spans follow the text they describe. When a person edits inside a generated span, the overlapping part becomes edited. A span that cannot be remapped becomes unknown and is displayed as unknown rather than quietly counted as typed. Trap: Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. Done when: Typed text is recorded as typed and generated text as generated, tested at insertion. Editing inside a generated span marks the overlapping range as edited and leaves the remainder generated, tested. Inserting text before a span shifts that span's offsets and the span still covers the same words, tested. A span that cannot be remapped becomes unknown and the interface shows it as unknown. Do not build yet: No instruction library, cost ceiling or export yet. ### Phase 3 · Instructions you can reproduce, and a bill that stops Build: Named instructions stored as rows, applied to a selection or the whole document. Archive rather than edit, so existing spans keep pointing at the text that produced them. Record every run with its hashes, token counts and cost. Cap the monthly spend and leave editing working when the cap is reached. Trap: Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. Done when: Editing an instruction creates a new row and archives the old one, and existing spans still resolve to the version that produced them, tested. Every run records input and output hashes, token counts and cost. Reaching the monthly ceiling blocks generation with a clear message and leaves typing, versioning and export working, tested. A failed run creates no version and no span. Do not build yet: No export or backup yet. ### Phase 4 · Export that carries the record with the text Build: Markdown export accompanied by a machine-readable provenance file naming each span, its origin, the model and the date. A JSON backup covering documents, versions, spans, instructions and runs, which reimports into an empty installation. A README section stating the marking and disclosure obligations and the date they apply from. Trap: Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. Done when: Markdown export writes a companion provenance file listing every span with origin, model and date, tested against a document containing all four origins. The JSON backup reimports into an empty installation with identical documents, versions, spans and runs, tested. No route, instruction template or setting offers to disguise generated text, asserted by a test over the route table and the shipped instruction library. The README states the obligations, their application date and where the text is published. Do not build yet: Nothing after this. ### Out of scope (and why) - A browser extension that rewrites text in other people's applications, which is a permission and maintenance burden far larger than the workspace itself. - Team workspaces, shared prompt libraries and approvals, which is what the subscription sells and which turns you into the provider rather than the deployer. - Any humanising or detection-evasion feature, refused rather than deferred. - A knowledge base with retrieval, which is a genuinely separate build and should not be bolted onto a drafts folder. ### README must contain - That every generation creates a new version and nothing is overwritten, with the restore behaviour spelled out - How provenance is recorded, what the four origins mean, and what unknown means when you see it - The marking and disclosure obligations for artificially generated text, their application date, and where the current text is published - That there is no humanising or detection-evasion feature and that a test asserts its absence - Where the database lives, what the backup contains, and how to reimport it into an empty installation
# Agent instructions · TextCortex indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Next.js 15 with TypeScript, running locally, single user, SQLite beside the application, with every version retained, A plain text or rich text editor with offset-addressable content, One call per instruction run, with the run recorded, Spans recorded at insertion, remapped on every edit, exported alongside the text. 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 - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it.
# Build plan · TextCortex A local writing workspace whose hard problems are never losing the paragraph a rewrite replaced, tracking which spans a model wrote through every subsequent edit, and exporting in a way that carries that record with the text. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A document that cannot lose the paragraph you liked Every change that a model makes creates a new version and the previous body survives byte for byte. ### Steps 1. Make the save path incapable of overwriting a version Append only, at the storage layer rather than by convention. Files: `src/lib/versions.ts`, `tests/versions.test.ts` 2. Give every version a reason and a parent So the history reads as a story rather than a stack. Files: `src/lib/schema.sql` 3. Make restore a forward operation Going back should not destroy what you are going back from. Files: `src/lib/restore.ts`, `tests/restore.test.ts` ### Done when - [ ] A generation leaves the previous body byte-identical - [ ] A crash mid-generation stores no partial version - [ ] Versions carry reason and parent - [ ] Restore creates a version and deletes nothing ### Watch out - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. ## Phase 2 · Knowing which words are yours Provenance is recorded at insertion and survives every subsequent edit. ### Steps 1. Record origin at insertion, never by diffing later A diff between versions is a guess about who typed what. Files: `src/lib/spans.ts`, `tests/spans.test.ts` 2. Remap offsets on every edit The span has to follow the words, not the position. Files: `src/lib/remap.ts`, `tests/remap.test.ts` 3. Make unknown a visible state Better a gap you can see than a claim you cannot support. Files: `src/components/Provenance.tsx` ### Done when - [ ] Origin is recorded at insertion - [ ] Editing generated text marks it edited - [ ] Offsets survive insertions before a span - [ ] Unmappable spans become visible unknowns ### Watch out - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. ## Phase 3 · Instructions you can reproduce, and a bill that stops A rewrite that worked last week can be run again with the same instruction and the same model. ### Steps 1. Archive instructions rather than editing them An instruction referenced by old spans is history, not configuration. Files: `src/lib/instructions.ts`, `tests/instructions.test.ts` 2. Record every run with hashes and cost Reproducibility and the bill come from the same table. Files: `src/lib/runs.ts` 3. Cap the spend, keep the editor working A writing tool should not stop being a text editor. Files: `src/lib/cost.ts`, `tests/cost.test.ts` ### Done when - [ ] Editing an instruction archives the old one - [ ] Old spans still resolve to their instruction - [ ] Runs record hashes, tokens and cost - [ ] The ceiling blocks generation, not editing ### Watch out - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ## Not in this build - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. ## After v1, if you want it - A side-by-side version diff that colours by origin rather than by change - A per-document provenance summary, which is the sentence you would put under a published piece - A second model configured alongside the first, so an instruction can be compared rather than trusted
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. One call per instruction run. Set a provider-side spend limit as well. MODEL_API_KEY=sk-...redacted # Required. Recorded on every run and every generated span, because output differs by model. MODEL_ID=a text model identifier # Required. Documents, versions, spans, instructions and runs. Back this up. DATABASE_PATH=./data/writing.db # Required. Generation stops here and the editor keeps working. MONTHLY_COST_CEILING=4.00 # Required. Markdown plus the companion provenance file for each export. EXPORT_DIR=./exports
# TextCortex · product brief ## Problem TextCortex's solo core is compact: build a private AI writing workspace workspace that sends user text to one chosen model, stores versions, applies reusable instructions, and exports Markdown. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on model, data, workflow. ## Product outcome A drafts folder with a model attached that never overwrites what you had, knows which words it wrote, can reproduce the instruction that wrote them, and exports the record alongside the text. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - OpenAI API key in .env - Node.js 22 - SQLite database ## Explicit non-goals for v1 - A browser extension for other applications. - Team workspaces and shared prompt libraries. - Any humanising or detection-evasion feature. - A retrieval-backed knowledge base. - team governance and integrations - vendor-managed prompt and quality tuning - proprietary models or classifiers - brand-trained workflows ## Success criteria - A generation is confirmed to leave the previous body byte-identical - Editing generated text is confirmed to mark it edited - Span offsets are confirmed to survive insertions before them - The export is confirmed to carry a provenance companion file - No evasion or humanising feature is confirmed to exist
# Build brief · TextCortex The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private writing workspace that keeps every version and records which spans of a document a model wrote to replace TextCortex. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two things this build does that a chat window does not, and the first is now an obligation rather than a nicety. Article 50 of the EU AI Act requires providers of systems generating synthetic text to mark the outputs in a machine-readable format and make them detectable as artificially generated, and requires deployers who publish artificially generated text to inform the public on matters of public interest to disclose that, with an exemption where the content has had human review or editorial control and a person holds editorial responsibility for it. Those obligations apply from 2 August 2026. If you publish what this produces you are the deployer, and if you run it for anyone else you are closer to the provider than you would like. So provenance is a first-class part of the data model rather than a footer. Every span of every document records whether it was typed, generated, or generated and then edited, along with the model and the date, and that record survives edits, versions and export. It is also the only honest way to answer the question in six months. Second, a refusal. There is no humanising mode, no detector-evasion setting and no claim anywhere that the output is undetectable or original. A feature whose selling point is that a machine wrote it and nobody can tell is the exact opposite of the paragraph above, and this build does not have one. ### Stack (fixed, do not substitute) - Runtime: Next.js 15 with TypeScript, running locally, single user · this is your drafts folder and it should live where your drafts folder lives - Storage: SQLite beside the application, with every version retained · the version you are about to overwrite is the one you will want back - Editor: A plain text or rich text editor with offset-addressable content · provenance is stored as ranges and needs offsets that survive editing - Model: One call per instruction run, with the run recorded · the rewrite you liked was produced by a specific instruction and a specific model - Provenance: Spans recorded at insertion, remapped on every edit, exported alongside the text · a single boolean saying the document is AI generated is useless by the second paragraph ### Data model (create this before Phase 1) - documents(id, title, created_at, updated_at, current_version_id) - versions(id, document_id, ordinal, body, created_at, reason, parent_version_id) · every version is kept and reason says why it exists - spans(id, version_id, start_offset, end_offset, origin, model_id, instruction_id, at) · origin is typed, generated, edited or unknown - instructions(id, name, body, created_at, archived_at) · reusable prompts, archived rather than edited in place - runs(id, version_id, instruction_id, model_id, input_hash, output_hash, tokens_in, tokens_out, cost_minor, status, at) - snippets(id, name, body) · the boilerplate you paste often, kept out of the prompt library so it is not confused with instructions - No row in this schema is ever overwritten with generated text. A generation creates a new version whose parent is the one you had. ### Phase 1 · A document that cannot lose the paragraph you liked Build: An editor with autosave writing versions rather than overwriting a body. Running an instruction creates a new version with a reason and a parent. A version list showing reason, parent and time. Restoring an older version creates a further new version rather than deleting anything. Trap: Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. Done when: Running an instruction creates a new version and the previous version's body is byte-identical to what it was, tested. Killing the process during a generation leaves the previous version current and no partial version stored, tested. The version list shows reason, parent and time for every entry. Restoring an old version creates a new version and deletes nothing, verified in the database. Do not build yet: No provenance spans, instruction library, cost tracking or export yet. ### Phase 2 · Knowing which words are yours Build: Record a span whenever text is inserted, with its origin, the model and the time. Remap offsets on every edit so spans follow the text they describe. When a person edits inside a generated span, the overlapping part becomes edited. A span that cannot be remapped becomes unknown and is displayed as unknown rather than quietly counted as typed. Trap: Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. Done when: Typed text is recorded as typed and generated text as generated, tested at insertion. Editing inside a generated span marks the overlapping range as edited and leaves the remainder generated, tested. Inserting text before a span shifts that span's offsets and the span still covers the same words, tested. A span that cannot be remapped becomes unknown and the interface shows it as unknown. Do not build yet: No instruction library, cost ceiling or export yet. ### Phase 3 · Instructions you can reproduce, and a bill that stops Build: Named instructions stored as rows, applied to a selection or the whole document. Archive rather than edit, so existing spans keep pointing at the text that produced them. Record every run with its hashes, token counts and cost. Cap the monthly spend and leave editing working when the cap is reached. Trap: Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. Done when: Editing an instruction creates a new row and archives the old one, and existing spans still resolve to the version that produced them, tested. Every run records input and output hashes, token counts and cost. Reaching the monthly ceiling blocks generation with a clear message and leaves typing, versioning and export working, tested. A failed run creates no version and no span. Do not build yet: No export or backup yet. ### Phase 4 · Export that carries the record with the text Build: Markdown export accompanied by a machine-readable provenance file naming each span, its origin, the model and the date. A JSON backup covering documents, versions, spans, instructions and runs, which reimports into an empty installation. A README section stating the marking and disclosure obligations and the date they apply from. Trap: Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience. Done when: Markdown export writes a companion provenance file listing every span with origin, model and date, tested against a document containing all four origins. The JSON backup reimports into an empty installation with identical documents, versions, spans and runs, tested. No route, instruction template or setting offers to disguise generated text, asserted by a test over the route table and the shipped instruction library. The README states the obligations, their application date and where the text is published. Do not build yet: Nothing after this. ### Out of scope (and why) - A browser extension that rewrites text in other people's applications, which is a permission and maintenance burden far larger than the workspace itself. - Team workspaces, shared prompt libraries and approvals, which is what the subscription sells and which turns you into the provider rather than the deployer. - Any humanising or detection-evasion feature, refused rather than deferred. - A knowledge base with retrieval, which is a genuinely separate build and should not be bolted onto a drafts folder. ### README must contain - That every generation creates a new version and nothing is overwritten, with the restore behaviour spelled out - How provenance is recorded, what the four origins mean, and what unknown means when you see it - The marking and disclosure obligations for artificially generated text, their application date, and where the current text is published - That there is no humanising or detection-evasion feature and that a test asserts its absence - Where the database lives, what the backup contains, and how to reimport it into an empty installation
# Architecture · TextCortex ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Next.js 15 with TypeScript, running locally, single user | this is your drafts folder and it should live where your drafts folder lives | | Storage | SQLite beside the application, with every version retained | the version you are about to overwrite is the one you will want back | | Editor | A plain text or rich text editor with offset-addressable content | provenance is stored as ranges and needs offsets that survive editing | | Model | One call per instruction run, with the run recorded | the rewrite you liked was produced by a specific instruction and a specific model | | Provenance | Spans recorded at insertion, remapped on every edit, exported alongside the text | a single boolean saying the document is AI generated is useless by the second paragraph | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Versions | Append-only bodies, parents and restore. | Any store, provided no path overwrites a version. | | Spans | Origin at insertion and offset remapping. | Any editor, provided offsets are addressable and remappable. | | Instructions | Named prompts, archiving and run records. | Any prompt store, provided editing archives rather than mutates. | | Cost | Token accounting and the ceiling. | Any accounting, provided the editor survives the ceiling. | | Export | Markdown, the provenance companion and the backup. | Any format, provided provenance leaves with the text. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `MODEL_API_KEY` · required, secret · One call per instruction run. Set a provider-side spend limit as well. - `MODEL_ID` · required · Recorded on every run and every generated span, because output differs by model. - `DATABASE_PATH` · required · Documents, versions, spans, instructions and runs. Back this up. - `MONTHLY_COST_CEILING` · required · Generation stops here and the editor keeps working. - `EXPORT_DIR` · required · Markdown plus the companion provenance file for each export. ## 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 · TextCortex product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Next.js 15 with TypeScript, running locally, single user, SQLite beside the application, with every version retained, A plain text or rich text editor with offset-addressable content, One call per instruction run, with the run recorded, Spans recorded at insertion, remapped on every edit, exported alongside the text. - 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 - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. - Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience.
# Delivery milestones · TextCortex Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A document that cannot lose the paragraph you liked Every change that a model makes creates a new version and the previous body survives byte for byte. ### Steps 1. Make the save path incapable of overwriting a version Append only, at the storage layer rather than by convention. Files: `src/lib/versions.ts`, `tests/versions.test.ts` 2. Give every version a reason and a parent So the history reads as a story rather than a stack. Files: `src/lib/schema.sql` 3. Make restore a forward operation Going back should not destroy what you are going back from. Files: `src/lib/restore.ts`, `tests/restore.test.ts` ### Done when - [ ] A generation leaves the previous body byte-identical - [ ] A crash mid-generation stores no partial version - [ ] Versions carry reason and parent - [ ] Restore creates a version and deletes nothing ### Watch out - Applying a rewrite by replacing the document text and trusting the editor's undo stack means the paragraph you had before the rewrite is gone the moment the page reloads, and losing work to the assistant is the fastest way to stop using it. Make every generation a new version row with a parent, keep the previous body untouched, and give the save path no way to write over an existing version. ## M2 · Knowing which words are yours Provenance is recorded at insertion and survives every subsequent edit. ### Steps 1. Record origin at insertion, never by diffing later A diff between versions is a guess about who typed what. Files: `src/lib/spans.ts`, `tests/spans.test.ts` 2. Remap offsets on every edit The span has to follow the words, not the position. Files: `src/lib/remap.ts`, `tests/remap.test.ts` 3. Make unknown a visible state Better a gap you can see than a claim you cannot support. Files: `src/components/Provenance.tsx` ### Done when - [ ] Origin is recorded at insertion - [ ] Editing generated text marks it edited - [ ] Offsets survive insertions before a span - [ ] Unmappable spans become visible unknowns ### Watch out - Storing one flag saying the document was AI generated is useless by the second paragraph, because half of it is now yours and the half that is not is scattered through it; recomputing provenance later by diffing versions is guesswork dressed as a record. Record the origin at the moment of insertion with offsets, remap those offsets on every keystroke batch, and make unknown a real state you can see. ## M3 · Instructions you can reproduce, and a bill that stops A rewrite that worked last week can be run again with the same instruction and the same model. ### Steps 1. Archive instructions rather than editing them An instruction referenced by old spans is history, not configuration. Files: `src/lib/instructions.ts`, `tests/instructions.test.ts` 2. Record every run with hashes and cost Reproducibility and the bill come from the same table. Files: `src/lib/runs.ts` 3. Cap the spend, keep the editor working A writing tool should not stop being a text editor. Files: `src/lib/cost.ts`, `tests/cost.test.ts` ### Done when - [ ] Editing an instruction archives the old one - [ ] Old spans still resolve to their instruction - [ ] Runs record hashes, tokens and cost - [ ] The ceiling blocks generation, not editing ### Watch out - Keeping the prompt in the code and tweaking it until the output looks right means the rewrite that worked last week cannot be reproduced and you cannot tell whether the model changed or you did. Store instructions as rows, archive instead of editing, reference the instruction identifier on every run and every generated span, and record the model identifier next to it. ## M4 · Export that carries the record with the text (production only) What leaves this application says which parts a model wrote. ### Steps 1. Write provenance as a companion file Named in the export dialogue so nobody exports half of it. Files: `src/lib/export.ts`, `tests/export.test.ts` 2. Make the JSON backup complete and tested Every table, round-tripped, or it is not a backup. Files: `src/lib/backup.ts`, `tests/roundtrip.test.ts` 3. Assert that no evasion feature exists A test is a better commitment than a promise in a README. Files: `tests/no-evasion.test.ts`, `README.md` ### Done when - [ ] Markdown export has a provenance companion file - [ ] The JSON backup round-trips every table - [ ] No evasion or humanising feature exists - [ ] The README states the obligations and their date ### Watch out - Exporting Markdown with generated text indistinguishable from your own throws away the only record of which is which, and reconstructing it afterwards is guesswork; worse, the export is exactly the moment the text starts travelling to places you will not be present at. Write the provenance as a companion machine-readable file, name that file in the export dialogue, and make the JSON backup the complete record rather than a convenience.
# Operations · TextCortex ## Backup One SQLite file holding drafts, versions and the provenance record. Copy it on a schedule, and test the reimport once rather than assuming it, because the provenance is the part that cannot be reconstructed from the text. ## Restore Restore the file, confirm the document and version counts, then open a document with mixed authorship and confirm the spans still line up with the words they describe. Spans that have drifted mean the remapping state did not survive. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Monthly spend against the ceiling, failed run rate, and the proportion of spans marked unknown. A rising unknown proportion means the remapping is losing track during ordinary editing, which is the failure that matters here. ## Incident checklist The incident is a published document whose provenance was wrong. Every version, span and run is retained, so the question of what the model produced and what you changed has an answer rather than a recollection. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A generation is confirmed to leave the previous body byte-identical - [ ] Editing generated text is confirmed to mark it edited - [ ] Span offsets are confirmed to survive insertions before them - [ ] The export is confirmed to carry a provenance companion file - [ ] No evasion or humanising feature is confirmed to exist ## Launch constraint Do not market omitted TextCortex 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 · secret. One call per instruction run. Set a provider-side spend limit as well. MODEL_API_KEY=sk-...redacted # Required. Recorded on every run and every generated span, because output differs by model. MODEL_ID=a text model identifier # Required. Documents, versions, spans, instructions and runs. Back this up. DATABASE_PATH=./data/writing.db # Required. Generation stops here and the editor keeps working. MONTHLY_COST_CEILING=4.00 # Required. Markdown plus the companion provenance file for each export. EXPORT_DIR=./exports
$ choose a build depth, inspect the files, then open the complete pack in your agent
TextCortex: Customers pay for tuned workflows, predictable quality, governance, and a product team absorbing model churn rather than for the text box alone.
xteam governance and integrations
xvendor-managed prompt and quality tuning
xproprietary models or classifiers
xbrand-trained workflows
Don't feel like building it? These folks already made it free.
all 4 free alternatives to TextCortex →· no votes, no pay-to-list · just what's real
TextCortex pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | 50 MB storage; the public page does not publish a fixed generation count. |
| premium | $29.99/user | $23.99/user | 10 GB storage; compute wallet worth 50% of the monthly license fee; 25 Auto Mode queries/month during preview. |
| enterprise | custom | — | Custom seats, storage, integrations, security, and usage. |
free tier50 MB storage; fixed generation count not publicly disclosed
billingmonthly + annual for Premium; Enterprise is custom
hidden costsExtra Usage is charged at the underlying model cost plus a TextCortex markup; administrators can set caps. VAT may be added, and account sharing is prohibited.
verified 2026-08-12 · source ↗
Vibecode TextCortex
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal TextCortex replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does TextCortex cost?
TextCortex's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing TextCortex?
Honestly: team governance and integrations; vendor-managed prompt and quality tuning; proprietary models or classifiers; brand-trained workflows. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to TextCortex?
Yes: AnythingLLM (A local-first AI workspace with saved agents, files, memory, and reusable context; the model is yours, so the brand voice can be too.) Page Assist (A browser sidebar that reads the page, rewrites the selection, and saves custom actions; your local model does the thinking.) Witsy (System-wide selected-text commands, reusable experts, a scratchpad, and document context; bring the model and skip the subscription.) All 4 curated free alternatives are at vibecodeit.com/textcortex/alternatives. The prompt is for when you want it exactly your way.