Skip to content

ELK Integration Guide

Transform your ELK Mastodon client into an AI-powered social media experience with Corgi's seamless recommendation engine. This guide covers the complete integration that makes AI recommendations appear naturally in timelines while maintaining transparency and user control.

What is "Seamless" Integration?

The ELK integration follows a "seamless" philosophy where Corgi enhances timelines invisibly:

  • Zero Configuration: Users connect to standard Mastodon servers normally
  • Transparent Operation: AI recommendations blend naturally with regular posts
  • Graceful Degradation: ELK works perfectly even if Corgi is offline
  • Privacy-First: All processing happens locally with user consent

Architecture Overview

The integration consists of four main layers:

graph TB
    subgraph UI [User Interface Layer]
        A[TimelineHome.vue]
        B[TimelineCorgi.vue]
        C[StatusCard.vue]
        D[StatusRecommendationTag.vue]
    end

    subgraph ENH [Timeline Enhancement Layer]
        E[reorderAndFilter]
        F[enhanceTimelineWithCorgi]
        G[handleRemovePost]
    end

    subgraph COMP [Composable Integration Layer]
        H[corgi-seamless.ts]
        I[corgi-status-actions.ts]
        J[User Detection and Privacy]
        K[Caching and Performance]
    end

    subgraph API [Backend API Layer]
        L[seamless endpoint]
        M[preferences endpoint]
        N[Privacy Layer and Learning System]
    end

    UI --> ENH
    ENH --> COMP
    COMP --> API

    classDef ui fill:#e3f2fd,stroke:#1976d2,color:#000
    classDef enhancement fill:#f3e5f5,stroke:#7b1fa2,color:#000
    classDef composable fill:#e8f5e8,stroke:#388e3c,color:#000
    classDef backend fill:#fff3e0,stroke:#f57c00,color:#000

    class A,B,C,D ui
    class E,F,G enhancement
    class H,I,J,K composable
    class L,M,N backend

Prerequisites

  • ELK Instance: Running at localhost:5314 (or your preferred port)
  • Corgi Backend: Running at localhost:5002 with seamless endpoints
  • Node.js Environment: For ELK development and customization
  • ELK Source Access: /Users/andrewnordstrom/Elk_Corgi/ELK

Integration Components Deep Dive

1. Core Composable (corgi-seamless.ts)

The heart of the integration is the Vue 3 composable that handles all Corgi functionality:

// Multi-method user detection
function getCurrentUserId(): string {
  // Method 1: ELK global state
  const elkGlobal = (window as any)?.$elk?.currentUser?.account?.acct

  // Method 2: Nuxt app context
  const nuxtApp = (window as any)?.$nuxt?.$currentUser?.account?.acct

  // Method 3: localStorage user handle
  const currentUserHandle = localStorage.getItem('elk-current-user-handle')

  // 5 additional fallback methods...

  return userId || 'anonymous'
}

// Timeline enhancement with intelligent injection
export function useCorgiSeamless() {
  async function enhanceTimeline(
    type: 'home' | 'local' | 'public', 
    statuses: mastodon.v1.Status[], 
    userId?: string
  ): Promise<mastodon.v1.Status[]> {
    // Fetch contextual recommendations
    const recommendations = await fetchRecommendations(20, userId)

    // Transform to Mastodon-compatible format
    const transformed = recommendations.map(transformToMastodonPost)

    // Inject intelligently into timeline
    return injectRecommendations(statuses, transformed, 0.3) // 30% ratio
  }
}

2. Timeline Components

Standard Timeline Enhancement (TimelineHome.vue)

<script setup lang="ts">
import { useCorgiSeamless } from '../../../composables/corgi-seamless'

const { enhanceTimeline, recordInteraction, isEnabled } = useCorgiSeamless()
const enhancedTimeline = ref<mastodon.v1.Status[]>([])

function reorderAndFilter(items: mastodon.v1.Status[]) {
  // Apply standard timeline reordering
  const reordered = reorderedTimeline(items, 'home')

  // Check for cached enhanced timeline
  if (enhancedTimeline.value.length > reordered.length) {
    return enhancedTimeline.value // Use cached enhanced version
  }

  // Start background enhancement
  enhanceTimelineWithCorgi(reordered).then(enhanced => {
    enhancedTimeline.value = enhanced
  })

  return reordered // Return current timeline while enhancing
}

