DEV Community

real name hidden
real name hidden

Posted on

I replaced the like button with a weighted slider. Here's the architecture behind it.

Every social platform has a reaction system. Facebook has six emoji reactions. Twitter has a heart. Instagram has a heart. Reddit has upvote/downvote. They all share the same fundamental design: a binary or near-binary signal that tells the algorithm "more of this" or "less of this."
I built a social platform called LINKWAVEZ where the reaction system works differently. Instead of a like button, users respond with a Resonance Slider — a continuous input that weights their reaction between two values: Aura (emotional impact) and Wisdom (intellectual impact).
This post explains the technical architecture behind that system and the dual scoring engine it feeds into.
The Resonance Slider
When a user sees a post (we call them Poses), they can react by dragging a slider between two poles. Full left is pure Aura — the post moved them emotionally. Full right is pure Wisdom — the post made them think. Most reactions land somewhere in the middle.
The slider outputs a value between 0.0 and 1.0, where 0.0 is pure Aura and 1.0 is pure Wisdom. The database stores both the raw slider value and the computed weights:
sqlCREATE TABLE pose_reactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pose_id UUID NOT NULL REFERENCES poses(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
slider_value FLOAT NOT NULL CHECK (slider_value >= 0 AND slider_value <= 1),
aura_weight FLOAT GENERATED ALWAYS AS (1.0 - slider_value) STORED,
wisdom_weight FLOAT GENERATED ALWAYS AS (slider_value) STORED,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(pose_id, user_id)
);
The aura_weight and wisdom_weight columns are generated columns — computed automatically from the slider value. This avoids any client-server mismatch in weight calculation.
How reactions flow into the scoring system
LINKWAVEZ has a dual scoring system: every user has an Aura score and a Wisdom score. These are aggregate reputation metrics that reflect how you impact the community.
When someone reacts to your post with the Resonance Slider, the reaction flows through a scoring pipeline:
Reaction event
→ Calculate base points (3 points per reaction)
→ Split by slider weights
→ Apply area multiplier (Feed area = 1.0x)
→ Check daily caps
→ Credit to post creator's Aura and Wisdom
→ Update area scores
→ Recalculate overall scores
Here's the Node.js handler:
javascriptasync function processReaction(poseId, reactorId, sliderValue) {
const basePoints = 3;
const auraPoints = basePoints * (1.0 - sliderValue);
const wisdomPoints = basePoints * sliderValue;

// Check daily caps before crediting
const todayScores = await getTodayScores(creatorId, 'feed');

const cappedAura = Math.min(
auraPoints,
DAILY_AURA_CAP_FEED - todayScores.aura
);
const cappedWisdom = Math.min(
wisdomPoints,
DAILY_WISDOM_CAP_FEED - todayScores.wisdom
);

if (cappedAura > 0 || cappedWisdom > 0) {
await creditScores(creatorId, 'feed', cappedAura, cappedWisdom);
}
}
The daily cap system
This is where the design gets interesting from an anti-gaming perspective.
LINKWAVEZ has 9 scoring areas: Feed, Moments, Groups, Marketplace, Events, Social, Mentorship, Partnership, and Echo. Each area has separate Aura and Wisdom sub-scores. The overall score is a weighted average across all areas.
Every area has a daily cap. The total maximum across all areas is 275 Aura points per day. No matter how viral your content goes, you can't earn more than the cap in a single day.
javascriptconst DAILY_CAPS = {
feed: { aura: 70, wisdom: 50 },
moments: { aura: 30, wisdom: 30 },
groups: { aura: 40, wisdom: 35 },
marketplace: { aura: 25, wisdom: 15 },
events: { aura: 25, wisdom: 15 },
social: { aura: 35, wisdom: 25 },
mentorship: { aura: 20, wisdom: 30 },
partnership: { aura: 15, wisdom: 20 },
echo: { aura: 15, wisdom: 25 },
};
// Total max aura/day: 275
Why does this matter technically? Because the cap check needs to be atomic. If 50 people react to your post simultaneously, you can't just credit all of them and check the cap afterward — you'd overshoot. The solution is a PostgreSQL function with row-level locking:
sqlCREATE OR REPLACE FUNCTION credit_score_capped(
p_user_id UUID,
p_area TEXT,
p_aura FLOAT,
p_wisdom FLOAT
) RETURNS TABLE(credited_aura FLOAT, credited_wisdom FLOAT) AS $$
DECLARE
current_daily RECORD;
cap RECORD;
actual_aura FLOAT;
actual_wisdom FLOAT;
BEGIN
-- Lock the user's daily score row
SELECT * INTO current_daily
FROM daily_scores
WHERE user_id = p_user_id
AND area = p_area
AND score_date = CURRENT_DATE
FOR UPDATE;

-- Calculate what we can actually credit
actual_aura := LEAST(p_aura, cap.aura - COALESCE(current_daily.aura, 0));
actual_wisdom := LEAST(p_wisdom, cap.wisdom - COALESCE(current_daily.wisdom, 0));

-- Credit
INSERT INTO daily_scores (user_id, area, score_date, aura, wisdom)
VALUES (p_user_id, p_area, CURRENT_DATE, actual_aura, actual_wisdom)
ON CONFLICT (user_id, area, score_date)
DO UPDATE SET
aura = daily_scores.aura + actual_aura,
wisdom = daily_scores.wisdom + actual_wisdom;

