feat: pinned tickets, at hand on the home page and in search

Each person pins up to 20 tickets from the ticket's page («Fijar»). They
show as a strip under the projects on the home page and in the search
palette before typing. Private and without history, like the views of
«my work» (Mongo `pinned_issues`); tickets in the trash or out of reach
are left out, and when the list is full the unreadable ones are dropped
first.

API: GET /api/me/pins, PUT/DELETE /api/issues/{issue}/pin; GraphQL
myPins, pinIssue, unpinIssue and Issue.pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 10:15:28 +02:00
parent 423f82c1a3
commit dfa42a8a1a
32 changed files with 1612 additions and 46 deletions

View File

@@ -13,6 +13,7 @@ import {
ReleaseReadModel,
SavedFilterReadModel,
MyViewReadModel,
PinnedIssueReadModel,
SprintReadModel,
implicitRank,
} from '@task-stream/api/tracker/domain';
@@ -42,6 +43,7 @@ export type TimelineDoc = Doc<IssueTimelineEntry>;
export type NotificationDoc = Doc<NotificationReadModel>;
export type SavedFilterDoc = Doc<SavedFilterReadModel>;
export type MyViewDoc = Doc<MyViewReadModel>;
export type PinnedIssueDoc = PinnedIssueReadModel & { _id?: Types.ObjectId };
export type OutgoingWebhookDoc = Doc<OutgoingWebhook>;
export type AutomationDoc = Doc<Automation>;
export interface AutomationRunDoc {
@@ -104,6 +106,7 @@ export const ReadModels = {
NotificationSettings: 'NotificationSettings',
SavedFilter: 'SavedFilter',
MyView: 'MyView',
PinnedIssue: 'PinnedIssue',
Sprint: 'SprintReadModel',
GitIntegration: 'GitIntegration',
OutgoingWebhook: 'OutgoingWebhook',
@@ -322,6 +325,16 @@ class MyViewDocument {
}
export const MyViewSchema = SchemaFactory.createForClass(MyViewDocument);
@Schema({ ...LOOSE, collection: 'pinned_issues' })
class PinnedIssueDocument {
@Prop({ type: String }) ownerId!: string;
@Prop({ type: String }) issueId!: string;
@Prop({ type: String }) pinnedAt!: string;
}
export const PinnedIssueSchema =
SchemaFactory.createForClass(PinnedIssueDocument);
PinnedIssueSchema.index({ ownerId: 1, issueId: 1 }, { unique: true });
@Schema({ ...LOOSE, collection: 'git_integrations' })
class GitIntegrationDocument {
/** The project's id. */
@@ -424,6 +437,7 @@ export const READ_MODEL_SCHEMAS = [
{ name: ReadModels.NotificationSettings, schema: NotificationSettingsSchema },
{ name: ReadModels.SavedFilter, schema: SavedFilterSchema },
{ name: ReadModels.MyView, schema: MyViewSchema },
{ name: ReadModels.PinnedIssue, schema: PinnedIssueSchema },
{ name: ReadModels.CalendarFeed, schema: CalendarFeedSchema },
{ name: ReadModels.DashboardShare, schema: DashboardShareSchema },
{ name: ReadModels.PushSubscription, schema: PushSubscriptionSchema },