JSPM

@codeimplants/app-core

1.0.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q33260F
  • License MIT

Platform-agnostic foundational package for building robust applications across Web, React Native, Ionic, and Capacitor

Package Exports

  • @codeimplants/app-core
  • @codeimplants/app-core/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@codeimplants/app-core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

@codeimplants/app-core

Platform-agnostic App Foundation / Core SDK for building robust, production-ready applications across Web (React), React Native, Ionic, and Capacitor. Single shared packageβ€”imported by every app. No app overrides core behavior.

License: MIT

πŸ“‹ Table of Contents

🎯 Overview

@codeimplants/app-core is the platform-agnostic App Foundation / Core SDK that provides shared business logic and infrastructure independent of UI and platform. It acts as the single shared foundation for all applicationsβ€”every app imports it, and core behavior is not overridden by apps.

What it Does

  • Global Error Boundary - Unified error handling, retry & circuit breaker, central support
  • Environment and configuration management - Centralized config with feature flags
  • API layer abstraction - HTTP client, interceptors, error normalization
  • Authentication logic - Login, logout, token refresh, session handling
  • Storage abstraction - Unified interface for web and native storage
  • App lifecycle hooks - init, pause, resume, destroy
  • Logging system - Dev and production support with configurable levels
  • Integration layer - Offline/network status monitoring
  • Shared constants, types, and utilities - Type-safe interfaces

What it Doesn't Do

  • ❌ No UI components or styling (use @codeimplants/ui-kit for screens)
  • ❌ No platform-specific code (no direct localStorage, AsyncStorage, DOM usage)
  • ❌ No framework-specific dependencies

βœ… App Foundation Checklist

Requirement API / Feature
Global Error Boundary handleError(error, context), reportCrash(context) + Error Boundary integration
Unified fallback UI showFallback(type) returns fallback config; emit fallback_requested
Retry & circuit breaker executeWithRetry(), RetryManager, CircuitBreaker
Support Use @codeimplants/support with ui-kit for WhatsApp/Email/Phone buttons
App lifecycle hooks onInit(), onPause(), onResume(), destroy()
Config & feature flags getConfig(), updateConfig(), getFeatureFlag(key)

Required APIs (Must Provide)

appCore.handleError(error, context)   // Error classification + decision
appCore.showFallback(type)            // Unified fallback config
appCore.reportCrash(context)          // Crash reporting
// Support: use @codeimplants/support + ui-kit for WhatsApp/Email/Phone buttons

✨ Features

  • πŸ” Authentication Manager - Complete token management, auto-refresh, session handling
  • πŸ’Ύ Storage Abstraction - Unified interface for Web, React Native, and Ionic storage
  • 🌐 Robust API Client - Http client with interceptors, error normalization, and types
  • πŸ”„ Intelligent Retry Logic - Exponential backoff, circuit breakers, configurable strategies
  • πŸ›‘οΈ Robust Error Handling - Error classification, automatic recovery, support escalation
  • πŸ“Š App State Management - Health monitoring, connectivity tracking, degraded state handling
  • 🎯 Support Integration - Contextual support, frustration detection, multi-channel
  • πŸ”§ Recovery Strategies - Clear cache, reset state, reload app, safe mode
  • πŸ“ Comprehensive Logging - Configurable levels, namespace support, production-ready
  • πŸŽͺ Event System - Type-safe event emitter for cross-module communication
  • βœ… Full TypeScript Support - Complete type definitions and inference
  • πŸ§ͺ Testable - Dependency injection, mockable interfaces
  • πŸ“¦ Zero Dependencies - No runtime dependencies, minimal bundle size

πŸ“¦ Installation

npm install @codeimplants/app-core

or

yarn add @codeimplants/app-core

πŸ“‹ Copy-Paste Setup

Copy these blocks into your app to enable core features.

1. AppCore init (src/init.ts or appCore.ts)

import {
  AppCore,
  MemoryStorage,
  Logger,
} from '@codeimplants/app-core'

const appCore = new AppCore({
  errorConfig: {
    maxRetries: 3,
    supportThreshold: 2,
    recoveryThreshold: 5,
  },
  featureFlags: {
    // your flags
  },
})

