Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
+++ src/routes/posts.js	(revision f1956d7c01134c1d4e87af110af984c14a84d8b0)
@@ -580,6 +580,7 @@
   const sent = !!req.query.sent;
   const followed = !!req.query.followed;
+  const voted = !!req.query.voted;
   let target = null, followTarget = null;
-  if (!sent && !followed && uri) {
+  if (!sent && !followed && !voted && uri) {
     try { target = await ActivityPubService.resolveRemoteNote(uri); } catch { /* ignore */ }
     // Not a post? Maybe the URI is a profile/actor → offer Follow, not reply.
@@ -594,4 +595,5 @@
     sent,
     followed,
+    voted: !!req.query.voted,
     liked: !!req.query.liked,
     boosted: !!req.query.boosted,
@@ -599,4 +601,16 @@
     siteTitle: site ? site.title : '',
   });
+});
+
+// 📊 Vote on a remote fediverse poll from the interact page (any poll by URL, not just
+// followed ones). Casts the Mastodon-standard ballot straight to the poll's author.
+router.post('/authorize_interaction/vote', requireSiteManager, async (req, res) => {
+  const site = res.locals.site;
+  const uri = (req.body.uri || '').toString();
+  let choice = req.body.choice;
+  if (choice == null) choice = [];
+  if (!Array.isArray(choice)) choice = [choice];
+  if (site && uri && choice.length) { try { await ActivityPubService.voteOnRemotePoll(site, uri, choice.map(String)); } catch { /* ignore */ } }
+  res.redirect('/authorize_interaction?voted=1&uri=' + encodeURIComponent(uri));
 });
 