async function enhanceTimelineWithCorgi(items: mastodon.v1.Status[]) {
  if (!isEnabled()) return items

  const enhanced = await enhanceTimeline('home', items, currentUser.value?.account?.id)
  return enhanced
}
</script>

<template>
  <div>
    <TimelinePaginator 
      :preprocess="reorderAndFilter"
      context="home"
      @status-interaction="handleStatusInteraction"
    />
  </div>
</template>

Dedicated Corgi Timeline (TimelineCorgi.vue)

<script setup lang="ts">
// Custom paginator for Corgi recommendations
const createCorgiPaginator = (): mastodon.Paginator<mastodon.v1.Status[]> => {
  return {
    async next() {
      const posts = await fetchTimeline('home', { 
        limit: 20, 
        max_id: currentMaxId.value 
      })

      // Deduplication and pagination logic
      const newPosts = posts.filter(post => !allFetchedPostIds.value.has(post.id))

      if (newPosts.length === 0) {
        hasMorePosts.value = false
        return { value: [], done: true }
      }

      return { value: newPosts, done: false }
    }
  }
}

function handleRemovePost(postId: string) {
  removedPostIds.value.add(postId)
  // Force timeline refresh
  paginator.value = createCorgiPaginator()
}
</script>

<template>
  <div>
    <div v-if="!currentUser?.account" class="anonymous-banner">
      <h3>📚 Anonymous Corgi Demo</h3>
      <p>You're viewing Corgi recommendations without being logged in.</p>
    </div>

    <TimelinePaginator 
      :paginator="paginator"
      :preprocess="reorderAndFilter"
      context="public"
      @remove-post="handleRemovePost"
    />
  </div>
</template>

3. Enhanced Status Display (StatusCard.vue)

<script setup lang="ts">
const emit = defineEmits<{
  removePost: [postId: string]
}>()

const isRemoving = ref(false)
const shouldHide = ref(false)

function handleRemovePost(postId: string) {
  isRemoving.value = true

  setTimeout(() => {
    shouldHide.value = true
    emit('removePost', postId)
  }, 400) // Animation duration
}
</script>

<template>
  <div 
    v-if="!shouldHide"
    :class="[
      'transition-all duration-400 ease-in-out',
      isRemoving ? 'opacity-0 max-h-0 scale-y-0' : 'opacity-100 max-h-screen scale-y-100'
    ]"
  >
    <!-- Standard status content -->
    <StatusContent :status="status" />

    <!-- Corgi recommendation tag -->
    <StatusRecommendationTag 
      :status="status"
      @remove-post="handleRemovePost"
    />
  </div>
</template>

4. Rich Recommendation System (StatusRecommendationTag.vue)

<script setup lang="ts">
const emit = defineEmits<{
  removePost: [postId: string]
}>()

// Parse recommendation metadata
const parsedMetadata = computed(() => {
  const metadata = status.recommendation_metadata
  return typeof metadata === 'string' ? JSON.parse(metadata) : metadata
})

// Dynamic styling based on recommendation type
const reasonIcon = computed(() => {
  const reasonCode = parsedMetadata.value.reason_code

  switch (reasonCode) {
    case 'TRENDING_TAG': return 'i-ri:fire-line'
    case 'AUTHOR_INTERACTION': return 'i-ri:user-heart-line'
    case 'SEMANTIC_MATCH': return 'i-ri:brain-line'
    case 'HIGH_ENGAGEMENT': return 'i-ri:heart-pulse-line'
    default: return 'i-ri:lightbulb-flash-line'
  }
})

const tagColor = computed(() => {
  const reasonCode = parsedMetadata.value.reason_code

  switch (reasonCode) {
    case 'TRENDING_TAG': 
      return 'bg-red-50 text-red-700 border-red-200'
    case 'AUTHOR_INTERACTION': 
      return 'bg-pink-50 text-pink-700 border-pink-200'
    case 'SEMANTIC_MATCH': 
      return 'bg-indigo-50 text-indigo-700 border-indigo-200'
    default: 
      return 'bg-green-50 text-green-700 border-green-200'
  }
})