// Optional: global uncaught error handler (call early in bootstrap)
if (typeof window !== 'undefined') {
  window.onerror = (message, source, lineno, colno, error) => {
    appCore.reportCrash({
      message: String(message),
      stack: error?.stack,
      metadata: { source, lineno, colno },
    })
  }
}

export { appCore }

2. React – root wrap (App.tsx)

import React, { Component, ErrorInfo, ReactNode } from 'react'
import { appCore } from './init'

interface Props { children: ReactNode }

class AppErrorBoundary extends Component<Props, { hasError: boolean }> {
  state = { hasError: false }

  static getDerivedStateFromError() {
    return { hasError: true }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    appCore.reportCrash({
      message: error.message,
      stack: error.stack,
      component: errorInfo.componentStack,
    })
  }

  render() {
    if (this.state.hasError) {
      const fallback = appCore.showFallback('generic_error')
      return (
        <div>
          <h1>{fallback.title}</h1>
          <p>{fallback.message}</p>
          <button onClick={() => { appCore.resetErrorState(); this.setState({ hasError: false }) }}>
            Try Again
          </button>
          <button>Contact Support</button> {/* Wire to @codeimplants/support */}
        </div>
      )
    }
    return this.props.children
  }
}

function App() {
  return (
    <AppErrorBoundary>
      {/* Your app */}
    </AppErrorBoundary>
  )
}

3. React Native – root wrap + lifecycle

import React, { Component, ErrorInfo, ReactNode } from 'react'
import { View, Text, Button, AppState, AppStateStatus } from 'react-native'
import { appCore } from './init'

// Error boundary
interface Props { children: ReactNode }
class AppErrorBoundary extends Component<Props, { hasError: boolean }> {
  state = { hasError: false }

  static getDerivedStateFromError() {
    return { hasError: true }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    appCore.reportCrash({
      message: error.message,
      stack: error.stack,
      component: errorInfo.componentStack,
    })
  }

  render() {
    if (this.state.hasError) {
      const fallback = appCore.showFallback('generic_error')
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
          <Text>{fallback.title}</Text>
          <Text>{fallback.message}</Text>
          <Button title="Try Again" onPress={() => { appCore.resetErrorState(); this.setState({ hasError: false }) }} />
          <Button title="Contact Support" /> {/* Wire to @codeimplants/support */}
        </View>
      )
    }
    return this.props.children
  }
}

// Lifecycle sync (use in root component)
export function useAppLifecycle() {
  React.useEffect(() => {
    const sub = AppState.addEventListener('change', (state: AppStateStatus) => {
      if (state === 'active') appCore.onResume()
      else if (state === 'background') appCore.onPause()
    })
    return () => sub.remove()
  }, [])
}

// Usage in App.tsx
export default function App() {
  useAppLifecycle()
  return (
    <AppErrorBoundary>
      {/* Your app */}
    </AppErrorBoundary>
  )
}

4. Error handling in async code

import { appCore } from './init'

async function fetchData() {
  try {
    const data = await appCore.executeWithRetry(() => fetch('/api/data').then(r => r.json()), {
      maxAttempts: 3,
      onRetry: (attempt) => console.log(`Retry ${attempt}`),
    })
    return data
  } catch (error) {
    const decision = appCore.handleError(error as Error, { action: 'fetch_data', component: 'DataScreen' })
    if (decision.action === 'show_support') { /* Show ui-kit screen with support buttons */ }
    else if (decision.screenData) appCore.showFallback(decision.screen ?? 'generic_error', decision.screenData)
    throw error
  }
}

5. Connectivity sync (with @codeimplants/app-network)

import { useEffect } from 'react'
import { OfflineProvider, OfflineBanner, useOffline } from '@codeimplants/app-network'
import { appCore } from './init'

function ConnectivitySync() {
  const { isOnline } = useOffline()
  useEffect(() => { appCore.updateConnectivity(isOnline) }, [isOnline])
  return null
}

export function App() {
  return (
    <OfflineProvider>
      <ConnectivitySync />
      <OfflineBanner />
      {/* Your app */}
    </OfflineProvider>
  )
}

