millrace
v0.0.49
Published
Git-friendly Kanban work items as INI files under tasks/ — local server and web UI
Maintainers
Readme
Millrace
A millrace channels water to and from a water wheel. It has a narrow fast current with plenty of power. You control the flow of water to the wheel using the head race, and the water flows away from the wheel along the tail race.
The ability to control the flow of water along the millrace is crucial. The goal is an optimal flow that efficiently turns the water wheel. When it comes to mills, that's the backshot wheel (the water enters at the top of the wheel, in the opposite direction to the flow of the tail race), but for knowledge work it's Kanban with strategic WIP limits.
Millrace is an lightweight git-friendly work management tool designed for optimal flow.
Because it doesn't try to be a project plan, resource schedule, or Gantt chart, Millrace is the backshot wheel of the project management tools category. And it's ideal for software teams as it lives in a Git repository.
Millrace is a git-friendly work item manager with a Kanban-first workflow. You create a git repository for you work, add the Millrace package from npm, and run the app to visuallly manage work, while git does all the hard work tracking changes in high-fidelity.
This isn't a watefall. It's engineering.
Automatic screenshots
This project uses Playwright to automate screenshots. They can be refreshed using these stps:
- Installing packages
pnpm install - Installing screenshot dependencies
screenshots:install - Running the app
pnpm start - Running Playwright
pnpm screenshots
This uses the test data reset script (see below).
Notes on testing
There is a test data reset mechanism for the test board, "Demo".
This lives within the script folder.
- reset-demo-board.mjs
- Uses the set of INI files to reset the board
- Sets dates to be relative to now for easier testing
- demo-board-template/
- Contains a set of INI files
App design
We use the Web UI Boilerplate Storybook by Basher for guidance on accessible modern UI.
server.js
The API for the application: Express serves the static UI and /api/*, reads and writes tasks/*.ini, and runs git operations for sync and history. It imports the same assets/js ini, models, and git helpers as the browser bundles so parsing stays consistent.
How the server code is organized
The millrace npm binary points at the repo-root server.js. That file stays small on purpose: it re-exports app, setMillraceDataRootForTesting, and millraceIntegrationStartup for integration tests, starts listening only when Node’s main module is server.js, and delegates the rest to server/.
server/createApp.js— Builds the Express app: JSON body parser, registers all HTTP handlers, then mounts static file serving (your millrace data directory first, then the packaged UI from the repo root).server/routes/— Each route file exports aregister…Routes(app)function for one area (flow, boards, columns and charts, cards, git sync, local user, npm update). Card handlers are split underroutes/card/(read, CRUD, move/reorder).server/archive/— Retention policy: moving stale closed cards toarchive/andcold-storage/, plus startup git sync after moves.server/analytics/— Chart and completed-view data: time buckets, card row readers, completion/cycle-time/board-state aggregations.server/board/— Board catalog INI (catalog.js), model loading (model.js), and card path helpers (cardPaths.js).server/snapshots/— Column snapshot JSON format (format.js), storage/migration/capture (storage.js), and cumulative-flow chart building.- Other top-level
server/modules — Data root and CLI parsing, INI read/write fortasks/localuser.ini, git subprocess helpers, npm update checks, and so on.
Dependencies generally flow in one direction: route modules call domain helpers; domain helpers do not import the Express app. That keeps circular imports predictable.
flowchart TB
subgraph entry [Entry]
rootServer[server.js]
end
subgraph assembly [App wiring]
createApp[createApp.js]
routeLayer[routes register functions]
end
subgraph domain [Domain modules]
archive[archive retention]
analytics[analytics charts and rows]
board[board catalog and model]
snapshots[snapshots and cumulative flow]
shared[dataRoot gitOps localUserIni ...]
end
subgraph paths [Where files live]
userData[dataRoot tasks localuser git]
packagedUi[REPO_ROOT static UI assets]
end
rootServer --> createApp
createApp --> routeLayer
routeLayer --> domain
domain --> shared
shared --> userData
createApp -->|"express.static"| userData
createApp -->|"express.static"| packagedUiAt runtime the picture is still “browser talks HTTP to Express, which reads and writes your repo”:
flowchart LR
subgraph browser["Browser"]
bundles["Bundled JS"]
end
subgraph millraceServer["Millrace server Express"]
api["Static files plus /api"]
end
subgraph repo["Repo data root"]
ini["tasks/*.ini"]
git["git"]
end
bundles <-->|"HTTP"| api
api --> ini
api --> gitassets/js
Bundled browser modules. Route-specific entry scripts live under pages/; shared libraries sit in the folders below (alongside top-level modules such as app.js, client.js, and flow*.js).
Each route’s index.html loads one entry module:
flowchart LR
r1["/"] --> e1["app.js"]
r2["/admin/"] --> e2["pages/admin.js"]
r3["/charts/"] --> e3["pages/charts.js"]
r4["/complete/"] --> e4["pages/complete.js"]
r5["/preferences/"] --> e5["pages/preferences.js"]
r6["/users/"] --> e6["pages/users.js"]Conceptually, shared code stacks from route and app entry points down through UI and domain layers to parsing and small utilities:
flowchart TB
subgraph entry["Entry"]
app["app.js · client.js · flow*.js"]
pages["pages/"]
end
subgraph presentation["Presentation"]
dialogs["dialogs/"]
ui["ui/"]
end
subgraph domain["Domain"]
models["models/"]
end
subgraph utilities["Parsing & utilities"]
ini["ini/"]
html["html/"]
git["git/"]
end
entry --> presentation
entry --> domain
presentation --> domain
domain --> utilities
presentation --> utilitiesdialogs/: Modal flows for creating or editing boards and task cards (DOM, validation, and API calls).git/: Helpers around Git merge-conflict markers (detecting hunks, choosing a side), not a full Git client.html/: Low-level helpers: escape text for HTML, derive URL-safe slugs, parse markup strings into DOM nodes.ini/: INI parsing plus board/card helpers (sections, columns, swimlanes).models/: Structured board and task-card models derived from parsed INI text.pages/: Page entry bundles wired from each route’sindex.html(boards, charts, completed work, preferences, users).ui/: Shared presentation pieces: header brand mark and styled modal alerts, confirms, and email prompts (replacingalert/confirm).
features
Gherkin-syntax tests with cucumber.js steps.
