3.7 KiB
3.7 KiB
08 — Collaboration: Comments, Attachments, Links, Wiki, Markdown, Watchers
Comments
- Flat list per issue, chronological (no threading); markdown bodies
- Edit: author only; sets
updated_at; UI shows "(edited)" - Delete: soft; author or project owner; UI shows a "comment deleted" tombstone (author + timestamp, no body)
- Resolvable: any member can resolve/unresolve (
resolved_at,resolved_by); resolved comments render collapsed with a "resolved by X" header; issue detail shows resolved count - Emit
comment.created/updated/resolved/unresolved/deleted;comment.createdpayload carries parsed mentions
Markdown pipeline
- Client: markdown-it + DOMPurify sanitization; single shared
MarkdownViewcomponent; supported: CommonMark + tables, strikethrough, task lists, fenced code with highlight.js, autolink - Server (on save of any markdown field — issue/project/epic descriptions, comments, wiki bodies):
- Parse
@usernamementions (word-boundary, against existing usernames) → into the event payload for notification fan-out; mentions inside code blocks/spans are ignored - Parse
#Nreferences (this project's issues) → validated list into event payload; used for cross-link rendering and future relation hints - Server does NOT render HTML (client renders); server only extracts entities
- Parse
- Client renders
@usernameas a profile-ish chip and#Nas a router-link to the issue (resolve via a lightweightGET /projects/{id}/issues/{num}title lookup, cached) - Paste/drag an image into
MarkdownEditor→ uploads as attachment → inserts
Attachments
- Upload targets: project, issue, comment (multipart; ≤
MAX_UPLOAD_MB; any mime type, but SVG served withContent-Type: text/plainnosniff to avoid stored XSS) - Disk path:
UPLOAD_DIR/project_{pid}/{uploader_id}_{yyyymmddhhmmss}_{rand16}.{ext}— path stored relative in DB; never trust client filenames for the path (original name kept separately for download) - Serving:
GET /files/{path}checks membership of the owning project (lookup by attachments row, not by path prefix), setsContent-Disposition - Delete: soft; file stays on disk until cull (12-retention.md deletes row + file together)
- Image attachments get a thumbnail treatment client-side only (no server thumbnailing in v1)
- Emit
attachment.uploaded/deleted
Links
- Simple URL + optional title on project/issue/comment; validated absolute http(s) URL
- Emit
link.added/removed
Wiki
- Per-project markdown pages; slug generated from title (kebab-case, deduplicated with
-2suffix); slug is stable after creation (title edits don't re-slug) - Page list + view + edit (single editor, last-write-wins with
updated_atconflict warning: PATCH carriesexpect_updated_at, 409 on mismatch) updated_bytracked; no page history in v1 (17-decisions.md)- Wiki bodies participate in mentions/#refs parsing and search indexing
- Emit
wiki.created/updated/deleted
Watchers
- Any member can watch/unwatch any issue in their projects; watcher list shown on issue detail
- Auto-watch (insert if no row exists; never flip an existing
muted=truerow):- creator on issue create
- assignee on assign
- commenter on comment
- mentioned user on mention (issue description or comment)
- Unwatch = mute:
DELETE /watchsetsmuted = true(row kept) so no future auto-watch resurrects the subscription;PUT /watchclears muted - Watchers feed notification fan-out (09-notifications-realtime.md); muted watchers receive nothing