Planning method
How effort was estimated, how the scheduler turns it into dates, and how to change the plan.
Effort
Every task is estimated in person-days of focused work, twice:
| Column | Meaning |
|---|---|
| Classic | From scratch — no reusable internal code, no AI assistance. Kept for comparison only. |
| Optimized | This team: reusing what the team already built on previous projects (UI kit patterns, RBAC, queues, storage, PDF, e-mail and notification modules, CI and Docker recipes), with AI-assisted development. Only this number is scheduled. |
Sizes are a quick read of the optimized effort: XS ≤ 0.5 d · S ≤ 1.5 d · M ≤ 4 d · L ≤ 10 d.
What has no precedent barely moves between the two columns: the offline-first delegate app, the GPS and tour logic, the client workshops, the pilot and — above all — the design work, which is priced at Akram's own estimate.
Akram's 21 days are read as 21 working days (Sunday–Thursday). If he meant three calendar weeks, set the effort of DES-01…DES-21 to 0.7 in tasks.json and every date recomputes.
Scheduling
The scheduler (apps/docs/src/lib/plan/schedule-core.mjs) is the only thing that produces dates:
- Working days. Each person gets a stream of the days they can work: Sunday–Thursday from the plan start, minus public holidays (
settings.holidays), minus their absences. - Lanes. A task belongs to the first person of its
team. A person's lane is walked by phase (release: P0 → P4) then byorder. - Start. A task starts after its explicit
depsand after the previous task of its lane. - Duration.
optimized ÷ FTEworking days, kept fractional — two half-day tasks share one day instead of taking two. - Pinned starts. A
lockedtask withpinnedStartcannot start earlier than that date. - Done tasks sit on their
doneOndate and consume no capacity. - Phases and milestones are derived: a phase spans the earliest start to the latest end of its open tasks; a milestone is reached when the last task in its
afterlist ends.
Co-responsible people (the rest of team) appear on the task brief but do not consume their own lane — when a contribution is large it is a separate task in their lane.
Changing the plan
| To… | Change in apps/docs/content/plan/tasks.json |
|---|---|
| Re-estimate | optimized (and effort) |
| Reassign | team (first entry owns the lane) |
| Re-sequence | order within the lane, or deps |
| Move to another phase | release |
| Mark progress | status: "done" + doneOn |
| Add an absence or a holiday | people[].unavailable or settings.holidays |
| Change capacity | people[].fte |
Then:
pnpm --filter @hoco/docs schedule # print the new calendar, warns on unknown dependencies
pnpm --filter @hoco/docs generate # regenerate the team-task briefs
pnpm --filter @hoco/docs check-mdx # make sure every page still compilesCommit the plan change in its own commit (docs(plan): …) so the history of the plan is readable.
What the method deliberately does not do
It does not invent capacity. If a date is wrong, the levers are real: move scope to another phase, change who owns a task, change an FTE, add a person, or change the estimate itself — and say why in the commit. There is no setting that makes the same work take less time.
It does not model part-time contributions inside one lane (a two-week pilot is planned as ten days of each developer's lane because they are on call throughout).
It does not hide buffers. Gaps in a lane are visible on the timeline; they are the first capacity to use when something slips.