feat: «Recordármelo», a reminder of a ticket only for me

On a ticket, «Recordármelo» offers in an hour, this evening, tomorrow or
next Monday at 9, in a week, or any date and time, with an optional note.
The ticket shows the waiting one («Te lo recordaré el mié 21 a las 9:00»)
to change or drop. Every minute a scheduler tells the ones whose time
came as a REMINDER notification (bell, push, e-mail), once, and forgets
those whose ticket went to the trash or out of reach. One per person and
ticket, private, no history (Mongo `issue_reminders`).

API: setReminder / clearReminder, Issue.myReminder, myReminders;
PUT/DELETE /api/issues/{issue}/reminder, GET /api/me/reminders. MCP:
remind_me.

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

View File

@@ -52,6 +52,8 @@ import {
WatchIssueDocument,
MyPinsDocument,
FollowTreeDocument,
SetReminderDocument,
ClearReminderDocument,
UnfollowTreeDocument,
SearchDecisionsDocument,
PinIssueDocument,
@@ -220,6 +222,22 @@ export class GraphqlIssuesGateway extends IssuesGateway {
),
);
}
setReminder(
issueId: string,
at: string,
note: string | null,
): Observable<void> {
return this.gql
.mutate(SetReminderDocument, { issueId, at, note })
.pipe(map(() => undefined));
}
clearReminder(issueId: string): Observable<void> {
return this.gql
.mutate(ClearReminderDocument, { issueId })
.pipe(map(() => undefined));
}
followTree(issueId: string, on: boolean): Observable<void> {
const done$: Observable<unknown> = on
? this.gql.mutate(FollowTreeDocument, { issueId })

View File

@@ -122,6 +122,10 @@ fragment IssueFields on Issue {
...IssueSummaryFields
pinned
followingTree
myReminder {
at
note
}
decisions {
...CommentFields
}

View File

@@ -714,6 +714,7 @@ export type IssueFieldsFragment = {
startDate: string | null;
dueDate: string | null;
trashedAt: string | null;
myReminder: { at: string; note: string | null } | null;
decisions: Array<{
id: string;
issueId: string;
@@ -960,6 +961,7 @@ export type IssueQuery = {
startDate: string | null;
dueDate: string | null;
trashedAt: string | null;
myReminder: { at: string; note: string | null } | null;
decisions: Array<{
id: string;
issueId: string;
@@ -1153,6 +1155,20 @@ export type MyPinsQuery = {
}>;
};
export type SetReminderMutationVariables = Exact<{
issueId: string;
at: string;
note?: string | null | undefined;
}>;
export type SetReminderMutation = { setReminder: { at: string } };
export type ClearReminderMutationVariables = Exact<{
issueId: string;
}>;
export type ClearReminderMutation = { clearReminder: boolean };
export type FollowTreeMutationVariables = Exact<{
issueId: string;
}>;
@@ -3836,6 +3852,17 @@ export const IssueFieldsFragmentDoc = {
},
{ kind: 'Field', name: { kind: 'Name', value: 'pinned' } },
{ kind: 'Field', name: { kind: 'Name', value: 'followingTree' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'myReminder' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
{ kind: 'Field', name: { kind: 'Name', value: 'note' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'decisions' },
@@ -6437,6 +6464,17 @@ export const IssueDocument = {
},
{ kind: 'Field', name: { kind: 'Name', value: 'pinned' } },
{ kind: 'Field', name: { kind: 'Name', value: 'followingTree' } },
{
kind: 'Field',
name: { kind: 'Name', value: 'myReminder' },
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
{ kind: 'Field', name: { kind: 'Name', value: 'note' } },
],
},
},
{
kind: 'Field',
name: { kind: 'Name', value: 'decisions' },
@@ -7469,6 +7507,131 @@ export const MyPinsDocument = {
},
],
} as unknown as DocumentNode<MyPinsQuery, MyPinsQueryVariables>;
export const SetReminderDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'SetReminder' },
variableDefinitions: [
{
kind: 'VariableDefinition',
variable: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueId' },
},
type: {
kind: 'NonNullType',
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
},
},
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'at' } },
type: {
kind: 'NonNullType',
type: {
kind: 'NamedType',
name: { kind: 'Name', value: 'String' },
},
},
},
{
kind: 'VariableDefinition',
variable: { kind: 'Variable', name: { kind: 'Name', value: 'note' } },
type: { kind: 'NamedType', name: { kind: 'Name', value: 'String' } },
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{
kind: 'Field',
name: { kind: 'Name', value: 'setReminder' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'issueId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueId' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'at' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'at' },
},
},
{
kind: 'Argument',
name: { kind: 'Name', value: 'note' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'note' },
},
},
],
selectionSet: {
kind: 'SelectionSet',
selections: [
{ kind: 'Field', name: { kind: 'Name', value: 'at' } },
],
},
},
],
},
},
],
} as unknown as DocumentNode<SetReminderMutation, SetReminderMutationVariables>;
export const ClearReminderDocument = {
kind: 'Document',
definitions: [
{
kind: 'OperationDefinition',
operation: 'mutation',
name: { kind: 'Name', value: 'ClearReminder' },
variableDefinitions: [
{
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: 'clearReminder' },
arguments: [
{
kind: 'Argument',
name: { kind: 'Name', value: 'issueId' },
value: {
kind: 'Variable',
name: { kind: 'Name', value: 'issueId' },
},
},
],
},
],
},
},
],
} as unknown as DocumentNode<
ClearReminderMutation,
ClearReminderMutationVariables
>;
export const FollowTreeDocument = {
kind: 'Document',
definitions: [

View File

@@ -101,6 +101,16 @@ query MyPins {
}
}
mutation SetReminder($issueId: ID!, $at: String!, $note: String) {
setReminder(issueId: $issueId, at: $at, note: $note) {
at
}
}
mutation ClearReminder($issueId: ID!) {
clearReminder(issueId: $issueId)
}
mutation FollowTree($issueId: ID!) {
followTree(issueId: $issueId)
}