Aleem Dashboard · Engineering

CTO Dashboard — Rebuild

Rebuild /engineering/reports from the ground up: editable, history-aware SP goals; a new cycle-time engine measuring how long work sits in each status; and a fresh /impeccable visual pass over the whole page.

Scope  3 workstreams, one effort Surface  src/app/(app)/engineering/reports Data  Linear (cycles · issues · state history) Status  Draft for review
Cycle-time population
Shipped per cycle — Done only — + live WIP aging
Only issues that reached Done count; Canceled / Duplicate are excluded so they can't drag the averages. Plus a live "how long has this been sitting" view for open work.
Headline metrics
Intake latency · Todo → Done
Two headliners per cycle. Pickup, dev dwell, review, rework & merge waits expand underneath (progressive disclosure) as the Todo→Done decomposition.
SP goal model
Effective-dated default + per-day overrides
Default daily goal per person (changes preserved over time); any single day overridable, e.g. 0 for a sick day.
Delivery
All three, one build
Data + API first, then the /impeccable rebuild over the top so the UI is designed against real metrics.
0

Where we're starting

The current page and the data layer behind it — what we keep, what we replace.

  • The pageengineering/reports/page.tsx is one 1,000-line client component: cycle navigator, 3 summary cards, triage inbox, and per-person sections (stacked SP-by-team bars + task list + rollover). refactor into components
  • Daily goals are hardcoded in TEAM_MEMBERS (Temir 14, Tima 8, Niyaz 8, Nurakhmet 0). Historical cycles render with today's number, not the goal that was actually in effect. replace with DB model
  • State history already reachablegetIssueMilestones() reconstructs status transitions with timestamps; today it's lazy-loaded per issue on click and still references the dead "Sent to QA" state. promote to a real engine
  • Cycle model is solid — IELTS drives the navigator, sibling cycles matched per team by startsAt; pg-backed cache (6h active, forever for past cycles). reuse
A

Editable SP goals, with history

Goals change week to week, and a single day can differ (sick day, half day). The dashboard must show the goal that was in effect — not the current one — when you look back.

A1Data model new

Two small tables. Defaults are effective-dated so changing someone's weekly goal never rewrites the past. Overrides handle one-off days and carry a note so "why was Tuesday a 0?" answers itself.

// src/lib/db/schema.ts — keyed by email prefix (same keys as TEAM_MEMBERS)
spGoalDefaults(
  id           serial pk,
  team_member  varchar(100)  // "temirlan.temirbayev"
  daily_goal   integer,
  effective_from date,        // default applies from this date forward
  updated_at   timestamp,
  unique(team_member, effective_from)
)
spGoalOverrides(
  id           serial pk,
  team_member  varchar(100),
  date         date,          // the specific work-day
  daily_goal   integer,       // 0 = sick / off
  note         varchar(200),  // "sick", "half day", "on-call"
  updated_at   timestamp,
  unique(team_member, date)
)

Effective goal for (person, day) resolves in order:

override(person, day)
  ?? (isWorkday(day) ? latestDefault(person, effectiveFrom ≤ day) : 0)
  ?? hardcodedFallback(person)   // migration safety net

isWorkday = not Sunday (keeps the current Mon–Sat default); an override can still force any value on any day, including a Sunday or a 0 on a workday. Cycle target = sum of effective goals over the cycle's days, replacing today's dailyGoal × workDays.

A2API new

  • GET /api/analytics/sp-goals?from&to → per-person effective goal per day across the range, plus the raw defaults & overrides so the editor can render exact state.
  • POST /api/analytics/sp-goals → upsert one of {type:"default", teamMember, dailyGoal, effectiveFrom} or {type:"override", teamMember, date, dailyGoal, note}.
  • DELETE /api/analytics/sp-goals → drop an override (revert that day to its default).

Mutations are admin-gated by the existing dashboard_auth cookie. Writing a goal invalidates the affected cycle's cached payload so the target recomputes.

A3Editing UI new

