Skip to content
sdekenPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Devlog

A desktop app for keeping a running developer log. Posts are written in a Slack-style WYSIWYG Markdown composer, stored as plain Markdown files in a git repository, and committed and pushed automatically.

Three words cover the model. A canvas is anything you write about: a client, a project, a topic, a task. Every canvas has a surface (free markdown: links, how-tos, a research scratchpad) and a stream of dated blocks. Canvases nest. A canvas marked as a task is something time is tracked against; any block can become one.

  • WYSIWYG Markdown, no chrome. Bold, italic, strikethrough, inline code, syntax-highlighted code blocks, lists, quotes, headings and links render as you type, with the usual shortcuts (**bold**, - for a list, ```ts for a code block, ⌘B / ⌘I / ⌘K…). The composer is a bare input: the only formatting UI is a small bubble menu that appears when you select text. Enter posts, Shift+Enter starts a new line. Start typing with nothing focused and the text goes into the note box for the canvas on screen.
  • Every block is a page. Double-click a block (or its Open action) to open it: the block sits at the top as the page's surface, and what you write there goes inside it, as blocks of their own that can be opened in turn. Alt+Enter posts a block and opens it straight away; Alt+↑ (or ↑ Up, or the breadcrumb) goes back up a level. In a stream, a block with blocks inside shows a chip ("3 blocks inside") instead of the blocks themselves. Hover between two blocks and press + to insert one there, press ↑ in the empty composer to edit the last block (on a page, double-click the surface to edit it), drag a block's grip to reorder it among the blocks beside it, or Hide it to collapse it into a one-line stub. Timestamps and actions take no space: on hover they float over whatever sits above the block. Blocks you wrote have no marker; blocks the app created carry a leading brace, muted for captured commits and in the accent colour for task blocks (click it to open the task). Blocks on past days work the same way.
  • Canvases nest however you slice your work. Make a canvas per client, with project canvases inside it, with task canvases inside those; or flatter, or deeper. The sidebar is the tree; right-click a canvas there for its properties, to start it, to add a canvas or task inside it, or to archive it. "Website" under two clients is two different canvases. Blocks can be moved between canvases, and search covers all of them. Devlog opens on the canvas you last had open.
  • Every canvas has a surface. Above the stream sits free-form markdown that is not a dated note: links to the issue tracker, environments, contacts, credentials, how-tos, or the evolving write-up of whatever you are researching. It opens read-only so links just work; Edit surface (or a double-click) turns it into the editor, which saves as you type.
  • Notes are notes until you say otherwise. Posting on a client or project canvas is just a note. A block becomes a task with ⌘⇧Enter, with #task anywhere on its first line, or with Make task on hover: a task canvas appears beneath the current one, titled from the block, the block links to it, and the clock starts. The task canvas has its own surface and stream for everything that follows.
  • One active task, tracked for you. Time tracking is the built-in Time tracking extension (devlog-time). Posting on a task canvas, Start in its header, or Start in the status bar makes it the active task; it stays active until you start another task or press Stop. Locking the machine, going idle or sleeping pauses the clock, and it resumes on the same task. Devlog keeps running in the tray to watch. A block with an explicit duration like [2h] or [45m] overrides tracking for that window when you know better.
  • Todos are blocks, and always in view. A todo is a block with a checkbox, written anywhere: on a canvas, inside a block, inside another todo. A panel pinned to the right edge gathers the open ones for the page on screen and everything inside it (or every todo, with one click), grouped under the canvases and blocks they live in. Click one to open it as a page and write your notes about it there.
  • Archive what you're done with. Archive a canvas, with everything beneath it, to get it out of the sidebar. Archived things stay readable and searchable, and one click brings them back.
  • Activity timeline. Alongside your notes, Devlog records lock/unlock, idle and sleep (while time is tracked) and, with the Window tracking extension, which window was in front (app and title, so a browser tab or a Teams call shows up). A per-day Timeline view lays it all out next to your notes; the weekly review adds screen time by kind (coding, meetings, browser…).
  • Commits become blocks, branches become events. Map a canvas to the git repositories you use for it and every commit you make there is added to the canvas as a read-only block you can reply to, move or delete. Creating or switching branches, pushing, merging, rebasing and stashing show up on the timeline without cluttering the stream.
  • Weekly review and summary. The review rolls the week up by day and by client → project → task with tracked time, plus a per-day breakdown of what you wrote, task time and screen time. The Summary page (from the Time tracking extension, ⌘⇧H) answers the timesheet question directly: hours per client for any range, rounded to the nearest 15 minutes (or whatever you set), with projects and tasks one click away. Made for Friday.
  • Pasted evidence just works. Paste or drop an image into the composer and it is saved into the repository next to the day's entry and linked relatively, so the log also renders on GitHub; click an image in the feed to see it full size. Paste a stack trace, a diff, a log excerpt or a shell session and it lands in a code block instead of being mangled into paragraphs.
  • Jot from anywhere. The composer stays at the bottom of the canvas, review and timeline views, with a picker for which canvas the block goes to, and ⌘P (or ⌘K outside the editor) opens a quick switcher that jumps to any canvas, page or view by fuzzy name.
  • Its own chrome. No menu bar: the app draws the title bar Slack-style, with a hamburger menu on the left and search in the middle. Pick a colour theme in Settings (Graphite by default; Ocean, Forest, Ember, Aubergine, Paper) or set your own sidebar and accent colours; everything else is derived from those two.
  • Updates itself. Releases are checked for in the background, downloaded silently, and installed by restarting at a quiet moment (screen locked, window hidden, or input idle), never mid-edit and never with a dialog.
  • Git is the database. One Markdown file per day, one folder per month. Nothing proprietary: the repo is readable and editable with any tool.
  • Automatic save, commit and push. Every post is written to disk immediately, committed shortly afterwards, and pushed on a schedule and when the app quits. If another machine pushed first, the app pulls and rebases before pushing.

Getting started

Requirements: Node 20+, and git on your PATH. Pushing uses whatever credentials git already has (SSH agent, credential helper).

npm install
npm run dev        # run with hot reload
npm run build      # production build into out/
npm run package    # build installers into release/ (mac/win/linux variants exist too)

On first launch choose Create a new devlog (picks a folder, runs git init, makes the first commit; optionally set a remote) or Open an existing devlog (a clone from another machine).

Repository layout

README.md
devlog.json                       ← { "format": 4, … }: the storage format, and the extensions the devlog uses
devlog.lock.json                  ← the exact extension files in use, pinned by hash
.gitattributes                    ← block files and activity logs merge by keeping both sides
entries/                          ← the old journal (retired; only in devlogs that used it)
canvases/
  k3/                             ← shard: the first two characters of the id
    k3m9x2q7vd/                   ← a canvas (a client, "Acme Corp")
      canvas.md                   ← title, parent, type (task flag), repos, archived flag, extension fields + the surface
      assets/                     ← images pasted into the surface
      entries/2026/09/2026-09-19.md ← its stream, same day-file format
  7w/
    7wq0dz4hbe/                   ← "Website", parent: k3m9x2q7vd
    …
activity/
  desktop-4f1a/2026/09/2026-09-19.jsonl  ← the app's activity log, one folder per machine
  laptop-9c02/…
extensions/
  builtin.devlog-time/…           ← extensions' own synced data (the time log, the window log…)

Canvases are named by a random 10-character id (lowercase letters and digits, without the easily confused i, l, o and u) and filed in a folder named after its first two characters, so no folder ever holds more than a few hundred entries however many years of clients and tasks pile up. Titles, the hierarchy (the parent line) and everything else live in canvas.md, so renaming or moving a canvas never moves files or breaks history. canvas.md is a short front-matter block followed by the surface:

---
title: Website
parent: k3m9x2q7vd
created: 2026-09-19T10:00:00.000Z
repo: "C:\\src\\acme-site"
alias: website
---

Marketing site rebuild. Weekly sync on Tuesdays.

- Tracker: https://issues.example.com/acme

alias lines record ids a canvas had before (older devlogs used title slugs), so old activity logs and links still find it.

Older devlogs

This version writes storage format 4. A format 3 devlog is upgraded when you open it (its todo lists move into the streams; see Todos). A devlog from before Devlog 0.5 (format 1 or 2, or a 0.2 pages/ + categories/ layout) is refused with a message: open it once with Devlog 0.5, which upgrades it, then again with this version. A devlog written by a newer Devlog is refused too, rather than misread.

Canvases, surfaces and tasks

Click a canvas in the sidebar to open it. The header shows its breadcrumbs, the canvases inside it as chips, and actions: Properties (rename, move under another canvas, its type, repositories, extension fields), Archive, and whatever extensions add for canvases of their type (Start / Stop on a task, next to the title).

The surface sits above the stream. It opens rendered: links open in the browser, images open full size. Add surface / Edit surface (or a double-click) switches to a full-height editor with the same Markdown and image support as blocks, but no posting: Enter is just a new line and every change is saved a moment later (the header says "Saved"); Done switches back. Surfaces are searched along with blocks.

A canvas can have a type that an extension gives meaning to (its icon shows in the sidebar; choose it under Type in its Properties, or New task inside… on a canvas's right-click menu). The one that ships is the task, from the Time tracking extension: a canvas you record time against. Three ways to make one from a block you are writing or have written:

  • press ⌘⇧Enter instead of Enter when posting,
  • put #task anywhere on the block's first line (it is stripped on save),
  • hover an existing block and choose Make task.

Each creates a task canvas beneath the block's canvas, titled from the block's first sentence, marks the block as the link to it (a chip opens the task), and makes it the active task. Blocks inside a task canvas can be anything: more notes, pasted evidence, further tasks. Without the extension a task canvas is a plain canvas; nothing in it is lost.

On a task canvas, ▶ Start in the header starts it (Switch to this task when another is running, ■ Stop once it is the active one). The status bar's Start is a split button: while a task canvas is on screen its main part reads ▶ Start that task and starts it in one click; the ▾ beside it opens the list of every task (the canvas on screen, or the tasks inside it, first; type to filter; Enter picks), and + New task makes a task by the name you typed, under the canvas on screen, and starts it. Stop sits next to it while a task is active.

Block pages. A standup, a call, a bug: post a block for it (Alt+Enter opens it), write your notes inside, and later edit the block itself into the summary worth keeping. The stream above then reads as a list of those summaries, each one openable. Blocks inside a block live in its day file, whenever they were written; the review, summary, timeline and timesheet count each one on the day it was written. A page on a task canvas belongs to the task: writing there makes it the active task.

To put a block inside another, drag it by its grip onto the middle of that block (the top and bottom edges still reorder); a block on another day of the same canvas works too. Move → Out of this block takes one back out, beside the block it was in; Move to another canvas puts it at that canvas's top level. Making a block with notes inside it a task (Make task) moves those notes into the new task's stream; the block stays behind as the link to it. The quick switcher (⌘P, or Go to… in the top bar) lists the pages you opened lately first.

Hide on a block collapses it (with what is inside it) into a "1 hidden block" stub so a busy stream reads cleanly; click the stub to look inside and Unhide to bring it back. Nothing is deleted; hidden blocks stay in the file (hidden=1 in the marker), in search and in the review counts.

Drag to reorder. Hover a block and drag the grip at its left edge to another spot among the blocks beside it (on a canvas, within the same day; on a page, among the blocks inside the same block); the order is the file's order, so this is the same operation as insert-between. Timestamps do not change: the time on a block is when it was written, its position is where you keep it. Blocks cannot be dragged across days, because a day is a file; move them with Move instead.

Like everything else in the repository, canvases are plain files; treat the repository as sensitive, because it is.

Todos

A todo is a block with a checkbox. Post [ ] Call Dana (or several [ ] … lines, or - [ ] ones pasted from anywhere) and each line becomes a todo, wherever you are writing: on a canvas, on a block's page, on another todo's page. Ask for a review, paste the list of findings inside that block, and work through them one by one, writing notes inside each as you go; when something new comes up, add it right there.

The To do panel on the right stays put whatever the stream is doing, and collapses to a thin strip showing the open count.

  • Adding. Type in the box at the top and press Enter, or paste a list (from an email, Slack, a Markdown file) and each line becomes a todo; bullets, numbers and checkboxes are stripped. New todos go into the page on screen: the block you have open, or the canvas.
  • Scope. Here shows the page on screen and everything inside it; All shows every open todo. Anywhere but a canvas or block page (review, timeline, Summary, Timesheet) it is always everything.
  • Grouping. Todos sit under the canvases and blocks they live in, in the order they appear there. A heading with nothing of its own and a single heading beneath it merges into it ("Acme Corp / Website"). Click a heading to open it, or its arrow to fold it (remembered). Drag a todo to reorder it among the ones beside it.
  • Working a todo. Click it to open it as a page: its checkbox is at the top, and what you write goes inside it. The count beside a todo is how much is inside. Make task on its block turns it into a task canvas and starts the clock (it is then a task link, no longer a todo).
  • Done. Tick the box, in the panel or in the stream. The todo stays where it is, struck through (a run of them folds into one line), and moves to Done in the panel for two weeks. The timeline shows when it was ticked.

Todos are ordinary blocks (kind=todo, with a done time once ticked) in the day files, so they are searched, moved and reordered like everything else. Devlogs from before 0.15 kept each canvas's todos in its own todos.md; opening one moves them into the streams (each on the day it was written, its comments becoming blocks inside it) and marks the devlog as storage format 4.

Archiving

Archive in a canvas header hides the canvas and everything beneath it from the sidebar. They keep their blocks, stay searchable (results are marked "archived"), can still be opened and read, and come back with Unarchive. Archived canvases cannot become the active task or receive commits. The sidebar's collapsible Archived section lists everything archived so it is never lost.

The journal (a canvas-less notebook in earlier versions) is retired. If your devlog has notes in it, it is listed under Archived as Journal, read only; Move the blocks worth keeping onto a canvas.

Time tracking

Time tracking is the built-in Time tracking extension (devlog-time): without it Devlog has no tasks, no clock, no Summary and no Timesheet. A devlog that tracked time before 0.17 gets it added and allowed on each machine that tracked time, with the active task and idle setting carried over, so nothing changes; the first devlog a new install opens gets it the same way. Anywhere else, add it in Settings → Extensions. Its settings (Settings → Time tracking) are the idle minutes and whether its log is synced with the devlog.

There is one active task at a time, and a task is a canvas of the task type. The workflow: write a line or two to wrap up what you were doing, then either post on the task you are picking up (that makes it active), pick it from Start ▾ in the status bar, or write the next thing as a new block and post it with ⌘⇧Enter so it becomes a task of its own. The status bar shows the active task with a running clock and a Stop button (also ⌘⇧. and in the tray menu). Posting on a canvas that is not a task never touches the clock: those are just notes.

Time stops accruing while the screen is locked, the machine sleeps, or there has been no input for a while (default 10 minutes, in the extension's settings), and resumes on the same task afterwards. Each of those pauses the clock independently, so a laptop that wakes in the background while still locked stays paused until you unlock it. On Windows and macOS the lock state is also polled every 15 seconds, in case the lock event itself is missed.

Fixing tracked time. In Weekly review, each day's Task time list shows every tracked stretch. Hover one for Trim (keep only the hours you actually worked) or Remove. Corrections are recorded next to the raw activity log, which is never rewritten; removed stretches are listed under the day with Restore. Time from an explicit [2h] marker is not editable there: edit the block instead. Quitting Devlog stops the clock, so it keeps running in the tray when you close the window.

When you know better than the clock, say so in the block: [2h] Acme sync or [45m] code review counts exactly that much for the block's canvas, ending at the block's time, and replaces whatever was tracked in that window. Such blocks do not switch the active task.

Window tracking records every focus change, and if you alt-tab a lot that is a lot of sub-second flips. The raw log keeps all of them; the views clean them up: the Windows task switcher, Start menu, search box, lock screen and the like are dropped outright, and any focus shorter than a threshold (Settings → Activity → Ignore window switches shorter than, default 5 seconds) is folded into the window you were actually working in. So a 20-minute Outlook session that you alt-tabbed out of and back into five times shows as 20 minutes of Outlook.

What the clock does (start, task switches, stop, heartbeats) goes to the extension's own append-only log, and what the machine does (lock, idle, sleep) and git events go to the app's; both are one JSON-lines file per day and one folder per machine (extensions/builtin.devlog-time/<machine>/… and activity/<machine>/YYYY/MM/YYYY-MM-DD.jsonl, where <machine> is the host name plus a short id kept in the app's data folder). Older time, from before the extension, stays in activity/ and still counts. By default both live in the devlog repository, so the review and timeline add up time from every machine you work on; Settings → Activity and Settings → Time tracking can each keep theirs on this machine only. Window titles (with Window tracking) always go into the devlog, so consider what they contain. Each machine only appends to its own files, so syncing never conflicts. Each machine's events are replayed on their own (locking the laptop does not pause the desktop), and where two machines both tracked time at once, the task picked or machine woken most recently wins, so no minute is counted twice. Removing time in the review applies whichever machine tracked it. Recorded events: task switches, lock/unlock, idle/active, sleep/wake, app start/stop, a heartbeat every five minutes (only while the clock can run: nothing is written, and so nothing committed, while the machine is locked, idle or asleep), and git events from watched repositories.

Window tracking (which app and window title is in front, for screen time in the timeline and review) is the built-in Window tracking extension (devlog-focus): add it in Settings → Extensions and allow it. It runs unrestricted, so you are asked whether you trust it: it starts a small helper to read the window in front (PowerShell on Windows, osascript on macOS, where window titles need the Accessibility permission, xdotool on Linux if present). It records only while the machine is unlocked and awake, into extensions/builtin.devlog-focus/<machine>/ in the devlog.

Working copies: commits as blocks, branches as events

Every canvas header has a Link a repository… button (also under Properties → Repositories). Pick the folder of a working copy you code in, not the devlog repository. Devlog checks it is a git repository first (a subfolder resolves to its repository root) and refuses anything else. Each linked repository shows as a chip; ✕ on the chip unlinks it, keeping the commits already captured. A linked folder that is no longer a repository is flagged with ⚠. Link it to the client: one client is usually one branch at a time, and the routing below does the rest.

  • History, if you want it. The link dialog offers to import your own commits from the last 30 days (the number is in Settings), dated when they were made. It is off by default; linking alone only captures new commits.
  • New commits land where you are working. Devlog watches the repository's reflogs and, on every commit, adds a read-only block: repo, branch, short hash and message. If the active task (with time tracking) sits beneath the linked canvas (a task under that client), the block goes on the task; otherwise on the linked canvas itself. Reply to it, move it or delete it, but not edit it.

Everything else git records is captured as an activity event rather than a block, so the stream stays readable: creating a branch, switching branches (with where from), pushing, merging, rebasing, pulling, resetting and stashing. They appear on the day's Timeline with the repo name (they are recorded while the Time tracking extension is on). Commits in the devlog repository itself are ignored.

Weekly review

Weekly review in the sidebar (⌘⇧R) shows a Monday–Sunday grid: one row per top-level canvas (client), nested rows for the canvases beneath it (projects, tasks), one column per day, and a week total. Each cell shows tracked time and the number of blocks. Below the grid, every day is broken down by client → project → task with the blocks you wrote, the task time segments, and screen time by kind (coding, terminal, meetings, email & chat, browser) and by app.

Days with no tracking data at all are marked ~ and estimated from note timestamps instead (each note counts until the next one, capped at an hour).

Summary

The Summary and the Timesheet are pages of the Time tracking extension, listed in the sidebar with the app's own views (and in the quick switcher).

Summary (⌘⇧H) is the timesheet view: one row per top-level canvas (client) with hours for the chosen range (this week, last week, this month, last month, or any two dates), a share bar, the block count and the exact tracked minutes. Hours are rounded to the nearest 15 minutes by default; change the granularity in the header and it is remembered. Expand a client to see its projects and tasks rounded the same way; click a name to open the canvas. Rounding happens per row, so the rounded rows may not add up to the rounded total.

Timesheet

Timesheet (sidebar) turns a week of tracked time into what you report: sessions on the same task (gaps up to 30 minutes count as work), each rounded once to quarter hours (anything above zero is at least 15 minutes), starting on quarter hours. Days with no tracking use the review's estimate.

It is a grid: a column per day, a row per task, tasks grouped under their client with a total row for each client, and at the bottom the day's total reported (and, muted, what was actually worked). Click a cell to open its entries below the grid and change start times, durations (15-minute steps), the task, the day or the note, remove one, or add one; click an empty cell to add time there, and + Add a task… for a task with no time yet. When rounding inflates a client's day, its row shows the suggested trim (−0:45) on that day; click it to apply. Mark final approves the week.

Hour targets. Give any canvas a weekly or a monthly target (right-click → Properties → Time tracking: Hours per week, Hours per month). The Timesheet lists the targets that apply to the week on screen, each against the timesheet hours under its canvas (it and everything inside it) for its own period: this week, and each month the week touches (other weeks count as saved, or as drafted from tracked time). Targets overlap and each applies on its own: 168 h for a client in September and 20 h a week for a task under that client are two separate measures, and a target is not taken on by the canvases inside.

Sending to Jira. Add the built-in Jira worklogs extension in Settings → Extensions and allow it (it is sandboxed, and says it talks only to Jira). It gets a page of its own in Settings, marked until it is set up: the Jira address and your account email (saved in the devlog), and an API token (kept on this computer only; for Data Center leave the email empty and use a personal access token). Save and test checks the connection. Put the Jira issue key on the task canvases (right-click → Properties → Jira worklogs), or on a client or project canvas for everything beneath it. On a final week, Send to Jira… shows what would be created, changed or removed, then sends it: one worklog per entry, with its start time, duration and note. Sending again only sends what changed since the last time, from any machine. Check the Jira connection (quick switcher) tests the settings.

Sending to CMS. The built-in CMS timesheets extension fills in the Technology Partners consultant timesheet the way the browser does (CMS has no API): it logs in with your username and password, reads the week, and sets each day's hours (decimal, 7.5). Add it in Settings → Extensions, allow it, and on its page enter your CMS username (saved in the devlog) and password (kept on this computer only, never in the repository). List my CMS assignments (quick switcher) shows your assignments as number: Client / Project; put the number (or the project name) on each client canvas (right-click → Properties → CMS timesheets). On a final week, Send to CMS… shows the hours per assignment per day against what CMS has now, and only changes the days that differ; a day this sent before that no longer has time goes back to 0, and days you typed into CMS yourself are left alone. The note is sent as the day's description when there is one; otherwise the description CMS has is kept. CMS only opens a day on that day, so time on days still ahead (typically the week's Sunday) waits: send again then.

Timesheets are saved in a Timesheets canvas the Time tracking extension keeps (made on first use): one block per week on its Monday, a readable table with the exact data underneath, read-only in the stream; every change is an edit record, so the history of the adjustments is kept, and what was sent where is written inside the week. Sending goes through the app to the Jira or CMS extension, so each keeps its own credentials. See docs/TIMESHEETS.md.

Timeline

Timeline (⌘⇧T) shows one day sliced into fixed intervals (5, 15, 30 or 60 minutes, your choice). Each interval shows the task that was active, the notes and captured commits written in it, git events from watched repositories (branch created, switched, pushed…), system events such as lock or sleep, and the apps that were in front with minutes each; click the app chips to see the window titles behind them. Quiet intervals are collapsed into a "nothing recorded" line. Click a block to open it on its canvas.

A day file looks like this:

<!-- devlog:format 3 -->
# 2026-09-19

<!-- devlog:add id=k3j9d2ab pos=a0 at=2026-09-19T14:32:01.000Z -->
Started on the git sync. Pull before push, rebase on conflicts.

![shot](assets/2026-09-19-143201-a1b2.png)

<!-- devlog:add id=p0q1r2s3 parent=k3j9d2ab pos=a0 at=2026-09-19T15:02:00.000Z -->
A block written inside the first one (its page).

<!-- devlog:add id=q8v1m0zz pos=a1 at=2026-09-19T17:45:00.000Z -->
Fix the login redirect

<!-- devlog:edit id=k3j9d2ab at=2026-09-19T17:50:12.000Z -->
Started on the git sync. Pull before push; rebase on conflicts.

![shot](assets/2026-09-19-143201-a1b2.png)

<!-- devlog:set id=q8v1m0zz at=2026-09-19T17:51:00.000Z kind=task canvas=7wq0dz4hbe -->
<!-- devlog:set id=q8v1m0zz pos=Zz at=2026-09-19T17:52:00.000Z -->
<!-- devlog:delete id=p0q1r2s3 at=2026-09-19T18:00:00.000Z -->

The app only ever appends to a block file; nothing already written is changed. Each HTML comment (invisible when rendered) is one record: add creates a block, edit replaces its text, set changes its position, hidden flag, kind (todo, commit, timesheet, or task with the task canvas's id), a todo's done time, or other fields, and delete removes it (the record stays behind). The blocks you see are what you get by replaying the records in order.

  • Order comes from the pos keys, which sort as text: reordering a block appends one set with a new key between its neighbours' keys, and nesting a block inside another and inserting work the same way. Nothing else moves.
  • Safety. A bug can add a wrong record, but it cannot overwrite what is there: everything ever written stays in the file (and in git). A crash mid-write leaves at most a torn last record, which is skipped.
  • Syncing. Two machines appending to the same file never conflict: .gitattributes tells git to keep both sides, and each field goes to the record with the latest timestamp, so both machines end up with the same blocks whichever way the merge went.
  • Marker safety. A line in a block that looks like a record is escaped with one extra backslash on disk (and unescaped on reading), so nothing you type or paste can forge or split records.

Files grow with every edit; day files are small, so this will take a long time to matter. The data layer can already compact quiet files back to one add per block (checking that the result replays to exactly the same blocks first), but the app does not run it yet. Files from older formats (with a <!-- devlog:entry … --> per block and, before 0.4, a ### 14:32 heading) are still read.

Search and the local index

Listings, timelines and search are served from a small SQLite database in the app's data folder (index/), not from the repository, with a trigram full-text index so search finds any substring, in blocks, todos and surfaces, newest first. It is only a cache: the store updates it with every write, and it re-reads files that changed on disk when a devlog is opened, after a pull, and when the window regains focus (so edits made outside the app show up too). Delete it any time; it is rebuilt from the files.

Extensions

Features beyond notes (time tracking, window tracking, Jira, CMS, …) come as extensions that a devlog opts into. Open Extensions from the quick switcher or Settings:

  • Add one from a GitHub repository's releases (owner/repo and a version range such as ^1.0.0), from a single https://…/x.devlog-ext.zip, or one built into Devlog (builtin). It is recorded in devlog.json, and the exact file it resolved to is pinned by hash in devlog.lock.json, so every machine runs the same code.
  • Allow it before it runs, on each machine: the dialog shows what it says it talks to and lets you choose what it may read and add blocks to: the whole devlog, or chosen canvases (with everything inside them). A new version of a downloaded extension asks again; a built-in one keeps what you allowed when the app updates it.
  • It runs in its own process with no access to your files or other programs, and cannot see other extensions. An extension that needs more (such as window tracking) says so, and runs unrestricted only if you tick "I trust it" when allowing it. Its own data lives in extensions/<id>/ in the devlog (synced) and in user data on this machine. Network access is not restricted, so only grant read access you are comfortable sending to what it talks to.
  • Settings it declares go into devlog.json; secrets (tokens, passwords) are encrypted with the OS keychain on each machine and never written to the devlog. Per-canvas values (a Jira issue, a client id) appear as fields in the canvas dialog, are stored in canvas.md, and are inherited by canvases inside unless the field says otherwise (hour targets are not).
  • Its commands appear in the quick switcher (and, if it says so, in menus, on keys and in the note box); its pages are listed with the views; blocks it writes are marked as its own and are read-only.
  • Check for updates re-resolves the version ranges and pins what changed. Remove takes it out of the devlog (its data stays).

Writing one: packages/extension-api/README.md; the design: docs/EXTENSIONS.md.

Sync behaviour

Trigger What happens
Post / edit / delete / paste File written immediately; a commit is scheduled (default 30s)
Every N minutes (default 5) Commit if dirty, fetch, pull --rebase if behind, push
Sync now (⌘⇧S) Same, immediately
App start Pull (if a remote is configured)
App quit Commit and push pending changes (up to 20s)

The status bar shows the current state (uncommitted changes, committing, pushing, up to date, error) and the branch; the activity log and extensions' data are committed with every sync but do not count as uncommitted changes. A pull that conflicts is backed out (never left half-rebased) and reported. Errors such as a failed push are shown and retried on the next tick; nothing is ever lost because the files are already on disk.

All settings live under Settings (⌘,, or the foot of the sidebar), a page per topic: Repository (folder, remote, commit author), Sync, Appearance, Activity and Updates, then Extensions: Manage to add them, and a page for each one this devlog uses (what it may do, its settings, its secrets on this computer, its own test). Save lights up only once something changed, and each page with unsaved changes is marked in the navigation; closing with unsaved changes asks first. Canvas properties work the same way, with a page for the canvas, its repositories, and each extension that adds fields to canvases.

Updates and releases

Installed builds check GitHub Releases shortly after launch and every four hours, download a newer version in the background, and restart into it when the app is not in use: the screen is locked, the window is hidden or unfocused and there has been no input for a while, or an update has been waiting for a day and you pause typing. An open edit, reply, insert or an unsaved surface change always holds the restart. There is no prompt; Settings shows the version and update state and has a switch to turn it off. When an update is available, Update now in the status bar restarts into it right away (or as soon as the download finishes), after the usual final commit and push. The active task survives the restart, so tracking loses only a few seconds.

To cut a release, bump version in package.json (and package-lock.json) and commit it, then run the Build workflow by hand (Actions → Build → Run workflow) with release_version set to that version; it tags the commit vX.Y.Z. Pushing a vX.Y.Z tag (npm version minor && git push --follow-tags) does the same.

CI then runs typecheck, unit tests, the build and the Playwright smoke test; only if all pass does it package Windows and macOS builds and publish a GitHub release with the installers and the latest*.yml manifests that installed apps read. CI does not code-sign macOS builds, so they run but do not update themselves; the Windows build does.

Development

npm run typecheck   # main + renderer
npm test            # unit tests: file formats, store, git sync (local bare remote), extensions, time and timesheets, updates
npm run smoke       # builds, then drives the real app with Playwright (needs a display; use xvfb-run on Linux)
npm run screens     # builds, seeds a demo devlog and screenshots every view in light and dark mode

The window remembers its size and position, closes to the tray while an extension asks it to (time tracking does), and shows the current canvas in its title. Failed background actions (a move, an archive, a sync) surface as a toast in the corner rather than disappearing into the console.

Code map:

Path Purpose
packages/core/ @devlog/core: the data layer; the only code that touches a devlog repository
packages/core/src/format/ Block and canvas file formats, ids, hierarchy helpers
packages/core/src/node/store.ts DevlogStore: canvases, blocks, todos, assets
packages/core/src/node/repoIndex.ts RepoIndex: SQLite cache for listings and full-text search
packages/core/src/node/manifest.ts devlog.json (format check, extensions, settings) and devlog.lock.json
packages/core/src/extensions.ts Extension manifests, sources and ids, version ranges, grants
packages/core/src/timesheet.ts Timesheet rules: sessions, quarter-hour rounding, trims, the week's block format
packages/core/src/node/extensionFiles.ts The file broker behind an extension's private folders
packages/extension-api/ Types, wire protocol and test harness for extension authors
src/main/extensions/ Installer, sandboxed extension process, manager (consent, API)
packages/core/src/node/sync.ts SyncManager: commit / pull / push scheduler on simple-git
packages/core/src/node/activityLog.ts Per-machine append-only activity log
src/shared/theme.ts Colour presets and derived theme variables
src/shared/activity.ts Pure event → segment logic, app classification, roll-ups
src/shared/review.ts Weekly roll-up: week math, tracked/explicit/estimated time
src/main/activity/ Commit watcher
src/main/workingCopy.ts Checks that a folder picked for linking is a git working copy
builtin-extensions/ Extensions that ship with Devlog (devlog-time, devlog-focus, Jira, CMS)
packages/ui/ @devlog/ui: React components and the view bridge for extension views
src/main/updates.ts Silent auto-update via electron-updater and GitHub Releases
src/shared/updates.ts Pure "is now a good moment to restart" policy
src/main/protocol.ts devlog://asset/… (images from the repo) and devlog-ext:// (extension views)
src/main/ipc.ts, src/preload/ IPC surface exposed to the renderer as window.devlog
src/renderer/src/components/ React UI: top bar, sidebar tree, canvas view, composer, settings
src/renderer/src/editor/ TipTap extensions: highlighted code, asset images, Slack keys
scripts/ Built-in extension bundler, Playwright smoke test, screenshots
docs/DESIGN.md Design notes and rationale
docs/EXTENSIONS.md Extensions: packages, consent, sandbox, the API by topic
docs/TIMESHEETS.md Timesheets: rounding, trims, destinations, storage
docs/TIME-EXTENSION.md How time tracking became the devlog-time extension
docs/BLOCK-PAGES.md Block pages and todos as blocks
wiki/ The GitHub wiki's pages: user guide, extension developer docs and API reference (see wiki/Development.md to publish)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages