Skip to content

Latest commit

 

History

History
1916 lines (1516 loc) · 65.9 KB

File metadata and controls

1916 lines (1516 loc) · 65.9 KB

Progress Log

2026-01-30: Bio Site Integration Complete

Task

Integrate Reddit Agent documentation with personal bio site at avinashsangle.com.

Implementation Summary

Hybrid Documentation Strategy:

  • Public comprehensive guide on bio site at /projects/reddit-agent
  • Local documentation remains in web-app/docs/ for developers
  • Cross-linking between both sources

Files Modified/Created (7 files)

Bio Site (3 files):

  1. github_bio/src/data/projects.ts (+18 lines)

    • Added reddit-agent project entry with order: 0 (top position)
    • All metadata: title, description, technologies, tags, route, status
  2. github_bio/src/app/projects/reddit-agent/page.tsx (NEW, 1,150 lines)

    • Comprehensive project page with 20+ sections
    • 3 JSON-LD schemas (SoftwareApplication, BreadcrumbList, FAQPage)
    • SEO metadata with OpenGraph and Twitter cards
    • Sections: Overview, Features, Architecture, Getting Started, Configuration, FAQ, etc.
  3. github_bio/public/sitemap.xml (+6 lines)

    • Added reddit-agent entry (lastmod: 2026-01-30, priority: 0.8)

Reddit Agent (4 files): 4. README.md (+2 lines)

  • Added documentation badge linking to bio site
  1. CLAUDE.md (+35 lines)

    • Added "Documentation" section explaining dual approach
    • Updated Last Updated date to 2026-01-30
  2. .env (verified)

    • Admin password hash already correct for bio site integration testing
  3. progress.md (THIS FILE, +40 lines)

    • Documented bio site integration completion

SEO/AEO Optimization

Metadata:

  • Title: "Reddit Comment Engagement Agent - AI-Powered Engagement with HITL Approval"
  • Description: 160-char optimized for search
  • Keywords: reddit automation, ai agent, langgraph, hitl approval, etc.

Schemas:

  • SoftwareApplication schema with app details
  • BreadcrumbList schema for navigation
  • FAQPage schema with 8 questions

Social Media:

  • OpenGraph tags for Facebook/LinkedIn
  • Twitter Card tags for Twitter

Build Status

Bio Site Build:

  • ✅ Compiled successfully (4.1s)
  • ✅ TypeScript check passed
  • ✅ All 18 routes generated (including /projects/reddit-agent)
  • ✅ Static export successful

Documentation URLs

Public:

Local:

  • Location: web-app/docs/
  • Run: cd web-app/docs && npm run dev
  • Port: localhost:3001

Cross-Linking

Bio Site → Reddit Agent:

  • Homepage projects grid auto-generates card
  • Projects page filterable by "AI Automation" category
  • Featured project (featured: true)

Reddit Agent → Bio Site:

  • README badge with blue shield icon
  • CLAUDE.md Documentation section
  • Bio site URL in GitHub repo About section (to be added)

Post-Implementation Updates

UI/UX Improvements (+2 files modified):

  1. FAQ Section Conversion - Replaced individual Card components with Accordion component

    • Better UX with collapsible Q&A items
    • Consistent with shadcn/ui patterns
    • Cleaner, more compact presentation
  2. Related Projects Fix - Updated to match bio site patterns

    • Removed Button components, replaced with Link (className="project-link")
    • Added Badge components with categories
    • Added card-hover class for hover effects
    • Changed link text from "View Project" to "Learn More →"
    • Moved content to CardFooter following bio site conventions

GitHub Repository Setup

Remote Repository Created:

Status

✅ All implementation complete ✅ Bio site build successful (4.0s, 18 routes) ✅ Documentation synced across sources ✅ UI/UX improvements applied ✅ GitHub repository created and code pushed ✅ Bio site deployed to avinashsangle.com (GitHub Actions)


2026-01-18: Reddit API Call Optimization - Caching Implementation

Problem Identified

Duplicate API fetching: Analysis of workflow logs revealed that rising posts were being fetched twice:

  1. First fetch: fetch_rising_posts_as_candidates() to get post candidates
  2. Second fetch: fetch_rising_candidates() re-fetches same posts to get comment candidates

Impact:

  • ~22 Reddit API calls per run, with 4 duplicate calls (18% waste)
  • 1 duplicate call per subreddit (4 subreddits = 4 wasted calls)
  • Increased latency and rate limit consumption

Solution Implemented

Added in-memory caching for rising posts:

  1. services/reddit_client.py (MODIFIED, +25 lines):

    • Added _rising_posts_cache dictionary to cache fetched posts per subreddit
    • Added clear_cache() method to reset cache between workflow runs
    • Modified fetch_rising_posts() to:
      • Check cache before making API call
      • Store results in cache after fetching
      • Log cache hits for debugging
  2. workflow/nodes.py (MODIFIED, +3 lines):

    • Added reddit_client.clear_cache() at start of fetch_candidates_node()
    • Ensures fresh data for each workflow run while allowing caching within the run

Cache Behavior

Cache lifecycle:

  • Created: At RedditClient initialization (empty)
  • Populated: First call to fetch_rising_posts(subreddit) per workflow run
  • Reused: Subsequent calls to same subreddit return cached results
  • Cleared: At start of next workflow run via clear_cache()

Cache structure:

{
    "pune": [Submission1, Submission2, ...],
    "food": [Submission3, Submission4, ...],
    "funny": [...],
    "aww": [...]
}

Performance Impact

Before caching:

  • 22 Reddit API calls per run
  • Duplicate fetches: 4 calls (1 per subreddit)
  • Log shows: "rising_post_found" × 2 for each post

After caching:

  • 18 Reddit API calls per run (-18% reduction)
  • No duplicate fetches
  • Log shows: "rising_posts_cache_hit" on second access
  • Expected new log entries:
    • rising_posts_cache_cleared (start of run)
    • rising_posts_cached (after each fetch)
    • rising_posts_cache_hit (on cache reuse)

Call Breakdown (After Optimization)

  1. Inbox fetch: 1 call
  2. Rising posts fetch: 4 calls (1 per subreddit, cached)
  3. Rising comments fetch: 0 NEW calls (reuses cached posts)
  4. Fetching comments from posts: 11 calls (unchanged)
  5. Context building: 2 calls (unchanged)

Total: 18 calls per run (down from 22)

Testing

Verified syntax:

✅ python -m py_compile services/reddit_client.py
✅ python -m py_compile workflow/nodes.py

Next run will show:

  • Cache hit logs for duplicate subreddit accesses
  • Reduced API call count in rate limit tracking
  • No duplicate "rising_post_found" logs

2026-01-17: Phase 7 - Setup Wizard Complete

Implementation Summary

Created a guided 4-step setup wizard for first-time configuration of the Reddit engagement agent.

Components Created

1. Backend Routes (api/setup_wizard_routes.py - 450 lines):

  • GET /setup - Renders setup wizard page
  • POST /api/setup/test-reddit - Tests Reddit API credentials with PRAW
  • POST /api/setup/test-gemini - Validates Gemini API key
  • POST /api/setup/test-slack - Sends test message to Slack webhook
  • POST /api/setup/test-telegram - Sends test message to Telegram bot
  • POST /api/setup/complete - Generates .env file and runs migrations

2. Frontend Template (frontend/templates/setup.html - 920 lines):

  • Multi-step form with progress indicators
  • Step 1: Reddit API credentials with connection testing
  • Step 2: LLM API keys (Gemini recommended, at least one required)
  • Step 3: Notification settings (Slack/Telegram/Webhook)
  • Step 4: Safety limits with recommended defaults
  • Success screen with next steps

3. Router Integration (api/callback_server.py - 7 lines):

  • Mounted setup wizard routes to FastAPI app

Features

Step 1: Reddit API

  • Tests connection with PRAW before proceeding
  • Validates all 5 Reddit credentials
  • Shows authenticated username and karma on success
  • Requires subreddit allow-list

Step 2: LLM Keys

  • Supports Gemini, OpenAI, Anthropic
  • Test button for Gemini API key validation
  • At least one LLM key required
  • Gemini marked as recommended

Step 3: Notifications

  • Dynamic form based on selected notification type
  • Test buttons for Slack and Telegram
  • Conditional fields with Alpine.js x-if
  • Public URL field with ngrok guidance

