How Bonyad sprints work — a friendly walkthrough

This page explains the entire sprint workflow in Bonyad — from planning to closing — using plain-language diagrams and step-by-step prose. Read it top to bottom and you'll know exactly what every button on the sprints page does, who's allowed to do what, what the system tracks automatically, and what happens when a task slips past its deadline.

Reading time: ~10 min Audience: everyone on the team Last updated: 2026-05-27
Foundation

What is a sprint in Bonyad?

A sprint in Bonyad is a named, time-boxed batch of work. Think of it as a folder that says "these are the things we promised to ship between today and a future date." You put issues into it, assign them to people, give them estimates in story points, and then watch the sprint progress as work moves through the board.

Every sprint belongs to exactly one app — Bonyad, Mafeshsho8l, Tawazon, or Droop — so different teams never see each other's plans. The page you're reading right now lives at /bonyad/flow.html, but the actual sprint planning UI is at /bonyad/sprints.html?app=<your-app>.

What's inside a sprint?

name
A short title like "Sprint 12 — Auth hardening".
goal
One-line answer to "why does this sprint exist?". Optional but recommended.
start_date
When the sprint window opens.
end_date
When it's supposed to close — the burndown chart measures against this.
capacity_points
How many story points the team thinks it can ship in this window. Used to flag over-planning.
status
One of planning, active, completed, cancelled — determines what buttons appear.
issues
The work to be done. Each issue has its own assignee, estimate, due date, and current board state.
Tip
A sprint doesn't own issues — it just references them. An issue can be in zero or many sprints over time, and removing it from a sprint never deletes the issue itself. This means you can move work between sprints safely.
Overview

The big picture — one diagram, end to end

Here's the entire sprint workflow as one diagram. Read it left to right. Coloured borders tell you who's doing the work at each step — the team, the system, or someone outside the sprint (like the admin getting a Telegram alert).

