# SaaS kit: keep Issues board as owned five-column Kanban, not a ghost board

> Extend SaaS Dashboard kit Issues board as five-column Kanban with backlog and tasks seams so agents change status on owned issue rows — not a ghost board.
> By Dave · 2026-09-19
> Source: https://otf-kit.dev/blog/saas-kit-issues-board-kanban

The SaaS Dashboard kit ships an Issues board you can open on the live demo today: five status columns, drag cards between them, and the same issue rows show up in backlog and tasks views. That board only helps a production team if a card move updates one owned `issue` row — not a ghost board that lives in component state while your agent invents a second tracker beside the schema.

This post is that status write-path invariant. It is not the click-through product tour — that lives at [/blog/saas-kit-live-tour](/blog/saas-kit-live-tour). It is not Stripe Customer Portal for plan changes — that shipped earlier today at [/blog/saas-kit-stripe-customer-portal](/blog/saas-kit-stripe-customer-portal). It is not analytics charts you own — that is [/blog/how-to-build-an-analytics-dashboard-with-ai](/blog/how-to-build-an-analytics-dashboard-with-ai). It is not the Retool/Appsmith compare — that decision map is [/blog/otf-vs-retool-appsmith-dashboard](/blog/otf-vs-retool-appsmith-dashboard). And it is not the broader admin-panel assembly order — that is [/blog/how-to-build-an-admin-panel-with-ai](/blog/how-to-build-an-admin-panel-with-ai).

## What the kit already names for issues

