Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 55 additions & 38 deletions src/components/app-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Link, useRouterState } from "@tanstack/react-router";
import { ChevronDown, ChevronRight, User, Users } from "lucide-react";
import {
ChevronDown,
ChevronRight,
Megaphone,
User,
Users,
} from "lucide-react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";

Expand Down Expand Up @@ -59,7 +65,11 @@ function PersonNode({
style={{ paddingLeft: `${0.5 + depth * 0.875}rem` }}
>
{hasReports ? (
open ? <ChevronDown /> : <ChevronRight />
open ? (
<ChevronDown />
) : (
<ChevronRight />
)
) : (
<span className="w-4 shrink-0" />
)}
Expand Down Expand Up @@ -98,10 +108,10 @@ export function AppSidebar() {
<Sidebar>
<SidebarHeader>
<div className="flex items-center gap-2 px-2 py-1.5">
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex size-7 items-center justify-center rounded-md font-semibold">
<div className="flex size-7 items-center justify-center rounded-md bg-sidebar-primary font-semibold text-sidebar-primary-foreground">
I
</div>
<span className="text-sidebar-foreground font-semibold tracking-tight">
<span className="font-semibold tracking-tight text-sidebar-foreground">
{t("common.app_name")}
</span>
</div>
Expand All @@ -113,49 +123,56 @@ export function AppSidebar() {
<DevImpersonationHint />
) : viewer ? (
<SidebarMenu>
<PersonNode
node={viewer}
depth={0}
activeEmail={activeEmail}
/>
<PersonNode node={viewer} depth={0} activeEmail={activeEmail} />
</SidebarMenu>
) : null}
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
isActive={pathname === "/whats-new"}
render={<Link to="/whats-new" />}
>
<Megaphone />
<span>{t("whats_new.nav_label")}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<SidebarV2Settings />
<ThemeSwitcher />
{viewerEmail ? (
(() => {
const primaryEmail = viewer?.email ?? viewerEmail;
const primary = viewer?.display_name || primaryEmail;
const showSecondary = primary !== primaryEmail;
return (
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" className="cursor-default">
<Avatar className="size-8 shrink-0">
<AvatarFallback className="bg-sidebar-primary text-sidebar-primary-foreground text-xs font-semibold">
{getInitials(primary) || "?"}
</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col leading-tight">
<span className="text-sidebar-foreground truncate text-sm font-medium">
{primary}
</span>
{showSecondary ? (
<span className="text-sidebar-foreground/60 truncate text-xs">
{primaryEmail}
{viewerEmail
? (() => {
const primaryEmail = viewer?.email ?? viewerEmail;
const primary = viewer?.display_name || primaryEmail;
const showSecondary = primary !== primaryEmail;
return (
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton size="lg" className="cursor-default">
<Avatar className="size-8 shrink-0">
<AvatarFallback className="bg-sidebar-primary text-xs font-semibold text-sidebar-primary-foreground">
{getInitials(primary) || "?"}
</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col leading-tight">
<span className="truncate text-sm font-medium text-sidebar-foreground">
{primary}
</span>
) : null}
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
);
})()
) : null}
{showSecondary ? (
<span className="truncate text-xs text-sidebar-foreground/60">
{primaryEmail}
</span>
) : null}
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
);
})()
: null}
</SidebarFooter>
</Sidebar>
);
Expand Down
85 changes: 85 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,91 @@
"label": "New metrics UI"
}
},
"whats_new": {
"nav_label": "What's new",
"eyebrow": "Insight · What's new",
"title": "What's new — 13 July 2026",
"lead_html": "This update makes your dashboards <strong>more complete and more accurate</strong>. Data now flows and is attributed to the right people across Git, Jira, collaboration and Zoom, and several metrics that were showing wrong or empty values have been corrected. No new screens this time — everything below improves what you already use.",
"stamp": {
"highlights_label": "Highlights",
"highlights": "9 improvements",
"focus_label": "Focus",
"focus": "data accuracy & completeness"
},
"in_short_html": "<strong>In short:</strong> more of your team's activity now shows up correctly — Bitbucket reviews, Jira task delivery, collaboration and Zoom meetings are back and attributed properly, long-range Git charts are readable, and AI cost is shown as real money.",
"improvements_label": "Improvements you'll notice",
"items": {
"direct_reports_toggle": {
"category": "Team dashboards",
"title": "“Direct reports only” toggle is back",
"description_html": "Filter a team down to just your direct reports — members, heatmap and metrics all update together. <strong>On by default,</strong> and hidden automatically for teams without sub-teams, where it had no effect."
},
"member_expand_full_metrics": {
"category": "Team dashboards",
"title": "Full metrics when you expand a team member",
"description_html": "Expanding a person now opens their <strong>complete metric set</strong> instead of a trimmed subset — so you can assess someone without switching screens."
},
"bitbucket_prs": {
"category": "Git & code reviews",
"title": "Bitbucket pull requests now counted",
"description_html": "Pull-request activity from Bitbucket is attributed to the right person. <strong>Review metrics that used to show zero</strong> now reflect real work."
},
"consistent_git_metrics": {
"category": "Git & code reviews",
"title": "Consistent commit & lines-of-code metrics",
"description_html": "Commit and LOC figures now come from a single shared source, so <strong>Git activity is reported the same way across connectors.</strong>"
},
"readable_git_charts": {
"category": "Git & code reviews",
"title": "Readable quarterly & yearly Git charts",
"description_html": "Long-range charts no longer repeat the same label over and over. <strong>Quarterly and annual trends are now legible.</strong>"
},
"jira_task_delivery": {
"category": "Task delivery",
"title": "Jira Task Delivery metrics now populate",
"description_html": "Closed tasks are detected by their status <i>category</i> rather than English-only status names, so <strong>non-English and custom “Done” workflows report correctly.</strong>"
},
"zoom_data": {
"category": "Collaboration",
"title": "Zoom meeting data restored",
"description_html": "The Zoom sync was hitting an API limit and stalled for about ten days. It's fixed, so <strong>meeting activity repopulates</strong> going forward."
},
"ai_adoption_graphs": {
"category": "AI adoption",
"title": "AI adoption graphs fixed",
"description_html": "AI-adoption graphs now show <strong>real distributions with honest “no data” states</strong> instead of blanks or misleading zeros."
},
"claude_code_cost": {
"category": "AI adoption",
"title": "Claude Code cost shown as currency",
"description_html": "The “Claude Code Cost” metric now displays as <strong>money</strong> instead of a raw number of cents — no more figures that looked inflated."
}
},
"gaps": {
"title": "Still on our list",
"items": {
"identity": "<strong>People without a matching email</strong> aren't attributed yet — this can still leave some Bitbucket and Jira metrics incomplete. Identity resolution is in progress.",
"gitlab_loc": "<strong>GitLab lines-of-code</strong> still shows 0. The commit and LOC fix above applies elsewhere; GitLab LOC is being worked on.",
"jira_kpis": "<strong>Some Jira KPIs</strong> may still be blank or slow to load on freshly set-up environments."
}
},
"coming": {
"label": "Coming next",
"items": {
"metrics_engine": {
"category": "Platform",
"title": "One accurate metrics engine",
"description": "Bringing all metric families onto a single engine, with correct “no data” states in place of misleading zeros everywhere."
},
"people_matching": {
"category": "Platform",
"title": "Better people matching",
"description": "Attributing work to the right person even across multiple or missing email addresses — the root cause behind the gaps above."
}
}
},
"footer": "Insight · what's new · 13 July 2026"
},
"auth": {
"callback": {
"completing": "Completing sign-in...",
Expand Down
21 changes: 21 additions & 0 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as WhatsNewRouteImport } from './routes/whats-new'
import { Route as CallbackRouteImport } from './routes/callback'
import { Route as IndexRouteImport } from './routes/index'
import { Route as IcPersonRouteImport } from './routes/ic.$person'
import { Route as IcPersonIndexRouteImport } from './routes/ic.$person.index'
import { Route as IcPersonTeamRouteImport } from './routes/ic.$person.team'
import { Route as IcPersonPersonalRouteImport } from './routes/ic.$person.personal'

const WhatsNewRoute = WhatsNewRouteImport.update({
id: '/whats-new',
path: '/whats-new',
getParentRoute: () => rootRouteImport,
} as any)
const CallbackRoute = CallbackRouteImport.update({
id: '/callback',
path: '/callback',
Expand Down Expand Up @@ -50,6 +56,7 @@ const IcPersonPersonalRoute = IcPersonPersonalRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/callback': typeof CallbackRoute
'/whats-new': typeof WhatsNewRoute
'/ic/$person': typeof IcPersonRouteWithChildren
'/ic/$person/personal': typeof IcPersonPersonalRoute
'/ic/$person/team': typeof IcPersonTeamRoute
Expand All @@ -58,6 +65,7 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/callback': typeof CallbackRoute
'/whats-new': typeof WhatsNewRoute
'/ic/$person/personal': typeof IcPersonPersonalRoute
'/ic/$person/team': typeof IcPersonTeamRoute
'/ic/$person': typeof IcPersonIndexRoute
Expand All @@ -66,6 +74,7 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/callback': typeof CallbackRoute
'/whats-new': typeof WhatsNewRoute
'/ic/$person': typeof IcPersonRouteWithChildren
'/ic/$person/personal': typeof IcPersonPersonalRoute
'/ic/$person/team': typeof IcPersonTeamRoute
Expand All @@ -76,6 +85,7 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| '/callback'
| '/whats-new'
| '/ic/$person'
| '/ic/$person/personal'
| '/ic/$person/team'
Expand All @@ -84,13 +94,15 @@ export interface FileRouteTypes {
to:
| '/'
| '/callback'
| '/whats-new'
| '/ic/$person/personal'
| '/ic/$person/team'
| '/ic/$person'
id:
| '__root__'
| '/'
| '/callback'
| '/whats-new'
| '/ic/$person'
| '/ic/$person/personal'
| '/ic/$person/team'
Expand All @@ -100,11 +112,19 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
CallbackRoute: typeof CallbackRoute
WhatsNewRoute: typeof WhatsNewRoute
IcPersonRoute: typeof IcPersonRouteWithChildren
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/whats-new': {
id: '/whats-new'
path: '/whats-new'
fullPath: '/whats-new'
preLoaderRoute: typeof WhatsNewRouteImport
parentRoute: typeof rootRouteImport
}
'/callback': {
id: '/callback'
path: '/callback'
Expand Down Expand Up @@ -169,6 +189,7 @@ const IcPersonRouteWithChildren = IcPersonRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
CallbackRoute: CallbackRoute,
WhatsNewRoute: WhatsNewRoute,
IcPersonRoute: IcPersonRouteWithChildren,
}
export const routeTree = rootRouteImport
Expand Down
7 changes: 7 additions & 0 deletions src/routes/whats-new.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";

import { WhatsNewScreen } from "@/screens/whats-new";

export const Route = createFileRoute("/whats-new")({
component: WhatsNewScreen,
});
Loading
Loading