Skip to content

feat: add message reactions#98

Open
jjjhenriksen wants to merge 1 commit into
openclaw:mainfrom
jjjhenriksen:codex/message-reactions-submit
Open

feat: add message reactions#98
jjjhenriksen wants to merge 1 commit into
openclaw:mainfrom
jjjhenriksen:codex/message-reactions-submit

Conversation

@jjjhenriksen

@jjjhenriksen jjjhenriksen commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • persist per-user emoji reactions on channel and direct messages
  • render reaction counts and the current user's state in message rows
  • reconcile add/remove events in open channels and threads
  • provide accessible optimistic reaction controls with rollback and authoritative reconciliation

Real behavior proof

tests/e2e/message-reactions.spec.ts runs against ClickClack's locally built production web bundle served by the real Go API. In one isolated run it:

  1. creates a real ClickClack workspace and channel through the API;
  2. sends a message through the rendered ClickClack composer;
  3. adds 👍 through the rendered reaction picker and observes it immediately;
  4. reloads the page and observes the persisted reaction;
  5. removes the reaction through the authenticated API and observes the open UI remove it without a reload through the realtime event path;
  6. opens an older message through search, receives a realtime reaction update, and confirms the surrounding paged history window remains intact;
  7. forces two realtime reaction refreshes to finish out of order and confirms the stale response cannot overwrite the newer state;
  8. delays a reaction refresh across a concurrent message edit and confirms only reaction fields are reconciled;
  9. delays a reaction refresh across navigation away and back and confirms it cannot overwrite freshly hydrated state;
  10. delays the active message-page response and confirms a reaction event received during loading is merged into the stale page result;
  11. commits a removal while returning a simulated failed response and confirms reconciliation does not resurrect the reaction; and
  12. fails an optimistic mutation, accepts a newer realtime reaction snapshot, then fails the recovery GET and confirms the failed emoji rolls back without erasing the concurrent reaction.
  13. holds an older successful add response, applies a newer authenticated removal, then releases the response and confirms it cannot resurrect the reaction.
  14. forces an add conflict against an already-committed server reaction and confirms recovery keeps the reaction visible before clearing the optimistic intent.
  15. confirms all reaction controls are disabled while a mutation is pending, serializing per-message writes so concurrent refreshes cannot discard committed state.
  16. commits a reaction after an unrelated realtime update, fails the follow-up refresh, and confirms the committed reaction remains visible without a false mutation error.

The captured real-app state is committed at docs/proof/message-reactions-real-app.png, with reproduction details in docs/proof/message-reactions-proof.md.

Focused browser run:

pnpm exec playwright test tests/e2e/message-reactions.spec.ts
10 passed (10.9s)

Validation

  • pnpm check — passed on abfeb871fc3065103f8ae8b4e53afd2bfaf91368
  • pnpm exec playwright test tests/e2e/message-reactions.spec.ts — 10 passed
  • go test ./apps/api/internal/store/sqlite ./apps/api/internal/store/postgres -run '^TestGetThreadHydratesReactions$' -count=1 — passed
  • pnpm generate:sqlc — generated SQLite and PostgreSQL store code from the checked-in query sources
  • Autoreview — all actionable findings from completed passes fixed; the final exact-head rerun was blocked by the reviewer account usage limit
  • local ClawSweeper — all actionable findings from completed passes fixed; the final exact-head attempt hit the same reviewer usage limit, so a GitHub re-review is requested below

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal priority bug or improvement with limited blast radius. labels Jul 18, 2026
@clawsweeper

clawsweeper Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 18, 2026, 9:53 PM ET / July 19, 2026, 01:53 UTC.

Summary
The PR adds persisted per-user emoji reactions, realtime reconciliation, optimistic controls, generated web assets, and browser/store coverage for ClickClack messages.

Reproducibility: not applicable. as a bug report: this PR proposes a new capability. The contributor provides a high-confidence real-app execution path for the proposed behavior, but that does not replace the missing thread-path coverage.

Review metrics: 2 noteworthy metrics.

  • Cross-stack scope: 54 files affected; 1,475 added and 126 removed. The feature changes storage, API responses, realtime reconciliation, web UI, generated assets, and browser coverage, so a focused completion pass is warranted.
  • Focused browser coverage: 1 new Playwright spec with 10 passing scenarios. The supplied proof covers rendered UI behavior, persistence, realtime updates, and several optimistic-state races.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Add ReactionsBar or an equivalent shared reaction control to thread root and reply rendering.
  • [P2] Add a targeted Playwright assertion that adding or removing a thread reaction is visible and survives realtime reconciliation.
  • Provide focused existing-installation upgrade/migration evidence for the persisted reaction data.