async function handleFeedback(feedback: 'more' | 'less') {
  isAnimating.value = feedback

  await sendRecommendationFeedback(status.id, feedback)

  if (feedback === 'less') {
    isRemoving.value = true
    setTimeout(() => {
      emit('removePost', status.id)
    }, 500)
  }
}
</script>

<template>
  <div 
    v-if="shouldShow"
    :class="['recommendation-tag', tagColor]"
    class="inline-flex items-center gap-1 px-2 py-1 text-xs rounded-full border"
  >
    <component :is="reasonIcon" class="w-3 h-3" />
    <span>{{ parsedMetadata.reason_string }}</span>

    <span v-if="showScores" :class="scoreBadgeColor">
      {{ Math.round(parsedMetadata.score * 100) }}%
    </span>

    <!-- Feedback buttons -->
    <div class="feedback-buttons">
      <button @click="handleFeedback('more')" title="More like this">
        <i-ri:thumb-up-line />
      </button>
      <button @click="handleFeedback('less')" title="Less like this">
        <i-ri:thumb-down-line />
      </button>
    </div>
  </div>
</template>

Implementation Steps

Step 1: Backend Configuration

Ensure your Corgi backend supports the seamless integration endpoints:

# Verify seamless endpoint
curl -X POST http://localhost:5002/api/v1/recommendations/seamless \
  -H "Content-Type: application/json" \
  -d '{
    "timeline_type": "home",
    "existing_statuses": [
      {"id": "1", "content": "Hello world", "author": "user1"}
    ],
    "max_recommendations": 3
  }'

Expected Response:

[
  {
    "id": "rec_123",
    "content": {
      "id": "seamless_123",
      "account": { "id": "author_id", "username": "author" },
      "content": "Enhanced post content"
    },
    "score": 0.85,
    "reason": "Similar to posts you liked",
    "insertion_point": 6
  }
]

Step 2: ELK Environment Setup

Configure ELK to enable seamless integration:

# .env configuration
NUXT_PUBLIC_CORGI_SEAMLESS=true
NUXT_PUBLIC_CORGI_API_URL=http://localhost:5002
NUXT_PUBLIC_CORGI_DEBUG=true  # Development only

Step 3: User Detection Verification

Test the multi-method user detection:

// In browser console
const corgi = useCorgiSeamless()
console.log('Current user:', corgi.getCurrentUserId())

// Should detect user through multiple methods:
// - ELK global state
// - Nuxt app context
// - localStorage
// - URL parsing
// - Meta tags

Step 4: Timeline Enhancement Testing

# Start ELK development server
cd /Users/andrewnordstrom/Elk_Corgi/ELK
pnpm dev

# Open browser to localhost:5314
# Navigate to home timeline
# Look for enhanced posts with recommendation tags

Step 5: Feedback System Testing

  1. Positive Feedback: Click thumbs up on recommendations
  2. Negative Feedback: Click thumbs down to remove posts
  3. Preference Learning: Verify subsequent recommendations improve

Advanced Features

Smart User Detection

The integration uses 8 different methods to detect the current user:

// Method priorities (highest to lowest):
1. ELK global state ($elk.currentUser)
2. Nuxt app context ($nuxt.$currentUser)
3. localStorage user handle
4. localStorage accounts parsing
5. Settings key parsing
6. URL path extraction
7. Meta tag detection
8. Vue app instance state

Performance Optimization

// Recommendation caching
const recommendationCache = new Map<string, CorgiRecommendation[]>()

// Intelligent injection ratios
const injectionRatio = computed(() => {
  const userEngagement = getUserEngagementRate()
  return userEngagement > 0.7 ? 0.4 : 0.3 // More recs for engaged users
})

// Batch processing
const batchRecommendations = async (timelineChunks: Status[][]) => {
  const batched = timelineChunks.map(chunk => enhanceTimeline('home', chunk))
  return Promise.all(batched)
}

Privacy Features

// Pseudonymized user tracking
const userAlias = generateUserAlias(userId) // Never sends real user ID

// Consent management
const trackingConsent = localStorage.getItem('corgi-tracking-consent')
if (trackingConsent !== 'true') {
  // Disable interaction tracking
}

