• 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

0.0
/src/components/form/TextInput.tsx
1
/**
2
 * TextInput Component (Form)
3
 *
4
 * A styled <input> with label, helper text, error state, and organic styling.
5
 * Web-native replacement for the React Native TextInput form wrapper.
6
 */
7

8
import { useState } from 'react';
9

10
interface TextInputProps {
11
  /** Current input value */
12
  value?: string;
13
  /** Callback when text changes */
14
  onChange?: (text: string) => void;
15
  /** Placeholder text */
16
  placeholder?: string;
17
  /** Label text above the input */
18
  label?: string;
19
  /** Helper text below the input */
20
  helper?: string;
21
  /** Error message (shows in error state) */
22
  error?: string;
23
  /** Whether the input is disabled */
24
  disabled?: boolean;
25
  /** Input type */
26
  type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search';
27
  /** Maximum text length */
28
  maxLength?: number;
29
  /** Whether the field is required */
30
  required?: boolean;
31
  /** Auto-complete hint */
32
  autoComplete?: string;
33
  /** Additional CSS class names for the input */
34
  className?: string;
35
  /** Test identifier */
36
  testID?: string;
37
}
38

39
export function TextInput({
40
  value,
41
  onChange,
42
  placeholder,
43
  label,
44
  helper,
45
  error,
46
  disabled = false,
×
47
  type = 'text',
×
48
  maxLength,
49
  required = false,
×
50
  autoComplete,
51
  className = '',
×
52
  testID,
53
}: Readonly<TextInputProps>) {
NEW
54
  const [isFocused, setIsFocused] = useState(false);
×
NEW
55
  const hasError = Boolean(error);
×
56

NEW
57
  const borderClass = hasError
×
58
    ? 'border-coral-500'
59
    : isFocused
×
60
      ? 'border-teal-500'
61
      : 'border-neutral-600';
62

NEW
63
  return (
×
64
    <div className="w-full">
65
      {label && (
×
66
        <label
67
          htmlFor={testID || `input-${label}`}
×
68
          className="block font-body text-sm text-neutral-300 mb-1.5"
69
        >
70
          {label}
71
          {required && <span className="text-coral-500 ml-0.5">*</span>}
×
72
        </label>
73
      )}
74
      <input
75
        id={testID || (label ? `input-${label}` : undefined)}
×
76
        type={type}
77
        value={value}
NEW
78
        onChange={(e) => onChange?.(e.target.value)}
×
79
        placeholder={placeholder}
80
        disabled={disabled}
81
        maxLength={maxLength}
82
        autoComplete={autoComplete}
NEW
83
        onFocus={() => setIsFocused(true)}
×
NEW
84
        onBlur={() => setIsFocused(false)}
×
85
        className={`w-full bg-neutral-800 text-white font-body px-4 py-3 border rounded-organic-input placeholder:text-neutral-400 transition-colors disabled:opacity-50 ${borderClass} ${className}`}
86
        data-testid={testID}
87
        aria-invalid={hasError || undefined}
×
88
        aria-describedby={error || helper ? `${testID}-hint` : undefined}
×
89
      />
×
90
      {(helper || error) && (
91
        <p
92
          id={testID ? `${testID}-hint` : undefined}
×
93
          className={`font-body text-xs mt-1.5 ${hasError ? 'text-coral-400' : 'text-neutral-500'}`}
×
94
        >
95
          {error || helper}
×
96
        </p>
97
      )}
98
    </div>
99
  );
100
}
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