Mantis proof suggestion
A short redacted web-chat interaction can directly demonstrate the missing or repaired thread reaction controls alongside the existing main-timeline behavior. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis web UI chat proof: verify and capture that reactions can be added and removed on both a channel message and a thread reply, with the updated count visible in each view.

Risk before merge

  • [P1] Reaction persistence changes stored and transported message data; the supplied proof demonstrates fresh real-app behavior but does not clearly establish upgrade-migration behavior for existing installations.
  • [P1] Accepting reactions establishes a permanent core chat interaction and API/data-model surface, so feature scope should be explicitly sponsored before merge.

Maintainer options:

  1. Complete threads and prove upgrades (recommended)
    Render and mutate reactions in the thread panel and add a focused existing-database migration/upgrade check before considering merge.
  2. Accept scoped upgrade uncertainty
    A maintainer may accept the current upgrade evidence only after explicitly confirming the migration and additive message response are safe for existing deployments.

Next step before merge

  • [P2] A narrow mechanical repair can add the existing reaction surface to the thread rendering path and extend the focused browser proof, while the separate core-scope decision remains with a maintainer.

Maintainer decision needed

  • Question: Should ClickClack adopt persisted message reactions as a permanent core interaction after the thread-rendering and upgrade-proof gaps are resolved?
  • Rationale: The patch is a substantial new user-facing interaction across API, storage, realtime, and web UI; implementation quality cannot determine whether this capability belongs in the core product.
  • Likely owner: Unattributed current-main history — Current-main ownership could not be established from the supplied metadata, so a product owner for the chat surface should make the scope decision.
  • Options:
    • Sponsor core reactions (recommended): Accept the feature direction, require the thread rendering repair and upgrade evidence, then review the completed implementation for merge.
    • Defer core adoption: Keep the PR open only if maintainers want a narrower alternative or a future product plan for reactions; otherwise decline the direction without treating the implementation as a bug fix.

Security
Cleared: The supplied diff adds no dependency source, workflow permission, secret-handling, package-publishing, or external code-execution change with a concrete security or supply-chain concern.

Review findings

  • [P2] Render reactions in thread messages — apps/web/src/components/messages/MessageRow.svelte:131-137
Review details

Best possible solution:

Render and exercise reactions in both the main timeline and thread panel, add focused upgrade-path evidence for persisted reaction data, then land only if a maintainer sponsors reactions as a core ClickClack interaction.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a bug report: this PR proposes a new capability. The contributor provides a high-confidence real-app execution path for the proposed behavior, but that does not replace the missing thread-path coverage.

Is this the best way to solve the issue?

No. The architecture and proof are substantial, but rendering controls solely through MessageRow leaves thread roots and replies without the proposed capability; the narrower maintainable fix is to share the reaction surface with ThreadPanel and prove upgrade behavior.

Full review comments:

  • [P2] Render reactions in thread messages — apps/web/src/components/messages/MessageRow.svelte:131-137
    The current patch wires ReactionsBar into MessageRow, but the supplied exact-head review establishes that ThreadPanel.svelte renders thread roots and replies directly. Reactions are therefore hydrated and reconciled for thread messages without becoming visible or actionable there; add the shared reaction UI to that rendering path and cover it in the browser test.
    Confidence: 0.98

Overall correctness: patch is incorrect
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 146f76255bcc.

Label changes

Label changes:

  • add merge-risk: 🚨 compatibility: The PR introduces persisted reaction data and extends message transport fields, so existing-installation migration behavior needs explicit confirmation.

Label justifications:

  • P2: This is a useful new interaction with a concrete thread usability defect, but it is not an urgent regression or availability/security incident.
  • merge-risk: 🚨 compatibility: The PR introduces persisted reaction data and extends message transport fields, so existing-installation migration behavior needs explicit confirmation.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (screenshot): The PR includes committed screenshot proof plus a described after-fix Playwright run against the locally built production web bundle and real Go API, covering visible persistence and realtime behavior.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR includes committed screenshot proof plus a described after-fix Playwright run against the locally built production web bundle and real Go API, covering visible persistence and realtime behavior.
  • proof: 📸 screenshot: Contributor real behavior proof includes screenshot evidence. The PR includes committed screenshot proof plus a described after-fix Playwright run against the locally built production web bundle and real Go API, covering visible persistence and realtime behavior.