6. Version check before render (with @codeimplants/version-control)

import { VersionSDK } from '@codeimplants/version-control'
import { appCore } from './init'

export async function bootstrap() {
  const decision = await VersionSDK.check('https://api.yourapp.com', 'your-api-key')
  if (['FORCE_UPDATE', 'KILL_SWITCH', 'MAINTENANCE'].includes(decision.action)) {
    appCore.showFallback('maintenance', { message: decision.message })
    return false
  }
  appCore.onInit()
  return true
}

πŸš€ Quick Start

Basic Setup

import {
  AppCore,
  AuthManager,
  ApiClient,
  MemoryStorage,
  Logger,
  AppCoreEventEmitter,
} from '@codeimplants/app-core'

// 1. Initialize dependencies
const logger = new Logger('App')
const eventEmitter = new AppCoreEventEmitter()
const storage = new MemoryStorage() // Use platform-specific adapter in production

// 2. Initialize AppCore
const appCore = new AppCore({
  errorConfig: {
    maxRetries: 3,
  },
  // ... other config
})

// 3. Initialize AuthManager
const authManager = new AuthManager(
  storage,
  eventEmitter,
  {
    tokenKey: 'app_token',
    autoRefresh: true,
  },
  logger
)

// 4. Initialize ApiClient
const apiClient = new ApiClient(
  {
    baseURL: 'https://api.yourapp.com',
    timeout: 30000,
  },
  logger
)

export { appCore, authManager, apiClient }

Making Authenticated Requests

import { AuthInterceptor } from '@codeimplants/app-core'
import { apiClient, authManager } from './init'

// Add auth interceptor to automatically inject token
apiClient.addInterceptor(
  new AuthInterceptor(async () => {
    return await authManager.getToken()
  })
)

async function fetchUserProfile() {
  try {
    // Typed response
    const response = await apiClient.get<UserProfile>('/api/me')
    return response.data
  } catch (error) {
    // Error is automatically normalized
    throw error
  }
}

Error Handling

import { appCore } from './init'

async function safeFetch() {
  try {
    // ... operation
  } catch (error) {
    // Get intelligent decision on how to handle the error
    const decision = appCore.handleError(error as Error, {
      action: 'fetch_data',
      component: 'UserProfile',
    })

    switch (decision.action) {
      case 'retry':
        // ...
        break
      // ... handle other actions
    }
  }
}

Retry with Automatic Backoff

import appCore from './appCore'

async function fetchWithRetry() {
  try {
    const data = await appCore.executeWithRetry(
      async () => {
        const response = await fetch('/api/data')
        if (!response.ok) throw new Error('API Error')
        return response.json()
      },
      {
        maxAttempts: 3,
        exponentialBackoff: true,
        timeout: 5000,
        onRetry: (attempt, delay) => {
          console.log(`Retry attempt ${attempt}, waiting ${delay}ms`)
        },
      }
    )

    return data
  } catch (error) {
    console.error('All retries exhausted', error)
    throw error
  }
}

App State Monitoring

import appCore from './appCore'

// Get current app state
const state = appCore.getAppState()

console.log('Current state:', state.state) // 'healthy' | 'degraded' | 'critical' | 'offline' | 'maintenance'
console.log('Can make API calls:', state.allowedActions.canMakeApiCalls)
console.log('Can use cache:', state.allowedActions.canUseCache)

// Listen to state changes
appCore.on('state_changed', (event) => {
  console.log('State changed:', event.payload.previousState, '->', event.payload.newState)

  // Update UI based on new state
  if (event.payload.newState === 'offline') {
    showOfflineBanner()
  }
})

// Update connectivity manually
window.addEventListener('online', () => appCore.updateConnectivity(true))
window.addEventListener('offline', () => appCore.updateConnectivity(false))

🧩 Core Concepts

Managers

The package is organized into specialized managers:

  • ErrorManager - Classifies errors, tracks error history, determines recovery strategies

  • RetryManager - Handles retry logic with backoff strategies and circuit breakers

  • AppStateManager - Monitors app health, connectivity, and determines allowed actions

  • RecoveryManager - Executes recovery strategies when errors persist