// Local processing preference
const processLocally = userSettings.value.corgiProcessLocally

User Experience Flow

First-Time User

  1. Transparent Activation: User opens ELK normally
  2. Automatic Enhancement: Timeline gets enhanced without notification
  3. Gradual Discovery: User notices interesting content naturally
  4. Optional Feedback: User can engage with recommendation tags

Returning User

  1. Learned Preferences: Recommendations improve based on history
  2. Contextual Enhancement: Recommendations match current timeline context
  3. Preference Refinement: Continuous learning from user interactions

Troubleshooting

Common Issues

No Enhanced Content

# Check Corgi API health
curl http://localhost:5002/api/v1/health

# Verify user detection
# In browser console:
console.log(useCorgiSeamless().getCurrentUserId())

Timeline Not Loading

# Check ELK logs
tail -f ~/.nuxt/logs/elk.log

# Verify timeline endpoint
curl -X GET "http://localhost:5314/api/v1/timelines/home"

Recommendation Tags Not Showing

// Check status metadata
console.log(status.recommendation_metadata)
console.log(status.is_recommendation)

// Verify settings
console.log(localStorage.getItem('corgi-show-tags'))

Debug Mode

Enable comprehensive debugging:

# Environment variable
export NUXT_PUBLIC_CORGI_DEBUG=true

# Browser console filters
# [Corgi] - Main integration logs
# [Timeline] - Timeline enhancement logs
# [StatusCard] - Post display logs

Best Practices

Development

  1. Test Graceful Degradation: Disable Corgi API and verify ELK works
  2. Monitor Performance: Watch network requests and caching
  3. Validate Privacy: Ensure no sensitive data leaves the client

Production

  1. Silent Operation: Disable debug logs in production
  2. Error Handling: Comprehensive fallback mechanisms
  3. Performance Monitoring: Track enhancement success rates

User Experience

  1. Respect User Choices: Honor feedback immediately
  2. Maintain Transparency: Clear indication of AI content
  3. Preserve Privacy: Local processing when possible

Integration Testing

Unit Tests

// Test user detection
describe('getCurrentUserId', () => {
  it('should detect user from ELK global state', () => {
    window.$elk = { currentUser: { account: { acct: 'user@instance.com' } } }
    expect(getCurrentUserId()).toBe('user@instance.com')
  })
})

// Test timeline enhancement
describe('enhanceTimeline', () => {
  it('should inject recommendations at correct intervals', async () => {
    const timeline = createMockTimeline(10)
    const enhanced = await enhanceTimeline('home', timeline)
    expect(enhanced.length).toBeGreaterThan(10)
  })
})

Integration Tests

# Test full flow
npm test e2e/seamless-integration.test.ts

# Test user interactions
npm test e2e/recommendation-feedback.test.ts

Monitoring and Analytics

Performance Metrics

// Track enhancement success rate
const enhancementMetrics = {
  requests: 0,
  successes: 0,
  averageLatency: 0,
  cacheHitRate: 0
}

// User engagement tracking
const engagementMetrics = {
  recommendationClicks: 0,
  positiveFeedback: 0,
  negativeFeedback: 0,
  timelineScrollDepth: 0
}

Health Monitoring

// Automatic health checks
setInterval(async () => {
  const health = await checkCorgiHealth()
  if (!health.ok) {
    console.warn('[Corgi] API unhealthy, disabling enhancement')
    temporarilyDisableEnhancement()
  }
}, 30000) // Check every 30 seconds

Conclusion

The ELK + Corgi integration represents a sophisticated approach to AI-enhanced social media that prioritizes user experience, privacy, and transparency. By following this guide, you'll create a seamless experience where AI recommendations enhance timelines naturally without disrupting the core Mastodon experience.

The key principles that make this integration successful:

  • Transparent Operation: Users benefit without knowing the complexity
  • Privacy First: Local processing and consent-based tracking
  • Graceful Degradation: Works perfectly even when AI is offline
  • Rich Feedback: Comprehensive user control and preference learning
  • Performance Optimized: Intelligent caching and batch processing

This integration showcases how AI can enhance social media experiences while maintaining the open, decentralized principles of the Fediverse.