• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

agentic-dev-library / thumbcode / 21961208242

12 Feb 2026 07:29PM UTC coverage: 51.569% (-13.4%) from 64.947%
21961208242

push

github

web-flow
feat: complete Expo → Capacitor/Vite migration (#130)

## Summary

Full framework migration from Expo/React Native to Vite + Capacitor + React Router:

- **Framework**: Expo SDK 52 → Vite 6 + Capacitor 7
- **Routing**: expo-router → react-router-dom v7
- **Styling**: NativeWind → Tailwind CSS v4
- **Testing**: Jest → Vitest + @testing-library/react
- **Components**: React Native primitives → HTML/JSX (View→div, Text→span, etc.)
- **Build**: Metro bundler → Vite with HMR

### Key changes
- Removed all React Native and Expo dependencies
- Migrated 50+ components from RN to web HTML/JSX
- Rewrote 70 test files (844 tests passing)
- Replaced deploy-gh-pages.yml: Astro docs → Vite static site deployment
- Added android-release.yml: per-architecture debug APKs on GitHub releases
- Added ABI splits to build.gradle (armeabi-v7a, arm64-v8a, x86_64, universal)
- Fixed all biome lint, TypeScript, and E2E test issues
- Deleted dead `app/` directory (old Expo Router screens)

### CI Status
All critical checks passing: Lint & Type Check, Run Tests (844 passing), Build Web, Build Web + Capacitor Sync, E2E Tests (Web), Security Scan, CodeQL, Validate PR.

1329 of 2944 branches covered (45.14%)

Branch coverage included in aggregate %.

138 of 866 new or added lines in 82 files covered. (15.94%)

120 existing lines in 23 files now uncovered.

2188 of 3876 relevant lines covered (56.45%)

9.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.1
/src/components/chat/ChatThread.tsx
1
/**
2
 * Chat Thread Component
3
 *
4
 * Displays a scrollable list of messages in a chat thread.
5
 * Includes typing indicators and auto-scroll to bottom.
6
 */
7

8
import { selectThreadMessages, selectTypingIndicators, useChatStore } from '@thumbcode/state';
9
import { useCallback, useEffect, useRef } from 'react';
10
import { Text } from '@/components/ui';
11
import { ChatService } from '@/services/chat';
12
import { ChatMessage } from './ChatMessage';
13

14
/** Props for the ChatThread component */
15
interface ChatThreadProps {
16
  /** ID of the thread to display */
17
  threadId: string;
18
}
19

20
/**
21
 * Typing indicator dot animation placeholder
22
 */
23
function TypingIndicator({ senders }: { senders: string[] }) {
24
  if (senders.length === 0) return null;
2✔
25

26
  const names = senders.map((s) => {
1✔
27
    const nameMap: Record<string, string> = {
1✔
28
      architect: 'Architect',
29
      implementer: 'Implementer',
30
      reviewer: 'Reviewer',
31
      tester: 'Tester',
32
    };
33
    return nameMap[s] || s;
1!
34
  });
35

36
  const label = names.length === 1 ? `${names[0]} is typing` : `${names.join(', ')} are typing`;
1!
37

38
  return (
2✔
39
    <div className="flex-row items-center px-4 py-2">
40
      <div className="flex-row mr-2">
41
        <div className="w-2 h-2 bg-neutral-400 rounded-full mr-1" />
42
        <div className="w-2 h-2 bg-neutral-500 rounded-full mr-1" />
43
        <div className="w-2 h-2 bg-neutral-600 rounded-full" />
44
      </div>
45
      <Text className="text-xs text-neutral-400 font-body italic">{label}...</Text>
46
    </div>
47
  );
48
}
49

50
export function ChatThread({ threadId }: Readonly<ChatThreadProps>) {
51
  const scrollRef = useRef<HTMLDivElement>(null);
3✔
52

53
  // Subscribe to messages and typing indicators
54
  const messages = useChatStore(selectThreadMessages(threadId));
3✔
55
  const typingSenders = useChatStore(selectTypingIndicators(threadId));
3✔
56

57
  // Handle approval responses
58
  const handleApprovalResponse = useCallback(
3✔
59
    (messageId: string, approved: boolean) => {
60
      ChatService.respondToApproval(threadId, messageId, approved);
×
61
    },
62
    [threadId]
63
  );
64

65
  // Auto-scroll to bottom on new messages
66
  useEffect(() => {
3✔
67
    if (messages.length > 0 && scrollRef.current) {
3✔
68
      setTimeout(() => {
2✔
NEW
69
        scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
×
70
      }, 100);
71
    }
72
  }, [messages.length]);
73

74
  if (messages.length === 0) {
3✔
75
    return (
1✔
76
      <div className="flex-1 items-center justify-center p-4">
77
        <Text className="font-display text-lg text-neutral-400 text-center mb-2">
78
          Start the conversation
79
        </Text>
80
        <Text className="font-body text-sm text-neutral-500 text-center">
81
          Send a message to begin collaborating with AI agents
82
        </Text>
83
      </div>
84
    );
85
  }
86

87
  return (
2✔
88
    <div className="flex-1">
89
      <div ref={scrollRef} style={{ overflowY: 'auto', flex: 1 }}>
90
        {messages.map((item) => (
91
          <ChatMessage key={item.id} message={item} onApprovalResponse={handleApprovalResponse} />
4✔
92
        ))}
93
      </div>
94
      <TypingIndicator senders={typingSenders} />
95
    </div>
96
  );
97
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc