# task-inventory Personal inventory for tasks, ideas, notes, and anything else. Entries are organised in a hierarchy, with configurable entry types. Designed to grow into project management over time. ## Features - **Configurable entry types** — default types are task, idea, note; create your own - **Hierarchical entries** — unlimited nesting, any type can be a child of any other - **Tree view** (root entries filtered; expanded children always shown) and **flat view** - **Filters** by status, priority, and tag; free-text search across title and body; filter state stored in URL query params (`?status=open&priority=high&search=foo`); status defaults to `open` - **Clickable chips** — type, status, priority, and tag badges are all clickable to filter or navigate - **CodeMirror 6 editor** — markdown syntax highlighting, multi-cursor, write/preview tabs - **Keyboard shortcuts** — `Ctrl+E` edit, `Ctrl+S` save, `Ctrl+D` discard, `Ctrl+Alt+PgUp/Down` switch write/preview tab - **Entry detail view** — full rendered markdown, metadata, children list; URL-addressable (`#4`) - **URL routing** — `#` (all), `#type/`, `#type//tag/`, `#tag/`, `#`, `#edit/`, `#manage-types`; back/forward works - **Markdown rendering** via a local Gitea instance - **Sequential integer IDs** — global across all entry types - **Flat NDJSON key-value store** — single file, no database required ## Setup ```bash npm install make build # bundle CodeMirror vendor lib cp config.yaml.example config.yaml # edit config.yaml — add your Gitea URL and token node server.mjs ``` Server starts on `http://localhost:3025` by default. ## Configuration `config.yaml` (not committed): ```yaml gitea: url: https://gitea.example.com token: your_token_here # needs read:misc scope minimum ``` If Gitea is not configured, markdown is shown as plain text. Environment overrides: | Variable | Default | |---|---| | `PORT` | `3025` | | `BIND_ADDRESS` | `localhost` | ## Data model All data is stored in `data/tasks.ndjson` as a flat NDJSON key-value store. ``` Entry { id integer sequential, global across all types type string entry type id (e.g. "task", "idea", "note") title string markdown body string markdown, optional status open | deferred | done | cancelled priority high | normal | low tags string[] parent_id integer | null null = root entry created_at ms timestamp updated_at ms timestamp } Entry_Type { id string lowercase alphanumeric/underscore, e.g. "bug_report" title string display name description string created_at ms timestamp updated_at ms timestamp } ``` On first run, three default entry types are seeded: task, idea, note. Existing data from older flat-key format (`task:1`, `task:2`, …) is migrated automatically. ## API All responses: `{ ok: true, ...data }` or `{ ok: false, error: string }`. ``` GET /api/entry-types POST /api/entry-types body: { id, title, description? } PUT /api/entry-types/:id body: { title?, description? } DELETE /api/entry-types/:id blocked if type still has entries GET /api/entries ?type= to filter by type POST /api/entries body: { type, title, body?, status?, priority?, tags?, parent_id? } GET /api/entries/:id PUT /api/entries/:id body: any subset of entry fields (including type) DELETE /api/entries/:id blocked if entry has children POST /api/render-markdown body: { text, mode? } → { html } ``` ## Deployment ```bash # First time: create service user and config sudo useradd -r task-inventory sudo mkdir -p /srv/task-inventory sudo cp config.yaml.example /srv/task-inventory/config.yaml sudo chown task-inventory:task-inventory /srv/task-inventory/config.yaml # edit /srv/task-inventory/config.yaml # Deploy (and on every update) — builds vendor bundle, rsyncs, runs npm ci make deploy DEST=/srv/task-inventory # Enable service (first time only) sudo systemctl enable --now "$(realpath deployment/task-inventory.service)" ``` `make deploy` builds the CodeMirror vendor bundle, rsyncs files (excluding `data/`, `config.yaml`, `node_modules/`), fixes ownership, and runs `npm ci` as the service user. `make sync` does the rsync+chown only, skipping build and `npm ci`. To bind to all interfaces instead of localhost, uncomment `Environment=BIND_ADDRESS=0.0.0.0` in `deployment/task-inventory.service`. ## File map ``` server.mjs Entry point — Express 5, all routes Makefile build / deploy / sync targets codemirror-entry.mjs ESM entry point for esbuild CodeMirror bundle lib/ config.mjs Loads config.yaml (ENOENT-safe) kv-store.mjs Flat NDJSON key-value store (auto-load, debounced flush) storage.mjs Entry types + entries CRUD, ID generation, migration public/ app.mjs SPA — state, routing, rendering, dialogs index.html Shell style.css All styles templates.html HTML templates (injected at init) gitea-markup.css Vendored Gitea markup + chroma CSS for markdown rendering vendor/ Built by make build, not committed codemirror-bundle.mjs Bundled CodeMirror 6 (ESM) lib/ api.mjs fetch wrappers for all API endpoints dom.mjs qs(), clone(), set_text(), show(), hide() data/ Created at runtime, not committed tasks.ndjson All entry records and entry type definitions deployment/ task-inventory.service systemd unit file ```