RETURN QUERY SELECT actual_aura, actual_wisdom;
END;
$$ LANGUAGE plpgsql;
The FOR UPDATE lock ensures that concurrent reactions don't blow past the cap. This is critical for any scoring system that has limits.
The Emotional Temperature system
Every post in LINKWAVEZ carries an Emotional Temperature — a mood tag set by the creator: Calm, Warm, Playful, Intense, or Charged.
This feeds into two systems:

  1. Feed filtering. Users can filter their feed by energy level using the Vibe Spectrum Filter. The filter maps temperatures to a spectrum from Deep to Energized: Deep ← Calm ← Warm ← Playful → Intense → Charged → Energized The query is straightforward — it's just a WHERE clause on the temperature column. But the UX impact is significant. A user having a rough morning can filter to Calm and see only gentle, reflective content. The feed respects their emotional state.
  2. Auto Emoji Vibe. After a post receives 3 or more replies, the system analyzes the reply sentiment and assigns an emoji vibe to the post card. This is done with a simple keyword frequency analysis across all replies: javascriptfunction detectVibe(replies) { const signals = { hilarious: 0, fire: 0, heartfelt: 0, mindblown: 0 };

for (const reply of replies) {
const text = reply.content.toLowerCase();
if (/haha|lol|😂|🤣|funny|hilarious/.test(text)) signals.hilarious++;
if (/🔥|fire|amazing|incredible|insane/.test(text)) signals.fire++;
if (/❤️|💜|love|beautiful|heart|crying/.test(text)) signals.heartfelt++;
if (/🤯|mind|blown|wow|whoa|never thought/.test(text)) signals.mindblown++;
}

const dominant = Object.entries(signals)
.sort((a, b) => b[1] - a[1])[0];

if (dominant[1] >= 3) return dominant[0];
return null;
}
When a vibe is detected, the post card gets a subtle color tint and emoji badge. It's a small touch but it makes the feed feel alive and responsive to the community's actual reactions.
The feed algorithm: 70/20/10
Most social feeds show you more of what you already like. LINKWAVEZ deliberately breaks that pattern.
The feed composition is:

70% content aligned with your interests and values
20% content from different perspectives (the Echo Chamber Breaker)
10% pure discovery from random users

The implementation uses a weighted random sampling approach. For each feed load, we pull three separate query results and interleave them:
javascriptasync function buildFeed(userId, page, perPage) {
const alignedCount = Math.round(perPage * 0.7);
const differentCount = Math.round(perPage * 0.2);
const discoveryCount = perPage - alignedCount - differentCount;

const [aligned, different, discovery] = await Promise.all([
getAlignedPosts(userId, alignedCount),
getDifferentPerspectives(userId, differentCount),
getRandomDiscovery(userId, discoveryCount),
]);

// Interleave: don't cluster all "different" posts together
return interleave(aligned, different, discovery);
}
The getDifferentPerspectives query is the interesting one. It finds users whose reaction patterns on shared content differ significantly from the current user. If you consistently slide toward Wisdom on political posts and another user consistently slides toward Aura on the same posts, you have different perspectives worth exposing to each other.
Users can adjust the 70/20/10 ratio with sliders in settings. Some people want more challenge. Some want more comfort. But the default is intentionally set to gently expand your bubble.
Group auto-evolution
One more system worth discussing: groups in LINKWAVEZ evolve automatically based on community activity.
Every group starts as a Ripple (2-12 members). When it hits 13 active members and meets engagement thresholds, it auto-evolves into a Current (up to 100 members). Hit 100 with sustained activity, and it becomes a Tide (unlimited).
Each evolution unlocks new features: more post types, admin tools, analytics, pin slots. The evolution check runs as a scheduled job:
javascript// Runs daily at midnight
async function checkGroupEvolutions() {
const ripples = await getGroupsByStage('ripple');

for (const group of ripples) {
const activeMembers = await countActiveMembers(group.id, 7); // active in last 7 days
const weeklyPosts = await countRecentPosts(group.id, 7);

if (activeMembers >= 13 && weeklyPosts >= 20) {
  await evolveGroup(group.id, 'current');
  await notifyMembers(group.id, 'Your Ripple has grown into a Current!');
}
Enter fullscreen mode Exit fullscreen mode

}
}
The auto-evolution removes the burden from group admins to manually "upgrade" their community. The system recognizes organic growth and responds to it.
The stack
For anyone building something similar:

Flutter for cross-platform mobile (single codebase for Android and iOS)
Node.js for the backend API (Express, handles scoring logic and scheduled jobs)
Supabase for auth, PostgreSQL database, Realtime subscriptions, and file storage
Row Level Security policies handle all data access control at the database level

Total monthly infrastructure cost for the current scale: under $100. Supabase's Pro plan handles a surprising amount of traffic before you need to think about scaling.
What's live
LINKWAVEZ has over 150 features across social feed, groups, marketplace, events, mentorship, and wellness modules. It's live on Google Play as "LINKWAVEZ: Calm Social."
If you're interested in the technical side of building social platforms — especially the scoring, feed algorithms, or real-time architecture — happy to go deeper in the comments.
Download: [https://play.google.com/store/apps/details?id=com.linkwavez.app&pcampaignid=web_share
 ]
Website: linkwavez.com

Top comments (0)