Visibility and rules
This page explains what users are allowed to interact with, what Directus enforces, and what still belongs in application code.
The visibility rules are not just technical constraints. They are there to keep the social behavior of the product predictable.
Target visibility matrix
| Target | Allowed interaction state | Comments allowed | Reactions allowed | Enforced where |
|---|---|---|---|---|
books |
status = published |
Yes | Yes | Directus flows |
reading_journeys |
visibility = public and status in (active, finished) |
Yes | Yes | Directus flows |
reading_shares |
visibility = public and status = published |
Yes | Yes | Directus flows |
reading_shares with private or non-published state |
Not interactable for general users | No | No | Directus flows |
Why the interaction gates are different
- Books are treated as public reference content, so the main gate is publication state.
- Reading journeys can be personal, so both
visibilityandstatusmatter. - Reading shares behave like published posts, so they need to be both public and published before other members can interact.
This keeps private or unfinished member activity from accidentally becoming social content.
Comment visibility states
Only one comment collection currently carries its own extra visibility flag.
| Collection | Visibility field | Allowed values | Meaning |
|---|---|---|---|
reading_share_comments |
visibility |
public, share_author_only |
A share comment can be fully public or restricted |
reading_journey_comments |
none | n/a | Journey comments currently follow target-level visibility only |
book_comments |
none | n/a | Book comments currently follow target-level visibility only |
This difference is intentional. Reading share comments already needed a more nuanced visibility option, while book and journey comments are currently kept simpler to reduce complexity in both moderation and read filtering.
Important boundary: share_author_only
This is the one rule that is not fully solved inside Directus.
- The schema state exists in
reading_share_comments.visibility. - Directus write guardrails protect comment creation on invalid share targets.
- Final read filtering for
share_author_onlystill belongs in the app or API layer.
That means the CMS can store the state safely, but public listing endpoints still need to decide what to hide.
We left this split in place because viewer-specific read filtering is awkward to express globally in Directus. The app knows who is asking for the data, so it is the better place to decide what that viewer should or should not see.
Ownership rules
| Collection group | Owner field | Can clients set it directly? | Actual source of truth |
|---|---|---|---|
reading_journeys, reading_shares |
user |
Typically app-managed | App logic |
| All comment collections | user |
No | Directus create flows overwrite it from the authenticated user |
| All reaction collections | user |
No | Directus create flows overwrite it from the authenticated user |
In practice, user_created is not a substitute for the business owner field. The docs and flows treat user as the content owner.
This protects one of the most important trust boundaries in the system: members should not be able to create content on behalf of someone else just by sending a crafted payload.
Delete behavior
| Relation | Delete behavior | Why |
|---|---|---|
| Interaction target -> comments | CASCADE |
Deleting the target should remove attached discussion records |
| Interaction target -> reactions | CASCADE |
Reactions have no meaning after the target is gone |
| User -> comments | SET NULL |
Preserve discussion content if an account is deleted |
| User -> reactions | SET NULL |
Preserve aggregate history even if identity is gone |
The delete rules balance cleanup against preservation.
- If the target disappears, the interaction should disappear too.
- If the user disappears, the conversation or signal may still be worth keeping.
That is why target relations use CASCADE, while owner relations use SET NULL.
Moderation and status fields
| Collection | Status field | Values |
|---|---|---|
books |
status |
published, archived |
authors |
status |
published, archived |
reading_journeys |
status |
active, finished, archived |
reading_shares |
status |
draft, published, archived |
reading_share_comments |
status |
published, hidden, archived |
reading_journey_comments |
status |
published, hidden, archived |
book_comments |
status |
published, hidden, archived |
Reaction collections do not carry a status field. If moderation becomes necessary later, that would be a deliberate model change, not a hidden feature waiting to be turned on.
This was a conscious tradeoff. Reaction records are meant to stay cheap and simple. If we added a moderation workflow to them too early, we would add operational cost to the lightest interaction in the system.
Practical reading of the rules
If you are building against this model, the safe assumptions are:
- A comment or reaction row should never be trusted just because the client submitted it.
- The target record's status and visibility decide whether interaction is allowed.
- Share-comment visibility has an extra application-side read rule because of
share_author_only. - Reaction rows represent a single like, not a flexible emoji or multi-state reaction system.
The broader design goal is simple: make public interaction easy, make private content safe, and keep the rules understandable enough that developers and editors can reason about them without guessing.