🎯 Today's Challenge: Why Do We Need a Desktop Version?
Yesterday we completed the web version, but discussions with tribal offices revealed new requirements: The tribal activity centers need desktop versions for elders to use large screens and keyboards easily.
This requirement exposed real pain points:
Elders prefer desktop computers' large screens and keyboards
Cross-platform support needed for macOS and Windows
Need to export Excel reports for government subsidy applications
Native desktop application stability and performance
🔗 Smart API Configuration: One Codebase, Three Deployments
Environment-Aware API Architecture
After multiple deployment battles, I designed a smart environment detection system:
// config/api.ts - The essence of multi-environment API configuration
const getApiBaseUrl = () => {
// Tauri desktop version: Connect to local Django backend
if (window.__TAURI__) {
return 'http://127.0.0.1:8000'
}
// Development mode: Handle CORS through Vite proxy
if (import.meta.env.DEV) {
return '' // Use relative paths, forwarded by proxy
}
// Production mode: Direct connect to Railway API
return 'https://pangcah-accounting-system-production.up.railway.app'
}
// Unified API client configuration
export const apiClient = axios.create({
baseURL: getApiBaseUrl(),
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
})
Design Highlights:
Zero-config switching: Same frontend codebase supports three deployment modes
Development experience optimization: Automatically handles CORS in local development
Production stability: Direct Railway connection ensures optimal performance
Seamless JWT Token Refresh Mechanism
// Response interceptor: Users never see login expiration
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
// Smart 401 error handling
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
try {
const refreshToken = localStorage.getItem('refresh_token')
if (refreshToken) {
// Attempt token refresh
const response = await axios.post('/api/v1/auth/refresh/', {
refresh: refreshToken,
})
const newToken = response.data.access
localStorage.setItem('access_token', newToken)
// Retry original request, completely seamless to user
originalRequest.headers.Authorization = `Bearer ${newToken}`
return apiClient(originalRequest)
}
} catch (refreshError) {
// Actually expired, gracefully redirect to login
localStorage.clear()
window.location.href = '/login'
return Promise.reject(new Error('Please log in again'))
}
}
return Promise.reject(error)
}
)
User Experience: During 8-hour family gatherings, users are never interrupted by token expiration.
🖥️ Tauri Desktop Version: Cross-Platform Native Experience
Why Choose Tauri?
After technical evaluation, Tauri best meets our macOS/Windows cross-platform needs:
// tauri.conf.json - Application configuration
{
"$schema": "../gen/schemas/desktop-schema.json",
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:5173",
"distDir": "../dist"
},
"package": {
"productName": "PAPA Pangcah Family Accounting",
"version": "1.0.0"
},
"tauri": {
"allowlist": {
"all": false,
"shell": {
"all": false,
"open": true
},
"dialog": {
"all": false,
"open": true,
"save": true
},
"fs": {
"all": false,
"readFile": true,
"writeFile": true,
"createDir": true
}
}
}
}
Technical Advantages:
Small file size: 8MB vs Electron's 100MB+
Native performance: Rust core provides near-native speed
Security: Principle of least privilege, only expose necessary functions
Development efficiency: Reuse existing React code
💻 Core Feature Implementation of Tauri Desktop Version
Large-Screen Friendly Interface Design
The desktop version's greatest value is the large-screen friendly interface designed for elders:
// hooks/useDesktop.ts - Desktop feature detection
export const useDesktop = () => {
const [isDesktop, setIsDesktop] = useState(false)
const [desktopAPI, setDesktopAPI] = useState<any>(null)
useEffect(() => {
// Detect Tauri environment
if (window.__TAURI__) {
setIsDesktop(true)
// Dynamically load Tauri API
import('@tauri-apps/api').then((tauri) => {
setDesktopAPI(tauri)
})
}
}, [])
// Desktop-exclusive feature: large font settings
const enableLargeTextMode = () => {
document.documentElement.style.fontSize = '120%'
localStorage.setItem('fontSize', 'large')
}
// Export report feature
const exportToExcel = async (eventData: EventData) => {
if (!isDesktop || !desktopAPI) return false
try {
const { save } = desktopAPI.dialog
const filePath = await save({
defaultPath: `${eventData.name}_Financial_Report.xlsx`,
filters: [{
name: 'Excel files',
extensions: ['xlsx']
}]
})
if (filePath) {
const excelData = generateExcelReport(eventData)
await desktopAPI.fs.writeTextFile(filePath, excelData)
showSnackbar(`Report saved to ${filePath}`, 'success')
return true
}
} catch (error) {
showSnackbar('Export failed: ' + error.message, 'error')
}
return false
}
return { isDesktop, exportToExcel, enableLargeTextMode }
}
Desktop-Exclusive Features:
Large screen optimization: Elder-friendly interface design
File system access: Direct Excel/PDF export
Native system integration: System notifications, file associations
Cross-platform support: Unified macOS and Windows experience
🚀 Deployment Battle: Railway + Vercel Cloud-Native Architecture
Railway's Smart Startup Strategy
# start.sh - Production environment stability guarantee
#!/bin/bash
echo "🚀 Starting PAPA System..."
# Health check: Ensure database connection
python manage.py migrate --check
if [ $? -ne 0 ]; then
echo "❌ Database migration check failed"
exit 1
fi
# Database migration
python manage.py migrate --settings=pangcah_accounting.settings.railway
# Static file handling
python manage.py collectstatic --noinput
# Server startup: Prioritize gunicorn
if command -v gunicorn &> /dev/null; then
echo "✅ Starting with Gunicorn + Uvicorn worker"
gunicorn pangcah_accounting.asgi:application \
-k uvicorn.workers.UvicornWorker \
-b 0.0.0.0:$PORT \
--workers 1 \
--worker-connections 10 \
--max-requests 100 \
--timeout 120
else
echo "✅ Fallback to Daphne"
daphne -b 0.0.0.0 -p $PORT pangcah_accounting.asgi:application
fi
Vercel Frontend Performance Optimization Configuration
{
"version": 2,
"builds": [{
"src": "package.json",
"use": "@vercel/static-build",
"config": { "distDir": "dist" }
}],
"routes": [
{
"src": "/assets/(.*)",
"headers": { "cache-control": "max-age=31536000" }
},
{
"src": "/(.*)",
"dest": "/index.html"
}
],
"env": {
"VITE_API_URL": "https://pangcah-accounting-system-production.up.railway.app"
}
}
Deployment Effects:
Cold start time: Railway < 2 seconds, Vercel < 1 second
Static resource caching: 1-year validity, global CDN acceleration
Zero downtime deployment: Git push triggers automatic deployment
📱 Cross-Platform Architecture: macOS + Windows Unified Experience
Tauri's Cross-Platform Configuration
The desktop version's core value is providing unified cross-platform experience:
// Cross-platform desktop management
class DesktopManager {
private apiClient: APIClient
async initialize() {
// Detect operating system
const { platform } = await import('@tauri-apps/api/os')
const currentPlatform = await platform()
// Adjust UI based on platform
this.adjustPlatformUI(currentPlatform)
}
adjustPlatformUI(platform: string) {
const root = document.documentElement
switch (platform) {
case 'darwin': // macOS
root.style.setProperty('--title-bar-height', '28px')
root.style.setProperty('--button-spacing', '8px')
break
case 'win32': // Windows
root.style.setProperty('--title-bar-height', '32px')
root.style.setProperty('--button-spacing', '4px')
break
}
// Elder-friendly settings
if (localStorage.getItem('elderMode') === 'true') {
root.style.fontSize = '120%'
root.style.setProperty('--button-size', '48px')
root.style.setProperty('--input-height', '44px')
}
}
async saveExpense(expense: CreateExpenseRequest) {
try {
// Direct cloud API connection
const result = await this.apiClient.post('/expenses/', expense)
return result.data
} catch (error) {
showSnackbar('Save failed, please check network connection', 'error')
throw error
}
}
}
Cross-Platform Advantages:
macOS native experience: Complies with Apple design guidelines
Windows native experience: Integrates Windows UI standards
Elder-friendly design: Large fonts, large buttons, clear interface
Keyboard operation optimization: Complete keyboard shortcut support
iPhone/Android Quick Expense Recording Design
Mobile version focuses on quick expense recording during events, perfectly complementing the desktop version:
Mobile Features:
One-tap recording: Large bottom add button
Simplified dashboard: Key information at a glance
Large font design: Cross-generational friendly reading
Touch optimization: Finger-friendly button sizes
📊 Excel Report Generation: Essential Feature for Government Subsidy Applications
Large-Screen Friendly Report Interface
When tribes apply for government subsidies, they need specific format financial reports. Desktop version designed specifically for large-screen operation:
// Excel report generation system
interface GovernmentReportFormat {
eventInfo: {
name: string
date: string
totalParticipants: number
totalBudget: number
}
expenseBreakdown: Array<{
category: string
amount: number
percentage: number
receipts: string[] // Receipt file paths
}>
participantList: Array<{
name: string
idNumber?: string // Optional ID number
contribution: number
splitAmount: number
}>
}
// Generate government format reports
const generateGovernmentReport = async (eventData: EventData) => {
const { save } = await import('@tauri-apps/api/dialog')
const { writeTextFile } = await import('@tauri-apps/api/fs')
// Choose save location
const filePath = await save({
defaultPath: `${eventData.name}_Government_Subsidy_Report.xlsx`,
filters: [{ name: 'Excel files', extensions: ['xlsx'] }]
})
if (filePath) {
const reportData = {
// Event basic information
EventName: eventData.name,
EventDate: formatDate(eventData.start_date),
ParticipantCount: eventData.participants.length,
TotalBudget: eventData.budget,
ActualExpenses: eventData.total_expenses,
// Expense details (categorized per government requirements)
FoodExpenses: calculateCategoryTotal(eventData.expenses, 'Food'),
TransportExpenses: calculateCategoryTotal(eventData.expenses, 'Transport'),
VenueExpenses: calculateCategoryTotal(eventData.expenses, 'Venue'),
OtherExpenses: calculateCategoryTotal(eventData.expenses, 'Other'),
// Participant list (government format compliant)
ParticipantList: eventData.participants.map(p => ({
Name: p.user.name,
Contribution: p.contribution,
SplitAmount: p.split_amount
}))
}
await writeTextFile(filePath, JSON.stringify(reportData, null, 2))
showSnackbar('Government report exported', 'success')
}
}
Government Application Support:
Standard format: Complies with Indigenous Affairs Council requirements
Complete records: Expense details + participant list
One-click export: Directly generates submittable reports
Privacy protection: Sensitive data stored locally only
🧪 Test-Driven Quality Assurance
Real-World Application of End-to-End Testing
# test_split_api.py - Complete testing of split functionality
class ExpenseSplitAPITest:
def test_complex_split_scenario(self):
"""Test complex split scenario: Real family gathering situation"""
# Create test event: Family dinner
event_data = {
"name": "Spring Festival Family Dinner",
"budget": 5000,
"participant_count": 8
}
# Test time-sensitive splitting
# Xiao Ming joins midway, doesn't split previous expenses
late_joiner_test = {
"user": "Xiao Ming",
"joined_at": "2025-01-02",
"split_option": "NO_SPLIT_BEFORE_JOIN"
}
# Create expense: Day 1 BBQ costs
expense_day1 = {
"amount": 2000,
"date": "2025-01-01", # Before Xiao Ming joined
"description": "Day 1 BBQ costs"
}
# Verify: Xiao Ming shouldn't split this expense
response = self.client.post('/api/v1/expenses/', expense_day1)
expense_id = response.json()['id']
splits = self.client.get(f'/api/v1/expenses/{expense_id}/splits/')
# Assert: Xiao Ming not in split list
participant_names = [s['participant']['user']['name'] for s in splits.json()]
assert "Xiao Ming" not in participant_names
# Create expense: Day 2 transport (after Xiao Ming joined)
expense_day2 = {
"amount": 1000,
"date": "2025-01-02",
"description": "Day 2 transport costs"
}
response = self.client.post('/api/v1/expenses/', expense_day2)
expense_id = response.json()['id']
splits = self.client.get(f'/api/v1/expenses/{expense_id}/splits/')
participant_names = [s['participant']['user']['name'] for s in splits.json()]
# Assert: Xiao Ming should split this expense
assert "Xiao Ming" in participant_names
🎯 Today's Achievements and Tomorrow's Outlook
Technical Achievements
✅ Smart environment detection API architecture
✅ Seamless JWT refresh mechanism
✅ Tauri cross-platform desktop version (macOS + Windows)
✅ Elder-friendly large-screen interface design
✅ Native desktop experience and performance optimization
✅ End-to-end testing covering core functionality
Key Insights
Architectural flexibility beats over-engineering: One API configuration supports three deployment modes
User experience is priceless: Seamless token refresh makes collaboration smoother
Cross-platform is essential: Unified macOS and Windows support reduces learning costs
Elder-friendly design: Large-screen interfaces improve usability and operation confidence
AI-Assisted Development Effects
GitHub Copilot generated 50% of test cases
Claude 3.5 helped design reconnection strategies and error handling
But core architectural decisions still require battle-tested experience guidance
🏀 Tomorrow's Preview
Day 5 will share Team Collaboration and Product Management Battle Experience, including:
How to balance the dual roles of engineer and PM
Agile development application in cultural technology projects
User feedback-driven product iteration strategies
Technology's ultimate goal is serving people, not showing off skills. PAPA's frontend-backend integration proves: When technology deeply integrates with culture, truly valuable solutions are created.
🚀 Blake Lab - Leading in Both AI and Cultural Technology
💡 20 years of government projects | 🤖 AI Indigenous applications | 🎯 Indigenous language NLP development | 📈 Cultural-sensitive design
🌐 wchung.tw | 📧 [email protected]
Follow Blake Lab to explore the infinite possibilities of technology and culture together!