flowchart LR
  classDef user   fill:#1a2540,stroke:#5eb3ff,stroke-width:2px,color:#f4f4f8;
  classDef sys    fill:#2a1a10,stroke:#ff6b1a,stroke-width:2px,color:#f4f4f8;
  classDef ok     fill:#0a3b30,stroke:#2ee6b8,stroke-width:2px,color:#f4f4f8;
  classDef danger fill:#3d1717,stroke:#f87171,stroke-width:2px,color:#f4f4f8;
  classDef decide fill:#161620,stroke:#71717f,stroke-width:2px,color:#f4f4f8,stroke-dasharray:5 4;

  S1["Create sprint
name + dates + goal"]:::user S2["Add issues
from backlog or quick-add"]:::user S3["Assign owner
estimate + start/end date"]:::user S4["Start sprint
status changes to active"]:::user S5["Move cards through
the kanban board"]:::user D{"Past due?"}:::decide S6["Mark sprint
complete"]:::ok L1["Card turns red
OVERDUE indicator"]:::danger L2["Assignee files
a written reason"]:::user L3["System alerts
admin via Telegram"]:::sys S1 --> S2 --> S3 --> S4 --> S5 --> D D -- no --> S6 D -- yes --> L1 --> L2 --> L3 --> S5
Team member action Automatic system action Success state Problem / overdue Decision branch
Read it like this: a team member creates a sprint, fills it with issues and assignments, and starts it. From there, the team moves cards through the kanban. At every step the system checks whether anything has gone past its deadline — if so, the assignee files a written reason, an automated Telegram alert goes to the admin, and work continues. Once the team is done, they mark the sprint complete.
Lifecycle

A sprint moves through four lifecycle stages

Every sprint has a status field. The status determines which buttons appear on the detail page. You can't "start" a completed sprint, and you can't "complete" one that hasn't been started — these guardrails prevent accidental state corruption.

stateDiagram-v2
  classDef plan fill:#1a1720,stroke:#71717f,color:#cdcdd8;
  classDef act  fill:#0a3b30,stroke:#2ee6b8,color:#2ee6b8;
  classDef done fill:#0a2540,stroke:#5eb3ff,color:#5eb3ff;
  classDef bad  fill:#3d1717,stroke:#f87171,color:#f87171;

  [*] --> planning : you create it
  planning --> active : you click
Start sprint planning --> cancelled : you cancel
before starting active --> completed : you click
Complete active --> cancelled : something went wrong
need to bail completed --> [*] cancelled --> [*]
Planning
The sprint exists but the clock isn't ticking. You're filling it with issues, setting estimates, and rallying owners. Nothing is tracked yet.
Active
The sprint is live. The burndown chart starts plotting daily progress, the board accepts drag-and-drop, and overdue checks run against the end date.
Completed
Work is locked in. Future velocity reports will count this sprint. Unfinished issues stay on the board for reference but no longer count toward "done points."
Cancelled
Sprint was abandoned (priorities changed, team unavailable, scope was wrong). Issues return to the backlog or get carried into a new sprint.
Stage 1 of 5

Plan the sprint — create it and fill it with work

Click + New sprint on the sprints page. A modal opens asking for the essentials. Don't worry about getting every field perfect — you can edit any of them later.

  1. Click + New sprint
    Top-right of the sprints page. A modal slides in with the create form.
  2. Fill in name, goal, and dates
    Name is required. The goal and dates are optional but strongly recommended — the goal becomes the subtitle and the dates feed the burndown chart.
  3. Set the capacity in story points
    A realistic guess of how much work the team can ship. The system will later warn you if you've planned more points than this capacity allows.
  4. Click Save — sprint opens automatically
    You're now in the detail view with an empty board. Time to fill it.
  5. Add issues — two ways to do it
    From backlog: click From backlog to pick existing issues across all sheets for this app. Tick the ones you want, click Add selected.
    Quick-add: click + Quick add to create a brand-new issue (bug, feature, task, chore…) and drop it into this sprint in one step.
  6. Assign each issue
    Click any card on the board to open the detail drawer. Set the assignee, estimate (story points), start date, and end date. Save closes the drawer and updates the card.
Example payload — what gets sent when you save a new sprint
POST /api/bonyad/sprints
{
  "name": "Sprint 12 — Auth hardening",
  "goal": "Ship 2FA + lock out brute-force attempts",
  "start_date": "2026-06-01",
  "end_date":   "2026-06-14",
  "capacity_points": 30,
  "status": "planning"
}
Good to know
Story points are an abstract measure of effort — not hours. A common scale is the Fibonacci sequence: 1, 2, 3, 5, 8, 13. A "1" is trivial. A "13" is a full week of complex work. The team learns its own scale over a few sprints.
Stage 2 of 5

Start the sprint — flip the switch to active

When you're happy with the planned work, click ▶ Start sprint. The status flips from planning to active and a few things turn on automatically:

  • The burndown chart starts plotting. Day 0 = total points on the sprint. The chart compares the ideal line (linear decrease to zero) against the actual remaining points.
  • The "+ Quick add" and "From backlog" buttons stay available, but added issues land in the Todo column instead of being treated as "planned scope."
  • Overdue checks become active. The moment any issue's due_date falls behind today, the card lights up red.
  • The hero stats strip (top of the detail page) updates: Planned points, Completed, Issues, Time left.
Once started, hard to undo cleanly
There's no "revert to planning" button. If you started a sprint by mistake, the cleanest recovery is to Cancel it and create a new planning sprint with the same issues.
Stage 3 of 5

Work the board — drag cards through four columns

This is where day-to-day work happens. The kanban board has four columns. Each card represents one issue. Drag a card from left to right as the work progresses.

flowchart LR
  classDef todo fill:#1a1720,stroke:#71717f,color:#cdcdd8,stroke-width:2px;
  classDef prog fill:#2b1f10,stroke:#ffb86b,color:#ffb86b,stroke-width:2px;
  classDef rev  fill:#1d172b,stroke:#a78bfa,color:#a78bfa,stroke-width:2px;
  classDef done fill:#0a2a20,stroke:#2ee6b8,color:#2ee6b8,stroke-width:2px;

  T["Todo
queued for work"]:::todo P["In progress
someone is actively working"]:::prog R["In review
code or design done
awaiting check
"]:::rev D["Done
shipped or merged"]:::done T --> P P --> R R --> D R -.changes requested.-> P
The reverse arrow from In review back to In progress is what makes the workflow honest — reviews aren't a rubber stamp. If a reviewer requests changes, the card goes back to In progress until those changes are made.

What the system tracks automatically when you drag

Every drag triggers a single API call: PATCH /api/bonyad/issues/<id>/assign with the new state. The server then auto-stamps lifecycle timestamps so you never have to remember when something started or finished.

first move to "in progress"
Server sets started_at = NOW(). This is the moment work actually began — used later for cycle-time reports.
move to "done"
Server sets completed_at = NOW() AND is_done = 1. The card now counts toward the sprint's "done points" total.
move backwards (review → progress)
Lifecycle timestamps are not reset — you don't lose the original started_at. Only forward transitions stamp timestamps.

What's on each card

Cards are designed to be readable at a glance. From top to bottom:

  • Issue ID + type (e.g. #42 · BUG) — the "eyebrow" row at the top
  • Title — the main bug/task title
  • Chips row:
    • Priority chip (high / medium / low) — colour-coded
    • Estimate chip (e.g. 3 pt) — only shown if estimate is set
    • Due-date chip — pulses red if overdue
    • Late-reason count chip — appears only if reasons have been filed
    • Sheet chip — which platform/area the issue belongs to
  • Footer row: comment count on the left, circular assignee avatar on the right (tinted by role)
Stage 4 of 5

Track progress — burndown + hero stats + filter chips

The detail page gives you three different reading angles on how the sprint is going. Use the one that matches the question you have right now.

Hero stats (top of the page)

Four large tiles, one number each. This is the "glance and go" view — pull these up during standup.

Planned points
Sum of estimates across all issues in the sprint. Compared to capacity (if set) — shows when you've over-planned.
Completed
Points in the Done column, plus a progress bar showing percentage of planned points shipped so far.
Issues
Count of done issues out of total issues. Tells you the number of cards shipped, separate from points.
Time left
Days remaining (or "X days ago" if you're past end_date). Auto-coloured — green when comfortable, orange when tight, red when overdue.

Filter chips (above the board)

Three filters narrow the board to whatever you're investigating:

  • Priority — High / Medium / Low / All
  • Assignee — pick a teammate or "Unassigned". Useful during standup ("show me Ali's cards")
  • Search box — free-text match against the card title. Live filter as you type.

Burndown chart (bottom of the page)

A line chart showing remaining points over time:

  • Grey dashed line = ideal burndown. A straight line from total points to zero.
  • Orange filled line = actual remaining points each day.
  • Vertical dashed "TODAY" line shows where you are in the sprint window.
  • Each data point is hoverable — the tooltip shows that day's remaining-points number.
How to read the burndown
If the orange line is above the grey line, you're behind. If it's below, you're ahead. If it's flat for several days, work isn't being marked done — investigate during standup.
Stage 5 of 5

Close the sprint — mark it complete

When the sprint window ends and (hopefully) the work is shipped, click ✓ Complete. The status moves to completed. From this point on:

  • The sprint becomes read-only in spirit (you can still view it, but it's frozen).
  • Its done-points number gets counted in future velocity reports.
  • Any unfinished issues stay on the board as a record — they don't disappear, but they don't count toward "completed" either.
  • The next sprint you create can pull those unfinished issues in via From backlog.
Coming in batch 3
A "Carry over unfinished" button on the Complete dialog will let you move all open issues into the next sprint in one click, so you don't have to re-add them manually.
When things slip · 1 of 3

What "overdue" actually means in Bonyad

An issue is overdue when all three of the following are true:

  1. It has a due_date set
  2. That date is before today (in the server's local time)
  3. The issue is not yet in done or cancelled state

The system checks this on every render — there's no separate cron job needed. As soon as the clock passes midnight on the due date, the next page load shows the overdue indicators. Three things change visually:

On the board card
Red right-edge bar appears, the due-date chip pulses red, and an OVERDUE eyebrow text shows next to the issue ID.
In the drawer
A red banner appears at the top of the late-reason panel — if you're the assignee, it asks you to file a reason.
On the sprint list card
A ⚠ N overdue pill appears next to the sprint name. The number is the count of currently-overdue issues in that sprint.
When things slip · 2 of 3

Filing a late reason — explain the slip

When work slips past its deadline, the assignee (or an admin) is required to file a short written reason. This isn't about blame — it's about capturing context so the team can spot patterns: "are scope creeps the issue? Dependencies? Estimation errors?"

flowchart TD
  classDef user   fill:#1a2540,stroke:#5eb3ff,color:#f4f4f8,stroke-width:2px;
  classDef decide fill:#161620,stroke:#71717f,color:#f4f4f8,stroke-width:2px,stroke-dasharray:5 4;
  classDef ok     fill:#0a3b30,stroke:#2ee6b8,color:#f4f4f8,stroke-width:2px;
  classDef sys    fill:#2a1a10,stroke:#ff6b1a,color:#f4f4f8,stroke-width:2px;
  classDef danger fill:#3d1717,stroke:#f87171,color:#f4f4f8,stroke-width:2px;

  A["Issue is overdue"]:::danger
  B["User opens the card"]:::user
  C{"Is this person the
assignee or admin?"}:::decide D["Read-only view
of past reasons"]:::user E["Submit form is visible
reason + optional new ETA"]:::user F["Submit"]:::user G["Server records reason
and updates due_date
if new ETA was given"]:::sys H["Telegram alert
fires to admin"]:::sys A --> B --> C C -- viewer only --> D C -- assignee or admin --> E --> F --> G --> H

Who is allowed to file a reason?

Only the issue's assignee or an admin. The check happens server-side in POST /api/bonyad/issues/<id>/late-reason, so even if a user pokes at the API directly, they can't bypass the rule.

What the form asks for

  • Reason (required) — a short text explanation. Be specific: "Stripe webhook signature failing under replay" beats "blocked." Max 4,000 characters.
  • New ETA (optional) — if you can commit to a new date, set it here. The server updates the issue's due_date in the same database transaction, so the card stops being overdue until the new date passes.
Everyone can read the history
Only assignees can file a reason, but anyone with access to the app can see the list of past reasons. This keeps the team honest and gives the admin full context without needing to ask "why is this still open?"

What gets stored

Row in bonyad_issue_late_reasons
{
  "id": 17,
  "issue_id": 42,
  "app_slug": "mafeshsho8l",
  "user_id": 5,
  "user_name": "Ali",
  "user_role": "developer",
  "old_due_date": "2026-05-25",
  "new_due_date": "2026-06-02",
  "reason": "Stripe webhook signature verification needs a rewrite — the existing implementation chokes on event replay.",
  "submitted_at": "2026-05-27T10:42:00Z"
}
When things slip · 3 of 3

The admin gets pinged on Telegram

The moment a late reason is filed, the server sends a Markdown-formatted message to the app's Telegram channel. The admin doesn't have to be watching the dashboard — the phone buzzes.

sequenceDiagram
  participant User as Assignee
  participant API as Bonyad API
  participant Notify as notifyApp()
  participant Env as Server config
  participant TG as Telegram cloud
  participant Phone as Admin's phone

  User->>API: POST /late-reason
{ reason, new_due_date } API->>API: Validate auth + scope
Insert row in DB API->>Notify: Call notifyApp(app_slug, message) Notify->>Env: Get bot token
Get chat_id for this app Env-->>Notify: Token + chat_id Notify->>TG: POST sendMessage
(chat_id, text, Markdown) TG-->>Notify: 200 OK Notify-->>API: Done API-->>User: 200 success TG-->>Phone: Push notification

How channels are routed per app

Each app has its own Telegram channel. Routing is done with environment variables on the Node.js server:

TELEGRAM_BOT_TOKEN
The bot's HTTP API token. One per workspace. Required.
TELEGRAM_CHAT_ID_BONYAD
Chat ID for the Bonyad channel.
TELEGRAM_CHAT_ID_MAFESHSHO8L
Chat ID for the Mafeshsho8l channel.
TELEGRAM_CHAT_ID_TAWAZON
Chat ID for the Tawazon channel.
TELEGRAM_CHAT_ID_DROOP
Chat ID for the Droop channel.
TELEGRAM_CHAT_ID
Global fallback. If a per-app one isn't set, alerts route here.

What the message looks like

Message rendered in Telegram (Markdown)
⏰ Late-reason filed — Mafeshsho8l
🔖 issue #42: Wire Stripe Checkout into onboarding
👤 by: Ali (developer)
📅 was due: 2026-05-25
📅 new ETA: 2026-06-02

> Stripe webhook signature verification needs
> a rewrite — the existing implementation chokes
> on event replay.
If the alert doesn't arrive
Check the env vars above are set in cPanel → Node.js app → Environment variables. The API never fails if Telegram is down or misconfigured — the request returns success and the alert is silently dropped. This means a missed alert is hard to detect from inside the app; check the Node server logs (logger.error lines starting with [TelegramWebhook]).
Reference

Who does what — roles and permissions

Bonyad has four built-in roles. The role you have is set on your user account in bonyad_users.role and decides what you can do in each part of the app.

Admin
  • Create / edit / delete any sprint
  • Edit any user's comment
  • File a late reason on any issue (not just their own)
  • Receives Telegram alerts for slips
  • Access to ?app=* by default
Developer
  • Pick up assigned issues
  • Drag own cards through the board
  • Edit own comments
  • File late reasons on issues assigned to them
  • Scoped to apps in their app_scope CSV
Designer
  • Same as Developer for sprints
  • Upload designs to /designs.html
  • Card avatar tinted violet on the board
  • Scoped to apps in their app_scope CSV
Tester
  • Same as Developer for sprints
  • Often moves cards back from "In review" to "In progress"
  • Card avatar tinted teal on the board
  • Scoped to apps in their app_scope CSV
Reference

What gets stored in the database per sprint

Behind the sprint UI sit four tables in MySQL. Knowing the shape helps you reason about what's tracked and what isn't.

How the tables relate
bonyad_sprints ─ 1 to many ─> bonyad_sprint_issues one sprint contains many issue links
bonyad_issues ─ 1 to many ─> bonyad_sprint_issues an issue can be in many sprints over time
bonyad_issues ─ 1 to many ─> bonyad_issue_late_reasons one issue can have many late-reason rows
bonyad_sprints main entity One row per sprint. Stores name, dates, status, capacity.
idPK
int
app_slug
string
name
string
goal
text
status planning | active | completed | cancelled
enum
start_date
date
end_date
date
capacity_points
int
created_by
int
created_at
timestamp
closed_at
timestamp
Joins tobonyad_sprint_issues (via id)
bonyad_sprint_issues join table Pure many-to-many bridge — which issues are in which sprints.
sprint_id PK FK
int
issue_id PK FK
int
added_by
int
added_at
timestamp
Bridge betweenbonyad_sprints & bonyad_issues
bonyad_issues main entity The work itself — a bug, feature, chore. Existed before sprints.
idPK
int
sheet_slug FK
string
title
string
priority
enum
state backlog | todo | in_progress | in_review | done | cancelled
enum
assignee_id FK
int
estimate_points
tinyint
start_date NEW
date
due_date
date
started_at
timestamp
completed_at
timestamp
is_done
bool
Joins tobonyad_sprint_issues & bonyad_issue_late_reasons
bonyad_issue_late_reasons slip log Every late-reason ever filed — written history of deadline slips.
idPK
bigint
issue_id FK
int
app_slug
string
user_id FK
int
user_name
string
user_role
string
old_due_date
date
new_due_date
date
reason
text
submitted_at
timestamp
Joins tobonyad_issues (via issue_id)
PKPrimary key FKForeign key NEWAdded in the latest batch enumAllowed values listed inline
Key design choice
Sprint membership lives in a join table (bonyad_sprint_issues), not as a foreign key on the issue itself. This is what lets the same issue belong to multiple sprints across time (carry-over) without rewriting the issue.
Reference

Glossary — terms used on this page

Sprint
A named, time-boxed batch of issues with start/end dates, a goal, and a capacity.
Issue
A single unit of work — a bug, feature, chore, research item, etc. Lives in a sheet, may live in zero or many sprints.
Sheet
A grouping of issues by platform or area (e.g. "Android", "iOS", "Backend"). Issues always belong to exactly one sheet.
Story point
An abstract estimate of effort, usually on a Fibonacci scale (1, 2, 3, 5, 8, 13).
Burndown
A daily chart of remaining points versus the ideal linear-decrease line.
Velocity
Average story points completed per sprint over the last N sprints. Used to plan capacity for the next sprint.
Cycle time
Average time between started_at and completed_at for completed issues. Tells you how long work actually takes.
app_slug
The per-app namespace identifier — bonyad, mafeshsho8l, tawazon, droop, or quickin.
app_scope
A CSV column on each user row listing which apps that user can access. * means everything.
Edit key
A secret value that proves you're a known user. Stored in the browser's localStorage and sent on every API call.