Build Memtime

KINDAreplaces $14/mosaves $168/yrback to the verdict

0%0 of 16 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A local activity recorder that watches which application and document you were in, groups the day into blocks, and lets you turn selected blocks into timesheet entries. The engineering is in grouping a fragmented day into something recognisable, and the design constraint is that raw capture never leaves the machine.

estimated effort multi-daythe files for this build are in the project pack

CaptureA local agent polling the focused window and idle stateStorageSQLite on the machine, encrypted at restGroupingSegmentation into blocks by application, document and gapApprovalNothing becomes a timesheet entry without an explicit actionExportApproved entries only, in a format your billing system takes

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the agent and the review interface.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree

    Why This is platform-specific and on some systems requires an accessibility or screen recording permission, which changes what you must tell the user.

    Get it Whatever your platform provides, and find out which permission it needs before designing the onboarding.

    Verify The focused application, its window title and idle seconds can all be read

  3. decidefree

    Why Deciding what not to record after you have a month of recordings is deciding it too late, since the data already exists.

    Get it List the applications and title patterns that must never be stored: banking, health, private messaging, anything belonging to someone else. Err wide.

    Verify The list exists and is loaded by the sampler before the first run

  4. have readyfree, and it takes a week

    Why Segmentation thresholds and block summarisation can only be tuned against a real fragmented day, and a synthetic one is far tidier than reality.

    Get it Run the capture for a week and reconstruct each day from memory to compare against.

    Verify A week of capture exists and you have tried to reconstruct at least three days from it

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- samples(at, app, window_title, document, idle_seconds) · the raw stream, local only, never exported
- blocks(id, started_at, ended_at, app, title_summary, sample_count) · derived by segmentation
- exclusions(id, kind, pattern, created_at) · kind is app or title. Applied before writing a sample
- projects(id, name, client, billable, archived_at)
- entries(id, project_id, started_at, ended_at, description, approved_at, exported_at)
- entry_blocks(entry_id, block_id) · which capture a timesheet entry was built from, kept locally
- The link from an entry back to its blocks stays on the machine. What leaves is the entry alone.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DB_PATHrequired~/.local/share/memtime/capture.dbRaw capture. The most sensitive file this build produces. Encrypted at rest.
DB_KEYsecretrequireda passphrase or keychain referenceEncrypts the capture store. Without it the raw titles sit in plain text.
SAMPLE_INTERVAL_SECONDSrequired15How often focus is sampled. Shorter is more accurate and more intrusive.
IDLE_THRESHOLD_SECONDSrequired180When you are considered away. Blocks end at idle rather than continuing.
RAW_RETENTION_DAYSrequired30How long raw samples live. Short, because their usefulness drops fast and their sensitivity does not.
EXCLUSIONS_PATHrequired./config/exclusions.txtLoaded by the sampler before the first write. Not a display filter.

The build, in order

  1. Capture, with the exclusions applied first

    Your day is recorded, minus everything you said not to record.

    1. Application, title, document, idle seconds. Nothing else.

      Files src/capture.ts

    2. In the sampler. Display filtering leaves the data on disk.

      Files src/exclude.tssrc/exclude.test.ts

    3. No sampling, and a visible gap rather than an interpolated one.

      Files src/capture.ts

    done when · tick each as it passes
    watch out
    • Filtering excluded applications at display time means the sensitive titles are still on disk, still in backups, and still readable by anything that can open the database, which is exactly the outcome the exclusion was meant to prevent. Apply exclusions in the sampler, before the write.
  2. Turning a fragmented day into blocks

    A day of thousands of samples reads as a dozen recognisable blocks.

    1. Tune it against your own day. This is the design work, not a constant to guess.

      Files src/segment.tssrc/segment.test.ts

    2. Checking a message does not end a block of work.

      Files src/segment.ts

    3. A block needs a label you recognise, not a list of forty window titles.

      Files src/summarise.ts

    done when · tick each as it passes
    watch out
    • Segmenting on every application change produces four hundred blocks for a normal day, which is unreadable and defeats the purpose, while merging too aggressively produces one block called various and tells you nothing. The gap threshold and the merge rules are the actual design work here and they need tuning against your own real day.
  3. Reconstructing a day you did not track

    You can sit down at five and rebuild the day accurately.

    1. The point is recall. You need to see what was on screen at four o'clock.

      Files src/timeline.ts

    2. Combining several blocks is the normal case, not the exception.

      Files src/entries.ts

    3. An unreviewed automatic timesheet gets invoiced. That is the failure.

      Files src/suggest.ts

    done when · tick each as it passes
    watch out
    • Automatically assigning blocks to projects by matching window titles produces confident, wrong timesheets, and a wrong timesheet that was never reviewed is worse than an empty one because it gets invoiced. Suggest a project, never assign one, and require the approval action for every entry.
  4. Rounding and billing rules, stated honestly

    Exported time matches the rule you agreed with the client.

    1. Store actual. A client asking what the real figure was deserves an answer.

      Files src/rounding.tssrc/rounding.test.ts

    2. Actual and rounded, side by side. Hiding the difference is how disputes start.

      Files src/export.ts

    3. Both are ordinary. Both break naive duration arithmetic.

      Files src/duration.ts

    done when · tick each as it passes
    watch out
    • Rounding the stored entry destroys the actual figure, so you can never answer a client asking what the real time was, and repeated rounding on edits compounds the error. Store actual, round at export, and show both.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more time tracking.