Event System

All managers emit events through a centralized event emitter:

// Listen to all events
appCore.on('*', (event) => {
  console.log('Event:', event.type, event.payload)
})

// Listen to specific events
appCore.on('error_occurred', (event) => {
  // Send to analytics
  analytics.track('error', event.payload)
})

appCore.on('retry_attempted', (event) => {
  console.log(`Retry ${event.payload.attemptNumber}/${event.payload.maxAttempts}`)
})

Error Classification

Errors are automatically classified into types:

  • network - Network connectivity issues
  • timeout - Request timeouts
  • server - 5xx server errors
  • client - 4xx client errors
  • auth - Authentication/authorization errors
  • not_found - 404 errors
  • rate_limit - Rate limiting errors
  • validation - Input validation errors
  • critical - Critical system errors
  • unknown - Unclassified errors

Retry Strategies

Built-in retry strategies:

  • ExponentialBackoff - Delay doubles with each retry (1s, 2s, 4s, 8s...)
  • LinearBackoff - Fixed delay between retries
  • CircuitBreaker - Prevents cascading failures by opening circuit after threshold

πŸ“š API Reference

AppCore

Constructor

new AppCore(config: AppCoreConfig)

Methods

Method Description
handleError(error, context?) Handle error, return ErrorDecision (retry, show_error, show_support, recover)
showFallback(type, overrides?) Return unified fallback config; emit fallback_requested
reportCrash(context) Report crash; emit crash_reported; track in ErrorManager
executeWithRetry(operation, options?) Execute with retry & circuit breaker
getAppState() Current app state (healthy, degraded, critical, offline, maintenance)
updateConnectivity(isOnline) Update network status
resetErrorState() Reset consecutive error count
getFeatureFlag(key) Get feature flag value
onInit(metadata?) App lifecycle: init
onPause(metadata?) App lifecycle: pause (background)
onResume(metadata?) App lifecycle: resume (foreground)
on(eventType, callback) Subscribe to events
getConfig() / updateConfig() Config access
destroy() Cleanup

Fallback Types

showFallback(type) accepts: offline, api_error, maintenance, slow_connection, permission, rate_limit, generic_error, loading, empty_state, session_expired.

Event Types

  • error_occurred - When an error is handled
  • fallback_requested - When showFallback() is called
  • crash_reported - When reportCrash() is called
  • retry_attempted, state_changed, recovery_started
  • app_init, app_pause, app_resume, app_destroy - Lifecycle
  • circuit_breaker_opened, circuit_breaker_closed

πŸ”Œ Platform Integration

React

// hooks/useAppCore.ts
import { useEffect, useState } from 'react';
import appCore from '../appCore';

export function useAppState() {
  const [state, setState] = useState(appCore.getAppState());

  useEffect(() => {
    const unsubscribe = appCore.on('state_changed', () => {
      setState(appCore.getAppState());
    });

    return unsubscribe;
  }, []);

  return state;
}

// Component usage
function App() {
  const appState = useAppState();

  if (appState.state === 'offline') {
    return <OfflineBanner />;
  }

  return <YourApp />;
}

React Native

import NetInfo from '@react-native-community/netinfo'
import appCore from './appCore'

// Monitor network connectivity
NetInfo.addEventListener((state) => {
  appCore.updateConnectivity(state.isConnected ?? false)
})

Ionic/Capacitor

import { Network } from '@capacitor/network'
import appCore from './appCore'

// Monitor network status
Network.addListener('networkStatusChange', (status) => {
  appCore.updateConnectivity(status.connected)
})

App-core is the foundation; integrate with these packages for full-stack resilience:

@codeimplants/version-control

Version checks (soft/force updates, maintenance, kill switch). Call before rendering the app.

import { VersionSDK } from '@codeimplants/version-control'
import { appCore } from './init'

async function bootstrap() {
  const decision = await VersionSDK.check('https://api.yourapp.com', 'your-api-key')

  switch (decision.action) {
    case 'FORCE_UPDATE':
    case 'KILL_SWITCH':
      appCore.showFallback('maintenance', { message: decision.message })
      return
    case 'MAINTENANCE':
      appCore.showFallback('maintenance', { message: decision.message })
      return
  }

  appCore.onInit()
  // Render app
}

