Create comprehensive Obsidian-style documentation structure
- Reorganize all markdown documentation into structured docs/ folder - Create 7 main documentation categories (00-overview through 06-deployment) - Add comprehensive index files for each category with cross-linking - Implement Obsidian-compatible [[link]] syntax throughout - Move legacy/deprecated documentation to archive folder - Establish documentation standards and maintenance guidelines - Provide complete coverage of modular architecture, services, and deployment - Enable better navigation and discoverability for developers and contributors 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
46856134ef
commit
cdf099e45f
29 changed files with 3733 additions and 0 deletions
663
docs/06-deployment/index.md
Normal file
663
docs/06-deployment/index.md
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
# 🚀 Deployment Guide
|
||||
|
||||
> **Production deployment and operations** for Ario's web application, desktop app, and Progressive Web App across multiple platforms and environments.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [[#Deployment Options]]
|
||||
- [[#Production Build Process]]
|
||||
- [[#Environment Configuration]]
|
||||
- [[#Platform-Specific Deployment]]
|
||||
- [[#Monitoring & Maintenance]]
|
||||
- [[#Security Considerations]]
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### **Web Application (SPA)**
|
||||
- **Static Hosting** - Deploy to CDN or static hosting service
|
||||
- **Traditional Web Server** - Nginx, Apache, or similar
|
||||
- **Cloud Platforms** - Vercel, Netlify, Cloudflare Pages
|
||||
- **Container Deployment** - Docker with web server
|
||||
|
||||
### **Progressive Web App (PWA)**
|
||||
- **Service Worker** - Offline capabilities and caching
|
||||
- **App Manifest** - Installation and app-like experience
|
||||
- **Push Notifications** - Real-time updates (future feature)
|
||||
- **Background Sync** - Offline data synchronization
|
||||
|
||||
### **Desktop Application**
|
||||
- **Electron** - Cross-platform desktop packaging
|
||||
- **Platform-Specific** - Windows MSI, macOS DMG, Linux AppImage/Snap
|
||||
- **Auto-Update** - Seamless application updates
|
||||
- **Code Signing** - Security and trust verification
|
||||
|
||||
### **Self-Hosted**
|
||||
- **Full Control** - Complete control over infrastructure
|
||||
- **Privacy** - No third-party hosting dependencies
|
||||
- **Custom Configuration** - Tailored environment variables and settings
|
||||
- **Local Development** - Internal network deployment
|
||||
|
||||
## Production Build Process
|
||||
|
||||
### **Build Commands**
|
||||
```bash
|
||||
# Standard web build
|
||||
npm run build
|
||||
|
||||
# Build with analysis
|
||||
npm run analyze
|
||||
|
||||
# Electron desktop build
|
||||
npm run electron:build
|
||||
|
||||
# Platform-specific builds
|
||||
npm run build:win # Windows
|
||||
npm run build:mac # macOS
|
||||
npm run build:linux # Linux
|
||||
```
|
||||
|
||||
### **Build Configuration**
|
||||
|
||||
#### **Vite Production Config**
|
||||
```typescript
|
||||
// vite.config.ts - Production optimizations
|
||||
export default defineConfig({
|
||||
build: {
|
||||
target: 'es2020',
|
||||
minify: 'terser',
|
||||
sourcemap: false, // Disable in production
|
||||
chunkSizeWarningLimit: 600,
|
||||
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'vue-vendor': ['vue', 'vue-router', 'pinia'],
|
||||
'ui-vendor': ['@headlessui/vue', '@heroicons/vue'],
|
||||
'nostr-vendor': ['nostr-tools'],
|
||||
'crypto-vendor': ['crypto-js']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **Build Optimization**
|
||||
- **Tree Shaking** - Remove unused code
|
||||
- **Code Splitting** - Lazy load modules and routes
|
||||
- **Asset Optimization** - Compress images and fonts
|
||||
- **Bundle Analysis** - Monitor bundle size and dependencies
|
||||
|
||||
### **Build Output Structure**
|
||||
```
|
||||
dist/
|
||||
├── index.html # Entry point
|
||||
├── assets/ # Static assets with hashed names
|
||||
│ ├── index-[hash].js # Main application bundle
|
||||
│ ├── vendor-[hash].js # Vendor dependencies
|
||||
│ └── [module]-[hash].js # Module-specific bundles
|
||||
├── icons/ # PWA icons
|
||||
├── manifest.json # PWA manifest
|
||||
└── sw.js # Service worker
|
||||
```
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### **Environment Variables**
|
||||
|
||||
#### **Production Environment (.env.production)**
|
||||
```bash
|
||||
# Application Configuration
|
||||
VITE_APP_NAME="Ario"
|
||||
VITE_APP_VERSION="1.0.0"
|
||||
VITE_BASE_URL="/"
|
||||
|
||||
# Nostr Configuration
|
||||
VITE_NOSTR_RELAYS='["wss://relay.damus.io","wss://nos.lol","wss://relay.snort.social"]'
|
||||
VITE_ADMIN_PUBKEYS='["admin_pubkey_1","admin_pubkey_2"]'
|
||||
|
||||
# Lightning Configuration (if using LNbits)
|
||||
VITE_LNBITS_URL="https://your-lnbits-instance.com"
|
||||
VITE_LNBITS_ADMIN_KEY="" # Only for invoice creation
|
||||
|
||||
# Security & Performance
|
||||
VITE_DEBUG=false
|
||||
VITE_ENABLE_PWA=true
|
||||
VITE_ENABLE_ANALYTICS=true
|
||||
|
||||
# Optional Features
|
||||
VITE_ENABLE_NOTIFICATIONS=true
|
||||
VITE_MAX_RELAY_CONNECTIONS=10
|
||||
VITE_EVENT_CACHE_SIZE=1000
|
||||
```
|
||||
|
||||
#### **Configuration Validation**
|
||||
```typescript
|
||||
// src/config/production.ts
|
||||
export const productionConfig = {
|
||||
app: {
|
||||
name: import.meta.env.VITE_APP_NAME || 'Ario',
|
||||
version: import.meta.env.VITE_APP_VERSION || '1.0.0',
|
||||
baseUrl: import.meta.env.VITE_BASE_URL || '/',
|
||||
},
|
||||
|
||||
nostr: {
|
||||
relays: JSON.parse(import.meta.env.VITE_NOSTR_RELAYS || '[]'),
|
||||
adminPubkeys: JSON.parse(import.meta.env.VITE_ADMIN_PUBKEYS || '[]'),
|
||||
maxConnections: Number(import.meta.env.VITE_MAX_RELAY_CONNECTIONS) || 10,
|
||||
},
|
||||
|
||||
features: {
|
||||
debug: import.meta.env.VITE_DEBUG === 'true',
|
||||
pwa: import.meta.env.VITE_ENABLE_PWA === 'true',
|
||||
notifications: import.meta.env.VITE_ENABLE_NOTIFICATIONS === 'true',
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required configuration
|
||||
if (!productionConfig.nostr.relays.length) {
|
||||
throw new Error('VITE_NOSTR_RELAYS must be configured')
|
||||
}
|
||||
```
|
||||
|
||||
### **Security Configuration**
|
||||
|
||||
#### **Content Security Policy (CSP)**
|
||||
```html
|
||||
<!-- In index.html for production -->
|
||||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src 'self';
|
||||
script-src 'self' 'unsafe-inline';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
connect-src 'self' wss: ws: https:;
|
||||
img-src 'self' data: https:;
|
||||
font-src 'self' data:;
|
||||
manifest-src 'self';
|
||||
">
|
||||
```
|
||||
|
||||
#### **Security Headers (nginx example)**
|
||||
```nginx
|
||||
# nginx.conf security headers
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";
|
||||
```
|
||||
|
||||
## Platform-Specific Deployment
|
||||
|
||||
### **Web Hosting - Vercel**
|
||||
|
||||
#### **vercel.json Configuration**
|
||||
```json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist",
|
||||
"framework": "vite",
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/((?!api/).*)",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
],
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "X-Frame-Options",
|
||||
"value": "DENY"
|
||||
},
|
||||
{
|
||||
"key": "X-Content-Type-Options",
|
||||
"value": "nosniff"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### **Deployment Steps**
|
||||
```bash
|
||||
# 1. Install Vercel CLI
|
||||
npm i -g vercel
|
||||
|
||||
# 2. Deploy to Vercel
|
||||
vercel --prod
|
||||
|
||||
# 3. Set environment variables in Vercel dashboard
|
||||
# - VITE_NOSTR_RELAYS
|
||||
# - VITE_ADMIN_PUBKEYS
|
||||
# - Other configuration variables
|
||||
```
|
||||
|
||||
### **Web Hosting - Netlify**
|
||||
|
||||
#### **netlify.toml Configuration**
|
||||
```toml
|
||||
[build]
|
||||
command = "npm run build"
|
||||
publish = "dist"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
X-Frame-Options = "DENY"
|
||||
X-XSS-Protection = "1; mode=block"
|
||||
X-Content-Type-Options = "nosniff"
|
||||
```
|
||||
|
||||
### **Self-Hosted - Docker**
|
||||
|
||||
#### **Dockerfile**
|
||||
```dockerfile
|
||||
# Multi-stage build for production
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy nginx configuration
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
#### **Docker Compose**
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
ario-web:
|
||||
build: .
|
||||
ports:
|
||||
- "80:80"
|
||||
environment:
|
||||
- VITE_NOSTR_RELAYS=["wss://relay.damus.io"]
|
||||
- VITE_ADMIN_PUBKEYS=["your_admin_pubkey"]
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### **Desktop Application**
|
||||
|
||||
#### **Electron Forge Configuration**
|
||||
```javascript
|
||||
// forge.config.js
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
name: 'Ario',
|
||||
executableName: 'ario',
|
||||
icon: './src/assets/icon',
|
||||
asar: true,
|
||||
|
||||
// Code signing (production)
|
||||
osxSign: {
|
||||
identity: process.env.APPLE_IDENTITY,
|
||||
'hardened-runtime': true,
|
||||
entitlements: './entitlements.plist',
|
||||
'entitlements-inherit': './entitlements.plist'
|
||||
},
|
||||
|
||||
osxNotarize: {
|
||||
tool: 'notarytool',
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_PASSWORD,
|
||||
teamId: process.env.APPLE_TEAM_ID
|
||||
}
|
||||
},
|
||||
|
||||
makers: [
|
||||
{
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
config: {
|
||||
name: 'ario',
|
||||
setupIcon: './src/assets/icon.ico'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-dmg',
|
||||
config: {
|
||||
format: 'ULFO',
|
||||
icon: './src/assets/icon.icns'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-deb',
|
||||
config: {
|
||||
options: {
|
||||
maintainer: 'Ario Team',
|
||||
homepage: 'https://ario.app'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
publishers: [
|
||||
{
|
||||
name: '@electron-forge/publisher-github',
|
||||
config: {
|
||||
repository: {
|
||||
owner: 'your-org',
|
||||
name: 'ario'
|
||||
},
|
||||
prerelease: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### **Auto-Update Configuration**
|
||||
```typescript
|
||||
// electron/main.ts - Auto-updater setup
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
|
||||
if (!app.isPackaged) {
|
||||
autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml')
|
||||
}
|
||||
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Update Available',
|
||||
message: 'A new version is available. It will be downloaded in the background.',
|
||||
buttons: ['OK']
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Monitoring & Maintenance
|
||||
|
||||
### **Performance Monitoring**
|
||||
|
||||
#### **Web Vitals Tracking**
|
||||
```typescript
|
||||
// src/utils/analytics.ts
|
||||
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
|
||||
|
||||
export function initPerformanceMonitoring() {
|
||||
if (import.meta.env.VITE_ENABLE_ANALYTICS) {
|
||||
getCLS(sendToAnalytics)
|
||||
getFID(sendToAnalytics)
|
||||
getFCP(sendToAnalytics)
|
||||
getLCP(sendToAnalytics)
|
||||
getTTFB(sendToAnalytics)
|
||||
}
|
||||
}
|
||||
|
||||
function sendToAnalytics(metric: any) {
|
||||
// Send to your analytics service
|
||||
console.log('Performance metric:', metric)
|
||||
}
|
||||
```
|
||||
|
||||
#### **Application Health Monitoring**
|
||||
```typescript
|
||||
// src/services/HealthMonitor.ts
|
||||
export class HealthMonitor extends BaseService {
|
||||
private healthStatus = ref({
|
||||
relayConnections: 0,
|
||||
lastEventReceived: null as Date | null,
|
||||
memoryUsage: 0,
|
||||
errorCount: 0
|
||||
})
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
setInterval(() => this.checkHealth(), 30000) // Every 30 seconds
|
||||
}
|
||||
|
||||
private checkHealth(): void {
|
||||
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
||||
|
||||
this.healthStatus.value = {
|
||||
relayConnections: relayHub.connectedRelays.value.length,
|
||||
lastEventReceived: this.getLastEventTime(),
|
||||
memoryUsage: this.getMemoryUsage(),
|
||||
errorCount: this.getErrorCount()
|
||||
}
|
||||
|
||||
// Alert if critical issues detected
|
||||
if (this.healthStatus.value.relayConnections === 0) {
|
||||
console.warn('No relay connections available')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Error Tracking**
|
||||
|
||||
#### **Global Error Handler**
|
||||
```typescript
|
||||
// src/utils/errorHandler.ts
|
||||
export function setupGlobalErrorHandler() {
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('Global error:', event.error)
|
||||
|
||||
// Send to error tracking service
|
||||
if (import.meta.env.VITE_ERROR_TRACKING) {
|
||||
sendErrorToService(event.error)
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('Unhandled promise rejection:', event.reason)
|
||||
sendErrorToService(event.reason)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### **Logging Strategy**
|
||||
|
||||
#### **Production Logging**
|
||||
```typescript
|
||||
// src/utils/logger.ts
|
||||
class Logger {
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
if (import.meta.env.PROD && level === 'debug') return false
|
||||
return true
|
||||
}
|
||||
|
||||
info(message: string, ...args: unknown[]): void {
|
||||
if (this.shouldLog('info')) {
|
||||
console.log(`[INFO] ${message}`, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
error(message: string, error?: Error, ...args: unknown[]): void {
|
||||
if (this.shouldLog('error')) {
|
||||
console.error(`[ERROR] ${message}`, error, ...args)
|
||||
|
||||
// Send to error tracking in production
|
||||
if (import.meta.env.PROD && error) {
|
||||
this.sendToErrorTracking(message, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sendToErrorTracking(message: string, error: Error): void {
|
||||
// Implementation for error tracking service
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger()
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### **Client-Side Security**
|
||||
|
||||
#### **Key Storage Security**
|
||||
```typescript
|
||||
// src/utils/keyStorage.ts
|
||||
export class SecureKeyStorage {
|
||||
private static readonly STORAGE_KEY = 'ario_encrypted_keys'
|
||||
|
||||
static async storeEncryptedKey(privateKey: string, passphrase: string): Promise<void> {
|
||||
const encrypted = await this.encrypt(privateKey, passphrase)
|
||||
localStorage.setItem(this.STORAGE_KEY, encrypted)
|
||||
}
|
||||
|
||||
static async retrieveDecryptedKey(passphrase: string): Promise<string | null> {
|
||||
const encrypted = localStorage.getItem(this.STORAGE_KEY)
|
||||
if (!encrypted) return null
|
||||
|
||||
try {
|
||||
return await this.decrypt(encrypted, passphrase)
|
||||
} catch {
|
||||
return null // Invalid passphrase
|
||||
}
|
||||
}
|
||||
|
||||
private static async encrypt(data: string, passphrase: string): Promise<string> {
|
||||
// Use Web Crypto API for encryption
|
||||
const encoder = new TextEncoder()
|
||||
const key = await window.crypto.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(passphrase),
|
||||
{ name: 'PBKDF2' },
|
||||
false,
|
||||
['deriveKey']
|
||||
)
|
||||
|
||||
// Implementation details...
|
||||
return encryptedData
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Input Validation**
|
||||
```typescript
|
||||
// src/utils/validation.ts
|
||||
export const validators = {
|
||||
nostrPublicKey: (pubkey: string): boolean => {
|
||||
return /^[0-9a-f]{64}$/i.test(pubkey)
|
||||
},
|
||||
|
||||
nostrPrivateKey: (privkey: string): boolean => {
|
||||
return /^[0-9a-f]{64}$/i.test(privkey)
|
||||
},
|
||||
|
||||
lightningInvoice: (invoice: string): boolean => {
|
||||
return /^(lnbc|lntb|lnbcrt)[0-9]+[munp]?[0-9a-z]+$/i.test(invoice)
|
||||
},
|
||||
|
||||
sanitizeContent: (content: string): string => {
|
||||
// Sanitize user-generated content
|
||||
return content
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/javascript:/gi, '')
|
||||
.trim()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Deployment Security**
|
||||
|
||||
#### **HTTPS Enforcement**
|
||||
```nginx
|
||||
# nginx.conf - Force HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name ario.app www.ario.app;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name ario.app www.ario.app;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/private.key;
|
||||
|
||||
# Security headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Privacy Protection**
|
||||
|
||||
#### **Data Minimization**
|
||||
- **No Server Storage** - All user data stored client-side
|
||||
- **Ephemeral Sessions** - No persistent server-side sessions
|
||||
- **Optional Analytics** - Analytics can be disabled by users
|
||||
- **Relay Privacy** - Users can configure their own relays
|
||||
|
||||
#### **User Privacy Controls**
|
||||
```typescript
|
||||
// src/services/PrivacyService.ts
|
||||
export class PrivacyService extends BaseService {
|
||||
private settings = ref({
|
||||
shareAnalytics: false,
|
||||
shareErrorReports: false,
|
||||
allowLocationTracking: false,
|
||||
publicProfile: false
|
||||
})
|
||||
|
||||
updatePrivacySettings(updates: Partial<PrivacySettings>): void {
|
||||
this.settings.value = { ...this.settings.value, ...updates }
|
||||
|
||||
// Apply privacy settings immediately
|
||||
if (!this.settings.value.shareAnalytics) {
|
||||
this.disableAnalytics()
|
||||
}
|
||||
|
||||
if (!this.settings.value.shareErrorReports) {
|
||||
this.disableErrorReporting()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
### Configuration Documentation
|
||||
- **[[configuration|⚙️ Environment Configuration]]** - Detailed configuration options
|
||||
- **[[pwa-setup|📱 PWA Configuration]]** - Progressive Web App setup
|
||||
- **[[electron|🖥️ Desktop App Packaging]]** - Electron configuration and distribution
|
||||
|
||||
### Operations Documentation
|
||||
- **[[../04-development/index|💻 Development Guide]]** - Development environment setup
|
||||
- **[[../05-api-reference/index|📡 API Reference]]** - External service integrations
|
||||
- **[[../01-architecture/index|🏗️ Architecture Overview]]** - System architecture principles
|
||||
|
||||
---
|
||||
|
||||
**Tags:** #deployment #production #security #monitoring #pwa #electron
|
||||
**Last Updated:** 2025-09-06
|
||||
**Author:** Development Team
|
||||
Loading…
Add table
Add a link
Reference in a new issue