sojorn/sojorn_docs/philosophy/CALM_UX_GUIDE.md
Patrick Britton 38653f5854 Sojorn Backend Finalization & Cleanup - Complete Migration from Supabase
##  Phase 1: Critical Feature Completion (Beacon Voting)
- Add VouchBeacon, ReportBeacon, RemoveBeaconVote methods to PostRepository
- Implement beacon voting HTTP handlers with confidence score calculations
- Register new beacon routes: /beacons/:id/vouch, /beacons/:id/report, /beacons/:id/vouch (DELETE)
- Auto-flag beacons at 5+ reports, confidence scoring (0.5 base + 0.1 per vouch)

##  Phase 2: Feed Logic & Post Distribution Integrity
- Verify unified feed logic supports all content types (Standard, Quips, Beacons)
- Ensure proper distribution: Profile Feed + Main/Home Feed for followers
- Beacon Map integration for location-based content
- Video content filtering for Quips feed

##  Phase 3: The Notification System
- Create comprehensive NotificationService with FCM integration
- Add CreateNotification method to NotificationRepository
- Implement smart deep linking: beacon_map, quip_feed, main_feed
- Trigger notifications for beacon interactions and cross-post comments
- Push notification logic with proper content type detection

##  Phase 4: The Great Supabase Purge
- Delete function_proxy.go and remove /functions/:name route
- Remove SupabaseURL, SupabaseKey from config.go
- Remove SupabaseID field from User model
- Clean all Supabase imports and dependencies
- Sanitize codebase of legacy Supabase references

##  Phase 5: Flutter Frontend Integration
- Implement vouchBeacon(), reportBeacon(), removeBeaconVote() in ApiService
- Replace TODO delay in video_comments_sheet.dart with actual publishComment call
- Fix compilation errors (named parameters, orphaned child properties)
- Complete frontend integration with Go API endpoints

##  Additional Improvements
- Fix compilation errors in threaded_comment_widget.dart (orphaned child property)
- Update video_comments_sheet.dart to use proper named parameters
- Comprehensive error handling and validation
- Production-ready notification system with deep linking

##  Migration Status: 100% Complete
- Backend: Fully migrated from Supabase to custom Go/Gin API
- Frontend: Integrated with new Go endpoints
- Notifications: Complete FCM integration with smart routing
- Database: Clean of all Supabase dependencies
- Features: All functionality preserved and enhanced

Ready for VPS deployment and production testing!
2026-01-30 09:24:31 -06:00

19 KiB

Sojorn Calm UX Guide

Design Philosophy

Sojorn enforces calm through intentional friction, respectful boundaries, and visual restraint. Every interaction should feel like settling in, not being rushed.


Part 1: Reading & Feed Experience

Design Intent

  • Reading feels like settling into a book, not scrolling a timeline
  • No visual shouting or metric obsession
  • No urgency cues or FOMO mechanics

Feed Layout Decisions

1. Comfortable Max Width (680px)

Why:

  • Optimal line length for reading is 50-75 characters
  • Wide text blocks strain eye tracking
  • Creates "settling in" feeling vs infinite scroll anxiety

Implementation:

Container(
  constraints: const BoxConstraints(maxWidth: 680),
  // ...
)

2. Generous Vertical Spacing (24px between posts)

Why:

  • Tight spacing = rushed feeling
  • Space = permission to pause
  • Each post gets breathing room

Visual Effect:

  • Posts feel like chapters in a book
  • No "wall of content" anxiety

3. No Aggressive Dividers

Why:

  • Heavy dividers create visual noise
  • Soft shadows and spacing create natural separation
  • Avoids the "list item" feeling

Implementation:

boxShadow: AppTheme.shadowSm  // Only 4% opacity

Post Card Design Decisions

1. Body Text is THE HERO

Why:

  • You came here to read, not to scan metrics
  • Large, comfortable type (17px at 1.7 line-height)
  • Primary visual weight goes to content

Implementation:

Text(
  post.body,
  style: AppTheme.bodyLarge.copyWith(
    height: 1.7,  // Extra generous for reading
    letterSpacing: 0.1,  // Slight tracking
  ),
)

Contrast with Twitter/X:

  • Twitter: 15px text, 1.4 line-height, competing with images/metrics
  • Sojorn: 17px text, 1.7 line-height, nothing competes

2. Author Identity: Clear but Not Dominant

Why:

  • You need to know who's speaking
  • But not at the expense of the message
  • Small avatar (36px vs typical 48px)
  • De-emphasized text color (textSecondary, not textPrimary)

