Vibecode Everhour
track this build4 phases, 12 steps, beginner friendly0%Everhour's solo core is compact: build a private time + budget tracking ledger that imports user-supplied CSV files, categorizes entries, reconciles balances, and exports reports. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on banking, compliance, trust.
You are building a lean indie version of Everhour. 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 ===== # Everhour · indie build A solo billing ledger whose hard problems are measuring elapsed time on a machine that sleeps, pricing work at the rate that was in force when it happened, and producing an invoice a receiving system can process rather than a picture of one. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node with TypeScript and SQLite, single user, bound to localhost | one person's billable time does not need a server anybody else operates | | Timing | Elapsed measured from a monotonic source, with wall clock start and stop stored beside it | a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice | | Money | Integer minor units with a currency, and rates that are valid over a period | a rate change must never silently reprice work that has already been billed | | Invoices | Issued invoices are immutable and every later change is a credit note | an invoice you can edit after issuing is a number two systems will disagree about | | Export | A structured invoice document beside the readable one, and a stable column contract for the ledger | a document a person has to retype into the next system is where your figures start to diverge | ## 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 ledger, the timer and the export. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Which structured invoice syntax you will emit** · free - Why: The semantic standard permits more than one, and the receiving side usually accepts one of them, so picking after you have written the serialiser means writing it twice. - Get it: Ask whoever receives your invoices which format they accept, read the current specification for it, and write the choice and the date into the repository. - Verify: A sample invoice in that syntax passes the standard's validation artefacts - [ ] **A validator for the structured invoice format** · free - Why: Emitting something that looks right and is rejected on receipt is the failure this whole phase exists to prevent. - Get it: Use the validation artefacts published alongside the standard for your chosen syntax. - Verify: A deliberately broken invoice fails and a good one passes - [ ] **Real time entries and one real past invoice** · free - Why: Rate periods, rounding and the sequence rules only bite against data that already has a history, and a clean fixture hides all three. - Get it: Take a month of your own tracked time and an invoice you have already issued. - Verify: The ledger reproduces that invoice's total exactly - [ ] **How long you must keep invoices, and where that comes from** · free - Why: The retention default should be a figure you looked up rather than one that felt sensible, because it is the one operational setting with an outside authority behind it. - Get it: Read your own jurisdiction's published record-keeping requirement and note the figure, the source and the date. - Verify: The configuration records the period, the source and the date checked ## 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: - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. - bank, payment, tax, and reconciliation integrations - live bank feeds and payment rails - tax and accounting rule updates - audited security and recovery - multi-user controls and accountant workflows If one of those is essential to you, that is the reason to keep paying for Everhour, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Everhour The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private time, budget and invoice ledger whose exports another system can read without retyping to replace Everhour. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node with TypeScript and SQLite, single user, bound to localhost · one person's billable time does not need a server anybody else operates - Timing: Elapsed measured from a monotonic source, with wall clock start and stop stored beside it · a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice - Money: Integer minor units with a currency, and rates that are valid over a period · a rate change must never silently reprice work that has already been billed - Invoices: Issued invoices are immutable and every later change is a credit note · an invoice you can edit after issuing is a number two systems will disagree about - Export: A structured invoice document beside the readable one, and a stable column contract for the ledger · a document a person has to retype into the next system is where your figures start to diverge ### Data model (create this before Phase 1) - clients(id, name, currency, address_json, tax_id) and projects(id, client_id, name, budget_kind, budget_minor, budget_hours, rounding_rule, closed_at) - rates(id, project_id, person, rate_minor, currency, valid_from, valid_to) · periods, so last quarter stays priced at last quarter's rate - entries(id, project_id, task, started_at_utc, stopped_at_utc, elapsed_ms, source, note, locked_by_invoice_id) · elapsed is measured rather than derived from the two timestamps - timer_state(id, project_id, started_wall, started_monotonic, heartbeat_at) · written on a heartbeat, so a crash costs seconds instead of an afternoon - invoices(id, client_id, number, issued_at, currency, subtotal_minor, tax_minor, total_minor, state, readable_path, structured_path, rounding_rule) - invoice_lines(invoice_id, ordinal, description, quantity_milli, unit_price_minor, amount_minor, entry_ids_json) · quantities in thousandths so a rounding rule has something to round - credit_notes(id, invoice_id, number, issued_at, amount_minor, reason) and expenses(id, project_id, at, amount_minor, currency, category, receipt_path, billable) - Once an invoice is issued its lines and the entries behind them are locked. Everything after that is a credit note, because the alternative is two parties holding different versions of the same document number. ### Phase 1 · A timer that survives a closed laptop Build: Start a timer by recording the wall clock time and a monotonic reading, and write a heartbeat while it runs. Compute elapsed from the monotonic source. On restart, a timer whose heartbeat is stale is presented with the gap for confirmation rather than committed. Manual entry always works. Trap: Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. Done when: A timer running across a simulated suspend produces elapsed equal to the awake time only, tested with a fake clock. A running timer whose heartbeat is stale is presented for confirmation with the gap shown and is never committed automatically, tested by killing the process. Killing the process loses at most one heartbeat interval of recorded time. Every entry records whether it was timed or entered by hand, and the interface shows which. Do not build yet: No rates, budgets, invoicing or export yet. ### Phase 2 · Rates that belong to a period Build: Give rates a validity period per project and person. Price each entry by the rate in force when the work started, and state in one place what happens to an entry spanning a boundary. Track budgets in both money and hours with alert thresholds. Apply the rounding rule once, at invoicing. Trap: Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. Done when: Raising a rate leaves every historic project total unchanged to the penny, tested. An entry spanning a rate boundary is priced by a stated rule and that rule appears in the README. Rounding is applied once at invoice time and the rule applied is stored on the invoice, tested by comparing an entry total to an invoice line. Budget alerts fire at the configured thresholds in money and in hours independently. Do not build yet: No invoicing, credit notes or export yet. ### Phase 3 · Invoices that stop being editable Build: Build an invoice from unbilled entries and lock them. Allocate the number only at the moment of issue. Issued lines are immutable, enforced by the database rather than by the interface. Voiding keeps the number and the document. Every later change is a credit note with its own sequence. Trap: Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. Done when: Numbers are allocated at issue and a test asserts no gaps across a sequence that includes voided invoices. An attempt to modify an issued invoice line fails at the database level, tested by trying it directly rather than through the interface. Entries locked by an issued invoice cannot be edited or invoiced again, tested. A credit note references its invoice and both appear in the client's balance with the right signs. Do not build yet: No structured export, ledger export or retention yet. ### Phase 4 · An invoice another system can read Build: Emit a structured invoice conforming to the European semantic model in one of its permitted syntaxes, alongside the readable document. Validate the structured file before writing it and fail the export rather than emitting something invalid. Give the ledger export a documented, stable column contract and a test that fails when a column is renamed or reordered. Trap: A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. Done when: Every issued invoice writes both a readable document and a structured one, asserted in the suite. The structured file is validated before it is written and an invalid one fails the export with the validation output stored, tested with a deliberately broken invoice. The ledger export has a documented column contract and a test fails if a column is renamed, removed or reordered. A sample structured invoice is checked against the standard's own validation artefacts and the result is recorded in the repository. Do not build yet: No retention, backup or handover work yet. ### Phase 5 · Handing it over without retyping Build: Produce a handover bundle: structured invoices, credit notes, the ledger export and the receipts, with a manifest. Record what was exported and when. Keep issued documents for a configured period with the source of that figure noted. Take dated backups and exercise a restore in the suite. Trap: The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. Done when: A handover bundle is read by the next system without any manual retyping, demonstrated once and the steps recorded in the README. Every export writes a manifest recording what left and when. A retention setting keeps issued invoices, credit notes and structured documents for a configured period, with the source of that period noted in configuration. Restoring a backup reproduces every invoice total and the whole sequence exactly, exercised in the test suite. Do not build yet: Nothing after this. ### Out of scope (and why) - Bank feeds and payment rails, which mean holding credentials to your own money inside a tool you wrote at the weekend. - Automatic tax determination, which changes by jurisdiction and by year and is the part where being approximately right is worthless. - Multiple users with roles and approvals, which needs an authorisation model and an audit trail before any of it can be trusted. - Integrations that pull tasks from a project tool, which are pleasant and are also a permanent maintenance commitment to somebody else's API. ### README must contain - That elapsed time is measured from a monotonic source, with the suspend test named as the reason - That rates have validity periods, and the stated rule for an entry spanning a boundary - That invoice numbers are allocated at issue, that issued invoices are immutable in the database, and that corrections are credit notes - That a readable document is not an electronic invoice under the European definition, quoted with the dates checked, and which syntax this build emits - That an export requiring manual retyping breaks the digital link the revenue guidance requires, and how the handover bundle avoids it ===== AGENTS.md ===== # Agent instructions · Everhour indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node with TypeScript and SQLite, single user, bound to localhost, Elapsed measured from a monotonic source, with wall clock start and stop stored beside it, Integer minor units with a currency, and rates that are valid over a period, Issued invoices are immutable and every later change is a credit note, A structured invoice document beside the readable one, and a stable column contract for the ledger. 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 - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ===== BUILD_PLAN.md ===== # Build plan · Everhour A solo billing ledger whose hard problems are measuring elapsed time on a machine that sleeps, pricing work at the rate that was in force when it happened, and producing an invoice a receiving system can process rather than a picture of one. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A timer that survives a closed laptop Elapsed time is measured, and a timer running through a suspend does not bill the gap. ### Steps 1. Measure elapsed from a monotonic source Wall clock subtraction bills the hours the machine spent asleep. Files: `src/timer/elapsed.ts`, `tests/suspend.test.ts` 2. Heartbeat while running It bounds the loss from a crash to something you will not notice. Files: `src/timer/heartbeat.ts` 3. Ask about the gap instead of guessing Only the person knows whether they were working. Files: `src/timer/recover.ts`, `src/views/recover.tsx` ### Done when - [ ] A suspend does not add billable time - [ ] A stale heartbeat is confirmed, never auto-committed - [ ] A crash loses at most one heartbeat interval - [ ] Timed and manual entries are distinguishable ### Watch out - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. ## Phase 2 · Rates that belong to a period Raising a rate changes nothing about work that has already happened. ### Steps 1. Give rates a validity period, not a current value The rate is a property of the week, not of the project. Files: `src/rates.ts`, `tests/historic.test.ts` 2. Decide and document the boundary rule Someone will work across a rate change on the first of the month. Files: `src/rates.ts`, `README.md` 3. Round once, at invoicing, and store the rule Rounding at entry time rounds the same minute twice. Files: `src/invoice/round.ts` ### Done when - [ ] A rate rise leaves historic totals untouched - [ ] A boundary-spanning entry follows a documented rule - [ ] Rounding happens once and the rule is stored - [ ] Budget alerts work in money and in hours ### Watch out - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. ## Phase 3 · Invoices that stop being editable Issuing is irreversible, the sequence has no gaps, and corrections are credit notes. ### Steps 1. Allocate the number at issue, never at draft A draft that is never issued should leave no trace in the sequence. Files: `src/invoice/issue.ts`, `tests/sequence.test.ts` 2. Enforce immutability in the database Interface-level protection is protection until the next feature. Files: `migrations/003_lock.sql` 3. Void by keeping, correct by crediting Both leave the trail somebody will ask to see. Files: `src/invoice/void.ts`, `src/creditnote.ts` ### Done when - [ ] The issued sequence has no gaps, voids included - [ ] Issued lines cannot be modified at the database level - [ ] Locked entries cannot be re-invoiced - [ ] Credit notes net correctly against the balance ### Watch out - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. ## Phase 4 · An invoice another system can read Every issued invoice produces a structured document as well as a readable one. ### Steps 1. Emit structured and readable together, always A structured document produced only on request is produced never. Files: `src/invoice/structured.ts`, `tests/structured.test.ts` 2. Validate before writing and fail the export An invalid file that exists is worse than no file. Files: `src/invoice/validate.ts` 3. Pin the ledger column contract with a test Whatever reads it downstream will break silently otherwise. Files: `src/export/ledger.ts`, `tests/columns.test.ts` ### Done when - [ ] Both documents are produced for every issued invoice - [ ] An invalid structured file fails the export - [ ] The column contract is pinned by a test - [ ] A sample passes the standard's own validation ### Watch out - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ## Not in this build - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. ## After v1, if you want it - Recurring invoices, which are worth doing only once the sequence and immutability rules are proven - A second structured syntax behind the same serialiser interface, which is how you find out the abstraction was real - A reminder for unpaid invoices built from the credit note and payment state rather than from a spreadsheet ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Entries, rates, invoices and credit notes. The irreplaceable part. DATABASE_PATH=./data/ledger.db # Required. Readable and structured invoices, credit notes and receipts. DOCUMENT_DIR=./data/documents # Required. The structured syntax you emit. Recorded on every invoice row as well. INVOICE_SYNTAX=ubl # Required. Prefix only. The sequence itself is allocated at issue and never reused. INVOICE_NUMBER_PREFIX=2026- # Required. Bounds how much recorded time a crash can cost. HEARTBEAT_SECONDS=15 # Required. Totals are presented in this. Each project keeps its own currency. BASE_CURRENCY=GBP # Required. Check your own jurisdiction's published requirement and record the source beside this. RETENTION_YEARS=6 # Required. Dated copies. The restore test reads from here. BACKUP_DIR=./data/backups
You are building a lean indie version of Everhour. 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 ===== # Everhour · indie build A solo billing ledger whose hard problems are measuring elapsed time on a machine that sleeps, pricing work at the rate that was in force when it happened, and producing an invoice a receiving system can process rather than a picture of one. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node with TypeScript and SQLite, single user, bound to localhost | one person's billable time does not need a server anybody else operates | | Timing | Elapsed measured from a monotonic source, with wall clock start and stop stored beside it | a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice | | Money | Integer minor units with a currency, and rates that are valid over a period | a rate change must never silently reprice work that has already been billed | | Invoices | Issued invoices are immutable and every later change is a credit note | an invoice you can edit after issuing is a number two systems will disagree about | | Export | A structured invoice document beside the readable one, and a stable column contract for the ledger | a document a person has to retype into the next system is where your figures start to diverge | ## 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 ledger, the timer and the export. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Which structured invoice syntax you will emit** · free - Why: The semantic standard permits more than one, and the receiving side usually accepts one of them, so picking after you have written the serialiser means writing it twice. - Get it: Ask whoever receives your invoices which format they accept, read the current specification for it, and write the choice and the date into the repository. - Verify: A sample invoice in that syntax passes the standard's validation artefacts - [ ] **A validator for the structured invoice format** · free - Why: Emitting something that looks right and is rejected on receipt is the failure this whole phase exists to prevent. - Get it: Use the validation artefacts published alongside the standard for your chosen syntax. - Verify: A deliberately broken invoice fails and a good one passes - [ ] **Real time entries and one real past invoice** · free - Why: Rate periods, rounding and the sequence rules only bite against data that already has a history, and a clean fixture hides all three. - Get it: Take a month of your own tracked time and an invoice you have already issued. - Verify: The ledger reproduces that invoice's total exactly - [ ] **How long you must keep invoices, and where that comes from** · free - Why: The retention default should be a figure you looked up rather than one that felt sensible, because it is the one operational setting with an outside authority behind it. - Get it: Read your own jurisdiction's published record-keeping requirement and note the figure, the source and the date. - Verify: The configuration records the period, the source and the date checked ## 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: - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. - bank, payment, tax, and reconciliation integrations - live bank feeds and payment rails - tax and accounting rule updates - audited security and recovery - multi-user controls and accountant workflows If one of those is essential to you, that is the reason to keep paying for Everhour, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Everhour The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private time, budget and invoice ledger whose exports another system can read without retyping to replace Everhour. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node with TypeScript and SQLite, single user, bound to localhost · one person's billable time does not need a server anybody else operates - Timing: Elapsed measured from a monotonic source, with wall clock start and stop stored beside it · a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice - Money: Integer minor units with a currency, and rates that are valid over a period · a rate change must never silently reprice work that has already been billed - Invoices: Issued invoices are immutable and every later change is a credit note · an invoice you can edit after issuing is a number two systems will disagree about - Export: A structured invoice document beside the readable one, and a stable column contract for the ledger · a document a person has to retype into the next system is where your figures start to diverge ### Data model (create this before Phase 1) - clients(id, name, currency, address_json, tax_id) and projects(id, client_id, name, budget_kind, budget_minor, budget_hours, rounding_rule, closed_at) - rates(id, project_id, person, rate_minor, currency, valid_from, valid_to) · periods, so last quarter stays priced at last quarter's rate - entries(id, project_id, task, started_at_utc, stopped_at_utc, elapsed_ms, source, note, locked_by_invoice_id) · elapsed is measured rather than derived from the two timestamps - timer_state(id, project_id, started_wall, started_monotonic, heartbeat_at) · written on a heartbeat, so a crash costs seconds instead of an afternoon - invoices(id, client_id, number, issued_at, currency, subtotal_minor, tax_minor, total_minor, state, readable_path, structured_path, rounding_rule) - invoice_lines(invoice_id, ordinal, description, quantity_milli, unit_price_minor, amount_minor, entry_ids_json) · quantities in thousandths so a rounding rule has something to round - credit_notes(id, invoice_id, number, issued_at, amount_minor, reason) and expenses(id, project_id, at, amount_minor, currency, category, receipt_path, billable) - Once an invoice is issued its lines and the entries behind them are locked. Everything after that is a credit note, because the alternative is two parties holding different versions of the same document number. ### Phase 1 · A timer that survives a closed laptop Build: Start a timer by recording the wall clock time and a monotonic reading, and write a heartbeat while it runs. Compute elapsed from the monotonic source. On restart, a timer whose heartbeat is stale is presented with the gap for confirmation rather than committed. Manual entry always works. Trap: Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. Done when: A timer running across a simulated suspend produces elapsed equal to the awake time only, tested with a fake clock. A running timer whose heartbeat is stale is presented for confirmation with the gap shown and is never committed automatically, tested by killing the process. Killing the process loses at most one heartbeat interval of recorded time. Every entry records whether it was timed or entered by hand, and the interface shows which. Do not build yet: No rates, budgets, invoicing or export yet. ### Phase 2 · Rates that belong to a period Build: Give rates a validity period per project and person. Price each entry by the rate in force when the work started, and state in one place what happens to an entry spanning a boundary. Track budgets in both money and hours with alert thresholds. Apply the rounding rule once, at invoicing. Trap: Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. Done when: Raising a rate leaves every historic project total unchanged to the penny, tested. An entry spanning a rate boundary is priced by a stated rule and that rule appears in the README. Rounding is applied once at invoice time and the rule applied is stored on the invoice, tested by comparing an entry total to an invoice line. Budget alerts fire at the configured thresholds in money and in hours independently. Do not build yet: No invoicing, credit notes or export yet. ### Phase 3 · Invoices that stop being editable Build: Build an invoice from unbilled entries and lock them. Allocate the number only at the moment of issue. Issued lines are immutable, enforced by the database rather than by the interface. Voiding keeps the number and the document. Every later change is a credit note with its own sequence. Trap: Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. Done when: Numbers are allocated at issue and a test asserts no gaps across a sequence that includes voided invoices. An attempt to modify an issued invoice line fails at the database level, tested by trying it directly rather than through the interface. Entries locked by an issued invoice cannot be edited or invoiced again, tested. A credit note references its invoice and both appear in the client's balance with the right signs. Do not build yet: No structured export, ledger export or retention yet. ### Phase 4 · An invoice another system can read Build: Emit a structured invoice conforming to the European semantic model in one of its permitted syntaxes, alongside the readable document. Validate the structured file before writing it and fail the export rather than emitting something invalid. Give the ledger export a documented, stable column contract and a test that fails when a column is renamed or reordered. Trap: A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. Done when: Every issued invoice writes both a readable document and a structured one, asserted in the suite. The structured file is validated before it is written and an invalid one fails the export with the validation output stored, tested with a deliberately broken invoice. The ledger export has a documented column contract and a test fails if a column is renamed, removed or reordered. A sample structured invoice is checked against the standard's own validation artefacts and the result is recorded in the repository. Do not build yet: No retention, backup or handover work yet. ### Phase 5 · Handing it over without retyping Build: Produce a handover bundle: structured invoices, credit notes, the ledger export and the receipts, with a manifest. Record what was exported and when. Keep issued documents for a configured period with the source of that figure noted. Take dated backups and exercise a restore in the suite. Trap: The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. Done when: A handover bundle is read by the next system without any manual retyping, demonstrated once and the steps recorded in the README. Every export writes a manifest recording what left and when. A retention setting keeps issued invoices, credit notes and structured documents for a configured period, with the source of that period noted in configuration. Restoring a backup reproduces every invoice total and the whole sequence exactly, exercised in the test suite. Do not build yet: Nothing after this. ### Out of scope (and why) - Bank feeds and payment rails, which mean holding credentials to your own money inside a tool you wrote at the weekend. - Automatic tax determination, which changes by jurisdiction and by year and is the part where being approximately right is worthless. - Multiple users with roles and approvals, which needs an authorisation model and an audit trail before any of it can be trusted. - Integrations that pull tasks from a project tool, which are pleasant and are also a permanent maintenance commitment to somebody else's API. ### README must contain - That elapsed time is measured from a monotonic source, with the suspend test named as the reason - That rates have validity periods, and the stated rule for an entry spanning a boundary - That invoice numbers are allocated at issue, that issued invoices are immutable in the database, and that corrections are credit notes - That a readable document is not an electronic invoice under the European definition, quoted with the dates checked, and which syntax this build emits - That an export requiring manual retyping breaks the digital link the revenue guidance requires, and how the handover bundle avoids it ===== AGENTS.md ===== # Agent instructions · Everhour indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node with TypeScript and SQLite, single user, bound to localhost, Elapsed measured from a monotonic source, with wall clock start and stop stored beside it, Integer minor units with a currency, and rates that are valid over a period, Issued invoices are immutable and every later change is a credit note, A structured invoice document beside the readable one, and a stable column contract for the ledger. 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 - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ===== BUILD_PLAN.md ===== # Build plan · Everhour A solo billing ledger whose hard problems are measuring elapsed time on a machine that sleeps, pricing work at the rate that was in force when it happened, and producing an invoice a receiving system can process rather than a picture of one. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A timer that survives a closed laptop Elapsed time is measured, and a timer running through a suspend does not bill the gap. ### Steps 1. Measure elapsed from a monotonic source Wall clock subtraction bills the hours the machine spent asleep. Files: `src/timer/elapsed.ts`, `tests/suspend.test.ts` 2. Heartbeat while running It bounds the loss from a crash to something you will not notice. Files: `src/timer/heartbeat.ts` 3. Ask about the gap instead of guessing Only the person knows whether they were working. Files: `src/timer/recover.ts`, `src/views/recover.tsx` ### Done when - [ ] A suspend does not add billable time - [ ] A stale heartbeat is confirmed, never auto-committed - [ ] A crash loses at most one heartbeat interval - [ ] Timed and manual entries are distinguishable ### Watch out - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. ## Phase 2 · Rates that belong to a period Raising a rate changes nothing about work that has already happened. ### Steps 1. Give rates a validity period, not a current value The rate is a property of the week, not of the project. Files: `src/rates.ts`, `tests/historic.test.ts` 2. Decide and document the boundary rule Someone will work across a rate change on the first of the month. Files: `src/rates.ts`, `README.md` 3. Round once, at invoicing, and store the rule Rounding at entry time rounds the same minute twice. Files: `src/invoice/round.ts` ### Done when - [ ] A rate rise leaves historic totals untouched - [ ] A boundary-spanning entry follows a documented rule - [ ] Rounding happens once and the rule is stored - [ ] Budget alerts work in money and in hours ### Watch out - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. ## Phase 3 · Invoices that stop being editable Issuing is irreversible, the sequence has no gaps, and corrections are credit notes. ### Steps 1. Allocate the number at issue, never at draft A draft that is never issued should leave no trace in the sequence. Files: `src/invoice/issue.ts`, `tests/sequence.test.ts` 2. Enforce immutability in the database Interface-level protection is protection until the next feature. Files: `migrations/003_lock.sql` 3. Void by keeping, correct by crediting Both leave the trail somebody will ask to see. Files: `src/invoice/void.ts`, `src/creditnote.ts` ### Done when - [ ] The issued sequence has no gaps, voids included - [ ] Issued lines cannot be modified at the database level - [ ] Locked entries cannot be re-invoiced - [ ] Credit notes net correctly against the balance ### Watch out - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. ## Phase 4 · An invoice another system can read Every issued invoice produces a structured document as well as a readable one. ### Steps 1. Emit structured and readable together, always A structured document produced only on request is produced never. Files: `src/invoice/structured.ts`, `tests/structured.test.ts` 2. Validate before writing and fail the export An invalid file that exists is worse than no file. Files: `src/invoice/validate.ts` 3. Pin the ledger column contract with a test Whatever reads it downstream will break silently otherwise. Files: `src/export/ledger.ts`, `tests/columns.test.ts` ### Done when - [ ] Both documents are produced for every issued invoice - [ ] An invalid structured file fails the export - [ ] The column contract is pinned by a test - [ ] A sample passes the standard's own validation ### Watch out - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ## Not in this build - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. ## After v1, if you want it - Recurring invoices, which are worth doing only once the sequence and immutability rules are proven - A second structured syntax behind the same serialiser interface, which is how you find out the abstraction was real - A reminder for unpaid invoices built from the credit note and payment state rather than from a spreadsheet ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Entries, rates, invoices and credit notes. The irreplaceable part. DATABASE_PATH=./data/ledger.db # Required. Readable and structured invoices, credit notes and receipts. DOCUMENT_DIR=./data/documents # Required. The structured syntax you emit. Recorded on every invoice row as well. INVOICE_SYNTAX=ubl # Required. Prefix only. The sequence itself is allocated at issue and never reused. INVOICE_NUMBER_PREFIX=2026- # Required. Bounds how much recorded time a crash can cost. HEARTBEAT_SECONDS=15 # Required. Totals are presented in this. Each project keeps its own currency. BASE_CURRENCY=GBP # Required. Check your own jurisdiction's published requirement and record the source beside this. RETENTION_YEARS=6 # Required. Dated copies. The restore test reads from here. BACKUP_DIR=./data/backups
You are building a production product version of Everhour. 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 ===== # Everhour · product brief ## Problem Everhour's solo core is compact: build a private time + budget tracking ledger that imports user-supplied CSV files, categorizes entries, reconciles balances, and exports reports. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on banking, compliance, trust. ## Product outcome A billing ledger that does not bill the hours your laptop spent asleep, does not reprice last quarter when you raise your rate, cannot have an issued invoice edited, and hands the next system a file it reads rather than a picture somebody has to retype. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - SQLite - CSV exports from the user's own accounts ## Explicit non-goals for v1 - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. - bank, payment, tax, and reconciliation integrations - live bank feeds and payment rails - tax and accounting rule updates - audited security and recovery - multi-user controls and accountant workflows ## Success criteria - A suspend is confirmed not to add billable time - A rate rise is confirmed to leave historic totals unchanged - An issued invoice line is confirmed immutable at the database level - An invalid structured invoice is confirmed to fail the export - A restore is confirmed to reproduce totals and the number sequence ===== BRIEF.md ===== # Build brief · Everhour The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private time, budget and invoice ledger whose exports another system can read without retyping to replace Everhour. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node with TypeScript and SQLite, single user, bound to localhost · one person's billable time does not need a server anybody else operates - Timing: Elapsed measured from a monotonic source, with wall clock start and stop stored beside it · a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice - Money: Integer minor units with a currency, and rates that are valid over a period · a rate change must never silently reprice work that has already been billed - Invoices: Issued invoices are immutable and every later change is a credit note · an invoice you can edit after issuing is a number two systems will disagree about - Export: A structured invoice document beside the readable one, and a stable column contract for the ledger · a document a person has to retype into the next system is where your figures start to diverge ### Data model (create this before Phase 1) - clients(id, name, currency, address_json, tax_id) and projects(id, client_id, name, budget_kind, budget_minor, budget_hours, rounding_rule, closed_at) - rates(id, project_id, person, rate_minor, currency, valid_from, valid_to) · periods, so last quarter stays priced at last quarter's rate - entries(id, project_id, task, started_at_utc, stopped_at_utc, elapsed_ms, source, note, locked_by_invoice_id) · elapsed is measured rather than derived from the two timestamps - timer_state(id, project_id, started_wall, started_monotonic, heartbeat_at) · written on a heartbeat, so a crash costs seconds instead of an afternoon - invoices(id, client_id, number, issued_at, currency, subtotal_minor, tax_minor, total_minor, state, readable_path, structured_path, rounding_rule) - invoice_lines(invoice_id, ordinal, description, quantity_milli, unit_price_minor, amount_minor, entry_ids_json) · quantities in thousandths so a rounding rule has something to round - credit_notes(id, invoice_id, number, issued_at, amount_minor, reason) and expenses(id, project_id, at, amount_minor, currency, category, receipt_path, billable) - Once an invoice is issued its lines and the entries behind them are locked. Everything after that is a credit note, because the alternative is two parties holding different versions of the same document number. ### Phase 1 · A timer that survives a closed laptop Build: Start a timer by recording the wall clock time and a monotonic reading, and write a heartbeat while it runs. Compute elapsed from the monotonic source. On restart, a timer whose heartbeat is stale is presented with the gap for confirmation rather than committed. Manual entry always works. Trap: Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. Done when: A timer running across a simulated suspend produces elapsed equal to the awake time only, tested with a fake clock. A running timer whose heartbeat is stale is presented for confirmation with the gap shown and is never committed automatically, tested by killing the process. Killing the process loses at most one heartbeat interval of recorded time. Every entry records whether it was timed or entered by hand, and the interface shows which. Do not build yet: No rates, budgets, invoicing or export yet. ### Phase 2 · Rates that belong to a period Build: Give rates a validity period per project and person. Price each entry by the rate in force when the work started, and state in one place what happens to an entry spanning a boundary. Track budgets in both money and hours with alert thresholds. Apply the rounding rule once, at invoicing. Trap: Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. Done when: Raising a rate leaves every historic project total unchanged to the penny, tested. An entry spanning a rate boundary is priced by a stated rule and that rule appears in the README. Rounding is applied once at invoice time and the rule applied is stored on the invoice, tested by comparing an entry total to an invoice line. Budget alerts fire at the configured thresholds in money and in hours independently. Do not build yet: No invoicing, credit notes or export yet. ### Phase 3 · Invoices that stop being editable Build: Build an invoice from unbilled entries and lock them. Allocate the number only at the moment of issue. Issued lines are immutable, enforced by the database rather than by the interface. Voiding keeps the number and the document. Every later change is a credit note with its own sequence. Trap: Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. Done when: Numbers are allocated at issue and a test asserts no gaps across a sequence that includes voided invoices. An attempt to modify an issued invoice line fails at the database level, tested by trying it directly rather than through the interface. Entries locked by an issued invoice cannot be edited or invoiced again, tested. A credit note references its invoice and both appear in the client's balance with the right signs. Do not build yet: No structured export, ledger export or retention yet. ### Phase 4 · An invoice another system can read Build: Emit a structured invoice conforming to the European semantic model in one of its permitted syntaxes, alongside the readable document. Validate the structured file before writing it and fail the export rather than emitting something invalid. Give the ledger export a documented, stable column contract and a test that fails when a column is renamed or reordered. Trap: A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. Done when: Every issued invoice writes both a readable document and a structured one, asserted in the suite. The structured file is validated before it is written and an invalid one fails the export with the validation output stored, tested with a deliberately broken invoice. The ledger export has a documented column contract and a test fails if a column is renamed, removed or reordered. A sample structured invoice is checked against the standard's own validation artefacts and the result is recorded in the repository. Do not build yet: No retention, backup or handover work yet. ### Phase 5 · Handing it over without retyping Build: Produce a handover bundle: structured invoices, credit notes, the ledger export and the receipts, with a manifest. Record what was exported and when. Keep issued documents for a configured period with the source of that figure noted. Take dated backups and exercise a restore in the suite. Trap: The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. Done when: A handover bundle is read by the next system without any manual retyping, demonstrated once and the steps recorded in the README. Every export writes a manifest recording what left and when. A retention setting keeps issued invoices, credit notes and structured documents for a configured period, with the source of that period noted in configuration. Restoring a backup reproduces every invoice total and the whole sequence exactly, exercised in the test suite. Do not build yet: Nothing after this. ### Out of scope (and why) - Bank feeds and payment rails, which mean holding credentials to your own money inside a tool you wrote at the weekend. - Automatic tax determination, which changes by jurisdiction and by year and is the part where being approximately right is worthless. - Multiple users with roles and approvals, which needs an authorisation model and an audit trail before any of it can be trusted. - Integrations that pull tasks from a project tool, which are pleasant and are also a permanent maintenance commitment to somebody else's API. ### README must contain - That elapsed time is measured from a monotonic source, with the suspend test named as the reason - That rates have validity periods, and the stated rule for an entry spanning a boundary - That invoice numbers are allocated at issue, that issued invoices are immutable in the database, and that corrections are credit notes - That a readable document is not an electronic invoice under the European definition, quoted with the dates checked, and which syntax this build emits - That an export requiring manual retyping breaks the digital link the revenue guidance requires, and how the handover bundle avoids it ===== ARCHITECTURE.md ===== # Architecture · Everhour ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node with TypeScript and SQLite, single user, bound to localhost | one person's billable time does not need a server anybody else operates | | Timing | Elapsed measured from a monotonic source, with wall clock start and stop stored beside it | a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice | | Money | Integer minor units with a currency, and rates that are valid over a period | a rate change must never silently reprice work that has already been billed | | Invoices | Issued invoices are immutable and every later change is a credit note | an invoice you can edit after issuing is a number two systems will disagree about | | Export | A structured invoice document beside the readable one, and a stable column contract for the ledger | a document a person has to retype into the next system is where your figures start to diverge | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Timer | Monotonic elapsed, heartbeat and recovery. | Any implementation, provided elapsed is measured rather than subtracted. | | Rates | Validity periods and the boundary rule. | Any store, provided a rate change cannot move a historic total. | | Invoicing | Sequence, immutability and credit notes. | Not swappable for anything that allocates a number before issue. | | Export | Structured documents, validation and the column contract. | Any syntax the receiving side accepts, provided it is validated before writing. | | Operations | Retention, manifests and restore. | Any scheduler, provided the restore is exercised rather than described. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Entries, rates, invoices and credit notes. The irreplaceable part. - `DOCUMENT_DIR` · required · Readable and structured invoices, credit notes and receipts. - `INVOICE_SYNTAX` · required · The structured syntax you emit. Recorded on every invoice row as well. - `INVOICE_NUMBER_PREFIX` · required · Prefix only. The sequence itself is allocated at issue and never reused. - `HEARTBEAT_SECONDS` · required · Bounds how much recorded time a crash can cost. - `BASE_CURRENCY` · required · Totals are presented in this. Each project keeps its own currency. - `RETENTION_YEARS` · required · Check your own jurisdiction's published requirement and record the source beside this. - `BACKUP_DIR` · required · Dated copies. The restore test reads from here. ## 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 · Everhour product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node with TypeScript and SQLite, single user, bound to localhost, Elapsed measured from a monotonic source, with wall clock start and stop stored beside it, Integer minor units with a currency, and rates that are valid over a period, Issued invoices are immutable and every later change is a credit note, A structured invoice document beside the readable one, and a stable column contract for the ledger. - 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 - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. - The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. ===== MILESTONES.md ===== # Delivery milestones · Everhour Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A timer that survives a closed laptop Elapsed time is measured, and a timer running through a suspend does not bill the gap. ### Steps 1. Measure elapsed from a monotonic source Wall clock subtraction bills the hours the machine spent asleep. Files: `src/timer/elapsed.ts`, `tests/suspend.test.ts` 2. Heartbeat while running It bounds the loss from a crash to something you will not notice. Files: `src/timer/heartbeat.ts` 3. Ask about the gap instead of guessing Only the person knows whether they were working. Files: `src/timer/recover.ts`, `src/views/recover.tsx` ### Done when - [ ] A suspend does not add billable time - [ ] A stale heartbeat is confirmed, never auto-committed - [ ] A crash loses at most one heartbeat interval - [ ] Timed and manual entries are distinguishable ### Watch out - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. ## M2 · Rates that belong to a period Raising a rate changes nothing about work that has already happened. ### Steps 1. Give rates a validity period, not a current value The rate is a property of the week, not of the project. Files: `src/rates.ts`, `tests/historic.test.ts` 2. Decide and document the boundary rule Someone will work across a rate change on the first of the month. Files: `src/rates.ts`, `README.md` 3. Round once, at invoicing, and store the rule Rounding at entry time rounds the same minute twice. Files: `src/invoice/round.ts` ### Done when - [ ] A rate rise leaves historic totals untouched - [ ] A boundary-spanning entry follows a documented rule - [ ] Rounding happens once and the rule is stored - [ ] Budget alerts work in money and in hours ### Watch out - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. ## M3 · Invoices that stop being editable Issuing is irreversible, the sequence has no gaps, and corrections are credit notes. ### Steps 1. Allocate the number at issue, never at draft A draft that is never issued should leave no trace in the sequence. Files: `src/invoice/issue.ts`, `tests/sequence.test.ts` 2. Enforce immutability in the database Interface-level protection is protection until the next feature. Files: `migrations/003_lock.sql` 3. Void by keeping, correct by crediting Both leave the trail somebody will ask to see. Files: `src/invoice/void.ts`, `src/creditnote.ts` ### Done when - [ ] The issued sequence has no gaps, voids included - [ ] Issued lines cannot be modified at the database level - [ ] Locked entries cannot be re-invoiced - [ ] Credit notes net correctly against the balance ### Watch out - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. ## M4 · An invoice another system can read Every issued invoice produces a structured document as well as a readable one. ### Steps 1. Emit structured and readable together, always A structured document produced only on request is produced never. Files: `src/invoice/structured.ts`, `tests/structured.test.ts` 2. Validate before writing and fail the export An invalid file that exists is worse than no file. Files: `src/invoice/validate.ts` 3. Pin the ledger column contract with a test Whatever reads it downstream will break silently otherwise. Files: `src/export/ledger.ts`, `tests/columns.test.ts` ### Done when - [ ] Both documents are produced for every issued invoice - [ ] An invalid structured file fails the export - [ ] The column contract is pinned by a test - [ ] A sample passes the standard's own validation ### Watch out - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ## M5 · Handing it over without retyping (production only) The next system reads your files directly, and a restore reproduces every total. ### Steps 1. Bundle everything with a manifest So the handover is one thing rather than six attachments. Files: `src/export/bundle.ts` 2. Record what was exported and when It is the answer to 'did you send me February'. Files: `src/export/log.ts` 3. Exercise the restore in the suite A backup nobody has restored is a hope with a filename. Files: `tests/restore.test.ts`, `scripts/backup.sh` ### Done when - [ ] The bundle is consumed without retyping - [ ] Every export writes a manifest - [ ] Retention is configured with its source noted - [ ] A restore reproduces totals and the sequence ### Watch out - The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. ===== OPERATIONS.md ===== # Operations · Everhour ## Backup Dated copies of the database and the document directory together, because a structured invoice on disk without the row that describes it is an orphan and the row without the file is a promise. Keep generations across a full retention period, not a fortnight. ## Restore Restore both, then run the suite's restore test: it should reproduce every invoice total and the full number sequence. A restore that renumbers anything has broken the one property an accountant checks first. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Timers running longer than a working day, entries not yet invoiced beyond a stated age, failed structured validations, and the age of the newest backup. The first catches the suspend case that the heartbeat missed. ## Incident checklist The incident is a figure that disagrees with the one the other side holds. The immutable invoice, the stored rounding rule, the rate validity periods and the export manifest together let you say exactly what was issued, priced how, and sent when. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A suspend is confirmed not to add billable time - [ ] A rate rise is confirmed to leave historic totals unchanged - [ ] An issued invoice line is confirmed immutable at the database level - [ ] An invalid structured invoice is confirmed to fail the export - [ ] A restore is confirmed to reproduce totals and the number sequence ## Launch constraint Do not market omitted Everhour 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. Entries, rates, invoices and credit notes. The irreplaceable part. DATABASE_PATH=./data/ledger.db # Required. Readable and structured invoices, credit notes and receipts. DOCUMENT_DIR=./data/documents # Required. The structured syntax you emit. Recorded on every invoice row as well. INVOICE_SYNTAX=ubl # Required. Prefix only. The sequence itself is allocated at issue and never reused. INVOICE_NUMBER_PREFIX=2026- # Required. Bounds how much recorded time a crash can cost. HEARTBEAT_SECONDS=15 # Required. Totals are presented in this. Each project keeps its own currency. BASE_CURRENCY=GBP # Required. Check your own jurisdiction's published requirement and record the source beside this. RETENTION_YEARS=6 # Required. Dated copies. The restore test reads from here. BACKUP_DIR=./data/backups
# Everhour · indie build A solo billing ledger whose hard problems are measuring elapsed time on a machine that sleeps, pricing work at the rate that was in force when it happened, and producing an invoice a receiving system can process rather than a picture of one. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node with TypeScript and SQLite, single user, bound to localhost | one person's billable time does not need a server anybody else operates | | Timing | Elapsed measured from a monotonic source, with wall clock start and stop stored beside it | a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice | | Money | Integer minor units with a currency, and rates that are valid over a period | a rate change must never silently reprice work that has already been billed | | Invoices | Issued invoices are immutable and every later change is a credit note | an invoice you can edit after issuing is a number two systems will disagree about | | Export | A structured invoice document beside the readable one, and a stable column contract for the ledger | a document a person has to retype into the next system is where your figures start to diverge | ## 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 ledger, the timer and the export. - Get it: nodejs.org or your version manager. - Verify: node --version prints v22 or higher - [ ] **Which structured invoice syntax you will emit** · free - Why: The semantic standard permits more than one, and the receiving side usually accepts one of them, so picking after you have written the serialiser means writing it twice. - Get it: Ask whoever receives your invoices which format they accept, read the current specification for it, and write the choice and the date into the repository. - Verify: A sample invoice in that syntax passes the standard's validation artefacts - [ ] **A validator for the structured invoice format** · free - Why: Emitting something that looks right and is rejected on receipt is the failure this whole phase exists to prevent. - Get it: Use the validation artefacts published alongside the standard for your chosen syntax. - Verify: A deliberately broken invoice fails and a good one passes - [ ] **Real time entries and one real past invoice** · free - Why: Rate periods, rounding and the sequence rules only bite against data that already has a history, and a clean fixture hides all three. - Get it: Take a month of your own tracked time and an invoice you have already issued. - Verify: The ledger reproduces that invoice's total exactly - [ ] **How long you must keep invoices, and where that comes from** · free - Why: The retention default should be a figure you looked up rather than one that felt sensible, because it is the one operational setting with an outside authority behind it. - Get it: Read your own jurisdiction's published record-keeping requirement and note the figure, the source and the date. - Verify: The configuration records the period, the source and the date checked ## 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: - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. - bank, payment, tax, and reconciliation integrations - live bank feeds and payment rails - tax and accounting rule updates - audited security and recovery - multi-user controls and accountant workflows If one of those is essential to you, that is the reason to keep paying for Everhour, and the README should say so rather than pretend.
# Build brief · Everhour The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private time, budget and invoice ledger whose exports another system can read without retyping to replace Everhour. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node with TypeScript and SQLite, single user, bound to localhost · one person's billable time does not need a server anybody else operates - Timing: Elapsed measured from a monotonic source, with wall clock start and stop stored beside it · a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice - Money: Integer minor units with a currency, and rates that are valid over a period · a rate change must never silently reprice work that has already been billed - Invoices: Issued invoices are immutable and every later change is a credit note · an invoice you can edit after issuing is a number two systems will disagree about - Export: A structured invoice document beside the readable one, and a stable column contract for the ledger · a document a person has to retype into the next system is where your figures start to diverge ### Data model (create this before Phase 1) - clients(id, name, currency, address_json, tax_id) and projects(id, client_id, name, budget_kind, budget_minor, budget_hours, rounding_rule, closed_at) - rates(id, project_id, person, rate_minor, currency, valid_from, valid_to) · periods, so last quarter stays priced at last quarter's rate - entries(id, project_id, task, started_at_utc, stopped_at_utc, elapsed_ms, source, note, locked_by_invoice_id) · elapsed is measured rather than derived from the two timestamps - timer_state(id, project_id, started_wall, started_monotonic, heartbeat_at) · written on a heartbeat, so a crash costs seconds instead of an afternoon - invoices(id, client_id, number, issued_at, currency, subtotal_minor, tax_minor, total_minor, state, readable_path, structured_path, rounding_rule) - invoice_lines(invoice_id, ordinal, description, quantity_milli, unit_price_minor, amount_minor, entry_ids_json) · quantities in thousandths so a rounding rule has something to round - credit_notes(id, invoice_id, number, issued_at, amount_minor, reason) and expenses(id, project_id, at, amount_minor, currency, category, receipt_path, billable) - Once an invoice is issued its lines and the entries behind them are locked. Everything after that is a credit note, because the alternative is two parties holding different versions of the same document number. ### Phase 1 · A timer that survives a closed laptop Build: Start a timer by recording the wall clock time and a monotonic reading, and write a heartbeat while it runs. Compute elapsed from the monotonic source. On restart, a timer whose heartbeat is stale is presented with the gap for confirmation rather than committed. Manual entry always works. Trap: Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. Done when: A timer running across a simulated suspend produces elapsed equal to the awake time only, tested with a fake clock. A running timer whose heartbeat is stale is presented for confirmation with the gap shown and is never committed automatically, tested by killing the process. Killing the process loses at most one heartbeat interval of recorded time. Every entry records whether it was timed or entered by hand, and the interface shows which. Do not build yet: No rates, budgets, invoicing or export yet. ### Phase 2 · Rates that belong to a period Build: Give rates a validity period per project and person. Price each entry by the rate in force when the work started, and state in one place what happens to an entry spanning a boundary. Track budgets in both money and hours with alert thresholds. Apply the rounding rule once, at invoicing. Trap: Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. Done when: Raising a rate leaves every historic project total unchanged to the penny, tested. An entry spanning a rate boundary is priced by a stated rule and that rule appears in the README. Rounding is applied once at invoice time and the rule applied is stored on the invoice, tested by comparing an entry total to an invoice line. Budget alerts fire at the configured thresholds in money and in hours independently. Do not build yet: No invoicing, credit notes or export yet. ### Phase 3 · Invoices that stop being editable Build: Build an invoice from unbilled entries and lock them. Allocate the number only at the moment of issue. Issued lines are immutable, enforced by the database rather than by the interface. Voiding keeps the number and the document. Every later change is a credit note with its own sequence. Trap: Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. Done when: Numbers are allocated at issue and a test asserts no gaps across a sequence that includes voided invoices. An attempt to modify an issued invoice line fails at the database level, tested by trying it directly rather than through the interface. Entries locked by an issued invoice cannot be edited or invoiced again, tested. A credit note references its invoice and both appear in the client's balance with the right signs. Do not build yet: No structured export, ledger export or retention yet. ### Phase 4 · An invoice another system can read Build: Emit a structured invoice conforming to the European semantic model in one of its permitted syntaxes, alongside the readable document. Validate the structured file before writing it and fail the export rather than emitting something invalid. Give the ledger export a documented, stable column contract and a test that fails when a column is renamed or reordered. Trap: A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. Done when: Every issued invoice writes both a readable document and a structured one, asserted in the suite. The structured file is validated before it is written and an invalid one fails the export with the validation output stored, tested with a deliberately broken invoice. The ledger export has a documented column contract and a test fails if a column is renamed, removed or reordered. A sample structured invoice is checked against the standard's own validation artefacts and the result is recorded in the repository. Do not build yet: No retention, backup or handover work yet. ### Phase 5 · Handing it over without retyping Build: Produce a handover bundle: structured invoices, credit notes, the ledger export and the receipts, with a manifest. Record what was exported and when. Keep issued documents for a configured period with the source of that figure noted. Take dated backups and exercise a restore in the suite. Trap: The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. Done when: A handover bundle is read by the next system without any manual retyping, demonstrated once and the steps recorded in the README. Every export writes a manifest recording what left and when. A retention setting keeps issued invoices, credit notes and structured documents for a configured period, with the source of that period noted in configuration. Restoring a backup reproduces every invoice total and the whole sequence exactly, exercised in the test suite. Do not build yet: Nothing after this. ### Out of scope (and why) - Bank feeds and payment rails, which mean holding credentials to your own money inside a tool you wrote at the weekend. - Automatic tax determination, which changes by jurisdiction and by year and is the part where being approximately right is worthless. - Multiple users with roles and approvals, which needs an authorisation model and an audit trail before any of it can be trusted. - Integrations that pull tasks from a project tool, which are pleasant and are also a permanent maintenance commitment to somebody else's API. ### README must contain - That elapsed time is measured from a monotonic source, with the suspend test named as the reason - That rates have validity periods, and the stated rule for an entry spanning a boundary - That invoice numbers are allocated at issue, that issued invoices are immutable in the database, and that corrections are credit notes - That a readable document is not an electronic invoice under the European definition, quoted with the dates checked, and which syntax this build emits - That an export requiring manual retyping breaks the digital link the revenue guidance requires, and how the handover bundle avoids it
# Agent instructions · Everhour indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node with TypeScript and SQLite, single user, bound to localhost, Elapsed measured from a monotonic source, with wall clock start and stop stored beside it, Integer minor units with a currency, and rates that are valid over a period, Issued invoices are immutable and every later change is a credit note, A structured invoice document beside the readable one, and a stable column contract for the ledger. 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 - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version.
# Build plan · Everhour A solo billing ledger whose hard problems are measuring elapsed time on a machine that sleeps, pricing work at the rate that was in force when it happened, and producing an invoice a receiving system can process rather than a picture of one. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A timer that survives a closed laptop Elapsed time is measured, and a timer running through a suspend does not bill the gap. ### Steps 1. Measure elapsed from a monotonic source Wall clock subtraction bills the hours the machine spent asleep. Files: `src/timer/elapsed.ts`, `tests/suspend.test.ts` 2. Heartbeat while running It bounds the loss from a crash to something you will not notice. Files: `src/timer/heartbeat.ts` 3. Ask about the gap instead of guessing Only the person knows whether they were working. Files: `src/timer/recover.ts`, `src/views/recover.tsx` ### Done when - [ ] A suspend does not add billable time - [ ] A stale heartbeat is confirmed, never auto-committed - [ ] A crash loses at most one heartbeat interval - [ ] Timed and manual entries are distinguishable ### Watch out - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. ## Phase 2 · Rates that belong to a period Raising a rate changes nothing about work that has already happened. ### Steps 1. Give rates a validity period, not a current value The rate is a property of the week, not of the project. Files: `src/rates.ts`, `tests/historic.test.ts` 2. Decide and document the boundary rule Someone will work across a rate change on the first of the month. Files: `src/rates.ts`, `README.md` 3. Round once, at invoicing, and store the rule Rounding at entry time rounds the same minute twice. Files: `src/invoice/round.ts` ### Done when - [ ] A rate rise leaves historic totals untouched - [ ] A boundary-spanning entry follows a documented rule - [ ] Rounding happens once and the rule is stored - [ ] Budget alerts work in money and in hours ### Watch out - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. ## Phase 3 · Invoices that stop being editable Issuing is irreversible, the sequence has no gaps, and corrections are credit notes. ### Steps 1. Allocate the number at issue, never at draft A draft that is never issued should leave no trace in the sequence. Files: `src/invoice/issue.ts`, `tests/sequence.test.ts` 2. Enforce immutability in the database Interface-level protection is protection until the next feature. Files: `migrations/003_lock.sql` 3. Void by keeping, correct by crediting Both leave the trail somebody will ask to see. Files: `src/invoice/void.ts`, `src/creditnote.ts` ### Done when - [ ] The issued sequence has no gaps, voids included - [ ] Issued lines cannot be modified at the database level - [ ] Locked entries cannot be re-invoiced - [ ] Credit notes net correctly against the balance ### Watch out - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. ## Phase 4 · An invoice another system can read Every issued invoice produces a structured document as well as a readable one. ### Steps 1. Emit structured and readable together, always A structured document produced only on request is produced never. Files: `src/invoice/structured.ts`, `tests/structured.test.ts` 2. Validate before writing and fail the export An invalid file that exists is worse than no file. Files: `src/invoice/validate.ts` 3. Pin the ledger column contract with a test Whatever reads it downstream will break silently otherwise. Files: `src/export/ledger.ts`, `tests/columns.test.ts` ### Done when - [ ] Both documents are produced for every issued invoice - [ ] An invalid structured file fails the export - [ ] The column contract is pinned by a test - [ ] A sample passes the standard's own validation ### Watch out - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ## Not in this build - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. ## After v1, if you want it - Recurring invoices, which are worth doing only once the sequence and immutability rules are proven - A second structured syntax behind the same serialiser interface, which is how you find out the abstraction was real - A reminder for unpaid invoices built from the credit note and payment state rather than from a spreadsheet
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Entries, rates, invoices and credit notes. The irreplaceable part. DATABASE_PATH=./data/ledger.db # Required. Readable and structured invoices, credit notes and receipts. DOCUMENT_DIR=./data/documents # Required. The structured syntax you emit. Recorded on every invoice row as well. INVOICE_SYNTAX=ubl # Required. Prefix only. The sequence itself is allocated at issue and never reused. INVOICE_NUMBER_PREFIX=2026- # Required. Bounds how much recorded time a crash can cost. HEARTBEAT_SECONDS=15 # Required. Totals are presented in this. Each project keeps its own currency. BASE_CURRENCY=GBP # Required. Check your own jurisdiction's published requirement and record the source beside this. RETENTION_YEARS=6 # Required. Dated copies. The restore test reads from here. BACKUP_DIR=./data/backups
# Everhour · product brief ## Problem Everhour's solo core is compact: build a private time + budget tracking ledger that imports user-supplied CSV files, categorizes entries, reconciles balances, and exports reports. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on banking, compliance, trust. ## Product outcome A billing ledger that does not bill the hours your laptop spent asleep, does not reprice last quarter when you raise your rate, cannot have an issued invoice edited, and hands the next system a file it reads rather than a picture somebody has to retype. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Node.js 22 - SQLite - CSV exports from the user's own accounts ## Explicit non-goals for v1 - Bank feeds and payment rails. - Automatic tax determination. - Multiple users with roles and approvals. - Project tool integrations. - bank, payment, tax, and reconciliation integrations - live bank feeds and payment rails - tax and accounting rule updates - audited security and recovery - multi-user controls and accountant workflows ## Success criteria - A suspend is confirmed not to add billable time - A rate rise is confirmed to leave historic totals unchanged - An issued invoice line is confirmed immutable at the database level - An invalid structured invoice is confirmed to fail the export - A restore is confirmed to reproduce totals and the number sequence
# Build brief · Everhour The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a private time, budget and invoice ledger whose exports another system can read without retyping to replace Everhour. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Node with TypeScript and SQLite, single user, bound to localhost · one person's billable time does not need a server anybody else operates - Timing: Elapsed measured from a monotonic source, with wall clock start and stop stored beside it · a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice - Money: Integer minor units with a currency, and rates that are valid over a period · a rate change must never silently reprice work that has already been billed - Invoices: Issued invoices are immutable and every later change is a credit note · an invoice you can edit after issuing is a number two systems will disagree about - Export: A structured invoice document beside the readable one, and a stable column contract for the ledger · a document a person has to retype into the next system is where your figures start to diverge ### Data model (create this before Phase 1) - clients(id, name, currency, address_json, tax_id) and projects(id, client_id, name, budget_kind, budget_minor, budget_hours, rounding_rule, closed_at) - rates(id, project_id, person, rate_minor, currency, valid_from, valid_to) · periods, so last quarter stays priced at last quarter's rate - entries(id, project_id, task, started_at_utc, stopped_at_utc, elapsed_ms, source, note, locked_by_invoice_id) · elapsed is measured rather than derived from the two timestamps - timer_state(id, project_id, started_wall, started_monotonic, heartbeat_at) · written on a heartbeat, so a crash costs seconds instead of an afternoon - invoices(id, client_id, number, issued_at, currency, subtotal_minor, tax_minor, total_minor, state, readable_path, structured_path, rounding_rule) - invoice_lines(invoice_id, ordinal, description, quantity_milli, unit_price_minor, amount_minor, entry_ids_json) · quantities in thousandths so a rounding rule has something to round - credit_notes(id, invoice_id, number, issued_at, amount_minor, reason) and expenses(id, project_id, at, amount_minor, currency, category, receipt_path, billable) - Once an invoice is issued its lines and the entries behind them are locked. Everything after that is a credit note, because the alternative is two parties holding different versions of the same document number. ### Phase 1 · A timer that survives a closed laptop Build: Start a timer by recording the wall clock time and a monotonic reading, and write a heartbeat while it runs. Compute elapsed from the monotonic source. On restart, a timer whose heartbeat is stale is presented with the gap for confirmation rather than committed. Manual entry always works. Trap: Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. Done when: A timer running across a simulated suspend produces elapsed equal to the awake time only, tested with a fake clock. A running timer whose heartbeat is stale is presented for confirmation with the gap shown and is never committed automatically, tested by killing the process. Killing the process loses at most one heartbeat interval of recorded time. Every entry records whether it was timed or entered by hand, and the interface shows which. Do not build yet: No rates, budgets, invoicing or export yet. ### Phase 2 · Rates that belong to a period Build: Give rates a validity period per project and person. Price each entry by the rate in force when the work started, and state in one place what happens to an entry spanning a boundary. Track budgets in both money and hours with alert thresholds. Apply the rounding rule once, at invoicing. Trap: Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. Done when: Raising a rate leaves every historic project total unchanged to the penny, tested. An entry spanning a rate boundary is priced by a stated rule and that rule appears in the README. Rounding is applied once at invoice time and the rule applied is stored on the invoice, tested by comparing an entry total to an invoice line. Budget alerts fire at the configured thresholds in money and in hours independently. Do not build yet: No invoicing, credit notes or export yet. ### Phase 3 · Invoices that stop being editable Build: Build an invoice from unbilled entries and lock them. Allocate the number only at the moment of issue. Issued lines are immutable, enforced by the database rather than by the interface. Voiding keeps the number and the document. Every later change is a credit note with its own sequence. Trap: Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. Done when: Numbers are allocated at issue and a test asserts no gaps across a sequence that includes voided invoices. An attempt to modify an issued invoice line fails at the database level, tested by trying it directly rather than through the interface. Entries locked by an issued invoice cannot be edited or invoiced again, tested. A credit note references its invoice and both appear in the client's balance with the right signs. Do not build yet: No structured export, ledger export or retention yet. ### Phase 4 · An invoice another system can read Build: Emit a structured invoice conforming to the European semantic model in one of its permitted syntaxes, alongside the readable document. Validate the structured file before writing it and fail the export rather than emitting something invalid. Give the ledger export a documented, stable column contract and a test that fails when a column is renamed or reordered. Trap: A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. Done when: Every issued invoice writes both a readable document and a structured one, asserted in the suite. The structured file is validated before it is written and an invalid one fails the export with the validation output stored, tested with a deliberately broken invoice. The ledger export has a documented column contract and a test fails if a column is renamed, removed or reordered. A sample structured invoice is checked against the standard's own validation artefacts and the result is recorded in the repository. Do not build yet: No retention, backup or handover work yet. ### Phase 5 · Handing it over without retyping Build: Produce a handover bundle: structured invoices, credit notes, the ledger export and the receipts, with a manifest. Record what was exported and when. Keep issued documents for a configured period with the source of that figure noted. Take dated backups and exercise a restore in the suite. Trap: The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice. Done when: A handover bundle is read by the next system without any manual retyping, demonstrated once and the steps recorded in the README. Every export writes a manifest recording what left and when. A retention setting keeps issued invoices, credit notes and structured documents for a configured period, with the source of that period noted in configuration. Restoring a backup reproduces every invoice total and the whole sequence exactly, exercised in the test suite. Do not build yet: Nothing after this. ### Out of scope (and why) - Bank feeds and payment rails, which mean holding credentials to your own money inside a tool you wrote at the weekend. - Automatic tax determination, which changes by jurisdiction and by year and is the part where being approximately right is worthless. - Multiple users with roles and approvals, which needs an authorisation model and an audit trail before any of it can be trusted. - Integrations that pull tasks from a project tool, which are pleasant and are also a permanent maintenance commitment to somebody else's API. ### README must contain - That elapsed time is measured from a monotonic source, with the suspend test named as the reason - That rates have validity periods, and the stated rule for an entry spanning a boundary - That invoice numbers are allocated at issue, that issued invoices are immutable in the database, and that corrections are credit notes - That a readable document is not an electronic invoice under the European definition, quoted with the dates checked, and which syntax this build emits - That an export requiring manual retyping breaks the digital link the revenue guidance requires, and how the handover bundle avoids it
# Architecture · Everhour ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node with TypeScript and SQLite, single user, bound to localhost | one person's billable time does not need a server anybody else operates | | Timing | Elapsed measured from a monotonic source, with wall clock start and stop stored beside it | a sleeping laptop or a stepped clock otherwise invents or loses hours that go onto an invoice | | Money | Integer minor units with a currency, and rates that are valid over a period | a rate change must never silently reprice work that has already been billed | | Invoices | Issued invoices are immutable and every later change is a credit note | an invoice you can edit after issuing is a number two systems will disagree about | | Export | A structured invoice document beside the readable one, and a stable column contract for the ledger | a document a person has to retype into the next system is where your figures start to diverge | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Timer | Monotonic elapsed, heartbeat and recovery. | Any implementation, provided elapsed is measured rather than subtracted. | | Rates | Validity periods and the boundary rule. | Any store, provided a rate change cannot move a historic total. | | Invoicing | Sequence, immutability and credit notes. | Not swappable for anything that allocates a number before issue. | | Export | Structured documents, validation and the column contract. | Any syntax the receiving side accepts, provided it is validated before writing. | | Operations | Retention, manifests and restore. | Any scheduler, provided the restore is exercised rather than described. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_PATH` · required · Entries, rates, invoices and credit notes. The irreplaceable part. - `DOCUMENT_DIR` · required · Readable and structured invoices, credit notes and receipts. - `INVOICE_SYNTAX` · required · The structured syntax you emit. Recorded on every invoice row as well. - `INVOICE_NUMBER_PREFIX` · required · Prefix only. The sequence itself is allocated at issue and never reused. - `HEARTBEAT_SECONDS` · required · Bounds how much recorded time a crash can cost. - `BASE_CURRENCY` · required · Totals are presented in this. Each project keeps its own currency. - `RETENTION_YEARS` · required · Check your own jurisdiction's published requirement and record the source beside this. - `BACKUP_DIR` · required · Dated copies. The restore test reads from here. ## 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 · Everhour product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node with TypeScript and SQLite, single user, bound to localhost, Elapsed measured from a monotonic source, with wall clock start and stop stored beside it, Integer minor units with a currency, and rates that are valid over a period, Issued invoices are immutable and every later change is a credit note, A structured invoice document beside the readable one, and a stable column contract for the ledger. - 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 - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. - The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice.
# Delivery milestones · Everhour Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A timer that survives a closed laptop Elapsed time is measured, and a timer running through a suspend does not bill the gap. ### Steps 1. Measure elapsed from a monotonic source Wall clock subtraction bills the hours the machine spent asleep. Files: `src/timer/elapsed.ts`, `tests/suspend.test.ts` 2. Heartbeat while running It bounds the loss from a crash to something you will not notice. Files: `src/timer/heartbeat.ts` 3. Ask about the gap instead of guessing Only the person knows whether they were working. Files: `src/timer/recover.ts`, `src/views/recover.tsx` ### Done when - [ ] A suspend does not add billable time - [ ] A stale heartbeat is confirmed, never auto-committed - [ ] A crash loses at most one heartbeat interval - [ ] Timed and manual entries are distinguishable ### Watch out - Computing elapsed time by subtracting the start timestamp from the stop timestamp hands you an entire afternoon of billable time the first time the machine sleeps with a timer running, and quietly loses or gains an hour whenever the clock steps, and both errors travel straight onto an invoice with nothing to flag them. Measure from a monotonic source, heartbeat while running, and on restart show the gap and ask rather than assuming either answer. ## M2 · Rates that belong to a period Raising a rate changes nothing about work that has already happened. ### Steps 1. Give rates a validity period, not a current value The rate is a property of the week, not of the project. Files: `src/rates.ts`, `tests/historic.test.ts` 2. Decide and document the boundary rule Someone will work across a rate change on the first of the month. Files: `src/rates.ts`, `README.md` 3. Round once, at invoicing, and store the rule Rounding at entry time rounds the same minute twice. Files: `src/invoice/round.ts` ### Done when - [ ] A rate rise leaves historic totals untouched - [ ] A boundary-spanning entry follows a documented rule - [ ] Rounding happens once and the rule is stored - [ ] Budget alerts work in money and in hours ### Watch out - Storing one current rate per project and raising it reprices the whole of last quarter the next time anybody opens a report, so a client who has already paid appears to owe more and your own totals stop agreeing with your bank statement. Give rates a validity period, price each entry from the rate in force when the work happened, and write a test that raises a rate and asserts every historic total is unchanged to the penny. ## M3 · Invoices that stop being editable Issuing is irreversible, the sequence has no gaps, and corrections are credit notes. ### Steps 1. Allocate the number at issue, never at draft A draft that is never issued should leave no trace in the sequence. Files: `src/invoice/issue.ts`, `tests/sequence.test.ts` 2. Enforce immutability in the database Interface-level protection is protection until the next feature. Files: `migrations/003_lock.sql` 3. Void by keeping, correct by crediting Both leave the trail somebody will ask to see. Files: `src/invoice/void.ts`, `src/creditnote.ts` ### Done when - [ ] The issued sequence has no gaps, voids included - [ ] Issued lines cannot be modified at the database level - [ ] Locked entries cannot be re-invoiced - [ ] Credit notes net correctly against the balance ### Watch out - Allocating the invoice number when the draft is created and then deleting the draft leaves a gap in the sequence, which is the first thing anybody reviewing your books asks about, and reusing the number instead produces two documents claiming the same identifier at two different clients. Allocate at issue, make issue irreversible, and void by keeping both the number and the document rather than by removing them. ## M4 · An invoice another system can read Every issued invoice produces a structured document as well as a readable one. ### Steps 1. Emit structured and readable together, always A structured document produced only on request is produced never. Files: `src/invoice/structured.ts`, `tests/structured.test.ts` 2. Validate before writing and fail the export An invalid file that exists is worse than no file. Files: `src/invoice/validate.ts` 3. Pin the ledger column contract with a test Whatever reads it downstream will break silently otherwise. Files: `src/export/ledger.ts`, `tests/columns.test.ts` ### Done when - [ ] Both documents are produced for every issued invoice - [ ] An invalid structured file fails the export - [ ] The column contract is pinned by a test - [ ] A sample passes the standard's own validation ### Watch out - A document emailed to a customer as a picture is not an electronic invoice under the rules now arriving: the European definition requires an invoice 'issued, transmitted and received in a structured electronic format which allows for its automatic and electronic processing', German law has required businesses to be able to receive such invoices since 1 January 2025 with issuing phased in from 1 January 2027, and the European package makes structured invoicing the default for covered intra-Union transactions from 1 July 2030. Checked on 15 September 2026. Emit the structured document from the first release; retrofitting it into a model that only ever produced a picture is the expensive version. ## M5 · Handing it over without retyping (production only) The next system reads your files directly, and a restore reproduces every total. ### Steps 1. Bundle everything with a manifest So the handover is one thing rather than six attachments. Files: `src/export/bundle.ts` 2. Record what was exported and when It is the answer to 'did you send me February'. Files: `src/export/log.ts` 3. Exercise the restore in the suite A backup nobody has restored is a hope with a filename. Files: `tests/restore.test.ts`, `scripts/backup.sh` ### Done when - [ ] The bundle is consumed without retyping - [ ] Every export writes a manifest - [ ] Retention is configured with its source noted - [ ] A restore reproduces totals and the sequence ### Watch out - The revenue guidance on digital record keeping states that it 'does not consider the use of cut and paste or copy and paste to select and move information' to be a digital link between software, and attaches a daily penalty to failing to maintain one; check the current figure in that notice rather than trusting a number from anywhere else. An export somebody has to retype into a return is therefore not merely tedious, it is the exact point where your figures and the ones filed stop matching, and nothing in either system will notice.
# Operations · Everhour ## Backup Dated copies of the database and the document directory together, because a structured invoice on disk without the row that describes it is an orphan and the row without the file is a promise. Keep generations across a full retention period, not a fortnight. ## Restore Restore both, then run the suite's restore test: it should reproduce every invoice total and the full number sequence. A restore that renumbers anything has broken the one property an accountant checks first. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Timers running longer than a working day, entries not yet invoiced beyond a stated age, failed structured validations, and the age of the newest backup. The first catches the suspend case that the heartbeat missed. ## Incident checklist The incident is a figure that disagrees with the one the other side holds. The immutable invoice, the stored rounding rule, the rate validity periods and the export manifest together let you say exactly what was issued, priced how, and sent when. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] A suspend is confirmed not to add billable time - [ ] A rate rise is confirmed to leave historic totals unchanged - [ ] An issued invoice line is confirmed immutable at the database level - [ ] An invalid structured invoice is confirmed to fail the export - [ ] A restore is confirmed to reproduce totals and the number sequence ## Launch constraint Do not market omitted Everhour 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. Entries, rates, invoices and credit notes. The irreplaceable part. DATABASE_PATH=./data/ledger.db # Required. Readable and structured invoices, credit notes and receipts. DOCUMENT_DIR=./data/documents # Required. The structured syntax you emit. Recorded on every invoice row as well. INVOICE_SYNTAX=ubl # Required. Prefix only. The sequence itself is allocated at issue and never reused. INVOICE_NUMBER_PREFIX=2026- # Required. Bounds how much recorded time a crash can cost. HEARTBEAT_SECONDS=15 # Required. Totals are presented in this. Each project keeps its own currency. BASE_CURRENCY=GBP # Required. Check your own jurisdiction's published requirement and record the source beside this. RETENTION_YEARS=6 # Required. Dated copies. The restore test reads from here. BACKUP_DIR=./data/backups
$ choose a build depth, inspect the files, then open the complete pack in your agent
Everhour: People pay because errors touch real money. Bank connections, reconciliation, tax changes, evidence, security, and support are the product.
xbank, payment, tax, and reconciliation integrations
xlive bank feeds and payment rails
xtax and accounting rule updates
xaudited security and recovery
xmulti-user controls and accountant workflows
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Everhour pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0/user | $0/user | Up to 5 users; time tracking, projects, tasks, reports, and export; 0 project-management integrations |
| team | $10/user | $8.50/user | Minimum 5 paid seats; unlimited seats; integrations, estimates, budgets, expenses, invoicing, and SSO |
| custom | custom | — | For 50+ users; custom commercial and support terms |
free tier5 users; time tracking, projects, tasks, reports, and export; 0 integrations
billingmonthly + annual (annual rate is 15% lower); 14-day Team trial
hidden costsTeam has a 5-seat minimum, making the minimum bill $50/month or $42.50/month at the annual rate. Purchased seat count is not automatically reduced when a user is removed; annual unused time is non-refundable.
verified 2026-08-10 · source ↗
Vibecode Everhour
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Everhour replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Everhour cost?
Everhour costs about $10/month (Team, checked 2026-07-31), which is $120 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Everhour?
Honestly: bank, payment, tax, and reconciliation integrations; live bank feeds and payment rails; tax and accounting rule updates; audited security and recovery; multi-user controls and accountant workflows. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Everhour?
Yes: Kimai (Time, budgets, expenses, invoices and an API; project-tool integrations are thinner than Everhour's.) Invoice Ninja self-hosted (Time, tasks, quotes, expenses and invoices for free on your server; deep project-tool integrations do not follow.) The prompt is for when you want it exactly your way.