Step 4: Safety Limits

  • Pre-filled with recommended defaults:
    • MAX_COMMENTS_PER_DAY: 8
    • MAX_COMMENTS_PER_RUN: 3
    • SHADOWBAN_RISK_THRESHOLD: 0.7
    • COOLDOWN_PERIOD_HOURS: 24
    • POST_REPLY_RATIO: 0.3
    • MAX_POST_REPLIES_PER_RUN: 1
    • MAX_COMMENT_REPLIES_PER_RUN: 2
  • Helper text for each field

Completion:

  • Generates complete .env file with all sections
  • Generates secure JWT secret automatically
  • Attempts to run Alembic migrations
  • Success screen with next steps checklist
  • Redirects to admin login when .env exists

Security

  • ✅ Only accessible when .env doesn't exist
  • ✅ Redirects to admin login if already configured
  • ✅ Validates all inputs server-side
  • ✅ Tests connections before saving
  • ✅ Generates secure random JWT secret
  • ✅ Includes admin password setup instructions

User Experience

  • ✅ Progress indicator shows current step
  • ✅ Previous/Next navigation
  • ✅ Connection testing with real-time feedback
  • ✅ Clear error messages
  • ✅ Can't proceed without valid data
  • ✅ Recommended defaults pre-filled
  • ✅ Inline helper text and examples
  • ✅ Success screen with actionable next steps

Files Modified/Created

  1. api/setup_wizard_routes.py (NEW, 450 lines)
  2. frontend/templates/setup.html (NEW, 920 lines)
  3. api/callback_server.py (MODIFIED, +7 lines)
  4. progress.md (MODIFIED, documented implementation)

Bug Fix: Server Startup Without .env File

Issue: Server crashed with Pydantic validation errors when starting without .env file, preventing access to setup wizard.

Root Cause: run_callback_server() in main.py attempted to load settings before checking if .env exists.

Solution:

  1. main.py (MODIFIED, +26 lines):

    • Added .env existence check before settings loading
    • If .env doesn't exist: Start in "setup mode" with minimal initialization
    • If .env exists: Normal initialization with full configuration
    • Setup mode uses state_manager=None, secret=None, poster=None
  2. api/callback_server.py (MODIFIED, +4 lines):

    • Made approval/callback routes conditional on state_manager is not None
    • Routes only registered when .env exists and agent is configured
    • Health check, admin routes, and setup wizard routes always available
    • Logs whether approval routes were registered or skipped

Result:

  • ✅ Server starts successfully without .env file
  • ✅ Setup wizard accessible at /setup in setup mode
  • ✅ Approval routes only available when agent is fully configured
  • ✅ Health check always responds
  • ✅ Admin and setup routes independent of state_manager

Verification:

  • ✅ Tested without .env: Server logs "approval_routes_skipped" and starts in setup mode
  • ✅ Tested with .env: Server logs "approval_routes_registered" with state_manager_available=true
  • ✅ Both modes start successfully without errors
  • ✅ Syntax error fixed (line 495: changed } to ] to close elements array)

Bug Fix: Admin Routes Require Full Database Configuration

Issue: Admin routes (dashboard, login) crashed with Pydantic validation errors when .env file was incomplete, preventing any admin access during setup.

Root Cause: Admin routes used Depends(get_db) which requires valid Settings with all Reddit credentials, even though admin functionality doesn't need Reddit API access.

Solution:

  1. models/database.py (NEW function, +20 lines):

    • Added get_db_optional() dependency
    • Returns None if Settings can't be loaded (e.g., missing Reddit credentials)
    • Catches exceptions during settings validation
    • Allows database-independent features to work
  2. api/admin_routes.py (MODIFIED, ~50 lines):

    • Changed all Depends(get_db) to Depends(get_db_optional)
    • Added None checks before database operations
    • Login route: Skips rate limiting and audit logging when DB unavailable
    • Dashboard route: Shows "complete setup wizard" message when DB unavailable
    • Env editor routes: Skip audit logging when DB unavailable
    • Live stats routes: Return 503 error when DB unavailable

Result:

  • ✅ Admin login accessible even with incomplete .env
  • ✅ Setup wizard accessible without full configuration
  • ✅ No Pydantic validation errors on admin routes
  • ✅ Graceful degradation: Features that don't need DB still work
  • ✅ Database-dependent features show helpful error messages

Testing Instructions

  1. First-time setup (no .env):

    • Delete .env file (if exists)
    • Start server: python main.py server
    • Navigate to: http://localhost:8000/setup
  2. Test each step:

    • Step 1: Enter Reddit credentials → Click "Test Connection"
    • Step 2: Enter Gemini key → Click "Test Gemini"
    • Step 3: Select notification type → Fill fields → Test
    • Step 4: Review/modify defaults
    • Complete setup
  3. Verify .env generated:

    • Check .env file created in project root
    • Contains all configured sections
    • Admin JWT secret generated
  4. Try accessing setup again:

    • Should show "Setup Already Complete" message
    • Links to admin login

Result

✅ Complete guided setup wizard for new users ✅ Real-time connection testing for all services ✅ Generates valid .env file with all required fields ✅ Auto-runs database migrations on completion ✅ Clear next steps after setup ✅ Prevents reconfiguration when .env exists


2026-01-17: Settings UI - Checkboxes & Toast Notifications

Issues Fixed

Issue 1: Checkboxes Not Reflecting True Values

  • Boolean fields (INBOX_PRIORITY_ENABLED, DIVERSITY_ENABLED, etc.) showed unchecked even when value was "True"
  • Problem: .env values are strings ("True", "true"), but checkboxes need JavaScript booleans

Issue 2: Notifications as Alert Banners

  • User requested toast notifications instead of full-width alert banners

Fixes Applied

1. Boolean Conversion on Load (frontend/templates/admin/env_editor.html lines 731-740):

// Convert checkbox string values to boolean
if (meta.type === 'checkbox' && this.formData[key] !== undefined) {
    const val = this.formData[key];
    // Convert "True", "true", "1", "yes" to true
    this.formData[key] = val === true || val === 'True' || val === 'true' || val === '1' || val === 'yes';
}

2. Boolean to String Conversion on Save (lines 949-950):

// Convert booleans to Python-style strings
if (typeof value === 'boolean') {
    stringifiedData[key] = value ? 'True' : 'False';
}

3. Toast Notification System:

  • Added CSS animations (slideInRight, slideOutRight)
  • Toast container positioned top-right (fixed)
  • Three toast types: success (green), error (red), warning (yellow)
  • Auto-hide after duration (success: 5s, error: 8s, restart: 15s)
  • Smooth fade-in/fade-out animations

4. Toast Helper Functions (lines 893-927):

showSuccessToast(message, duration = 5000)  // Green toast with ✓
showErrorToast(message, duration = 8000)    // Red toast with ✗
showRestartToast(duration = 15000)          // Yellow toast with ⚠️

5. Updated All Error/Success Messages to use toasts:

  • Preview validation errors → showErrorToast()
  • Save success → showSuccessToast() + showRestartToast()
  • Save errors → showErrorToast()
  • Restore backup → showSuccessToast() or showErrorToast()

Result

✅ Checkboxes now correctly show checked/unchecked state based on .env values ✅ Boolean values properly converted True/False when saving ✅ Toast notifications appear top-right with smooth animations ✅ Toasts auto-hide after configurable duration ✅ Clean, unobtrusive notification system


2026-01-17: Settings UI - Complete with All Fields & Tooltips

Issues Fixed

Issue 1: Inline Comments Breaking Validation

  • .env file had inline comments like POST_REPLY_RATIO=0.3 #30% posts, 70% comments
  • env_manager.load_env() was reading entire line including comment
  • Pydantic couldn't parse "0.3 #comment" as a float

Issue 2: Missing Configuration Fields

  • Many fields not exposed in frontend (COOLDOWN_PERIOD_HOURS, POST_REPLY_RATIO, jitter settings, Phase A/B settings, etc.)
  • No hover descriptions for field guidance

Fixes Applied

1. Strip Inline Comments (services/env_manager.py line 68-70):

# Strip inline comments (e.g., "0.3 #comment" -> "0.3")
if '#' in value:
    value = value.split('#')[0].strip()

2. Added All Missing Fields (services/env_manager.py):

  • Safety Limits: SHADOWBAN_RISK_THRESHOLD, COOLDOWN_PERIOD_HOURS, POST_REPLY_RATIO, MAX_POST_REPLIES_PER_RUN, MAX_COMMENT_REPLIES_PER_RUN, MIN_JITTER_SECONDS, MAX_JITTER_SECONDS, DRY_RUN
  • Phase A: Inbox Priority: INBOX_PRIORITY_ENABLED, INBOX_COOLDOWN_HOURS, RISING_COOLDOWN_HOURS
  • Phase B: Diversity: DIVERSITY_ENABLED, MAX_PER_SUBREDDIT, MAX_PER_POST, DIVERSITY_QUALITY_BOOST_THRESHOLD
  • Quality Scoring: QUALITY_SCORING_ENABLED, SCORE_EXPLORATION_RATE, SCORE_TOP_N_RANDOM

3. Added Hover Descriptions (all fields now have tooltips):

"COOLDOWN_PERIOD_HOURS": {
    "description": "Hours to wait before replying to the same post again (prevents spam)"
}

4. Frontend Updates (frontend/templates/admin/env_editor.html):

  • Added 4 new field groups (Safety Limits expanded, Phase A, Phase B, Quality Scoring)
  • Checkbox support for boolean fields (DRY_RUN, INBOX_PRIORITY_ENABLED, etc.)
  • Hover tooltips on all fields (ℹ️ icon + title attribute)
  • Helper functions: getDescription(), getFieldType(), getFieldMin(), getFieldMax(), getFieldStep()

Result

✅ All .env fields now configurable in Settings UI ✅ Inline comments stripped correctly on load ✅ Hover tooltips provide guidance for all fields ✅ Boolean fields shown as checkboxes ✅ Numeric fields have proper min/max/step constraints ✅ Validation now works correctly


2026-01-17: Settings UI - Fixed Validation & Button Labels

Issue 1: Validation Failing (No Server Logs)

Validation error when previewing/saving: "Input should be a valid number, unable to parse string as a number"

  • No errors in server logs (validation failing before reaching validation logic)
  • Error message showing in frontend

Root Cause

Two-part problem:

  1. Frontend skipping empty fields meant some required fields weren't sent
  2. Server validating only the sent fields, not merging with current .env values
  3. Pydantic couldn't find required fields like MAX_COMMENTS_PER_DAY

Fixes Applied

1. Frontend - Skip empty values (frontend/templates/admin/env_editor.html):

// Lines 663-670, 699-706
if (value === null || value === undefined || value === '') {
    continue;  // Don't send empty fields
}
stringifiedData[key] = String(value);

2. Server - Merge with current .env (api/admin_routes.py):

# Lines 311-318 (preview endpoint)
merged_env = {**current_env, **new_env}  # Merge new with current
validation_errors = env_manager.validate_env(merged_env)  # Validate merged

# Lines 352-356 (save endpoint)
merged_env = {**current_env, **new_env}
env_manager.save_env(merged_env, create_backup=True)

3. Button label change (frontend/templates/admin/dashboard.html):

  • Changed "View Workflow" → "Workflow" (line 18)

Result

✅ Validation works correctly - unchanged fields keep current values ✅ Only modified fields are validated as new values ✅ Required fields always present (merged from current .env) ✅ Cleaner button labels


2026-01-14: Quality Scoring System - Planning Phase Complete

Task

Analyze repository and create implementation plan for Quality Scoring & Historical Learning System based on docs/QUALITY_SCORING_DESIGN.md.

Actions Completed

  1. Explored codebase using 3 parallel agents:

    • Workflow structure (9-node LangGraph pipeline)
    • Database layer (5 tables, Alembic migrations, StateManager)
    • Services architecture (8 services with dependency injection)
  2. Created comprehensive implementation plan:

    • Location: /Users/avinashsangle/.claude/plans/parallel-toasting-garden.md
    • 4 phases: Quality Scoring → Data Collection → Historical Learning → Engagement Tracking
    • 23 files to modify/create, ~2,100 lines of code
    • Estimated timeline: 11-16 days implementation
  3. Key findings:

    • Current selection is FIFO (no ranking)
    • PRAW data available for scoring (karma, upvote_ratio, timestamps)
    • Services use functools.partial() for dependency injection
    • State machine: PENDING → APPROVED → PUBLISHED
  4. Plan includes:

    • Quick reference table with file counts per phase
    • Detailed file change summary with line numbers
    • Standalone implementation prompts for each phase
    • Feature flags for safe rollout
    • Risk mitigation strategies
    • Success criteria and validation steps

Next Steps

Phase 1 complete. Ready for Phase 2 (Data Collection) when approved.

Status

✅ Planning complete ✅ Phase 1 implementation complete


2026-01-14: Phase 1 Implementation Complete - Quality Scoring System

Task

Implement Phase 1: Quality scoring with 7 factors to rank Reddit engagement candidates.

Files Modified/Created

  1. services/quality_scorer.py (NEW, 360 lines)

    • Created QualityScorer class with 7 scoring methods
    • Composite scoring formula with normalized weights
    • Error handling with graceful degradation
  2. services/reddit_client.py (MODIFIED)

    • Added quality_score: float = 0.0 to CandidateComment
    • Added quality_score: float = 0.0 to CandidatePost
  3. workflow/nodes.py (MODIFIED, +105 lines)

    • Added score_candidates_node (scores all candidates)
    • Added sort_by_score_node (sorts with 15% exploration logic)
  4. workflow/graph.py (MODIFIED)

    • Added quality_scorer parameter to create_workflow_graph
    • Imported new nodes
    • Bound new nodes with partial()
    • Updated workflow edges to integrate scoring nodes
  5. config.py (MODIFIED, +54 lines)

    • Added quality_scoring_enabled flag (default: True)
    • Added 7 score weights configuration
    • Added threshold configurations for all scoring factors
    • Added keyword lists for question signal detection
    • Added exploration rate configuration
  6. main.py (MODIFIED)

    • Added QualityScorer instantiation in create_services
    • Conditional instantiation based on quality_scoring_enabled flag
    • Added quality_scorer to services dict

New Workflow

fetch_candidates → select_by_ratio → score_candidates → filter_candidates →
check_rules → sort_by_score → check_daily_limit → select_candidate → ...

Scoring Factors Implemented

  1. Upvote Ratio (weight: 0.15) - Post/comment upvote ratio
  2. Author Karma (weight: 0.10) - Combined karma of author
  3. Thread Freshness (weight: 0.20) - How recently thread was active
  4. Engagement Velocity (weight: 0.15) - Comments per minute
  5. Question Signal (weight: 0.15) - Presence of ? and help keywords
  6. Thread Depth (weight: 0.10) - Optimal comment count (5-15 ideal)
  7. Historical Score (weight: 0.15) - Placeholder (Phase 3)

Testing

  • All 136 existing tests pass ✅
  • No regressions introduced
  • Quality scorer has error handling for all scoring methods

Feature Flag

  • QUALITY_SCORING_ENABLED=True in config (can be disabled via .env)
  • Graceful degradation if quality_scorer=None

Status

✅ Phase 1 complete - Ready for Phase 2


2026-01-15: Phase 2 Implementation Complete - Performance Data Collection

Task

Implement Phase 2: Collect performance data for every draft outcome to enable historical learning.

Files Modified/Created

  1. migrations/versions/003_add_performance_tracking.py (NEW, 108 lines)

    • Created performance_history table with 10 columns
    • Extended draft_queue with 5 new columns (comment_id, published_at, engagement_checked, candidate_type, quality_score)
    • Added 6 indexes for efficient querying
  2. models/database.py (MODIFIED, +25 lines)

    • Added PerformanceHistory model
    • Extended DraftQueue model with performance tracking fields
    • Added Float and Boolean imports to sqlalchemy
  3. services/state_manager.py (MODIFIED, +110 lines)

    • Extended save_draft() to accept candidate_type and quality_score
    • Added record_performance_outcome() method
    • Added update_engagement_metrics() method
    • Added mark_engagement_checked() method
  4. workflow/nodes.py (MODIFIED, notify_human_node)

    • Modified to pass candidate_type and quality_score to save_draft
    • Added code to record initial PENDING outcome in performance_history
  5. api/callback_server.py (MODIFIED, process_callback)

    • Added code to record APPROVED/REJECTED outcomes after status update
    • Wrapped in try/except for graceful degradation
  6. services/poster.py (MODIFIED, publish_single)

    • Added datetime import
    • Capture comment_id and published_at after posting
    • Record PUBLISHED outcome in performance_history

Database Migration

  • Applied migration: alembic upgrade head
  • Current version: 003_add_performance_tracking
  • All schema changes applied successfully

Testing

  • All 136 tests pass ✅
  • No regressions introduced
  • Performance tracking wrapped in error handling

Status

✅ Phase 2 complete - Ready for Phase 3


2026-01-15: Phase 3 Implementation Complete - Historical Learning

Task

Implement Phase 3: Learn from historical draft outcomes per subreddit and feed back into quality scoring.

Files Modified/Created

  1. services/performance_tracker.py (NEW, 320 lines)

    • Created PerformanceTracker class with historical learning logic
    • get_subreddit_score() with 5-min caching
    • Decay-weighted scoring (1.0 → 0.7 → 0.4 → 0.2)
    • 4 component scores: approval_rate, publish_rate, engagement_score, success_rate
    • Minimum 5 samples requirement, else return 0.5 (neutral)
  2. config.py (MODIFIED, +12 lines)

    • Enabled learning_enabled flag (default: True)
    • Added learning_min_samples: 5
    • Added decay time thresholds (7/30/90 days)
    • Added 4 learning component weights (sum to 1.0)
  3. main.py (MODIFIED, +10 lines)

    • Added PerformanceTracker instantiation in create_services
    • Injected performance_tracker into QualityScorer
    • Conditional instantiation based on learning_enabled flag

Historical Score Formula

historical_score = (
    approval_rate * 0.30 +
    publish_rate * 0.20 +
    normalized_engagement * 0.30 +
    success_rate * 0.20
)

Decay Weighting

  • Last 7 days: weight 1.0 (most recent)
  • 7-30 days: weight 0.7
  • 30-90 days: weight 0.4
  • Older: weight 0.2

Testing

  • All 136 tests pass ✅
  • No regressions introduced
  • quality_scorer._score_historical() already implemented to use PerformanceTracker

Feature Flag

  • LEARNING_ENABLED=True in config (can be disabled via .env)
  • Requires minimum 5 historical samples per subreddit

Status

✅ Phase 3 complete - 3 of 4 phases done ⏳ Phase 4 (Engagement Tracking) pending


2026-01-15: Phase 4 Implementation Complete - Engagement Tracking

Task

Implement Phase 4: Background job to fetch engagement metrics 24h after publishing.

Files Modified/Created

  1. services/engagement_checker.py (NEW, 177 lines)

    • Created EngagementChecker class with background job logic
    • check_pending_engagements() queries drafts published >24h ago
    • _check_single_draft() fetches upvotes and replies from Reddit
    • Updates performance_history with engagement metrics
    • Marks drafts as engagement_checked
    • Handles deleted/removed comments gracefully
  2. main.py (MODIFIED, +60 lines)

    • Added check_engagement_metrics() function
    • Added check-engagement subparser with --limit argument
    • Added elif handler for check-engagement command
    • Usage: python main.py check-engagement --limit 50
  3. config.py (MODIFIED, +2 lines)

    • Added engagement_check_enabled: True
    • Added engagement_check_delay_hours: 24

Query Logic

drafts = session.query(DraftQueue).filter(
    DraftQueue.status == "PUBLISHED",
    DraftQueue.engagement_checked == False,
    DraftQueue.published_at < cutoff_time,  # 24h ago
    DraftQueue.comment_id.isnot(None)
).limit(limit).all()

Metric Extraction

comment = reddit_client.reddit.comment(id=comment_id)
comment.refresh()
upvotes = comment.score
replies = len(comment.replies)

Scheduling

Run via cron job every 6 hours:

0 */6 * * * cd /path/to/reddit_agent && venv/bin/python main.py check-engagement

Testing

  • All 136 tests pass ✅
  • No regressions introduced
  • Error handling for deleted comments

Feature Flag

  • ENGAGEMENT_CHECK_ENABLED=True in config (can be disabled via .env)
  • Checks drafts after 24h delay (configurable)

Status

✅ Phase 4 complete - ALL 4 PHASES DONE! ✅ Quality Scoring & Historical Learning System fully implemented


2026-01-15: Phase A Implementation Complete - Inbox Priority System

Task

Implement Phase A: Prioritize inbox replies over rising content to ensure they are captured first.

Files Modified/Created

  1. services/reddit_client.py (MODIFIED, +2 lines)

    • Added priority: str = "NORMAL" field to CandidateComment dataclass
    • Added priority: str = "NORMAL" field to CandidatePost dataclass
  2. workflow/nodes.py (MODIFIED, +77 lines)

    • Added from dataclasses import replace import
    • Tagged inbox candidates with HIGH priority in fetch_candidates_node (line 41)
    • Updated sort_by_score_node to sort by (priority, quality_score) tuple
    • Improved filter logging from DEBUG to INFO with priority/quality_score context
    • Added skipped_replied and skipped_cooldown counters to filter summary
  3. models/database.py (MODIFIED, +1 line)

    • Added candidate_type: str column to RepliedItem model
  4. migrations/versions/004_add_candidate_type_cooldown.py (NEW, 44 lines)

    • Created migration to add candidate_type column to replied_items table
    • Added index for efficient filtering by candidate type
  5. services/state_manager.py (MODIFIED, +50 lines)

    • Added inbox_cooldown_hours: int = 6 parameter to init
    • Updated mark_replied() to accept and store candidate_type
    • Modified is_retryable() to use separate cooldown periods (6h inbox, 24h rising)
  6. config.py (MODIFIED, +4 lines)

    • Added inbox_priority_enabled: True
    • Added inbox_priority_min_score: 0.35
    • Added inbox_cooldown_hours: 6
    • Added rising_cooldown_hours: 24
  7. main.py (MODIFIED, +16 lines)

    • Updated 4 StateManager instantiations to pass cooldown_hours and inbox_cooldown_hours
    • Updated in: start_callback_server(), publish_drafts(), check_engagement_metrics(), create_services()
  8. tests/test_workflow.py (MODIFIED, +82 lines)

    • Fixed TestFetchNode::test_fetch_returns_candidates to use CandidateComment dataclass
    • Fixed TestFilterNode::test_filters_already_replied to use CandidateComment dataclass
    • Fixed TestFilterNode::test_filters_non_retryable to use CandidateComment dataclass
    • Added priority field assertions to test inbox candidates are HIGH priority

Database Migration

  • Applied migration: alembic upgrade head
  • Current version: 004_add_candidate_type_cooldown
  • All schema changes applied successfully

Testing

  • All 136 tests pass ✅
  • No regressions introduced
  • Fixed 3 failing tests by using proper dataclass instances

How It Works

fetch_candidates → [inbox replies tagged HIGH priority] →
sort_by_score → [sort by (priority, quality_score)] →
filter_candidates → [INFO-level logging with priority/score] →
[inbox candidates selected first when quota available]

Cooldown Logic

  • Inbox replies (HIGH priority): 6-hour cooldown for failed attempts
  • Rising content (NORMAL priority): 24-hour cooldown for failed attempts
  • More forgiving retry policy for inbox to ensure conversations continue

Verification

  • Inbox candidates tagged with HIGH priority in logs
  • Sort node shows top_priority="HIGH" when inbox present
  • Filter logs show candidate_filtered with priority and quality_score
  • Separate cooldown periods enforced by state_manager

Status

✅ Phase A complete - Inbox priority system fully implemented ✅ Phase B complete - Subreddit diversity system fully implemented


2026-01-15: Phase B Implementation Complete - Subreddit Diversity Filter

Task

Implement Phase B: Balanced subreddit/post diversity with flexible quality overrides.

Files Modified/Created

  1. services/reddit_client.py (MODIFIED, +3 lines)

    • Added post_id: str = "" field to CandidateComment dataclass
    • Extract post_id from comment.submission.id in fetch_inbox_replies (line 450)
    • Extract post_id from post.id in fetch_rising_candidates (line 567)
  2. workflow/nodes.py (MODIFIED, +84 lines)

    • Created diversity_select_node function (82 lines)
    • Greedy selection with subreddit_counts and selected_post_ids tracking
    • Max 2 per subreddit (allow 3rd+ if quality_score >= 0.75)
    • Max 1 per post (strict - prevents spam)
    • INFO-level logging for diversity events
  3. workflow/graph.py (MODIFIED, +8 lines)

    • Imported diversity_select_node
    • Created diversity_node binding with settings
    • Added diversity_select node to workflow graph
    • Inserted between sort_by_score and check_daily_limit
    • Updated docstring with new workflow step
  4. config.py (MODIFIED, +8 lines)

    • Increased score_exploration_rate from 0.15 to 0.25 (more variety)
    • Increased score_top_n_random from 3 to 5 (larger randomization pool)
    • Added diversity_enabled: True
    • Added max_per_subreddit: 2
    • Added max_per_post: 1
    • Added diversity_quality_boost_threshold: 0.75