Visual Hierarchy:

1. Post body (textPrimary, 17px, bold visual weight)
2. Actions (tertiary, 16px icons)
3. Author (textSecondary, 13px)
4. Metadata (textTertiary, 11px)

3. Metrics De-emphasized

Why:

  • Like counts don't mean quality
  • Save counts are personal, not performance
  • Comment counts ≠ value (and don't boost reach!)

Design Choices:

  • Icons are small (18px, not 24px)
  • Text is labelSmall (11px)
  • Color is textTertiary (very light gray)
  • No visual weight

What's Hidden:

  • View counts (never shown)
  • Ratio metrics (likes/comments)
  • Trending indicators

4. Trust Tier Badge: Subtle Signal

Why:

  • Trust matters for context
  • But shouldn't dominate
  • Tiny badge (8px font, 12% opacity background)

vs Other Platforms:

  • Twitter verification: 20px, bright blue, dominant
  • Sojorn trust tier: 8px, muted color, barely visible

Interaction Behavior

1. Gentle Press States

Why:

  • Aggressive hover/press = visual aggression
  • Subtle border change (borderSubtle → borderStrong)
  • Shadow removal (not addition)

Implementation:

border: Border.all(
  color: _isPressed ? AppTheme.borderStrong : AppTheme.borderSubtle,
  width: 0.5,
),
boxShadow: _isPressed ? null : AppTheme.shadowSm,

Effect:

  • Card "settles in" when pressed
  • No bounce, no scale, no aggressive feedback

2. No Metric Celebration

Why:

  • No confetti when you hit 10 likes
  • No "trending" badges
  • No "your post is doing well!" notifications

Philosophy:

  • You wrote something calm → reward is internal
  • External validation ≠ quality

Reading Enhancements

1. Optimal Typography

  • 17px body text (larger than most platforms)
  • 1.7 line-height (research shows 1.5-1.6 is ideal, we go further)
  • 0.1 letter-spacing (slight tracking for comfort)
  • System fonts (SF Pro Text, Roboto) for familiarity

2. Interaction Affordances

  • Appreciate/Save always visible but quiet
  • No hidden menus (everything upfront)
  • Tap target size: 44px minimum (accessibility)

Part 2: Writing & Commenting Experience

Composer Design Intent

  • Writing pauses before publishing (no tweet-and-regret)
  • Friction feels supportive, not punitive
  • Tone guidance is optional and respectful

Composer UX Decisions

1. Large, Calm Text Area

Why:

  • Small inputs = rushed thoughts
  • Large area = room to think
  • 500 character limit shown gently (not alarming)

Design:

SojornTextArea(
  minLines: 5,  // Not 1-2 like Twitter
  maxLines: 15,
  maxLength: 500,
  style: AppTheme.bodyLarge,  // Same size as reading
)

2. Character Limit: Gentle, Not Alarming

What We Don't Do:

  • Turn red at 480/500
  • Show "You're over the limit!" in red text
  • Disable publish button aggressively

What We Do:

  • Show "487 / 500" in textTertiary
  • Turn accent color at 490
  • Fade publish button (not disable) at 501+

Copy:

Good: "487 / 500"
Bad: "ONLY 13 CHARACTERS LEFT!"

3. Category Selection: Clear and Required

Why:

  • Categories are structural boundaries
  • Must be intentional (no "general" default)
  • Clear labels, obvious UI

Tone Nudge UI

1. When Triggered

Scenario: Post gets CIS < 0.85

What We Don't Do:

  • Red warning banner
  • "This violates community guidelines"
  • Block publishing immediately
  • Shame the user

What We Do:

  • Neutral language
  • Soft amber background (not red)
  • Suggestion, not demand
  • Allow dismiss

Copy:

## Sharp Edges Detected

This post has language that may feel sharp to readers.

**Suggested rewrite:**
"I respectfully disagree with this approach."

**Original:**
"This is stupid and wrong."

[ Publish Anyway ]  [ Edit ]

Tone:

  • No "You violated..."
  • No "This is not allowed"
  • Just "This may feel sharp"

2. Allow Dismiss Without Penalty

Why:

  • You're an adult
  • Tone detection isn't perfect
  • Trust users to make decisions

But:

  • Persistent low CIS → harmony score impact
  • 3+ rejected posts → temporary slow-down
  • Trust tier may adjust

Philosophy:

  • Friction, not force
  • Consequences, not punishment

Comment UI

1. Mutual-Follow Only

Design:

  • Comment box only appears if mutual follow
  • Otherwise: "Follow each other to comment"
  • No shame, just structure

2. Compact, Conversational Layout

Why:

  • Comments are dialogue, not performance
  • Small avatars (28px)
  • Lighter visual weight than posts

3. Downvotes: De-emphasized

Why:

  • Downvotes useful for spam/quality
  • But not a weapon

Design:

  • No downvote count shown
  • Icon is tertiary gray (not red)
  • No "controversial" indicators

Empty States

1. No Pressure to Post

Bad Copy:

"Your feed is empty! Start following people!"
"Nothing to see here. Get active!"

Good Copy (Sojorn Voice):

"Nothing here yet"
"Posts you appreciate will appear here"
"Your feed is quiet right now"

Tone:

  • Welcoming, not urgent
  • Calm, not demanding
  • Permission to lurk

Part 3: Navigation & Information Architecture

Design Intent

  • Users always know where they are
  • No hidden mechanics or dark patterns
  • No surprise destinations

Bottom Navigation

1. Clear, Limited Tabs

What We Have:

  • Following (chronological from follows)
  • Sojorn (algorithmic FYP)
  • Profile (your stats and posts)

What We Don't Have:

  • "Discover" (too vague)
  • "Notifications" (reduces checking anxiety)
  • "Messages" (not yet implemented)

Why 3 Tabs:

  • Cognitive load: 3-5 is ideal
  • Each tab has one job
  • No confusion

2. No Surprise Destinations

Rule:

  • Tab icon = where you land
  • No "Following but actually Explore"
  • No "Profile but actually Settings"

Profile Hierarchy

1. Posts First

Why:

  • You came to see what they wrote
  • Not their bio or follower count

Layout:

1. Posts (primary view)
2. Bio (secondary, collapsed)
3. Stats (tertiary, small)
4. Controls (obvious but not dominant)

2. Follow/Unfollow: Obvious

Design:

  • Always visible in header
  • Clear label ("Follow" / "Following")
  • No hidden in "..." menu

Settings Organization

1. Grouped by Concern

📖 Reading & Filters
  - Category preferences
  - Content filters
  - Feed preferences

🔒 Privacy & Blocking
  - Blocked users
  - Profile visibility
  - Data sharing

👤 Account & Data
  - Email/password
  - Export data
  - Delete account

Why This Order:

  • Most common (reading) first
  • Safety (blocking) never buried
  • Destructive (delete) last

2. No Buried Safety Controls

Rule:

  • Block button on every profile
  • Privacy settings in top-level menu
  • Export data always accessible

Discoverability

1. Explore Tab Clearly Separate

Why:

  • "Explore" ≠ "Following"
  • No accidental algorithm exposure
  • Opt-in discovery

2. Categories Never Auto-Enable

Rule:

  • All categories off by default (except general)
  • Explicit opt-in required
  • No "We think you'd like..." suggestions

Part 4: Blocking & Filtering UX

Design Intent

  • Blocking is self-care, not confrontation
  • Filtering is private and encouraged
  • No shame, no drama

Block Affordance

1. Always Visible

Where:

  • On every profile (header menu)
  • On every post (overflow menu)
  • In comment threads

Design:

Icon: shield_outline (not block_circle)
Label: "Block @username"
Color: textSecondary (not error red)

Why Shield Icon:

  • Block = protection, not punishment
  • Shield = self-care
  • Less aggressive than

2. One-Tap Confirmation

Flow:

Tap "Block" →
  Dialog: "Block @username?"
  "You won't see their posts or comments. They won't be notified."
  [ Cancel ]  [ Block ]

No:

  • "Are you SURE?"
  • "This is permanent"
  • Multiple confirmations

Copy Tone:

  • Neutral, not dramatic
  • Reassuring, not scary

3. No Explanation Required

Why:

  • You don't owe anyone an explanation
  • Block is personal boundary
  • No "Report" pressure

But:

  • Separate "Report" option exists
  • Report ≠ block (different flows)

Filter Controls

1. Category Toggles

Design:

[ ] Quiet Reflections
[ ] Gratitude
[x] General Discussion
[ ] Deep Questions

Each Shows:

  • Name
  • Description (one sentence)
  • Post count (optional)

2. Keyword/Topic Filters (Optional)

Future Feature:

Hide posts containing:
- "election"
- "crypto"
- "diet"

Design:

  • Off by default
  • No suggestions
  • No "trending" pressure

3. Preview What's Hidden (Optional)

Design:

[ ] Show me what I'm filtering

When Enabled:

  • Filtered posts appear grayed out
  • "Hidden by your filters" label
  • Can tap to reveal

Default: OFF (out of sight, out of mind)

Feedback Copy

1. After Blocking

"You won't see posts from @username anymore."
"They won't be notified."

Not:

❌ "User blocked successfully!"
❌ "You'll never see them again!"

2. After Filtering

"Quiet Reflections hidden from your feeds."

Not:

❌ "Category disabled!"
❌ "You won't miss anything important"

Export/Import Block List

1. Clear Warnings

## Export Block List

This downloads a JSON file with usernames you've blocked.

⚠️ This file contains your personal blocking decisions.
⚠️ Sharing this may reveal your social boundaries.
⚠️ Sojorn does not endorse public block list sharing.

[ Cancel ]  [ Download JSON ]

2. No Recommendations

What We Don't Do:

  • "Import from popular block lists"
  • "People like you also block..."
  • "Suggested blocks based on your follows"

Why:

  • Block lists = personal boundaries
  • No crowd-sourcing judgment
  • No guilt by association

Part 5: Transparency & Explanation

Design Intent

  • Calm confidence through clarity
  • No mystery, no jargon
  • Trust through honesty

"How Reach Works" Page

Content Structure

# How Sojorn Ranking Works

Posts in your Sojorn feed are ranked by **calm velocity**—a measure of genuine appreciation over time.

## What Boosts Posts
- ❤️ Appreciations (likes)
- 🔖 Saves (strong signal)
- ⏱️ Time spent reading (dwell time)
- 🎯 High Content Integrity Score (CIS)

## What Slows Posts
- ⏰ Age (older posts fade naturally)
- 👎 Downvotes (quality filter)
- 🚩 Low CIS (tone issues)

## What Doesn't Matter
- 💬 Comment count (dialogue ≠ quality)
- 👥 Author's follower count
- 📊 Retweets/shares (we don't have those)

## Why This Way
Calm velocity rewards thoughtful content, not viral outrage. Posts earn reach through genuine appreciation, not reaction-baiting.

Design Choices

  • Plain language (no "algorithm" jargon)
  • Bullet points (scannable)
  • Emojis (visual anchors, but not excessive)
  • Honesty (explicitly state what doesn't matter)

"Rules & Tone" Page

Content Structure

# Community Tone Guidelines

Sojorn welcomes all ideas, but requires calm expression.

## Focus: Tone, Not Ideology

We don't police what you think. We ask how you express it.

### ✅ Allowed
- "I disagree with that approach."
- "This feels uncomfortable to me."
- "I see it differently."

### ⛔ Not Allowed
- "This is stupid."
- "You're an idiot."
- "What the hell were you thinking?"

## Why Tone Matters
Sharp language creates defensiveness. Calm language creates dialogue.

## How We Detect Tone
- Pattern-based analysis (not perfect)
- Content Integrity Score (CIS)
- Human review for edge cases

## What Happens
- CIS < 0.85: Gentle nudge to rephrase
- CIS < 0.70: Post blocked
- Persistent low CIS: Harmony score impact

Tone Choices

  • No shame ("not allowed" not "violations")
  • Examples (show, don't just tell)
  • Honesty (admit imperfection)

Contextual Help

1. "Why am I seeing this?"

Trigger: Tap "..." on any post

Copy:

## Why This Post

This appeared in your Sojorn feed because:
- High calm velocity (287 appreciates, 45 saves)
- Category: General Discussion (you're subscribed)
- Posted 2 hours ago

2. "Why can't I comment?"

Scenario: Non-mutual follow

Copy:

## Commenting

You can comment when you both follow each other.

This protects against drive-by harassment and ensures dialogue, not performance.

Tone:

  • No "You're not allowed"
  • Just "This is how it works"

Part 6: Accessibility & Inclusivity

Text Accessibility

1. Scalable Font Sizes

Implementation:

Text(
  post.body,
  style: Theme.of(context).textTheme.bodyLarge,
  // Respects user's OS text size settings
)

Why:

  • Low vision users need large text
  • Flutter automatically scales with OS settings
  • No hardcoded font sizes

2. High Readability Contrast

Ratios:

  • textPrimary on background: 12.5:1 (AAA)
  • textSecondary on background: 7.2:1 (AA)
  • textTertiary on background: 4.8:1 (AA large text)

Why:

  • WCAG AAA for body text
  • Still feels calm (not harsh black on white)

Interaction Accessibility

1. Keyboard Navigation (Web)

Requirements:

  • Tab through all interactive elements
  • Enter/Space activates buttons
  • Escape closes modals
  • Arrow keys in lists

Implementation:

Focus(
  onKey: (node, event) {
    // Handle keyboard events
  },
  child: Widget(),
)

2. Screen Reader Labels

Example:

IconButton(
  icon: Icon(Icons.favorite_border),
  tooltip: 'Appreciate this post',
  semanticsLabel: 'Appreciate post from ${post.author.displayName}',
)

Why:

  • Icon alone = meaningless to screen readers
  • Context matters

3. Focus States

Design:

focusColor: AppTheme.accent.withValues(alpha: 0.1),
// Soft highlight, not harsh outline

Motion Accessibility

1. Respect Reduced Motion

Check:

final reducedMotion = MediaQuery.of(context).accessibleNavigation;

final duration = reducedMotion
  ? Duration.zero
  : AppTheme.durationMedium;

Where Applied:

  • Fade transitions
  • Sheet slides
  • Loading spinners

2. No Required Animations

Rule:

  • All info accessible without animation
  • Skeleton loaders have static alt
  • Progress shown via text too

Cognitive Load

1. Avoid Dense UI

Guidelines:

  • Maximum 3 actions per card
  • One primary action per screen
  • Generous spacing (24px, not 8px)

2. Avoid Urgency

No:

  • Red badges
  • Pulsing animations
  • "New!" labels

Why:

  • Reduces anxiety
  • Allows focus
  • Respects attention

Part 7: Final Polish

Microinteractions

1. Subtle Haptics (Mobile)

When:

  • Appreciate/Save actions (light impact)
  • Publish post (medium impact)
  • Error state (notification feedback)

Implementation:

HapticFeedback.lightImpact();  // Not heavyImpact

Why:

  • Confirms action
  • But doesn't startle

2. Sound Cues (Optional, OFF by default)

Future:

  • Soft "bloom" on appreciate
  • Gentle "save" sound
  • Muted error tone

Default: OFF Why: Audio = intrusive

Loading States

1. Skeletons, Not Spinners

Design:

ShimmerSkeleton(
  child: PostCardSkeleton(),
)

Why:

  • Shows structure
  • Feels faster
  • Less anxiety than spinner

2. Calm Copy

Good:

"Loading..."
"One moment"

Bad:

❌ "Hang tight!"
❌ "Almost there!"
❌ "This won't take long!"

Tone:

  • Neutral, not cheerful
  • Honest, not performative

Error Handling

1. Gentle Language

Good:

"Couldn't load posts"
"Connection issue"

Bad:

❌ "ERROR: Network failure"
❌ "Oops! Something went wrong!"
❌ "Fatal exception occurred"

2. Clear Recovery

Pattern:

[Error message]
[What happened]
[Action button]

Example:

Couldn't load posts

Check your internet connection.

[ Try Again ]

3. No Blame

Don't:

  • "You're offline"
  • "Invalid input"

Do:

  • "No connection"
  • "Hmm, that didn't work"

Performance

1. Optimize List Rendering

ListView.builder(
  itemBuilder: (context, index) {
    return RepaintBoundary(
      child: PostCard(post: posts[index]),
    );
  },
)

Why:

  • RepaintBoundary = isolate repaints
  • Smooth 60fps scroll

2. Cache Feeds Responsibly

  • Cache for 5 minutes
  • Invalidate on pull-to-refresh
  • Respect memory limits

3. No Jank on Scroll

Techniques:

  • Lazy load images
  • Debounce pagination
  • Avoid setState in scroll listener

Summary: What Makes Sojorn Calm

Visual Calm

  • Warm neutrals, not cold grays
  • Soft shadows (4-8% opacity)
  • Generous spacing (24px, not 8px)
  • Muted colors (no bright red/blue)

Interaction Calm

  • Slow animations (300-400ms)
  • Gentle press states (no bounce)
  • Subtle haptics (light, not heavy)
  • No urgency cues

Content Calm

  • Body text is hero (17px, 1.7 line-height)
  • Metrics de-emphasized (11px, tertiary)
  • Author identity clear but quiet
  • No performance pressure

Structural Calm

  • Mutual-follow commenting
  • Category opt-in
  • Block without drama
  • Tone guidance without shame

Cognitive Calm

  • No mystery (transparency pages)
  • No dark patterns (honest UI)
  • No jargon (plain language)
  • No surprises (predictable navigation)

Result: An app that feels like settling into a good book, not scrolling a frantic timeline.

Enforcement: Design system + custom widgets make it impossible to violate calm principles.

Philosophy: Calm is not a feature. Calm is structural.