feat: «Mi día», the few tickets I pick for today

A home panel with the tickets picked for today (10 at most), crossed out
as they get done and counted («1 de 3 terminados»), my open tickets to
add with one click, and what was left unfinished on my last day to carry
over. «☀ Mi día» on a ticket adds it or takes it out. The day is the
person's own (YYYY-MM-DD from the browser); private, no history (Mongo
`my_days`).

API: myDay(day), addToMyDay, removeFromMyDay; GET /api/me/day,
PUT/DELETE /api/me/day/{issue}. MCP: my_day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 20:18:10 +02:00
parent b88296c22b
commit dfb5f973f4
31 changed files with 2183 additions and 50 deletions

View File

@@ -16,6 +16,7 @@ import {
IssueListFilter,
IssueSummary,
LinkIssuesInput,
MyDay,
MyIssuesFilter,
mergeBulkResults,
UpdateIssueInput,
@@ -53,6 +54,9 @@ import {
MyPinsDocument,
FollowTreeDocument,
SetReminderDocument,
MyDayDocument,
AddToMyDayDocument,
RemoveFromMyDayDocument,
ClearReminderDocument,
UnfollowTreeDocument,
SearchDecisionsDocument,
@@ -232,6 +236,31 @@ export class GraphqlIssuesGateway extends IssuesGateway {
.pipe(map(() => undefined));
}
myDay(day: string): Observable<MyDay> {
return this.gql.query(MyDayDocument, { day }).pipe(
map(({ myDay }) => ({
day: myDay.day,
issues: myDay.issues.map(toIssueSummary),
leftover: myDay.leftover && {
day: myDay.leftover.day,
issues: myDay.leftover.issues.map(toIssueSummary),
},
})),
);
}
addToMyDay(day: string, issueIds: string[]): Observable<void> {
return this.gql
.mutate(AddToMyDayDocument, { day, issueIds })
.pipe(map(() => undefined));
}
removeFromMyDay(day: string, issueId: string): Observable<void> {
return this.gql
.mutate(RemoveFromMyDayDocument, { day, issueId })
.pipe(map(() => undefined));
}
clearReminder(issueId: string): Observable<void> {
return this.gql
.mutate(ClearReminderDocument, { issueId })

View File

@@ -1155,6 +1155,91 @@ export type MyPinsQuery = {
}>;
};
export type MyDayQueryVariables = Exact<{
day: string;
}>;
export type MyDayQuery = {
myDay: {
day: string;
issues: Array<{
id: string;
projectId: string;
key: string;
type: string;
area: string | null;
team: string | null;
title: string;
status: string;
statusCategory: StatusCategory;
priority: IssuePriority;
labels: Array<string>;
commentCount: number;
updatedAt: string;
version: number;
votes: number;
voted: boolean;
rank: string;
storyPoints: number | null;
startDate: string | null;
dueDate: string | null;
trashedAt: string | null;
assignee: { id: string; name: string } | null;
release: { id: string; name: string } | null;
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
leftover: {
day: string;
issues: Array<{
id: string;
projectId: string;
key: string;
type: string;
area: string | null;
team: string | null;
title: string;
status: string;
statusCategory: StatusCategory;
priority: IssuePriority;
labels: Array<string>;
commentCount: number;
updatedAt: string;
version: number;
votes: number;
voted: boolean;
rank: string;
storyPoints: number | null;
startDate: string | null;
dueDate: string | null;
trashedAt: string | null;
assignee: { id: string; name: string } | null;
release: { id: string; name: string } | null;
sprint: { id: string; name: string } | null;
parent: { id: string; key: string; title: string } | null;
blockedBy: Array<{ id: string; key: string; title: string }>;
customFields: Array<{ fieldId: string; value: string }>;
}>;
} | null;
};
};
export type AddToMyDayMutationVariables = Exact<{
day: string;
issueIds: Array<string> | string;
}>;
export type AddToMyDayMutation = { addToMyDay: boolean };
export type RemoveFromMyDayMutationVariables = Exact<{
day: string;
issueId: string;
}>;
export type RemoveFromMyDayMutation = { removeFromMyDay: boolean };
export type SetReminderMutationVariables = Exact<{
issueId: string;
at: string;
@@ -7507,6 +7592,346 @@ export const MyPinsDocument = {
},
],
} as unknown as DocumentNode<MyPinsQuery, MyPinsQueryVariables>;
export const MyDayDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'query',
name: { kind: 'Name', value: 'MyDay' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'day' } },
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'String' },
},
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'myDay' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'day' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'day' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'day' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'issues' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'IssueSummaryFields' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'leftover' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'day' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'issues' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: {
kind: 'Name',
value: 'IssueSummaryFields',
},
},
],
},
},
],
},
},
],
},
},
],
},
},
{
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: 'IssueSummaryFields' },
typeCondition: {
kind: 'NamedType',
name: { kind: 'Name', value: 'Issue' },
},
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
{ kind: 'Field', name: { kind: 'Name', value: 'area' } },
{ kind: 'Field', name: { kind: 'Name', value: 'team' } },
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
{ kind: 'Field', name: { kind: 'Name', value: 'statusCategory' } },
{ kind: 'Field', name: { kind: 'Name', value: 'priority' } },
{ kind: 'Field', name: { kind: 'Name', value: 'labels' } },
{ kind: 'Field', name: { kind: 'Name', value: 'commentCount' } },
{ kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
{ kind: 'Field', name: { kind: 'Name', value: 'version' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'assignee' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'FragmentSpread',
name: { kind: 'Name', value: 'UserRefFields' },
},
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'release' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'sprint' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'parent' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'blockedBy' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'votes' } },
{ kind: 'Field', name: { kind: 'Name', value: 'voted' } },
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'customFields' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'fieldId' } },
{ kind: 'Field', name: { kind: 'Name', value: 'value' } },
],
},
},
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
],
},
},
],
} as unknown as DocumentNode<MyDayQuery, MyDayQueryVariables>;
export const AddToMyDayDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'AddToMyDay' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'day' } },
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'String' },
},
},
},
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueIds' },
},
type: {
kind: 'NonNullType',
type: {
kind: 'ListType',
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'ID' },
},
},
},
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'addToMyDay' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'day' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'day' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'issueIds' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueIds' },
},
},
],
},
],
},
},
],
} as unknown as DocumentNode<AddToMyDayMutation, AddToMyDayMutationVariables>;
export const RemoveFromMyDayDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'RemoveFromMyDay' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'day' } },
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'String' },
},
},
},
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'removeFromMyDay' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'day' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'day' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'issueId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueId' },
},
},
],
},
],
},
},
],
} as unknown as DocumentNode<
RemoveFromMyDayMutation,
RemoveFromMyDayMutationVariables
>;
export const SetReminderDocument = {
kind: 'Document',
definitions: [

View File

@@ -101,6 +101,29 @@ query MyPins {
}
}
query MyDay($day: String!) {
myDay(day: $day) {
day
issues {
...IssueSummaryFields
}
leftover {
day
issues {
...IssueSummaryFields
}
}
}
}
mutation AddToMyDay($day: String!, $issueIds: [ID!]!) {
addToMyDay(day: $day, issueIds: $issueIds)
}
mutation RemoveFromMyDay($day: String!, $issueId: ID!) {
removeFromMyDay(day: $day, issueId: $issueId)
}
mutation SetReminder($issueId: ID!, $at: String!, $note: String) {
setReminder(issueId: $issueId, at: $at, note: $note) {
at