Evidence reviewed

Acceptance criteria:

  • [P1] pnpm check.
  • [P1] pnpm exec playwright test tests/e2e/message-reactions.spec.ts.
  • [P1] go test ./apps/api/internal/store/sqlite ./apps/api/internal/store/postgres -run '^TestGetThreadHydratesReactions$' -count=1.

What I checked:

  • Unresolved thread rendering defect: The branch adds ReactionsBar only through MessageRow, while the supplied exact-head review confirms ThreadPanel renders thread roots and replies directly; thread reactions therefore remain invisible and cannot be toggled. (apps/web/src/components/messages/MessageRow.svelte:131, abfeb871fc30)
  • Prior exact-head review remains applicable: The previous ClawSweeper review against this same head SHA reported “Render reactions in thread messages”; no ThreadPanel change is present in the supplied 54-file diff. (apps/web/src/components/messages/MessageRow.svelte:131, abfeb871fc30)
  • Real behavior proof: The PR provides a committed real-app screenshot and describes a focused Playwright run against a locally built production bundle served by the real Go API, including persistence and realtime reconciliation scenarios. (docs/proof/message-reactions-proof.md:1, abfeb871fc30)
  • Repository SQL policy followed: The PR updates sqlc query sources and corresponding generated SQLite/PostgreSQL store code rather than hand-editing generated code alone. (apps/api/internal/store/sqlite/sqlc/queries.sql:1206, abfeb871fc30)
  • Automated validation: All seven reported checks succeeded, including Go, TypeScript, Playwright E2E, Docker Image, and the three desktop-platform jobs. (abfeb871fc30)

Likely related people:

  • Unattributed current-main history: The supplied review context identifies no prior current-main author for the affected chat and store surfaces, so ownership cannot be attributed confidently without the unavailable local history inspection. (role: likely follow-up owner; confidence: low; files: apps/web/src/components/messages/MessageRow.svelte, apps/web/src/components/messages/ThreadPanel.svelte, apps/api/internal/store/sqlite/message_pages.go)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (3 earlier review cycles)
  • reviewed 2026-07-18T23:14:46.226Z sha 61a1869 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-19T00:39:18.832Z sha 61a1869 :: needs maintainer review before merge. :: none
  • reviewed 2026-07-19T01:11:52.551Z sha abfeb87 :: found issues before merge. :: [P2] Render reactions in thread messages

@jjjhenriksen
jjjhenriksen marked this pull request as ready for review July 19, 2026 00:36

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61a1869d66

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +135 to +139
<ReactionsBar
messageId={message.id}
reactions={message.reactions ?? []}
{currentUserID}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Render reactions inside threads

This only wires ReactionsBar into the main timeline rows; the thread pane renders its root and replies directly in ThreadPanel.svelte and never includes the bar. As a result, reactions that are now hydrated and realtime-updated for selectedThread/replies are invisible and not toggleable for thread replies, which do not have a separate main-timeline row where the user could see them.

Useful? React with 👍 / 👎.

body: JSON.stringify({ emoji }),
});
}
const data = await api<{ message: { reactions?: Reaction[] } }>(`/api/messages/${messageId}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't treat refresh failures as reaction failures

When the POST/DELETE has already succeeded but this follow-up GET /api/messages/:id fails transiently, execution falls into the same catch as a failed mutation and may roll back the optimistic add/remove even though the server committed it. This leaves the current row showing an error and the opposite reaction state until a later realtime/reload corrects it; separate mutation failures from reconciliation-refresh failures or keep the committed optimistic state when only the refresh fails.

Useful? React with 👍 / 👎.

messages.some((message) => message.id === messageID) ||
replies.some((reply) => reply.id === messageID) ||
selectedThread?.id === messageID;
if (!isLoaded) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve reaction snapshots during in-flight reloads

When the reaction is for a message that is already loaded, this skips recording a pending snapshot and only mutates the current arrays. If a message-page or thread refresh that read data before the reaction resolves afterward, its stale response can overwrite the updated reactions because applyPendingReactionSnapshots has nothing to apply. Store the snapshot for loaded messages too, or version those reloads, so in-flight refreshes cannot revert the realtime reaction state.

Useful? React with 👍 / 👎.

@jjjhenriksen
jjjhenriksen force-pushed the codex/message-reactions-submit branch from 61a1869 to abfeb87 Compare July 19, 2026 01:08
@jjjhenriksen

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant