suhail
spoecs
9eafd9f

Research: Full-Stack Integration & UI Experience

Feature: 002-fullstack-ui-integration Date: 2026-01-09 Status: Complete

Overview

This research document captures technical decisions, patterns, and best practices for integrating existing functionality (Specs 1 & 2) into a cohesive user experience. Since this is a polish/integration feature rather than new functionality, most decisions reference existing implementations.

Research Areas

1. UI State Management Patterns

Decision: Use React hooks (useState, useEffect) with loading/error/data states

Rationale:

  • Already established pattern in existing components (TaskList, TaskForm)
  • Simple and effective for component-level state
  • No need for global state management (Redux, Zustand) for this scope
  • Aligns with Next.js App Router best practices

Pattern:

const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<T | null>(null);

Alternatives Considered:

  • React Query / TanStack Query: Overkill for current scope, adds dependency
  • Redux: Too complex for simple loading/error states
  • Context API: Not needed - state is component-local

References:


2. Loading State Indicators

Decision: Use Tailwind CSS spinner with descriptive text

Rationale:

  • Consistent with existing Tailwind-only styling constraint
  • Accessible (includes text for screen readers)
  • Lightweight (no external animation libraries)
  • Fast to implement and customize

Pattern:

{isLoading && (
  <div className="flex items-center justify-center p-8">
    <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
    <span className="ml-3 text-gray-600">Loading tasks...</span>
  </div>
)}

Alternatives Considered:

  • Skeleton screens: More complex, better for content-heavy pages
  • Progress bars: Not suitable for indeterminate loading
  • Third-party libraries (react-spinners): Adds dependency, unnecessary

References:


3. Empty State Design

Decision: Centered message with icon and call-to-action

Rationale:

  • Guides users toward next action (create first task)
  • Reduces confusion when no data exists
  • Industry standard pattern (GitHub, Notion, Linear)
  • Improves onboarding experience

Pattern:

{tasks.length === 0 && !isLoading && (
  <div className="text-center py-12">
    <p className="text-gray-500 text-lg mb-4">No tasks yet</p>
    <p className="text-gray-400 mb-6">Create your first task to get started</p>
    <button className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
      Create Task
    </button>
  </div>
)}

Alternatives Considered:

  • Blank screen: Poor UX, users don't know what to do
  • Tutorial overlay: Too intrusive for simple app
  • Animated illustrations: Adds complexity, not needed

References:


4. Error Handling & Display

Decision: Inline error messages with retry button

Rationale:

  • Keeps user in context (no modal dialogs)
  • Provides actionable recovery (retry button)
  • Consistent with existing API error handling
  • Follows progressive disclosure principle

Pattern:

{error && (
  <div className="bg-red-50 border border-red-200 rounded-md p-4 mb-4">
    <div className="flex items-start">
      <div className="flex-1">
        <h3 className="text-sm font-medium text-red-800">Error</h3>
        <p className="text-sm text-red-700 mt-1">{error}</p>
      </div>
      <button
        onClick={handleRetry}
        className="ml-3 text-sm font-medium text-red-600 hover:text-red-500"
      >
        Retry
      </button>
    </div>
  </div>
)}

Alternatives Considered:

  • Toast notifications: Disappear too quickly, users miss them
  • Modal dialogs: Disruptive, blocks entire UI
  • Console.error only: Not user-facing, poor UX

References:


5. Responsive Design Breakpoints

Decision: Use Tailwind's default breakpoints (sm: 640px, md: 768px, lg: 1024px)

Rationale:

  • Already configured in existing tailwind.config.ts
  • Industry-standard breakpoints
  • Covers mobile (320px-767px), tablet (768px-1023px), desktop (1024px+)
  • No custom breakpoints needed for this scope

Pattern:

<div className="grid gap-6 lg:grid-cols-3 md:grid-cols-2 grid-cols-1">
  {/* Mobile: 1 column, Tablet: 2 columns, Desktop: 3 columns */}
</div>

Breakpoint Strategy:

  • Mobile (<768px): Single column, stacked layout
  • Tablet (768px-1023px): Two columns where appropriate
  • Desktop (≥1024px): Three columns, full layout

Alternatives Considered:

  • Custom breakpoints: Unnecessary complexity
  • Container queries: Not widely supported yet
  • Fixed pixel widths: Not responsive

References:


6. Touch Target Sizing

Decision: Minimum 44x44px for all interactive elements

Rationale:

  • WCAG 2.1 Level AAA guideline (44x44px)
  • Apple Human Interface Guidelines (44x44pt)
  • Material Design (48x48dp)
  • Prevents accidental taps on mobile devices

Pattern:

<button className="min-h-[44px] min-w-[44px] px-4 py-2">
  Click Me
</button>

Implementation:

  • Buttons: min-h-[44px] class
  • Links: Adequate padding (py-2 px-3 minimum)
  • Form inputs: h-11 or h-12 classes
  • Checkboxes: w-5 h-5 (20px) with larger clickable area via padding

