Skip to content

feat(thread): upsert anonymous thread replies - #44

Merged
BastiDood merged 10 commits into
mainfrom
anonymously-reply-in-thread
May 16, 2026
Merged

BastiDood merged 10 commits into
mainfrom
anonymously-reply-in-thread

Conversation

@BastiDood

Copy link
Copy Markdown
Owner

This overhauls anonymous thread handling so thread submissions behave like an app-level upsert instead of leaving orphaned persisted confessions when Discord thread creation runs into an already-existing thread. For Reply as Anonymous Thread, Spectro now treats Discord's "thread already created for message" response as the existing thread target, records the managed thread mapping, and continues through the normal log/post or approval-dispatch flow.

This closes #42 by ensuring the thread-reply failure mode no longer persists a confession that is neither logged nor published. The same thread machinery now also supports approval-enabled channels, where the Discord thread is intentionally created or resolved later during approval.

Implementation Notes

Thread Data Model

  • Reworked thread persistence around pending_channel_thread, pending_channel_thread_title, and approved_channel_thread.
  • Moved thread titles out of pending_channel_thread and into pending_channel_thread_title, keyed by confession internal ID, so approval order determines the winning title.
  • Connected approved_channel_thread to the winning title row instead of storing a duplicated pending-thread pointer.
  • Added supporting foreign keys and indexes for the new relationships and future cascade behavior.

Upsert Thread Resolution

  • Added resolveApprovedChannelThread as the shared database primitive for idempotently resolving or inserting an approved Discord thread.
  • The resolver now:
    • looks up whether the pending thread is already approved,
    • takes an advisory lock on the pending thread before insertion,
    • rechecks state after the lock,
    • falls back to an existing Discord thread row when the thread was already registered,
    • inserts only when no existing approved mapping wins.
  • Query rows are post-processed into normalized state before the workflow code consumes them, so handler and Inngest paths do not need to assert impossible database states.

Submission And Approval Flows

  • Updated immediate /thread and reply-as-thread submissions to resolve already-created message threads instead of failing with an orphaned confession.
  • Allowed approval-gated thread submissions to defer thread creation until approval while keeping the same pending-thread model.
  • Updated approval dispatch to create or resolve the Discord thread, then publish into the resolved channel.
  • Kept recursive thread creation disallowed at the modal/submission boundary; replying inside an existing thread still uses the normal anonymous message path.

Query State Normalization

  • Collapsed split thread lookups into joined queries where practical.
  • Moved database invariant checks into query/state builders for approval dispatch, resend, submission, and verdict handling.
  • Removed stale assumptions that thread titles live on pending_channel_thread.
  • Refactored approval verdict loading so approval.ts consumes a shaped verdict object instead of raw nullable database rows.

Logging And Payloads

  • Preserved thread metadata in moderator log payloads.
  • Added thread title and thread channel context to approval/rejection logs where available.
  • Kept Reply To as a Discord message URL because it points to a specific message, while channel and thread fields stay channel-mention formatted.

Breaking Changes

This changes the thread-related database model. Existing pending_channel_thread.title data is migrated into the new title-centric model by the included migrations.

Test Cases

  • Submit /thread in a non-approval channel and confirm the confession is logged and published into the created thread.
  • Submit Reply as Anonymous Thread for a message that has no thread and confirm Spectro creates the message thread, logs the confession, and publishes into that thread.
  • Submit Reply as Anonymous Thread for a message that already has a Discord thread and confirm Spectro resolves the existing thread instead of leaving an unlogged confession.
  • Submit /thread in an approval-enabled channel and confirm the confession is logged as pending without creating the Discord thread immediately.
  • Approve an approval-gated /thread confession and confirm the Discord thread is created, registered, and receives the published confession.
  • Approve multiple pending replies for the same parent message out of submission order and confirm the first approved title wins.
  • Submit an anonymous message inside an existing thread and confirm recursive thread creation remains disallowed for thread-creation flows.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 6 additional findings.

Open in Devin Review

@BastiDood
BastiDood merged commit 416bb04 into main May 16, 2026
3 checks passed
@BastiDood
BastiDood deleted the anonymously-reply-in-thread branch May 16, 2026 11:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed anonymous thread creation still persists confession

1 participant