sojorn/_legacy/supabase/functions/_shared/harmony.ts
Patrick Britton 3c4680bdd7 Initial commit: Complete threaded conversation system with inline replies
**Major Features Added:**
- **Inline Reply System**: Replace compose screen with inline reply boxes
- **Thread Navigation**: Parent/child navigation with jump functionality
- **Chain Flow UI**: Reply counts, expand/collapse animations, visual hierarchy
- **Enhanced Animations**: Smooth transitions, hover effects, micro-interactions

 **Frontend Changes:**
- **ThreadedCommentWidget**: Complete rewrite with animations and navigation
- **ThreadNode Model**: Added parent references and descendant counting
- **ThreadedConversationScreen**: Integrated navigation handlers
- **PostDetailScreen**: Replaced with threaded conversation view
- **ComposeScreen**: Added reply indicators and context
- **PostActions**: Fixed visibility checks for chain buttons

 **Backend Changes:**
- **API Route**: Added /posts/:id/thread endpoint
- **Post Repository**: Include allow_chain and visibility fields in feed
- **Thread Handler**: Support for fetching post chains

 **UI/UX Improvements:**
- **Reply Context**: Clear indication when replying to specific posts
- **Character Counting**: 500 character limit with live counter
- **Visual Hierarchy**: Depth-based indentation and styling
- **Smooth Animations**: SizeTransition, FadeTransition, hover states
- **Chain Navigation**: Parent/child buttons with visual feedback

 **Technical Enhancements:**
- **Animation Controllers**: Proper lifecycle management
- **State Management**: Clean separation of concerns
- **Navigation Callbacks**: Reusable navigation system
- **Error Handling**: Graceful fallbacks and user feedback

This creates a Reddit-style threaded conversation experience with smooth
animations, inline replies, and intuitive navigation between posts in a chain.
2026-01-30 07:40:19 -06:00

194 lines
5.9 KiB
TypeScript

/**
* Harmony Score Calculation
*
* Design intent:
* - Influence adapts; people are not judged.
* - Guidance replaces punishment.
* - Fit emerges naturally.
*
* Philosophy:
* - Score is private, decays over time, and is reversible.
* - Never bans or removes beliefs.
* - Shapes distribution width, not access.
*
* Inputs:
* - Blocks received (pattern-based, not single incidents)
* - Trusted reports (from high-harmony users)
* - Category friction (posting to sensitive categories with low CIS)
* - Posting cadence (erratic spikes vs steady participation)
* - Rewrite prompts triggered (content rejected for tone)
* - False reports made (reports that were dismissed)
*
* Effects:
* - Shapes distribution width (reach)
* - Adds gentle posting friction if low
* - Limits Trending eligibility
*/
export interface HarmonyInputs {
user_id: string;
blocks_received_7d: number;
blocks_received_30d: number;
trusted_reports_against: number;
total_reports_against: number;
posts_rejected_7d: number; // rewrite prompts triggered
posts_created_7d: number;
false_reports_filed: number; // reports dismissed after review
validated_reports_filed: number; // reports confirmed after review
days_since_signup: number;
current_harmony_score: number;
current_tier: string;
}
export interface HarmonyAdjustment {
new_score: number;
delta: number;
reason: string;
new_tier: string;
}
/**
* Calculate harmony score adjustment based on recent behavior
*/
export function calculateHarmonyAdjustment(inputs: HarmonyInputs): HarmonyAdjustment {
let delta = 0;
const reasons: string[] = [];
// 1. Blocks received (pattern-based)
// Single block = minor signal. Pattern of blocks = strong negative signal.
if (inputs.blocks_received_7d >= 3) {
delta -= 10;
reasons.push('Multiple blocks received recently');
} else if (inputs.blocks_received_30d >= 5) {
delta -= 5;
reasons.push('Pattern of blocks over time');
}
// 2. Trusted reports
// Reports from high-harmony users are strong negative signals
if (inputs.trusted_reports_against >= 2) {
delta -= 8;
reasons.push('Multiple reports from trusted users');
} else if (inputs.trusted_reports_against === 1) {
delta -= 3;
reasons.push('Report from trusted user');
}
// 3. Content rejection rate (rewrite prompts)
// High rejection rate indicates persistent tone issues
const rejectionRate =
inputs.posts_created_7d > 0 ? inputs.posts_rejected_7d / inputs.posts_created_7d : 0;
if (rejectionRate > 0.3) {
delta -= 6;
reasons.push('High content rejection rate');
} else if (rejectionRate > 0.1) {
delta -= 2;
reasons.push('Some content rejected for tone');
}
// 4. False reports filed
// Filing false reports is harmful behavior
if (inputs.false_reports_filed >= 3) {
delta -= 7;
reasons.push('Multiple false reports filed');
} else if (inputs.false_reports_filed >= 1) {
delta -= 3;
reasons.push('False report filed');
}
// 5. Positive signals: Validated reports
// Accurate reporting helps the community
if (inputs.validated_reports_filed >= 3) {
delta += 5;
reasons.push('Helpful reporting behavior');
} else if (inputs.validated_reports_filed >= 1) {
delta += 2;
reasons.push('Validated report filed');
}
// 6. Time-based trust growth
// Steady participation without issues slowly builds trust
if (inputs.days_since_signup > 90 && delta >= 0) {
delta += 2;
reasons.push('Sustained positive participation');
} else if (inputs.days_since_signup > 30 && delta >= 0) {
delta += 1;
reasons.push('Consistent participation');
}
// 7. Natural decay toward equilibrium (50)
// Scores gradually drift back toward 50 over time
// This ensures old negative signals fade
if (inputs.current_harmony_score < 45) {
delta += 1;
reasons.push('Natural recovery over time');
} else if (inputs.current_harmony_score > 60) {
delta -= 1;
reasons.push('Natural equilibrium adjustment');
}
// 8. Calculate new score with bounds [0, 100]
const new_score = Math.max(0, Math.min(100, inputs.current_harmony_score + delta));
// 9. Determine tier based on new score
let new_tier: string;
if (new_score >= 75) {
new_tier = 'established';
} else if (new_score >= 50) {
new_tier = 'trusted';
} else if (new_score >= 25) {
new_tier = 'new';
} else {
new_tier = 'restricted';
}
return {
new_score,
delta,
reason: reasons.join('; ') || 'No significant changes',
new_tier,
};
}
/**
* Get user-facing explanation of harmony score (without revealing the number)
*/
export function getHarmonyExplanation(tier: string, score: number): string {
if (tier === 'restricted') {
return 'Your posts currently have limited reach. This happens when content patterns trigger community concerns. Your reach will naturally restore over time with calm participation.';
}
if (tier === 'new') {
return 'Your posts reach a modest audience while you build trust. Steady participation and helpful contributions will gradually expand your reach.';
}
if (tier === 'trusted') {
return 'Your posts reach a good audience. You have shown consistent, calm participation.';
}
if (tier === 'established') {
return 'Your posts reach a wide audience. You have built strong trust through sustained positive contributions.';
}
return 'Your reach is determined by your participation patterns and community response.';
}
/**
* Determine reach multiplier for feed algorithms
*/
export function getReachMultiplier(tier: string, score: number): number {
const baseMultiplier: Record<string, number> = {
restricted: 0.2,
new: 0.6,
trusted: 1.0,
established: 1.4,
};
// Fine-tune based on score within tier
const tierBase = baseMultiplier[tier] || 1.0;
const scoreAdjustment = (score - 50) / 200; // -0.25 to +0.25
return Math.max(0.1, tierBase + scoreAdjustment);
}