An "Edit goals" affordance on each person's section opens an inline editor: a default field (with an effective-from date) and a per-day grid for the visible cycle where any day can be overridden, with an optional note. Saving writes through A2 and the per-person target + the daily reference line update in place. Past cycles render read-only against the historical effective goals.

B

Cycle time

How long issues spend in each status — grounded in the real Linear workflow, not the docs' idealized one.

The pipeline we're measuring

Live states pulled from all four teams. In Progress / In Review / Changes Requested / Approved are all type started — distinguished by name. Backlog and Needs Clarification are both type backlog: parked. Only Done counts as a terminal for stats — issues ending in Canceled / Duplicate are dropped entirely.

① Intake latency  Triage → accepted ② Todo → Done  committed delivery time
Triage① Intake
Backlog / Needs Clarificationparked · resets
TodoPickup
In ProgressDev (dwell)
In ReviewReview
Done② ends here

Headliners: ① Intake = Triage dwell; ② Todo→Done = most-recent Todo entry → Done, decomposing on expand into Pickup + Dev + Review + Rework wait + Merge latency. Loops: Changes Requested → In Progress → In Review re-enters Dev and Review — both buckets sum every visit. Reset: a trip through Backlog or Needs Clarification zeroes the Pickup clock and restarts ② — only the most recent Todo entry counts. Dropped: Canceled / Duplicate issues are excluded from all stats.

B1Bucket definitions

BucketClock runs while inStartsStops
Intake latency headline ① Triage enters Triage leaves to Backlog / Todo
Todo → Done headline ② Todo … Done span most recent Todo entry reaches Done
Pickup latency expand Todo most recent Todo entry enters In Progress  (backlog resets)
Dev time (actual) expand In Progress enters In Progress leaves In Progress  (dwell, sums visits)
Review time expand In Review enters In Review → Approved / Changes Requested  (sums visits)
Rework wait expand Changes Requested enters Changes Requested leaves (→ In Progress / In Review)
Merge latency expand Approved enters Approved reaches Done

Two numbers headline each cycle — ① Intake and ② Todo→Done. The rest progressively disclose as ②'s decomposition. Every dwell bucket sums all intervals in that status (rework loops count each visit). Dev time is In Progress dwell specifically — the "actual coding" slice, not the whole span. Population is Done-only: issues ending Canceled / Duplicate are dropped so they don't drag the average.

B2Population & aggregation

  • Historical (per cycle) — issues that reached Done inside the cycle window, attributed to the cycle they shipped in. Canceled / Duplicate dropped entirely. Full history reconstructed per issue → the two headliners + decomposition.
  • Live WIP aging survivorship-bias antidote — for the active cycle, every still-open issue's time in its current status, surfacing the most-aged items ("RING-42 in Review 6d"). Plus the current Triage queue's age. This catches bottlenecks the Done-only view hides.
  • Median + p75 — cycle times are right-skewed; median is the headline, p75 the tail signal. Mean would be dragged by outliers (another reason to drop Canceled/Duplicate).
  • Per-cycle delta — each cycle compares against the previous cycle (green/rose), matching the dashboard's inline period-over-period pattern.

B3Visualization new

  • Per-cycle overview card — the two headline numbers (Intake, Todo→Done) with median + p75 and a delta vs the previous cycle; a small horizontal bar decomposes Todo→Done into Pickup / Dev / Review / Rework / Merge on expand.
  • Trend graph — last 8 cycles; stacked median-decomposition bars (or a Todo→Done line) so a creeping Review or Rework time is visible at a glance.
  • WIP aging panel — active cycle only; aged open issues grouped by status, worst first.
  • Drill-down — per-issue table (each bucket + the two headliners) behind the card for when a number looks off.

B4How it's computed (performance)

Buckets need each completed issue's full transition history. The SDK's lazy relations (await entry.toState per row) would explode into N+1 calls per cycle, so the engine issues one raw GraphQL query per team-cycle that returns issues with their history and state names inline:

