Files
task-inventory/README.md

146 lines
5.6 KiB
Markdown

# 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/<id>`, `#type/<id>/tag/<tag>`, `#tag/<tag>`, `#<entry-id>`, `#edit/<entry-id>`, `#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=<id> 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
```