@codeimplants/app-network

Connectivity monitoring + OfflineBanner. Feed status into app-core.

import { OfflineProvider, OfflineBanner, useOffline } from '@codeimplants/app-network'
import { useEffect } from 'react'
import { appCore } from './init'

// Sync connectivity to app-core (must be inside OfflineProvider)
function ConnectivitySync() {
  const { isOnline } = useOffline()
  useEffect(() => {
    appCore.updateConnectivity(isOnline)
  }, [isOnline])
  return null
}

function App() {
  return (
    <OfflineProvider>
      <ConnectivitySync />
      <OfflineBanner />
      <YourApp />
    </OfflineProvider>
  )
}

React Native: Use @react-native-community/netinfo; app-network detects RN and uses it. Add the same ConnectivitySync component.

@codeimplants/ui-kit

Error screens (ApiErrorScreen, NetworkOfflineScreen, etc.) map to showFallback / handleError results.

import {
  ApiErrorScreen,
  NetworkOfflineScreen,
  ErrorBoundaryScreen,
} from '@codeimplants/ui-kit'
import { appCore, type FallbackData } from '@codeimplants/app-core'

// Map fallback type to ui-kit screens
function FallbackRenderer({ fallback }: { fallback: FallbackData }) {
  switch (fallback.type) {
    case 'offline':
      return <NetworkOfflineScreen onRetry={() => appCore.resetErrorState()} />
    case 'api_error':
    case 'generic_error':
      return (
        <ApiErrorScreen
          onRetry={() => appCore.resetErrorState()}
          support={{ whatsapp: { number: '...' }, email: { address: '...' } }}
        />
      )
    default:
      return <ErrorBoundaryScreen message={fallback.message} />
  }
}

@codeimplants/support

Support logic (WhatsApp, Email, Phone). Use with ui-kit error screensβ€”ui-kit shows support buttons; on click, use useSupport().openWhatsApp() etc.

import { SupportProvider } from '@codeimplants/support'

// Wrap app; ui-kit screens use useSupport for button actions
<SupportProvider config={{ whatsapp: { number: '...' }, email: { address: '...' } }}>
  <YourApp />
</SupportProvider>

πŸ›‘οΈ Global Error Boundary Integration

App-core provides logic; your app provides the UI error boundary. Wire them together:

React

import React, { Component, ErrorInfo, ReactNode } from 'react'
import { appCore } from './init'

interface Props {
  children: ReactNode
  fallback?: ReactNode
}

export class AppErrorBoundary extends Component<Props, { hasError: boolean; error?: Error }> {
  state = { hasError: false, error: undefined as Error | undefined }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    appCore.reportCrash({
      message: error.message,
      stack: error.stack,
      component: errorInfo.componentStack,
      metadata: { errorInfo },
    })
    appCore.showFallback('generic_error', { message: error.message })
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? <div>Something went wrong.</div>
    }
    return this.props.children
  }
}

React Native

import React, { Component, ErrorInfo, ReactNode } from 'react'
import { View, Text, Button } from 'react-native'
import { appCore } from './init'
// Optional: use @codeimplants/ui-kit ErrorBoundaryScreen
// import { ErrorBoundaryScreen } from '@codeimplants/ui-kit'

interface Props {
  children: ReactNode
}

export class AppErrorBoundary extends Component<Props, { hasError: boolean; error?: Error }> {
  state = { hasError: false, error: undefined as Error | undefined }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    appCore.reportCrash({
      message: error.message,
      stack: error.stack,
      component: errorInfo.componentStack,
    })
    appCore.showFallback('generic_error', { message: error.message })
  }

  handleRetry = () => {
    appCore.resetErrorState()
    this.setState({ hasError: false, error: undefined })
  }

  render() {
    if (this.state.hasError) {
      return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 }}>
          <Text>Something went wrong.</Text>
          <Button title="Try Again" onPress={this.handleRetry} />
          <Button title="Contact Support" /> {/* Wire to @codeimplants/support */}
        </View>
      )
    }
    return this.props.children
  }
}

