Vibecode Akiflow
track this build4 phases, 12 steps, beginner friendly0%The planner UI can be built, but bringing every task source into one reliable, two-way calendar workflow means OAuth, APIs, sync conflicts, and maintenance.
You are building a lean indie version of Akiflow. 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 ===== # Akiflow · indie build A planner that pulls tasks out of the places they arrive and puts them into one queue you empty, where the engineering is keeping several external systems in sync without duplicating or losing anything, and the design is that every action must be reachable without a mouse. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Sources | Read-write connectors per source with stable external identity | a task that exists in two systems must be one task here | | Sync | Two-way with conflict detection, never last write wins | a task completed in both places at once must not lose a note | | Command bar | Every action reachable by keyboard from anywhere | the speed of triage is what determines whether the inbox gets emptied | | Planning | Tasks placed onto calendar time blocks | a task list without time is a list of intentions | | Runtime | TypeScript with a local application and SQLite | the inbox is personal and should work offline | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application and the sync workers. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Two real task sources with write access** · free with existing subscriptions - Why: Two-way sync behaviour, identifier stability and conflict cases differ per system and none of it can be assumed from documentation. - Get it: Accounts on two systems you actually use, with write scopes, and read their sync and identifier documentation before building. - Verify: A task created here appears there and a change there appears here - [ ] **A calendar account with write access** · free - Why: Time blocks are real calendar events, and the failure modes only appear against a real calendar with existing meetings. - Get it: A calendar with a month of realistic meetings including recurring series. - Verify: Events can be created and the tool's own events are identifiable by stored identifier - [ ] **Your real task backlog** · free - Why: Triage speed and inbox behaviour are only meaningful against a real backlog with the awkward items you have been avoiding. - Get it: Import everything from the systems you actually use, including the fifty things you have been ignoring. - Verify: At least two hundred tasks are imported across two sources - [ ] **Your keyboard scheme, decided early** · free - Why: Every action is keyboard reachable and retrofitting a scheme after building the interface means rebuilding the interface. - Get it: Decide the command bar key, the triage keys and the navigation keys before building phase three. - Verify: A written scheme covers every planned triage action ## 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: - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. - wide integrations - two-way sync - keyboard UX - mobile/desktop apps - support for API breakage If one of those is essential to you, that is the reason to keep paying for Akiflow, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Akiflow 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 one inbox for tasks from everywhere, emptied into a day plan by keyboard to replace Akiflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Sources: Read-write connectors per source with stable external identity · a task that exists in two systems must be one task here - Sync: Two-way with conflict detection, never last write wins · a task completed in both places at once must not lose a note - Command bar: Every action reachable by keyboard from anywhere · the speed of triage is what determines whether the inbox gets emptied - Planning: Tasks placed onto calendar time blocks · a task list without time is a list of intentions - Runtime: TypeScript with a local application and SQLite · the inbox is personal and should work offline ### Data model (create this before Phase 1) - sources(id, kind, account_ref, connected_at, last_synced_at, cursor, disabled_reason) - tasks(id, source_id, external_id, title, notes, due_at, completed_at, deleted_at, updated_at, local_version, remote_version) - inbox(task_id, arrived_at, triaged_at) · the queue you empty - blocks(id, task_id, starts_at, ends_at, calendar_event_id, pinned) · time on the calendar - conflicts(id, task_id, field, local_value, remote_value, detected_at, resolved_at, resolution) - commands(id, name, keys, context) · the command surface, so every action is reachable - A task holds both a local and a remote version, because detecting that both changed is the only way to avoid silently discarding one. ### Phase 1 · One task, however many systems Build: Connect sources read only first, importing tasks with their external identity. Deduplicate on the source's identifiers. Handle a source that renames or reissues identifiers, and one that disappears. Trap: Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. Done when: Tasks import with their external identity and re-importing does not duplicate. Two tasks with the same title from one source stay separate. A task renamed at the source updates rather than duplicating. A disconnected source's tasks are marked orphaned rather than deleted. Do not build yet: No two-way sync, inbox flow or planning. ### Phase 2 · Writing back without losing anything Build: Two-way sync with version tracking on both sides. Detect a task changed locally and remotely since the last sync and surface it as a conflict rather than choosing. Make completion idempotent. Trap: Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. Done when: A task changed on both sides since the last sync produces a conflict rather than a silent resolution, tested by editing in both. Completion applied twice does not produce two completions at the source. A conflict shows both values and the resolution is recorded. Sync resumes correctly after being interrupted mid-write. Do not build yet: No inbox flow, command bar or planning yet. ### Phase 3 · An inbox you can actually empty Build: An inbox of untriaged tasks with keyboard triage: schedule, defer, delegate, drop. Every action reachable from a command bar. Measure how long fifty items take to triage. Trap: A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. Done when: Fifty items triage in under five minutes without touching a mouse, timed by hand. Every action is reachable from the command bar and discoverable by search within it. Undo works for the last several triage actions. An item deferred returns to the inbox at the stated time. Do not build yet: No planning or calendar writing yet. ### Phase 4 · Tasks on the calendar, honestly Build: Place tasks into time blocks written to a calendar as events the tool owns. Never modify an event the tool did not create. Show when a day's blocks exceed the available time. Trap: Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. Done when: Blocks appear as calendar events identified by stored identifier rather than by title. A real meeting cannot be modified or deleted, verified by a test that attempts it. A day planned beyond its available time is flagged rather than silently accepted. Deleting a block removes its event and only its event. Do not build yet: No offline handling or reporting yet. ### Phase 5 · Working offline and knowing what happened Build: Queue local changes while offline and apply them on reconnection with conflict detection. Report sync health per source. Export everything including the mapping to external identifiers. Trap: A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. Done when: Changes made offline queue and apply on reconnection with conflicts detected rather than silently resolved. Each source shows its last successful sync and stale sources are surfaced. An expired connection produces an alert rather than silence. Export includes external identifier mappings so the data is portable. Do not build yet: Nothing after this. ### Out of scope (and why) - Breadth of source integrations, which is the hosted product's actual value and an endless maintenance commitment. - Team task assignment and shared projects, which is a different product with a permissions model. - Automatic scheduling of tasks into free time, which is a different build with its own solver requirements. - Mobile applications, where the keyboard-first design does not transfer at all. ### README must contain - That tasks are keyed on the source's own identifiers rather than on titles - That a task changed on both sides produces a conflict rather than a silent resolution - That every action is keyboard reachable, because one mouse reach per item ends the triage rhythm - That the tool owns calendar events by stored identifier and never touches anything else - That a stale source is alerted on, since nothing syncing produces no error by itself ===== AGENTS.md ===== # Agent instructions · Akiflow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Read-write connectors per source with stable external identity, Two-way with conflict detection, never last write wins, Every action reachable by keyboard from anywhere, Tasks placed onto calendar time blocks, TypeScript with a local application and SQLite. 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 - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ===== BUILD_PLAN.md ===== # Build plan · Akiflow A planner that pulls tasks out of the places they arrive and puts them into one queue you empty, where the engineering is keeping several external systems in sync without duplicating or losing anything, and the design is that every action must be reachable without a mouse. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One task, however many systems A task from any source is one item here and stays linked to its origin. ### Steps 1. Key on external identifiers Titles collide and change. Identifiers do not. Files: `src/sources.ts` 2. Handle identifier reissue Some systems reuse identifiers, which is worse than changing them. Files: `src/sync.ts` 3. Orphan rather than delete A disconnected source is not a deletion instruction. Files: `src/sources.ts` ### Done when - [ ] Re-importing does not duplicate - [ ] Same-titled tasks stay separate - [ ] Renaming updates rather than duplicating - [ ] Disconnection orphans rather than deletes ### Watch out - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. ## Phase 2 · Writing back without losing anything A change made in both places is detected rather than silently resolved. ### Steps 1. Track versions on both sides It is the only way to know both changed. Files: `src/versions.ts` 2. Surface conflicts rather than resolving They are rare enough that asking is tolerable and losing a note is not. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Make writes idempotent A retried completion should not complete twice. Files: `src/write.ts` ### Done when - [ ] Both-side changes produce a conflict - [ ] Repeated completion is idempotent - [ ] Conflicts show both values and record resolution - [ ] Interrupted sync resumes correctly ### Watch out - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. ## Phase 3 · An inbox you can actually empty Triaging fifty items takes minutes and never a mouse. ### Steps 1. Make every action keyboard reachable One mouse reach per item breaks the rhythm and the model. Files: `src/commands.ts` 2. Make the command bar searchable So actions are discoverable without memorising them. Files: `src/command-bar.ts` 3. Support undo across triage Fast triage produces mistakes and they should be cheap. Files: `src/undo.ts` ### Done when - [ ] Fifty items triage in under five minutes without a mouse - [ ] Every action is in the searchable command bar - [ ] Undo covers recent triage actions - [ ] Deferred items return at the stated time ### Watch out - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. ## Phase 4 · Tasks on the calendar, honestly Planned work occupies real time and the calendar reflects it. ### Steps 1. Own events by stored identifier Never by title. A rename must not orphan a block. Files: `src/calendar.ts` 2. Refuse to touch foreign events Tested by attempting it and expecting a refusal. Files: `tests/foreign-events.test.ts` 3. Flag an overfull day Planning eleven hours into eight is worth being told about. Files: `src/capacity.ts` ### Done when - [ ] Blocks are identified by stored id - [ ] Foreign events cannot be modified - [ ] An overfull day is flagged - [ ] Deleting a block removes only its event ### Watch out - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ## Not in this build - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. ## After v1, if you want it - Snoozing with a reason, which turns deferral into information rather than avoidance - A weekly review of what was deferred repeatedly, which is where a backlog gets honest - Capturing a task from anywhere with a global hotkey, once the inbox flow is proven ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Tasks, conflicts and the offline queue. DB_PATH=~/.local/share/planner/tasks.db # Required · secret. Write access to your task systems. Scope narrowly. SOURCE_TOKENS=a keychain reference # Required · secret. Create and update only the tool's own events. CALENDAR_CLIENT_ID=a provider client id # Required. When a source is surfaced as stale. Silence is the failure mode. SYNC_STALE_HOURS=12 # Required. Never last write wins. The rare case is worth a question. CONFLICT_RESOLUTION=ask # Required. The entry point to every action. Choose something nothing else claims. COMMAND_BAR_KEY=Ctrl+K
You are building a lean indie version of Akiflow. 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 ===== # Akiflow · indie build A planner that pulls tasks out of the places they arrive and puts them into one queue you empty, where the engineering is keeping several external systems in sync without duplicating or losing anything, and the design is that every action must be reachable without a mouse. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Sources | Read-write connectors per source with stable external identity | a task that exists in two systems must be one task here | | Sync | Two-way with conflict detection, never last write wins | a task completed in both places at once must not lose a note | | Command bar | Every action reachable by keyboard from anywhere | the speed of triage is what determines whether the inbox gets emptied | | Planning | Tasks placed onto calendar time blocks | a task list without time is a list of intentions | | Runtime | TypeScript with a local application and SQLite | the inbox is personal and should work offline | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application and the sync workers. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Two real task sources with write access** · free with existing subscriptions - Why: Two-way sync behaviour, identifier stability and conflict cases differ per system and none of it can be assumed from documentation. - Get it: Accounts on two systems you actually use, with write scopes, and read their sync and identifier documentation before building. - Verify: A task created here appears there and a change there appears here - [ ] **A calendar account with write access** · free - Why: Time blocks are real calendar events, and the failure modes only appear against a real calendar with existing meetings. - Get it: A calendar with a month of realistic meetings including recurring series. - Verify: Events can be created and the tool's own events are identifiable by stored identifier - [ ] **Your real task backlog** · free - Why: Triage speed and inbox behaviour are only meaningful against a real backlog with the awkward items you have been avoiding. - Get it: Import everything from the systems you actually use, including the fifty things you have been ignoring. - Verify: At least two hundred tasks are imported across two sources - [ ] **Your keyboard scheme, decided early** · free - Why: Every action is keyboard reachable and retrofitting a scheme after building the interface means rebuilding the interface. - Get it: Decide the command bar key, the triage keys and the navigation keys before building phase three. - Verify: A written scheme covers every planned triage action ## 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: - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. - wide integrations - two-way sync - keyboard UX - mobile/desktop apps - support for API breakage If one of those is essential to you, that is the reason to keep paying for Akiflow, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Akiflow 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 one inbox for tasks from everywhere, emptied into a day plan by keyboard to replace Akiflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Sources: Read-write connectors per source with stable external identity · a task that exists in two systems must be one task here - Sync: Two-way with conflict detection, never last write wins · a task completed in both places at once must not lose a note - Command bar: Every action reachable by keyboard from anywhere · the speed of triage is what determines whether the inbox gets emptied - Planning: Tasks placed onto calendar time blocks · a task list without time is a list of intentions - Runtime: TypeScript with a local application and SQLite · the inbox is personal and should work offline ### Data model (create this before Phase 1) - sources(id, kind, account_ref, connected_at, last_synced_at, cursor, disabled_reason) - tasks(id, source_id, external_id, title, notes, due_at, completed_at, deleted_at, updated_at, local_version, remote_version) - inbox(task_id, arrived_at, triaged_at) · the queue you empty - blocks(id, task_id, starts_at, ends_at, calendar_event_id, pinned) · time on the calendar - conflicts(id, task_id, field, local_value, remote_value, detected_at, resolved_at, resolution) - commands(id, name, keys, context) · the command surface, so every action is reachable - A task holds both a local and a remote version, because detecting that both changed is the only way to avoid silently discarding one. ### Phase 1 · One task, however many systems Build: Connect sources read only first, importing tasks with their external identity. Deduplicate on the source's identifiers. Handle a source that renames or reissues identifiers, and one that disappears. Trap: Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. Done when: Tasks import with their external identity and re-importing does not duplicate. Two tasks with the same title from one source stay separate. A task renamed at the source updates rather than duplicating. A disconnected source's tasks are marked orphaned rather than deleted. Do not build yet: No two-way sync, inbox flow or planning. ### Phase 2 · Writing back without losing anything Build: Two-way sync with version tracking on both sides. Detect a task changed locally and remotely since the last sync and surface it as a conflict rather than choosing. Make completion idempotent. Trap: Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. Done when: A task changed on both sides since the last sync produces a conflict rather than a silent resolution, tested by editing in both. Completion applied twice does not produce two completions at the source. A conflict shows both values and the resolution is recorded. Sync resumes correctly after being interrupted mid-write. Do not build yet: No inbox flow, command bar or planning yet. ### Phase 3 · An inbox you can actually empty Build: An inbox of untriaged tasks with keyboard triage: schedule, defer, delegate, drop. Every action reachable from a command bar. Measure how long fifty items take to triage. Trap: A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. Done when: Fifty items triage in under five minutes without touching a mouse, timed by hand. Every action is reachable from the command bar and discoverable by search within it. Undo works for the last several triage actions. An item deferred returns to the inbox at the stated time. Do not build yet: No planning or calendar writing yet. ### Phase 4 · Tasks on the calendar, honestly Build: Place tasks into time blocks written to a calendar as events the tool owns. Never modify an event the tool did not create. Show when a day's blocks exceed the available time. Trap: Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. Done when: Blocks appear as calendar events identified by stored identifier rather than by title. A real meeting cannot be modified or deleted, verified by a test that attempts it. A day planned beyond its available time is flagged rather than silently accepted. Deleting a block removes its event and only its event. Do not build yet: No offline handling or reporting yet. ### Phase 5 · Working offline and knowing what happened Build: Queue local changes while offline and apply them on reconnection with conflict detection. Report sync health per source. Export everything including the mapping to external identifiers. Trap: A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. Done when: Changes made offline queue and apply on reconnection with conflicts detected rather than silently resolved. Each source shows its last successful sync and stale sources are surfaced. An expired connection produces an alert rather than silence. Export includes external identifier mappings so the data is portable. Do not build yet: Nothing after this. ### Out of scope (and why) - Breadth of source integrations, which is the hosted product's actual value and an endless maintenance commitment. - Team task assignment and shared projects, which is a different product with a permissions model. - Automatic scheduling of tasks into free time, which is a different build with its own solver requirements. - Mobile applications, where the keyboard-first design does not transfer at all. ### README must contain - That tasks are keyed on the source's own identifiers rather than on titles - That a task changed on both sides produces a conflict rather than a silent resolution - That every action is keyboard reachable, because one mouse reach per item ends the triage rhythm - That the tool owns calendar events by stored identifier and never touches anything else - That a stale source is alerted on, since nothing syncing produces no error by itself ===== AGENTS.md ===== # Agent instructions · Akiflow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Read-write connectors per source with stable external identity, Two-way with conflict detection, never last write wins, Every action reachable by keyboard from anywhere, Tasks placed onto calendar time blocks, TypeScript with a local application and SQLite. 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 - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ===== BUILD_PLAN.md ===== # Build plan · Akiflow A planner that pulls tasks out of the places they arrive and puts them into one queue you empty, where the engineering is keeping several external systems in sync without duplicating or losing anything, and the design is that every action must be reachable without a mouse. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One task, however many systems A task from any source is one item here and stays linked to its origin. ### Steps 1. Key on external identifiers Titles collide and change. Identifiers do not. Files: `src/sources.ts` 2. Handle identifier reissue Some systems reuse identifiers, which is worse than changing them. Files: `src/sync.ts` 3. Orphan rather than delete A disconnected source is not a deletion instruction. Files: `src/sources.ts` ### Done when - [ ] Re-importing does not duplicate - [ ] Same-titled tasks stay separate - [ ] Renaming updates rather than duplicating - [ ] Disconnection orphans rather than deletes ### Watch out - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. ## Phase 2 · Writing back without losing anything A change made in both places is detected rather than silently resolved. ### Steps 1. Track versions on both sides It is the only way to know both changed. Files: `src/versions.ts` 2. Surface conflicts rather than resolving They are rare enough that asking is tolerable and losing a note is not. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Make writes idempotent A retried completion should not complete twice. Files: `src/write.ts` ### Done when - [ ] Both-side changes produce a conflict - [ ] Repeated completion is idempotent - [ ] Conflicts show both values and record resolution - [ ] Interrupted sync resumes correctly ### Watch out - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. ## Phase 3 · An inbox you can actually empty Triaging fifty items takes minutes and never a mouse. ### Steps 1. Make every action keyboard reachable One mouse reach per item breaks the rhythm and the model. Files: `src/commands.ts` 2. Make the command bar searchable So actions are discoverable without memorising them. Files: `src/command-bar.ts` 3. Support undo across triage Fast triage produces mistakes and they should be cheap. Files: `src/undo.ts` ### Done when - [ ] Fifty items triage in under five minutes without a mouse - [ ] Every action is in the searchable command bar - [ ] Undo covers recent triage actions - [ ] Deferred items return at the stated time ### Watch out - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. ## Phase 4 · Tasks on the calendar, honestly Planned work occupies real time and the calendar reflects it. ### Steps 1. Own events by stored identifier Never by title. A rename must not orphan a block. Files: `src/calendar.ts` 2. Refuse to touch foreign events Tested by attempting it and expecting a refusal. Files: `tests/foreign-events.test.ts` 3. Flag an overfull day Planning eleven hours into eight is worth being told about. Files: `src/capacity.ts` ### Done when - [ ] Blocks are identified by stored id - [ ] Foreign events cannot be modified - [ ] An overfull day is flagged - [ ] Deleting a block removes only its event ### Watch out - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ## Not in this build - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. ## After v1, if you want it - Snoozing with a reason, which turns deferral into information rather than avoidance - A weekly review of what was deferred repeatedly, which is where a backlog gets honest - Capturing a task from anywhere with a global hotkey, once the inbox flow is proven ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Tasks, conflicts and the offline queue. DB_PATH=~/.local/share/planner/tasks.db # Required · secret. Write access to your task systems. Scope narrowly. SOURCE_TOKENS=a keychain reference # Required · secret. Create and update only the tool's own events. CALENDAR_CLIENT_ID=a provider client id # Required. When a source is surfaced as stale. Silence is the failure mode. SYNC_STALE_HOURS=12 # Required. Never last write wins. The rare case is worth a question. CONFLICT_RESOLUTION=ask # Required. The entry point to every action. Choose something nothing else claims. COMMAND_BAR_KEY=Ctrl+K
You are building a production product version of Akiflow. 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 ===== # Akiflow · product brief ## Problem The planner UI can be built, but bringing every task source into one reliable, two-way calendar workflow means OAuth, APIs, sync conflicts, and maintenance. ## Product outcome One inbox for tasks from several systems, emptied by keyboard in minutes, planned onto real calendar time, and never silently losing a change made in two places. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Google/Microsoft OAuth - integrations to task apps - hosted backend - background sync - notifications ## Explicit non-goals for v1 - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. - wide integrations - two-way sync - keyboard UX - mobile/desktop apps - support for API breakage ## Success criteria - Re-importing is confirmed not to duplicate - Both-side changes are confirmed to produce a conflict - Fifty items are confirmed to triage in under five minutes without a mouse - A foreign calendar event is confirmed unmodifiable - A stale source is confirmed to alert ===== BRIEF.md ===== # Build brief · Akiflow 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 one inbox for tasks from everywhere, emptied into a day plan by keyboard to replace Akiflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Sources: Read-write connectors per source with stable external identity · a task that exists in two systems must be one task here - Sync: Two-way with conflict detection, never last write wins · a task completed in both places at once must not lose a note - Command bar: Every action reachable by keyboard from anywhere · the speed of triage is what determines whether the inbox gets emptied - Planning: Tasks placed onto calendar time blocks · a task list without time is a list of intentions - Runtime: TypeScript with a local application and SQLite · the inbox is personal and should work offline ### Data model (create this before Phase 1) - sources(id, kind, account_ref, connected_at, last_synced_at, cursor, disabled_reason) - tasks(id, source_id, external_id, title, notes, due_at, completed_at, deleted_at, updated_at, local_version, remote_version) - inbox(task_id, arrived_at, triaged_at) · the queue you empty - blocks(id, task_id, starts_at, ends_at, calendar_event_id, pinned) · time on the calendar - conflicts(id, task_id, field, local_value, remote_value, detected_at, resolved_at, resolution) - commands(id, name, keys, context) · the command surface, so every action is reachable - A task holds both a local and a remote version, because detecting that both changed is the only way to avoid silently discarding one. ### Phase 1 · One task, however many systems Build: Connect sources read only first, importing tasks with their external identity. Deduplicate on the source's identifiers. Handle a source that renames or reissues identifiers, and one that disappears. Trap: Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. Done when: Tasks import with their external identity and re-importing does not duplicate. Two tasks with the same title from one source stay separate. A task renamed at the source updates rather than duplicating. A disconnected source's tasks are marked orphaned rather than deleted. Do not build yet: No two-way sync, inbox flow or planning. ### Phase 2 · Writing back without losing anything Build: Two-way sync with version tracking on both sides. Detect a task changed locally and remotely since the last sync and surface it as a conflict rather than choosing. Make completion idempotent. Trap: Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. Done when: A task changed on both sides since the last sync produces a conflict rather than a silent resolution, tested by editing in both. Completion applied twice does not produce two completions at the source. A conflict shows both values and the resolution is recorded. Sync resumes correctly after being interrupted mid-write. Do not build yet: No inbox flow, command bar or planning yet. ### Phase 3 · An inbox you can actually empty Build: An inbox of untriaged tasks with keyboard triage: schedule, defer, delegate, drop. Every action reachable from a command bar. Measure how long fifty items take to triage. Trap: A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. Done when: Fifty items triage in under five minutes without touching a mouse, timed by hand. Every action is reachable from the command bar and discoverable by search within it. Undo works for the last several triage actions. An item deferred returns to the inbox at the stated time. Do not build yet: No planning or calendar writing yet. ### Phase 4 · Tasks on the calendar, honestly Build: Place tasks into time blocks written to a calendar as events the tool owns. Never modify an event the tool did not create. Show when a day's blocks exceed the available time. Trap: Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. Done when: Blocks appear as calendar events identified by stored identifier rather than by title. A real meeting cannot be modified or deleted, verified by a test that attempts it. A day planned beyond its available time is flagged rather than silently accepted. Deleting a block removes its event and only its event. Do not build yet: No offline handling or reporting yet. ### Phase 5 · Working offline and knowing what happened Build: Queue local changes while offline and apply them on reconnection with conflict detection. Report sync health per source. Export everything including the mapping to external identifiers. Trap: A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. Done when: Changes made offline queue and apply on reconnection with conflicts detected rather than silently resolved. Each source shows its last successful sync and stale sources are surfaced. An expired connection produces an alert rather than silence. Export includes external identifier mappings so the data is portable. Do not build yet: Nothing after this. ### Out of scope (and why) - Breadth of source integrations, which is the hosted product's actual value and an endless maintenance commitment. - Team task assignment and shared projects, which is a different product with a permissions model. - Automatic scheduling of tasks into free time, which is a different build with its own solver requirements. - Mobile applications, where the keyboard-first design does not transfer at all. ### README must contain - That tasks are keyed on the source's own identifiers rather than on titles - That a task changed on both sides produces a conflict rather than a silent resolution - That every action is keyboard reachable, because one mouse reach per item ends the triage rhythm - That the tool owns calendar events by stored identifier and never touches anything else - That a stale source is alerted on, since nothing syncing produces no error by itself ===== ARCHITECTURE.md ===== # Architecture · Akiflow ## Stack | Part | Choice | Why | | --- | --- | --- | | Sources | Read-write connectors per source with stable external identity | a task that exists in two systems must be one task here | | Sync | Two-way with conflict detection, never last write wins | a task completed in both places at once must not lose a note | | Command bar | Every action reachable by keyboard from anywhere | the speed of triage is what determines whether the inbox gets emptied | | Planning | Tasks placed onto calendar time blocks | a task list without time is a list of intentions | | Runtime | TypeScript with a local application and SQLite | the inbox is personal and should work offline | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sources | Identity and import per system. | Any connector, provided identity comes from the source rather than from titles. | | Sync | Two-way writes with conflict detection. | Not swappable for last write wins, which discards notes silently. | | Commands | Keyboard reachability for everything. | Any interface, provided no action requires a mouse. | | Blocks | Calendar events the tool owns. | Any provider, provided ownership is by stored identifier. | | Offline | Queued changes and sync health. | Any implementation, provided staleness is alerted rather than silent. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Tasks, conflicts and the offline queue. - `SOURCE_TOKENS` · required, secret · Write access to your task systems. Scope narrowly. - `CALENDAR_CLIENT_ID` · required, secret · Create and update only the tool's own events. - `SYNC_STALE_HOURS` · required · When a source is surfaced as stale. Silence is the failure mode. - `CONFLICT_RESOLUTION` · required · Never last write wins. The rare case is worth a question. - `COMMAND_BAR_KEY` · required · The entry point to every action. Choose something nothing else claims. ## 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 · Akiflow product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Read-write connectors per source with stable external identity, Two-way with conflict detection, never last write wins, Every action reachable by keyboard from anywhere, Tasks placed onto calendar time blocks, TypeScript with a local application and SQLite. - 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 - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. - A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. ===== MILESTONES.md ===== # Delivery milestones · Akiflow Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · One task, however many systems A task from any source is one item here and stays linked to its origin. ### Steps 1. Key on external identifiers Titles collide and change. Identifiers do not. Files: `src/sources.ts` 2. Handle identifier reissue Some systems reuse identifiers, which is worse than changing them. Files: `src/sync.ts` 3. Orphan rather than delete A disconnected source is not a deletion instruction. Files: `src/sources.ts` ### Done when - [ ] Re-importing does not duplicate - [ ] Same-titled tasks stay separate - [ ] Renaming updates rather than duplicating - [ ] Disconnection orphans rather than deletes ### Watch out - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. ## M2 · Writing back without losing anything A change made in both places is detected rather than silently resolved. ### Steps 1. Track versions on both sides It is the only way to know both changed. Files: `src/versions.ts` 2. Surface conflicts rather than resolving They are rare enough that asking is tolerable and losing a note is not. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Make writes idempotent A retried completion should not complete twice. Files: `src/write.ts` ### Done when - [ ] Both-side changes produce a conflict - [ ] Repeated completion is idempotent - [ ] Conflicts show both values and record resolution - [ ] Interrupted sync resumes correctly ### Watch out - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. ## M3 · An inbox you can actually empty Triaging fifty items takes minutes and never a mouse. ### Steps 1. Make every action keyboard reachable One mouse reach per item breaks the rhythm and the model. Files: `src/commands.ts` 2. Make the command bar searchable So actions are discoverable without memorising them. Files: `src/command-bar.ts` 3. Support undo across triage Fast triage produces mistakes and they should be cheap. Files: `src/undo.ts` ### Done when - [ ] Fifty items triage in under five minutes without a mouse - [ ] Every action is in the searchable command bar - [ ] Undo covers recent triage actions - [ ] Deferred items return at the stated time ### Watch out - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. ## M4 · Tasks on the calendar, honestly Planned work occupies real time and the calendar reflects it. ### Steps 1. Own events by stored identifier Never by title. A rename must not orphan a block. Files: `src/calendar.ts` 2. Refuse to touch foreign events Tested by attempting it and expecting a refusal. Files: `tests/foreign-events.test.ts` 3. Flag an overfull day Planning eleven hours into eight is worth being told about. Files: `src/capacity.ts` ### Done when - [ ] Blocks are identified by stored id - [ ] Foreign events cannot be modified - [ ] An overfull day is flagged - [ ] Deleting a block removes only its event ### Watch out - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ## M5 · Working offline and knowing what happened (production only) The planner works on a train and reconciles cleanly afterwards. ### Steps 1. Queue offline changes A planner that requires a connection is not a planner on a train. Files: `src/offline.ts` 2. Alert on stale sources Silence is the failure mode. Nothing errors when nothing syncs. Files: `src/health.ts` 3. Export with external mappings So the data can move rather than being trapped between systems. Files: `src/export.ts` ### Done when - [ ] Offline changes apply with conflict detection - [ ] Each source shows its last successful sync - [ ] An expired connection alerts - [ ] Export includes external identifier mappings ### Watch out - A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. ===== OPERATIONS.md ===== # Operations · Akiflow ## Backup The local database holds the offline queue and the external identifier mappings, which are what make the data reconcilable. Tasks themselves mostly live in their source systems, so the mappings are the irreplaceable part. ## Restore Restore and confirm external identifier mappings survived, since without them a resync treats every task as new and duplicates your entire backlog in the source systems. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Time since last successful sync per source, and unresolved conflict count. The first fails silently and the second accumulates until the data is untrustworthy. ## Incident checklist Duplicated tasks in a source system is the visible failure and means identity was lost, usually after a token expiry and a full resync. The mapping table is what prevents it and what diagnoses it. 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 - [ ] Re-importing is confirmed not to duplicate - [ ] Both-side changes are confirmed to produce a conflict - [ ] Fifty items are confirmed to triage in under five minutes without a mouse - [ ] A foreign calendar event is confirmed unmodifiable - [ ] A stale source is confirmed to alert ## Launch constraint Do not market omitted Akiflow 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. Tasks, conflicts and the offline queue. DB_PATH=~/.local/share/planner/tasks.db # Required · secret. Write access to your task systems. Scope narrowly. SOURCE_TOKENS=a keychain reference # Required · secret. Create and update only the tool's own events. CALENDAR_CLIENT_ID=a provider client id # Required. When a source is surfaced as stale. Silence is the failure mode. SYNC_STALE_HOURS=12 # Required. Never last write wins. The rare case is worth a question. CONFLICT_RESOLUTION=ask # Required. The entry point to every action. Choose something nothing else claims. COMMAND_BAR_KEY=Ctrl+K
# Akiflow · indie build A planner that pulls tasks out of the places they arrive and puts them into one queue you empty, where the engineering is keeping several external systems in sync without duplicating or losing anything, and the design is that every action must be reachable without a mouse. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Sources | Read-write connectors per source with stable external identity | a task that exists in two systems must be one task here | | Sync | Two-way with conflict detection, never last write wins | a task completed in both places at once must not lose a note | | Command bar | Every action reachable by keyboard from anywhere | the speed of triage is what determines whether the inbox gets emptied | | Planning | Tasks placed onto calendar time blocks | a task list without time is a list of intentions | | Runtime | TypeScript with a local application and SQLite | the inbox is personal and should work offline | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the local application and the sync workers. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **Two real task sources with write access** · free with existing subscriptions - Why: Two-way sync behaviour, identifier stability and conflict cases differ per system and none of it can be assumed from documentation. - Get it: Accounts on two systems you actually use, with write scopes, and read their sync and identifier documentation before building. - Verify: A task created here appears there and a change there appears here - [ ] **A calendar account with write access** · free - Why: Time blocks are real calendar events, and the failure modes only appear against a real calendar with existing meetings. - Get it: A calendar with a month of realistic meetings including recurring series. - Verify: Events can be created and the tool's own events are identifiable by stored identifier - [ ] **Your real task backlog** · free - Why: Triage speed and inbox behaviour are only meaningful against a real backlog with the awkward items you have been avoiding. - Get it: Import everything from the systems you actually use, including the fifty things you have been ignoring. - Verify: At least two hundred tasks are imported across two sources - [ ] **Your keyboard scheme, decided early** · free - Why: Every action is keyboard reachable and retrofitting a scheme after building the interface means rebuilding the interface. - Get it: Decide the command bar key, the triage keys and the navigation keys before building phase three. - Verify: A written scheme covers every planned triage action ## 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: - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. - wide integrations - two-way sync - keyboard UX - mobile/desktop apps - support for API breakage If one of those is essential to you, that is the reason to keep paying for Akiflow, and the README should say so rather than pretend.
# Build brief · Akiflow 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 one inbox for tasks from everywhere, emptied into a day plan by keyboard to replace Akiflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Sources: Read-write connectors per source with stable external identity · a task that exists in two systems must be one task here - Sync: Two-way with conflict detection, never last write wins · a task completed in both places at once must not lose a note - Command bar: Every action reachable by keyboard from anywhere · the speed of triage is what determines whether the inbox gets emptied - Planning: Tasks placed onto calendar time blocks · a task list without time is a list of intentions - Runtime: TypeScript with a local application and SQLite · the inbox is personal and should work offline ### Data model (create this before Phase 1) - sources(id, kind, account_ref, connected_at, last_synced_at, cursor, disabled_reason) - tasks(id, source_id, external_id, title, notes, due_at, completed_at, deleted_at, updated_at, local_version, remote_version) - inbox(task_id, arrived_at, triaged_at) · the queue you empty - blocks(id, task_id, starts_at, ends_at, calendar_event_id, pinned) · time on the calendar - conflicts(id, task_id, field, local_value, remote_value, detected_at, resolved_at, resolution) - commands(id, name, keys, context) · the command surface, so every action is reachable - A task holds both a local and a remote version, because detecting that both changed is the only way to avoid silently discarding one. ### Phase 1 · One task, however many systems Build: Connect sources read only first, importing tasks with their external identity. Deduplicate on the source's identifiers. Handle a source that renames or reissues identifiers, and one that disappears. Trap: Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. Done when: Tasks import with their external identity and re-importing does not duplicate. Two tasks with the same title from one source stay separate. A task renamed at the source updates rather than duplicating. A disconnected source's tasks are marked orphaned rather than deleted. Do not build yet: No two-way sync, inbox flow or planning. ### Phase 2 · Writing back without losing anything Build: Two-way sync with version tracking on both sides. Detect a task changed locally and remotely since the last sync and surface it as a conflict rather than choosing. Make completion idempotent. Trap: Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. Done when: A task changed on both sides since the last sync produces a conflict rather than a silent resolution, tested by editing in both. Completion applied twice does not produce two completions at the source. A conflict shows both values and the resolution is recorded. Sync resumes correctly after being interrupted mid-write. Do not build yet: No inbox flow, command bar or planning yet. ### Phase 3 · An inbox you can actually empty Build: An inbox of untriaged tasks with keyboard triage: schedule, defer, delegate, drop. Every action reachable from a command bar. Measure how long fifty items take to triage. Trap: A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. Done when: Fifty items triage in under five minutes without touching a mouse, timed by hand. Every action is reachable from the command bar and discoverable by search within it. Undo works for the last several triage actions. An item deferred returns to the inbox at the stated time. Do not build yet: No planning or calendar writing yet. ### Phase 4 · Tasks on the calendar, honestly Build: Place tasks into time blocks written to a calendar as events the tool owns. Never modify an event the tool did not create. Show when a day's blocks exceed the available time. Trap: Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. Done when: Blocks appear as calendar events identified by stored identifier rather than by title. A real meeting cannot be modified or deleted, verified by a test that attempts it. A day planned beyond its available time is flagged rather than silently accepted. Deleting a block removes its event and only its event. Do not build yet: No offline handling or reporting yet. ### Phase 5 · Working offline and knowing what happened Build: Queue local changes while offline and apply them on reconnection with conflict detection. Report sync health per source. Export everything including the mapping to external identifiers. Trap: A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. Done when: Changes made offline queue and apply on reconnection with conflicts detected rather than silently resolved. Each source shows its last successful sync and stale sources are surfaced. An expired connection produces an alert rather than silence. Export includes external identifier mappings so the data is portable. Do not build yet: Nothing after this. ### Out of scope (and why) - Breadth of source integrations, which is the hosted product's actual value and an endless maintenance commitment. - Team task assignment and shared projects, which is a different product with a permissions model. - Automatic scheduling of tasks into free time, which is a different build with its own solver requirements. - Mobile applications, where the keyboard-first design does not transfer at all. ### README must contain - That tasks are keyed on the source's own identifiers rather than on titles - That a task changed on both sides produces a conflict rather than a silent resolution - That every action is keyboard reachable, because one mouse reach per item ends the triage rhythm - That the tool owns calendar events by stored identifier and never touches anything else - That a stale source is alerted on, since nothing syncing produces no error by itself
# Agent instructions · Akiflow indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Read-write connectors per source with stable external identity, Two-way with conflict detection, never last write wins, Every action reachable by keyboard from anywhere, Tasks placed onto calendar time blocks, TypeScript with a local application and SQLite. 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 - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable.
# Build plan · Akiflow A planner that pulls tasks out of the places they arrive and puts them into one queue you empty, where the engineering is keeping several external systems in sync without duplicating or losing anything, and the design is that every action must be reachable without a mouse. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · One task, however many systems A task from any source is one item here and stays linked to its origin. ### Steps 1. Key on external identifiers Titles collide and change. Identifiers do not. Files: `src/sources.ts` 2. Handle identifier reissue Some systems reuse identifiers, which is worse than changing them. Files: `src/sync.ts` 3. Orphan rather than delete A disconnected source is not a deletion instruction. Files: `src/sources.ts` ### Done when - [ ] Re-importing does not duplicate - [ ] Same-titled tasks stay separate - [ ] Renaming updates rather than duplicating - [ ] Disconnection orphans rather than deletes ### Watch out - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. ## Phase 2 · Writing back without losing anything A change made in both places is detected rather than silently resolved. ### Steps 1. Track versions on both sides It is the only way to know both changed. Files: `src/versions.ts` 2. Surface conflicts rather than resolving They are rare enough that asking is tolerable and losing a note is not. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Make writes idempotent A retried completion should not complete twice. Files: `src/write.ts` ### Done when - [ ] Both-side changes produce a conflict - [ ] Repeated completion is idempotent - [ ] Conflicts show both values and record resolution - [ ] Interrupted sync resumes correctly ### Watch out - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. ## Phase 3 · An inbox you can actually empty Triaging fifty items takes minutes and never a mouse. ### Steps 1. Make every action keyboard reachable One mouse reach per item breaks the rhythm and the model. Files: `src/commands.ts` 2. Make the command bar searchable So actions are discoverable without memorising them. Files: `src/command-bar.ts` 3. Support undo across triage Fast triage produces mistakes and they should be cheap. Files: `src/undo.ts` ### Done when - [ ] Fifty items triage in under five minutes without a mouse - [ ] Every action is in the searchable command bar - [ ] Undo covers recent triage actions - [ ] Deferred items return at the stated time ### Watch out - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. ## Phase 4 · Tasks on the calendar, honestly Planned work occupies real time and the calendar reflects it. ### Steps 1. Own events by stored identifier Never by title. A rename must not orphan a block. Files: `src/calendar.ts` 2. Refuse to touch foreign events Tested by attempting it and expecting a refusal. Files: `tests/foreign-events.test.ts` 3. Flag an overfull day Planning eleven hours into eight is worth being told about. Files: `src/capacity.ts` ### Done when - [ ] Blocks are identified by stored id - [ ] Foreign events cannot be modified - [ ] An overfull day is flagged - [ ] Deleting a block removes only its event ### Watch out - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ## Not in this build - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. ## After v1, if you want it - Snoozing with a reason, which turns deferral into information rather than avoidance - A weekly review of what was deferred repeatedly, which is where a backlog gets honest - Capturing a task from anywhere with a global hotkey, once the inbox flow is proven
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Tasks, conflicts and the offline queue. DB_PATH=~/.local/share/planner/tasks.db # Required · secret. Write access to your task systems. Scope narrowly. SOURCE_TOKENS=a keychain reference # Required · secret. Create and update only the tool's own events. CALENDAR_CLIENT_ID=a provider client id # Required. When a source is surfaced as stale. Silence is the failure mode. SYNC_STALE_HOURS=12 # Required. Never last write wins. The rare case is worth a question. CONFLICT_RESOLUTION=ask # Required. The entry point to every action. Choose something nothing else claims. COMMAND_BAR_KEY=Ctrl+K
# Akiflow · product brief ## Problem The planner UI can be built, but bringing every task source into one reliable, two-way calendar workflow means OAuth, APIs, sync conflicts, and maintenance. ## Product outcome One inbox for tasks from several systems, emptied by keyboard in minutes, planned onto real calendar time, and never silently losing a change made in two places. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Google/Microsoft OAuth - integrations to task apps - hosted backend - background sync - notifications ## Explicit non-goals for v1 - Breadth of source integrations. - Team assignment and shared projects. - Automatic scheduling into free time. - Mobile applications. - wide integrations - two-way sync - keyboard UX - mobile/desktop apps - support for API breakage ## Success criteria - Re-importing is confirmed not to duplicate - Both-side changes are confirmed to produce a conflict - Fifty items are confirmed to triage in under five minutes without a mouse - A foreign calendar event is confirmed unmodifiable - A stale source is confirmed to alert
# Build brief · Akiflow 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 one inbox for tasks from everywhere, emptied into a day plan by keyboard to replace Akiflow. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Sources: Read-write connectors per source with stable external identity · a task that exists in two systems must be one task here - Sync: Two-way with conflict detection, never last write wins · a task completed in both places at once must not lose a note - Command bar: Every action reachable by keyboard from anywhere · the speed of triage is what determines whether the inbox gets emptied - Planning: Tasks placed onto calendar time blocks · a task list without time is a list of intentions - Runtime: TypeScript with a local application and SQLite · the inbox is personal and should work offline ### Data model (create this before Phase 1) - sources(id, kind, account_ref, connected_at, last_synced_at, cursor, disabled_reason) - tasks(id, source_id, external_id, title, notes, due_at, completed_at, deleted_at, updated_at, local_version, remote_version) - inbox(task_id, arrived_at, triaged_at) · the queue you empty - blocks(id, task_id, starts_at, ends_at, calendar_event_id, pinned) · time on the calendar - conflicts(id, task_id, field, local_value, remote_value, detected_at, resolved_at, resolution) - commands(id, name, keys, context) · the command surface, so every action is reachable - A task holds both a local and a remote version, because detecting that both changed is the only way to avoid silently discarding one. ### Phase 1 · One task, however many systems Build: Connect sources read only first, importing tasks with their external identity. Deduplicate on the source's identifiers. Handle a source that renames or reissues identifiers, and one that disappears. Trap: Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. Done when: Tasks import with their external identity and re-importing does not duplicate. Two tasks with the same title from one source stay separate. A task renamed at the source updates rather than duplicating. A disconnected source's tasks are marked orphaned rather than deleted. Do not build yet: No two-way sync, inbox flow or planning. ### Phase 2 · Writing back without losing anything Build: Two-way sync with version tracking on both sides. Detect a task changed locally and remotely since the last sync and surface it as a conflict rather than choosing. Make completion idempotent. Trap: Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. Done when: A task changed on both sides since the last sync produces a conflict rather than a silent resolution, tested by editing in both. Completion applied twice does not produce two completions at the source. A conflict shows both values and the resolution is recorded. Sync resumes correctly after being interrupted mid-write. Do not build yet: No inbox flow, command bar or planning yet. ### Phase 3 · An inbox you can actually empty Build: An inbox of untriaged tasks with keyboard triage: schedule, defer, delegate, drop. Every action reachable from a command bar. Measure how long fifty items take to triage. Trap: A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. Done when: Fifty items triage in under five minutes without touching a mouse, timed by hand. Every action is reachable from the command bar and discoverable by search within it. Undo works for the last several triage actions. An item deferred returns to the inbox at the stated time. Do not build yet: No planning or calendar writing yet. ### Phase 4 · Tasks on the calendar, honestly Build: Place tasks into time blocks written to a calendar as events the tool owns. Never modify an event the tool did not create. Show when a day's blocks exceed the available time. Trap: Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. Done when: Blocks appear as calendar events identified by stored identifier rather than by title. A real meeting cannot be modified or deleted, verified by a test that attempts it. A day planned beyond its available time is flagged rather than silently accepted. Deleting a block removes its event and only its event. Do not build yet: No offline handling or reporting yet. ### Phase 5 · Working offline and knowing what happened Build: Queue local changes while offline and apply them on reconnection with conflict detection. Report sync health per source. Export everything including the mapping to external identifiers. Trap: A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself. Done when: Changes made offline queue and apply on reconnection with conflicts detected rather than silently resolved. Each source shows its last successful sync and stale sources are surfaced. An expired connection produces an alert rather than silence. Export includes external identifier mappings so the data is portable. Do not build yet: Nothing after this. ### Out of scope (and why) - Breadth of source integrations, which is the hosted product's actual value and an endless maintenance commitment. - Team task assignment and shared projects, which is a different product with a permissions model. - Automatic scheduling of tasks into free time, which is a different build with its own solver requirements. - Mobile applications, where the keyboard-first design does not transfer at all. ### README must contain - That tasks are keyed on the source's own identifiers rather than on titles - That a task changed on both sides produces a conflict rather than a silent resolution - That every action is keyboard reachable, because one mouse reach per item ends the triage rhythm - That the tool owns calendar events by stored identifier and never touches anything else - That a stale source is alerted on, since nothing syncing produces no error by itself
# Architecture · Akiflow ## Stack | Part | Choice | Why | | --- | --- | --- | | Sources | Read-write connectors per source with stable external identity | a task that exists in two systems must be one task here | | Sync | Two-way with conflict detection, never last write wins | a task completed in both places at once must not lose a note | | Command bar | Every action reachable by keyboard from anywhere | the speed of triage is what determines whether the inbox gets emptied | | Planning | Tasks placed onto calendar time blocks | a task list without time is a list of intentions | | Runtime | TypeScript with a local application and SQLite | the inbox is personal and should work offline | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Sources | Identity and import per system. | Any connector, provided identity comes from the source rather than from titles. | | Sync | Two-way writes with conflict detection. | Not swappable for last write wins, which discards notes silently. | | Commands | Keyboard reachability for everything. | Any interface, provided no action requires a mouse. | | Blocks | Calendar events the tool owns. | Any provider, provided ownership is by stored identifier. | | Offline | Queued changes and sync health. | Any implementation, provided staleness is alerted rather than silent. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Tasks, conflicts and the offline queue. - `SOURCE_TOKENS` · required, secret · Write access to your task systems. Scope narrowly. - `CALENDAR_CLIENT_ID` · required, secret · Create and update only the tool's own events. - `SYNC_STALE_HOURS` · required · When a source is surfaced as stale. Silence is the failure mode. - `CONFLICT_RESOLUTION` · required · Never last write wins. The rare case is worth a question. - `COMMAND_BAR_KEY` · required · The entry point to every action. Choose something nothing else claims. ## 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 · Akiflow product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Read-write connectors per source with stable external identity, Two-way with conflict detection, never last write wins, Every action reachable by keyboard from anywhere, Tasks placed onto calendar time blocks, TypeScript with a local application and SQLite. - 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 - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. - A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself.
# Delivery milestones · Akiflow Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · One task, however many systems A task from any source is one item here and stays linked to its origin. ### Steps 1. Key on external identifiers Titles collide and change. Identifiers do not. Files: `src/sources.ts` 2. Handle identifier reissue Some systems reuse identifiers, which is worse than changing them. Files: `src/sync.ts` 3. Orphan rather than delete A disconnected source is not a deletion instruction. Files: `src/sources.ts` ### Done when - [ ] Re-importing does not duplicate - [ ] Same-titled tasks stay separate - [ ] Renaming updates rather than duplicating - [ ] Disconnection orphans rather than deletes ### Watch out - Deduplicating imported tasks on title means two genuinely different tasks called follow up merge into one and a note is lost, while the same task retitled at the source arrives as a second item. Key on the source's own identifiers and treat a missing identifier as a source you cannot sync rather than one to guess about. ## M2 · Writing back without losing anything A change made in both places is detected rather than silently resolved. ### Steps 1. Track versions on both sides It is the only way to know both changed. Files: `src/versions.ts` 2. Surface conflicts rather than resolving They are rare enough that asking is tolerable and losing a note is not. Files: `src/conflicts.ts`, `tests/conflict.test.ts` 3. Make writes idempotent A retried completion should not complete twice. Files: `src/write.ts` ### Done when - [ ] Both-side changes produce a conflict - [ ] Repeated completion is idempotent - [ ] Conflicts show both values and record resolution - [ ] Interrupted sync resumes correctly ### Watch out - Last write wins across a two-way sync silently discards whichever change arrived first, and for a task that is usually a note somebody typed and will never think to check again. Track both versions, detect divergence, and ask, since the cases are rare enough that asking is tolerable. ## M3 · An inbox you can actually empty Triaging fifty items takes minutes and never a mouse. ### Steps 1. Make every action keyboard reachable One mouse reach per item breaks the rhythm and the model. Files: `src/commands.ts` 2. Make the command bar searchable So actions are discoverable without memorising them. Files: `src/command-bar.ts` 3. Support undo across triage Fast triage produces mistakes and they should be cheap. Files: `src/undo.ts` ### Done when - [ ] Fifty items triage in under five minutes without a mouse - [ ] Every action is in the searchable command bar - [ ] Undo covers recent triage actions - [ ] Deferred items return at the stated time ### Watch out - A triage flow requiring a mouse for any step breaks the rhythm entirely, and a rhythm broken every few items means the inbox does not get emptied and the whole model collapses. Make every action keyboard reachable and measure a realistic triage session rather than assuming. ## M4 · Tasks on the calendar, honestly Planned work occupies real time and the calendar reflects it. ### Steps 1. Own events by stored identifier Never by title. A rename must not orphan a block. Files: `src/calendar.ts` 2. Refuse to touch foreign events Tested by attempting it and expecting a refusal. Files: `tests/foreign-events.test.ts` 3. Flag an overfull day Planning eleven hours into eight is worth being told about. Files: `src/capacity.ts` ### Done when - [ ] Blocks are identified by stored id - [ ] Foreign events cannot be modified - [ ] An overfull day is flagged - [ ] Deleting a block removes only its event ### Watch out - Identifying the tool's own calendar events by title or colour means a renamed block becomes invisible to the tool and gets duplicated, or worse, a coincidence of naming makes it modify a real meeting. Store the created event identifier and treat everything else as untouchable. ## M5 · Working offline and knowing what happened (production only) The planner works on a train and reconciles cleanly afterwards. ### Steps 1. Queue offline changes A planner that requires a connection is not a planner on a train. Files: `src/offline.ts` 2. Alert on stale sources Silence is the failure mode. Nothing errors when nothing syncs. Files: `src/health.ts` 3. Export with external mappings So the data can move rather than being trapped between systems. Files: `src/export.ts` ### Done when - [ ] Offline changes apply with conflict detection - [ ] Each source shows its last successful sync - [ ] An expired connection alerts - [ ] Export includes external identifier mappings ### Watch out - A source whose connection expired quietly stops syncing, so tasks completed here never reach the system your colleagues look at, and nobody finds out until someone asks why a finished task is still open. Show last successful sync per source and alert on staleness, since absence of data raises no error by itself.
# Operations · Akiflow ## Backup The local database holds the offline queue and the external identifier mappings, which are what make the data reconcilable. Tasks themselves mostly live in their source systems, so the mappings are the irreplaceable part. ## Restore Restore and confirm external identifier mappings survived, since without them a resync treats every task as new and duplicates your entire backlog in the source systems. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Time since last successful sync per source, and unresolved conflict count. The first fails silently and the second accumulates until the data is untrustworthy. ## Incident checklist Duplicated tasks in a source system is the visible failure and means identity was lost, usually after a token expiry and a full resync. The mapping table is what prevents it and what diagnoses it. 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 - [ ] Re-importing is confirmed not to duplicate - [ ] Both-side changes are confirmed to produce a conflict - [ ] Fifty items are confirmed to triage in under five minutes without a mouse - [ ] A foreign calendar event is confirmed unmodifiable - [ ] A stale source is confirmed to alert ## Launch constraint Do not market omitted Akiflow 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. Tasks, conflicts and the offline queue. DB_PATH=~/.local/share/planner/tasks.db # Required · secret. Write access to your task systems. Scope narrowly. SOURCE_TOKENS=a keychain reference # Required · secret. Create and update only the tool's own events. CALENDAR_CLIENT_ID=a provider client id # Required. When a source is surfaced as stale. Silence is the failure mode. SYNC_STALE_HOURS=12 # Required. Never last write wins. The rare case is worth a question. CONFLICT_RESOLUTION=ask # Required. The entry point to every action. Choose something nothing else claims. COMMAND_BAR_KEY=Ctrl+K
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay to stop context-switching between task apps and calendars.
xwide integrations
xtwo-way sync
xkeyboard UX
xmobile/desktop apps
xsupport for API breakage
Don't feel like building it? These folks already made it free.
all 3 free alternatives to Akiflow →· no votes, no pay-to-list · just what's real
Akiflow pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| pro | $34/user | $19/user | 1 user; tasks, calendars, integrations, and time blocking; no numeric task/calendar cap published. |
| teams | custom | — | Custom seats, onboarding, and team controls. |
free tierno free tier
billingmonthly + annual (-44% advertised); 7-day trial; Teams custom
hidden costsThe $19 annual equivalent is promotional; renewal terms beyond the displayed offer are not stated publicly.
verified 2026-08-14 · source ↗
Vibecode Akiflow
Kinda. The core of Akiflow is buildable in a weekend with the prompt on this page, but there are real gaps: wide integrations, two-way sync. Read the honest list above before committing.
How much does Akiflow cost?
Akiflow costs about $34/month (Pro Monthly, checked 2026-07-30), which is $408 per year.
What do I lose by replacing Akiflow?
Honestly: wide integrations; two-way sync; keyboard UX; mobile/desktop apps; support for API breakage. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Akiflow?
Yes: Super Productivity (A local task planner with timeboxes, focus mode and its own stopwatch; less glossy, less interested in your wallet.) FlowSavvy Free (It auto-plans unlimited tasks for the next two weeks; priorities and dependencies are how the paid plan gets you.) Trevor AI Free (One calendar gets task-to-time-block planning and some AI suggestions; multiple calendars and deeper automation cost extra.) All 3 curated free alternatives are at vibecodeit.com/akiflow/alternatives. The prompt is for when you want it exactly your way.