feat: a home page with life

Greeting and summary of the day, the coming week, the latest across my
projects, who is online now, the week in numbers (mine and the team's,
with a streak), sprints under way, «Retomar», project cards with their
activity, what to pick up when nothing is mine and first steps for
newcomers. REST (GET /api/me/home), GraphQL (myHome) and MCP
(get_my_week).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 19:20:41 +02:00
parent affce615a7
commit 0946e9517d
57 changed files with 3728 additions and 53 deletions

View File

@@ -706,6 +706,7 @@ export type ReleaseFieldsFragment = {
export type ActivityFieldsFragment = {
id: string;
type: string;
projectId: string;
issueId: string | null;
issueKey: string | null;
summary: string;
@@ -2142,6 +2143,73 @@ export type PortfolioQuery = {
}>;
};
export type MyHomeQueryVariables = Exact<{ [key: string]: never }>;
export type MyHomeQuery = {
myHome: {
agenda: Array<{
day: string;
kind: string;
projectKey: string;
title: string;
issueKey: string | null;
}>;
activity: Array<{
id: string;
type: string;
projectId: string;
issueId: string | null;
issueKey: string | null;
summary: string;
occurredAt: string;
actor: { id: string; name: string };
}>;
team: { perDay: Array<number>; thisWeek: number; lastWeek: number };
me: { thisWeek: number; lastWeek: number; streak: number };
sprints: Array<{
projectKey: string;
projectName: string;
sprint: {
sprintId: string;
name: string;
startDate: string;
endDate: string;
totalDays: number;
daysLeft: number;
tickets: number;
doneTickets: number;
points: number;
donePoints: number;
pacePerDay: number | null;
neededPerDay: number | null;
behind: boolean;
};
}>;
projects: Array<{
projectId: string;
open: number;
overdue: number;
activity: Array<number>;
sprint: { name: string; percent: number; behind: boolean } | null;
}>;
suggestions: Array<{ kind: string; projectKey: string; count: number }>;
firstSteps: {
project: boolean;
invited: boolean;
ticket: boolean;
calendar: boolean;
};
online: Array<{
projectKey: string;
issueKey: string | null;
issueTitle: string | null;
editing: boolean;
at: string;
user: { id: string; name: string };
}>;
};
};
export type ReleasesQueryVariables = Exact<{
idOrKey: string;
}>;
@@ -2198,6 +2266,7 @@ export type ActivityQuery = {
activity: Array<{
id: string;
type: string;
projectId: string;
issueId: string | null;
issueKey: string | null;
summary: string;
@@ -2540,6 +2609,7 @@ export type ActivityAddedSubscription = {
activityAdded: {
id: string;
type: string;
projectId: string;
issueId: string | null;
issueKey: string | null;
summary: string;
@@ -3600,6 +3670,7 @@ export const ActivityFieldsFragmentDoc = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueKey' } },
{ kind: 'Field', name: { kind: 'Name', value: 'summary' } },
@@ -12690,6 +12761,353 @@ export const PortfolioDocument = {
},
],
} as unknown as DocumentNode<PortfolioQuery, PortfolioQueryVariables>;
export const MyHomeDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'MyHome' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'myHome' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'agenda' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'day' } },
{ kind: 'Field', name: { kind: 'Name', value: 'kind' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'projectKey' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'issueKey' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'activity' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'ActivityFields' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'team' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'perDay' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'thisWeek' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'lastWeek' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'me' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'thisWeek' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'lastWeek' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'streak' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'sprints' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'projectKey' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'projectName' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'sprint' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'sprintId' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'name' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'startDate' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'endDate' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'totalDays' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'daysLeft' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'tickets' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'doneTickets' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'points' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'donePoints' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'pacePerDay' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'neededPerDay' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'behind' },
},
],
},
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'projects' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'projectId' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'open' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'overdue' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'activity' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'sprint' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'name' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'percent' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'behind' },
},
],
},
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'suggestions' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'kind' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'projectKey' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'count' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'firstSteps' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'project' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'invited' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'ticket' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'calendar' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'online' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'user' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'projectKey' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'issueKey' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'issueTitle' },
},
{
kind: 'Field',
name: { kind: 'Name', value: 'editing' },
},
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
],
},
},
],
},
},
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'UserRefFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'UserRef' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
],
},
},
{
kind: 'FragmentDefinition',
name: { kind: 'Name', value: 'ActivityFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'Activity' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueKey' } },
{ kind: 'Field', name: { kind: 'Name', value: 'summary' } },
{ kind: 'Field', name: { kind: 'Name', value: 'occurredAt' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'actor' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
],
},
},
],
} as unknown as DocumentNode<MyHomeQuery, MyHomeQueryVariables>;
export const ReleasesDocument = {
kind: 'Document',
definitions: [
@@ -13144,6 +13562,7 @@ export const ActivityDocument = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueKey' } },
{ kind: 'Field', name: { kind: 'Name', value: 'summary' } },
@@ -14875,6 +15294,7 @@ export const ActivityAddedDocument = {
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'issueKey' } },
{ kind: 'Field', name: { kind: 'Name', value: 'summary' } },