// one round-trip per team-cycle via client.client.rawRequest(...)
issues(filter:{ cycle:{id:{eq:$c}}, state:{type:{eq:"completed"}} }){
  nodes{ id identifier estimate
    history(first:50){ nodes{
      createdAt
      fromState{ name type }
      toState  { name type }
    }}
  }
}

Per-cycle summaries are cached in the existing pg cache — forever for past cycles, 6h for the active one — so the trend graph just stitches cached summaries and stays cheap. A dedicated /api/analytics/linear-cto-cycletime endpoint keeps the main dashboard's first paint off the history fetch. Calendar time (incl. nights/weekends) by design — the clock not stopping at 6pm is the point: it pressures closing issues rather than leaving them open at EOD.

C

The /impeccable rebuild

Designed against the real metrics above, to the DESIGN.md system — light Stripe/Linear theme, Instrument Serif title, one purple accent, tabular numbers, restraint.

Page flow, top to bottom:

  • Header← Back + large serif italic title, cache pill, refetch. keep, refine
  • Cycle navigator — prev/next, active badge. keep
  • Summary cards — triage processed, team SP vs target, issues completed; add the cycle-time headliners (Intake · Todo→Done). extend
  • SP throughput — per-person stacked bars + task lists, now reading goals from the new model, with the Edit goals editor. goals editor
  • Cycle Time section — overview card · trend graph · WIP aging · drill-down. new

The monolithic page.tsx gets split into composable section components (SummaryCards, PersonSection, GoalsEditor, CycleTimeSection, …) so the page reads top-down and each piece is testable. /impeccable runs live against the running dashboard for the visual pass; that's the last step, after the data is real.

D

New surfaces, at a glance

Everything this plan adds, in one place.

Database
  • spGoalDefaults
  • spGoalOverrides
API routes
  • sp-goals  GET·POST·DELETE
  • linear-cto-cycletime  GET
Lib
  • cycle-time.ts — history → buckets
  • sp-goals.ts — effective-goal resolver
  • retire dead "Sent to QA" refs
Components
  • GoalsEditor
  • CycleTimeSection + trend / WIP / drill-down

Decisions, resolved

The open calls, now settled — folded into the sections above.

1
"Spec latency" is named Intake latency. Triage dwell — the lead's handling time on a raw request.
2
Two headliners: Intake latency + Todo → Done. Dev time is In Progress dwell ("actual coding"); Todo→Done is the full committed-delivery span from the most-recent Todo entry. The rest progressively disclose underneath.
3
Gap buckets in the progressive disclosure. Rework wait (Changes Requested) and Merge latency (Approved) live in the expand, decomposing Todo→Done.
4
Last 8 cycles + delta vs the previous cycle. Trend graph spans 8; each card carries a period-over-period delta.
5
Calendar time. Clock runs through nights/weekends on purpose — pressure to not leave issues open at EOD.
+
Population is Done-only. Canceled / Duplicate issues are excluded from every statistic.

Build sequence

One effort, ordered so each step is shippable and verifiable on its own.

  1. SP goals data + API — migrate the two tables, build the effective-goal resolver and the sp-goals route. Verify targets recompute against seeded goals.
  2. Goals editor UI — wire the inline editor into the existing person sections; confirm history (past cycles show historical goals).
  3. Cycle-time enginecycle-time.ts with the raw GraphQL history fetch + bucket math; unit-test the reset/loop/terminal edge cases on fixtures.
  4. Cycle-time endpoint + cache — per-cycle summaries, cached; assemble the trend; add live WIP aging for the active cycle.
  5. Cycle-time UI — overview card, trend graph, WIP panel, drill-down.
  6. /impeccable pass — split page.tsx into sections, run the live visual rebuild over the whole page to the DESIGN.md system.
No production or destructive commands; migrations run via drizzle-kit against the dev DB first. This repo is exempt from the Closes TEAM-XXX footer hook. /impeccable is invoked at step 6 — once the UI has real metrics to design against.
CTO Dashboard rebuild · draft plan docs/plans/cto-dashboard-rebuild.html