feat(notification): carry the whole deep-link target on every notification - #221
Merged
Conversation
…ation Tapping a notification could not open what it was about. Post likes and comment likes persisted no referenceId at all, so the stored row had no destination even though the realtime event carried one. Comment notifications stored only the comment id, leaving the client to resolve the post or article it lives under. Post and article likes shared the LIKE type with nothing to tell them apart, articles are read by slug while notifications only knew their uuid, and the notification id was never mapped out of Prisma, so a single notification could not be addressed at all. Notifications now store postId / articleId / commentId next to referenceId, which the entity derives from the target: the comment, else the article, else the post. Article slugs are resolved on read rather than denormalised, since a slug follows its title. The columns are real foreign keys with ON DELETE CASCADE, so a notification cannot outlive what it points at. The response exposes the notification id along with the target ids, and the realtime payload carries the article slug so both paths can build the same URL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PwzkQ5YGFXSB9jWCZKzX4H
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 26, 2026
# [1.8.0](v1.7.4...v1.8.0) (2026-08-26) ### Features * **notification:** carry the whole deep-link target on every notification ([#221](#221)) ([de8fbe3](de8fbe3))
|
🎉 This PR is included in version 1.8.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes findings 1-5 of the notification audit. Findings 6-7 (unread-count endpoint, per-notification read) follow in a second PR.
Problem
Tapping a notification could not reliably open what it was about.
referenceIdon two types.like-post.usecase.tsandlike-comment.usecase.tscalledNotification.createwithout a fourth argument, so the stored row hadreferenceId = null. The realtime event carried the right ids, the persisted notification did not — a link that worked live and broke once the user opened the list.LIKEdid not distinguish post from article. Both used the same type; the client could not tell what the uuid it received pointed at.GET /articles/:slug), but notifications only knew the article uuid, and no endpoint resolves one into the other.Change
Notificationnow storespostId/articleId/commentIdalongsidereferenceId, andNotification.createtakes aNotificationTargetinstead of a bare reference:referenceIdis derived from that target — the comment, else the article, else the post — so it is populated consistently and every old client keeps working unchanged.article: { select: { slug: true } }, soarticleSlugis always current even after a title change.ON DELETE CASCADE. A notification can no longer outlive the content it points at, so a tap never lands on a 404. This also covers part of finding 8.id, unblocking the per-notification read endpoint in the follow-up PR.articleSlug, so the live event and the REST row build the same URL.Client rule after this change:
commentId→ open the post/article and scroll to the comment;articleId→/articles/{articleSlug};postId→ the post; none of them → the issuer's profile viausername.Migration
20260826233221_notification_deep_link_targetsadds three nullable columns, their indexes and their cascading foreign keys. Existing rows keep theirreferenceIdand get NULL targets — they render as before and simply are not deep-linkable.Verification
pnpm test:unit— 785 passedpnpm test:integration— 104 passed (new cases: post target round-trip, article slug resolution, cascade on target delete)pnpm test:e2e— 317 passed (newtests/e2e/notification/deep-links.test.tsdrives a real post like, comment and comment like, then asserts the payload)tsc -p tsconfig.build.json --noEmit,pnpm lint, prettier — cleanThe test database was reset with the user's explicit consent to apply and verify the migration.
Not in this PR
Findings 6-14 of the audit, notably: unread-count endpoint and per-notification read (6-7, next PR), notification cleanup on unlike/unfollow (8), realtime emit inside the transaction (9), one socket per user in
WebSocketManager(11), and the wrongdefault-avatar.pngfallback in the mapper (12).🤖 Generated with Claude Code
https://claude.ai/code/session_01PwzkQ5YGFXSB9jWCZKzX4H