Build MeisterTask
KINDAreplaces $9/mosaves $108/yrback to the verdict
A team board whose hard problems are ordering cards two people can drag at once, enforcing roles in one place rather than in every route, and generating recurring work from rules that do not invent dates the calendar does not have.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why The application, the database and the job runner come up together, which is the difference between a tracker a team uses and one nobody installs.
Get it Docker Desktop or your distribution's packages. open ↗
Verify
docker compose up brings the stack to a healthy state - installfree
Why Runs the application and the migration tooling outside the container during development.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally, a few pounds a month managed
Why The ordering, the append-only log and the authorisation scopes all rely on constraints and permissions the database enforces.
Get it Included in the compose file, or a managed instance for a shared deployment. open ↗
Verify
Migrations apply and the activity table refuses an update - API keyfree tiers are common
Why Invitations and mention notifications are the only outbound path, and a tracker nobody can be invited to is a personal list.
Get it Any transactional email provider, or a mailbox with application-specific credentials.
Verify
An invitation arrives at a real address - have readya few pounds a month
Why A project board on one person's laptop is a private list, and the permission model only means anything with more than one account on it.
Get it A small virtual server with a domain and HTTPS.
Verify
Two accounts in different roles can use it simultaneously - decidefree
Why The authorisation tests are generated from this list, and deciding it while writing the routes is how viewers end up able to edit.
Get it Write a table of role against operation before Phase 2, including the awkward ones like closing someone else's issue.
Verify
Every row in the table has a corresponding test
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- orgs(id, name), memberships(org_id, user_id, role) · role is owner, member or viewer - projects(id, org_id, name, archived_at), columns(id, project_id, name, rank) - issues(id, project_id, column_id, title, body, assignee_id, rank, due_at, recurrence_rule, recurrence_parent_id, created_at, closed_at) - labels, milestones and watchers as join tables, all scoped to a project - comments(id, issue_id, author_id, body, created_at, edited_at, deleted_at) · soft deleted, because a thread with holes is unreadable - activity(id, org_id, project_id, actor_id, entity_type, entity_id, verb, before_json, after_json, at) · append only, never updated or deleted - The rank columns are sortable strings rather than integers, so inserting between two cards touches one row and two simultaneous drags cannot collide on a value.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://user:pass@db:5432/boards | Issues, activity and memberships. |
APP_BASE_URL | required | https://boards.example.com | Used in invitations and mention notifications. |
SESSION_SECRETsecret | required | a long random string, redacted | Signs sessions. Rotating it logs everyone out, which is the point. |
SMTP_URLsecret | required | smtps://user:pass@smtp.example.com:465 | Invitations and mentions only. No marketing path exists. |
BACKUP_DIR | required | /var/backups/boards | Nightly dumps, with a restore exercised in the suite. |
RANK_REBALANCE_CRON | required | 0 4 * * * | When rank rebalancing runs. It must never change visible order. |
ADMIN_RECOVERY_TOKENsecret | required | a long random string, redacted | Grants owner access to a locked-out organisation. Store it away from the server. |
The build, in order
One issue model and an order two people can change at once
Dragging a card writes one row, and concurrent drags do not fight.
The drag should cost one update, not a column rewrite.
Files
src/lib/rank.tstests/rank.test.tsThis is the bug people report as the board being flaky.
Files
tests/concurrent-move.test.tsA maintenance job that changes the order is worse than no job.
Files
src/jobs/rebalance.ts
done when · tick each as it passeswatch out- Storing position as an integer means every drag rewrites every card below it, so two people reordering the same column at the same time produce interleaved writes, cards that snap back after a refresh, and a board nobody trusts. Compute a rank between the neighbours, write one row, and rebalance on a schedule rather than on every move.
Roles decided in one place
Every read and write passes the same authorisation function, and each mutation has a test.
Routes should not be the place this is decided.
Files
src/lib/authz.tsThe check that survives the next hurried feature.
Files
tests/authz-coverage.test.tsOtherwise the error message is an existence oracle.
Files
src/db/scope.ts
done when · tick each as it passeswatch out- Checking permissions in the route handler means the one endpoint somebody adds in a hurry becomes the way a viewer edits the backlog, and nothing in the test suite notices because the test suite tests the routes that were written carefully. Put the check in the data access layer and write an enumeration test that fails when a mutation exists without a matching authorisation test.
Recurring work that respects the calendar
A repeating task generates the next instance correctly, including on dates that do not exist.
The specification already decided this; do not improvise a friendlier answer.
Files
src/lib/recurrence.tstests/recurrence.test.tsA board full of future cards is a board people stop reading.
Files
src/jobs/next-instance.tsBecause most people asking for the thirty-first mean the last day.
Files
src/ui/RecurrenceEditor.tsx
done when · tick each as it passeswatch out- Materialising a year of future instances for every repeating task fills the board with cards nobody wants to see, and editing the rule afterwards leaves you deciding which of three hundred existing rows to rewrite. Generate one instance at a time on completion, keep the rule on the parent, and store the link from child to parent.
Conversation, and a log that outlives the entities
Comments, mentions and assignments all land in an append-only history.
History that depends on live rows is not history.
Files
src/lib/activity.tsA revoked update permission beats a code comment saying do not update.
Files
migrations/00x_activity.sqlEditing a comment should not page the team again.
Files
src/lib/mentions.tstests/mentions.test.ts
done when · tick each as it passeswatch out- Rendering history by joining the activity log to the entities it mentions produces blank lines the moment something is deleted, and an issue nobody can find is exactly the one the history was needed for. Denormalise the display values into the log row at write time and accept the duplication.
Leaving, arriving, and getting everything outproduct builder
A departing member changes nothing about the record, and the whole project exports.
The reasoning behind old decisions is the asset here.
Files
src/lib/membership.tsAn export nobody has reimported is a guess.
Files
tests/export-roundtrip.test.tsA backup nobody has restored is a hope.
Files
ops/backup.shtests/restore.test.ts
done when · tick each as it passeswatch out- Deleting a departed colleague's user row cascades through comments and activity and quietly removes half the reasoning behind decisions the team is still living with. Revoke access, retain the attribution, and make deletion of history a separate deliberate action that names exactly what it will remove.
That is the whole plan for MeisterTask. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Third-party integrations.
- Single sign-on and directory sync.
- Portfolio and capacity planning.
- Native mobile clients.
- cross-platform polish, integrations, automations, and team administration
- deep integrations
- enterprise permissions
- large-scale reporting
- battle-tested notifications and mobile apps
- Attachments, once backup and retention have run for a month without surprises
- A saved-filter view built on the existing issue model rather than a new one
- Time tracking, which is only worth adding if someone will actually read the numbers
Need the files? The project pack on the verdict page hands your agent the whole brief · more project management.