Wire global uncaught errors

// index.tsx or App.tsx (early in bootstrap)
window.onerror = (message, source, lineno, colno, error) => {
  appCore.reportCrash({
    message: String(message),
    stack: error?.stack,
    metadata: { source, lineno, colno },
  })
}

// React Native: use ErrorUtils
if (typeof ErrorUtils !== 'undefined') {
  const originalHandler = ErrorUtils.getGlobalHandler()
  ErrorUtils.setGlobalHandler((error, isFatal) => {
    appCore.reportCrash({
      message: error.message,
      stack: error.stack,
      metadata: { isFatal },
    })
    originalHandler?.(error, isFatal)
  })
}

πŸ’‘ Best Practices

1. Initialize Early

Initialize AppCore as early as possible in your application lifecycle:

// index.ts or App.tsx
import appCore from './appCore'

// Configure logger for development
if (process.env.NODE_ENV === 'development') {
  Logger.configure({ minLevel: 'debug' })
}

2. Centralize Error Handling

Use the core APIsβ€”never bypass app-core:

// errorHandler.ts
import { appCore } from './init'

export function globalErrorHandler(error: Error, context?: ErrorContext) {
  const decision = appCore.handleError(error, context)

  switch (decision.action) {
    case 'retry':
      return appCore.executeWithRetry(() => /* retry logic */)
    case 'show_error':
      return appCore.showFallback(decision.screen ?? 'generic_error', decision.screenData)
    case 'show_support':
      return appCore.showFallback(decision.screen ?? 'generic_error', {
        ...decision.screenData,
        supportAvailable: true,
      }) // ui-kit screen shows support buttons; use @codeimplants/support for actions
    case 'recover':
      return /* trigger recovery via RecoveryManager */
  }
}

3. Use Type Guards

Leverage TypeScript for type safety:

import { NetworkError, APIError } from '@codeimplants/app-core'

function handleError(error: Error) {
  if (error instanceof NetworkError) {
    // Handle network error
  } else if (error instanceof APIError) {
    // Handle API error
  }
}

4. Monitor Events

Set up analytics tracking:

appCore.on('*', (event) => {
  // Send to analytics service
  analytics.track(`app_core_${event.type}`, event.payload)
})

5. Clean Up

Always clean up when unmounting:

useEffect(() => {
  const unsubscribe = appCore.on('error_occurred', handleError)

  return () => {
    unsubscribe()
  }
}, [])

πŸ“– Full Application Integration

Recommended bootstrap order:

  1. Initialize AppCore (single instance)
  2. Run VersionSDK.check() β†’ show maintenance/update fallback if needed
  3. Wrap app with OfflineProvider + ConnectivitySync (app-network)
  4. Wrap app with AppErrorBoundary (calls reportCrash, showFallback)
  5. Set global window.onerror / ErrorUtils.setGlobalHandler
  6. Wrap with SupportProvider (@codeimplants/support) if using support
  7. Call appCore.onInit() when app is ready
  8. Use appCore.onPause() / appCore.onResume() on app state changes (React Native: AppState)

React Native lifecycle example:

import { AppState, AppStateStatus } from 'react-native'

useEffect(() => {
  const sub = AppState.addEventListener('change', (state: AppStateStatus) => {
    if (state === 'active') appCore.onResume()
    else if (state === 'background') appCore.onPause()
  })
  return () => sub.remove()
}, [])

πŸ“– Examples

See the examples directory for complete examples:

  • React SPA integration
  • React Native app integration
  • Ionic/Capacitor integration
  • API client wrapper
  • Custom error handling

πŸ“„ License

MIT Β© Code Implants Software Technologies Pvt. Ltd.

Package Purpose
@codeimplants/version-control Version checks, force/soft updates, maintenance, kill switch
@codeimplants/app-network Connectivity monitoring, OfflineBanner, resilient API client
@codeimplants/ui-kit Error screens (ApiErrorScreen, NetworkOfflineScreen, etc.)
@codeimplants/support Support logic (WhatsApp, Email, Phone); use with ui-kit error screens