Alternatives Considered:

  • 48x48px: More generous but takes more space
  • 40x40px: Below accessibility guidelines
  • Variable sizing: Inconsistent, harder to maintain

References:


7. API Client Error Handling

Decision: Centralized error handling in fetchAPI with typed errors

Rationale:

  • Already implemented in frontend/src/lib/api.ts
  • Consistent error structure across all API calls
  • TypeScript types for error responses
  • Automatic 401 handling with signin redirect

Existing Implementation:

class APIError extends Error {
  constructor(
    message: string,
    public status: number,
    public errorCode?: string,
    public fieldErrors?: Record<string, string[]>
  ) {
    super(message);
    this.name = 'APIError';
  }
}

Enhancement Needed: None - existing implementation is sufficient

Alternatives Considered:

  • Per-component error handling: Inconsistent, duplicated code
  • Global error boundary: Too coarse-grained, loses context
  • Axios interceptors: Adds dependency, fetch is sufficient

References:

  • Existing: frontend/src/lib/api.ts (lines 6-16, 18-59)

8. Form Validation Patterns

Decision: Client-side validation with inline error messages

Rationale:

  • Already implemented in SignUpForm and SignInForm
  • Immediate feedback improves UX
  • Reduces unnecessary API calls
  • Backend validation still enforced (defense in depth)

Existing Pattern:

const [errors, setErrors] = useState<Record<string, string>>({});

const validateEmail = (email: string): boolean => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};

// Display errors inline
{errors.email && (
  <p className="text-red-600 text-sm mt-1">{errors.email}</p>
)}

Enhancement Needed: None - existing validation is sufficient

Alternatives Considered:

  • Form libraries (React Hook Form, Formik): Overkill for simple forms
  • Schema validation (Zod, Yup): Adds complexity, not needed
  • Server-side only: Poor UX, slow feedback

References:

  • Existing: frontend/src/components/auth/SignUpForm.tsx (lines 20-40)
  • Existing: frontend/src/components/auth/SignInForm.tsx

9. Optimistic UI Updates

Decision: Update UI immediately, rollback on error

Rationale:

  • Improves perceived performance
  • Makes app feel responsive
  • Standard pattern for modern web apps
  • Easy to implement with React state

Pattern:

const handleToggleComplete = async (taskId: number) => {
  // Optimistic update
  setTasks(tasks.map(t =>
    t.id === taskId ? { ...t, completed: !t.completed } : t
  ));

  try {
    await patchTask(taskId, { completed: !task.completed });
  } catch (error) {
    // Rollback on error
    setTasks(tasks.map(t =>
      t.id === taskId ? { ...t, completed: task.completed } : t
    ));
    setError('Failed to update task');
  }
};

Alternatives Considered:

  • Wait for server response: Slower, less responsive
  • No rollback: Inconsistent state on errors
  • Pessimistic updates: Poor UX

References:


10. Environment Configuration

Decision: Use .env files with clear documentation

Rationale:

  • Already established in Specs 1 & 2
  • Standard practice for web applications
  • Keeps secrets out of source code
  • Easy to configure for different environments

Existing Configuration:

  • Backend: backend/.env (DATABASE_URL, BETTER_AUTH_SECRET, JWT_ALGORITHM, JWT_EXPIRATION_DAYS)
  • Frontend: frontend/.env.local (NEXT_PUBLIC_API_URL, BETTER_AUTH_SECRET)

Enhancement Needed: Document in README files and quickstart.md

Alternatives Considered:

  • Hardcoded values: Security risk, not flexible
  • Config files: Less standard than .env
  • Cloud secret managers: Overkill for local development

References:


Summary of Decisions

Area Decision Status
UI State Management React hooks (useState, useEffect) ✅ Existing
Loading Indicators Tailwind CSS spinner with text 🔄 To implement
Empty States Centered message with CTA 🔄 To implement
Error Display Inline errors with retry button 🔄 To implement
Responsive Design Tailwind default breakpoints ✅ Existing
Touch Targets Minimum 44x44px 🔄 To verify
API Error Handling Centralized fetchAPI with typed errors ✅ Existing
Form Validation Client-side with inline errors ✅ Existing
Optimistic Updates Immediate UI update with rollback 🔄 To implement
Environment Config .env files with documentation ✅ Existing

Legend:

  • ✅ Existing: Already implemented in Specs 1 & 2
  • 🔄 To implement: Needs to be added in this feature
  • 🔄 To verify: Needs to be checked/refined

Implementation Priorities

Based on user story priorities (P1-P5):

  1. P1 (Authentication Flow): Verify existing implementation works end-to-end
  2. P2 (UI States): Implement loading, empty, and error states
  3. P3 (Responsive Design): Verify and refine responsive layouts
  4. P4 (API Communication): Verify centralized API client works correctly
  5. P5 (Environment Setup): Document configuration in README files

Next Steps

  1. Generate data-model.md (reference existing User and Task entities)
  2. Generate contracts/ (document existing API endpoints)
  3. Generate quickstart.md (testing and setup guide)
  4. Proceed to task generation (/sp.tasks)