feat: steadier live updates and notices for public proposals

- The web pings the live connection, reconnects when it goes quiet and
  reloads what each screen shows after reconnecting.
- Dropping a card on a column it cannot go to puts it back.
- Owners and admins get a notice when a proposal arrives.
- The proposal limit lives in Redis, so it holds across instances.
- REST tells whether I voted; pasted images leave the attachment list.
- Deleting a project removes its Git integration; no lint warnings left.
- Reactions and the blocked badge are components of their own.
- Docs: what is done and what is left (chapter 10), Word and PDF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 19:57:21 +02:00
parent 74903f7842
commit c91df110f8
50 changed files with 615 additions and 321 deletions

View File

@@ -135,7 +135,10 @@ export class IssuesController {
limit: query.limit,
offset: query.offset,
});
return { ...page, items: await this.refs.issueDtos(page.items) };
return {
...page,
items: await this.refs.issueDtos(page.items, viewerOf(me)),
};
}
@Get('projects/:project/issues')
@@ -173,7 +176,10 @@ export class IssuesController {
limit: query.limit,
offset: query.offset,
});
return { ...page, items: await this.refs.issueDtos(page.items) };
return {
...page,
items: await this.refs.issueDtos(page.items, viewerOf(me)),
};
}
@Post('projects/:project/issues')
@@ -219,7 +225,7 @@ export class IssuesController {
return Promise.all(
epics.map(async (epic) => ({
...epic,
issue: await this.refs.issueDto(epic.issue),
issue: await this.refs.issueDto(epic.issue, viewerOf(me)),
})),
);
}
@@ -234,7 +240,10 @@ export class IssuesController {
@CurrentUser() me: User | undefined,
@Param('issue') issue: string,
) {
return this.refs.issueDto(await this.issues.findOne(viewerOf(me), issue));
return this.refs.issueDto(
await this.issues.findOne(viewerOf(me), issue),
viewerOf(me),
);
}
@Patch('issues/:issue')
@@ -392,6 +401,7 @@ export class IssuesController {
viewerOf(me),
await this.refs.projectId(viewerOf(me), project),
),
viewerOf(me),
);
}