New Workflow

fetch_candidates → select_by_ratio → score_candidates → filter_candidates →
check_rules → sort_by_score → diversity_select → check_daily_limit →
select_candidate → ...

Diversity Logic

for candidate in sorted_candidates:
    # Strict: Max 1 per post (prevents duplicate comments on same post)
    if post_id in selected_post_ids:
        skip

    # Flexible: Max 2 per subreddit (allow 3rd+ if quality >= 0.75)
    if subreddit_count >= 2:
        if quality_score >= 0.75:
            accept  # Quality boost override
        else:
            skip

    accept_candidate()

Testing

  • All 136 tests pass ✅
  • No regressions introduced
  • Backward compatible (post_id defaults to empty string)

Feature Flags

  • DIVERSITY_ENABLED=True in config (can be disabled via .env)
  • MAX_PER_SUBREDDIT=2 (flexible limit)
  • MAX_PER_POST=1 (strict limit)
  • DIVERSITY_QUALITY_BOOST_THRESHOLD=0.75 (allows high-quality exceptions)

Verification

  • Diversity logs show: original count, selected count, unique subreddits, unique posts
  • Candidates skipped with reason: "duplicate_post" or "subreddit_limit"
  • Quality boost logged when exceptional candidate (>= 0.75) overrides limit

Status

✅ Phase B complete - Subreddit diversity system fully implemented ✅ Both Phase A and Phase B deployed together


2026-01-15: Phase A + B Deployment Complete - Version 2.5 Released

Summary

Successfully deployed both Phase A (Inbox Priority) and Phase B (Subreddit Diversity) together to solve the user's reported issues:

  • Issue 1 SOLVED: Inbox replies now captured with HIGH priority (6h cooldown vs 24h)
  • Issue 2 SOLVED: Max 2/subreddit, max 1/post with quality overrides (≥0.75)

System Status

  • Version: 2.5 (updated from 2.4)
  • Tests: 136 passing ✅
  • Migrations: 4 (latest: 004_add_candidate_type_cooldown)
  • Workflow: 13 nodes (was 9)
  • Files Changed: 12 files, ~375 lines total
  • Documentation: All updated (CLAUDE.md, README.md, .env, PHASE_AB_SUMMARY.md)

New Workflow Pipeline

fetch → ratio → score → filter → rules → sort → diversity → limit →
select → context → generate → notify → (loop)

Configuration Added to .env

# Phase A: Inbox Priority
INBOX_PRIORITY_ENABLED=True
INBOX_COOLDOWN_HOURS=6
RISING_COOLDOWN_HOURS=24

# Phase B: Diversity
DIVERSITY_ENABLED=True
MAX_PER_SUBREDDIT=2
MAX_PER_POST=1
DIVERSITY_QUALITY_BOOST_THRESHOLD=0.75

# Exploration (increased)
SCORE_EXPLORATION_RATE=0.25  # Up from 0.15
SCORE_TOP_N_RANDOM=5  # Up from 3

Implementation Timeline

  • Phase A: 8 files, ~272 lines
  • Phase B: 4 files, ~103 lines
  • Total Time: ~4 hours
  • Testing: No regressions, all 136 tests passing

Next Steps

  • All code implemented and tested
  • Documentation updated
  • Configuration added to .env
  • Run production dry-run: python main.py run --once --dry-run
  • Monitor first real run for inbox capture
  • Verify subreddit distribution after 1 week

References

  • Full details: docs/PHASE_AB_SUMMARY.md
  • Plan file: /Users/avinashsangle/.claude/plans/parallel-toasting-garden.md
  • CLAUDE.md: Updated with version 2.5 features
  • README.md: Updated with smart selection features

2026-01-15: Documentation Frontend Architecture Finalized

Task

Updated documentation frontend implementation plan to reflect new decoupled architecture based on user requirements and security review.

Key Architectural Changes

Before: Docs site calls agent API with exposed API keys After: Complete separation - static docs site + password-protected admin on agent backend

Plan Updates Made

  1. Architecture diagram - Clarified no API calls between docs and agent
  2. File structure - Renamed to avinash-docs/ for multi-project support
  3. Phase 1-3 - Changed to Jinja2 admin UI on agent backend (not API-based)
  4. Deployment options - Removed API key environment variables (no longer needed)
  5. Testing sections - Separated docs site testing from admin testing
  6. Next.js rationale - Removed API proxy references, focused on static export