OTF lists the SaaS Dashboard kit on [https://otf-kit.dev/templates/saas-dashboard](https://otf-kit.dev/templates/saas-dashboard) and documents screens, tables, and deploy on [https://otf-kit.dev/docs/templates/saas-dashboard](https://otf-kit.dev/docs/templates/saas-dashboard). Standalone price is $99; the kit is also in the Everything Bundle on [https://otf-kit.dev/pricing](https://otf-kit.dev/pricing). Live proof before buy sits at [https://saas.otf-kit.dev](https://saas.otf-kit.dev).

Kit docs name the issue surfaces explicitly:

- Issues board: `/home/issues-board` — Kanban with five status columns
- Issues backlog: `/home/issues-backlog` — drag-orderable backlog grouped by priority
- Tasks: `/home/tasks` — all issues as a record table plus an issue-detail sheet
- Projects: `/home/projects` and `/home/projects/:id` — list plus a project detail that includes team and tasks
- App tables that own the domain: `issue`, `label`, `issueLabel`, `comment`, `attachment` (alongside `workspace`, `team`, `teamMember`, `project`, `notification`, `savedView`, and related auth tables)
- Seed: `bun run db:seed` loads a deterministic 4-user / 5-project / 50-issue dataset
- Agent handoff: `CLAUDE.md`, `.cursorrules`, and 20+ tested prompts under `ai/prompts/` — including bounded jobs such as add a CRUD entity and add a screen
- Deploy: `bash scripts/deploy-railway.sh`

That is the product spine. The marketing page also lists issue tracking and a Kanban board among the shipped dashboard features. The kit does not claim a hosted Linear clone or a third-party project tool as a module. For the default loop, the invariant is simpler: a card on the board must be the same `issue` row backlog and tasks already read — never a parallel board store an agent invents after checkout.

## Why ghost boards fail teams (and agents)

Three failure modes show up the week after you buy:

1. **Board-only status** — drag updates local column state, refresh restores the seed status, and teammates see a different column than the person who moved the card.
2. **Parallel tracker invent** — an agent prompted with "add a Kanban" creates a second `cards` table beside `issue`, then backlog, tasks, and project detail diverge.
3. **Backlog and board as two products** — priority order on `/home/issues-backlog` writes one place while board columns write another, so "what's next" and "what's in progress" disagree.

The public Kanban component docs on [https://otf-kit.dev/docs/components/ui/advanced](https://otf-kit.dev/docs/components/ui/advanced) show the UI contract: five columns, drag-reorder, and an `onCardMove(cardId, toCol, toIndex)` callback. That callback is where honesty lives. If move only mutates component state, you shipped a demo. If move updates the owned `issue` status (and optional rank) the other screens already query, you shipped a product.

![Board, backlog, and tasks converge on one issue row](https://cdn.otf-kit.dev/blog/saas-kit-issues-board-kanban/inbody1-20260919d.png)

*Board, backlog, and tasks share one owned issue — not a parallel cards table.*


## The owned status contract: board, backlog, and tasks share one row

Treat the Issues board as a view over `issue`, not a separate product:

1. **Identity** — every card id is an `issue` id. Do not allocate a second card identity on drop.
2. **Status** — the five columns map to the issue status field the table and seed already understand. Kit docs describe the board as five status columns; the public Kanban component example uses Backlog, To do, In progress, Review, and Done as the five-column shape — keep one enum in schema, not five hard-coded strings copied into three screens.
3. **Move** — `onCardMove` persists status (and column index if you store rank). Idempotent: a second drop to the same column updates or no-ops; it never inserts a twin issue.
4. **Backlog seam** — `/home/issues-backlog` reorders by priority on the same rows. Priority changes do not invent a backlog-only entity.
5. **Tasks seam** — `/home/tasks` lists the same issues in a record table and opens the same detail sheet. Filters by project, status, and priority (as the live product page states for issue tracking) must hit the same columns the board writes.
6. **Project seam** — project detail shows team + tasks for that project; those tasks are still `issue` rows scoped by `project`, not a third store.

Illustrative shape (labeled sketch — not a private kit file dump):

```ts
// Illustrative board move — YOUR-PRODUCTION-DOMAIN API
type IssueStatus = "backlog" | "todo" | "doing" | "review" | "done";

async function onCardMove(
  issueId: string,
  toStatus: IssueStatus,
  toIndex: number
) {
  // Idempotent update on issue id — never INSERT a second row for a drag
  await api.patch(`/issues/${issueId}`, {
    status: toStatus,
    boardIndex: toIndex,
  });
  // Invalidate board + backlog + tasks queries that share this entity
}
```

Keep labels and comments on `label` / `issueLabel` / `comment`. When an agent wants a "simple tags array on the card," point it back at the join tables the seed already uses. Parallel tag strings are how filters and saved views rot.

## Keep ai/prompts as the extension path, not a second board

After clone, migrate, and seed (documented on the SaaS kit docs page), open Cursor or Claude Code on the buyer repo. Prefer a bounded prompt over "rebuild issue tracking":

```text
Read CLAUDE.md and ai/prompts/.
1) Keep board, backlog, and tasks on the issue table — do not invent a second tracker
2) Card move must PATCH status (and rank) by issue id — never double-insert
3) New fields use add-CRUD-entity / add-screen recipes (schema → route → hook → screen → sidebar)
4) Labels stay on label + issueLabel; comments stay on comment
5) Do not replace the five-column board with a third-party project SaaS embed
6) Prefer the existing /home/issues-board path over a parallel /kanban route
```

That prompt works because the kit already names the hard seams. You are extending a SaaS product, not interviewing the model about project-management information architecture. Preview the shipped board on [https://saas.otf-kit.dev](https://saas.otf-kit.dev) before you buy if you have not opened the demo login yet.

When you need a new entity beside issues (for example a customer request object that still lands on the board), use the add-CRUD-entity recipe: generate migration, add the server route under the kit's router conventions, copy a query hook with optimistic updates, wrap a screen, register the route, add the sidebar item. Do not paste a new board library next to the Kanban already documented in the component docs.

![Dragging a Kanban card persists status on the issue row](https://cdn.otf-kit.dev/blog/saas-kit-issues-board-kanban/inbody2-20260919d.png)

*onCardMove must persist status so refresh cannot resurrect the old column.*


## How to verify the invariant before you call it shipped

Run the checks a release engineer would run on day two:

| Check | Pass signal |
| --- | --- |
| Seed load | `bun run db:seed` yields ~50 issues across projects |
| Board move | Drag a card; refresh; column matches the persisted status |
| Backlog sync | Same issue's priority order survives reload |
| Tasks sync | Record table and detail sheet show the same status as the board |
| Agent restraint | A "add Kanban" prompt does not create a second cards table |
| Deploy path | `bash scripts/deploy-railway.sh` still deploys your clone, not a canvas |

If any row fails, fix the write path before you theme the columns. Pretty boards with dishonest status are worse than a plain table that tells the truth.

## How this fits the rest of OTF without confusing the SKU

SaaS Dashboard is the web product lane — auth, billing, dashboard KPIs, issues, analytics, projects, teams, settings — not a $9 landing template and not Fitness or Booking. Landing templates remain the marketing-site lane. Fitness-kit remains the wellness product lane. Booking-kit remains the appointment product lane. Full-stack kits remain the $99 product lane when you need auth, data, payments, and a deployable app wired together.

If you need the live click-through before purchase, read [/blog/saas-kit-live-tour](/blog/saas-kit-live-tour). If you need self-serve plan changes after Checkout, read [/blog/saas-kit-stripe-customer-portal](/blog/saas-kit-stripe-customer-portal). If you need analytics ownership, read [/blog/how-to-build-an-analytics-dashboard-with-ai](/blog/how-to-build-an-analytics-dashboard-with-ai). If you still need the internal-tools versus product-repo decision, read [/blog/otf-vs-retool-appsmith-dashboard](/blog/otf-vs-retool-appsmith-dashboard) once. If you need the wider admin assembly order (roles → tables → audit), read [/blog/how-to-build-an-admin-panel-with-ai](/blog/how-to-build-an-admin-panel-with-ai).

When the issue loop matches — five-column board, priority backlog, tasks table, owned `issue` rows, agent recipes that extend instead of regenerate — start on [https://otf-kit.dev/templates/saas-dashboard](https://otf-kit.dev/templates/saas-dashboard), open the live demo, and buy when the screen set matches. Keep Issues board as owned Kanban. Leave ghost boards and parallel trackers on the scrap heap.

## Sources

SaaS Dashboard kit product page: https://otf-kit.dev/templates/saas-dashboard

SaaS Dashboard kit docs (screens, tables, seed, deploy): https://otf-kit.dev/docs/templates/saas-dashboard

Live SaaS demo: https://saas.otf-kit.dev

Kanban component docs (five-column board + onCardMove): https://otf-kit.dev/docs/components/ui/advanced

Pricing: https://otf-kit.dev/pricing

Templates catalog: https://otf-kit.dev/templates

Related: SaaS kit live tour — https://otf-kit.dev/blog/saas-kit-live-tour

Related: SaaS kit Stripe Customer Portal — https://otf-kit.dev/blog/saas-kit-stripe-customer-portal

Related: How to build an analytics dashboard with AI — https://otf-kit.dev/blog/how-to-build-an-analytics-dashboard-with-ai

Related: OTF vs Retool / Appsmith — https://otf-kit.dev/blog/otf-vs-retool-appsmith-dashboard

Related: How to build an admin panel with AI — https://otf-kit.dev/blog/how-to-build-an-admin-panel-with-ai
