Jay43 downloadsRandomly draws a task from the checklist at your cursor and stamps tasks with selected-at / completed-at datetimes.
An Obsidian plugin that picks your next task for you.
Put your cursor in a Markdown checklist, run one command, and the plugin spins through the unchecked items and randomly lands on one — marking it active and stamping it with the time it was selected. When you later check the task off, it stamps the completion time too. No more staring at a list deciding what to do next.
#in-progress
by default) plus a start glyph (🚀) and a selected-at timestamp. A checklist
holds at most one active task at a time.YYYY-MM-DDTHH:mm) instead of
date-only stamps.See CONTEXT.md for the precise vocabulary (checklist, candidate,
active, timestamps) the plugin is built on.
(Pending review.) Once published: Settings → Community plugins → Browse, search for "Random Task Selector", install, and enable.
main.js, manifest.json, and styles.css from the
latest release.<your-vault>/.obsidian/plugins/random-task-selector/.Open a note with a Markdown checklist:
- [ ] write the tests
- [ ] fix the flaky build
- [ ] update the changelog
Put your cursor on any line of the checklist.
Run Pick for me from the command palette (⌘/Ctrl-P). Optionally bind it to a hotkey.
The highlight spins and lands on the winner, which is marked active:
- [ ] write the tests
- [ ] fix the flaky build #in-progress 🚀 2026-07-03T14:22
- [ ] update the changelog
Check it off when done — the completed timestamp is stamped automatically and the active tag is removed:
- [x] fix the flaky build 🚀 2026-07-03T14:22 ✅ 2026-07-03T15:40
| Setting | Default | Meaning |
|---|---|---|
| Active tag | #in-progress |
Tag marking the one active task in a checklist |
| Start glyph | 🚀 | Written with the selected-at datetime |
| Completed glyph | ✅ | Written with the completed-at datetime |
Contributions are welcome — bug reports, feature ideas, and pull requests.
node --version)git clone <your-fork-url>
cd obsidian-random-task
npm install
For a live-reload workflow, clone directly into a test vault's
.obsidian/plugins/random-task-selector/ folder.
npm run dev # esbuild watch: recompiles src/ → main.js on save
Reload Obsidian (or use a hot-reload plugin) to pick up changes. The production build (type-checks first) is:
npm run build
npm run lint # ESLint + eslint-plugin-obsidianmd
CI lints every commit on every branch.
The codebase follows two decisions that any change should respect (full
rationale in docs/adr/):
src/core/ with no Obsidian
imports. The src/commands/, src/reconcile/, and src/animation/ layers
are thin adapters that read from Obsidian, call the core, and write back. New
logic belongs in the core so it can be unit-tested exhaustively.src/
main.ts plugin lifecycle only (load, settings, register)
settings*.ts settings interface, defaults, and settings tab
core/ pure domain logic (checklist, draw, task-line, datetime, transition)
commands/ Draw command adapter
reconcile/ completion-stamping editor extension
animation/ in-editor spin/highlight
ui/ modals (emoji picker)
Two seams, no Obsidian mocks:
Unit (Vitest) — the pure core. Fast and exhaustive:
npm test
End-to-end (wdio-obsidian-service) — the Obsidian adapter (change
listener, command wiring, committed draw result, Notices) driven against a
real, sandboxed Obsidian. Build first: the service loads the bundled
main.js, not the TypeScript source.
npm run build && npm run test:e2e
By default the service downloads a pinned Obsidian into .obsidian-cache
(git-ignored, cached in CI). This is the supported path and what
.github/workflows/test.yml runs headlessly on Linux.
<details>
<summary>Optional: run E2E against your installed Obsidian (fragile)</summary>
export OBSIDIAN_BINARY_PATH="/Applications/Obsidian.app/Contents/MacOS/Obsidian"
export OBSIDIAN_INSTALLER_VERSION="latest" # must match the installed app
npm run test:e2e
OBSIDIAN_INSTALLER_VERSION selects the ChromeDriver, which must match the
installed app's Electron/Chrome version. Obsidian auto-updates its app JS
ahead of the launcher's ChromeDriver database, so a freshly-updated install
can have no matching driver and the session dies at creation. If it fails,
unset OBSIDIAN_BINARY_PATH and use the download path.
src/core/ with unit tests.npm run lint, npm test, and (for adapter changes) the E2E suite before
opening a PR.Maintainer steps for cutting a release:
minAppVersion in manifest.json if it changed.npm version patch | minor | major — which updates
manifest.json, package.json, and adds the entry to versions.json.npm run build to produce main.js.v prefix) and
attach main.js, manifest.json, and styles.css as binaries.