Work

02 KYRO

KYRO

Case file 02

A Linear-style issue tracker, built to try ideas

KYRO is an issue tracker I built for myself: issues and projects, human-readable keys like ENG-42, a drag-and-drop board, sprints, and a home page for what to do next. The point is to try new ideas and see how a tracker could be more useful.

Year
2026
Role
Solo: product, design, code
Stack
Next.js, Supabase, Drizzle
Built in
32 commits, 10 PRs
Status
Live
KYRO's Home page: the current sprint's progress, then in-progress and up-next issues
Home: the Mine view

Why

A playground for my own ideas: what would make an issue tracker more useful?

What I built

Issues, projects, a kanban board, workspaces with invites, sprints that roll forward on their own, and a Mine / Team home.

Where it is

Live, with open sign-up. Anyone can create a workspace and try it.

Why it exists

A tracker nobody asked for

I'm the only user. KYRO is where I try out ideas and see what makes a tracker more useful.

Placeholder: what got you started, and what you wanted to explore that existing trackers didn't give you.

Key decisions

The calls behind the features

From the decision log I kept while building, written down after each feature shipped.

  1. decision 1

    How should sprints start and roll forward?

    A background cron job

    The app has no job runner or queue to run it on.

    Reconcile when a page loadswent with this

    The same way the current cycle was already worked out live elsewhere in the app.

    An admin sets a cadence once (for example, 2-week sprints starting Monday). On page load, the app tops up the queue of planned sprints and activates the one that's due. Activation uses up a planned slot, so it generates, activates, then generates again to keep the count right.

    The costNo true background schedule. A Vercel Cron upgrade is noted for later, not built.

  2. decision 2

    Should an overdue sprint close itself?

    Auto-complete it

    Its unfinished issues would be dealt with without anyone deciding.

    A person completes itwent with this

    Move unfinished issues to the next sprint, back to the backlog, or review them one by one.

    Completing a sprint needs a human decision about the issues that didn't get done, so reconciliation activates sprints but never completes one.

  3. decision 3

    Who can configure sprints and see the Team view?

    A new Team entity or capability flag

    A much larger, separate piece of work.

    The existing owner and admin roleswent with this

    Reused for both sprint settings and Team visibility.

    The app has no finer-grained permission system yet to extend, and building one was out of scope.

  4. decision 4

    What about mock-up buttons with nothing behind them?

    Half-build them

    Nudge, Escalate, Reassign, Rebalance and Assign would ship as dead clicks.

    Skip them entirelywent with this

    Leave them out until the workflows exist.

    They implied workflows this app doesn't have, like notifications or escalation.

  5. decision 5

    Blocked tickets: ship the schema now and the UI later?

    Schema only, populate later

    Smaller change now.

    A minimal mark and clear control nowwent with this

    Plus a blocked reason and date on each issue.

    Without a way to mark an issue blocked, the Blocked section could never show real data. It's a new flag: the existing “blocks” relation between issues is a different concept.

  6. decision 6

    Ticket detail: replace the full page with a side panel?

    Replace the full page

    One way to see a ticket.

    Add a panel alongside itwent with this

    The full page stays, reachable from an “Open full page” link in the panel.

    The full page's data fetching was pulled out into one shared module, so the page and the panel use the same source of truth. Checking one ticket no longer means leaving Home.

  7. decision 7

    Advanced fields when creating an issue: collapsed or visible?

    Collapsed under “Advanced”

    What the spec said.

    Every field visiblewent with this

    Estimate, due date, cycle, milestone, parent, relations and attachments, all on the form.

    I reversed my own spec mid-build. Placeholder: why visible won.

Honest substitutes

When the mock-up wanted data the app didn't have

The Home redesign mock-up had fields with nothing real behind them. Each got an honest substitute instead of made-up numbers.

Points added mid-sprint

Approximated from each issue's creation date compared with the sprint's start date.

Reviews waiting

The app has no review concept, so it became a “stuck” signal: issues sitting idle in progress.

On leave

Dropped entirely. Nothing in the app tracks leave.

How it got built

Spec, plan, decision log, then code

Each big feature started as a spec file. PLAN.md tracked what was built and what's next, and FEATURE_LOG.md recorded why.

  1. 2–3 Jul 2026

    The core tracker

    Issue keys and numbering, the issues list and creation, issue detail with inline edits and comments, projects, the kanban board and the home briefing. Then members and invites, with an invitation-received UI (PR #3, #4).

  2. 5 Jul 2026

    Multiple workspaces

    The multiple-workspaces feature and a workspaces page (PR #5).

  3. 8–9 Jul 2026

    Theme, advanced create and profiles

    Advanced issue creation, user profiles with onboarding, then logos and a switch to a green theme (PR #6, #7).

  4. 24 Aug 2026

    Sprints

    Cadence settings, a sprints hub and a planning board with bulk assign (PR #8).

  5. 6 Sep 2026

    Home redesign

    Mine and Team views, the ticket side panel, keyboard shortcuts, and a new background on the top section (PR #9, #10).

What broke

Bugs worth remembering

The panel wouldn't close

Closing updated local state, but the URL never changed: router.push silently did nothing on a search-param-only change. Switched to router.replace, which also keeps panel toggles out of browser history.

Out of database connections

postgres.js opens 10 connections per process and the Supabase session pooler allows 15 in total, so dev, prod and scripts ran it out. Capped the pool at 5.

Dates broke raw SQL

Raw SQL fragments threw on Date parameters with prepare turned off. They now bind ISO strings.

The product

What it looks like

The issues list with filters for status, priority, project, label, assignee and sprint
Issues, with keys like PAT-8 and filters down to the sprint
The sprints hub: the current cycle's progress, three upcoming sprints and completed ones
Sprints: the current one, the queue that tops itself up, and past ones
An issue's detail view with a side column for status, blocked, priority, assignee, sprint and more
Issue detail, with the new Mark as blocked control
The issues list in the light theme with the sidebar collapsed to icons
The same list in the light theme
The workspace picker, listing one workspace as owner and one as member
Workspaces: one account, several teams
Profile settings with avatar upload, name, username, bio, timezone and job title
Profile settings
KYRO's log-in screen on a dark, softly lit background
Log in, with open sign-up

Still open

Known gaps

  1. 01

    Board order

    Board positions are fractional numbers that need renormalizing after many drops in the same slot. Not built yet.

  2. 02

    Pagination

    Nothing past 100 issues in the list or 300 on the board.

  3. 03

    Issue key prefix

    Taken from the workspace name, and not yet editable in settings.

  4. 04

    Scheduled sprints

    Sprints roll forward on page load; a Vercel Cron job is the noted upgrade.

What I learned

What I learned

Placeholder: what building KYRO taught you, and what you'd try next.

Back to all work