|
| 1 | +# ADR: Reactions via Properties |
| 2 | + |
| 3 | +## Status |
| 4 | +- Proposed |
| 5 | + |
| 6 | +## Context |
| 7 | +- Users want lightweight reactions (e.g., 👍 ❤️) on blocks and pages. |
| 8 | +- Reactions must be stored in the graph so they sync, can be queried, and work across devices. |
| 9 | +- The system already uses properties to attach structured metadata to blocks/pages. |
| 10 | +- We need to show who reacted and which emoji they used. |
| 11 | + |
| 12 | +## Decision |
| 13 | +- Store reactions as separate entities linked to the reacted block/page. |
| 14 | +- Each reaction entity records: |
| 15 | + - Emoji id (from Logseq’s supported `emojis-data` set). |
| 16 | + - Optional `:logseq.property/created-by-ref` pointing to the reacting user (absent for anonymous graphs). |
| 17 | + - Reacted block/page reference. |
| 18 | + - `:block/created-at` timestamp for the reaction entity. |
| 19 | +- No `:logseq.property/reactions` collection property is required; use the reverse ref |
| 20 | + `(:logseq.property.reaction/_target node-entity)` to fetch reactions for a node. |
| 21 | +- Keep the property name namespaced in logseq.db.frontend.property/built-in-properties. |
| 22 | + |
| 23 | +### Proposed entity shape |
| 24 | +``` |
| 25 | +{:db/id ... |
| 26 | + :logseq.property.reaction/emoji-id "smile" |
| 27 | + :logseq.property/created-by-ref <user-db-id> ;; omitted for anonymous graphs |
| 28 | + :logseq.property.reaction/target <target-db-id> ;; block/page db id |
| 29 | + :block/created-at 1710000000000} |
| 30 | +``` |
| 31 | + |
| 32 | +### Read/write rules |
| 33 | +- Toggling a reaction adds/removes a reaction entity for the current emoji/user. |
| 34 | +- If anonymous, only one reaction per emoji per block/page (no user id). |
| 35 | +- Reactions are derived via reverse reference lookup; no dedicated collection |
| 36 | + property is stored on the node. |
| 37 | + |
| 38 | +### Example queries |
| 39 | +```clj |
| 40 | +;; Given a block/page entity `node-entity`, fetch all reactions. |
| 41 | +(:logseq.property.reaction/_target node-entity) |
| 42 | + |
| 43 | +;; Filter reactions by emoji id. |
| 44 | +(filter #(= "smile" (:logseq.property.reaction/emoji-id %)) |
| 45 | + (:logseq.property.reaction/_target node-entity)) |
| 46 | + |
| 47 | +;; Count reactions per emoji id. |
| 48 | +(->> (:logseq.property.reaction/_target node-entity) |
| 49 | + (map :logseq.property.reaction/emoji-id) |
| 50 | + (frequencies)) |
| 51 | + |
| 52 | +;; Filter reactions by user id (when present). |
| 53 | +(filter #(= user-db-id (:logseq.property/created-by-ref %)) |
| 54 | + (:logseq.property.reaction/_target node-entity)) |
| 55 | +``` |
| 56 | + |
| 57 | +## Consequences |
| 58 | +- Reactions sync naturally as part of DB transactions and are queryable. |
| 59 | +- Data model supports “who reacted” and multiple users per emoji without map merging. |
| 60 | +- Adds more entities; need efficient queries and indexes. |
| 61 | + |
| 62 | +## Alternatives Considered |
| 63 | +- **Dedicated table/attribute per emoji**: complicates schema, increases complexity. |
| 64 | +- **Property map (emoji -> users)**: smaller but harder to resolve conflicts and query per user. |
| 65 | +- **Inline text markers**: not structured, hard to query and sync. |
| 66 | + |
| 67 | +## Open Questions |
| 68 | +- Which user identifier should be stored as `:logseq.property/created-by-ref`? |
| 69 | + Each user has a page in the graph |
| 70 | +- How to handle anonymous/local graphs (no user identity)? |
| 71 | + Record reactions, for anonymous graphs, don't store :logseq.property/created-by-ref |
| 72 | + |
| 73 | +## Notes for Implementation |
| 74 | +- Add emoji entity schema to DB validation. |
| 75 | +- UI should show a summary (emoji + count) and a hover/popover with user list. |
| 76 | +- User can toggle reaction. |
0 commit comments