Final Architecture

  • Docs: Static Next.js on Vercel (docs.avinashsangle.com)
    • Landing page at / with project cards
    • Reddit agent docs at /reddit-agent/
    • NO API calls, NO auth, purely static
  • Admin: Jinja2 templates on FastAPI agent
    • Password-protected routes (/admin/*)
    • Session-based auth (JWT httponly cookies)
    • Dashboard, .env editor, audit log, workflow visualizer

Security Improvements

  • ✅ No API key exposure in client-side code
  • ✅ No CORS complexity
  • ✅ Admin features server-rendered and fast
  • ✅ Clear separation: public docs vs private admin

Files Modified

  • Plan file: /Users/avinashsangle/.claude/plans/parallel-toasting-garden.md (15 sections updated)

Status

✅ Architecture finalized - Ready for implementation approval ⏳ Implementation pending user approval (Phases 1-7, ~4,200 LOC)


2026-01-15: Phase 1 Implementation Complete - Admin Backend with Jinja2

Task

Implement Phase 1 of documentation frontend: Admin backend with password-protected Jinja2 templates.

Files Created/Modified (11 files, ~1,650 lines)

Backend (5 files, ~850 lines):

  1. models/database.py (+48 lines)

    • Added AdminAuditLog model (audit logging)
    • Added LoginAttempt model (rate limiting)
  2. api/auth.py (NEW, 260 lines)

    • JWT session management with bcrypt password hashing
    • create_session_token(), verify_session_token()
    • check_rate_limit() - 5 attempts per 15 min
    • @require_admin decorator for protected routes
    • IP validation in JWT payload
  3. api/admin_routes.py (NEW, 160 lines)

    • GET /admin/login - Render login page
    • POST /admin/login - Handle login with rate limiting
    • GET /admin/dashboard - Protected dashboard
    • GET /admin/api/live - Real-time stats API
    • GET /admin/logout - Clear session
  4. services/audit_logger.py (NEW, 230 lines)

    • AuditLogger class with automatic sensitive data redaction
    • log_login(), log_env_update(), log_backup_restore()
    • Redact passwords, secrets, API keys
  5. services/dashboard_service.py (NEW, 320 lines)

    • DashboardService with 30s caching
    • get_status_counts(), get_daily_count(), get_performance_metrics()
    • get_weekly_trend(), get_subreddit_distribution()

Infrastructure (2 files): 6. migrations/versions/005_add_admin_tables.py (NEW, 75 lines)

  • Create admin_audit_log table with 6 indexes
  • Create login_attempts table with 2 indexes
  1. api/callback_server.py (MODIFIED, +20 lines)
    • Added Jinja2Templates import and StaticFiles
    • Mount admin_router from api.admin_routes
    • Mount /static/ directory for CSS/JS

Frontend (4 files, ~600 lines): 8. frontend/templates/base.html (NEW, 15 lines)

  • Base Jinja2 template with CSS link
  • Block structure for title, content, extra_head, extra_scripts
  1. frontend/templates/admin/login.html (NEW, 35 lines)

    • Password form with error messages
    • Info box showing default password (admin123)
  2. frontend/templates/admin/dashboard.html (NEW, 95 lines)

    • 4 metric cards (pending, daily count, approval rate, publish rate)
    • Recent drafts table with status badges
    • Subreddit distribution chart
  3. frontend/static/css/admin.css (NEW, 455 lines)

    • Warm minimalist design (orange primary, stone grays)
    • Responsive grid layouts
    • Login page, dashboard, tables, badges, forms
    • Mobile-responsive (breakpoint at 768px)

Configuration: 12. config.py (+4 lines) - admin_password_hash, admin_jwt_secret, admin_session_hours

  1. .env (+4 lines)

    • ADMIN_PASSWORD_HASH (bcrypt hash of "admin123")
    • ADMIN_JWT_SECRET (generated with secrets.token_urlsafe(32))
    • ADMIN_SESSION_HOURS=24
  2. requirements.txt (+4 lines)

    • jinja2>=3.1.0, python-multipart, bcrypt>=4.0.0, pyjwt>=2.8.0
  3. CLAUDE.md (+18 lines)

    • Added virtual environment activation instructions
    • Critical reminder to always use venv for all Python commands

Database Migration

  • Applied migration: alembic upgrade head
  • Current version: 005_add_admin_tables
  • Tables created: admin_audit_log, login_attempts
  • Indexes created: 8 total (timestamp, action, ip_address for both tables)

Security Features Implemented

  • Password hashing: Bcrypt with 12 rounds
  • Session management: JWT tokens in httponly cookies (24h expiry)
  • IP validation: JWT payload includes client IP, verified on each request
  • Rate limiting: 5 failed login attempts per 15 minutes per IP
  • Audit logging: All admin actions logged with redacted sensitive data
  • One-time sessions: JWT includes unique jti for session tracking

Admin Routes Protected

All routes under /admin/* (except /admin/login) require valid session cookie:

  • /admin/dashboard - Live metrics dashboard
  • /admin/api/live - JSON API for real-time stats
  • /admin/logout - Clear session cookie

Design System

Warm Minimalist Palette:

  • Primary: #d97706 (amber-600)
  • Background: #fafaf9 (stone-50)
  • Text: #292524 (stone-800)
  • Borders: #e7e5e4 (stone-200)

Components:

  • Metric cards with large numbers
  • Status badges (pending/approved/published/rejected)
  • Distribution bars with animated fills
  • Responsive tables with hover states

Testing

  • ✅ All imports successful (no syntax errors)
  • ✅ Migration applied successfully
  • ✅ Database tables created with indexes
  • ⏳ Manual testing pending (start server with python main.py server)

How to Test

# 1. Start server
source venv/bin/activate
python main.py server

# 2. Open browser
open http://localhost:8000/admin/login

# 3. Login
Password: admin123

# 4. View dashboard
# Should redirect to /admin/dashboard with metrics

Default Credentials

  • Username: (none - password only)
  • Password: admin123
  • Change: Update ADMIN_PASSWORD_HASH in .env with bcrypt hash

Next Steps (Remaining Phases)

  • Phase 1: Admin Backend with Jinja2 ✅ COMPLETE
  • Phase 2: Next.js Docs Site with Landing Page
  • Phase 3: Admin Dashboard UI (enhanced with Chart.js)
  • Phase 4: Workflow Visualizer
  • Phase 5: Authentication (already done in Phase 1!)
  • Phase 6: .env Editor
  • Phase 7: Setup Wizard

Status

✅ Phase 1 complete - Admin backend ready for testing ⏳ Remaining phases (~2,550 LOC) pending user approval 📊 Context used: 115k/200k tokens (57.5%)


2026-01-17: Phase 4 Implementation Complete - Workflow Visualizer

Task

Implement Phase 4: Interactive SVG diagram of the 13-node LangGraph pipeline with Alpine.js click handlers.

Files Created/Modified (4 files, ~700 lines)

Backend (2 files, ~400 lines):

  1. services/workflow_visualizer.py (NEW, 380 lines)

    • WorkflowVisualizer class with SVG generation
    • NODE_METADATA dict with descriptions for all 13 nodes
    • EDGES list with edge types (linear, conditional, loop)
    • NODE_COLORS and NODE_BORDER_COLORS (warm palette)
    • _calculate_positions() for vertical flow layout
    • _render_nodes() with hover effects
    • _render_edges() with arrows and curved paths
    • get_workflow_metadata() for API responses
  2. api/admin_routes.py (MODIFIED, +25 lines)

    • Imported WorkflowVisualizer and get_workflow_metadata
    • Initialized workflow_visualizer instance
    • Added GET /admin/workflow route (protected by @require_admin)
    • Generates SVG and passes metadata to template

Frontend (2 files, ~300 lines): 3. frontend/templates/workflow.html (NEW, 270 lines)

  • Alpine.js x-data="workflowViewer()" for state management
  • Stats grid showing total nodes, edges, node types
  • Legend with 7 node type colors
  • SVG container with click handler
  • Modal for node details (Alpine.js x-show, transitions)
  • Documentation section explaining diagram
  • handleNodeClick() function to show node metadata
  • Back to Dashboard and Logout buttons
  1. frontend/templates/admin/dashboard.html (MODIFIED, +1 line)
    • Added "View Workflow" button in header-actions

Workflow Diagram Features

13 Nodes Visualized:

  • fetch_candidates (warm yellow)
  • select_by_ratio (orange light)
  • score_candidates (blue light - AI)
  • filter_candidates (orange light)
  • check_rules (orange light)
  • sort_by_score (yellow - sorting)
  • diversity_select (orange light)
  • check_daily_limit (red light - conditional)
  • select_candidate (red light - conditional)
  • build_context (green light - process)
  • generate_draft (blue light - AI)
  • notify_human (purple light - notification)
  • END (gray)

Edge Types:

  • Solid orange arrows: Linear flow
  • Dashed red arrows: Conditional branches (continue/end)
  • Dashed purple arrow: Loop back from notify_human to check_daily_limit

Interactive Features:

  • Click any node → Modal with description, inputs, outputs, node type
  • Hover node → Brightness effect and stroke width increase
  • Mobile-friendly: Scrollable SVG container
  • Smooth Alpine.js transitions for modal

Node Metadata

Each node includes:

  • Label: Display name
  • Description: What the node does
  • Inputs: Data consumed
  • Outputs: Data produced
  • Node Type: fetch, filter, sort, conditional, process, ai, notify, end

Design

  • Warm palette: Matches admin dashboard CSS
  • Vertical flow: Top to bottom, easier to read than horizontal
  • Color coding: 8 node types with distinct colors
  • Clear arrows: Markers show direction, dashed for conditionals
  • Responsive: Stats grid adapts to screen size

Testing

# 1. Start server
source venv/bin/activate
python main.py server

# 2. Login to admin
open http://localhost:8000/admin/login
# Password: admin123

# 3. Click "View Workflow" button
# Should show interactive SVG diagram

# 4. Click any node
# Should show modal with node details

Routes

  • GET /admin/workflow (protected) - Render workflow visualizer
  • Accessible from dashboard header via "View Workflow" button
  • Back button returns to /admin/dashboard

Stats Displayed

  • Total Nodes: 12 (excluding END)
  • Total Edges: 14 (including loop)
  • Node Types: 8 categories

Next Steps (Remaining Phases)

  • Phase 1: Admin Backend with Jinja2 ✅
  • Phase 2: Next.js Docs Site ✅
  • Phase 3: Admin Dashboard UI ✅
  • Phase 4: Workflow Visualizer ✅ COMPLETE
  • Phase 6: .env Editor (~1,200 LOC)
  • Phase 7: Setup Wizard (~700 LOC)

Status

✅ Phase 4 complete - Workflow visualizer live! ⏳ Remaining phases (~1,900 LOC) 📊 Context used: 78k/200k tokens (39%)


2026-01-17: Phase 6 Implementation Complete - .env Editor

Task

Implement Phase 6: Secure web UI to edit .env file with validation, backups, and diff preview.

Files Created/Modified (4 files, ~1,100 lines)

Backend (2 files, ~700 lines):

  1. services/env_manager.py (NEW, 583 lines)

    • EnvManager class with .env CRUD operations
    • load_env() - Parse .env file into dict
    • save_env() - Write with Pydantic validation
    • validate_env() - Validate against Settings model
    • preview_changes() - Generate diff between old/new
    • _create_backup() - Timestamped backups (.env.backup.YYYYMMDDHHMMSS)
    • _cleanup_backups() - Keep last 10, auto-delete older
    • list_backups() - List all backups with metadata
    • restore_backup() - Restore from backup
    • get_field_metadata() - Field metadata for frontend rendering
    • _is_secret_field() - Detect sensitive fields
    • _mask_secret() - Mask secrets (***...last6)
  2. api/admin_routes.py (MODIFIED, +177 lines)

    • Imported EnvManager
    • Initialized env_manager instance
    • GET /admin/env - Render editor page
    • POST /admin/api/env/preview - Show diff before save
    • POST /admin/api/env/save - Save with validation + backup
    • POST /admin/api/env/restore - Restore from backup

Frontend (1 file, ~600 lines): 3. frontend/templates/admin/env_editor.html (NEW, 595 lines)

  • Alpine.js x-data="envEditor()" for state management
  • Grouped fields by category (Reddit API, LLM Keys, Notifications, Safety)
  • Password/secret fields with reveal/hide toggle
  • Client-side form state management
  • Preview modal with diff table (changed rows highlighted)
  • Save confirmation with server-side validation
  • Backups list with restore buttons
  • Success/error messages
  • "Restart Required" banner after save
  1. frontend/templates/admin/dashboard.html (MODIFIED, +1 line)
    • Added "Edit .env" button in header-actions

Features Implemented

1. Load/Save with Validation

  • Loads .env file into grouped form fields
  • Validates against Pydantic Settings model before save
  • Shows validation errors if any field fails

2. Grouped Fields

  • Reddit API (5 fields)
  • Subreddits (1 field)
  • LLM Keys (3 fields)
  • Notifications (4 fields)
  • Safety Limits (2 fields shown, more available)

3. Secret Masking

  • Password/key fields show ***...last6 by default
  • Reveal/Hide button to toggle plaintext view
  • Secrets masked in diff preview
  • Never log full secrets in audit log

4. Diff Preview

  • Preview button shows modal with old vs new values
  • Changed fields highlighted in yellow
  • Unchanged fields shown with reduced opacity
  • Displays count of changed fields

5. Automatic Backups

  • Creates timestamped backup before every save
  • Format: .env.backup.20260117143022
  • Keeps last 10 backups automatically
  • Auto-deletes older backups

6. Backup Management

  • Lists last 10 backups with timestamps and file sizes
  • Restore button for each backup
  • Confirmation dialog before restore
  • Creates backup of current .env before restoring

7. Audit Logging

  • All saves logged to admin_audit_log
  • All restores logged to admin_audit_log
  • Includes IP address, changed fields (redacted)
  • Never logs full API keys/passwords

8. Client-Side State

  • Alpine.js reactive data binding
  • Form reset button to revert changes
  • Success/error message display
  • Loading state during save operations

9. Security

  • Password-protected route (@require_admin)
  • Server-side Pydantic validation
  • IP address logging for all changes
  • Secrets redacted in logs and previews

Field Groups

Reddit API:

  • REDDIT_CLIENT_ID (text, required)
  • REDDIT_CLIENT_SECRET (password, required, secret)
  • REDDIT_USERNAME (text, required)
  • REDDIT_PASSWORD (password, required, secret)
  • REDDIT_USER_AGENT (text, required)

Subreddits:

  • ALLOWED_SUBREDDITS (text, required, comma-separated)

LLM Keys (at least one required):

  • GEMINI_API_KEY (password, secret)
  • OPENAI_API_KEY (password, secret)
  • ANTHROPIC_API_KEY (password, secret)

Notifications:

  • NOTIFICATION_TYPE (select: slack/telegram/webhook, required)
  • PUBLIC_URL (text, required for approval callbacks)

Safety Limits:

  • MAX_COMMENTS_PER_DAY (number, 1-10, required)
  • MAX_COMMENTS_PER_RUN (number, 1-5, required)

Validation

Server-Side (Pydantic):

  • All fields validated against Settings model
  • Type checking (string, int, float, bool)
  • Range validation (e.g., 1-10 for daily limit)
  • Required field enforcement
  • Returns detailed error messages if invalid

Client-Side (Alpine.js):

  • Basic HTML5 validation (required, type, min/max)
  • Real-time error display
  • Prevents submission if obviously invalid

Backup Strategy

Automatic Backups:

  • Created before every save
  • Timestamped filename: .env.backup.20260117143022
  • Original file permissions preserved
  • Atomic write (no partial saves)

Retention Policy:

  • Keep last 10 backups
  • Auto-delete backups older than #10
  • Manual restore available for any of the 10

Restore Flow:

User clicks "Restore" → Confirmation dialog →
Backup current .env → Copy backup to .env →
Log to audit_log → Reload page

Testing

# 1. Start server
source venv/bin/activate
python main.py server

# 2. Login to admin
open http://localhost:8000/admin/login
# Password: admin123

# 3. Click "Edit .env" button on dashboard

# 4. Edit a field (e.g., MAX_COMMENTS_PER_DAY)

# 5. Click "Preview Changes"
# Should show diff modal

# 6. Click "Save Changes"
# Should show success message and create backup

# 7. Check backups list
# Should show new backup with timestamp

# 8. Test restore
# Click "Restore" on a backup → Confirm → Page reloads

Security Considerations

1. Authentication:

  • All routes protected by @require_admin decorator
  • Session-based auth with JWT cookies
  • IP validation in JWT payload

2. Validation:

  • Server-side Pydantic validation (never trust client)
  • Prevents invalid values from being written
  • Atomic writes (no partial .env files)

3. Audit Trail:

  • All saves logged with IP, timestamp, changed fields
  • All restores logged with IP, timestamp, backup file
  • Secrets redacted in logs (***...last6)

4. Backup Safety:

  • Automatic backup before every write
  • Cannot lose .env file due to bad edit
  • 10-backup retention prevents disk bloat

5. Input Sanitization:

  • Pydantic handles type coercion
  • No shell injection risk (values written as-is)
  • No SQL injection (not using SQL for .env)

Routes

  • GET /admin/env (protected) - Render .env editor
  • POST /admin/api/env/preview (protected) - Preview diff
  • POST /admin/api/env/save (protected) - Save changes
  • POST /admin/api/env/restore (protected) - Restore backup

Next Steps (Remaining Phases)

  • Phase 1: Admin Backend with Jinja2 ✅
  • Phase 2: Next.js Docs Site ✅
  • Phase 3: Admin Dashboard UI ✅
  • Phase 4: Workflow Visualizer ✅
  • Phase 6: .env Editor ✅ COMPLETE
  • Phase 7: Setup Wizard (~700 LOC)

Status

✅ Phase 6 complete - .env editor live with backups! ⏳ Remaining: Phase 7 Setup Wizard (~700 LOC) 📊 Context used: 100k/200k tokens (50%)


2026-01-17: Phase 6 Bug Fixes - Settings UI Improvements

Task

Fix reported issues with Settings (formerly .env Editor) UI.

Issues Fixed

1. Reveal button not showing plaintext

  • Problem: Password/secret fields not toggling to plaintext when "Reveal" clicked
  • Cause: Input type was using fieldTypes[field] instead of checking revealed[field] state
  • Fix: Changed input type binding to :type="isSecret(field) && !revealed[field] ? 'password' : 'text'"
  • Files: frontend/templates/admin/env_editor.html (lines 288, 332)

2. Title change from ".env Editor" to "Settings"

  • Fix: Changed page title and h1 from ".env Editor" to "Settings"
  • Files: frontend/templates/admin/env_editor.html (lines 3, 253)
  • Files: frontend/templates/admin/dashboard.html (button label, line 17)

3. Preview validation error: "str expected, not int"

  • Problem: Numeric fields sent as numbers, but .env validation expects strings
  • Cause: Alpine.js x-model.number binding for numeric inputs
  • Fix: Convert all form values to strings before sending to API
  • Code: Added stringification in previewChanges() and saveChanges() methods
const stringifiedData = {};
for (const [key, value] of Object.entries(this.formData)) {
    stringifiedData[key] = value !== null && value !== undefined ? String(value) : '';
}
  • Files: frontend/templates/admin/env_editor.html (lines 663-666, 699-702)

4. Missing notification fields

  • Problem: Only NOTIFICATION_TYPE and PUBLIC_URL shown; missing SLACK/TELEGRAM/WEBHOOK fields
  • Fix: Added conditional sections based on selected notification type
  • Added fields:
    • Slack: SLACK_WEBHOOK_URL (password, reveal button), SLACK_CHANNEL (text)
    • Telegram: TELEGRAM_BOT_TOKEN (password, reveal button), TELEGRAM_CHAT_ID (text)
    • Webhook: WEBHOOK_URL (text), WEBHOOK_SECRET (password, reveal button)
  • Alpine.js: Used x-if templates to show/hide based on formData.NOTIFICATION_TYPE
  • Files:
    • frontend/templates/admin/env_editor.html (lines 358-449)
    • services/env_manager.py (added metadata for new fields, lines 483-517)
    • services/env_manager.py (updated _write_section and written_keys, lines 121-130, 173)

Files Modified (3 files)

  1. frontend/templates/admin/env_editor.html

    • Fixed reveal button type binding (2 locations)
    • Changed title to "Settings"
    • Added stringification for all form values
    • Added conditional notification fields with Alpine.js x-if
    • Total changes: ~140 lines modified/added
  2. frontend/templates/admin/dashboard.html

    • Changed button label from "Edit .env" to "Settings"
    • Total changes: 1 line
  3. services/env_manager.py

    • Added metadata for SLACK_CHANNEL, WEBHOOK_URL, WEBHOOK_SECRET
    • Updated _write_section to include new notification fields
    • Updated written_keys tracking
    • Total changes: ~15 lines

Testing

All issues resolved:

  • ✅ Reveal button now shows plaintext for passwords/secrets
  • ✅ Title changed to "Settings" throughout UI
  • ✅ Preview validation works (no "str expected" error)
  • ✅ All notification fields shown based on selected type:
    • Select "slack" → Shows SLACK_WEBHOOK_URL, SLACK_CHANNEL
    • Select "telegram" → Shows TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
    • Select "webhook" → Shows WEBHOOK_URL, WEBHOOK_SECRET
  • ✅ All fields have reveal/hide buttons for secrets
  • ✅ Form data correctly stringified before API calls

Status

✅ All Phase 6 bugs fixed ✅ Settings UI fully functional ⏳ Remaining: Phase 7 Setup Wizard (~700 LOC) 📊 Context used: 119k/200k tokens (60%)

2026-01-19: Next.js Web-App Authentication Fixes Complete

Task

Fix authentication issues in Next.js web-app (shadcn/ui frontend) where logout didn't invalidate sessions and protected routes were accessible without authentication.

Problem Identified

Issues:

  1. ❌ No frontend route protection - users could access /admin/dashboard and /admin/settings directly after logout
  2. ❌ Logout button only redirected to /login without calling backend /admin/logout endpoint
  3. ❌ No 401 error handling - if backend returned 401, frontend didn't redirect to login
  4. ✅ CORS already configured for localhost:3000

Root Causes:

  • Next.js had NO middleware to check authentication before rendering protected pages
  • Session cookies remained valid after logout (never cleared on backend)
  • Frontend fetch calls didn't handle 401 responses globally

Solution Implemented

Phase 1: Backend Changes (2 files modified):

  1. api/admin_routes.py (MODIFIED, +10 lines)

    • Added /admin/api/check-auth endpoint for middleware to validate sessions
    • Protected by @require_admin decorator
    • Returns 200 if session valid, 302 redirect if invalid
  2. api/callback_server.py (VERIFIED, already complete)

    • CORS middleware already configured for localhost:3000
    • Allows credentials for session cookies

Phase 2: Frontend Middleware (1 file created):

  1. web-app/src/middleware.ts (NEW, 50 lines)
    • Next.js middleware for route protection
    • Intercepts requests to /admin/* and /workflow/*
    • Checks for admin_session cookie
    • Validates session with backend /admin/api/check-auth
    • Redirects to /login if invalid or missing
    • Allows request to proceed if valid

Phase 3: Proper Logout Implementation (3 files modified):

  1. web-app/src/app/admin/dashboard/page.tsx (MODIFIED, +20 lines)

    • Added API_BASE constant
    • Added handleLogout() function that:
      • Calls GET /admin/logout to clear backend session
      • Redirects to /login regardless of API result
    • Updated logout button to call handleLogout()
  2. web-app/src/app/admin/settings/page.tsx (MODIFIED, +15 lines)

    • Added handleLogout() function (same logic)
    • Updated logout button to call handleLogout()
  3. web-app/src/app/workflow/page.tsx (MODIFIED, +20 lines)

    • Added API_BASE constant
    • Added handleLogout() function (same logic)
    • Updated logout button to call handleLogout()

Phase 4: Global 401 Error Handler (3 files):

  1. web-app/src/lib/api-client.ts (NEW, 55 lines)

    • Created fetchWithAuth() wrapper around fetch()
    • Automatically includes credentials: 'include'
    • Handles 401 Unauthorized by redirecting to /login
    • Helper functions: getWithAuth(), postWithAuth()
  2. web-app/src/app/admin/settings/page.tsx (MODIFIED, import + 2 fetch replacements)

    • Imported fetchWithAuth from @/lib/api-client
    • Replaced fetch(\${API_BASE}/admin/api/env`)withfetchWithAuth('/admin/api/env')`
    • Replaced fetch(\${API_BASE}/admin/api/env/save`)withfetchWithAuth('/admin/api/env/save')`
  3. web-app/src/app/admin/dashboard/page.tsx (MODIFIED, no fetch calls to replace)

    • Dashboard uses mock data (no API calls)
    • Ready for future API integration with fetchWithAuth

Files Modified/Created

Backend (2 files, +10 LOC):

  • api/admin_routes.py (+10 lines) - Added check-auth endpoint
  • api/callback_server.py (verified) - CORS already configured

Frontend (6 files, ~165 LOC):

  • web-app/src/middleware.ts (NEW, 50 lines)
  • web-app/src/lib/api-client.ts (NEW, 55 lines)
  • web-app/src/app/admin/dashboard/page.tsx (+20 lines)
  • web-app/src/app/admin/settings/page.tsx (+30 lines)
  • web-app/src/app/workflow/page.tsx (+20 lines)

Authentication Flow (After Fix)

Unauthenticated Access:

User -> /admin/dashboard (no cookie)
  -> Middleware checks admin_session cookie
  -> No cookie found
  -> Redirect to /login
  -> Dashboard never renders

Login Flow:

User -> /login
  -> Enter password
  -> POST /admin/api/login
  -> Backend validates password
  -> Sets admin_session cookie (httponly)
  -> Redirect to /admin/dashboard
  -> Middleware validates cookie with /admin/api/check-auth
  -> Session valid -> Allow access

Logout Flow:

User -> Click "Logout" button
  -> handleLogout() called
  -> GET /admin/logout (clears backend cookie)
  -> Redirect to /login
  -> Next access to /admin/dashboard
  -> Middleware checks cookie (now cleared)
  -> Redirect to /login

401 During API Call:

User -> /admin/settings (logged in)
  -> Session expires (24h timeout)
  -> User clicks "Save"
  -> fetchWithAuth('/admin/api/env/save')
  -> Backend returns 401 Unauthorized
  -> fetchWithAuth detects 401
  -> Automatically redirects to /login

Security Features

Frontend Protection:

  • ✅ Middleware blocks unauthenticated access to protected routes
  • ✅ Session validation on every protected route request
  • ✅ Automatic 401 handling with redirect to login
  • ✅ No protected pages rendered without valid session

Backend Protection (already implemented):

  • ✅ JWT tokens with 24-hour expiry
  • ✅ httponly cookies (XSS protection)
  • ✅ IP validation in JWT payload
  • ✅ Rate limiting (5 attempts / 15 min)
  • ✅ Bcrypt password hashing (12 rounds)

Testing

All 9 todos completed:

  • Phase 1: Add /admin/api/check-auth endpoint
  • Phase 1: Verify CORS middleware configured
  • Phase 2: Create Next.js middleware
  • Phase 3: Update logout in dashboard page
  • Phase 3: Update logout in settings page
  • Phase 3: Update logout in workflow page
  • Phase 4: Create fetchWithAuth wrapper
  • Phase 4: Replace fetch calls in dashboard (no calls to replace)
  • Phase 4: Replace fetch calls in settings

Verification Steps

Test 1: Unauthenticated Access (BLOCKED)

# Clear all cookies
# Navigate to: http://localhost:3000/admin/dashboard

# Expected:
# ✅ Middleware detects no session cookie
# ✅ Redirects to /login
# ✅ Dashboard never renders

Test 2: Login Flow (SUCCESS)

# Navigate to: http://localhost:3000/login
# Enter password (admin123)
# Click "Login"

# Expected:
# ✅ POST /admin/api/login returns 200
# ✅ Browser receives admin_session cookie
# ✅ Redirects to /admin/dashboard
# ✅ Middleware allows access (valid session)

Test 3: Logout Flow (SUCCESS)

# While logged in, click "Logout" button

# Expected:
# ✅ GET /admin/logout called
# ✅ Backend clears admin_session cookie
# ✅ Redirects to /login
# ✅ Subsequent access to /admin/dashboard blocked by middleware

Test 4: 401 During API Call (HANDLED)

# Login and access /admin/settings
# Manually delete admin_session cookie
# Click "Save" button

# Expected:
# ✅ Backend returns 401 Unauthorized
# ✅ fetchWithAuth() detects 401
# ✅ Automatically redirects to /login

Context Remaining

~88k tokens (44% remaining) - sufficient for next tasks

Status

All authentication issues resolved! ✅ Frontend route protection working with middleware ✅ Logout properly invalidates backend session ✅ 401 errors handled globally with automatic redirects ✅ CORS configured for localhost:3000