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.
/impeccable rebuild over the top so the UI is designed against real metrics.The current page and the data layer behind it — what we keep, what we replace.
engineering/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 componentsTEAM_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 modelgetIssueMilestones() 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 enginestartsAt; pg-backed cache (6h active, forever for past cycles). reuseGoals 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.
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.
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.
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.
How long issues spend in each status — grounded in the real Linear workflow, not the docs' idealized one.
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.
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.
| Bucket | Clock runs while in | Starts | Stops |
|---|---|---|---|
| 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.
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.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.
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:
← Back + large serif italic title, cache pill, refetch. keep, refineThe 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.
Everything this plan adds, in one place.
spGoalDefaultsspGoalOverridessp-goals GET·POST·DELETElinear-cto-cycletime GETcycle-time.ts — history → bucketssp-goals.ts — effective-goal resolverGoalsEditorCycleTimeSection + trend / WIP / drill-downThe open calls, now settled — folded into the sections above.
One effort, ordered so each step is shippable and verifiable on its own.
sp-goals route. Verify targets recompute against seeded goals.cycle-time.ts with the raw GraphQL history fetch + bucket math; unit-test the reset/loop/terminal edge cases on fixtures.page.tsx into sections, run the live visual rebuild over the whole page to the DESIGN.md system.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.