Sahand82 downloadsCollects tagged task lines from across your vault into a sidebar inbox, sorts them with the GTD method, and flags projects that have stalled.
An Obsidian plugin that collects #task lines from anywhere in your vault into
a sidebar inbox, walks them through the GTD clarify flow, and marks each line
*(Handled)* once you've sorted it.
An open loop is anything you've committed to that isn't closed yet. TaskLoops finds them, helps you file them, and tells you when a project has quietly stalled.
Two things, both of which you trigger deliberately:
*(Handled)* appended to the end of a task's own line, once you sort it.
Turn off Mark lines when sorted and this stops entirely.- … #task line appended to your capture note, and only when you
use quick capture. That note is named in settings, defaults to
TaskLoops Inbox.md, and is created on first use. Capture only ever appends
to the end of that one file.Beyond those, nothing. It never touches frontmatter or note properties, never adds tags, never reorders or moves lines, never deletes anything, and never edits a line you have not personally sorted or edited. If you never press the capture button, the capture note is never created.
All state — bucket, context, delegate, date, project links, done — lives in the
plugin's own data.json, never in your notes.
One row of controls at the top of the panel:
A sidebar is narrow for a board, so the columns scroll horizontally. For the full width, run Open in a main tab from the command palette — it is the same view over the same data, just with room.
Standard GTD, one question at a time:
You can back out of any step, and re-file anything later from the ⋯ menu.
The + button in the panel header opens a capture row. Type, press Enter, and the line is appended to your capture note and appears in the Inbox unsorted — the row stays open so a burst of thoughts costs one keystroke each. Escape closes it. There is also a Capture a task to the inbox command you can bind to a hotkey.
The first time you capture anything, you are asked which folder captures should
live in, and TaskLoops Inbox.md is created there. Dismissing that prompt is a
valid answer — it puts the note at the vault root. You are only asked once; the
capture row shows the destination underneath it, and clicking it moves the
capture note somewhere else at any time.
The projects list stays a list of outcomes, not a place to keep steps. The plugin tracks one thing about a project: which actions belong to it, so it can tell you when a project has nothing open against it. A project showing no next action has stalled, which is the single failure mode the weekly review exists to catch. Those projects sort to the top and put a dot on the Projects tab.
Actions get attached to a project three ways:
#task line indented under a project's #task line is
its action. Blank lines are transparent; any non-blank line at column zero
ends the block, so an outline further down the note won't attach itself to
something unrelated. This is read-only inference — nothing is written.⋯ → Part of project….Expanding a project shows its actions one level deep. There is no nesting beyond that, and a project can't belong to another project — choosing Standalone pins a task as unattached even if it sits under a project in the outline.
A project whose only children are unclarified inbox items still counts as stalled. That is deliberate: an uncaptured thought is not something you can act on.
Double-click a task's text in the panel to rewrite it, or use ⋯ → Edit text.
Enter saves, Escape cancels, Shift+Enter adds a line break.
This is the one place the plugin rewrites something you wrote, so it is careful
about it: only the sentence changes. The line's indentation, bullet, checkbox,
blockquote or heading markers, the tag exactly as you wrote it — #task/work
stays #task/work — any block id, and the handled marker are all preserved.
Before writing, the line is re-located by its exact previous text, so an edit
made in the note meanwhile cannot cause the wrong line to be rewritten.
Renaming changes a task's identity, since identity is derived from its text. The record is moved across explicitly, so its bucket, context, date and project links all survive.
The context, waiting-on and date chips under each task are clickable: one click
changes any of them. Empty ones appear on hover as + context and + date, and
on touch they live on the ⋯ menu instead.
Changing an attribute never re-files a task. A context, a date and a person
are orthogonal to the filing decision — a delegated task can have a context, a
next action can have a date — so setting one leaves the task exactly where it
is. Buckets change only through the clarify flow, the ⋯ menu, or a drag, all
of which are explicit. Attributes also survive a re-file; returning a task to
the Inbox is the one reset, since that means it is unclarified again.
Each task has a priority slot: P1 high, P2 medium, P3 low, or none.
Click the chip, or use ⋯ → Priority. P1 and P2 also colour the card's left
edge, so urgency reads without reading the chip, and priority tints the dots on
the calendar.
Priority is a sort key, applied after any manual order you set and before how recently a task was filed. Tasks without one sort last, so nothing reshuffles until you start using it.
Everything files by dragging, in every layout:
Dropping onto Scheduled asks for a date and Waiting asks for a name, because those buckets are meaningless without them; every other target files immediately.
Dragging is mouse-only — it uses HTML5 drag events, which touch screens don't
fire. Every drag action is also on the ⋯ menu, which is the path to use on a
phone.
Three scales, switched with the Day / Week / Month buttons. The heading steps back and forward by whichever scale you are on, and clicking it jumps to today.
Every day shown is a drop target, at every scale, so rescheduling is a drag. Each day also has a + to create a task already dated to it. Undated tasks are listed at the bottom so there is always something to drag onto the calendar.
Every task row has a date chip. Click it to set, change or clear the date —
there are Today / Tomorrow / In a week shortcuts plus a picker. The same is on
the ⋯ menu. Setting a date records the date and nothing more — it does not
move the task to Scheduled. Use the clarify flow, the ⋯ menu or a drag onto
the Scheduled tab for that, all of which ask for the date too.
Tag positions come from Obsidian's own metadata cache, not a text search, so
fenced code blocks, inline code and YAML frontmatter are excluded automatically.
#task/work and other child tags count. A line holding nothing but the tag is
ignored. Bullets, checkboxes, ordered lists, blockquotes, headings and block ids
are stripped from the display text but left untouched in the note.
A task is identified by a hash of its note path, its cleaned text, and its index among identical lines in that file. Two consequences worth knowing:
Before writing a marker the plugin re-locates the line by its exact prior text. If the line moved and the text is still unique, it follows it; if it cannot identify the line unambiguously, it writes nothing and tells you.
Renaming or moving a note carries its filed tasks along with it.
The panel sizes itself against its own width, not the window's, using a CSS container query — a desktop sidebar is often narrower than a phone screen, so viewport media queries would tune the wrong thing. Below about 290px the tab labels drop and the rail becomes icons and counts.
Touch devices get larger targets and lose nothing to hover: the ⋯ button is
always visible rather than fading in, and the hover-only "add a date" chip is
hidden outright, since its actions live on the ⋯ menu anyway. Verified with no
horizontal overflow down to a 260px panel.
From Obsidian — open Settings → Community plugins → Browse, search for TaskLoops, install and enable. This is the easiest route and gets you updates automatically. Plugin page
Requires Obsidian 1.7.2 or later, on desktop and mobile.
From a release — download main.js, manifest.json and styles.css from
the releases page into
<vault>/.obsidian/plugins/taskloops/, then enable it under
Settings → Community plugins.
From source — clone this repo, then:
npm install
OBSIDIAN_VAULT="/path/to/your/vault" npm run deploy
To avoid repeating the path, write it once into a .vault-path file, which is
gitignored:
echo "/path/to/your/vault" > .vault-path
npm run deploy
deploy builds and copies only the three files Obsidian loads. It never touches
data.json, so your sorting survives a redeploy.
npm install
npm run dev # watch build
npm run build # typecheck + minified build
npm test # 79 tests over the scanner
npm run deploy # build, then copy into a vault
Source layout:
src/
├── main.ts plugin lifecycle, state, reconciliation, filing actions
├── scanner.ts finding tagged lines, identity, the one write path
├── view.ts sidebar: tabs, lists, cards, projects, drag and drop
├── wizard.ts the GTD clarify flow
├── modals.ts folder, project, task and date pickers
├── settings.ts settings pane
├── types.ts buckets, stored shapes, defaults
├── dates.ts calendar-day helpers
└── text.ts string helpers
scripts/ build tooling: deploy, version bump
test/ node test suite over scanner.ts
main.js is built to the repository root, because that is where Obsidian loads
it from and where the release workflow picks it up. styles.css and
manifest.json stay at the root for the same reason — they ship verbatim rather
than being compiled.
The tests cover the risky parts: which lines count as tasks, how identity is derived, how indentation implies a parent, and that marking a line is idempotent and reversible without disturbing its indent, bullet or checkbox.
Obsidian resolves a release by its git tag, which must be the bare version
number with no v prefix, and expects main.js, manifest.json and
styles.css attached individually as assets.
npm version patch
git push && git push --tags
npm version bumps package.json, then version-bump.mjs writes the same
version into manifest.json and adds a row to versions.json. The .npmrc
sets tag-version-prefix="", which is what stops npm creating a v0.1.1 style
tag — Obsidian would not resolve that.
Pushing the tag runs .github/workflows/release.yml, which installs, tests,
builds, verifies the tag matches manifest.json, attests build provenance, and
opens a draft release with main.js, manifest.json and styles.css
attached individually. Review it and publish when ready.
versions.json maps each plugin version to the minimum Obsidian version it
needs, so older Obsidian installs resolve to a release they can actually run.
If TaskLoops is useful to you, you can buy me a coffee. Entirely optional — the plugin is free and always will be.
MIT © Sahand Poursadeghi Khiavi