diff --git a/README.md b/README.md index e65bf07..8061f10 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ # task-inventory -Personal task management. Organise tasks, thoughts, and ideas as a hierarchy. Designed to grow into project management over time. +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 -- Hierarchical tasks with unlimited nesting — create subtasks, reparent tasks, or promote subtasks to roots -- Tree view (roots only, expandable) and flat view (all tasks) -- Filters by status, priority, and tag; free-text search across title and body -- Markdown rendering via a local Gitea instance — preview tab in the editor, rendered inline in the task list -- Sequential integer IDs -- Flat NDJSON key-value store — single file, no database required +- **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** (roots only, expandable — children always shown unfiltered) and **flat view** +- **Filters** by status, priority, and tag; free-text search across title and body; clickable tag chips +- **Entry detail view** — full rendered markdown, metadata, children list; URL-addressable (`#4`) +- **URL routing** — `#all`, `#type/`, `#`, `#manage-types`; back/forward works +- **Markdown rendering** via a local Gitea instance — edit/preview tabs in the editor +- **Sequential integer IDs** — global across all entry types +- **Flat NDJSON key-value store** — single file, no database required ## Setup @@ -32,7 +35,7 @@ gitea: token: your_token_here # needs read:misc scope minimum ``` -If Gitea is not configured, task titles and bodies are shown as plain text. +If Gitea is not configured, titles and bodies are shown as plain text. Environment overrides: @@ -43,73 +46,84 @@ Environment overrides: ## Data model -Tasks are stored in `data/tasks.ndjson`. Sequential IDs are tracked in `data/ids.ndjson`. +All data is stored in `data/tasks.ndjson` as a flat NDJSON key-value store. ``` -Task { - id integer sequential, auto-assigned +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 task + 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/tasks -POST /api/tasks body: { title, body?, status?, priority?, tags?, parent_id? } -GET /api/tasks/:id -PUT /api/tasks/:id body: any subset of task fields -DELETE /api/tasks/:id blocked if task has subtasks +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 -POST /api/render-markdown body: { text, mode? } → { html } +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 +DELETE /api/entries/:id blocked if entry has children + +POST /api/render-markdown body: { text, mode? } → { html } ``` -## Running as a system service - -`deployment/task-inventory.service` targets `/srv/task-inventory` with a dedicated `task-inventory` user. +## Deployment ```bash -# Create service user +# First time: create service user and config sudo useradd -r task-inventory - -# Deploy files -sudo cp -r . /srv/task-inventory -sudo chown -R task-inventory:task-inventory /srv/task-inventory - -# Install dependencies as service user -sudo -u task-inventory npm ci --prefix /srv/task-inventory - -# Copy and configure config.yaml +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 -# Enable and start +# Deploy (and on every update) +make deploy DEST=/srv/task-inventory + +# Enable service (first time only) sudo systemctl enable --now "$(realpath deployment/task-inventory.service)" ``` -Packaging (Arch, Debian, etc.) is not set up yet. +`make deploy` runs rsync (excluding `data/`, `config.yaml`, `node_modules/`), fixes ownership, and runs `npm ci` as the service user. `make sync` does the rsync+chown only, skipping `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 deploy / sync targets lib/ config.mjs Loads config.yaml (ENOENT-safe) - ids.mjs Sequential integer ID generator per namespace kv-store.mjs Flat NDJSON key-value store (auto-load, debounced flush) - storage.mjs Task CRUD wrappers + storage.mjs Entry types + entries CRUD, ID generation, migration public/ - app.mjs SPA — state, rendering, dialogs + app.mjs SPA — state, routing, rendering, dialogs index.html Shell style.css All styles templates.html HTML templates (injected at init) @@ -118,6 +132,7 @@ public/ 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 task records - ids.ndjson Sequence counters + tasks.ndjson All entry records and entry type definitions +deployment/ + task-inventory.service systemd unit file ```