diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 56ff59f..d88f4a2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: npm - name: Install dependencies @@ -45,7 +45,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: npm - name: Install dependencies diff --git a/.gitignore b/.gitignore index b2d6de3..ded26b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,41 @@ # Dependencies -/node_modules +node_modules/ -# Production -/build +# Production build +build/ +.docusaurus/ +.cache-loader/ # Generated files -.docusaurus -.cache-loader +.docusaurus/ +.cache/ # Misc .DS_Store +.env .env.local .env.development.local .env.test.local .env.production.local +# Logs npm-debug.log* yarn-debug.log* yarn-error.log* +lerna-debug.log* + +# Editor directories and files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.project +.classpath +.settings/ +*.sublime-workspace + +# OS +.DS_Store +Thumbs.db + diff --git a/README.md b/README.md index 4793827..4cdab0f 100644 --- a/README.md +++ b/README.md @@ -62,4 +62,4 @@ The website is deployed automatically using GitHub Actions when changes are push 2. Runs full Docusaurus build 3. Deploys to GitHub Pages (only if all checks pass) -Broken links or validation errors will prevent deployment. \ No newline at end of file +Broken links or validation errors will prevent deployment. diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..c0097fc --- /dev/null +++ b/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; + diff --git a/create-placeholders.sh b/create-placeholders.sh new file mode 100755 index 0000000..4237b74 --- /dev/null +++ b/create-placeholders.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# Create placeholder markdown files for all documentation pages + +# Guides - Forms +echo "---\nsidebar_position: 2\n---\n\n# Schema Definition\n\n[Placeholder: Content will be added]" > docs/guides/forms/schema-definition.md +echo "---\nsidebar_position: 3\n---\n\n# UI Schema\n\n[Placeholder: Content will be added]" > docs/guides/forms/ui-schema.md +echo "---\nsidebar_position: 4\n---\n\n# Validation\n\n[Placeholder: Content will be added]" > docs/guides/forms/validation.md +echo "---\nsidebar_position: 5\n---\n\n# Conditional Logic\n\n[Placeholder: Content will be added]" > docs/guides/forms/advanced-features.md + +# Guides - Custom Apps +echo "---\nsidebar_position: 1\n---\n\n# Custom Applications Overview\n\n[Placeholder: Content will be added]" > docs/guides/custom-apps/overview.md +echo "---\nsidebar_position: 2\n---\n\n# Building Custom Applications\n\n[Placeholder: Content will be added]" > docs/guides/custom-apps/building.md +echo "---\nsidebar_position: 3\n---\n\n# App Bundle Structure\n\n[Placeholder: Content will be added]" > docs/guides/custom-apps/app-bundle-structure.md +echo "---\nsidebar_position: 4\n---\n\n# Deployment\n\n[Placeholder: Content will be added]" > docs/guides/custom-apps/deployment.md +echo "---\nsidebar_position: 5\n---\n\n# Custom Renderers\n\n[Placeholder: Content will be added]" > docs/guides/custom-apps/custom-renderers.md + +# Guides - Deployment +echo "---\nsidebar_position: 1\n---\n\n# Deployment Overview\n\n[Placeholder: Content will be added]" > docs/guides/deployment/overview.md +echo "---\nsidebar_position: 2\n---\n\n# Docker Deployment\n\n[Placeholder: Content will be added]" > docs/guides/deployment/docker.md +echo "---\nsidebar_position: 3\n---\n\n# Production Deployment\n\n[Placeholder: Content will be added]" > docs/guides/deployment/production.md +echo "---\nsidebar_position: 4\n---\n\n# Monitoring\n\n[Placeholder: Content will be added]" > docs/guides/deployment/monitoring.md + +# Guides - Other +echo "---\nsidebar_position: 5\n---\n\n# User Management\n\n[Placeholder: Content will be added]" > docs/guides/user-management.md +echo "---\nsidebar_position: 6\n---\n\n# Branding\n\n[Placeholder: Content will be added]" > docs/guides/branding.md +echo "---\nsidebar_position: 7\n---\n\n# Translations\n\n[Placeholder: Content will be added]" > docs/guides/translations.md + +# Reference - API +echo "---\nsidebar_position: 1\n---\n\n# API Overview\n\n[Placeholder: Content will be added]" > docs/reference/api/overview.md +echo "---\nsidebar_position: 2\n---\n\n# Authentication\n\n[Placeholder: Content will be added]" > docs/reference/api/authentication.md +echo "---\nsidebar_position: 3\n---\n\n# API Endpoints\n\n[Placeholder: Content will be added]" > docs/reference/api/endpoints.md + +# Reference - Configuration +echo "---\nsidebar_position: 1\n---\n\n# Server Configuration\n\n[Placeholder: Content will be added]" > docs/reference/configuration/server.md +echo "---\nsidebar_position: 2\n---\n\n# Client Configuration\n\n[Placeholder: Content will be added]" > docs/reference/configuration/client.md + +# Reference - Other +echo "---\nsidebar_position: 2\n---\n\n# Form Specifications\n\n[Placeholder: Content will be added]" > docs/reference/form-specifications.md +echo "---\nsidebar_position: 3\n---\n\n# App Bundle Format\n\n[Placeholder: Content will be added]" > docs/reference/app-bundle-format.md + +# Reference - Components +echo "---\nsidebar_position: 1\n---\n\n# Formulus\n\n[Placeholder: Content will be added]" > docs/reference/components/formulus.md +echo "---\nsidebar_position: 2\n---\n\n# Synkronus\n\n[Placeholder: Content will be added]" > docs/reference/components/synkronus.md +echo "---\nsidebar_position: 3\n---\n\n# Synkronus CLI\n\n[Placeholder: Content will be added]" > docs/reference/components/synkronus-cli.md +echo "---\nsidebar_position: 4\n---\n\n# Formplayer\n\n[Placeholder: Content will be added]" > docs/reference/components/formplayer.md + +# Development - Getting Started +echo "---\nsidebar_position: 1\n---\n\n# Development Setup\n\n[Placeholder: Content will be added]" > docs/development/getting-started/setup.md +echo "---\nsidebar_position: 2\n---\n\n# Architecture Overview\n\n[Placeholder: Content will be added]" > docs/development/getting-started/architecture.md +echo "---\nsidebar_position: 3\n---\n\n# Codebase Overview\n\n[Placeholder: Content will be added]" > docs/development/getting-started/codebase-overview.md + +# Development - Architecture +echo "---\nsidebar_position: 1\n---\n\n# Architecture Overview\n\n[Placeholder: Content will be added]" > docs/development/architecture/overview.md +echo "---\nsidebar_position: 2\n---\n\n# Components\n\n[Placeholder: Content will be added]" > docs/development/architecture/components.md +echo "---\nsidebar_position: 3\n---\n\n# Data Flow\n\n[Placeholder: Content will be added]" > docs/development/architecture/data-flow.md +echo "---\nsidebar_position: 4\n---\n\n# Sync Protocol\n\n[Placeholder: Content will be added]" > docs/development/architecture/sync-protocol.md +echo "---\nsidebar_position: 5\n---\n\n# Database\n\n[Placeholder: Content will be added]" > docs/development/architecture/database.md + +# Development - Contributing +echo "---\nsidebar_position: 1\n---\n\n# Contributing Guide\n\n[Placeholder: Content will be added]" > docs/development/contributing/guide.md +echo "---\nsidebar_position: 2\n---\n\n# Code of Conduct\n\n[Placeholder: Content will be added]" > docs/development/contributing/code-of-conduct.md +echo "---\nsidebar_position: 3\n---\n\n# First Contribution\n\n[Placeholder: Content will be added]" > docs/development/contributing/first-contribution.md +echo "---\nsidebar_position: 4\n---\n\n# Coding Standards\n\n[Placeholder: Content will be added]" > docs/development/contributing/coding-standards.md + +# Development - Building +echo "---\nsidebar_position: 1\n---\n\n# Building from Source\n\n[Placeholder: Content will be added]" > docs/development/building/from-source.md +echo "---\nsidebar_position: 2\n---\n\n# Building Components\n\n[Placeholder: Content will be added]" > docs/development/building/components.md +echo "---\nsidebar_position: 3\n---\n\n# Testing\n\n[Placeholder: Content will be added]" > docs/development/building/testing.md +echo "---\nsidebar_position: 4\n---\n\n# CI/CD Pipeline\n\n[Placeholder: Content will be added]" > docs/development/building/ci-cd.md + +# Development - Extending +echo "---\nsidebar_position: 1\n---\n\n# Extending ODE Overview\n\n[Placeholder: Content will be added]" > docs/development/extending/overview.md +echo "---\nsidebar_position: 2\n---\n\n# Custom Renderers\n\n[Placeholder: Content will be added]" > docs/development/extending/custom-renderers.md +echo "---\nsidebar_position: 3\n---\n\n# Plugins\n\n[Placeholder: Content will be added]" > docs/development/extending/plugins.md +echo "---\nsidebar_position: 4\n---\n\n# Internal APIs\n\n[Placeholder: Content will be added]" > docs/development/extending/internal-apis.md + +# Development - Technical +echo "---\nsidebar_position: 1\n---\n\n# Database Schema\n\n[Placeholder: Content will be added]" > docs/development/technical/database-schema.md +echo "---\nsidebar_position: 2\n---\n\n# Sync Protocol\n\n[Placeholder: Content will be added]" > docs/development/technical/sync-protocol.md +echo "---\nsidebar_position: 3\n---\n\n# Security\n\n[Placeholder: Content will be added]" > docs/development/technical/security.md +echo "---\nsidebar_position: 4\n---\n\n# Performance\n\n[Placeholder: Content will be added]" > docs/development/technical/performance.md + +# Community +echo "---\nsidebar_position: 2\n---\n\n# Getting Help\n\n[Placeholder: Content will be added]" > docs/community/getting-help.md +echo "---\nsidebar_position: 3\n---\n\n# Reporting Issues\n\n[Placeholder: Content will be added]" > docs/community/reporting-issues.md +echo "---\nsidebar_position: 4\n---\n\n# Examples\n\n[Placeholder: Content will be added]" > docs/community/examples.md +echo "---\nsidebar_position: 5\n---\n\n# Projects\n\n[Placeholder: Content will be added]" > docs/community/projects.md + +echo "Placeholder files created successfully" diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/build/_category_.json b/docs/build/_category_.json deleted file mode 100644 index d651fe1..0000000 --- a/docs/build/_category_.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "label": "Build", - "position": 3 -} - diff --git a/docs/build/branding.md b/docs/build/branding.md deleted file mode 100644 index 851db66..0000000 --- a/docs/build/branding.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -sidebar_position: 7 ---- - -# Branding - -Customize the appearance of your ODE application. - -## Overview - -[Description placeholder] - -## Application Branding - -[Description placeholder] - -## Custom Themes - -[Description placeholder] - -## Logo & Assets - -[Description placeholder] - -## Related Content - -- [Custom Applications](/docs/build/custom-applications/overview) -- [Formulus Configuration](/docs/components/formulus/configuration) - diff --git a/docs/build/custom-applications/app-bundle-structure.md b/docs/build/custom-applications/app-bundle-structure.md deleted file mode 100644 index 579b76e..0000000 --- a/docs/build/custom-applications/app-bundle-structure.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 3 ---- - -# App Bundle Structure - -Structure of ODE app bundles. - -## Overview - -[Description placeholder] - -## Directory Structure - -[Description placeholder] - -## Required Files - -[Description placeholder] - -## Related Content - -- [App Bundle API](/docs/reference/rest-api/app-bundle) -- [Deployment](/docs/build/custom-applications/deployment) - diff --git a/docs/build/custom-applications/custom-renderers.md b/docs/build/custom-applications/custom-renderers.md deleted file mode 100644 index 6e5a20a..0000000 --- a/docs/build/custom-applications/custom-renderers.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Custom Renderers - -Create custom form renderers for JSON Forms. - -## Overview - -[Description placeholder] - -## Renderer Development - -[Description placeholder] - -## Integration - -[Description placeholder] - -## Related Content - -- [JSON Forms](/docs/technical-overview/concepts/json-forms) -- [Form Design](/docs/build/forms/design/ui-schema) - diff --git a/docs/build/custom-applications/deployment.md b/docs/build/custom-applications/deployment.md deleted file mode 100644 index aee95ff..0000000 --- a/docs/build/custom-applications/deployment.md +++ /dev/null @@ -1,376 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Deployment - -Deploy custom applications to ODE by creating and uploading app bundles. - -## What is an App Bundle? - -An **app bundle** is a ZIP file containing: - -- **`app/`** - Custom web application (HTML, JS, CSS) -- **`forms/`** - JSON form definitions (schema + UI) -- **`APP_INFO.json`** - Generated manifest (created by server) - -The Formulus mobile app downloads and runs the app bundle, enabling customized data collection workflows. - -## App Bundle Structure - -``` -bundle.zip -├── app/ -│ ├── index.html # Entry point -│ ├── config.json # App configuration -│ ├── formulus-load.js # Formulus bridge script -│ └── assets/ -│ └── *.js, *.css # Built assets -└── forms/ - ├── registration/ - │ ├── schema.json # JSON Schema (draft-07) - │ └── ui.json # UI layout definition - ├── visit/ - │ ├── schema.json - │ └── ui.json - └── result/ - ├── schema.json - └── ui.json -``` - -## Prerequisites - -- [ ] Synkronus CLI configured and logged in (see [CLI Installation](/docs/components/synkronus-cli/installation)) -- [ ] Node.js installed (for building apps) -- [ ] Demo app or custom app ready - -## Building an App Bundle - -### Using the Demo Malaria Screening App - -Navigate to the demo app: - -```bash -cd /path/to/demos/demo_malaria_screening -``` - -#### Step 1: Install Dependencies - -```bash -cd app -npm install -``` - -#### Step 2: Build the App - -```bash -npm run build -``` - -**Output:** Built files in `../app-bundles/app/` - -#### Step 3: Validate Forms (Optional) - -```bash -npm run validate:forms -``` - -Checks: -- JSON Schema syntax -- UI schema format -- Field references - -#### Step 4: Create the Bundle ZIP - -```bash -npm run zip -``` - -**Output:** - -``` -Created zip: /path/to/demo_malaria_screening/app-bundles/bundle-v1.0.6.zip -``` - -### Bundle Contents Verification - -Check what's in the bundle: - -```bash -unzip -l app-bundles/bundle-v1.0.6.zip -``` - -**Expected output:** - -``` -Archive: bundle-v1.0.6.zip - Length Date Time Name ---------- ---------- ----- ---- - 0 2025-12-11 19:39 app/assets/ - 443314 2025-12-11 19:39 app/assets/index-f2c34314.js - 494 2025-12-11 19:39 app/config.json - 3327 2025-12-11 19:39 app/formulus-load.js - 470 2025-12-11 19:39 app/index.html - 0 2025-12-11 19:39 forms/registration/ - 3371 2025-12-11 19:39 forms/registration/schema.json - 2430 2025-12-11 19:39 forms/registration/ui.json - ... -``` - -## Uploading an App Bundle - -### Basic Upload - -```bash -cd synkronus-cli -./bin/synk app-bundle upload /path/to/bundle-v1.0.6.zip -``` - -### Upload with Auto-Activation - -```bash -./bin/synk app-bundle upload /path/to/bundle-v1.0.6.zip --activate -``` - -### Upload with Verbose Output - -```bash -./bin/synk app-bundle upload /path/to/bundle-v1.0.6.zip --activate --verbose -``` - -**Expected output:** - -``` -Validating bundle structure... -✓ Bundle structure is valid - -Bundle Information: - Size: 148328 bytes - Files: 10 - Forms: 3 - Renderers: 0 - -Uploading bundle... -✓ App bundle uploaded successfully! -Version: 0001 - -Manifest: - Version: 0001 - Hash: b0494d5cc7164221f1c6dd957b9e61ff... - -Activating version 0001... -✓ Version 0001 activated successfully! -``` - -### Skip Validation (Not Recommended) - -```bash -./bin/synk app-bundle upload bundle.zip --skip-validation -``` - -## Managing App Bundle Versions - -### List Available Versions - -```bash -./bin/synk app-bundle versions -``` - -**Output:** - -``` -Available App Bundle Versions: -- 0001 * -- 0002 -``` - -The `*` indicates the currently active version. - -### View Current Manifest - -```bash -./bin/synk app-bundle manifest -``` - -**Output:** - -``` -App Bundle Manifest: -Version: 0001 -Generated At: 2025-12-11T16:41:19Z -Hash: b0494d5cc7164221f1c6dd957b9e61ff... -Files: 11 - - APP_INFO.json (10640 bytes) - - app/assets/index-f2c34314.js (443314 bytes) - - app/config.json (494 bytes) - - app/formulus-load.js (3327 bytes) - - app/index.html (470 bytes) -... and 6 more files (use --all to show all) -``` - -### Show All Files - -```bash -./bin/synk app-bundle manifest --all -``` - -### Switch Active Version - -```bash -./bin/synk app-bundle switch 0002 -``` - -**Output:** - -``` -✓ Version 0002 activated successfully! -``` - -### View Version Changes - -```bash -./bin/synk app-bundle changes 0001 0002 -``` - -## Downloading App Bundles - -### Download Current Bundle - -```bash -./bin/synk app-bundle download --output ./downloaded-bundle/ -``` - -### Download Specific File - -```bash -./bin/synk app-bundle download app/index.html -``` - -## Creating a Custom App Bundle - -### Minimal Structure - -``` -my-bundle/ -├── app/ -│ ├── index.html -│ └── formulus-load.js -└── forms/ - └── myform/ - ├── schema.json - └── ui.json -``` - -### Minimal index.html - -```html - - - - - - My App - - - -

My Custom App

- - - -``` - -### Create ZIP - -```bash -cd my-bundle -zip -r ../my-bundle.zip app/ forms/ -``` - -### Upload - -```bash -./bin/synk app-bundle upload my-bundle.zip --activate -``` - -## Best Practices - -### Version Control - -- Use semantic versioning in `package.json` -- Tag releases in Git -- Keep changelog of form changes - -### Testing - -- Test locally before uploading -- Validate forms: `npm run validate:forms` -- Test on actual device before production - -### Deployment - -- Upload to staging first -- Test with sample data -- Switch versions atomically -- Keep previous versions for rollback - -### Form Design - -- Keep forms focused and simple -- Use clear field labels -- Add validation rules -- Test on mobile devices - -## Troubleshooting - -### Issue: "Bundle structure is invalid" - -**Check:** -- Bundle has `app/` directory -- Bundle has `forms/` directory (if using forms) -- `app/index.html` exists - -### Issue: Upload fails with auth error - -**Solution:** - -```bash -./bin/synk status # Check if logged in -./bin/synk login -u admin # Re-login if needed -``` - -### Issue: Forms don't appear in app - -**Check:** -- Form schema is valid JSON Schema draft-07 -- UI schema references correct property paths -- Bundle was uploaded and activated - -### Issue: App shows blank screen - -**Check:** -- Use `HashRouter` not `BrowserRouter` (React apps) -- `formulus-load.js` is included -- No JavaScript errors (check device logs) - -## Command Reference - -| Command | Description | -|---------|-------------| -| `synk app-bundle upload FILE` | Upload bundle | -| `synk app-bundle upload FILE --activate` | Upload and activate | -| `synk app-bundle versions` | List versions | -| `synk app-bundle manifest` | Show manifest | -| `synk app-bundle switch VERSION` | Change active version | -| `synk app-bundle download` | Download bundle | -| `synk app-bundle changes V1 V2` | Compare versions | - -## Related Content - -- [Synkronus CLI Installation](/docs/components/synkronus-cli/installation) -- [Synkronus CLI Commands](/docs/components/synkronus-cli/commands-reference) -- [App Bundle API](/docs/reference/rest-api/app-bundle) -- [App Bundle Structure](/docs/build/custom-applications/app-bundle-structure) -- [Building Custom Apps](/docs/build/custom-applications/building) diff --git a/docs/build/custom-applications/overview.md b/docs/build/custom-applications/overview.md deleted file mode 100644 index 0500217..0000000 --- a/docs/build/custom-applications/overview.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Custom Applications Overview - -Build custom web applications that integrate with ODE. - -## Overview - -ODE allows you to build custom web applications that run within Formulus and integrate with the ODE platform. - -## Getting Started - -- [Building Custom Apps](/docs/build/custom-applications/building) -- [App Bundle Structure](/docs/build/custom-applications/app-bundle-structure) -- [Deployment](/docs/build/custom-applications/deployment) - -## Integration - -[Description placeholder] - -## Related Content - -- [Formulus Integration](/docs/components/formulus/integration) -- [App Bundles](/docs/technical-overview/concepts/app-bundles) - diff --git a/docs/build/data-management/attachments.md b/docs/build/data-management/attachments.md deleted file mode 100644 index e9f2d60..0000000 --- a/docs/build/data-management/attachments.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Attachments - -Manage file attachments in ODE. - -## Overview - -[Description placeholder] - -## Attachment Storage - -[Description placeholder] - -## Attachment Sync - -[Description placeholder] - -## Related Content - -- [Multimedia in Forms](/docs/build/forms/advanced-features/multimedia) -- [Synchronization](/docs/build/synchronization/overview) - diff --git a/docs/build/data-management/export.md b/docs/build/data-management/export.md deleted file mode 100644 index b584ca8..0000000 --- a/docs/build/data-management/export.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Data Export - -Export data from ODE for analysis. - -## Overview - -[Description placeholder] - -## Export Formats - -[Description placeholder] - -## Using Synkronus CLI - -[Description placeholder] - -## Related Content - -- [Synkronus CLI](/docs/components/synkronus-cli/commands-reference) -- [Observations](/docs/build/data-management/observations) - diff --git a/docs/build/data-management/import.md b/docs/build/data-management/import.md deleted file mode 100644 index af8de9e..0000000 --- a/docs/build/data-management/import.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Data Import - -Import data into ODE. - -## Overview - -[Description placeholder] - -## Import Formats - -[Description placeholder] - -## Import Process - -[Description placeholder] - -## Related Content - -- [Data Export](/docs/build/data-management/export) -- [Observations](/docs/build/data-management/observations) - diff --git a/docs/build/data-management/observations.md b/docs/build/data-management/observations.md deleted file mode 100644 index 9205e8c..0000000 --- a/docs/build/data-management/observations.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Observations - -Manage form submission data (observations) in ODE. - -## Overview - -An **observation** is a single data record: -- Created when user fills out a form -- Contains answers to form questions -- Includes metadata (who, when, where) -- May have attachments (photos, etc.) - -## Observation Structure - -When a user submits a form, an observation is created: - -```json -{ - "id": "uuid-generated-on-device", - "formId": "registration", - "schemaType": "registration", - "data": { - "firstName": "John", - "lastName": "Doe", - "dateOfBirth": "1990-05-15", - "gender": "male", - "phoneNumber": "0712345678", - "email": "john.doe@example.com", - "consent": true - }, - "metadata": { - "createdAt": "2025-12-11T10:30:00Z", - "updatedAt": "2025-12-11T10:35:00Z", - "createdBy": "fieldworker1", - "deviceId": "device-uuid", - "appVersion": "1.0.0", - "location": { - "latitude": -1.2921, - "longitude": 36.8219, - "accuracy": 10 - } - }, - "attachments": [ - { - "id": "attachment-uuid", - "field": "patientPhoto", - "filename": "photo_123.jpg", - "mimeType": "image/jpeg", - "size": 245678 - } - ], - "status": "pending", - "syncedAt": null -} -``` - -## Observation Lifecycle - -``` -┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ -│ Draft │────▶│ Pending │────▶│ Syncing │────▶│ Synced │ -└─────────┘ └─────────┘ └─────────┘ └─────────┘ - │ │ │ - │ │ │ - ▼ ▼ ▼ -┌─────────┐ ┌─────────┐ ┌─────────┐ -│ Deleted │ │ Error │────▶│ Retry │ -└─────────┘ └─────────┘ └─────────┘ -``` - -### States - -| State | Description | Actions | -|-------|-------------|---------| -| **Draft** | Incomplete, saved locally | Edit, Delete, Submit | -| **Pending** | Complete, awaiting sync | Edit (maybe), Delete | -| **Syncing** | Currently uploading | Wait | -| **Synced** | Successfully uploaded | View | -| **Error** | Sync failed | Retry, View error | - -## Managing Observations - -### Viewing Observations - -In the Formulus app: - -1. **Go to Observations screen** -2. **See list of all saved observations** -3. **Each entry shows:** - - Form name - - Date/time created - - Status (draft, pending, synced) - - Key data fields - -### Filtering and Search - -- **Filter by form type** - Dropdown or tabs -- **Filter by status** - Draft, pending, synced -- **Search** - Find by keyword -- **Sort** - By date, form name, status - -### Editing Observations - -#### Edit Draft - -1. **Tap on draft observation** -2. **Form opens with saved data** -3. **Make changes** -4. **Save or Submit** - -#### Edit Pending (if allowed) - -1. **Tap on pending observation** -2. **May need to "Unlock" for editing** -3. **Make changes** -4. **Re-submit** - -### Deleting Observations - -1. **Long-press on observation** or tap menu (⋮) -2. **Select "Delete"** -3. **Confirm deletion** - -**Note:** Synced observations may not be deletable locally. - -## Sync Protocol - -### Pull (Server → Device) - -```json -// Request -GET /sync/pull?since=1702300000&schemaTypes=registration,visit - -// Response -{ - "observations": [...], - "lastChangeId": 12345, - "hasMore": false -} -``` - -### Push (Device → Server) - -```json -// Request -POST /sync/push -{ - "clientId": "device-uuid", - "observations": [ - { - "id": "local-uuid", - "schemaType": "registration", - "data": {...}, - "createdAt": "2025-12-11T10:30:00Z" - } - ] -} - -// Response -{ - "accepted": ["local-uuid"], - "rejected": [], - "warnings": [] -} -``` - -### Attachment Sync - -Attachments sync separately: - -``` -1. Observation syncs first (with attachment references) -2. Attachments queue for upload -3. Each attachment uploads individually -4. Server confirms receipt -``` - -## Querying Observations - -### Via API - -```bash -# Get all observations -curl http://localhost/api/v1.0.0/observations \ - -H "Authorization: Bearer $TOKEN" - -# Filter by schema type -curl "http://localhost/api/v1.0.0/observations?schemaType=registration" \ - -H "Authorization: Bearer $TOKEN" - -# Filter by date range -curl "http://localhost/api/v1.0.0/observations?since=2025-12-01&until=2025-12-31" \ - -H "Authorization: Bearer $TOKEN" -``` - -### Via CLI - -```bash -# Export observations -./bin/synk data export observations.zip - -# Export with filters (if supported) -./bin/synk data export --schema-type registration output.zip -``` - -## Data Export - -### Export Format - -The export creates a ZIP containing Parquet files: - -``` -export.zip -├── observations.parquet -├── attachments.parquet -└── metadata.json -``` - -### Export via CLI - -```bash -# Export all observations as Parquet -./bin/synk data export observations.zip - -# Export with filters (if supported) -./bin/synk data export --schema-type registration output.zip -``` - -## Best Practices - -### Data Quality - -1. **Validate early** - Client-side validation -2. **Require key fields** - Don't allow empty -3. **Use formats** - date, email, etc. -4. **Capture metadata** - Who, when, where -5. **Handle offline** - Graceful sync - -### Sync Management - -1. **Sync before fieldwork** - Get latest forms -2. **Save frequently** - Avoid data loss -3. **Check pending count** - Before leaving field -4. **Sync immediately** - When back online - -## Troubleshooting - -### Issue: Observations stuck as "Pending" - -**Try:** - -1. Check network connection -2. Manual sync from Sync screen -3. Check for sync errors -4. Verify server is running - -### Issue: Sync fails with error - -**Check logs:** - -1. Open app developer menu -2. Check sync error message -3. Review server logs - -**Common causes:** - -- Network timeout -- Server error -- Authentication expired -- File too large - -### Issue: Attachments won't upload - -**Check:** - -1. File size (may be too large) -2. Network connection -3. Server storage space -4. Retry from Sync screen - -## Related Content - -- [Forms Overview](/docs/build/forms/overview) -- [Data Export](/docs/build/data-management/export) -- [Synchronization](/docs/build/synchronization/overview) -- [Database Schema](/docs/technical-overview/database/schema) diff --git a/docs/build/data-management/overview.md b/docs/build/data-management/overview.md deleted file mode 100644 index 18e3857..0000000 --- a/docs/build/data-management/overview.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Data Management Overview - -Manage observations, attachments, and other data in ODE. - -## Overview - -[Description placeholder] - -## Key Topics - -- [Observations](/docs/build/data-management/observations) -- [Attachments](/docs/build/data-management/attachments) -- [Data Export](/docs/build/data-management/export) -- [Data Import](/docs/build/data-management/import) - -## Related Content - -- [Database Schema](/docs/technical-overview/database/schema) -- [Synchronization](/docs/build/synchronization/overview) - diff --git a/docs/build/forms/advanced-features/attachments.md b/docs/build/forms/advanced-features/attachments.md deleted file mode 100644 index 22dc832..0000000 --- a/docs/build/forms/advanced-features/attachments.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 3 ---- - -# File Attachments - -Handle file attachments in forms. - -## Overview - -[Description placeholder] - -## Supported Formats - -[Description placeholder] - -## Attachment Management - -[Description placeholder] - -## Related Content - -- [Multimedia in Forms](/docs/build/forms/advanced-features/multimedia) -- [Data Management](/docs/build/data-management/attachments) - diff --git a/docs/build/forms/advanced-features/location.md b/docs/build/forms/advanced-features/location.md deleted file mode 100644 index e9288ab..0000000 --- a/docs/build/forms/advanced-features/location.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 2 ---- - -# GPS & Location - -Capture and use location data in forms. - -## Overview - -[Description placeholder] - -## GPS Capture - -[Description placeholder] - -## Location Display - -[Description placeholder] - -## Related Content - -- [Form Design](/docs/build/forms/design/schema-definition) -- [Formulus Features](/docs/components/formulus/features) - diff --git a/docs/build/forms/design/conditional-logic.md b/docs/build/forms/design/conditional-logic.md deleted file mode 100644 index 8248765..0000000 --- a/docs/build/forms/design/conditional-logic.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Conditional Logic - -Implement conditional logic in forms. - -## Overview - -[Description placeholder] - -## Conditional Display - -[Description placeholder] - -## Conditional Validation - -[Description placeholder] - -## Related Content - -- [UI Schema Configuration](/docs/build/forms/design/ui-schema) -- [Validation Rules](/docs/build/forms/design/validation) - diff --git a/docs/build/forms/design/schema-definition.md b/docs/build/forms/design/schema-definition.md deleted file mode 100644 index 2877163..0000000 --- a/docs/build/forms/design/schema-definition.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Schema Definition - -Define form data structure using JSON Schema. - -## Overview - -[Description placeholder] - -## Basic Schema - -[Description placeholder] - -## Field Types - -[Description placeholder] - -## Validation - -[Description placeholder] - -## Related Content - -- [UI Schema Configuration](/docs/build/forms/design/ui-schema) -- [Validation Rules](/docs/build/forms/design/validation) - diff --git a/docs/build/forms/design/ui-schema.md b/docs/build/forms/design/ui-schema.md deleted file mode 100644 index 9d15f78..0000000 --- a/docs/build/forms/design/ui-schema.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 2 ---- - -# UI Schema Configuration - -Configure form layout using JSON Forms UI Schema. - -## Overview - -[Description placeholder] - -## Layout Options - -[Description placeholder] - -## Custom Renderers - -[Description placeholder] - -## Related Content - -- [Schema Definition](/docs/build/forms/design/schema-definition) -- [JSON Forms UI Schema](https://jsonforms.io/docs/uischema) - diff --git a/docs/build/forms/design/validation.md b/docs/build/forms/design/validation.md deleted file mode 100644 index 9ccd31e..0000000 --- a/docs/build/forms/design/validation.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Validation Rules - -Define validation rules for form fields. - -## Overview - -[Description placeholder] - -## Built-in Validators - -[Description placeholder] - -## Custom Validators - -[Description placeholder] - -## Related Content - -- [Schema Definition](/docs/build/forms/design/schema-definition) -- [Conditional Logic](/docs/build/forms/design/conditional-logic) - diff --git a/docs/build/forms/overview.md b/docs/build/forms/overview.md deleted file mode 100644 index 6ee8841..0000000 --- a/docs/build/forms/overview.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Forms Overview - -Create and configure data collection forms in ODE. - -## Understanding Forms - -Forms in ODE are defined using **JSON Schema** for data structure and **JSON Forms UI Schema** for layout. - -A **form** is a template that defines: -- What data to collect (schema) -- How to display it (UI) -- Validation rules -- Conditional logic - -## Form Structure - -### Required Files - -Each form requires two files: - -``` -forms/ -└── myform/ - ├── schema.json # Data structure (JSON Schema) - └── ui.json # Display layout (UI Schema) -``` - -### schema.json - -Defines the data structure using JSON Schema draft-07: - -```json -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "title": "Patient Registration", - "description": "Register a new patient", - "properties": { - "firstName": { - "type": "string", - "title": "First Name", - "minLength": 1, - "maxLength": 100 - }, - "lastName": { - "type": "string", - "title": "Last Name", - "minLength": 1, - "maxLength": 100 - }, - "dateOfBirth": { - "type": "string", - "format": "date", - "title": "Date of Birth" - }, - "gender": { - "type": "string", - "title": "Gender", - "enum": ["male", "female", "other"] - } - }, - "required": ["firstName", "lastName", "dateOfBirth", "gender"] -} -``` - -### ui.json - -Defines how the form is displayed: - -```json -{ - "type": "Layout", - "layout": "vertical", - "elements": [ - { - "type": "Label", - "text": "Patient Information", - "variant": "h5" - }, - { - "type": "Control", - "scope": "#/properties/firstName" - }, - { - "type": "Control", - "scope": "#/properties/lastName" - }, - { - "type": "Control", - "scope": "#/properties/dateOfBirth" - }, - { - "type": "Control", - "scope": "#/properties/gender", - "options": { - "format": "radio" - } - } - ] -} -``` - -## Data Flow - -``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ Forms │────▶│ User │────▶│ Observations│ -│ (Schema) │ │ (Mobile) │ │ (Data) │ -└─────────────┘ └─────────────┘ └──────┬──────┘ - │ - │ Sync - ▼ - ┌─────────────┐ - │ Synkronus │ - │ (Server) │ - └─────────────┘ -``` - -## Field Types - -Forms support various field types: - -| Field Type | Description | Schema Type | -|------------|-------------|-------------| -| **Text** | Single/multi-line text | `string` | -| **Number** | Numeric values | `number`, `integer` | -| **Date/Time** | Date and time selection | `string` with `format` | -| **Select** | Single choice | `string` with `enum` | -| **Multi-select** | Multiple choices | `array` | -| **Boolean** | Checkbox/toggle | `boolean` | -| **Photo** | Camera capture | `string` with `formulus:type: "photo"` | -| **GPS** | Location capture | `object` with `formulus:type: "gps"` | -| **Signature** | Digital signature | `string` with `formulus:type: "signature"` | -| **Audio** | Voice recording | `string` with `formulus:type: "audio"` | -| **QR Code** | Scan QR/barcode | `string` with `formulus:type: "qrcode"` | - -## Form Design - -- [Schema Definition](/docs/build/forms/design/schema-definition) - Define data structure -- [UI Schema Configuration](/docs/build/forms/design/ui-schema) - Configure layout -- [Validation Rules](/docs/build/forms/design/validation) - Add validation -- [Conditional Logic](/docs/build/forms/design/conditional-logic) - Show/hide fields - -## Advanced Features - -- [Multimedia in Forms](/docs/build/forms/advanced-features/multimedia) - Photos, audio, video -- [GPS & Location](/docs/build/forms/advanced-features/location) - Location capture -- [File Attachments](/docs/build/forms/advanced-features/attachments) - File uploads - -## Best Practices - -### Form Design - -1. **Keep forms focused** - One purpose per form -2. **Use clear labels** - Avoid jargon -3. **Group related fields** - Use sections -4. **Mark required fields** - Use schema `required` -5. **Add help text** - Guide users -6. **Test on device** - Verify usability - -### Schema Design - -1. **Use appropriate types** - String, number, boolean -2. **Add validation** - minLength, maximum, pattern -3. **Use enums** - For fixed choices -4. **Document fields** - Use `description` -5. **Version schemas** - Track changes - -## Related Content - -- [Form Versioning](/docs/build/forms/versioning) -- [Observations](/docs/build/data-management/observations) -- [JSON Forms Documentation](https://jsonforms.io) -- [JSON Schema Documentation](https://json-schema.org) diff --git a/docs/build/forms/versioning.md b/docs/build/forms/versioning.md deleted file mode 100644 index ef53548..0000000 --- a/docs/build/forms/versioning.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Form Versioning - -Manage form versions and updates. - -## Overview - -[Description placeholder] - -## Version Strategy - -[Description placeholder] - -## Migration - -[Description placeholder] - -## Related Content - -- [App Bundle Versioning](/docs/technical-overview/concepts/app-bundles) -- [Custom Applications](/docs/build/custom-applications/deployment) - diff --git a/docs/build/index.md b/docs/build/index.md deleted file mode 100644 index 5201715..0000000 --- a/docs/build/index.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Build - -Build ODE applications and forms. - -## Overview - -This section provides guides for building ODE applications, creating forms, and managing data. - -ODE applications are built using JSON Schema for form definitions, JSON Forms for UI layouts, and custom web applications that integrate with the ODE platform. All ODE applications share the same foundation, providing consistent capabilities across different implementations. - - - - - - - - diff --git a/docs/build/synchronization/conflict-resolution.md b/docs/build/synchronization/conflict-resolution.md deleted file mode 100644 index bfa595a..0000000 --- a/docs/build/synchronization/conflict-resolution.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Conflict Resolution - -How ODE handles synchronization conflicts. - -## Overview - -[Description placeholder] - -## Conflict Types - -[Description placeholder] - -## Resolution Strategies - -[Description placeholder] - -## Related Content - -- [Sync Protocol](/docs/build/synchronization/sync-protocol) -- [Offline-First Architecture](/docs/technical-overview/concepts/offline-first) - diff --git a/docs/build/synchronization/overview.md b/docs/build/synchronization/overview.md deleted file mode 100644 index d8cbca4..0000000 --- a/docs/build/synchronization/overview.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Synchronization Overview - -How data synchronization works in ODE. - -## Overview - -[Description placeholder] - -## Sync Protocol - -[Description placeholder] - -## Conflict Resolution - -[Description placeholder] - -## Related Content - -- [Sync Protocol Details](/docs/build/synchronization/sync-protocol) -- [Conflict Resolution](/docs/build/synchronization/conflict-resolution) - diff --git a/docs/build/synchronization/sync-protocol.md b/docs/build/synchronization/sync-protocol.md deleted file mode 100644 index f912fb8..0000000 --- a/docs/build/synchronization/sync-protocol.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Sync Protocol - -Technical details of the ODE synchronization protocol. - -## Overview - -[Description placeholder] - -## Pull Operations - -[Description placeholder] - -## Push Operations - -[Description placeholder] - -## Related Content - -- [Conflict Resolution](/docs/build/synchronization/conflict-resolution) -- [Data Flow](/docs/technical-overview/architecture/data-flow) - diff --git a/docs/build/synchronization/troubleshooting.md b/docs/build/synchronization/troubleshooting.md deleted file mode 100644 index 07ef091..0000000 --- a/docs/build/synchronization/troubleshooting.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Troubleshooting Synchronization - -Common synchronization issues and solutions. - -## Overview - -[Description placeholder] - -## Common Issues - -[Description placeholder] - -## Debugging - -[Description placeholder] - -## Related Content - -- [Sync Protocol](/docs/build/synchronization/sync-protocol) -- [Formulus Troubleshooting](/docs/components/formulus/troubleshooting) - diff --git a/docs/build/translations.md b/docs/build/translations.md deleted file mode 100644 index b35f7bb..0000000 --- a/docs/build/translations.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: 6 ---- - -# Translations - -Configure translations and multi-language support. - -## Overview - -[Description placeholder] - -## Translation Files - -[Description placeholder] - -## Configuration - -[Description placeholder] - -## Related Content - -- [Form Design](/docs/build/forms/design/schema-definition) -- [Custom Applications](/docs/build/custom-applications/overview) - diff --git a/docs/build/users-authentication.md b/docs/build/users-authentication.md deleted file mode 100644 index 0366505..0000000 --- a/docs/build/users-authentication.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Users & Authentication - -Manage users and authentication in ODE. - -## Overview - -[Description placeholder] - -## User Management - -[Description placeholder] - -## Authentication - -[Description placeholder] - -## Permissions - -[Description placeholder] - -## Related Content - -- [Synkronus API](/docs/reference/rest-api/authentication) -- [Hosting Security](/docs/host/security) - diff --git a/docs/community/.gitkeep b/docs/community/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/community/examples.md b/docs/community/examples.md new file mode 100644 index 0000000..46819f4 --- /dev/null +++ b/docs/community/examples.md @@ -0,0 +1,344 @@ +--- +sidebar_position: 2 +--- + +# Examples + +Example ODE applications, forms, and configurations to help you get started. + +## Form Examples + +### Basic Survey Form + +A simple form collecting name and age: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Full Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] +} +``` + +### Health Survey Form + +Example health survey form collecting patient information: + +```json +{ + "type": "object", + "properties": { + "patientId": { + "type": "string", + "title": "Patient ID" + }, + "visitDate": { + "type": "string", + "format": "date", + "title": "Visit Date" + }, + "symptoms": { + "type": "array", + "title": "Symptoms", + "items": { + "type": "string", + "enum": ["fever", "cough", "headache", "fatigue"] + } + }, + "temperature": { + "type": "number", + "title": "Temperature (°C)", + "minimum": 35, + "maximum": 42 + }, + "notes": { + "type": "string", + "title": "Clinical Notes" + } + }, + "required": ["patientId", "visitDate"] +} +``` + +### Research Data Collection Form + +Example research form for field data collection: + +```json +{ + "type": "object", + "properties": { + "siteId": { + "type": "string", + "title": "Site ID" + }, + "location": { + "type": "string", + "format": "gps", + "title": "Site Location" + }, + "sitePhoto": { + "type": "object", + "format": "photo", + "title": "Site Photo" + }, + "observations": { + "type": "string", + "title": "Field Observations" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Observation Time" + } + }, + "required": ["siteId", "location"] +} +``` + +## Custom Application Examples + +### Basic Custom App + +Simple custom application that lists available forms: + +```html + + + + Form Selector + + + + +

Available Forms

+ + + + + +``` + +### Advanced Custom App + +Advanced custom applications can include complex workflows, custom navigation, and integration with external systems. Examples of advanced patterns will be added as they become available. For now, see the [Custom Applications guide](/guides/custom-applications) for detailed implementation guidance. + +## Configuration Examples + +### Server Configuration + +Example server configuration for development: + +```bash +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=debug +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +``` + +### Docker Compose Configuration + +Example Docker Compose configuration for production: + +```yaml +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + + synkronus: + image: ghcr.io/opendataensemble/synkronus:latest + environment: + DB_CONNECTION: postgres://synkronus:${DB_PASSWORD}@postgres:5432/synkronus?sslmode=disable + JWT_SECRET: ${JWT_SECRET} + LOG_LEVEL: info + depends_on: + - postgres + volumes: + - app-bundles:/app/data/app-bundles + + nginx: + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - synkronus + +volumes: + postgres-data: + app-bundles: +``` + +## Integration Examples + +### API Integration + +Example API integration using different methods: + + + + +```bash +# Authenticate +TOKEN=$(curl -X POST http://your-server:8080/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"user","password":"pass"}' \ + | jq -r '.token') + +# Pull data +curl -X POST http://your-server:8080/sync/pull \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"client_id":"my-client","since_change_id":0}' + +# Push data +curl -X POST http://your-server:8080/sync/push \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "transmission_id": "550e8400-e29b-41d4-a716-446655440000", + "client_id": "my-client", + "records": [...] + }' +``` + + + + +```bash +# Login (stores token automatically) +synk login --username user + +# Pull data +synk sync pull data.json --client-id my-client + +# Push data +synk sync push data.json +``` + + + + +```python +import requests + +# Authenticate +response = requests.post( + 'http://your-server:8080/auth/login', + json={'username': 'user', 'password': 'pass'} +) +token = response.json()['token'] + +# Pull data +response = requests.post( + 'http://your-server:8080/sync/pull', + headers={'Authorization': f'Bearer {token}'}, + json={'client_id': 'my-client', 'since_change_id': 0} +) +data = response.json() +``` + + + + +### Data Export Examples + +Export observations using different methods: + + + + +```bash +# Export all observations +synk data export observations.zip + +# Export to specific directory +synk data export ./backups/observations_$(date +%Y%m%d).zip + +# Export to JSON +synk data export observations.json --format json +``` + + + + +```bash +# Export to Parquet ZIP +curl -X GET http://your-server:8080/api/dataexport/parquet \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -o observations.zip +``` + + + + +1. Navigate to the Portal +2. Go to "Data Export" +3. Select format (Parquet, JSON, CSV) +4. Click "Export" to download + + + + +The exported ZIP contains Parquet files organized by schema type, suitable for analysis in tools like Python pandas, R, or data analysis platforms. + +## Community Projects + +Projects built by the ODE community will be showcased here as they become available. + +## Contributing Examples + +If you have examples you'd like to share, please: + +1. Create a pull request with your example +2. Include clear documentation +3. Follow the existing example format +4. Ensure examples are tested and working + +## Related Resources + +- [Form Design Guide](/guides/form-design) +- [Custom Applications Guide](/guides/custom-applications) +- [API Reference](/reference/api) +- [Getting Help](/community/getting-help) diff --git a/docs/community/getting-help.md b/docs/community/getting-help.md new file mode 100644 index 0000000..20f08b9 --- /dev/null +++ b/docs/community/getting-help.md @@ -0,0 +1,74 @@ +--- +sidebar_position: 1 +--- + +# Getting Help + +Resources and channels for getting help with ODE. + +## Support Channels + +### Documentation + +The first place to look for help is this documentation site. It covers: + +- Installation and setup guides +- Usage instructions +- API reference +- Troubleshooting guides +- FAQ section + +### GitHub + +- **Issues**: Report bugs, request features, or ask questions on [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- **Discussions**: Join discussions on [GitHub Discussions](https://github.com/OpenDataEnsemble/ode/discussions) +- **Repository**: Browse the source code at [GitHub Repository](https://github.com/OpenDataEnsemble/ode) + +### Email + +For general inquiries or to get involved: + +- **Email**: [hello@opendataensemble.org](mailto:hello@opendataensemble.org) + +## Before Asking for Help + +To get the most effective help, please: + +1. **Search existing resources**: Check the documentation, FAQ, and existing GitHub issues +2. **Provide context**: Include relevant information about your setup, error messages, and what you've tried +3. **Be specific**: Describe the problem clearly and include steps to reproduce if applicable + +## Common Issues + +Many common issues are covered in the [Troubleshooting guide](/using/troubleshooting). Check there first for: + +- Connection issues +- Synchronization problems +- Form-related errors +- Data management issues +- Performance problems + +## Reporting Issues + +When reporting issues on GitHub, please include: + +- **Description**: Clear description of the problem +- **Steps to reproduce**: If applicable, steps to reproduce the issue +- **Expected behavior**: What you expected to happen +- **Actual behavior**: What actually happened +- **Environment**: Operating system, ODE version, component versions +- **Error messages**: Any error messages or logs +- **Screenshots**: If applicable, screenshots showing the issue + +See [Reporting Issues on GitHub](https://github.com/OpenDataEnsemble/ode/issues/new) to create a new issue. + +## Contributing + +If you'd like to contribute to ODE, see the [Contributing guide](/development/contributing) for information on how to get started. + +## Related Resources + +- [FAQ](/getting-started/faq) +- [Troubleshooting Guide](/using/troubleshooting) +- [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- [Examples](/community/examples) diff --git a/docs/community/index.md b/docs/community/index.md index 795a41a..e590363 100644 --- a/docs/community/index.md +++ b/docs/community/index.md @@ -1,35 +1,40 @@ --- -sidebar_position: 1 +sidebar_position: 0 --- # Community -Get involved with the ODE community. +Connect with the ODE community, get help, and share your experiences. -## Overview +## Get Help & Support -Join the ODE community to contribute, get help, and stay updated. +
+
+
+
+

Getting Help

+
+
+

Find resources and channels to get help with ODE.

+ Get Help → +
+
+
+
+
+
+

Examples

+
+
+

Explore example projects, use cases, and implementations.

+ View Examples → +
+
+
+
- - - - - - +## Join the Community +- **Forum**: [forum.opendataensemble.org](https://forum.opendataensemble.org) - Ask questions and share knowledge +- **GitHub**: [github.com/OpenDataEnsemble/ode](https://github.com/OpenDataEnsemble/ode) - Contribute code and report issues +- **Email**: [hello@opendataensemble.org](mailto:hello@opendataensemble.org) - Contact the team directly diff --git a/docs/development/.gitkeep b/docs/development/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/development/architecture.md b/docs/development/architecture.md new file mode 100644 index 0000000..21b6e43 --- /dev/null +++ b/docs/development/architecture.md @@ -0,0 +1,263 @@ +--- +sidebar_position: 2 +--- + +# Architecture + +Complete architecture documentation for ODE, including system overview, components, data flow, and technical details. + +## System Overview + +ODE follows a client-server architecture designed for offline-first data collection: + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formulus │◄───────►│ Synkronus │◄───────►│ Formulus │ +│ (Mobile) │ Sync │ (Server) │ Sync │ (Mobile) │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formplayer │ │ Database │ │ Formplayer │ +│ (WebView) │ │ (PostgreSQL) │ │ (WebView) │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +## Components + +### Formulus + +React Native mobile application providing: + +- **Local Storage**: WatermelonDB for offline data storage +- **Sync Module**: Handles synchronization with server +- **WebView Hosts**: Runs Formplayer and custom applications +- **Native Features**: Camera, GPS, file system access + +**Technology Stack:** +- React Native +- WatermelonDB +- TypeScript + +### Synkronus + +Go backend server providing: + +- **REST API**: Comprehensive API for all operations +- **Synchronization**: Bidirectional sync protocol +- **Storage**: PostgreSQL database +- **Authentication**: JWT-based authentication +- **App Bundle Management**: Versioning and distribution + +**Technology Stack:** +- Go 1.22+ +- PostgreSQL +- Chi router +- JWT authentication + +### Formplayer + +React web application providing: + +- **Form Rendering**: JSON Forms-based form rendering +- **Question Types**: Various input types and renderers +- **Validation**: Client-side and schema validation +- **Integration**: JavaScript interface for custom apps + +**Technology Stack:** +- React +- JSON Forms +- Material-UI +- TypeScript + +### Synkronus CLI + +Go command-line utility providing: + +- **Server Management**: Administrative operations +- **Data Operations**: Sync, export, import +- **App Bundle Management**: Upload, download, version management + +**Technology Stack:** +- Go 1.22+ +- Cobra CLI framework + +## Data Flow + +### Observation Creation + +1. User fills out form in Formplayer (WebView) +2. Formplayer validates and submits to Formulus +3. Formulus creates observation in local database (WatermelonDB) +4. Observation is marked for synchronization + +### Synchronization + +1. **Pull Phase**: Formulus requests changes from server + - Server returns records with `change_id > client_last_seen` + - Client applies changes to local database + +2. **Push Phase**: Formulus sends local changes to server + - Client sends records with transmission ID for idempotency + - Server validates and stores records + - Server returns success/failure for each record + +3. **Attachment Sync**: Separate phase for binary files + - Upload: Client uploads attachments referenced in observations + - Download: Client downloads attachments referenced in new observations + +### Conflict Resolution + +Conflicts are detected using hash comparison: + +- Each record has a hash computed from data, schemaType, and schemaVersion +- If server hash ≠ client's last seen hash, conflict detected +- Server accepts overwrite with warning +- Previous version stored in conflicts table + +## Sync Protocol + +### Change Detection + +Uses cursor-based approach with `change_id`: + +- Each record has a strictly increasing `change_id` (server-assigned) +- Client stores last seen `change_id` per schemaType +- Pull returns all records where `change_id > last_seen` + +**Advantages:** +- No dependence on system clocks +- No ambiguity about ordering +- Enables clean pagination and deduplication + +### Record Model + +Each form submission is an entity: + +- `id`: Unique identifier +- `schemaType`: Form type identifier +- `schemaVersion`: Form version +- `data`: JSON data (form responses) +- `hash`: Computed hash for conflict detection +- `change_id`: Strictly increasing change identifier +- `last_modified`: Server-assigned timestamp +- `last_modified_by`: Username from JWT +- `deleted`: Soft delete flag +- `origin_client_id`: Client that created the record + +### Attachment Handling + +Attachments are managed separately: + +- **Immutable**: Once uploaded, cannot be modified +- **Referenced**: Observations reference attachments by ID +- **Separate Sync**: Uploaded/downloaded in separate phase +- **Manifest-based**: Server provides manifest of changes + +See the [Synchronization guide](/using/synchronization) for more details. + +## Database Design + +### Observations Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | UUID | Primary key | +| `schema_type` | String | Form type identifier | +| `schema_version` | String | Form version | +| `data` | JSONB | Form data | +| `hash` | String | Computed hash | +| `change_id` | Integer | Strictly increasing change ID | +| `last_modified` | Timestamp | Last modification time | +| `last_modified_by` | String | Username | +| `deleted` | Boolean | Soft delete flag | +| `origin_client_id` | String | Creating client ID | +| `created_at` | Timestamp | Creation time | + +### Attachments Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | String | Attachment ID (UUID) | +| `hash` | String | SHA-256 hash | +| `size` | Integer | File size in bytes | +| `mime_type` | String | MIME type | +| `change_id` | Integer | Change ID for sync | +| `last_modified` | Timestamp | Last modification time | +| `sync_state` | String | Sync state | + +## Security Architecture + +### Authentication + +- **JWT Tokens**: JSON Web Tokens for authentication +- **Token Refresh**: Refresh tokens for long-lived sessions +- **Role-Based Access**: `read-only`, `read-write`, `admin` roles + +### Authorization + +- **Endpoint Protection**: Middleware validates JWT on protected routes +- **Role Checks**: Endpoints check required roles +- **Resource Access**: Users can only access their own data (unless admin) + +### Data Security + +- **Transport**: HTTPS enforced in production +- **Storage**: Database encryption via PostgreSQL +- **Secrets**: Environment variables for sensitive data +- **Validation**: Input validation on all endpoints + +## Performance Considerations + +### Client-Side + +- **Local Database**: Fast queries using WatermelonDB +- **Incremental Sync**: Only sync changes since last sync +- **Lazy Loading**: Load attachments on demand +- **Caching**: Cache app bundles and form specifications + +### Server-Side + +- **Database Indexing**: Indexes on `change_id`, `schema_type`, `hash` +- **Connection Pooling**: Efficient database connection management +- **Pagination**: Cursor-based pagination for large datasets +- **Caching**: ETag support for efficient caching + +## Extension Points + +### Custom Renderers + +Create custom question type renderers: + +1. Define result interface in `FormulusInterfaceDefinition.ts` +2. Add interface method +3. Implement React component +4. Register renderer +5. Add mock implementation + +### Custom Applications + +Build custom web applications: + +1. Create HTML/CSS/JavaScript files +2. Include Formulus load script +3. Use Formulus JavaScript interface +4. Package as app bundle +5. Upload to server + +### Plugins + +ODE's plugin system allows for extending functionality without modifying core code. The plugin architecture is under active development and will be documented as it evolves. + +Current extension points: +- Custom renderers for question types +- Custom applications +- Server-side handlers (for advanced use cases) + +## Related Documentation + +- [Synchronization Details](/using/synchronization) +- [Sync Protocol Technical Details](/development/architecture) +- [Database Schema](/development/architecture) +- [API Reference](/reference/api) diff --git a/docs/development/building-testing.md b/docs/development/building-testing.md new file mode 100644 index 0000000..fb56592 --- /dev/null +++ b/docs/development/building-testing.md @@ -0,0 +1,359 @@ +--- +sidebar_position: 4 +--- + +# Building & Testing + +Complete guide to building ODE components from source and running tests. + +## Building from Source + +### Formulus + +Build the React Native mobile application: + + + + +```bash +cd formulus +npm install +npm run android # For Android +npm run ios # For iOS (macOS only) +``` + + + + +```bash +cd formulus/android +./gradlew assembleRelease +``` + +APK will be in `android/app/build/outputs/apk/release/` + + + + +```bash +cd formulus/ios +xcodebuild -workspace Formulus.xcworkspace -scheme Formulus -configuration Release +``` + +Or build from Xcode: +1. Open `Formulus.xcworkspace` in Xcode +2. Select "Any iOS Device" or specific device +3. Product → Archive +4. Distribute App + + + + +### Formplayer + +Build the React web form renderer: + +```bash +cd formulus-formplayer +npm install +npm run build +``` + +**Build for React Native:** + +```bash +npm run build:rn +``` + +This builds and copies the output to the Formulus app. + +### Synkronus + +Build the Go server: + +```bash +cd synkronus +go build -o bin/synkronus cmd/synkronus/main.go +``` + +**Cross-platform builds:** + + + + +```bash +GOOS=linux GOARCH=amd64 go build -o bin/synkronus-linux cmd/synkronus/main.go +``` + + + + +```powershell +$env:GOOS="windows"; $env:GOARCH="amd64"; go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + +Or using bash (Git Bash/WSL): + +```bash +GOOS=windows GOARCH=amd64 go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + + + + +```bash +GOOS=darwin GOARCH=amd64 go build -o bin/synkronus-macos cmd/synkronus/main.go +``` + + + + +### Synkronus CLI + +Build the CLI: + +```bash +cd synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +## Testing + +### Frontend Testing + +#### Formulus + +```bash +cd formulus +npm test +``` + +Runs Jest tests with React Native Testing Library. + +#### Formplayer + +```bash +cd formulus-formplayer +npm test +``` + +Runs Jest tests for React components. + +### Backend Testing + +#### Synkronus + +```bash +cd synkronus +go test ./... +``` + +Run all tests: + +```bash +# With coverage +go test -cover ./... + +# Verbose output +go test -v ./... + +# Specific package +go test ./internal/handlers +``` + +#### Integration Tests + +```bash +# Run integration tests (requires database) +go test -tags=integration ./... +``` + +### End-to-End Testing + +End-to-end testing infrastructure is under development. Current testing focuses on: + +- Unit tests for individual components +- Integration tests for API endpoints +- Component tests for React components + +E2E testing will be added as the testing infrastructure evolves. + +## Code Quality Checks + +### Linting + +**Frontend:** + +```bash +# Formulus +cd formulus +npm run lint +npm run lint:fix + +# Formplayer +cd formulus-formplayer +npm run lint +npm run lint:fix +``` + +**Backend:** + +```bash +# Synkronus +cd synkronus +golangci-lint run # If configured +``` + +### Formatting + +**Frontend:** + +```bash +# Format code +npm run format + +# Check formatting +npm run format:check +``` + +**Backend:** + +```bash +# Format Go code +go fmt ./... + +# Check with goimports +goimports -w . +``` + +## CI/CD Pipeline + +The project uses GitHub Actions for continuous integration: + +### Workflows + +**Synkronus Docker Build:** +- Triggers on push to `main` or PRs affecting `synkronus/` +- Builds Docker image +- Publishes to GitHub Container Registry +- Tags: `latest`, `v{version}`, `{branch-name}` + +**Frontend Quality Checks:** +- Runs on all PRs +- Checks linting and formatting +- Runs tests +- Builds components + +### Local CI Simulation + +Run CI checks locally: + +```bash +# Frontend +cd formulus && npm run lint && npm run format:check && npm test +cd formulus-formplayer && npm run lint && npm run format:check && npm test + +# Backend +cd synkronus && go test ./... && go fmt ./... +``` + +## Docker Builds + +### Synkronus Docker Image + +Build locally: + +```bash +cd synkronus +docker build -t synkronus:local . +``` + +**Multi-platform build:** + +```bash +docker buildx create --name multiplatform --use +docker buildx build --platform linux/amd64,linux/arm64 -t synkronus:local . +``` + +## Release Process + +### Versioning + +ODE follows semantic versioning (MAJOR.MINOR.PATCH): + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +### Creating a Release + +1. Update version numbers in: + - `package.json` (frontend projects) + - `versioninfo.json` (Go projects) + - Documentation + +2. Create release branch: + +```bash +git checkout -b release/v1.0.0 +``` + +3. Update changelog + +4. Create tag: + +```bash +git tag -a v1.0.0 -m "Release version 1.0.0" +git push origin v1.0.0 +``` + +5. CI/CD will automatically: + - Build Docker images + - Publish to container registry + - Create GitHub release + +## Troubleshooting Build Issues + +### Node Modules Issues + +```bash +# Clear and reinstall +rm -rf node_modules package-lock.json +npm install +``` + +### Go Module Issues + +```bash +# Clean module cache +go clean -modcache +go mod download +go mod tidy +``` + +### Android Build Issues + +```bash +# Clean Android build +cd android +./gradlew clean +cd .. +npm run android +``` + +### iOS Build Issues + +```bash +# Clean pods +cd ios +rm -rf Pods Podfile.lock +bundle exec pod install +cd .. +npm run ios +``` + +## Related Documentation + +- [Development Setup](/development/setup) +- [Contributing Guide](/development/contributing) +- [Architecture Overview](/development/architecture) diff --git a/docs/development/contributing.md b/docs/development/contributing.md new file mode 100644 index 0000000..a8582ca --- /dev/null +++ b/docs/development/contributing.md @@ -0,0 +1,252 @@ +--- +sidebar_position: 3 +--- + +# Contributing + +Guide to contributing to ODE, including contribution process, coding standards, and community guidelines. + +## Overview + +ODE is an open-source project and welcomes contributions from the community. We believe that diverse perspectives and varied skill sets make our project stronger. + +## Ways to Contribute + +You can contribute to ODE in many ways: + +- **Code Contributions**: Bug fixes, new features, improvements +- **Documentation**: Improve existing docs, add examples, fix typos +- **Testing**: Test the platform, report bugs, verify fixes +- **Community**: Help others, answer questions, share use cases +- **Design**: UI/UX improvements, design system contributions +- **Translation**: Help translate documentation and interfaces + +## Contribution Process + +### 1. Find Something to Work On + +- Browse [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) for open issues +- Look for issues labeled `good first issue` for beginners +- Check discussions for ideas and feature requests +- Review documentation for gaps or improvements + +### 2. Set Up Development Environment + +Follow the [Development Setup guide](/development/setup) to set up your local environment. + +### 3. Create a Branch + +```bash +git checkout -b feature/your-feature-name +# or +git checkout -b fix/your-bug-fix +``` + +Use descriptive branch names: +- `feature/` for new features +- `fix/` for bug fixes +- `docs/` for documentation +- `refactor/` for code refactoring + +### 4. Make Your Changes + +- Write clean, well-documented code +- Follow coding standards (see below) +- Add tests for new functionality +- Update documentation as needed + +### 5. Test Your Changes + +```bash +# Frontend projects +npm test +npm run lint +npm run format:check + +# Go projects +go test ./... +go fmt ./... +``` + +### 6. Commit Your Changes + +Write clear, descriptive commit messages: + +``` +feat: Add support for custom question types + +- Add interface for custom renderers +- Implement renderer registration +- Add documentation and examples +``` + +**Commit Message Format:** +- `feat:` for new features +- `fix:` for bug fixes +- `docs:` for documentation +- `style:` for formatting +- `refactor:` for code refactoring +- `test:` for tests +- `chore:` for maintenance + +### 7. Push and Create Pull Request + +```bash +git push origin feature/your-feature-name +``` + +Create a pull request on GitHub with: + +- Clear description of changes +- Reference to related issues +- Screenshots (if UI changes) +- Testing notes + +## Coding Standards + +### Frontend (React/React Native) + +**TypeScript:** +- Use TypeScript for all new code +- Enable strict type checking +- Define interfaces for data structures +- Avoid `any` type + +**Code Style:** +- Follow ESLint rules +- Use Prettier for formatting +- Use functional components with hooks +- Keep components small and focused + +**Example:** + +```typescript +interface User { + id: string; + username: string; + role: 'read-only' | 'read-write' | 'admin'; +} + +const UserProfile: React.FC<{user: User}> = ({user}) => { + return
{user.username}
; +}; +``` + +### Backend (Go) + +**Code Style:** +- Follow `gofmt` formatting +- Use `golint` recommendations +- Write clear, descriptive function names +- Add godoc comments for exported functions + +**Example:** + +```go +// GetUser retrieves a user by username. +// Returns an error if the user is not found. +func (s *Service) GetUser(username string) (*User, error) { + // Implementation +} +``` + +**Error Handling:** +- Always handle errors explicitly +- Return descriptive error messages +- Use error wrapping for context + +### General Guidelines + +- **Write clear code**: Code should be self-documenting +- **Add comments**: Explain why, not what +- **Keep functions small**: Single responsibility principle +- **Use meaningful names**: Variables and functions should be descriptive +- **Avoid duplication**: DRY (Don't Repeat Yourself) +- **Test your code**: Write tests for new functionality + +## Code Quality Checks + +### Before Submitting + +Ensure your code passes all quality checks: + +```bash +# Frontend +npm run lint +npm run format:check +npm test + +# Backend +go test ./... +go fmt ./... +go vet ./... +``` + +### CI/CD Checks + +The CI pipeline automatically checks: + +- Linting (ESLint for frontend) +- Formatting (Prettier for frontend, gofmt for backend) +- Tests (Jest for frontend, go test for backend) +- Build (ensures code compiles) + +## Pull Request Guidelines + +### PR Description + +Include: + +- **Summary**: Brief description of changes +- **Motivation**: Why this change is needed +- **Changes**: What was changed +- **Testing**: How it was tested +- **Screenshots**: If UI changes +- **Related Issues**: Link to related issues + +### Review Process + +- Maintainers will review your PR +- Address feedback promptly +- Be open to suggestions +- Keep discussions constructive + +### After Approval + +- Maintainers will merge your PR +- Your contribution will be included in the next release +- Thank you for contributing! + +## Code of Conduct + +We are committed to providing a welcoming and inclusive environment. Please: + +- Be respectful and considerate +- Welcome newcomers and help them learn +- Focus on constructive feedback +- Respect different viewpoints and experiences + +## Getting Help + +If you need help: + +- Check the [documentation](/) +- Search [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- Ask in [GitHub Discussions](https://github.com/OpenDataEnsemble/ode/discussions) +- Contact maintainers + +## Recognition + +Contributors are recognized in: + +- GitHub contributors list +- Release notes +- Project documentation + +Thank you for contributing to ODE! + +## Related Documentation + +- [Development Setup](/development/setup) +- [Building & Testing](/development/building-testing) +- [Architecture Overview](/development/architecture) diff --git a/docs/development/extending.md b/docs/development/extending.md new file mode 100644 index 0000000..e223bf0 --- /dev/null +++ b/docs/development/extending.md @@ -0,0 +1,242 @@ +--- +sidebar_position: 5 +--- + +# Extending ODE + +Guide to extending ODE functionality through custom renderers, plugins, and internal APIs. + +## Overview + +ODE is designed to be extensible. You can extend functionality through: + +- **Custom Renderers**: Add new question types +- **Custom Applications**: Build specialized web applications +- **Internal APIs**: Extend server functionality (advanced) + +## Custom Renderers + +Custom renderers allow you to add new question types to the Formplayer. + +### Implementation Steps + +1. **Define Result Interface** + +In `FormulusInterfaceDefinition.ts`: + +```typescript +export interface MyCustomResultData { + type: 'mycustom'; + value: string; + timestamp: string; +} + +export type MyCustomResult = ActionResult; +``` + +2. **Add Interface Method** + +```typescript +export interface FormulusInterface { + requestMyCustom(fieldId: string): Promise; +} +``` + +3. **Create React Component** + +Create `MyCustomQuestionRenderer.tsx`: + +```typescript +import React, {useState, useCallback} from 'react'; +import {withJsonFormsControlProps} from '@jsonforms/react'; +import {FormulusClient} from './FormulusInterface'; + +const MyCustomQuestionRenderer: React.FC = ({ + data, + handleChange, + path, +}) => { + const [isLoading, setIsLoading] = useState(false); + const formulusClient = useRef(new FormulusClient()); + + const handleAction = useCallback(async () => { + setIsLoading(true); + try { + const result = await formulusClient.current.requestMyCustom(path); + if (result.status === 'success') { + handleChange(path, result.data.value); + } + } finally { + setIsLoading(false); + } + }, [path, handleChange]); + + return ( + + ); +}; + +export default withJsonFormsControlProps(MyCustomQuestionRenderer); +``` + +4. **Register Renderer** + +In `App.tsx`: + +```typescript +import MyCustomQuestionRenderer, { + myCustomQuestionTester, +} from './MyCustomQuestionRenderer'; + +const customRenderers = [ + ...materialRenderers, + {tester: myCustomQuestionTester, renderer: MyCustomQuestionRenderer}, +]; +``` + +5. **Add Mock Implementation** + +For development testing, add mock support in `webview-mock.ts`. + +6. **Implement Native Handler** + +In Formulus React Native code, implement the native handler in `FormulusMessageHandlers.ts`. + +See the [Form Design guide](/guides/form-design) for complete examples. + +## Custom Applications + +Custom applications are web-based interfaces that run within Formulus. + +### Creating a Custom App + +1. **Create HTML Structure** + +```html + + + + My Custom App + + + +

My Custom App

+ + + +``` + +2. **Use Formulus API** + +```javascript +async function init() { + const api = await getFormulus(); + + // Use API methods + const forms = await api.getAvailableForms(); + // ... +} +``` + +3. **Package as App Bundle** + +Create a ZIP file with: +- `index.html` +- `manifest.json` +- Assets (CSS, JS, images) + +4. **Upload to Server** + +```bash +synk app-bundle upload bundle.zip --activate +``` + +See the [Custom Applications guide](/guides/custom-applications) for details. + +## Internal APIs + +For advanced extensions, you can extend the server functionality. + +### Adding New Endpoints + +1. **Define Handler** + +In `internal/handlers/`: + +```go +func (h *Handler) MyNewEndpoint(w http.ResponseWriter, r *http.Request) { + // Implementation +} +``` + +2. **Register Route** + +In `internal/api/api.go`: + +```go +r.Route("/my-endpoint", func(r chi.Router) { + r.Get("/", h.MyNewEndpoint) +}) +``` + +3. **Add Tests** + +Create tests in `internal/handlers/`: + +```go +func TestMyNewEndpoint(t *testing.T) { + // Test implementation +} +``` + +### Adding New Services + +1. **Create Service** + +In `internal/services/`: + +```go +type MyService struct { + // Dependencies +} + +func NewMyService(config *Config) (*MyService, error) { + // Initialization +} +``` + +2. **Integrate with Handlers** + +Pass service to handlers and use in endpoints. + +## Best Practices + +### Custom Renderers + +- Follow existing renderer patterns +- Handle loading and error states +- Provide fallback options (manual input) +- Test thoroughly in mock and native environments + +### Custom Applications + +- Always use `getFormulus()` before accessing API +- Handle offline scenarios gracefully +- Optimize for mobile devices +- Test on actual devices + +### Internal APIs + +- Follow Go best practices +- Add comprehensive tests +- Document endpoints +- Consider backward compatibility + +## Related Documentation + +- [Form Design Guide](/guides/form-design) +- [Custom Applications Guide](/guides/custom-applications) +- [API Reference](/reference/api) +- [Architecture Overview](/development/architecture) diff --git a/docs/development/formplayer-development.md b/docs/development/formplayer-development.md new file mode 100644 index 0000000..e5f106e --- /dev/null +++ b/docs/development/formplayer-development.md @@ -0,0 +1,95 @@ +--- +sidebar_position: 4 +--- + +# Formplayer Development + +Complete guide for developing the Formplayer form rendering component. + +## Overview + +Formplayer is a React web application that renders JSON Forms. It runs within WebViews in the Formulus mobile app. + +## Prerequisites + +- **Node.js** 18+ and npm +- **React** development experience + +## Local Development + +### Setup + +```bash +cd formulus-formplayer +npm install +``` + +### Development Server + +```bash +npm start +``` + +Opens at http://localhost:3000 + +### Development Features + +- **Hot Reload**: Changes reflect immediately +- **Source Maps**: Debug in browser DevTools +- **Error Overlay**: Errors shown in browser + +## Building + +### Build for React Native + +Build and copy to Formulus app: + +```bash +npm run build:rn +``` + +This: +1. Builds the React app +2. Copies build to Formulus app directory +3. Updates WebView assets + +### Build for Web + +Standard web build: + +```bash +npm run build +``` + +Output in `build/` directory. + +## Project Structure + +- `src/`: React source code +- `public/`: Static assets +- `build/`: Production build output + +## Adding Question Types + +1. **Create Renderer Component:** + ```typescript + // src/NewQuestionRenderer.tsx + export function NewQuestionRenderer(props) { + return + } + ``` + +2. **Register in Formplayer:** + Add to Formplayer configuration when initialized by Formulus. + +## Testing + +```bash +npm test +``` + +## Related Documentation + +- [Formplayer Reference](/reference/formplayer) - Component reference +- [Form Design Guide](/guides/form-design) - Creating forms + diff --git a/docs/development/formulus-development.md b/docs/development/formulus-development.md new file mode 100644 index 0000000..24e3fad --- /dev/null +++ b/docs/development/formulus-development.md @@ -0,0 +1,365 @@ +--- +sidebar_position: 3 +--- + +# Formulus Development + +Complete guide for developing the Formulus mobile application. + +## Overview + +This guide covers local development and production building for the Formulus React Native application. + +## Prerequisites + +### Required Tools + +- **Node.js** 18+ and npm +- **React Native CLI** or Expo CLI +- **Java Development Kit (JDK)** 11 or higher +- **Android Studio** (for Android development) +- **Xcode** (for iOS development, macOS only) +- **Android SDK** (via Android Studio) +- **CocoaPods** (for iOS, macOS only) + +### Platform-Specific Requirements + +#### Android + +- Android SDK Platform 33+ +- Android SDK Build Tools +- Android Emulator or physical device + +#### iOS (macOS only) + +- Xcode 14+ +- CocoaPods +- iOS Simulator or physical device + +## Local Development Setup + +### Step 1: Clone Repository + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/formulus +``` + +### Step 2: Install Dependencies + +```bash +npm install +``` + +### Step 3: Install iOS Dependencies + + + + +```bash +cd ios +bundle install +bundle exec pod install +cd .. +``` + + + + +iOS development is only available on macOS. Skip this step if you're developing for Android only. + + + + +### Step 4: Generate API Client + +Generate the Synkronus API client from OpenAPI spec: + +```bash +npm run generate:api +``` + +### Step 5: Generate WebView Injection Script + +Generate the JavaScript injection script: + +```bash +npm run generate +``` + +### Step 6: Start Metro Bundler + +```bash +npm start +``` + +Keep this terminal open. Metro is the JavaScript bundler. + +### Step 7: Run on Device/Emulator + + + + +```bash +npm run android +``` + +This will: +1. Build the Android app +2. Install it on your connected device/emulator +3. Start the app +4. Connect to Metro bundler + + + + +```bash +npm run ios +``` + +This will: +1. Build the iOS app +2. Install it on your iOS Simulator or connected device +3. Start the app +4. Connect to Metro bundler + + + + +## Development Workflow + +### Hot Reload + +Changes to JavaScript/TypeScript files automatically reload: + +- **Fast Refresh**: React components update without losing state +- **Live Reload**: Full app reload on some changes +- **Error Overlay**: Errors displayed in app + +### Debugging + +#### React Native Debugger + +1. Shake device or press `Ctrl+M` (Windows/Linux) or `Cmd+M` (macOS) +2. Select "Debug" +3. Chrome DevTools opens +4. Set breakpoints and inspect code + +#### Logging + +```typescript +import { console } from 'react-native' + +console.log('Debug message') +console.warn('Warning message') +console.error('Error message') +``` + +View logs: + + + + +```bash +adb logcat | grep -i formulus +``` + + + + +View logs in Xcode console: +1. Open Xcode +2. Run the app +3. View logs in the bottom panel + +Or use Console.app on macOS: +```bash +# Filter for your app +log stream --predicate 'processImagePath contains "Formulus"' +``` + + + + +For Android development on Windows: + +```powershell +adb logcat | Select-String formulus +``` + +Or using Git Bash/WSL: + +```bash +adb logcat | grep -i formulus +``` + +For iOS development, Windows is not supported. Use macOS with Xcode. + + + + +### Testing + +```bash +# Run tests +npm test + +# Run tests in watch mode +npm test -- --watch +``` + +## Building for Production + +### Android Production Build + +#### Step 1: Generate Signing Key + +```bash +keytool -genkeypair -v -storetype PKCS12 -keystore formulus-release.keystore -alias formulus -keyalg RSA -keysize 2048 -validity 10000 +``` + +#### Step 2: Configure Signing + +Edit `android/gradle.properties`: + +```properties +FORMULUS_RELEASE_STORE_FILE=formulus-release.keystore +FORMULUS_RELEASE_KEY_ALIAS=formulus +FORMULUS_RELEASE_STORE_PASSWORD=your-store-password +FORMULUS_RELEASE_KEY_PASSWORD=your-key-password +``` + +#### Step 3: Build APK + +```bash +cd android +./gradlew assembleRelease +``` + +APK location: `android/app/build/outputs/apk/release/app-release.apk` + +#### Step 4: Build AAB (for Play Store) + +```bash +cd android +./gradlew bundleRelease +``` + +AAB location: `android/app/build/outputs/bundle/release/app-release.aab` + +### iOS Production Build (macOS only) + +#### Step 1: Configure Signing + +1. Open `ios/Formulus.xcworkspace` in Xcode +2. Select project in navigator +3. Go to "Signing & Capabilities" +4. Select team and provisioning profile + +#### Step 2: Build Archive + +1. In Xcode: Product → Archive +2. Wait for build to complete +3. Distribute App in Organizer + +#### Step 3: Export IPA + +1. Select archive in Organizer +2. Click "Distribute App" +3. Choose distribution method (App Store, Ad Hoc, Enterprise) +4. Follow export wizard + +## Project Structure + +### Key Directories + +- `src/`: TypeScript source code +- `android/`: Android native code +- `ios/`: iOS native code +- `assets/`: Static assets (images, fonts, etc.) + +### Important Files + +- `App.tsx`: Main application component +- `package.json`: Dependencies and scripts +- `tsconfig.json`: TypeScript configuration +- `metro.config.js`: Metro bundler configuration +- `babel.config.js`: Babel configuration + +## Common Tasks + +### Adding Dependencies + +```bash +npm install package-name +``` + +For native dependencies, may need to: + +```bash +# Android +cd android && ./gradlew clean && cd .. + +# iOS +cd ios && pod install && cd .. +``` + +### Updating API Client + +When Synkronus API changes: + +```bash +npm run generate:api +``` + +### Updating WebView Interface + +When Formulus interface changes: + +```bash +npm run generate +``` + +### Cleaning Build + +```bash +# Android +cd android && ./gradlew clean && cd .. + +# iOS +cd ios && xcodebuild clean && cd .. + +# Remove node_modules +rm -rf node_modules +npm install +``` + +## Troubleshooting + +### Common Issues + +**Metro Bundler Won't Start:** +- Clear Metro cache: `npm start -- --reset-cache` +- Delete `node_modules` and reinstall + +**Build Fails:** +- Clean build: `cd android && ./gradlew clean` +- Check Java version: `java -version` (should be 11+) +- Verify Android SDK is installed + +**iOS Build Fails:** +- Run `pod install` in `ios/` directory +- Clean build folder in Xcode +- Check signing configuration + +**App Crashes on Launch:** +- Check logs: `adb logcat` or Xcode console +- Verify API client is generated +- Check WebView injection script is generated + +## Related Documentation + +- [Installing Formulus for Development](/development/installing-formulus-dev) - ADB/emulator setup +- [Formulus Reference](/reference/formulus) - Component reference +- [Building and Testing](/development/building-testing) - Build procedures + diff --git a/docs/development/index.md b/docs/development/index.md new file mode 100644 index 0000000..6b011a5 --- /dev/null +++ b/docs/development/index.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 0 +--- + +# Development + +Resources for developers who want to contribute to ODE or extend its functionality. + +## Getting Started with Development + +
+
+
+
+

Setup

+
+
+

Set up your development environment and get the codebase running locally.

+ Setup Guide → +
+
+
+
+
+
+

Architecture

+
+
+

Understand the architecture and design decisions behind ODE.

+ View Architecture → +
+
+
+
+ +## Component Development + +
+
+
+
+

Formulus Development

+
+
+

Develop and contribute to the Formulus mobile app (React Native).

+ View Docs → +
+
+
+
+
+
+

Formplayer Development

+
+
+

Develop and contribute to the Formplayer form engine (React).

+ View Docs → +
+
+
+
+
+
+

Synkronus Development

+
+
+

Develop and contribute to the Synkronus server backend (Go).

+ View Docs → +
+
+
+
+
+
+

Synkronus Portal Development

+
+
+

Develop and contribute to the Synkronus web portal.

+ View Docs → +
+
+
+
+ +## Contributing & Extending + +
+
+
+
+

Contributing

+
+
+

Learn how to contribute to ODE, including code style and best practices.

+ Contribute → +
+
+
+
+
+
+

Building & Testing

+
+
+

Build ODE components from source and run the test suite.

+ Build & Test → +
+
+
+
+
+
+

Extending ODE

+
+
+

Extend ODE functionality with custom renderers and integrations.

+ Extend → +
+
+
+
+
+
+

Installing Formulus Dev

+
+
+

Install and run the Formulus development build on your device.

+ Install → +
+
+
+
diff --git a/docs/development/installing-formulus-dev.md b/docs/development/installing-formulus-dev.md new file mode 100644 index 0000000..0156b8e --- /dev/null +++ b/docs/development/installing-formulus-dev.md @@ -0,0 +1,510 @@ +--- +sidebar_position: 2 +--- + +# Installing Formulus for Development + +Complete guide for developers to install Formulus on physical devices or emulators using ADB or development builds. + +## Overview + +Developers can install Formulus on Android devices or emulators using several methods: + +- **ADB Installation** - Install APK directly via Android Debug Bridge +- **Android Emulator** - Run and test on virtual devices +- **Development Build** - Build and run from source with hot reload + +This guide covers cross-platform commands for Linux, macOS, and Windows. + +## Prerequisites + +Before installing, ensure you have: + +| Requirement | Description | +|-------------|-------------| +| **Android SDK** | Android SDK Platform Tools (includes ADB) | +| **ADB** | Android Debug Bridge (part of SDK Platform Tools) | +| **USB Drivers** | Device-specific USB drivers (for physical devices) | +| **Developer Options** | Enabled on Android device (for physical devices) | +| **USB Debugging** | Enabled on Android device (for physical devices) | + +### Installing Android SDK Platform Tools + + + + +```bash +# Ubuntu/Debian +sudo apt-get update +sudo apt-get install android-tools-adb android-tools-fastboot + +# Fedora +sudo dnf install android-tools + +# Arch Linux +sudo pacman -S android-tools + +# Verify installation +adb version +``` + + + + +```bash +# Using Homebrew +brew install android-platform-tools + +# Verify installation +adb version +``` + + + + +1. **Download Android SDK Platform Tools** from [developer.android.com](https://developer.android.com/studio/releases/platform-tools) +2. **Extract the ZIP file** to a location like `C:\platform-tools` +3. **Add to PATH**: + - Open System Properties → Environment Variables + - Add `C:\platform-tools` to the PATH variable +4. **Verify installation**: + ```powershell + adb version + ``` + + + + +## Method 1: ADB Installation on Physical Device + +### Step 1: Enable Developer Options + +1. **Open Settings** on your Android device +2. **Navigate to About Phone** (or About Device) +3. **Find "Build Number"** (usually at the bottom) +4. **Tap "Build Number" 7 times** until you see "You are now a developer!" + +### Step 2: Enable USB Debugging + +1. **Go back to Settings** +2. **Open Developer Options** (now visible in Settings) +3. **Enable "USB Debugging"** +4. **Accept the warning** about USB debugging + +### Step 3: Connect Device + +1. **Connect your device** to your computer via USB cable +2. **On your device**, you may see a prompt: "Allow USB debugging?" +3. **Check "Always allow from this computer"** (optional but recommended) +4. **Tap "Allow"** + +### Step 4: Verify Device Connection + + + + +```bash +adb devices +``` + + + + +```powershell +adb devices +``` + + + + +**Expected output:** +``` +List of devices attached +ABC123XYZ456 device +``` + +If you see "unauthorized", check your device and accept the USB debugging prompt. + +### Step 5: Install Formulus APK + +#### Option A: Install from Local APK File + + + + +```bash +# Navigate to directory containing APK +cd /path/to/formulus/android/app/build/outputs/apk/debug + +# Install APK +adb install app-debug.apk +``` + + + + +```powershell +# Navigate to directory containing APK +cd C:\path\to\formulus\android\app\build\outputs\apk\debug + +# Install APK +adb install app-debug.apk +``` + + + + +#### Option B: Install from Remote URL + + + + +```bash +# Download and install in one command +curl -L https://github.com/OpenDataEnsemble/ode/releases/download/v1.0.0/formulus.apk -o /tmp/formulus.apk +adb install /tmp/formulus.apk +``` + + + + +```powershell +# Download and install +Invoke-WebRequest -Uri "https://github.com/OpenDataEnsemble/ode/releases/download/v1.0.0/formulus.apk" -OutFile "$env:TEMP\formulus.apk" +adb install "$env:TEMP\formulus.apk" +``` + + + + +### Step 6: Verify Installation + + + + +```bash +# Check if app is installed +adb shell pm list packages | grep formulus + +# Launch the app +adb shell am start -n com.opendataensemble.formulus/.MainActivity +``` + + + + +```powershell +# Check if app is installed +adb shell pm list packages | Select-String formulus + +# Launch the app +adb shell am start -n com.opendataensemble.formulus/.MainActivity +``` + + + + +## Method 2: Android Emulator Installation + +### Step 1: Set Up Android Emulator + +#### Using Android Studio + +1. **Install Android Studio** from [developer.android.com](https://developer.android.com/studio) +2. **Open Android Studio** → **Tools** → **Device Manager** +3. **Create Virtual Device** → Select a device (e.g., Pixel 5) +4. **Select System Image** (e.g., Android 11, API 30) +5. **Finish** and start the emulator + +#### Using Command Line (Linux/macOS) + +```bash +# Install emulator via SDK Manager +sdkmanager "emulator" "platform-tools" "platforms;android-30" + +# List available system images +sdkmanager --list | grep system-images + +# Create AVD (Android Virtual Device) +avdmanager create avd -n formulus_emulator -k "system-images;android-30;google_apis;x86_64" + +# Start emulator +emulator -avd formulus_emulator & +``` + +### Step 2: Verify Emulator Connection + + + + +```bash +# Wait for emulator to boot (may take 1-2 minutes) +adb devices + +# Should show emulator +List of devices attached +emulator-5554 device +``` + + + + +```powershell +adb devices +``` + + + + +### Step 3: Install Formulus on Emulator + + + + +```bash +# Build APK first (if not already built) +cd formulus +npm run android + +# Install on emulator +adb -s emulator-5554 install android/app/build/outputs/apk/debug/app-debug.apk +``` + + + + +```powershell +# Build APK first +cd formulus +npm run android + +# Install on emulator +adb -s emulator-5554 install android\app\build\outputs\apk\debug\app-debug.apk +``` + + + + +### Step 4: Important Notes for Emulator + +When configuring Formulus on an emulator to connect to a local server: + +- **Use `10.0.2.2` instead of `localhost`** - This is the special IP that the emulator uses to access the host machine +- **Example server URL**: `http://10.0.2.2:8080` +- **For network servers**: Use the actual IP address or domain name + +## Method 3: Development Build with Hot Reload + +### Prerequisites + +- **Node.js** 18+ and npm +- **React Native CLI** or **Expo CLI** +- **Java Development Kit (JDK)** 11 or higher +- **Android Studio** with Android SDK + +### Step 1: Install Dependencies + + + + +```bash +cd formulus +npm install +``` + + + + +### Step 2: Start Metro Bundler + + + + +```bash +# In formulus directory +npm start +``` + +Keep this terminal open. Metro is the JavaScript bundler for React Native. + + + + +### Step 3: Build and Run on Device/Emulator + + + + +```bash +# For Android device (connected via USB) +npm run android + +# For Android emulator (must be running) +npm run android +``` + + + + +This command will: +1. Build the Android app +2. Install it on your device/emulator +3. Start the app +4. Connect to Metro bundler for hot reload + +### Step 4: Development Features + +With development build, you get: + +- **Hot Reload** - Changes reflect immediately +- **Fast Refresh** - React components update without losing state +- **Debug Menu** - Shake device or press `Ctrl+M` (Windows/Linux) or `Cmd+M` (macOS) +- **React Native Debugger** - Debug JavaScript code in Chrome DevTools + +## Common ADB Commands + +### Useful Commands for Development + +**List connected devices:** +```bash +adb devices +``` + +**Install APK:** +```bash +adb install path/to/app.apk +``` + +**Uninstall app:** +```bash +adb uninstall com.opendataensemble.formulus +``` + +**Reinstall app (uninstall + install):** +```bash +adb install -r path/to/app.apk +``` + +**View app logs:** +```bash +# All logs +adb logcat + +# Filter for Formulus only +adb logcat | grep -i formulus + +# Clear logs +adb logcat -c +``` + +**Pull file from device:** +```bash +adb pull /path/on/device /path/on/computer +``` + +**Push file to device:** +```bash +adb push /path/on/computer /path/on/device +``` + +**Open app:** +```bash +adb shell am start -n com.opendataensemble.formulus/.MainActivity +``` + +**Stop app:** +```bash +adb shell am force-stop com.opendataensemble.formulus +``` + +**Clear app data:** +```bash +adb shell pm clear com.opendataensemble.formulus +``` + +## Troubleshooting + +### Device Not Detected + +**Problem**: `adb devices` shows no devices. + +**Solutions**: +- Check USB cable connection +- Try a different USB port +- Install device-specific USB drivers (Windows) +- Enable USB debugging on device +- Accept USB debugging prompt on device +- Restart ADB server: `adb kill-server && adb start-server` + +### Installation Fails + +**Problem**: `adb install` fails with error. + +**Solutions**: +- Uninstall existing version first: `adb uninstall com.opendataensemble.formulus` +- Use `-r` flag to reinstall: `adb install -r app.apk` +- Check device has enough storage +- Verify APK is not corrupted +- Check device is in file transfer mode (not charging only) + +### Emulator Connection Issues + +**Problem**: Cannot connect to local server from emulator. + +**Solutions**: +- Use `10.0.2.2` instead of `localhost` for server URL +- Check firewall isn't blocking connections +- Verify server is running and accessible +- Use actual IP address instead of localhost + +### Permission Denied Errors + +**Problem**: ADB commands fail with permission errors. + + + + +```bash +# Add user to plugdev group +sudo usermod -a -G plugdev $USER + +# Create udev rules +sudo nano /etc/udev/rules.d/51-android.rules +# Add: SUBSYSTEM=="usb", ATTR{idVendor}=="####", MODE="0664", GROUP="plugdev" + +# Reload udev rules +sudo udevadm control --reload-rules +sudo udevadm trigger + +# Log out and log back in +``` + + + + +macOS typically doesn't require special permissions for ADB. If you encounter permission issues: + +1. Check USB cable connection +2. Try a different USB port +3. Restart ADB: `adb kill-server && adb start-server` +4. Check System Preferences → Security & Privacy for blocked apps + + + + +Windows typically handles USB device permissions automatically. If you encounter issues: + +1. Install device-specific USB drivers from manufacturer +2. Check Device Manager for unrecognized devices +3. Try different USB port or cable +4. Restart ADB: `adb kill-server && adb start-server` + + + + +## Related Documentation + +- [Formulus Development Setup](/development/formulus-development) - Complete development environment setup +- [Formulus Component Reference](/reference/formulus) - Detailed component documentation +- [Building and Testing](/development/building-testing) - Build and test procedures + diff --git a/docs/development/setup.md b/docs/development/setup.md new file mode 100644 index 0000000..1a7c1fc --- /dev/null +++ b/docs/development/setup.md @@ -0,0 +1,306 @@ +--- +sidebar_position: 1 +--- + +# Development Setup + +Complete guide to setting up a development environment for ODE. + +## Prerequisites + +Before setting up the development environment, ensure you have: + +- **Node.js** 18.0 or higher +- **npm** 9.0 or higher +- **Go** 1.22 or higher (for server and CLI development) +- **PostgreSQL** 13.0 or higher (for server development) +- **Git** for version control +- **Docker** and **Docker Compose** (optional, for containerized development) + +## Repository Structure + +ODE is a monorepo containing multiple components: + +``` +ode/ +├── formulus/ # React Native mobile app +├── formulus-formplayer/ # React web form renderer +├── synkronus/ # Go backend server +├── synkronus-cli/ # Go command-line utility +├── synkronus-portal/ # React web portal +└── packages/ + └── tokens/ # Design tokens package +``` + +## Clone the Repository + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode +``` + +## Formulus Development + +### Setup + +```bash +cd formulus +npm install +``` + +### Running + +```bash +# Start Metro bundler +npm start + +# Run on Android +npm run android + +# Run on iOS (macOS only) +npm run ios +``` + +### Code Quality + +ODE enforces consistent formatting and linting: + +```bash +# Run linting +npm run lint + +# Run linting with auto-fix +npm run lint:fix + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check +``` + +### Generating Files + +```bash +# Generate WebView injection script +npm run generate + +# Generate API client from OpenAPI spec +npm run generate:api +``` + +## Formplayer Development + +### Setup + +```bash +cd formulus-formplayer +npm install +``` + +### Running + +```bash +# Development server +npm start + +# Build for React Native +npm run build:rn +``` + +### Code Quality + +```bash +# Run linting +npm run lint + +# Run linting with auto-fix +npm run lint:fix + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check +``` + +## Synkronus Development + +### Setup + +```bash +cd synkronus +go mod download +``` + +### Configuration + +Create a `.env` file: + +```bash +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-for-development +LOG_LEVEL=debug +APP_BUNDLE_PATH=./data/app-bundles +``` + +### Running + +```bash +# Build +go build -o bin/synkronus cmd/synkronus/main.go + +# Run +./bin/synkronus + +# Or run directly +go run cmd/synkronus/main.go +``` + +### Database Setup + +Ensure PostgreSQL is running and create a database: + +```sql +CREATE DATABASE synkronus; +``` + +The schema will be created automatically on first run. + +## Synkronus CLI Development + +### Setup + +```bash +cd synkronus-cli +go mod download +``` + +### Building + +```bash +# Build +go build -o bin/synk ./cmd/synkronus + +# Run +./bin/synk +``` + +## Development Workflow + +### 1. Create a Feature Branch + +```bash +git checkout -b feature/your-feature-name +``` + +### 2. Make Changes + +Make your code changes following the coding standards. + +### 3. Test Locally + +```bash +# Run tests +npm test # For frontend projects +go test ./... # For Go projects + +# Check code quality +npm run lint +npm run format:check +``` + +### 4. Commit Changes + +```bash +git add . +git commit -m "Description of changes" +``` + +### 5. Push and Create Pull Request + +```bash +git push origin feature/your-feature-name +``` + +Create a pull request on GitHub. + +## Code Quality Standards + +### Frontend (React/React Native) + +- **Linting**: ESLint with project-specific rules +- **Formatting**: Prettier with consistent configuration +- **TypeScript**: Strict type checking enabled +- **Testing**: Jest for unit tests + +### Backend (Go) + +- **Formatting**: `gofmt` or `goimports` +- **Linting**: `golangci-lint` (if configured) +- **Testing**: Standard Go testing package +- **Documentation**: Godoc comments for exported functions + +### CI/CD + +The CI pipeline automatically: + +- Runs linting and formatting checks +- Runs tests +- Builds components +- Publishes Docker images (for synkronus) + +## Development Tools + +### Recommended IDE Setup + +- **VS Code**: With extensions for TypeScript, Go, and React +- **IntelliJ IDEA**: With Go and JavaScript plugins +- **Android Studio**: For Android development +- **Xcode**: For iOS development (macOS only) + +### Useful Commands + +```bash +# Check all components +cd formulus && npm run lint && cd .. +cd formulus-formplayer && npm run lint && cd .. +cd synkronus && go test ./... && cd .. + +# Format all code +cd formulus && npm run format && cd .. +cd formulus-formplayer && npm run format && cd .. +cd synkronus && go fmt ./... && cd .. +``` + +## Troubleshooting + +### Node Modules Issues + +```bash +# Clear and reinstall +rm -rf node_modules package-lock.json +npm install +``` + +### Go Module Issues + +```bash +# Clean module cache +go clean -modcache +go mod download +``` + +### Database Connection Issues + +- Verify PostgreSQL is running +- Check connection string format +- Ensure database exists +- Verify user permissions + +## Related Documentation + +- [Architecture Overview](/development/architecture) +- [Contributing Guide](/development/contributing) +- [Building & Testing](/development/building-testing) diff --git a/docs/development/synkronus-development.md b/docs/development/synkronus-development.md new file mode 100644 index 0000000..ce542d4 --- /dev/null +++ b/docs/development/synkronus-development.md @@ -0,0 +1,183 @@ +--- +sidebar_position: 5 +--- + +# Synkronus Server Development + +Complete guide for developing the Synkronus server component. + +## Prerequisites + +- **Go** 1.22+ +- **PostgreSQL** 12+ +- **Git** + +## Local Development Setup + +### Step 1: Clone Repository + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus +``` + +### Step 2: Install Dependencies + +```bash +go mod download +``` + +### Step 3: Set Up Database + + + + +```bash +# Create database +createdb synkronus + +# Or using psql +psql -U postgres -c "CREATE DATABASE synkronus;" +``` + + + + +Using PowerShell or Command Prompt: + +```powershell +# Using psql +psql -U postgres -c "CREATE DATABASE synkronus;" +``` + +Or using Git Bash/WSL: + +```bash +# Create database +createdb synkronus + +# Or using psql +psql -U postgres -c "CREATE DATABASE synkronus;" +``` + + + + +### Step 4: Configure Environment + +Create `.env` file: + +```bash +PORT=8080 +DB_CONNECTION=postgres://user:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=dev-secret-change-in-production +LOG_LEVEL=debug +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin +``` + +### Step 5: Create Directories + +```bash +mkdir -p data/app-bundles +``` + +### Step 6: Run Server + +```bash +go run cmd/synkronus/main.go +``` + +Or build and run: + +```bash +go build -o bin/synkronus cmd/synkronus/main.go +./bin/synkronus +``` + +## Development Workflow + +### Hot Reload + +Use tools like `air` for hot reload: + +```bash +go install github.com/cosmtrek/air@latest +air +``` + +### Testing + +```bash +go test ./... +``` + +### API Documentation + +View OpenAPI docs: + +```bash +# Server must be running +open http://localhost:8080/openapi/swagger-ui.html +``` + +## Building for Production + +### Build Binary + +```bash +go build -o bin/synkronus cmd/synkronus/main.go +``` + +### Cross-Platform Builds + + + + +```bash +GOOS=linux GOARCH=amd64 go build -o bin/synkronus-linux cmd/synkronus/main.go +``` + + + + +```powershell +$env:GOOS="windows"; $env:GOARCH="amd64"; go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + +Or using bash (Git Bash/WSL): + +```bash +GOOS=windows GOARCH=amd64 go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + + + + +```bash +GOOS=darwin GOARCH=amd64 go build -o bin/synkronus-macos cmd/synkronus/main.go +``` + + + + +## Docker Development + +### Using Docker Compose + +```bash +docker compose up -d +``` + +### Development with Hot Reload + +Mount source code for live updates. + +## Related Documentation + +- [Synkronus Server Reference](/reference/synkronus-server) - Component reference +- [Deployment Guide](/guides/deployment) - Production deployment +- [Configuration Guide](/guides/configuration) - Configuration options + diff --git a/docs/development/synkronus-portal-development.md b/docs/development/synkronus-portal-development.md new file mode 100644 index 0000000..fbfe969 --- /dev/null +++ b/docs/development/synkronus-portal-development.md @@ -0,0 +1,100 @@ +--- +sidebar_position: 6 +--- + +# Synkronus Portal Development + +Complete guide for developing the Synkronus Portal web interface. + +## Prerequisites + +- **Node.js** 20+ and npm +- **Go** 1.22+ (for backend) +- **PostgreSQL** 17+ (for backend) + +## Local Development Setup + + + + +#### Step 1: Set Up Backend + +See [Synkronus Development](/development/synkronus-development) for backend setup. + +#### Step 2: Set Up Frontend + +```bash +cd synkronus-portal +npm install +``` + +#### Step 3: Start Development Server + +```bash +npm run dev +``` + +Portal available at http://localhost:5174 + + + + +#### Start Backend Services + +```bash +docker compose up -d postgres synkronus +``` + +#### Start Frontend + +```bash +cd synkronus-portal +npm install +npm run dev +``` + +Portal available at http://localhost:5174 + + + + +## Development Features + +- **Hot Module Replacement**: Instant code updates +- **Fast Refresh**: React components update without losing state +- **Source Maps**: Debug in browser DevTools +- **Error Overlay**: Errors shown in browser + +## Building for Production + +### Build + +```bash +npm run build +``` + +Output in `dist/` directory. + +### Docker Production Build + +```bash +docker compose up -d --build +``` + +## Project Structure + +- `src/`: React source code +- `src/components/`: Reusable components +- `src/pages/`: Page components +- `src/services/`: API service +- `src/contexts/`: React contexts + +## Adding Features + +See [Synkronus Portal Reference](/reference/synkronus-portal) for detailed patterns. + +## Related Documentation + +- [Synkronus Portal Reference](/reference/synkronus-portal) - Component reference +- [Deployment Guide](/guides/deployment) - Production deployment + diff --git a/docs/getting-started/.gitkeep b/docs/getting-started/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md new file mode 100644 index 0000000..36a25c1 --- /dev/null +++ b/docs/getting-started/faq.md @@ -0,0 +1,110 @@ +--- +sidebar_position: 5 +--- + +# Frequently Asked Questions + +Common questions about ODE installation, usage, and development. + +## General Questions + +### What is ODE? + +Open Data Ensemble (ODE) is a platform for mobile data collection and synchronization. It provides tools for creating forms, collecting data offline, and synchronizing data across devices and servers. + +### Is ODE free to use? + +Yes, ODE is open source and free to use. The code is available under the MIT license. + +### What platforms does ODE support? + +ODE supports Android and iOS mobile devices. The server component runs on Linux, macOS, and Windows. + +### Do I need internet connectivity to use ODE? + +No, ODE is designed to work offline. Data is stored locally and synchronized when connectivity is available. + +## Installation Questions + +### What are the system requirements? + +See the [Prerequisites](/getting-started/installation/prerequisites) page for detailed system requirements. + +### Can I run ODE in the cloud? + +Yes, ODE can be deployed to cloud platforms such as AWS, Google Cloud, or Azure. See the [Deployment guide](/guides/deployment/production) for details. + +### Do I need a database? + +Yes, ODE requires PostgreSQL for data storage. The database schema is created automatically on first run. + +## Usage Questions + +### How do I create forms? + +Forms are defined using JSON schema. See the [Form Design guide](/guides/forms/overview) for details. + +### Can I customize the user interface? + +Yes, ODE supports custom applications and renderers. See [Custom Applications](/guides/custom-apps/overview) for details. + +### How does synchronization work? + +ODE uses a bidirectional sync protocol that pushes local data to the server and pulls new data from the server. See [Synchronization](/using/synchronization) for details. + +### What happens if two devices modify the same data? + +ODE automatically resolves conflicts using a version-based approach. The most recent version takes precedence. + +## Development Questions + +### How do I contribute to ODE? + +See the [Contributing guide](/development/contributing/guide) for information on how to contribute. + +### Can I extend ODE functionality? + +Yes, ODE is designed to be extensible. See [Extending ODE](/development/extending/overview) for details. + +### What programming languages are used? + +ODE uses React Native for mobile apps, React for web components, and Go for the server. + +## Troubleshooting + +### The app won't connect to the server + +- Verify the server is running and accessible +- Check the server URL in app settings +- Verify firewall and network settings +- For Android emulator, use `10.0.2.2` instead of `localhost` + +### Forms are not appearing + +- Verify forms were uploaded to the server +- Check that the app has synchronized +- Review server logs for errors + +### Data is not synchronizing + +- Check network connectivity +- Verify authentication credentials +- Review server logs for sync errors +- Ensure observations were saved locally + +### Build errors + +- Ensure all prerequisites are installed +- Check that dependencies are up to date +- Review error messages for specific issues +- See component-specific documentation for build instructions + +## Getting Help + +If you cannot find an answer to your question: + +- Check the [Troubleshooting guide](/using/troubleshooting) +- Review the [API Reference](/reference/api/overview) +- Search [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- Ask questions in the [Community section](/community/getting-help) + diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 0000000..329389c --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,85 @@ +--- +sidebar_position: 0 +--- + +# Getting Started + +Welcome to ODE! This section will help you understand what ODE is, why you should use it, and how to get up and running quickly. + +## What You'll Learn + +Whether you're a researcher, developer, or data practitioner, these guides will help you get started with ODE and start collecting data efficiently. + +
+
+
+
+

What is ODE?

+
+
+

Learn about the Open Data Ensemble platform and its architecture.

+ Get Started → +
+
+
+
+
+
+

Why ODE?

+
+
+

Discover the benefits and advantages of using ODE.

+ Learn More → +
+
+
+
+
+
+

Key Concepts

+
+
+

Understand fundamental concepts and terminology.

+ Read Guide → +
+
+
+
+
+
+

Installation

+
+
+

Step-by-step instructions to install ODE components.

+ Install Now → +
+
+
+
+
+
+

Quick Start

+
+
+

Get up and running quickly with a simple example.

+ Start Here → +
+
+
+
+
+
+

FAQ

+
+
+

Find answers to frequently asked questions.

+ View FAQ → +
+
+
+
+ +## Next Steps + +Once you've completed the getting started guides, explore the [Using ODE](/docs/using/your-first-form) section to learn how to create forms and manage data. + diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..197d762 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,420 @@ +--- +sidebar_position: 4 +--- + +# Installation + +Complete installation guide for all ODE components. This guide covers prerequisites, server setup, and mobile app installation. + +## Prerequisites + +Before installing ODE components, ensure your system meets the following requirements. + +### System Requirements + +#### For Mobile Development (Formulus) + +| Requirement | Minimum | Recommended | +|-------------|---------|-------------| +| **Operating System** | macOS 10.15, Windows 10, or Linux | Latest stable version | +| **Node.js** | 18.0 or higher | 20.0 or higher | +| **npm** | 9.0 or higher | 10.0 or higher | +| **Android Studio** | Latest stable | Latest stable | +| **Xcode** | 14.0 or higher (macOS only) | Latest stable | +| **Java Development Kit** | JDK 17 | JDK 17 or higher | + +#### For Server Deployment (Synkronus) + +| Requirement | Minimum | Recommended | +|-------------|---------|-------------| +| **Operating System** | Linux, macOS, or Windows | Linux | +| **Go** | 1.22 or higher | Latest stable | +| **PostgreSQL** | 13.0 or higher | 15.0 or higher | +| **Docker** | 20.10 or higher (optional) | Latest stable | +| **Memory** | 2 GB RAM | 4 GB RAM or more | +| **Storage** | 10 GB free space | 50 GB or more | + +### Development Tools + +**Required Tools:** +- Git: Version control system +- Code Editor: Visual Studio Code, IntelliJ IDEA, or similar +- Terminal: Command-line interface for running commands + +**Recommended Tools:** +- Postman or curl: For testing API endpoints +- pgAdmin or DBeaver: For database management +- Docker Desktop: For containerized development + +### Verification + +Verify your installation by running: + + + + +```bash +# Check Node.js version +node --version + +# Check npm version +npm --version + +# Check Go version +go version + +# Check PostgreSQL version +psql --version + +# Check Docker version (if using) +docker --version +``` + + + + +Using PowerShell: + +```powershell +# Check Node.js version +node --version + +# Check npm version +npm --version + +# Check Go version +go version + +# Check PostgreSQL version +psql --version + +# Check Docker version (if using) +docker --version +``` + +Or using Git Bash/WSL (same commands as Linux/macOS): + +```bash +node --version +npm --version +go version +psql --version +docker --version +``` + + + + +## Installing Synkronus Server + +Synkronus is the backend server component of ODE, responsible for data synchronization, storage, and API services. + + + + +The easiest way to run Synkronus is using Docker: + +```bash +# Pull the latest image +docker pull ghcr.io/opendataensemble/synkronus:latest + +# Run the container +docker run -d \ + --name synkronus \ + -p 8080:8080 \ + -e DB_CONNECTION="postgres://user:password@host:5432/synkronus" \ + -e JWT_SECRET="your-secret-key-here" \ + -v synkronus-bundles:/app/data/app-bundles \ + ghcr.io/opendataensemble/synkronus:latest +``` + + + + +For a complete setup with PostgreSQL: + +```bash +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Copy the example configuration +cp docker-compose.example.yml docker-compose.yml + +# Edit docker-compose.yml with your configuration +# Then start the services +docker compose up -d +``` + + + + +Build and run Synkronus from source: + + + + +```bash +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Build the application +go build -o bin/synkronus cmd/synkronus/main.go + +# Run the application +./bin/synkronus +``` + + + + +Using PowerShell: + +```powershell +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Build the application +go build -o bin/synkronus.exe cmd/synkronus/main.go + +# Run the application +.\bin\synkronus.exe +``` + +Or using Git Bash/WSL (same as Linux/macOS): + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus +go build -o bin/synkronus cmd/synkronus/main.go +./bin/synkronus +``` + + + + + + + +### Configuration + +Synkronus is configured using environment variables. Create a `.env` file or set environment variables: + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | HTTP server port | `8080` | No | +| `DB_CONNECTION` | PostgreSQL connection string | - | Yes | +| `JWT_SECRET` | Secret key for JWT signing | - | Yes | +| `LOG_LEVEL` | Logging level (debug, info, warn, error) | `info` | No | +| `APP_BUNDLE_PATH` | Directory for app bundles | `./data/app-bundles` | No | +| `MAX_VERSIONS_KEPT` | Maximum app bundle versions to keep | `5` | No | + +**Example Configuration:** + +```bash +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=info +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +``` + +### Database Setup + +Before running Synkronus, set up a PostgreSQL database: + + + + +```bash +# Connect to PostgreSQL +psql -U postgres +``` + +Then run SQL commands: + +```sql +-- Create database +CREATE DATABASE synkronus; + +-- Create user (optional) +CREATE USER synkronus WITH PASSWORD 'your-password'; +GRANT ALL PRIVILEGES ON DATABASE synkronus TO synkronus; +``` + + + + +Using PowerShell or Command Prompt: + +```powershell +# Connect to PostgreSQL +psql -U postgres +``` + +Then run SQL commands: + +```sql +-- Create database +CREATE DATABASE synkronus; + +-- Create user (optional) +CREATE USER synkronus WITH PASSWORD 'your-password'; +GRANT ALL PRIVILEGES ON DATABASE synkronus TO synkronus; +``` + +Or using Git Bash/WSL (same as Linux/macOS): + +```bash +psql -U postgres +``` + + + + +```bash +# Connect to PostgreSQL container +docker compose exec postgres psql -U postgres +``` + +Then run SQL commands: + +```sql +CREATE DATABASE synkronus; +CREATE USER synkronus WITH PASSWORD 'your-password'; +GRANT ALL PRIVILEGES ON DATABASE synkronus TO synkronus; +``` + + + + +The database schema will be created automatically on first run. + +### Verification + +Verify the installation: + +1. Check that the server is running: + ```bash + curl http://localhost:8080/health + ``` + +2. Check the API documentation: + ```bash + curl http://localhost:8080/api/docs + ``` + +## Installing Formulus App + +Formulus is the mobile application component of ODE, available for Android and iOS devices. + +For detailed installation instructions, see the [Installing Formulus guide](/getting-started/installing-formulus) which covers: + +- F-Droid installation (recommended for end users) +- Direct APK installation +- System requirements +- Post-installation setup + +For developers who want to install via ADB or emulator, see the [Development Installation guide](/development/installing-formulus-dev). + +### Quick Installation Summary + +#### For End Users + +1. **F-Droid** (Recommended): Install via F-Droid app store +2. **Direct APK**: Download and install APK file directly + +See [Installing Formulus](/getting-started/installing-formulus) for complete instructions. + +#### For Developers + +1. **ADB Installation**: Install via Android Debug Bridge +2. **Emulator**: Run on Android emulator +3. **Development Build**: Build from source with hot reload + +See [Installing Formulus for Development](/development/installing-formulus-dev) for complete instructions. + +### App Configuration + +After installation, configure the app to connect to your Synkronus server: + +1. Open the Formulus app +2. Navigate to Settings +3. Enter your server URL (e.g., `https://your-server.com`) +4. Enter your authentication credentials +5. Save the configuration + +## Installing Synkronus CLI + +The Synkronus CLI is a command-line utility for interacting with the Synkronus server. + +### Installation + +```bash +go install github.com/OpenDataEnsemble/ode/synkronus-cli/cmd/synkronus@latest +``` + +Or build from source: + +```bash +git clone https://github.com/OpenDataEnsemble/ode/synkronus-cli.git +cd synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +### Configuration + +By default, the CLI uses a configuration file located at `$HOME/.synkronus.yaml`. + +**Example configuration file:** + +```yaml +api: + url: http://localhost:8080 + version: 1.0.0 +``` + +You can override this per-command with `--config ` or use `synk config use ` to set a persistent default. + +## Troubleshooting + +### Server Not Accessible + +If the mobile app cannot connect to the server: + +- Verify the server is running: `curl http://localhost:8080/health` +- Check firewall settings +- For Android emulator, use `10.0.2.2` instead of `localhost` +- For iOS simulator, use `localhost` or your machine's IP address + +### Database Connection Issues + +**Problem**: Cannot connect to database + +**Solution**: Verify the `DB_CONNECTION` string format and ensure PostgreSQL is running and accessible. + +### Port Already in Use + +**Problem**: Port 8080 is already in use + +**Solution**: Change the `PORT` environment variable to use a different port. + +### Build Errors + +**Problem**: Build fails with errors + +**Solution**: +- Ensure all prerequisites are installed +- Check that dependencies are up to date +- Review error messages for specific issues +- See component-specific documentation for build instructions + +## Next Steps + +- Follow the [Quick Start guide](/getting-started/quick-start) for a complete setup +- Learn how to [use the app](/using/your-first-form) for data collection +- Review the [Deployment guide](/guides/deployment) for production setup + diff --git a/docs/getting-started/installation/.gitkeep b/docs/getting-started/installation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/getting-started/installing-formulus.md b/docs/getting-started/installing-formulus.md new file mode 100644 index 0000000..29d0297 --- /dev/null +++ b/docs/getting-started/installing-formulus.md @@ -0,0 +1,232 @@ +--- +sidebar_position: 4 +--- + +# Installing Formulus App + +Complete guide for installing the Formulus mobile application on Android devices. + +## Overview + +Formulus is available for Android devices through multiple installation methods. Choose the method that best fits your needs: + +- **F-Droid** (Recommended for end users) - Official app store for free and open source Android apps +- **Direct APK** - Download and install the APK file directly +- **Development Build** - For developers who want to build from source + +## System Requirements + +Before installing, ensure your device meets these requirements: + +| Requirement | Minimum | +|-------------|---------| +| **Android Version** | Android 7.0 (API level 24) or higher | +| **Storage Space** | 50 MB free space | +| **Internet Connection** | Required for initial setup and synchronization | +| **Permissions** | Camera, Storage, Location (for form features) | + +## Installation Methods + +### Method 1: F-Droid (Recommended) + +F-Droid is the recommended installation method for end users. It provides automatic updates and ensures you're installing the official version. + +#### Step 1: Install F-Droid + +If you don't have F-Droid installed: + +1. **Download F-Droid** from [f-droid.org](https://f-droid.org/) +2. **Enable installation from unknown sources**: + - Go to Settings → Security → Unknown Sources + - Enable the option for your browser or file manager +3. **Install F-Droid** by opening the downloaded APK file + +#### Step 2: Add Formulus Repository + +1. **Open F-Droid** on your device +2. **Navigate to Settings** → **Repositories** +3. **Tap the "+" button** to add a new repository +4. **Enter the Formulus repository URL** (provided by your organization) +5. **Tap "Add"** to save the repository + +#### Step 3: Install Formulus + +1. **Open F-Droid** on your device +2. **Navigate to the Updates tab** or search for "Formulus" +3. **Find Formulus** in the app list +4. **Tap on Formulus** to open the app details +5. **Tap "Install"** to begin installation +6. **Wait for installation** to complete +7. **Tap "Open"** to launch the app + +#### Automatic Updates + +Once installed via F-Droid, Formulus will automatically update when new versions are available: + +1. **F-Droid checks for updates** periodically +2. **Notifications appear** when updates are available +3. **Tap the notification** or open F-Droid to update +4. **Updates install automatically** through F-Droid + +### Method 2: Direct APK Installation + +If F-Droid is not available or you prefer direct installation: + +#### Step 1: Download the APK + +1. **Download the latest APK** from the [releases page](https://github.com/OpenDataEnsemble/ode/releases) +2. **Save the file** to your device's Downloads folder + +#### Step 2: Enable Unknown Sources + +1. **Go to Settings** → **Security** (or **Apps** on newer Android versions) +2. **Enable "Install unknown apps"** or **"Unknown Sources"** +3. **Select your browser or file manager** and enable installation + +#### Step 3: Install the APK + +1. **Open your file manager** or Downloads app +2. **Navigate to the Downloads folder** +3. **Tap on the Formulus APK file** +4. **Review the permissions** requested by the app +5. **Tap "Install"** to begin installation +6. **Wait for installation** to complete +7. **Tap "Open"** to launch the app + +### Method 3: Development Build + +For developers who want to build and install from source, see the [Development Installation Guide](/development/formulus-development). + +## Post-Installation Setup + +After installing Formulus, you need to configure it to connect to your Synkronus server: + +### Initial Configuration + +1. **Open Formulus** on your device +2. **You'll see the welcome screen** with configuration options +3. **Choose your configuration method**: + - **QR Code Scan** (Recommended) - Scan a QR code with server details + - **Manual Entry** - Enter server URL and credentials manually + +### QR Code Configuration + +1. **Tap "Scan QR Code"** on the welcome screen +2. **Grant camera permission** if prompted +3. **Point the camera** at the QR code provided by your administrator +4. **Settings auto-populate** with server URL, username, and password +5. **Tap "Connect"** to verify and save the configuration + +### Manual Configuration + +1. **Tap "Manual Configuration"** on the welcome screen +2. **Enter Server URL**: `http://your-server-ip:8080` or `https://your-server-domain` +3. **Enter Username**: Your username provided by your administrator +4. **Enter Password**: Your password +5. **Tap "Test Connection"** to verify connectivity +6. **Tap "Save"** to store the configuration + +### First Login + +1. **After configuration**, you'll be prompted to log in +2. **Credentials should be pre-filled** (if using QR code) +3. **Tap "Login"** to authenticate +4. **Wait for authentication** - A token is stored locally for future sessions +5. **You'll be redirected** to the main app interface + +## Verification + +To verify that Formulus is installed correctly: + +1. **Check app icon** appears in your app drawer +2. **Open the app** and verify it launches without errors +3. **Check Settings** to confirm server configuration is saved +4. **Test connection** by tapping "Test Connection" in Settings +5. **Verify login** by logging in with your credentials + +## Troubleshooting Installation + +### Installation Fails + +**Problem**: APK installation fails with "App not installed" error. + +**Solutions**: +- Ensure you have enough storage space (at least 50 MB free) +- Check that "Unknown Sources" is enabled for your file manager +- Try downloading the APK again (file may be corrupted) +- Ensure your device meets minimum Android version requirements (7.0+) + +### App Crashes on Launch + +**Problem**: Formulus crashes immediately after opening. + +**Solutions**: +- Restart your device +- Clear app data: Settings → Apps → Formulus → Storage → Clear Data +- Uninstall and reinstall the app +- Check that your device has sufficient RAM available + +### Cannot Connect to Server + +**Problem**: App cannot connect to the Synkronus server. + +**Solutions**: +- Verify server URL is correct (check for typos) +- Ensure device has internet connection +- Check that server is running and accessible +- Verify firewall settings aren't blocking the connection +- For local development, use `10.0.2.2` instead of `localhost` on Android emulator + +### F-Droid Not Finding Updates + +**Problem**: F-Droid doesn't show Formulus updates. + +**Solutions**: +- Ensure the Formulus repository is added correctly +- Refresh F-Droid repositories: Settings → Repositories → Tap refresh +- Check that F-Droid has internet connection +- Verify repository URL is correct and accessible + +## Updating Formulus + +### Via F-Droid + +Updates are automatic when using F-Droid: + +1. **F-Droid checks for updates** automatically +2. **Notification appears** when updates are available +3. **Open F-Droid** and navigate to Updates tab +4. **Tap "Update"** next to Formulus +5. **Wait for download and installation** + +### Via Direct APK + +1. **Download the latest APK** from the releases page +2. **Install over existing installation** (no need to uninstall) +3. **App data is preserved** during update + +## Uninstalling Formulus + +To uninstall Formulus: + +1. **Go to Settings** → **Apps** (or **Application Manager**) +2. **Find Formulus** in the app list +3. **Tap on Formulus** +4. **Tap "Uninstall"** +5. **Confirm uninstallation** + +**Note**: Uninstalling will remove all local data, including: +- Saved observations (not yet synced) +- App configuration +- Cached app bundles +- Local database + +**Important**: Ensure all data is synced to the server before uninstalling. + +## Related Documentation + +- [Formulus Features](/using/formulus-features) - Learn about app features and usage +- [Your First Form](/using/your-first-form) - Get started with data collection +- [Synchronization](/using/synchronization) - Understand how data syncs work +- [Development Installation](/development/formulus-development) - For developers building from source + diff --git a/docs/getting-started/key-concepts.md b/docs/getting-started/key-concepts.md new file mode 100644 index 0000000..4a96564 --- /dev/null +++ b/docs/getting-started/key-concepts.md @@ -0,0 +1,102 @@ +--- +sidebar_position: 3 +--- + +# Key Concepts + +Understanding these core concepts will help you work effectively with ODE. + +## Core Concepts + +### Forms + +Forms are the primary mechanism for data collection in ODE. A form consists of: + +- **Schema**: Defines the data structure and validation rules +- **UI Schema**: Defines how the form is presented to users +- **Question Types**: Define the input methods available (text, number, date, etc.) + +Forms are defined using JSON and follow the JSON Forms specification. See the [Form Design guide](/guides/forms/overview) for details. + +### Observations + +An observation is a single data record collected through a form. Each observation contains: + +- A unique identifier +- The form type used to collect it +- The data values entered by the user +- Metadata such as creation time and last modification time +- A sync status indicating whether it has been synchronized with the server + +### Synchronization + +Synchronization is the process of exchanging data between mobile devices and the server. ODE uses a bidirectional sync protocol that: + +- Pushes local observations to the server +- Pulls new or updated observations from the server +- Resolves conflicts when the same observation is modified on multiple devices +- Handles attachments separately from observation metadata + +See [Synchronization](/using/synchronization) for more details. + +### App Bundles + +An app bundle is a collection of resources that define a custom application. It includes: + +- Custom HTML, CSS, and JavaScript files +- Form specifications +- Custom renderers for question types +- Configuration files + +App bundles are uploaded to the server and downloaded by mobile devices during synchronization. See [Custom Applications](/guides/custom-apps/overview) for details. + +### Custom Applications + +Custom applications are web-based interfaces that run within the Formulus mobile app. They provide: + +- Custom navigation and user interfaces +- Integration with the ODE form system +- Access to observation data through the Formulus JavaScript interface + +Custom applications are defined in app bundles and can be tailored to specific use cases. + +## Data Flow + +The following diagram illustrates how data flows through the ODE system: + +``` +User Input → Form → Observation (Local) → Sync → Server → Database + ↓ + Sync → Other Devices +``` + +1. User fills out a form on a mobile device +2. An observation is created and stored locally +3. When connectivity is available, the observation is synchronized to the server +4. The server stores the observation in the database +5. Other devices can pull the observation during their sync operations + +## Terminology + +| Term | Definition | +|------|------------| +| **Form** | A data collection interface defined by schema and UI schema | +| **Observation** | A single data record collected through a form | +| **Schema** | JSON schema defining the structure and validation rules for a form | +| **UI Schema** | JSON schema defining the presentation of form fields | +| **Question Type** | A component that handles a specific type of input (text, number, etc.) | +| **Renderer** | A component that renders a question type in the form | +| **Sync** | The process of exchanging data between devices and server | +| **App Bundle** | A collection of resources defining a custom application | +| **Custom App** | A web-based application that runs within Formulus | +| **Formulus** | The mobile application component of ODE | +| **Synkronus** | The server component of ODE | +| **Formplayer** | The web-based form rendering component | + +## Related Documentation + +- [Form Design Guide](/guides/forms/overview) +- [Synchronization Details](/using/synchronization) +- [Custom Applications](/guides/custom-apps/overview) +- [Architecture Overview](/development/architecture/overview) + diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md new file mode 100644 index 0000000..ede1381 --- /dev/null +++ b/docs/getting-started/quick-start.md @@ -0,0 +1,195 @@ +--- +sidebar_position: 5 +--- + +# Quick Start + +Get up and running with ODE in approximately 15 minutes. + +## Overview + +This guide walks you through setting up a complete ODE environment and creating your first form. + +## Step 1: Start Synkronus Server + +Using Docker Compose is the fastest method: + +```bash +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Start the server with Docker Compose +docker compose up -d +``` + +The server will be available at `http://localhost:8080`. + +## Step 2: Install Formulus App + + + + +Download and install the APK from the [releases page](https://github.com/OpenDataEnsemble/ode/releases), or build from source: + +```bash +cd ode/formulus +npm install +npm run android +``` + + + + +Build from source (requires macOS and Xcode): + +```bash +cd ode/formulus +npm install +cd ios && bundle install && bundle exec pod install && cd .. +npm run ios +``` + + + + +## Step 3: Configure the App + +1. Open the Formulus app +2. Navigate to Settings +3. Enter server URL: `http://your-server-ip:8080` (or `http://localhost:8080` for emulator) +4. Enter your credentials (create a user account first if needed) +5. Save the configuration + +## Step 4: Create Your First Form + +Forms are defined using JSON schema. Create a simple form: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] +} +``` + +Upload this form to your Synkronus server using the API or CLI tool. + +## Step 5: Collect Data + +1. Open the Formulus app +2. Navigate to your form +3. Fill out the form fields +4. Submit the observation +5. The data will be stored locally and synchronized to the server + +## Step 6: Verify Data Collection + +Check that your observation was created: + + + + +```bash +curl http://localhost:8080/api/observations \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + + + + +```bash +synk observations list +``` + + + + +1. Navigate to the Portal +2. Go to "Observations" +3. View your submitted observations + + + + +## Next Steps + +Now that you have a working setup: + +- Learn about [form design](/guides/form-design) to create more complex forms +- Explore [custom applications](/guides/custom-applications) for specialized workflows +- Review the [API reference](/reference/api) for integration options +- Read about [synchronization](/using/synchronization) to understand data flow + +## Troubleshooting + +### Server Not Accessible + +If the mobile app cannot connect to the server: + + + + +Verify the server is running: `curl http://localhost:8080/health` + +Check firewall settings + +Use your machine's IP address: `http://192.168.1.100:8080` + +Ensure device and server are on the same network + + + + +Use `10.0.2.2` instead of `localhost`: `http://10.0.2.2:8080` + +Verify the server is running on the host machine + +Check that port 8080 is accessible + + + + +Use `localhost` or your machine's IP address: `http://localhost:8080` + +Verify the server is running + +Check firewall settings + + + + +### Forms Not Appearing + +If forms don't appear in the app: + +- Verify the form was uploaded correctly +- Check that the app has synchronized with the server +- Review server logs for errors + +### Synchronization Issues + +If data is not synchronizing: + +- Check network connectivity +- Verify authentication credentials +- Review server logs for sync errors +- Ensure the observation was saved locally before sync + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [Your First Form](/using/your-first-form) +- [Form Design Guide](/guides/form-design) + diff --git a/docs/getting-started/what-is-ode.md b/docs/getting-started/what-is-ode.md new file mode 100644 index 0000000..01fea78 --- /dev/null +++ b/docs/getting-started/what-is-ode.md @@ -0,0 +1,92 @@ +--- +sidebar_position: 1 +--- + +# What is ODE? + +Open Data Ensemble (ODE) is a platform designed to simplify mobile data collection and management. It provides tools and infrastructure for creating forms, collecting data in the field, and synchronizing information across devices and servers. + +## Overview + +ODE addresses the challenges of data collection in environments where connectivity is unreliable or intermittent. The platform is built with an offline-first architecture, ensuring that data collection continues regardless of network availability. + +## Problem Statement + +Traditional data collection solutions often require constant internet connectivity, making them unsuitable for field work in remote areas. ODE solves this by: + +- Storing data locally on mobile devices +- Synchronizing data when connectivity is available +- Resolving conflicts automatically when multiple devices modify the same data +- Providing a flexible form design system that adapts to various use cases + +## Key Features + +### Offline-First Design + +All data is stored locally on the device using WatermelonDB, a reactive database optimized for React Native. This ensures that data collection continues even when the device is offline. + +### Flexible Form System + +Forms are defined using JSON schema, following the JSON Forms specification. This allows for complex validation rules, conditional logic, and custom question types. + +### Reliable Synchronization + +The synchronization protocol handles conflicts, ensures data integrity, and supports incremental updates to minimize bandwidth usage. + +### Cross-Platform Support + +ODE applications run on Android and iOS devices, with a web-based form player for preview and testing. + +### Extensible Architecture + +The platform supports custom applications and renderers, allowing organizations to tailor the user experience to their specific needs. + +## Use Cases + +ODE is suitable for various data collection scenarios: + +| Use Case | Description | +|----------|-------------| +| **Health Surveys** | Collect patient data, medical records, and health indicators | +| **Research Studies** | Gather research data in field conditions | +| **Monitoring & Evaluation** | Track program outcomes and indicators | +| **Asset Management** | Inventory and track assets in the field | +| **Quality Assurance** | Conduct inspections and quality checks | + +## Architecture Overview + +ODE follows a client-server architecture: + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formulus │◄───────►│ Synkronus │◄───────►│ Formulus │ +│ (Mobile) │ Sync │ (Server) │ Sync │ (Mobile) │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formplayer │ │ Database │ │ Formplayer │ +│ (WebView) │ │ (PostgreSQL) │ │ (WebView) │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +The mobile application (Formulus) communicates with the server (Synkronus) to synchronize data. Forms are rendered using the Formplayer component, which can be embedded in custom applications. + +## Technology Stack + +ODE is built using modern, open-source technologies: + +- **React Native** for mobile applications +- **React** for web-based form rendering +- **Go** for the backend server +- **PostgreSQL** for data storage +- **WatermelonDB** for local data storage on mobile devices +- **JSON Forms** for form rendering and validation + +## Next Steps + +- Learn about [Why ODE?](/getting-started/why-ode) to understand the benefits +- Review [Key Concepts](/getting-started/key-concepts) to understand the terminology +- Follow the [Installation guide](/getting-started/installation/prerequisites) to set up your environment + diff --git a/docs/getting-started/why-ode.md b/docs/getting-started/why-ode.md new file mode 100644 index 0000000..ca3d2de --- /dev/null +++ b/docs/getting-started/why-ode.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 2 +--- + +# Why ODE? + +ODE provides several advantages over traditional data collection solutions, particularly for organizations working in challenging environments with unreliable connectivity. + +## Advantages + +### Offline-First Architecture + +Unlike many data collection platforms that require constant connectivity, ODE is designed to work offline. Data is stored locally and synchronized when connectivity is available, ensuring that field work is never interrupted by network issues. + +### Conflict Resolution + +When multiple devices modify the same data, ODE automatically resolves conflicts using a version-based approach. This ensures data integrity without requiring manual intervention. + +### Flexible Form Design + +The JSON-based form system allows for complex validation rules, conditional logic, and custom question types. Forms can be updated without requiring application updates, enabling rapid iteration and adaptation. + +### Open Source + +ODE is fully open source, allowing organizations to audit the code, customize the platform, and contribute improvements. This transparency builds trust and enables community-driven development. + +### Cross-Platform Support + +Applications built with ODE run on both Android and iOS devices, reducing the need to maintain separate codebases for different platforms. + +### Extensible + +The platform supports custom applications and renderers, allowing organizations to create specialized workflows and user interfaces that match their specific needs. + +## Comparison with Alternatives + +| Feature | ODE | Traditional Solutions | +|--------|-----|----------------------| +| Offline Support | Full offline functionality | Limited or none | +| Conflict Resolution | Automatic | Manual or none | +| Form Updates | Without app updates | Requires app updates | +| Customization | High flexibility | Limited | +| Open Source | Yes | Often proprietary | +| Cost | Free and open source | Licensing fees | + +## When to Use ODE + +ODE is particularly well-suited for: + +- Organizations conducting field research or data collection in remote areas +- Projects requiring complex forms with validation and conditional logic +- Teams needing to customize the user experience +- Organizations prioritizing data privacy and security +- Projects requiring offline functionality + +## When to Consider Alternatives + +ODE may not be the best fit if: + +- Your use case requires real-time collaboration features +- You need extensive third-party integrations out of the box +- Your team lacks technical resources for deployment and maintenance +- Your data collection needs are very simple and don't require offline support + +## Next Steps + +- Review [Key Concepts](/getting-started/key-concepts) to understand ODE terminology +- Check [Prerequisites](/getting-started/installation/prerequisites) for system requirements +- Follow the [Installation guide](/getting-started/installation/quick-start) to get started + diff --git a/docs/guides/.gitkeep b/docs/guides/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md new file mode 100644 index 0000000..cc29d14 --- /dev/null +++ b/docs/guides/configuration.md @@ -0,0 +1,360 @@ +--- +sidebar_position: 4 +--- + +# Configuration + +Complete configuration guide for ODE components including server settings, client settings, and environment variables. + +## Server Configuration (Synkronus) + +Synkronus is configured using environment variables. You can use either environment variables directly or a `.env` file for local development. + +### Configuration File Locations + +For local development, create a `.env` file in one of these locations (searched in order): + +1. Current working directory (where you run the command from) +2. Same directory as the executable +3. Parent directory of the executable + +### Required Configuration + +| Variable | Description | Example | +|----------|-------------|---------| +| `DB_CONNECTION` | PostgreSQL connection string | `postgres://user:password@localhost:5432/synkronus?sslmode=disable` | +| `JWT_SECRET` | Secret key for JWT token signing (minimum 32 characters) | Generate with `openssl rand -base64 32` | + +### Optional Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8080` | HTTP server port | +| `LOG_LEVEL` | `info` | Logging level: `debug`, `info`, `warn`, or `error` | +| `APP_BUNDLE_PATH` | `./data/app-bundles` | Directory path for app bundle storage | +| `MAX_VERSIONS_KEPT` | `5` | Maximum number of app bundle versions to retain | +| `ADMIN_USERNAME` | `admin` | Initial admin username | +| `ADMIN_PASSWORD` | `admin` | Initial admin password (must be changed in production) | + +### Example Configuration File + +```bash +# Server Configuration +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=info +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 + +# Admin Configuration +ADMIN_USERNAME=admin +ADMIN_PASSWORD=change-this-password +``` + +### Database Connection String Format + +The `DB_CONNECTION` string follows PostgreSQL connection URI format: + +``` +postgres://[user[:password]@][host][:port][/database][?param1=value1&...] +``` + +**Components:** +- `user`: Database username +- `password`: Database password +- `host`: Database hostname or IP address +- `port`: Database port (default: 5432) +- `database`: Database name +- `sslmode`: SSL mode (`disable`, `require`, `verify-full`, etc.) + +**Examples:** + +```bash +# Local development +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable + +# Remote database +DB_CONNECTION=postgres://synkronus:password@db.example.com:5432/synkronus?sslmode=require + +# Docker Compose +DB_CONNECTION=postgres://synkronus:password@postgres:5432/synkronus?sslmode=disable +``` + +## Client Configuration (Formulus) + +The Formulus mobile app is configured through the app settings interface. + +### Server URL + +Enter the URL of your Synkronus server: + + + + +**Physical Device**: `http://192.168.1.100:8080` (your machine's IP address) + +**Android Emulator**: `http://10.0.2.2:8080` (special IP for emulator) + +**iOS Simulator**: `http://localhost:8080` or your machine's IP address + + + + +**HTTPS URL**: `https://synkronus.your-domain.com` + +**Custom Port**: `https://synkronus.your-domain.com:8443` (if using non-standard port) + + + + +### Authentication + +Configure authentication credentials: + +1. **Username**: Your user account username +2. **Password**: Your user account password +3. **Server URL**: As described above + +The app stores credentials securely and uses them for API authentication. + +### Sync Settings + +Configure synchronization behavior: + +- **Auto-sync**: Enable automatic synchronization when connectivity is available +- **Sync interval**: How often to check for sync (default: every 15 minutes) +- **Sync on app start**: Automatically sync when the app is opened +- **Sync on observation save**: Sync immediately after saving an observation + +## Synkronus CLI Configuration + +The Synkronus CLI uses a configuration file located at `$HOME/.synkronus.yaml` by default. + +### Configuration File Format + +```yaml +api: + url: http://localhost:8080 + version: 1.0.0 +``` + +### Multiple Endpoints + +You can manage multiple endpoint configurations: + +```bash +# Create separate config files +synk config init -o ~/.synkronus-dev.yaml +synk config init -o ~/.synkronus-prod.yaml + +# Point CLI at dev by default +synk config use ~/.synkronus-dev.yaml + +# Point CLI at prod by default +synk config use ~/.synkronus-prod.yaml + +# Temporarily override for a single command +synk --config ~/.synkronus-dev.yaml status +``` + +### Authentication + +The CLI stores authentication tokens in the configuration file after login: + +```bash +# Login to the API +synk login --username your-username + +# Check authentication status +synk status + +# Logout +synk logout +``` + +## Docker Configuration + +### Docker Compose Configuration + +When using Docker Compose, configuration is set in the `docker-compose.yml` file: + +```yaml +services: + synkronus: + image: ghcr.io/opendataensemble/synkronus:latest + environment: + PORT: 8080 + DB_CONNECTION: postgres://synkronus:password@postgres:5432/synkronus?sslmode=disable + JWT_SECRET: your-secret-key + LOG_LEVEL: info + APP_BUNDLE_PATH: /app/data/app-bundles + MAX_VERSIONS_KEPT: 5 + volumes: + - app-bundles:/app/data/app-bundles + depends_on: + - postgres +``` + +### Environment File + +You can also use a `.env` file with Docker Compose: + +```bash +# .env file +DB_CONNECTION=postgres://synkronus:password@postgres:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key +LOG_LEVEL=info +``` + +Reference in `docker-compose.yml`: + +```yaml +services: + synkronus: + env_file: + - .env +``` + +## Security Configuration + +### JWT Secret + +Generate a strong JWT secret: + +```bash +# Using OpenSSL +openssl rand -base64 32 + +# Using PowerShell (Windows) +[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })) +``` + +**Important:** Use a different secret for each environment (development, staging, production). + +### Database Passwords + +Generate strong database passwords: + +```bash +# Generate random password +openssl rand -base64 24 +``` + +### Admin Password + +Change the default admin password immediately after deployment: + +```bash +# Via API +curl -X POST https://your-server.com/users/change-password \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"current_password":"admin","new_password":"new-secure-password"}' +``` + +## Logging Configuration + +### Log Levels + +| Level | Description | Use Case | +|-------|-------------|----------| +| `debug` | Detailed diagnostic information | Development, troubleshooting | +| `info` | General informational messages | Production (default) | +| `warn` | Warning messages | Production | +| `error` | Error messages only | Production (minimal logging) | + +### Log Output + +Logs are written to standard output (stdout) and can be captured by: + +- Docker logging drivers +- Systemd journal +- Log aggregation services (e.g., Fluentd, Logstash) + +### Docker Logging + +Configure Docker logging in `docker-compose.yml`: + +```yaml +services: + synkronus: + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" +``` + +## Performance Configuration + +### PostgreSQL Settings + +Optimize PostgreSQL for your workload: + +```yaml +services: + postgres: + command: + - "postgres" + - "-c" + - "max_connections=100" + - "-c" + - "shared_buffers=256MB" + - "-c" + - "effective_cache_size=1GB" +``` + +### Resource Limits + +Set resource limits in Docker Compose: + +```yaml +services: + synkronus: + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + reservations: + cpus: '0.5' + memory: 256M +``` + +## Configuration Validation + +### Verify Configuration + +Test your configuration: + +```bash +# Check environment variables are set +docker compose config + +# Test database connection +docker compose exec synkronus sh -c 'apk add postgresql-client && psql "$DB_CONNECTION"' + +# Check server health +curl http://localhost:8080/health +``` + +### Common Configuration Issues + +**Problem**: Database connection fails + +**Solution**: Verify `DB_CONNECTION` string format and database accessibility. + +**Problem**: JWT authentication fails + +**Solution**: Ensure `JWT_SECRET` is at least 32 characters and matches across all instances. + +**Problem**: App bundles not persisting + +**Solution**: Verify `APP_BUNDLE_PATH` is set and volume is properly mounted. + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [Deployment Guide](/guides/deployment) +- [API Reference](/reference/api) diff --git a/docs/guides/custom-applications.md b/docs/guides/custom-applications.md new file mode 100644 index 0000000..9055a4d --- /dev/null +++ b/docs/guides/custom-applications.md @@ -0,0 +1,196 @@ +--- +sidebar_position: 2 +--- + +# Custom Applications + +Complete guide to building and deploying custom applications that integrate with ODE. + +## Overview + +Custom applications are web-based interfaces that run within the Formulus mobile app, providing specialized workflows and user experiences. They allow you to create custom navigation, integrate with the ODE form system, and build specialized interfaces for specific use cases. + +## How Custom Applications Work + +Custom applications are defined in app bundles, which include: + +- HTML, CSS, and JavaScript files +- Form specifications +- Custom renderers for question types +- Configuration files + +The app bundle is uploaded to the Synkronus server and downloaded by mobile devices during synchronization. When a user opens a custom application, it runs in a WebView within the Formulus app. + +## Formulus JavaScript Interface + +Custom applications interact with Formulus through a JavaScript interface. The API is injected automatically when your app loads. + +### Getting the Formulus API + +Always use the `getFormulus()` helper function to ensure the API is ready: + +```html + + + + +``` + +### Available Methods + +The Formulus interface provides methods for: + +- **Form Operations**: Create, edit, and delete observations +- **Data Access**: Query observations and form specifications +- **Device Features**: Access camera, GPS, file system, etc. +- **Synchronization**: Trigger sync operations + +### Creating Observations + +```javascript +// Create a new observation +await formulus.addObservation(formType, initializationData); +``` + +### Editing Observations + +```javascript +// Edit an existing observation +await formulus.editObservation(formType, observationId); +``` + +### Deleting Observations + +```javascript +// Delete an observation +await formulus.deleteObservation(formType, observationId); +``` + +## App Bundle Structure + +An app bundle is a ZIP file containing: + +``` +app-bundle/ +├── index.html # Main entry point +├── assets/ +│ ├── css/ +│ ├── js/ +│ └── images/ +├── forms/ # Form specifications (optional) +└── manifest.json # Bundle metadata +``` + +### Manifest File + +The manifest defines bundle metadata: + +```json +{ + "version": "1.0.0", + "name": "My Custom App", + "description": "Description of the app", + "entryPoint": "index.html" +} +``` + +## Building Custom Applications + +### Development Setup + +1. **Reference the API**: Copy `formulus-api.js` into your project for autocompletion +2. **Include the Load Script**: Add `formulus-load.js` to your HTML +3. **Use getFormulus()**: Always await the API before using it + +### Example Application + +```html + + + + My Custom App + + + +

My Custom App

+ + + + + +``` + +## Deployment + +### Uploading App Bundles + +Upload app bundles using the Synkronus CLI: + +```bash +synk app-bundle upload bundle.zip --activate +``` + +Or use the API: + +```bash +curl -X POST http://your-server:8080/api/app-bundle/upload \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "bundle=@bundle.zip" +``` + +### Version Management + +App bundles support versioning: + +- Each upload creates a new version +- Versions are identified by timestamp +- Switch between versions using the CLI or API +- Mobile devices download the active version during sync + +## Custom Renderers + +Custom applications can include custom renderers for question types. See the [Form Design guide](/guides/form-design) for details on creating custom renderers. + +## Best Practices + +1. **Wait for API**: Always use `getFormulus()` before accessing the API +2. **Error Handling**: Implement proper error handling for all API calls +3. **Offline Support**: Design apps to work gracefully when offline +4. **Performance**: Optimize for mobile devices and slower networks +5. **Testing**: Test thoroughly in both development and production environments + +## Related Documentation + +- [Form Design Guide](/guides/form-design) +- [Formulus Interface Documentation](https://github.com/OpenDataEnsemble/ode/tree/main/formulus/src/webview) +- [App Bundle Format Reference](/reference/app-bundle-format) + diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md new file mode 100644 index 0000000..b72d7e0 --- /dev/null +++ b/docs/guides/deployment.md @@ -0,0 +1,560 @@ +--- +sidebar_position: 3 +--- + +# Deployment + +Complete guide to deploying ODE in production environments using Docker and Docker Compose. + +## Overview + +ODE can be deployed using Docker containers, which simplifies deployment and ensures consistency across environments. This guide covers production deployment with Docker Compose, including PostgreSQL, Nginx reverse proxy, and optional Cloudflared tunnel for secure external access. + +## Recommended Production Setup + +For production deployment, we recommend: + +- **Clean Linux server** (Ubuntu 22.04 LTS or Debian 12) +- **Docker & Docker Compose** installed +- **Cloudflared tunnel** for secure external access (no port forwarding needed) +- **PostgreSQL** database (dockerized via docker-compose) +- **Nginx** reverse proxy (included in docker-compose) +- **Persistent volumes** for data storage + +## Quick Start + +### Server Preparation + + + + +```bash +# Update system +sudo apt update && sudo apt upgrade -y + +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# Install Docker Compose +sudo apt install docker-compose-plugin -y + +# Verify installation +docker --version +docker compose version +``` + + + + +```bash +# Install Docker Desktop from https://www.docker.com/products/docker-desktop +# Or use Homebrew: +brew install --cask docker + +# Docker Compose is included with Docker Desktop +# Verify installation +docker --version +docker compose version +``` + + + + +1. **Install Docker Desktop** from [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop) +2. **Docker Compose is included** with Docker Desktop +3. **Verify installation** (PowerShell): + ```powershell + docker --version + docker compose version + ``` + + + + +### Deploy Synkronus + +```bash +# Create deployment directory +mkdir -p ~/synkronus +cd ~/synkronus + +# Download configuration files +wget https://raw.githubusercontent.com/opendataensemble/ode/main/synkronus/docker-compose.example.yml -O docker-compose.yml +wget https://raw.githubusercontent.com/opendataensemble/ode/main/synkronus/nginx.conf + +# Generate secure secrets +JWT_SECRET=$(openssl rand -base64 32) +DB_ROOT_PASSWORD=$(openssl rand -base64 24) +ADMIN_PASSWORD=$(openssl rand -base64 16) + +# Update docker-compose.yml with secrets +sed -i "s/CHANGE_THIS_PASSWORD/$DB_ROOT_PASSWORD/g" docker-compose.yml +sed -i "s/CHANGE_THIS_TO_RANDOM_32_CHAR_STRING/$JWT_SECRET/g" docker-compose.yml +sed -i "s/CHANGE_THIS_ADMIN_PASSWORD/$ADMIN_PASSWORD/g" docker-compose.yml + +# Start the stack +docker compose up -d + +# Verify it's running +curl http://localhost/health +``` + +### Database Setup + +Create a database and user for Synkronus: + + + + +```bash +# Open a psql shell into the Postgres container +docker compose exec postgres psql -U postgres +``` + +From the `psql` prompt: + +```sql +-- Create role and database +CREATE ROLE synkronus_user LOGIN PASSWORD 'CHANGE_THIS_APP_PASSWORD'; +CREATE DATABASE synkronus OWNER synkronus_user; +``` + + + + +```bash +# Connect to local PostgreSQL +psql -U postgres +``` + +From the `psql` prompt: + +```sql +-- Create role and database +CREATE ROLE synkronus_user LOGIN PASSWORD 'CHANGE_THIS_APP_PASSWORD'; +CREATE DATABASE synkronus OWNER synkronus_user; +``` + + + + +Update the `synkronus` service `DB_CONNECTION` in `docker-compose.yml`: + +```yaml +services: + synkronus: + environment: + DB_CONNECTION: "postgres://synkronus_user:CHANGE_THIS_APP_PASSWORD@postgres:5432/synkronus?sslmode=disable" +``` + +## Using Pre-built Images + +Pre-built images are automatically published to GitHub Container Registry (GHCR) via CI/CD. + +### Pull the Latest Image + +```bash +docker pull ghcr.io/opendataensemble/synkronus:latest +``` + +### Available Tags + +| Tag | Description | +|-----|-------------| +| `latest` | Latest stable release from main branch | +| `v1.0.0` | Specific version tags | +| `develop` | Development branch (pre-release) | +| `feature-xyz` | Feature branches (pre-release) | + +### Run Pre-built Image + +```bash +docker run -d \ + --name synkronus \ + -p 8080:8080 \ + -e DB_CONNECTION="postgres://user:password@host:5432/synkronus" \ + -e JWT_SECRET="your-secret-key" \ + -e APP_BUNDLE_PATH="/app/data/app-bundles" \ + -v synkronus-bundles:/app/data/app-bundles \ + ghcr.io/opendataensemble/synkronus:latest +``` + +## Cloudflared Tunnel Setup + +Cloudflared provides secure external access without exposing ports or managing SSL certificates. + +### Install Cloudflared + +```bash +# Download and install +wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb +sudo dpkg -i cloudflared-linux-amd64.deb + +# Verify installation +cloudflared --version +``` + +### Create Tunnel + +```bash +# Login to Cloudflare +cloudflared tunnel login + +# Create tunnel +cloudflared tunnel create synkronus + +# Note the tunnel ID from the output +``` + +### Configure Tunnel + +Create `~/.cloudflared/config.yml`: + +```yaml +tunnel: +credentials-file: /root/.cloudflared/.json + +ingress: + - hostname: synkronus.your-domain.com + service: http://localhost:80 + - service: http_status:404 +``` + +### Route DNS + +```bash +# Route your domain to the tunnel +cloudflared tunnel route dns synkronus synkronus.your-domain.com +``` + +### Run Tunnel as Service + +```bash +# Install as systemd service +sudo cloudflared service install + +# Start service +sudo systemctl start cloudflared +sudo systemctl enable cloudflared + +# Check status +sudo systemctl status cloudflared +``` + +Your Synkronus instance is now accessible at `https://synkronus.your-domain.com` with automatic SSL. + +## Environment Variables + +### Required Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `DB_CONNECTION` | PostgreSQL connection string | `postgres://user:pass@postgres:5432/synkronus` | +| `JWT_SECRET` | Secret key for JWT token signing | Generate with `openssl rand -base64 32` | + +### Optional Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8080` | HTTP server port | +| `LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) | +| `APP_BUNDLE_PATH` | `/app/data/app-bundles` | Path for app bundle storage | +| `MAX_VERSIONS_KEPT` | `5` | Number of app bundle versions to retain | +| `ADMIN_USERNAME` | `admin` | Initial admin username | +| `ADMIN_PASSWORD` | `admin` | Initial admin password (CHANGE THIS!) | + +## Volume Management + +### Persistent Volumes + +The docker-compose setup creates persistent volumes: + +1. **postgres-data**: PostgreSQL database files +2. **app-bundles**: Uploaded application bundles + +```bash +# List volumes +docker volume ls + +# Inspect volume +docker volume inspect synkronus_postgres-data + +# Backup volume +docker run --rm -v synkronus_postgres-data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data + +# Restore volume +docker run --rm -v synkronus_postgres-data:/data -v $(pwd):/backup alpine tar xzf /backup/postgres-backup.tar.gz -C / +``` + +### App Bundle Directory Permissions + +When bind-mounting a host directory for `app-bundles`, ensure proper permissions. The container runs as user `synkronus` with `uid=1000` and `gid=1000`: + +```bash +# Fix permissions on host directory +sudo chown -R 1000:1000 ~/server/app-bundles + +# Restart after fixing permissions +docker compose restart synkronus +``` + +## Monitoring and Maintenance + +### View Logs + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f synkronus +docker compose logs -f postgres +docker compose logs -f nginx + +# Last 100 lines +docker compose logs --tail=100 synkronus +``` + +### Health Checks + +```bash +# Check service status +docker compose ps + +# Test health endpoint +curl http://localhost/health + +# Via cloudflared tunnel +curl https://synkronus.your-domain.com/health +``` + +### Restart Services + +```bash +# Restart all services +docker compose restart + +# Restart specific service +docker compose restart synkronus + +# Reload nginx configuration +docker compose exec nginx nginx -s reload +``` + +### Update to Latest Version + +```bash +# Pull latest image +docker compose pull + +# Recreate containers with new image +docker compose up -d + +# Remove old images +docker image prune -f +``` + +## Backup and Restore + +### Database Backup + +```bash +# Create backup +docker compose exec postgres pg_dump -U synkronus_user synkronus > backup-$(date +%Y%m%d).sql + +# Automated daily backups (add to crontab) +0 2 * * * cd ~/synkronus && docker compose exec -T postgres pg_dump -U synkronus_user synkronus > /backups/synkronus-$(date +\%Y\%m\%d).sql +``` + +### Database Restore + +```bash +# Restore from backup +docker compose exec -T postgres psql -U synkronus_user synkronus < backup-20250114.sql +``` + +### Full System Backup + +```bash +# Backup everything +tar czf synkronus-full-backup-$(date +%Y%m%d).tar.gz \ + docker-compose.yml \ + nginx.conf \ + $(docker volume inspect synkronus_postgres-data --format '{{ .Mountpoint }}') \ + $(docker volume inspect synkronus_app-bundles --format '{{ .Mountpoint }}') +``` + +## Security Best Practices + +### 1. Use Strong Secrets + +```bash +# Generate strong JWT secret +openssl rand -base64 32 + +# Generate strong passwords +openssl rand -base64 24 +``` + +### 2. Change Default Admin Password + +After first deployment, change the admin password via API or CLI. + +### 3. Regular Updates + +```bash +# Update system packages +sudo apt update && sudo apt upgrade -y + +# Update Docker images +docker compose pull +docker compose up -d +``` + +### 4. Firewall Configuration + +If not using Cloudflared, configure firewall: + +```bash +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +``` + +## Performance Tuning + +### PostgreSQL Optimization + +Add to `docker-compose.yml` under postgres service: + +```yaml +command: + - "postgres" + - "-c" + - "max_connections=100" + - "-c" + - "shared_buffers=256MB" + - "-c" + - "effective_cache_size=1GB" +``` + +### Resource Limits + +Add to `docker-compose.yml` under each service: + +```yaml +deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + reservations: + cpus: '0.5' + memory: 256M +``` + +## Architecture + +The deployment architecture includes: + +``` +┌─────────────────────────────────────────┐ +│ Cloudflared Tunnel │ +│ (Optional - Cloudflare) │ +│ Automatic SSL/TLS │ +└──────────────┬──────────────────────────┘ + │ HTTPS + ▼ +┌─────────────────────────────────────────┐ +│ Nginx Reverse Proxy │ +│ Port 80/443 │ +│ - Load balancing │ +│ - Request routing │ +│ - Compression │ +└──────────────┬──────────────────────────┘ + │ HTTP + ▼ +┌─────────────────────────────────────────┐ +│ Synkronus Container │ +│ Port 8080 (internal) │ +│ - API endpoints │ +│ - Business logic │ +│ - File storage │ +└──────────────┬──────────────────────────┘ + │ PostgreSQL protocol + ▼ +┌─────────────────────────────────────────┐ +│ PostgreSQL Database │ +│ Port 5432 (internal) │ +│ - Data persistence │ +│ - Transactions │ +└─────────────────────────────────────────┘ +``` + +## Troubleshooting + +### Service Won't Start + +```bash +# Check logs +docker compose logs synkronus + +# Check environment variables +docker compose config + +# Verify database connection +docker compose exec synkronus sh +# Inside container: +apk add postgresql-client +psql "$DB_CONNECTION" +``` + +### Database Connection Issues + +```bash +# Check PostgreSQL is running +docker compose ps postgres + +# Check PostgreSQL logs +docker compose logs postgres + +# Test connection from synkronus container +docker compose exec synkronus sh -c 'apk add postgresql-client && psql "$DB_CONNECTION"' +``` + +### Nginx Issues + +```bash +# Test nginx configuration +docker compose exec nginx nginx -t + +# Reload nginx +docker compose exec nginx nginx -s reload + +# Check nginx logs +docker compose logs nginx +``` + +## Production Checklist + +Before going live: + +- [ ] Strong JWT secret generated +- [ ] Strong database password set +- [ ] Admin password changed from default +- [ ] Cloudflared tunnel configured (or SSL certificates installed) +- [ ] Backup strategy implemented +- [ ] Monitoring configured +- [ ] Health checks passing +- [ ] Firewall configured (if not using Cloudflared) +- [ ] Resource limits set +- [ ] Log rotation configured +- [ ] Documentation reviewed +- [ ] Test deployment verified + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [Configuration Guide](/guides/configuration) +- [API Reference](/reference/api) diff --git a/docs/guides/form-design.md b/docs/guides/form-design.md new file mode 100644 index 0000000..0672c88 --- /dev/null +++ b/docs/guides/form-design.md @@ -0,0 +1,1362 @@ +--- +sidebar_position: 1 +--- + +# Form Design + +Complete guide to designing forms in ODE using JSON schema and JSON Forms. + +## Overview + +Forms in ODE are defined using JSON schema, following the JSON Forms specification. A form consists of two main components: + +1. **Schema**: Defines the data structure and validation rules +2. **UI Schema**: Defines how the form is presented to users + +## How Forms Work in ODE + +Understanding the mental model of forms in ODE is essential for effective form design. + +### What is a "Form" in ODE? + +A form in ODE is a structured data collection interface that consists of: + +- **JSON Schema**: Defines what data can be collected, its structure, types, and validation rules +- **UI Schema**: Defines how the form is presented to users, including layout, field ordering, and conditional visibility +- **Formplayer**: The rendering engine that interprets these schemas and creates the interactive form interface + +### The Role of JSON Schema + +JSON Schema serves as the **data contract** for your form: + +- **Structure Definition**: Defines the shape of data (properties, types, nesting) +- **Validation Rules**: Enforces data quality (required fields, ranges, formats) +- **Type Safety**: Ensures data types match expectations (string, number, boolean, etc.) + +JSON Schema follows the [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) specification, but ODE Formplayer intentionally supports a **safe, predictable subset** of JSON Schema features. + +### The Role of UI Schema + +UI Schema (JSON Forms UI Schema) controls the **presentation layer**: + +- **Layout**: How fields are arranged (vertical, horizontal, grouped, paginated) +- **Ordering**: The sequence in which fields appear +- **Conditional Logic**: When fields are shown or hidden +- **Field Configuration**: Labels, placeholders, and display options + +### The Role of Formplayer + +Formplayer is the React-based rendering engine that: + +- **Interprets Schemas**: Reads JSON Schema and UI Schema to understand form structure +- **Renders Components**: Creates interactive form elements (inputs, selects, media capture, etc.) +- **Validates Input**: Enforces schema validation rules in real-time +- **Manages State**: Tracks form data, validation errors, and user interactions + +### Why ODE Supports a Subset of JSON Schema + +**Key Message**: ODE Formplayer intentionally supports a safe, predictable subset of JSON Schema and JSON Forms to ensure: + +1. **Reliability**: Forms work consistently across all devices and scenarios +2. **Performance**: Complex schema features don't slow down form rendering +3. **Predictability**: Form behavior is deterministic and easy to reason about +4. **Mobile Optimization**: Features work well in resource-constrained mobile environments + +**Important**: Forms that use unsupported JSON Schema features may load but are **not guaranteed to work**. Always refer to the [Formplayer Supported Schema & UI Profile](/reference/formplayer#supported-schema--ui-profile) for the definitive list of supported features. + +## Basic Form Structure + +Here's a simple form example: + +```json +{ + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] + }, + "uischema": { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/age" + } + ] + } +} +``` + +## Schema Definition + +The schema defines the data structure and validation rules for your form. It follows the JSON Schema specification. + +### Property Types + +ODE supports various property types: + +| Type | Description | Example | +|------|-------------|---------| +| `string` | Text input | Name, description | +| `integer` | Whole number | Age, count | +| `number` | Decimal number | Weight, temperature | +| `boolean` | True/false value | Consent, agreement | +| `array` | List of items | Multiple selections | +| `object` | Nested object | Complex data structures | + +### Validation Rules + +You can add validation rules to properties: + +```json +{ + "type": "string", + "title": "Email", + "format": "email", + "minLength": 5, + "maxLength": 100 +} +``` + +Common validation rules: + +- `minimum` / `maximum`: For numbers +- `minLength` / `maxLength`: For strings +- `pattern`: Regular expression pattern +- `format`: Predefined formats (email, date, etc.) +- `enum`: List of allowed values + +## Designing UI Schemas for ODE Formplayer + +The UI schema defines how form fields are presented to users. It controls layout, ordering, and presentation. ODE Formplayer has specific requirements and best practices for UI schema design. + +### Required Layout Structure + +**All ODE forms must use `SwipeLayout` as the root element.** This enables pagination and swipe navigation between form sections. + +#### SwipeLayout (Required Root) + +`SwipeLayout` is the root layout type that enables multi-page forms with swipe navigation. It automatically wraps other layout types if not explicitly specified. + +**Required Structure:** +```json +{ + "type": "SwipeLayout", + "elements": [ + // Each element becomes a swipeable page + ] +} +``` + +**Key Characteristics:** +- **Root Element**: Must be the top-level element in your UI schema +- **Pagination**: Each element in `elements[]` becomes a separate page +- **Swipe Navigation**: Users can swipe left/right to navigate between pages +- **Progress Tracking**: Shows progress bar indicating current page +- **Auto-wrapping**: If root is not SwipeLayout, Formplayer automatically wraps it + +**Safe Example:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/age" + } + ] + }, + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/email" + } + ] + } + ] +} +``` + +**Unsafe Example:** +```json +{ + "type": "VerticalLayout", // ❌ Not SwipeLayout - will be auto-wrapped + "elements": [...] +} +``` + +### Layout Types + +#### VerticalLayout + +Fields arranged vertically in a single column. + +**Required Fields:** +- `type`: `"VerticalLayout"` +- `elements`: Array of UI schema elements + +**Usage:** +```json +{ + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/field1" + }, + { + "type": "Control", + "scope": "#/properties/field2" + } + ] +} +``` + +#### HorizontalLayout + +Fields arranged horizontally in a row. + +**Required Fields:** +- `type`: `"HorizontalLayout"` +- `elements`: Array of UI schema elements + +**Usage:** +```json +{ + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/firstName" + }, + { + "type": "Control", + "scope": "#/properties/lastName" + } + ] +} +``` + +#### Group + +Groups related fields together with a label. + +**Required Fields:** +- `type`: `"Group"` +- `label`: Group title (required) +- `elements`: Array of UI schema elements + +**Usage:** +```json +{ + "type": "Group", + "label": "Personal Information", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/email" + } + ] +} +``` + +**Note**: Groups can be used as pages within SwipeLayout. Each Group element in a SwipeLayout's `elements[]` becomes a separate page. + +### Control Configuration + +Controls bind UI elements to schema properties. + +**Required Fields:** +- `type`: `"Control"` +- `scope`: JSON pointer to schema property (must exist in schema) + +**Optional Fields:** +- `label`: Override field label +- `options`: Additional configuration + +**Safe Example:** +```json +{ + "type": "Control", + "scope": "#/properties/name", // ✅ Scope exists in schema + "label": "Full Name", + "options": { + "placeholder": "Enter your name" + } +} +``` + +**Unsafe Example:** +```json +{ + "type": "Control", + "scope": "#/properties/nonexistent" // ❌ Scope doesn't exist - will cause error +} +``` + +### Label Element + +Displays text labels within forms. + +**Required Fields:** +- `type`: `"Label"` +- `text`: Label text content + +**Usage:** +```json +{ + "type": "Label", + "text": "Section Introduction" +} +``` + +### UI Schema Best Practices + +#### Pagination Patterns + +**Recommended**: Use SwipeLayout with VerticalLayout pages for multi-step forms: + +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { "type": "Label", "text": "Step 1: Basic Info" }, + { "type": "Control", "scope": "#/properties/name" }, + { "type": "Control", "scope": "#/properties/age" } + ] + }, + { + "type": "VerticalLayout", + "elements": [ + { "type": "Label", "text": "Step 2: Contact" }, + { "type": "Control", "scope": "#/properties/email" } + ] + } + ] +} +``` + +#### Grouping Best Practices + +**Use Groups for logical organization:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "Group", + "label": "Demographics", + "elements": [ + { "type": "Control", "scope": "#/properties/age" }, + { "type": "Control", "scope": "#/properties/gender" } + ] + }, + { + "type": "Group", + "label": "Health Information", + "elements": [ + { "type": "Control", "scope": "#/properties/height" }, + { "type": "Control", "scope": "#/properties/weight" } + ] + } + ] +} +``` + +#### Common Pitfalls + +**❌ Missing elements array:** +```json +{ + "type": "SwipeLayout" + // ❌ Missing elements - will cause error +} +``` + +**✅ Always include elements:** +```json +{ + "type": "SwipeLayout", + "elements": [] // ✅ Empty array is safe +} +``` + +**❌ Invalid scope paths:** +```json +{ + "type": "Control", + "scope": "#/properties/missingField" // ❌ Field doesn't exist in schema +} +``` + +**✅ Verify scope exists:** +```json +{ + "type": "Control", + "scope": "#/properties/existingField" // ✅ Field exists in schema +} +``` + +**❌ Nested SwipeLayout:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "SwipeLayout", // ❌ Nested SwipeLayout not supported + "elements": [...] + } + ] +} +``` + +**✅ Use VerticalLayout or Group inside SwipeLayout:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", // ✅ Use VerticalLayout for pages + "elements": [...] + } + ] +} +``` + +## Question Types + +ODE supports various question types through the Formplayer component. Question types are specified using the `format` property in the schema. + +### Basic Input Types + +**Text Input:** +```json +{ + "type": "string", + "title": "Name", + "format": "text" +} +``` + +**Number Input:** +```json +{ + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 +} +``` + +**Date and Time:** +```json +{ + "type": "string", + "title": "Date", + "format": "date" +} +``` + +**Selection:** +```json +{ + "type": "string", + "title": "Choice", + "enum": ["option1", "option2"], + "enumNames": ["Option 1", "Option 2"] +} +``` + +**Boolean:** +```json +{ + "type": "boolean", + "title": "Consent" +} +``` + +## Working with Media & Special Field Types + +ODE Formplayer supports specialized field types for capturing media, location, signatures, and scanning codes. Each type has specific schema requirements and platform constraints. + +### Photo Capture + +Captures photos using device camera or gallery selection. + +**Expected Schema:** +```json +{ + "type": "object", + "format": "photo", + "title": "Profile Photo", + "properties": { + "filename": { "type": "string" }, + "uri": { "type": "string" }, + "width": { "type": "integer" }, + "height": { "type": "integer" }, + "timestamp": { "type": "string", "format": "date-time" } + } +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/photo" +} +``` + +**Renderer Behavior:** +- Opens device camera or gallery picker +- Stores photo as attachment (synchronized separately) +- Returns metadata object with filename, URI, dimensions, timestamp + +**Platform Constraints:** +- Requires camera permission on mobile devices +- Photo files are stored locally and synced as attachments +- Large photos may be compressed automatically + +### GPS Location + +Captures GPS coordinates with accuracy information. + +**Expected Schema:** +```json +{ + "type": "object", + "format": "gps", + "title": "Current Location", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" }, + "altitude": { "type": "number" }, + "accuracy": { "type": "number" }, + "timestamp": { "type": "string", "format": "date-time" } + } +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/location" +} +``` + +**Renderer Behavior:** +- Requests location permission (first time) +- Captures current GPS coordinates +- Shows accuracy indicator +- May display map preview (platform dependent) + +**Platform Constraints:** +- Requires location permission +- GPS accuracy depends on device capabilities and environment +- Indoor locations may have poor accuracy +- Battery impact: GPS usage drains battery faster + +### Signature Capture + +Captures digital signatures using touch input. + +**Expected Schema:** +```json +{ + "type": "object", + "format": "signature", + "title": "Customer Signature", + "properties": { + "data": { "type": "string" }, // Base64 encoded image + "timestamp": { "type": "string", "format": "date-time" } + } +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/signature" +} +``` + +**Renderer Behavior:** +- Opens signature pad interface +- User draws signature with finger/stylus +- Stores as base64-encoded image +- Provides clear/retry functionality + +**Platform Constraints:** +- Works best on touch-enabled devices +- Signature quality depends on screen size and resolution +- Stored as image data (may be large) + +### Audio Recording + +Records audio using device microphone. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "audio", + "title": "Voice Note" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/audio" +} +``` + +**Renderer Behavior:** +- Opens audio recording interface +- Records audio using device microphone +- Stores audio file with metadata (duration, format, file size) +- Provides playback preview + +**Platform Constraints:** +- Requires microphone permission +- Audio files are stored as attachments +- File size depends on recording duration and quality +- Format: Platform-dependent (typically MP3, M4A, or WAV) + +### Video Recording + +Records video using device camera. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "video", + "title": "Instructional Video" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/video" +} +``` + +**Renderer Behavior:** +- Opens video recording interface +- Records video using device camera +- Stores video file with metadata (duration, resolution, format) +- Provides playback preview + +**Platform Constraints:** +- Requires camera permission +- Video files are large (stored as attachments) +- File size depends on duration and resolution +- Format: Platform-dependent (typically MP4) +- Battery and storage intensive + +### File Selection + +Allows users to select files from device storage. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "select_file", + "title": "Upload Document" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/document" +} +``` + +**Renderer Behavior:** +- Opens device file picker +- User selects file from storage +- Stores file reference and metadata +- File is uploaded as attachment + +**Platform Constraints:** +- File type restrictions depend on platform +- Large files may take time to upload +- Storage permissions required + +### QR Code / Barcode Scanner + +Scans QR codes and barcodes using device camera. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "qrcode", + "title": "QR Code Scanner" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/qrCode" +} +``` + +**Renderer Behavior:** +- Opens camera scanner interface +- Automatically detects and scans codes +- Returns scanned data as string +- Supports multiple barcode formats + +**Supported Formats:** +- QR Code +- Code 128 +- Code 39 +- EAN-13 +- UPC-A +- Data Matrix +- PDF417 +- Aztec + +**Platform Constraints:** +- Requires camera permission +- Requires good lighting for reliable scanning +- Some formats may not be supported on all platforms + +### Best Practices for Media Fields + +1. **Consider File Sizes**: Media files are large - be mindful of storage and sync bandwidth +2. **Request Permissions Early**: Request camera/microphone/location permissions before users need them +3. **Provide Clear Instructions**: Users may not understand how to use specialized field types +4. **Handle Offline Scenarios**: Media capture works offline, but upload requires connectivity +5. **Test on Real Devices**: Media features behave differently on different devices +6. **Compress When Possible**: Large photos/videos should be compressed before storage +7. **Validate File Types**: Ensure selected files match expected formats + +## Conditional Logic in ODE Forms + +Conditional logic allows you to show or hide fields based on other field values. This is essential for creating dynamic, context-aware forms. + +### How Rules Work + +Rules are defined in the UI schema using the `rule` property on Control elements. Rules evaluate conditions and apply effects (SHOW or HIDE) based on the result. + +**Basic Rule Structure:** +```json +{ + "type": "Control", + "scope": "#/properties/targetField", + "rule": { + "effect": "SHOW", // or "HIDE" + "condition": { + "scope": "#/properties/sourceField", + "schema": { + // Condition schema + } + } + } +} +``` + +### Scope Resolution Rules + +**Critical Rule**: Rule condition scopes **must exist in the schema at all times**, even when the field is hidden. The scope is evaluated against the current form data. + +**Safe Pattern:** +```json +{ + "schema": { + "type": "object", + "properties": { + "contactMethod": { + "type": "string", + "enum": ["email", "phone"] + }, + "email": { + "type": "string", + "format": "email" + }, + "phone": { + "type": "string" + } + } + }, + "uischema": { + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/contactMethod" + }, + { + "type": "Control", + "scope": "#/properties/email", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", // ✅ Scope exists in schema + "schema": { + "const": "email" + } + } + } + }, + { + "type": "Control", + "scope": "#/properties/phone", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", // ✅ Scope exists in schema + "schema": { + "const": "phone" + } + } + } + } + ] + } + ] + } +} +``` + +### Safe Patterns + +#### Equality Check + +Show field when another field equals a specific value: + +```json +{ + "type": "Control", + "scope": "#/properties/email", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", + "schema": { + "const": "email" + } + } + } +} +``` + +#### Enum Value Check + +Show field when another field is one of several values: + +```json +{ + "type": "Control", + "scope": "#/properties/otherDetails", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/category", + "schema": { + "enum": ["category1", "category2"] + } + } + } +} +``` + +#### Boolean Check + +Show field when boolean is true: + +```json +{ + "type": "Control", + "scope": "#/properties/consentDetails", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/hasConsent", + "schema": { + "const": true + } + } + } +} +``` + +### Unsafe Patterns + +#### ❌ Rule Referencing Missing Field + +**Problem**: Rule condition scope doesn't exist in schema. + +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + // field2 doesn't exist + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/field2", // ❌ Field doesn't exist - will crash + "schema": { "const": "value" } + } + } + } +} +``` + +**Fix**: Ensure all fields referenced in rules exist in the schema. + +#### ❌ Using $data References + +**Problem**: `$data` references are not supported and will cause errors. + +```json +{ + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/field2", + "schema": { + "minimum": { "$data": "#/properties/minValue" } // ❌ $data not supported + } + } + } +} +``` + +**Fix**: Use literal values only: + +```json +{ + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/field2", + "schema": { + "minimum": 0 // ✅ Literal value + } + } + } +} +``` + +#### ❌ Using if/then/else + +**Problem**: JSON Schema `if/then/else` is not supported in rule conditions. + +```json +{ + "rule": { + "condition": { + "scope": "#/properties/field", + "schema": { + "if": { "type": "string" }, // ❌ Not supported + "then": { "const": "value" } + } + } + } +} +``` + +**Fix**: Use simple const or enum checks instead. + +### Best Practices for Conditional Logic + +1. **Always Define Referenced Fields**: Every field referenced in a rule condition must exist in the schema +2. **Use Simple Conditions**: Prefer `const` and `enum` over complex schema conditions +3. **Test All Conditions**: Verify rules work for all possible field values +4. **Avoid Circular Dependencies**: Don't create rules that depend on each other +5. **Document Complex Logic**: Comment complex conditional logic in your form specifications + +## Advanced Features + +### Multimedia Capture + +Forms can include fields for capturing photos, audio, and video. These are handled as attachments and synchronized separately from observation metadata. + +### Location Capture + +GPS coordinates can be captured automatically or manually entered. + +### File Attachments + +Files can be attached to observations and are synchronized separately from the observation data. + +## Form Versioning + +Forms support versioning to allow updates while maintaining compatibility with existing observations. When editing an observation, the form version used to create it is used. + +## Form Design Best Practices by Application Type + +Different application types have different requirements and constraints. Follow these best practices for your specific use case. + +### Research Data Collection + +**Characteristics**: Structured data collection, often longitudinal, requires high data quality. + +**Best Practices:** +1. **Use Clear Field Names**: Use descriptive, research-standard field names (e.g., `participant_id`, `visit_date`) +2. **Implement Validation**: Strict validation rules to ensure data quality +3. **Version Control**: Carefully version forms to maintain data consistency +4. **Minimize Required Fields**: Only require essential fields to reduce data entry burden +5. **Use Conditional Logic**: Show relevant fields based on participant characteristics +6. **Document Changes**: Maintain detailed changelog for form versions + +**Example Pattern:** +```json +{ + "schema": { + "properties": { + "participant_id": { + "type": "string", + "title": "Participant ID", + "pattern": "^P-[0-9]{4}$" + }, + "visit_number": { + "type": "integer", + "minimum": 1, + "maximum": 10 + } + }, + "required": ["participant_id", "visit_number"] + } +} +``` + +### Medical / Clinical Records + +**Characteristics**: Sensitive data, regulatory compliance, complex workflows. + +**Best Practices:** +1. **Consent Management**: Always include consent fields with clear labels +2. **Date/Time Precision**: Use precise date-time fields for clinical events +3. **Signature Requirements**: Use signature fields for consent and approvals +4. **Photo Documentation**: Use photo fields for wound tracking, skin conditions, etc. +5. **Structured Data**: Use enums for standardized values (e.g., diagnosis codes) +6. **Audit Trail**: Forms should capture who, what, when for audit purposes +7. **HIPAA/GDPR Compliance**: Ensure forms comply with data protection regulations + +**Example Pattern:** +```json +{ + "schema": { + "properties": { + "consent_obtained": { + "type": "boolean", + "title": "Patient consent obtained" + }, + "consent_signature": { + "type": "object", + "format": "signature", + "title": "Patient signature" + }, + "visit_date": { + "type": "string", + "format": "date-time", + "title": "Visit date and time" + } + }, + "required": ["consent_obtained", "consent_signature", "visit_date"] + } +} +``` + +### Surveys with Consent + +**Characteristics**: User-facing, requires clear consent, may be anonymous. + +**Best Practices:** +1. **Consent First**: Place consent fields at the beginning of the form +2. **Clear Language**: Use plain language, avoid jargon +3. **Progress Indicators**: Show progress to encourage completion +4. **Optional Fields**: Mark optional fields clearly +5. **Privacy Notice**: Include privacy information if collecting personal data +6. **Thank You Message**: Provide confirmation after submission + +**Example Pattern:** +```json +{ + "uischema": { + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Label", + "text": "Consent and Privacy" + }, + { + "type": "Control", + "scope": "#/properties/consent_read", + "label": "I have read and understood the consent form" + }, + { + "type": "Control", + "scope": "#/properties/consent_agreed", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/consent_read", + "schema": { "const": true } + } + } + } + ] + } + ] + } +} +``` + +### Longitudinal Forms + +**Characteristics**: Same form used multiple times, data comparison over time. + +**Best Practices:** +1. **Consistent Structure**: Maintain consistent field structure across versions +2. **Version Tracking**: Track which version was used for each observation +3. **Baseline Data**: Include baseline measurement fields +4. **Change Detection**: Use conditional logic to highlight changes +5. **Date Tracking**: Always include date/time fields for temporal analysis +6. **Backward Compatibility**: Ensure new versions don't break existing data + +**Example Pattern:** +```json +{ + "schema": { + "properties": { + "measurement_date": { + "type": "string", + "format": "date", + "title": "Measurement Date" + }, + "baseline_value": { + "type": "number", + "title": "Baseline Value" + }, + "current_value": { + "type": "number", + "title": "Current Value" + }, + "change_from_baseline": { + "type": "number", + "title": "Change from Baseline", + "readOnly": true // Calculated field + } + } + } +} +``` + +### General Best Practices + +**Form Structure:** +1. **Keep Forms Focused**: Each form should have a clear, single purpose +2. **Logical Grouping**: Group related fields together using Groups or separate pages +3. **Progressive Disclosure**: Use conditional logic to show fields only when relevant +4. **Clear Navigation**: Use SwipeLayout for multi-step forms with clear progress indicators + +**Field Design:** +1. **Use Clear Labels**: Field labels should be descriptive and unambiguous +2. **Provide Help Text**: Use descriptions or placeholders to guide users +3. **Validate Input**: Use validation rules to catch errors early +4. **Use Appropriate Types**: Choose the right field type for the data (date picker for dates, not text) + +**Data Quality:** +1. **Required Fields**: Only require truly essential fields +2. **Default Values**: Provide sensible defaults when appropriate +3. **Enum Constraints**: Use enums for fields with limited valid values +4. **Format Validation**: Use format validation (email, date, etc.) when applicable + +**Testing:** +1. **Test All Paths**: Test all conditional logic branches +2. **Test Validation**: Verify validation rules work correctly +3. **Test on Devices**: Test on actual mobile devices, not just emulators +4. **Test Offline**: Verify forms work correctly in offline mode +5. **User Testing**: Get feedback from actual users before deployment + +## Validating Forms Before Deployment + +Validating forms before deployment prevents errors and ensures forms work correctly in production. This section covers validation tools and workflows. + +### Validation Tools + +#### Form Validation Script + +ODE provides a validation script (`validate-forms.js`) that checks forms for common issues: + +**Usage:** +```bash +npm run validate:forms +``` + +**Checks Performed:** +- JSON Schema syntax validation +- UI Schema format validation +- Field reference validation (scope paths exist in schema) +- Required field validation +- Conditional logic validation (rule scopes exist) +- SwipeLayout structure validation + +**Common Validation Failures:** + +1. **Invalid JSON Schema:** + ``` + Error: Schema validation failed + - Property "minimum" must be a number + ``` + **Fix**: Ensure `minimum`/`maximum` use literal numbers, not `$data` references + +2. **Missing Scope:** + ``` + Error: Scope "#/properties/missingField" not found in schema + ``` + **Fix**: Add the referenced field to the schema or correct the scope path + +3. **Invalid UI Schema Structure:** + ``` + Error: SwipeLayout missing required "elements" array + ``` + **Fix**: Ensure SwipeLayout has an `elements` array (can be empty) + +4. **Rule Scope Error:** + ``` + Error: Rule condition scope "#/properties/field" not found + ``` + **Fix**: Ensure all fields referenced in rules exist in the schema + +### CI Integration + +Integrate form validation into your CI/CD pipeline: + +**GitHub Actions Example:** +```yaml +name: Validate Forms + +on: [push, pull_request] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '18' + - run: npm install + - run: npm run validate:forms +``` + +**GitLab CI Example:** +```yaml +validate_forms: + script: + - npm install + - npm run validate:forms +``` + +### Recommended Workflow + +1. **Local Validation**: Run validation script before committing changes +2. **Pre-commit Hooks**: Set up git hooks to validate forms automatically +3. **CI Validation**: Include validation in CI pipeline to catch errors early +4. **Manual Testing**: Test forms on actual devices before deployment +5. **Staging Deployment**: Deploy to staging environment and test thoroughly +6. **Production Deployment**: Only deploy validated, tested forms + +### Validation Checklist + +Before deploying a form, verify: + +- [ ] JSON Schema is valid JSON Schema Draft 7 +- [ ] All scope paths in UI schema exist in JSON schema +- [ ] SwipeLayout is root element (or will be auto-wrapped) +- [ ] All rule condition scopes exist in schema +- [ ] No `$data` references used +- [ ] No `if/then/else` in rule conditions +- [ ] All required fields are clearly marked +- [ ] Validation rules use literal values (not dynamic) +- [ ] Media field types have correct schema structure +- [ ] Form tested on actual mobile devices +- [ ] Form tested in offline mode +- [ ] All conditional logic paths tested + +### Troubleshooting Validation Errors + +**"minimum value must be ['number']"** +- **Cause**: Using `$data` or non-numeric value in `minimum` +- **Fix**: Use literal number: `"minimum": 0` + +**"Cannot read properties of undefined (reading 'find')"** +- **Cause**: Layout missing `elements` array, invalid scope, or rule referencing missing field +- **Fix**: + - Add `elements: []` to layouts + - Validate all scope paths exist + - Ensure rule scopes reference existing fields + +**"Rule condition scope not found"** +- **Cause**: Rule references field that doesn't exist in schema +- **Fix**: Add the referenced field to schema or correct the scope path + +## Related Documentation + +- [Form Specifications Reference](/reference/form-specifications) +- [Formplayer Supported Schema & UI Profile](/reference/formplayer#supported-schema--ui-profile) +- [Formplayer Errors Explained](/using/troubleshooting#formplayer-errors) +- [Your First Form](/using/your-first-form) +- [Custom Applications](/guides/custom-applications) + diff --git a/docs/guides/index.md b/docs/guides/index.md new file mode 100644 index 0000000..159b8f1 --- /dev/null +++ b/docs/guides/index.md @@ -0,0 +1,61 @@ +--- +sidebar_position: 0 +--- + +# Guides + +Step-by-step guides for common tasks and workflows in ODE. + +## Form Design & Configuration + +
+
+
+
+

Form Design

+
+
+

Complete guide to designing forms, including schema definitions and validation rules.

+ View Guide → +
+
+
+
+
+
+

Configuration

+
+
+

Configure ODE components, server settings, and client applications.

+ Configure → +
+
+
+
+ +## Deployment & Customization + +
+
+
+
+

Deployment

+
+
+

Deploy ODE to production environments, including Docker and cloud platforms.

+ Deploy → +
+
+
+
+
+
+

Custom Applications

+
+
+

Build and deploy custom applications using the ODE platform.

+ Build Apps → +
+
+
+
diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..9d218a4 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,91 @@ +--- +sidebar_position: 1 +--- + +# Open Data Ensemble + +Open Data Ensemble (ODE) is a comprehensive platform for mobile data collection and synchronization. Built for researchers, health professionals, implementers, and developers, ODE provides a robust solution for designing forms, managing data securely, and synchronizing seamlessly across devices, even in offline conditions. + +:::info Pre-release Available + +The source code for the pre-release version of ODE is now publicly available. While we're working toward the full 1.0 release, we welcome anyone willing to help with testing, development, or getting involved in the project. + +**Try the pre-release:** [Install Formulus on Android](/getting-started/installation) + +**Get involved:** Reach out to us at [hello@opendataensemble.org](mailto:hello@opendataensemble.org) - we'd love to hear from you! + +**Source Code:** Our monorepo on GitHub contains the source code for all components. Visit our repository: [https://github.com/OpenDataEnsemble/ode](https://github.com/OpenDataEnsemble/ode) + +::: + +## What is ODE? + +ODE is a modern toolkit that simplifies mobile data collection through: + +- **Simplicity & Efficiency**: Quickly create complex, validated forms using a powerful yet intuitive JSON-based approach +- **Advanced Offline Sync**: Reliable and conflict-resilient synchronization powered by WatermelonDB +- **Flexible & Extensible UI**: Customize form presentation and interaction effortlessly with JSON Forms, enabling rich, interactive user experiences +- **Cross-platform Support**: Applications run on Android, iOS, and web platforms + +## Current Members of the Ensemble + +Here's an overview of the current members of the ensemble: + +Component overview + +* [formulus](/reference/formulus): The Android and iOS app for data collection and form interaction. +* [synkronus](/reference/synkronus-server): The robust server backend managing synchronization and data storage. +* [synkronus-cli](/reference/synkronus-cli): Command-line interface for convenient server management and administrative tasks. + +## Key Components + +ODE consists of four main components that work together: + +| Component | Description | Technology | +|-----------|-------------|------------| +| **Formulus** | Mobile application for Android and iOS | React Native | +| **Formplayer** | Web-based form rendering engine | React | +| **Synkronus** | Backend server for data synchronization | Go | +| **Synkronus CLI** | Command-line utility for administration | Go | + +## Core Capabilities + +### Form Design + +Create sophisticated forms using JSON schema definitions. ODE supports various question types including text, numbers, dates, selections, multimedia capture, GPS coordinates, and custom renderers. + +### Offline Functionality + +Work seamlessly in areas with unreliable connectivity. Data is stored locally and synchronized when network connectivity is available. + +### Data Synchronization + +Reliable bidirectional synchronization ensures data consistency across devices and servers, with conflict resolution built into the protocol. + +### Custom Applications + +Build custom applications that integrate with the ODE platform, allowing for specialized workflows and user interfaces. + +## Getting Started + +New to ODE? Start with our [Getting Started guide](/getting-started/what-is-ode) to understand the platform and begin your first project. + +For developers looking to contribute or extend ODE, see the [Development section](/development/setup) for architecture details and contribution guidelines. + +## Documentation Structure + +This documentation is organized to help you find information quickly: + +- **Getting Started**: Introduction, installation, and basic concepts +- **Using ODE**: Practical guides for creating forms and managing data +- **Guides**: Step-by-step workflows for form design, custom apps, and deployment +- **Reference**: Complete API documentation and configuration options +- **Development**: Architecture details and contribution guidelines +- **Community**: Support resources and examples + +## Next Steps + +- Read [What is ODE?](/getting-started/what-is-ode) for a detailed overview +- Follow the [Installation guide](/getting-started/installation) to set up your environment +- Create your [first form](/using/your-first-form) to see ODE in action +- Explore the [API Reference](/reference/api) for technical details diff --git a/docs/reference/.gitkeep b/docs/reference/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/reference/api.md b/docs/reference/api.md new file mode 100644 index 0000000..1c7eb35 --- /dev/null +++ b/docs/reference/api.md @@ -0,0 +1,665 @@ +--- +sidebar_position: 1 +--- + +# API Reference + +Complete REST API reference for the Synkronus server. + +## Overview + +The Synkronus API provides endpoints for data synchronization, authentication, app bundle management, attachment handling, and user management. All endpoints use JSON for request and response bodies, except for binary file operations. + +## Base URL + +The API base URL depends on your deployment: + +- **Development**: `http://localhost:8080` +- **Production**: `https://synkronus.your-domain.com` + +All API endpoints are prefixed with `/api` for portal compatibility, though many endpoints are also available without the prefix. + +## Authentication + +The API uses JWT (JSON Web Tokens) for authentication. Most endpoints require authentication, except for health checks and version information. + +### Obtaining a Token + +Authenticate using the login endpoint: + + + + +```bash +curl -X POST http://localhost:8080/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "username": "your-username", + "password": "your-password" + }' +``` + +**Response:** + +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expires_in": 3600 +} +``` + + + + +```bash +synk login --username your-username +``` + +The CLI will prompt for password interactively and store the token automatically. + + + + +1. Navigate to the Portal login page +2. Enter your username and password +3. Click "Login" +4. Token is stored automatically in your browser session + + + + +### Using the Token + +Include the token in the Authorization header: + + + + +```bash +curl -X GET http://localhost:8080/api/endpoint \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + + + + +The CLI automatically includes the token after login. No manual token handling needed: + +```bash +synk status # Uses stored token automatically +``` + + + + +The Portal automatically includes the token in all API requests. No manual configuration needed. + + + + +### Refreshing Tokens + +Refresh an expired token: + + + + +```bash +curl -X POST http://localhost:8080/auth/refresh \ + -H "Content-Type: application/json" \ + -d '{ + "refresh_token": "your-refresh-token" + }' +``` + + + + +The CLI automatically refreshes tokens when needed. If refresh fails, you'll be prompted to login again: + +```bash +synk status # Automatically refreshes if needed +``` + + + + +The Portal automatically refreshes tokens in the background. If refresh fails, you'll be redirected to the login page. + + + + +### Roles + +The API supports role-based access control: + +| Role | Description | Permissions | +|------|-------------|-------------| +| `read-only` | Read-only access | Can pull data, view app bundles, download attachments | +| `read-write` | Read and write access | All read-only permissions plus push data, create observations | +| `admin` | Administrative access | All read-write permissions plus user management, app bundle management | + +## API Endpoints + +### Health and Version + +#### Health Check + +```http +GET /health +``` + +Returns the health status of the service. + +**Response:** + +```json +{ + "status": "ok", + "timestamp": "2025-01-14T10:30:00Z", + "version": "1.0.0" +} +``` + +#### Get Version + +```http +GET /version +GET /api/version +``` + +Returns detailed version information about the server. + +**Response:** + +```json +{ + "version": "1.0.0", + "build_time": "2025-01-14T08:00:00Z", + "git_commit": "abc123...", + "go_version": "go1.22.0" +} +``` + +### Synchronization + +#### Pull Data + +Pulls records that have changed since the specified `change_id`. + + + + +```bash +curl -X POST http://localhost:8080/sync/pull \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "unique-client-identifier", + "since_change_id": 0, + "schema_types": ["observation"] + }' +``` + + + + +```bash +synk sync pull output.json --client-id unique-client-identifier +``` + + + + +The Portal provides a UI for viewing synchronized data. Use the Observations tab to view pulled data. + + + + +**Request Parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `client_id` | string | Yes | Unique identifier for the client | +| `since_change_id` | integer | No | Last change ID seen by client (default: 0) | +| `schema_types` | array | No | Filter by schema types | + +**Response:** + +```json +{ + "records": [ + { + "id": "obs-123", + "schema_type": "observation", + "schema_version": "1.0.0", + "data": {...}, + "change_id": 1234, + "last_modified": "2025-01-14T10:00:00Z", + "deleted": false + } + ], + "change_cutoff": 1234, + "next_page_token": "eyJ...", + "has_more": false +} +``` + +#### Push Data + +Pushes local records to the server. Requires `read-write` or `admin` role. + + + + +```bash +curl -X POST http://localhost:8080/sync/push \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "transmission_id": "550e8400-e29b-41d4-a716-446655440000", + "client_id": "unique-client-identifier", + "records": [ + { + "id": "obs-123", + "schema_type": "observation", + "schema_version": "1.0.0", + "data": {...} + } + ] + }' +``` + + + + +```bash +synk sync push data.json +``` + +The JSON file should contain observations in the sync format. + + + + +The Portal provides a UI for viewing and managing observations. Data is automatically pushed from Formulus app during sync. + + + + +**Request Parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `transmission_id` | string (UUID) | Yes | Client-generated unique ID for idempotency | +| `client_id` | string | Yes | Unique identifier for the client | +| `records` | array | Yes | Array of records to push | + +**Response:** + +```json +{ + "successes": [ + { + "id": "obs-123", + "change_id": 1234 + } + ], + "failures": [], + "warnings": [], + "change_cutoff": 1234 +} +``` + +### App Bundle Management + +#### Get Manifest + +```http +GET /app-bundle/manifest +GET /api/app-bundle/manifest +Authorization: Bearer +``` + +Returns the current app bundle manifest. + +**Response:** + +```json +{ + "version": "20250114-123456", + "files": [ + { + "path": "index.html", + "hash": "abc123...", + "size": 1024 + } + ] +} +``` + +#### Download File + +```http +GET /app-bundle/download/{path} +GET /api/app-bundle/download/{path} +Authorization: Bearer +``` + +Downloads a specific file from the app bundle. + +#### List Versions + +```http +GET /app-bundle/versions +GET /api/app-bundle/versions +Authorization: Bearer +``` + +Lists all available app bundle versions. + +**Response:** + +```json +{ + "versions": [ + { + "version": "20250114-123456", + "created_at": "2025-01-14T10:00:00Z", + "active": true + } + ] +} +``` + +#### Upload Bundle + +Uploads a new app bundle. Requires `admin` role. + + + + +```bash +curl -X POST http://localhost:8080/api/app-bundle/push \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "bundle=@app-bundle.zip" \ + -F "activate=true" +``` + + + + +```bash +synk bundle push app-bundle.zip --activate +``` + + + + +1. Navigate to the Portal +2. Go to "App Bundles" +3. Click "Upload Bundle" +4. Select your ZIP file +5. Check "Activate immediately" if desired +6. Click "Upload" + + + + +#### Switch Version + +```http +POST /app-bundle/switch/{version} +POST /api/app-bundle/switch/{version} +Authorization: Bearer +``` + +Switches the active app bundle version. Requires `admin` role. + +### Attachments + +#### Get Manifest + +```http +GET /attachments/manifest +Authorization: Bearer + +?after_change_id=0 +``` + +Returns a manifest of attachments that have changed since the specified `change_id`. + +**Query Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `after_change_id` | integer | Only return attachments changed after this ID | + +**Response:** + +```json +{ + "operations": [ + { + "attachment_id": "att-123", + "operation": "upload", + "change_id": 1234 + } + ], + "change_cutoff": 1234 +} +``` + +#### Upload Attachment + +```http +PUT /attachments/{attachment_id} +Authorization: Bearer +Content-Type: application/octet-stream + + +``` + +Uploads an attachment file. Requires `read-write` or `admin` role. + +#### Download Attachment + +```http +GET /attachments/{attachment_id} +Authorization: Bearer + +?quality=medium +``` + +Downloads an attachment file. + +**Query Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `quality` | string | For images: `original`, `large`, `medium`, `small` (default: `medium`) | + +### User Management + +#### Create User + +```http +POST /users +POST /users/create +POST /api/users +POST /api/users/create +Authorization: Bearer +Content-Type: application/json + +{ + "username": "newuser", + "password": "secure-password", + "role": "read-write" +} +``` + +Creates a new user. Requires `admin` role. + +#### List Users + +```http +GET /users +GET /api/users +Authorization: Bearer +``` + +Lists all users. Requires `admin` role. + +#### Delete User + +```http +DELETE /users/delete/{username} +DELETE /api/users/delete/{username} +Authorization: Bearer +``` + +Deletes a user. Requires `admin` role. + +#### Change Password + +```http +POST /users/change-password +POST /api/users/change-password +Authorization: Bearer +Content-Type: application/json + +{ + "current_password": "old-password", + "new_password": "new-password" +} +``` + +Changes the password for the authenticated user. + +#### Reset Password + +```http +POST /users/reset-password +POST /api/users/reset-password +Authorization: Bearer +Content-Type: application/json + +{ + "username": "target-user", + "new_password": "new-password" +} +``` + +Resets a user's password. Requires `admin` role. + +### Data Export + +#### Export to Parquet + +```http +GET /dataexport/parquet +GET /api/dataexport/parquet +Authorization: Bearer +``` + +Exports all observations as a Parquet ZIP archive. Requires `read-only` role or higher. + +**Response:** + +Returns a ZIP file containing Parquet files with observation data. + +## API Versioning + +The API supports versioning through the `x-api-version` header: + +```http +x-api-version: 1.0.0 +``` + +If omitted, the server defaults to the latest stable version. + +### Version Discovery + +```http +GET /api/versions +``` + +Returns all available API versions and their status. + +## Error Handling + +### Error Response Format + +Errors follow RFC 7807 (Problem Details for HTTP APIs): + +```json +{ + "type": "https://synkronus.org/docs/errors/validation", + "title": "Validation Error", + "status": 422, + "detail": "One or more records failed validation", + "errors": [ + { + "recordId": "abc-123", + "path": "data.age", + "message": "Age must be a positive integer", + "code": "TYPE_ERROR" + } + ] +} +``` + +### HTTP Status Codes + +| Code | Description | +|------|-------------| +| `200` | Success | +| `201` | Created | +| `400` | Bad Request | +| `401` | Unauthorized | +| `403` | Forbidden | +| `404` | Not Found | +| `409` | Conflict | +| `422` | Unprocessable Entity | +| `429` | Too Many Requests | +| `500` | Internal Server Error | +| `503` | Service Unavailable | + +## Rate Limiting + +The API implements rate limiting to prevent abuse. When rate limits are exceeded, the server returns `429 Too Many Requests` with a `Retry-After` header indicating when to retry. + +## Pagination + +List endpoints support cursor-based pagination: + +- Include `next_page_token` from previous response +- Server returns `has_more` flag indicating if more data is available +- Default page size: 50 records +- Maximum page size: 500 records + +## ETag Support + +Many endpoints support ETags for caching: + +```http +GET /app-bundle/manifest +If-None-Match: "abc123..." +``` + +If the resource hasn't changed, the server returns `304 Not Modified`. + +## OpenAPI Specification + +The complete OpenAPI specification is available at: + +``` +GET /openapi/synkronus.yaml +``` + +## Related Documentation + +- [Synchronization Guide](/using/synchronization) +- [Configuration Guide](/guides/configuration) +- [Components Reference](/reference/components) diff --git a/docs/reference/app-bundle-format.md b/docs/reference/app-bundle-format.md index 6935551..0fc6fa4 100644 --- a/docs/reference/app-bundle-format.md +++ b/docs/reference/app-bundle-format.md @@ -4,26 +4,212 @@ sidebar_position: 4 # App Bundle Format -Technical specification for app bundle format. +Technical specification for ODE app bundle format. ## Overview -[Description placeholder] +App bundles are ZIP archives containing custom application files, form specifications, and configuration. They are uploaded to the Synkronus server and downloaded by mobile devices during synchronization. ## Bundle Structure -[Description placeholder] +An app bundle is a ZIP file with the following structure: + +``` +app-bundle.zip +├── index.html # Main entry point (required) +├── manifest.json # Bundle metadata (required) +├── assets/ +│ ├── css/ +│ │ └── styles.css +│ ├── js/ +│ │ ├── app.js +│ │ └── formulus-load.js +│ └── images/ +│ └── logo.png +├── forms/ # Form specifications (optional) +│ ├── survey-v1.json +│ └── health-v1.json +└── config/ # Configuration files (optional) + └── settings.json +``` ## Manifest Format -[Description placeholder] +The `manifest.json` file defines bundle metadata: + +```json +{ + "version": "20250114-123456", + "name": "My Custom App", + "description": "Description of the custom application", + "entryPoint": "index.html", + "createdAt": "2025-01-14T10:00:00Z" +} +``` + +### Manifest Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `version` | string | Yes | Version identifier (typically timestamp-based) | +| `name` | string | Yes | Application name | +| `description` | string | No | Application description | +| `entryPoint` | string | Yes | Path to main HTML file (relative to bundle root) | +| `createdAt` | string (ISO 8601) | No | Creation timestamp | + +## Entry Point + +The entry point (`index.html` by default) is the main HTML file loaded when the app bundle is opened. It should: + +1. Include the Formulus load script +2. Initialize the application +3. Use the Formulus JavaScript interface + +**Example:** + +```html + + + + My Custom App + + + + +

My Custom App

+ + + +``` + +## Form Specifications + +Form specifications can be included in the `forms/` directory. Each form file should contain: + +- Schema definition +- UI schema definition +- Form metadata + +**Example form file:** + +```json +{ + "formType": "survey", + "version": "1.0.0", + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + } + } + }, + "uischema": { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + } + ] + } +} +``` ## Versioning -[Description placeholder] +App bundles support versioning: + +- Each upload creates a new version +- Versions are identified by timestamp (format: `YYYYMMDD-HHMMSS`) +- Only one version is active at a time +- Mobile devices download the active version during sync + +### Version Management + +Versions are managed through the API or CLI: + +```bash +# List versions +synk app-bundle versions + +# Switch active version +synk app-bundle switch 20250114-123456 +``` + +## File Requirements + +### Required Files + +- `manifest.json`: Bundle metadata +- `index.html`: Main entry point (or file specified in `entryPoint`) + +### Optional Files + +- `assets/`: CSS, JavaScript, images, and other assets +- `forms/`: Form specification files +- `config/`: Configuration files + +## File Size Limits + +| Component | Limit | Notes | +|-----------|-------|-------| +| **Total bundle size** | 100 MB | Maximum ZIP file size | +| **Individual file** | 50 MB | Maximum size per file | +| **Form specification** | 1 MB | Maximum size per form file | + +## Upload Process + +### Using CLI + +```bash +# Upload bundle +synk app-bundle upload bundle.zip --activate + +# Upload with validation skipped (not recommended) +synk app-bundle upload bundle.zip --skip-validation +``` + +### Using API + +```bash +curl -X POST http://your-server:8080/api/app-bundle/push \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "bundle=@bundle.zip" \ + -F "activate=true" +``` + +## Validation + +Before activation, bundles are validated: + +1. **Structure validation**: Verifies required files exist +2. **Manifest validation**: Validates manifest.json format +3. **Entry point validation**: Verifies entry point file exists +4. **Form validation**: Validates form specification files (if present) + +## Download Process + +Mobile devices download app bundles during synchronization: + +1. Device requests app bundle manifest +2. Server returns current active version +3. Device checks if it has the active version +4. If not, device downloads bundle files +5. Bundle is extracted and cached locally + +## Best Practices -## Related Content +1. **Keep bundles small**: Minimize file sizes for faster downloads +2. **Use relative paths**: All file references should be relative to bundle root +3. **Version consistently**: Use timestamp-based versioning +4. **Test before upload**: Test bundles locally before uploading +5. **Include formulus-load.js**: Always include the Formulus load script +6. **Optimize assets**: Compress images and minify JavaScript/CSS -- [App Bundle Structure](/docs/build/custom-applications/app-bundle-structure) -- [App Bundle API](/docs/reference/rest-api/app-bundle) +## Related Documentation +- [Custom Applications Guide](/guides/custom-applications) +- [API Reference](/reference/api) +- [Form Design Guide](/guides/form-design) diff --git a/docs/reference/components.md b/docs/reference/components.md new file mode 100644 index 0000000..b4a1b64 --- /dev/null +++ b/docs/reference/components.md @@ -0,0 +1,179 @@ +--- +sidebar_position: 2 +--- + +# Components Reference + +Complete reference documentation for all ODE components. + +## Formulus + +Formulus is the mobile application component of ODE, available for Android and iOS devices. + +### Overview + +Formulus is a React Native application that provides offline-first data collection capabilities. It integrates with the Synkronus server for data synchronization and supports custom applications through WebView integration. + +### Key Features + +- **Offline-first architecture**: Data is stored locally using WatermelonDB +- **Automatic synchronization**: Syncs with server when connectivity is available +- **Custom application support**: Runs custom web applications in WebViews +- **JSON Forms integration**: Renders forms using JSON Forms specification +- **Multimedia capture**: Supports photos, audio, video, GPS, and signatures +- **Conflict resolution**: Automatically resolves sync conflicts + +### Installation + +- **End Users**: See [Installing Formulus](/getting-started/installing-formulus) +- **Developers**: See [Installing Formulus for Development](/development/installing-formulus-dev) + +### Configuration + +Configure the app through Settings: + +- **Server URL**: Your Synkronus server address +- **Authentication**: Username and password +- **Sync settings**: Auto-sync, sync interval, sync triggers + +### JavaScript Interface + +Formulus exposes a JavaScript interface for custom applications: + +```javascript +// Get the Formulus API +const api = await getFormulus(); + +// Create observation +await api.addObservation(formType, initializationData); + +// Edit observation +await api.editObservation(formType, observationId); + +// Delete observation +await api.deleteObservation(formType, observationId); +``` + +### Documentation + +- **User Guide**: [Formulus Features](/using/formulus-features) +- **Component Reference**: [Formulus Reference](/reference/formulus) +- **Development**: [Formulus Development](/development/formulus-development) + +## Synkronus + +Synkronus is the server component of ODE, built in Go, that handles data synchronization, storage, and API services. + +### Overview + +Synkronus provides a RESTful API for data synchronization, app bundle management, attachment handling, and user management. It uses PostgreSQL for data storage and JWT for authentication. + +### Key Features + +- **RESTful API**: Comprehensive API for all operations +- **PostgreSQL integration**: Robust data storage +- **Conflict resolution**: Version-based conflict detection and resolution +- **Versioning system**: Supports schema and app bundle versioning +- **Security**: JWT-based authentication with role-based access control +- **Attachment management**: Separate handling of binary files +- **API versioning**: Supports multiple API versions + +### Installation + +See the [Installation guide](/getting-started/installation) for detailed installation instructions. + +### Configuration + +Configure using environment variables: + +- `DB_CONNECTION`: PostgreSQL connection string +- `JWT_SECRET`: Secret for JWT token signing +- `PORT`: HTTP server port (default: 8080) +- `LOG_LEVEL`: Logging level (default: info) + +See the [Configuration guide](/guides/configuration) for complete configuration options. + +### API + +See the [API Reference](/reference/api) for complete API documentation. + +## Synkronus CLI + +Synkronus CLI is a command-line utility for interacting with the Synkronus server. + +### Overview + +The CLI provides convenient access to server operations including authentication, app bundle management, data synchronization, and data export. + +### Installation + +```bash +go install github.com/OpenDataEnsemble/ode/synkronus-cli/cmd/synkronus@latest +``` + +Or build from source: + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +### Key Features + +- **Authentication**: Login, logout, token management +- **App bundle management**: Upload, download, version management +- **Data synchronization**: Push and pull operations +- **Data export**: Export observations as Parquet ZIP archives +- **Configuration management**: Multiple endpoint support + +### Documentation + +- **Complete Reference**: [Synkronus CLI Reference](/reference/synkronus-cli) +- **Common Commands**: See CLI reference for all commands + +## Formplayer + +Formplayer is the web-based form rendering component of ODE. + +### Overview + +Formplayer is a React application that renders JSON Forms and communicates with the Formulus mobile app. It provides the dynamic form interface that powers data collection workflows. + +### Key Features + +- **JSON Forms rendering**: Renders forms based on JSON schema +- **Question type support**: Supports various input types including text, numbers, dates, multimedia, GPS, signatures +- **Validation**: Client-side and schema-based validation +- **Custom renderers**: Support for custom question type renderers +- **Integration**: Seamlessly integrates with Formulus mobile app + +### Documentation + +- **Complete Reference**: [Formplayer Reference](/reference/formplayer) +- **Development**: [Formplayer Development](/development/formplayer-development) + +## Component Integration + +All components work together to provide a complete data collection solution: + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formulus │◄───────►│ Synkronus │◄───────►│ Formulus │ +│ (Mobile) │ Sync │ (Server) │ Sync │ (Mobile) │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formplayer │ │ Database │ │ Formplayer │ +│ (WebView) │ │ (PostgreSQL) │ │ (WebView) │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [API Reference](/reference/api) +- [Form Design Guide](/guides/form-design) +- [Custom Applications Guide](/guides/custom-applications) diff --git a/docs/reference/form-specifications.md b/docs/reference/form-specifications.md index 21b8154..e7acf9f 100644 --- a/docs/reference/form-specifications.md +++ b/docs/reference/form-specifications.md @@ -4,26 +4,335 @@ sidebar_position: 3 # Form Specifications -Technical specifications for ODE forms. +Technical specification for ODE form definitions using JSON schema and JSON Forms. ## Overview -[Description placeholder] +Forms in ODE are defined using two JSON documents: + +1. **Schema**: Defines the data structure and validation rules (JSON Schema) +2. **UI Schema**: Defines how the form is presented to users (JSON Forms UI Schema) + +Both follow established standards: JSON Schema for data validation and JSON Forms for UI specification. ## Schema Format -[Description placeholder] +The schema follows the JSON Schema specification (draft 7). It defines the structure and validation rules for form data. + +### Basic Structure + +```json +{ + "type": "object", + "properties": { + "fieldName": { + "type": "string", + "title": "Field Label", + "description": "Field description" + } + }, + "required": ["fieldName"] +} +``` + +### Property Types + +| Type | Description | Example | +|------|-------------|---------| +| `string` | Text input | Names, descriptions, text fields | +| `integer` | Whole number | Age, count, quantity | +| `number` | Decimal number | Weight, temperature, measurements | +| `boolean` | True/false value | Consent, agreement, flags | +| `array` | List of items | Multiple selections, lists | +| `object` | Nested object | Complex data structures | + +### Validation Rules + +Common validation rules: + +```json +{ + "type": "string", + "minLength": 5, + "maxLength": 100, + "pattern": "^[A-Za-z]+$", + "format": "email" +} +``` + +**Available formats:** +- `email`: Email address validation +- `date`: Date validation (ISO 8601) +- `date-time`: Date and time validation +- `uri`: URI validation +- `uuid`: UUID validation + +**Numeric constraints:** +- `minimum` / `maximum`: For numbers +- `exclusiveMinimum` / `exclusiveMaximum`: Exclude boundary values +- `multipleOf`: Must be multiple of value + +**String constraints:** +- `minLength` / `maxLength`: String length +- `pattern`: Regular expression pattern + +### Enumeration + +Define allowed values: + +```json +{ + "type": "string", + "enum": ["option1", "option2", "option3"], + "enumNames": ["Option 1", "Option 2", "Option 3"] +} +``` ## UI Schema Format -[Description placeholder] +The UI schema follows the JSON Forms UI Schema specification. It controls how form fields are presented and organized. + +### Basic Structure + +```json +{ + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/fieldName" + } + ] +} +``` + +### Layout Types + +| Layout | Description | Use Case | +|--------|-------------|----------| +| `VerticalLayout` | Fields arranged vertically | Default layout | +| `HorizontalLayout` | Fields arranged horizontally | Side-by-side fields | +| `Group` | Group related fields | Logical grouping | +| `Categorization` | Organize into categories | Complex forms with sections | + +### Control Configuration + +```json +{ + "type": "Control", + "scope": "#/properties/fieldName", + "label": "Custom Label", + "options": { + "placeholder": "Enter value", + "format": "password" + } +} +``` + +### Conditional Display + +Show or hide fields based on other field values: + +```json +{ + "type": "Control", + "scope": "#/properties/email", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", + "schema": { + "const": "email" + } + } + } +} +``` + +## Question Types + +Question types are specified using the `format` property in the schema: + +### Text Input + +```json +{ + "type": "string", + "title": "Name", + "format": "text" +} +``` + +### Number Input + +```json +{ + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 +} +``` + +### Date and Time + +```json +{ + "type": "string", + "title": "Date", + "format": "date" +} +``` + +### Selection + +```json +{ + "type": "string", + "title": "Choice", + "enum": ["option1", "option2"], + "enumNames": ["Option 1", "Option 2"] +} +``` + +### Multimedia Types + +#### Photo + +```json +{ + "type": "object", + "format": "photo", + "title": "Profile Photo" +} +``` + +#### Audio + +```json +{ + "type": "string", + "format": "audio", + "title": "Voice Note" +} +``` + +#### Video + +```json +{ + "type": "string", + "format": "video", + "title": "Instructional Video" +} +``` + +#### GPS + +```json +{ + "type": "string", + "format": "gps", + "title": "Current Location" +} +``` + +#### Signature + +```json +{ + "type": "object", + "format": "signature", + "title": "Customer Signature" +} +``` + +#### QR Code + +```json +{ + "type": "string", + "format": "qrcode", + "title": "QR Code Scanner" +} +``` + +#### File Selection + +```json +{ + "type": "string", + "format": "select_file", + "title": "Upload Document" +} +``` + +## Form Versioning + +Forms support versioning to allow updates while maintaining compatibility: -## Core Fields +- Each form has a `schemaType` and `schemaVersion` +- When editing an observation, the form version used to create it is used +- New observations use the latest form version -[Description placeholder] +## Complete Example -## Related Content +```json +{ + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Full Name", + "minLength": 1, + "maxLength": 100 + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + }, + "email": { + "type": "string", + "title": "Email Address", + "format": "email" + }, + "photo": { + "type": "object", + "format": "photo", + "title": "Profile Photo" + } + }, + "required": ["name", "age"] + }, + "uischema": { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/age" + }, + { + "type": "Control", + "scope": "#/properties/email" + }, + { + "type": "Control", + "scope": "#/properties/photo" + } + ] + } +} +``` -- [Form Design](/docs/build/forms/design/schema-definition) -- [JSON Forms](/docs/technical-overview/concepts/json-forms) +## Related Documentation +- [Form Design Guide](/guides/form-design) +- [JSON Forms Documentation](https://jsonforms.io) +- [JSON Schema Documentation](https://json-schema.org) diff --git a/docs/reference/formplayer-contract.md b/docs/reference/formplayer-contract.md new file mode 100644 index 0000000..62d5836 --- /dev/null +++ b/docs/reference/formplayer-contract.md @@ -0,0 +1,1537 @@ +--- +sidebar_position: 7 +--- + +# Formplayer Contract + +**Version:** 1.0 +**Last Updated:** 2024 +**Status:** Authoritative Reference + +This document defines the complete contract for ODE Formplayer, including supported JSON Schema features, UI schema structure, validation rules, and behavioral guarantees. Use this as the definitive reference when creating forms for ODE. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Stability Guarantees](#stability-guarantees) +3. [Why Formplayer Restricts JSON Schema](#why-formplayer-restricts-json-schema) +4. [Entry Point & Initialization](#entry-point--initialization) +5. [JSON Schema Support](#json-schema-support) +6. [UI Schema Structure](#ui-schema-structure) +7. [Validation Layer](#validation-layer) +8. [Custom Formats & Renderers](#custom-formats--renderers) +9. [Rules & Conditional Logic](#rules--conditional-logic) +10. [Renderer Boundaries](#renderer-boundaries) +11. [Error Patterns & Solutions](#error-patterns--solutions) +12. [Safe vs Unsafe Patterns](#safe-vs-unsafe-patterns) +13. [Examples](#examples) +14. [Future Evolution (Non-Contractual)](#future-evolution-non-contractual) + +--- + +## Overview + +**Formplayer** is a React-based form rendering engine that uses: +- **JSON Forms 3.5.1** for form rendering +- **JSON Schema Draft-07** for data validation +- **Ajv 8.17.1** for schema validation +- **Material UI** for component rendering + +**Key Characteristics:** +- All forms are normalized to `SwipeLayout` root (auto-wrapped if needed) +- `Finalize` element is automatically appended to all forms +- Validation occurs both pre-deployment (static) and runtime (dynamic) +- Custom formats require custom renderers registered in the renderer chain + +--- + +## Stability Guarantees + +**Formplayer guarantees correct behavior only when the contract in this document is followed.** + +Forms that violate the contract may: +- Crash at runtime (e.g., "Cannot read properties of undefined") +- Render partially or incorrectly +- Exhibit undefined rule behavior +- Pass validation but fail during rendering +- Produce unexpected validation errors (e.g., "minimum value must be ['number']") + +**Such behavior is considered out of contract and will not be treated as a Formplayer bug.** + +This contract defines the supported subset of JSON Schema and JSON Forms. Forms that exceed these boundaries operate at their own risk. Maintainers should reject forms that violate the contract during pre-deployment validation. + +**For maintainers:** When investigating form-related crashes or errors, first verify the form adheres to this contract. Issues arising from contract violations are not bugs in Formplayer itself. + +--- + +## Why Formplayer Restricts JSON Schema + +Although Formplayer uses JSON Schema Draft-07, it intentionally supports only a subset of the full specification. + +**Reasons for Restrictions:** + +1. **Mobile Performance Constraints** + - Complex schema evaluation (e.g., `$data` references, dynamic `$ref` resolution) is computationally expensive + - Mobile devices need predictable, fast form rendering + - Offline-first execution requires static schema analysis + +2. **Predictable Rendering** + - Formplayer needs to know renderer selection at schema load time + - Dynamic schema features make renderer selection ambiguous + - Predictable structure enables better error handling and user experience + +3. **Offline-First Execution** + - Forms must work without network connectivity + - External `$ref` resolution requires network access + - Static schema validation can be done pre-deployment + +4. **Simpler Mental Model for Form Authors** + - Research teams, medical staff, and field workers need straightforward form design + - Complex JSON Schema features create cognitive overhead + - Clear contract reduces support burden + +5. **Avoiding Runtime Schema Evaluation** + - `$data` references require runtime schema evaluation + - This creates unpredictable validation behavior + - Literal values enable compile-time validation + +**This is a design choice, not a limitation of JSON Schema itself.** + +JSON Schema Draft-07 supports many features that Formplayer intentionally does not. This restriction is by design to ensure reliable, performant form rendering across diverse use cases (research tools, medical records, CHT-like flows, bespoke apps). + +--- + +## Entry Point & Initialization + +### Entry Files + +- **`src/index.tsx`**: React app entry point +- **`src/App.tsx`**: Main component with form initialization logic + +### Initialization Flow + +```typescript +// 1. Form data received via window.onFormInit +// 2. initializeForm() called with FormInitData +// 3. Schema normalization: + setSchema(formSchema) + const swipeLayoutUISchema = ensureSwipeLayoutRoot(uiSchema) + const processedUISchema = processUISchemaWithFinalize(swipeLayoutUISchema) + setUISchema(processedUISchema) +// 4. JsonForms component renders with normalized schemas +``` + +### Auto-Wrapping Behavior + +**Function:** `ensureSwipeLayoutRoot()` (App.tsx:61-103) + +The UI schema root is **always** normalized to `SwipeLayout`: + +| Input | Output | +|-------|--------| +| `null` or `undefined` | `{ type: 'SwipeLayout', elements: [] }` | +| `{ type: 'SwipeLayout', ... }` | Returns as-is | +| `{ type: 'Group', ... }` | Wrapped: `{ type: 'SwipeLayout', elements: [original] }` | +| `{ type: 'VerticalLayout', ... }` | Wrapped: `{ type: 'SwipeLayout', elements: [original] }` | +| `{ type: 'HorizontalLayout', ... }` | Wrapped: `{ type: 'SwipeLayout', elements: [original] }` | +| Array of elements | Wrapped: `{ type: 'SwipeLayout', elements: array }` | + +**Guarantee:** The root UI schema will always be `SwipeLayout` before rendering. + +### Finalize Element Injection + +**Function:** `processUISchemaWithFinalize()` (App.tsx:106-148) + +**Behavior:** +- Removes any existing `Finalize` elements (with warning) +- Always appends `{ type: 'Finalize' }` as the last element +- If no `elements` array exists, creates `VerticalLayout` with just `Finalize` + +**Guarantee:** Every form will have exactly one `Finalize` element as the last page. + +### Validation vs Rendering + +**Pre-Deployment Validation:** +- Static validation via `validate-forms.js` scripts +- Checks schema structure, UI schema format, field references +- **Fails early** - prevents invalid forms from being deployed + +**Runtime Validation:** +- Dynamic validation via Ajv during form interaction +- Validates data against schema (`validationMode="ValidateAndShow"`) +- **Fails late** - shows errors as user interacts with form + +**Rendering:** +- Occurs after normalization +- Uses `JsonForms` component with custom renderers +- Assumes normalized structure (SwipeLayout root, Finalize present) + +--- + +## JSON Schema Support + +### Supported JSON Schema Draft-07 Features + +#### ✅ Fully Supported + +**Root Properties:** +- `$schema`: Must be `"http://json-schema.org/draft-07/schema#"` +- `type`: Must be `"object"` at root level +- `properties`: Required object containing field definitions +- `required`: Array of required field names +- `title`: Form title (displayed in UI) +- `description`: Form description + +**Data Types:** +- `string`, `integer`, `number`, `boolean`, `object`, `array` + +**String Validation:** +- `minLength`: Minimum string length (integer) +- `maxLength`: Maximum string length (integer) +- `pattern`: Regex pattern (string) +- `format`: Built-in formats (see [Custom Formats](#custom-formats--renderers)) + +**Number Validation:** +- `minimum`: Minimum value (**literal number only**) +- `maximum`: Maximum value (**literal number only**) +- `exclusiveMinimum`: Exclusive minimum (**literal number only**) +- `exclusiveMaximum`: Exclusive maximum (**literal number only**) +- `multipleOf`: Must be multiple of value (**literal number only**) + +**Array Validation:** +- `items`: Schema for array items +- `minItems`: Minimum array length +- `maxItems`: Maximum array length +- `uniqueItems`: Boolean (enforces uniqueness) + +**Selection Fields:** +- `enum`: Array of allowed values +- `oneOf`: Array of `{ const: value, title: "Label" }` objects (**recommended**) +- `const`: Single constant value + +**Object Nesting:** +- `type: "object"` with `properties` object +- Nested objects require explicit UI schema (see [Object Handling](#object-handling)) + +**Advanced Features:** +- `allOf`: Array of schemas (all must match) +- `if/then/else`: Conditional schema validation (supported in schema, not in rule conditions) + +#### ❌ NOT Supported / Unsafe + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes all validation keywords (`minimum`, `maximum`, etc.) contain literal values. +> `$data` references or dynamic values will cause Ajv validation errors at runtime. +> Always use literal numbers, strings, or arrays for validation constraints. + +**Unsupported Features:** +- `$data` references: **Will cause runtime errors** + - Error: `"minimum value must be ['number']"` + - Use literal values only +- `$ref` to external schemas: Not validated or resolved +- `anyOf`: Not explicitly supported (use `oneOf` instead) +- `$defs`/`definitions`: Not resolved +- Dynamic constraints: All validation values must be literals + +**Unsafe Patterns:** +```json +// ❌ UNSAFE - $data reference +{ + "type": "integer", + "minimum": { "$data": "/otherField" } +} + +// ✅ SAFE - Literal value +{ + "type": "integer", + "minimum": 0 +} +``` + +### Validator Configuration + +**Ajv Setup** (App.tsx:537-542): +```typescript +const ajv = new Ajv({ + allErrors: true, // Collect all errors, not just first + strictTypes: false, // Allow custom formats without strict type checking +}); +addErrors(ajv); // Enhanced error messages +addFormats(ajv); // Standard format validators (date, email, etc.) +// Custom formats registered separately (see Custom Formats section) +``` + +--- + +## UI Schema Structure + +### Root Layout Types + +**Valid Root Types** (before auto-wrapping): +- `SwipeLayout` (recommended, explicit) +- `VerticalLayout` (auto-wrapped to SwipeLayout) +- `HorizontalLayout` (auto-wrapped to SwipeLayout) +- `Group` (auto-wrapped to SwipeLayout) + +**After Normalization:** +- Always `SwipeLayout` at root + +### Element Types + +#### SwipeLayout + +**Structure:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { "type": "Group", "label": "Page 1", "elements": [...] }, + { "type": "Group", "label": "Page 2", "elements": [...] } + ] +} +``` + +**Required:** +- `type`: `"SwipeLayout"` +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Creates swipeable pages +- Each element in `elements` becomes a page +- Progress indicator shows current page +- Navigation buttons (Previous/Next) provided automatically + +#### VerticalLayout + +**Structure:** +```json +{ + "type": "VerticalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" }, + { "type": "Control", "scope": "#/properties/field2" } + ] +} +``` + +**Required:** +- `type`: `"VerticalLayout"` +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Arranges elements vertically +- Scrollable if content exceeds viewport +- Commonly used inside SwipeLayout pages + +#### HorizontalLayout + +**Structure:** +```json +{ + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" }, + { "type": "Control", "scope": "#/properties/field2" } + ] +} +``` + +**Required:** +- `type`: `"HorizontalLayout"` +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Arranges elements horizontally +- Useful for side-by-side fields + +#### Group + +**Structure:** +```json +{ + "type": "Group", + "label": "Section Title", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" } + ] +} +``` + +**Required:** +- `type`: `"Group"` +- `label`: String (section header) +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Groups related controls +- Displays label as section header +- Can be used as SwipeLayout page (auto-converted) + +#### Control + +**Structure:** +```json +{ + "type": "Control", + "scope": "#/properties/fieldName", + "label": "Custom Label", // Optional, overrides schema title + "options": { // Optional renderer options + "multi": true + }, + "rule": { // Optional conditional logic + "effect": "SHOW", + "condition": { ... } + } +} +``` + +**Required:** +- `type`: `"Control"` +- `scope`: JSON Pointer to schema property (must start with `#/properties/`) + +**Optional:** +- `label`: String or `false` (to hide label) +- `options`: Object with renderer-specific options +- `rule`: Conditional display/enable rule + +**Scope Format:** +- Must start with `#/properties/` +- Examples: + - `"#/properties/name"` - Root property + - `"#/properties/person/properties/age"` - Nested property (requires explicit UI schema) + +#### Label + +**Structure:** +```json +{ + "type": "Label", + "text": "Instructions or information" +} +``` + +**Required:** +- `type`: `"Label"` +- `text`: String to display + +**Behavior:** +- Static text display +- No `elements` array needed +- Useful for instructions or section headers + +### Critical Requirements + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes every layout element has an `elements` array. +> Missing this will cause runtime crashes in some render paths (e.g., "Cannot read properties of undefined (reading 'find')"). +> Always include `elements: []` even if the layout is empty. + +**All Layouts Must Have `elements` Array:** +```json +// ❌ UNSAFE - Missing elements +{ + "type": "Group", + "label": "Section" +} + +// ✅ SAFE - elements array present (even if empty) +{ + "type": "Group", + "label": "Section", + "elements": [] +} +``` + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes all `Control.scope` paths exist in the schema. +> Invalid scopes may cause runtime crashes or silent rendering failures. +> Always validate scope paths exist before deployment. + +**All Scopes Must Exist in Schema:** +- Every `Control.scope` must reference a property in `schema.properties` +- Validation script checks this pre-deployment +- Runtime crashes if scope doesn't exist + +**Nested Objects Require Explicit UI Schema:** +- Object-typed properties don't auto-render nested fields +- Must provide UI schema for each nested property +- See [Object Handling](#object-handling) section + +--- + +## Validation Layer + +### Pre-Deployment Validation + +**Script:** `validate-forms.js` (found in app directories) + +**What's Enforced:** + +#### Schema Validation + +1. **Structure Checks:** + - `$schema` must be `"http://json-schema.org/draft-07/schema#"` + - Root `type` must be `"object"` + - Must have `properties` object + +2. **Compilation Check:** + - Schema must compile with Ajv (structural validity) + - Catches syntax errors, invalid keywords + +#### UI Schema Validation + +1. **Root Type:** + - Must be `SwipeLayout`, `VerticalLayout`, or `HorizontalLayout` + - (Note: Auto-wrapping happens at runtime, but validation checks input) + +2. **Element Types:** + - Valid types: `Control`, `Label`, `VerticalLayout`, `HorizontalLayout`, `SwipeLayout` + - Invalid types are flagged + +3. **Control Elements:** + - Must have `scope` property + - `scope` must start with `#/properties/` + +4. **Layout Elements:** + - `VerticalLayout`, `HorizontalLayout`, `SwipeLayout` must have `elements` array + - Missing `elements` is flagged as error + +5. **Rules:** + - `rule.effect` must be `SHOW`, `HIDE`, `ENABLE`, or `DISABLE` + - `rule.condition.scope` must start with `#/properties/` + +#### Field Reference Validation + +- All `Control.scope` paths must exist in schema properties +- All `rule.condition.scope` paths must exist in schema properties +- Prevents runtime crashes from invalid references + +### Runtime Validation + +**When:** During form interaction (`validationMode="ValidateAndShow"`) + +**What's Validated:** +- Data values against schema constraints +- Required fields +- Type constraints (string, number, etc.) +- Format validators (date, email, etc.) +- Custom format validators (photo, gps, etc.) + +**What's NOT Validated:** +- Schema structure (assumed valid from pre-deployment) +- UI schema structure (assumed valid from pre-deployment) +- Field existence (assumed valid from pre-deployment) + +### What's NOT Enforced (Runtime Only) + +These issues are **not caught** by validation scripts but **will cause runtime errors**: + +1. **`$data` References:** + - Validation script doesn't check for `$data` + - Runtime error: `"minimum value must be ['number']"` + +2. **Missing `elements` on Nested Layouts:** + - Validation only checks root and direct children + - Deeply nested missing `elements` may not be caught + - Runtime error: `"Cannot read properties of undefined (reading 'find')"` + +3. **Invalid Rule Scopes:** + - Validation checks scope format, not existence in all cases + - May cause unexpected rule behavior + +--- + +## Custom Formats & Renderers + +### Supported Custom Formats + +All custom formats are registered in Ajv and have corresponding custom renderers: + +| Format | Schema Type | Renderer | Description | +|--------|-------------|----------|-------------| +| `photo` | `object` | `PhotoQuestionRenderer` | Camera capture | +| `gps` | `string` or `object` | `GPSQuestionRenderer` | GPS coordinates | +| `signature` | `object` | `SignatureQuestionRenderer` | Signature pad | +| `qrcode` | `string` or `object` | `QrcodeQuestionRenderer` | Barcode scanner | +| `audio` | `string` or `object` | `AudioQuestionRenderer` | Audio recording | +| `video` | `string` or `object` | `VideoQuestionRenderer` | Video recording | +| `select_file` | `string` or `object` | `FileQuestionRenderer` | File picker | + +### Format Registration + +**Ajv Registration** (App.tsx:544-551): +```typescript +ajv.addFormat('photo', () => true); // Accepts any value +ajv.addFormat('qrcode', () => true); +ajv.addFormat('signature', () => true); +ajv.addFormat('select_file', () => true); +ajv.addFormat('audio', () => true); +ajv.addFormat('gps', () => true); +ajv.addFormat('video', () => true); +``` + +**Renderer Registration** (App.tsx:164-175): +```typescript +export const customRenderers = [ + { tester: photoQuestionTester, renderer: PhotoQuestionRenderer }, + { tester: qrcodeQuestionTester, renderer: QrcodeQuestionRenderer }, + { tester: signatureQuestionTester, renderer: SignatureQuestionRenderer }, + { tester: fileQuestionTester, renderer: FileQuestionRenderer }, + { tester: audioQuestionTester, renderer: AudioQuestionRenderer }, + { tester: gpsQuestionTester, renderer: GPSQuestionRenderer }, + { tester: videoQuestionTester, renderer: VideoQuestionRenderer }, +]; +``` + +### Format Requirements + +**Photo:** +```json +// Schema +{ + "patient_photo": { + "type": "object", + "format": "photo", + "title": "Patient Photo" + } +} + +// UI Schema +{ + "type": "Control", + "scope": "#/properties/patient_photo" +} +``` + +**GPS:** +```json +// Schema (string format) +{ + "location": { + "type": "string", + "format": "gps", + "title": "Location" + } +} + +// OR (object format) +{ + "location": { + "type": "object", + "format": "gps", + "title": "Location" + } +} + +// UI Schema +{ + "type": "Control", + "scope": "#/properties/location" +} +``` + +**Key Points:** +- ✅ No special UI schema required (standard `Control` is sufficient) +- ✅ Format must be specified in schema +- ✅ Type can be `string` or `object` (depends on format) +- ✅ Custom renderer handles all UI and interaction + +--- + +## Rules & Conditional Logic + +### Rule Structure + +```json +{ + "type": "Control", + "scope": "#/properties/targetField", + "rule": { + "effect": "SHOW|HIDE|ENABLE|DISABLE", + "condition": { + "scope": "#/properties/sourceField", + "schema": { + // Condition schema (const, enum, etc.) + } + } + } +} +``` + +### Rule Effects + +| Effect | Behavior | +|--------|----------| +| `SHOW` | Element is hidden until condition is true | +| `HIDE` | Element is shown until condition is true | +| `ENABLE` | Element is disabled until condition is true | +| `DISABLE` | Element is enabled until condition is true | + +### Condition Schema + +**Supported Condition Types:** + +1. **Constant Match:** +```json +{ + "condition": { + "scope": "#/properties/field", + "schema": { "const": "value" } + } +} +``` + +2. **Enum Match:** +```json +{ + "condition": { + "scope": "#/properties/field", + "schema": { "enum": ["value1", "value2"] } + } +} +``` + +3. **Boolean Match:** +```json +{ + "condition": { + "scope": "#/properties/field", + "schema": { "const": true } + } +} +``` + +### Rule Evaluation + +**How Rules Work:** +- Rules are evaluated by JSON Forms core (not custom Formplayer code) +- Condition is evaluated by validating the referenced field's value against `condition.schema` +- If validation passes, effect is applied +- Evaluation happens on every data change + +**Undefined Scope Behavior:** +- JSON Forms treats undefined scopes as condition success (by default) +- Formplayer doesn't add defensive checks +- **Best Practice:** Ensure all rule condition scopes exist in schema + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes all rule condition scopes exist in the schema. +> Invalid scopes may cause rules to fail silently or exhibit undefined behavior. +> JSON Forms treats undefined scopes as condition success by default, which may not match intended behavior. + +### Critical Requirements + +**Rule Condition Scope Must Exist:** +```json +// ❌ UNSAFE - Scope doesn't exist +{ + "rule": { + "condition": { + "scope": "#/properties/nonexistent", // Field doesn't exist + "schema": { "const": "value" } + } + } +} + +// ✅ SAFE - Scope exists in schema +{ + "schema": { + "properties": { + "hasConsent": { "type": "boolean" } + } + }, + "uischema": { + "rule": { + "condition": { + "scope": "#/properties/hasConsent", // Field exists + "schema": { "const": true } + } + } + } +} +``` + +**Rule Condition Schema Must Be Simple:** +```json +// ❌ UNSAFE - Complex schema not supported +{ + "condition": { + "schema": { + "if": { "type": "string" }, + "then": { "const": "value" } + } + } +} + +// ✅ SAFE - Simple const or enum +{ + "condition": { + "schema": { "const": "value" } + } +} +``` + +### Rule Examples + +**Show When Value Equals:** +```json +{ + "type": "Control", + "scope": "#/properties/detailField", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/showDetail", + "schema": { "const": true } + } + } +} +``` + +**Hide When Value Equals:** +```json +{ + "type": "Control", + "scope": "#/properties/skipReason", + "rule": { + "effect": "HIDE", + "condition": { + "scope": "#/properties/completed", + "schema": { "const": true } + } + } +} +``` + +**Show When One of Multiple Values:** +```json +{ + "type": "Control", + "scope": "#/properties/referralForm", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/testResult", + "schema": { "enum": ["positive", "inconclusive"] } + } + } +} +``` + +**Group-Level Rules:** +```json +{ + "type": "Group", + "label": "TB Screening", + "elements": [...], + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/screeningType", + "schema": { "const": "tb" } + } + } +} +``` + +--- + +## Renderer Boundaries + +### SwipeLayoutRenderer + +**File:** `SwipeLayoutRenderer.tsx` + +**Critical Assumptions:** +- `uischema.elements` exists (defensive: `|| []` at line 53) +- `layouts[currentPage]` exists (uses optional chaining at line 89) +- `layouts.length > 0` checked before rendering (line 126) + +**What Must Exist:** +- `uischema.type` (checked at line 48) +- `uischema.elements` (defensive fallback to `[]`) + +**Where Undefined Causes Crashes:** +- If `uischema` is `null` or `undefined` (shouldn't happen after normalization) +- If `layouts[currentPage]` is accessed when `currentPage >= layouts.length` (guarded by length check) + +### FinalizeRenderer + +**File:** `FinalizeRenderer.tsx` + +**Critical Assumptions:** +- `fullUISchema.elements` exists (guarded at line 139) +- `screen.elements` exists before iteration (guarded at line 153) +- `fullSchema.properties` exists (guarded at line 177) + +**What Must Exist:** +- `fullUISchema` and `fullUISchema.elements` (guarded) +- `screen.elements` for each screen (guarded with `'elements' in screen`) +- `fullSchema.properties` (guarded) + +**Where Undefined Causes Crashes:** +- `screen.elements.find()` if `screen.elements` is undefined (guarded at line 153) +- `fullUISchema.elements.forEach()` if `elements` is missing (guarded at line 139) +- `schema.properties[key]` if `properties` is missing (guarded at line 177) + +### Structural Assumptions + +**Guaranteed by Normalization:** +1. Root is always `SwipeLayout` (via `ensureSwipeLayoutRoot()`) +2. `Finalize` element always present (via `processUISchemaWithFinalize()`) +3. Root has `elements` array (created if missing) + +**Not Guaranteed (Must Be Enforced):** +1. Nested layouts have `elements` arrays (validation script checks, but deep nesting may be missed) +2. All `Control.scope` paths exist (validation script checks) +3. All `rule.condition.scope` paths exist (validation script checks format, not always existence) + +**Defensive Checks in Code:** +- Most renderers use optional chaining (`?.`) and fallbacks (`|| []`) +- Some code paths assume structure exists (crashes if assumption fails) +- **Best Practice:** Always include `elements: []` even if empty + +--- + +## Error Patterns & Solutions + +### Error: "minimum value must be ['number']" + +**Cause:** +- Using `$data` reference or non-numeric value for `minimum`/`maximum` +- Ajv expects literal numbers, not references + +**Example:** +```json +// ❌ CAUSES ERROR +{ + "type": "integer", + "minimum": { "$data": "/otherField" } +} + +// ✅ FIX +{ + "type": "integer", + "minimum": 0 +} +``` + +**Solution:** +- Use literal numbers only for `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` +- If dynamic constraints needed, handle at application level, not schema level + +### Error: "Cannot read properties of undefined (reading 'find')" + +**Cause:** +- Accessing `.find()` or other array methods on undefined `elements` array +- Layout element missing `elements` property + +**Example:** +```json +// ❌ CAUSES ERROR +{ + "type": "Group", + "label": "Section" + // Missing "elements" array +} + +// ✅ FIX +{ + "type": "Group", + "label": "Section", + "elements": [] // Always include, even if empty +} +``` + +**Solution:** +- Always include `elements: []` for all layout types +- Validation script should catch this, but check nested layouts manually +- Defensive code exists in some renderers, but not all paths are protected + +### Error: Rule Condition Fails Unexpectedly + +**Cause:** +- Rule condition scope doesn't exist in schema +- Rule condition scope points to undefined data +- Complex condition schema not supported + +**Example:** +```json +// ❌ CAUSES ISSUES +{ + "rule": { + "condition": { + "scope": "#/properties/nonexistent", // Field doesn't exist + "schema": { "const": "value" } + } + } +} + +// ✅ FIX +// Ensure field exists in schema: +{ + "schema": { + "properties": { + "hasConsent": { "type": "boolean" } + } + }, + "uischema": { + "rule": { + "condition": { + "scope": "#/properties/hasConsent", // Field exists + "schema": { "const": true } + } + } + } +} +``` + +**Solution:** +- Validate all rule condition scopes exist in schema +- Use simple condition schemas (`const` or `enum`) +- Test rules with missing data to verify behavior + +### Error: Object Properties Not Rendering + +**Cause:** +- Object-typed property without explicit UI schema for nested fields +- JSON Forms doesn't auto-render nested object properties + +**Example:** +```json +// ❌ NESTED FIELDS WON'T RENDER +{ + "schema": { + "properties": { + "gps_location": { + "type": "object", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" } + } + } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/gps_location" + // Missing UI schema for nested fields + } +} + +// ✅ FIX - Explicit UI Schema +{ + "uischema": { + "type": "Group", + "label": "GPS Location", + "elements": [ + { "type": "Control", "scope": "#/properties/gps_location/properties/latitude" }, + { "type": "Control", "scope": "#/properties/gps_location/properties/longitude" } + ] + } +} +``` + +**Solution:** +- Provide explicit UI schema for nested object properties +- Use `Group` or `VerticalLayout` to organize nested fields +- Exception: Custom format objects (photo, gps, etc.) don't need nested UI schema + +--- + +## Safe vs Unsafe Patterns + +### Schema Patterns + +#### ✅ Safe: Literal Validation Values +```json +{ + "type": "integer", + "minimum": 0, + "maximum": 100 +} +``` + +#### ❌ Unsafe: $data References +```json +{ + "type": "integer", + "minimum": { "$data": "/otherField" } +} +``` + +#### ✅ Safe: oneOf with const + title +```json +{ + "type": "string", + "oneOf": [ + { "const": "value1", "title": "Display 1" }, + { "const": "value2", "title": "Display 2" } + ] +} +``` + +#### ⚠️ Works but Less Flexible: enum +```json +{ + "type": "string", + "enum": ["value1", "value2"] +} +``` + +### UI Schema Patterns + +#### ✅ Safe: Complete Layout Structure +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "Group", + "label": "Section", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" } + ] + } + ] +} +``` + +#### ❌ Unsafe: Missing elements Array +```json +{ + "type": "Group", + "label": "Section" + // Missing "elements" +} +``` + +#### ✅ Safe: Valid Scope References +```json +{ + "type": "Control", + "scope": "#/properties/existingField" +} +``` + +#### ❌ Unsafe: Invalid Scope References +```json +{ + "type": "Control", + "scope": "#/properties/nonexistentField" +} +``` + +### Rule Patterns + +#### ✅ Safe: Rule with Existing Scope +```json +{ + "schema": { + "properties": { + "hasConsent": { "type": "boolean" } + } + }, + "uischema": { + "rule": { + "condition": { + "scope": "#/properties/hasConsent", + "schema": { "const": true } + } + } + } +} +``` + +#### ❌ Unsafe: Rule with Missing Scope +```json +{ + "rule": { + "condition": { + "scope": "#/properties/nonexistent", + "schema": { "const": "value" } + } + } +} +``` + +#### ✅ Safe: Simple Condition Schema +```json +{ + "condition": { + "schema": { "const": "value" } + } +} +``` + +#### ❌ Unsafe: Complex Condition Schema +```json +{ + "condition": { + "schema": { + "if": { "type": "string" }, + "then": { "const": "value" } + } + } +} +``` + +### Object Handling Patterns + +#### ✅ Safe: Custom Format Object (No Nested UI Needed) +```json +{ + "schema": { + "patient_photo": { + "type": "object", + "format": "photo" + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/patient_photo" + } +} +``` + +#### ✅ Safe: Regular Object with Explicit UI Schema +```json +{ + "schema": { + "gps_location": { + "type": "object", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" } + } + } + }, + "uischema": { + "type": "Group", + "label": "GPS Location", + "elements": [ + { "type": "Control", "scope": "#/properties/gps_location/properties/latitude" }, + { "type": "Control", "scope": "#/properties/gps_location/properties/longitude" } + ] + } +} +``` + +#### ❌ Unsafe: Regular Object without UI Schema +```json +{ + "schema": { + "gps_location": { + "type": "object", + "properties": { + "latitude": { "type": "number" } + } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/gps_location" + // Nested fields won't render + } +} +``` + +--- + +## Examples + +### Example 1: Complete Working Form + +**File:** `demos/demo_malaria_screening/forms/registration/` + +**Schema (`schema.json`):** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Patient Registration", + "type": "object", + "properties": { + "full_name": { + "type": "string", + "title": "Full Name" + }, + "gender": { + "type": "integer", + "title": "Gender", + "oneOf": [ + { "const": 1, "title": "Male" }, + { "const": 2, "title": "Female" } + ] + }, + "age_years": { + "type": "integer", + "title": "Age in years", + "minimum": 0 + }, + "temperature_c": { + "type": "number", + "title": "Temperature (°C)", + "exclusiveMinimum": 25, + "exclusiveMaximum": 46 + } + }, + "required": ["full_name", "gender"] +} +``` + +**UI Schema (`ui.json`):** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/full_name" + }, + { + "type": "Control", + "scope": "#/properties/gender" + }, + { + "type": "Control", + "scope": "#/properties/age_years" + } + ] + }, + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/temperature_c" + } + ] + } + ] +} +``` + +**Why This Works:** +- ✅ Proper `$schema` declaration +- ✅ Root `type: "object"` with `properties` +- ✅ Literal validation values (`minimum`, `exclusiveMinimum`) +- ✅ `oneOf` with `const` + `title` for choices +- ✅ `SwipeLayout` root with `elements` arrays +- ✅ All `Control.scope` paths exist in schema +- ✅ All layouts have `elements` arrays + +### Example 2: Form with Conditional Logic + +**Schema:** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Health Screening", + "type": "object", + "properties": { + "has_cough": { + "type": "string", + "title": "Does patient have cough?", + "oneOf": [ + { "const": "yes", "title": "Yes" }, + { "const": "no", "title": "No" } + ] + }, + "cough_duration": { + "type": "string", + "title": "Cough duration", + "oneOf": [ + { "const": "less_2_weeks", "title": "Less than 2 weeks" }, + { "const": "2_weeks_plus", "title": "2 weeks or more" } + ] + } + }, + "required": ["has_cough"] +} +``` + +**UI Schema:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/has_cough" + }, + { + "type": "Control", + "scope": "#/properties/cough_duration", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/has_cough", + "schema": { "const": "yes" } + } + } + } + ] + } + ] +} +``` + +**Why This Works:** +- ✅ Rule condition scope (`has_cough`) exists in schema +- ✅ Simple condition schema (`const`) +- ✅ Proper rule structure with `effect` and `condition` + +### Example 3: Form with Custom Format + +**Schema:** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Patient Intake", + "type": "object", + "properties": { + "patient_photo": { + "type": "object", + "format": "photo", + "title": "Patient Photo" + }, + "location": { + "type": "string", + "format": "gps", + "title": "Location" + } + } +} +``` + +**UI Schema:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/patient_photo" + }, + { + "type": "Control", + "scope": "#/properties/location" + } + ] + } + ] +} +``` + +**Why This Works:** +- ✅ Custom formats registered in Ajv +- ✅ Custom renderers registered in renderer chain +- ✅ Standard `Control` elements (no special UI schema needed) +- ✅ Format specified in schema + +--- + +## Summary Checklist + +When creating a form, ensure: + +### Schema Checklist +- [ ] `$schema` is `"http://json-schema.org/draft-07/schema#"` +- [ ] Root `type` is `"object"` +- [ ] Has `properties` object +- [ ] All validation values are literals (no `$data`) +- [ ] Use `oneOf` with `const` + `title` for choices (recommended) +- [ ] Nested objects have explicit UI schema (unless custom format) + +### UI Schema Checklist +- [ ] Root is `SwipeLayout` (or will be auto-wrapped) +- [ ] All layouts have `elements` arrays (even if empty) +- [ ] All `Control.scope` paths exist in schema +- [ ] All `Group` elements have `label` +- [ ] Rules reference existing fields +- [ ] Rule condition schemas are simple (`const` or `enum`) + +### Validation Checklist +- [ ] Run `validate-forms.js` script before deployment +- [ ] All field references validated +- [ ] No `$data` references +- [ ] All `elements` arrays present +- [ ] All scopes valid + +--- + +## Future Evolution (Non-Contractual) + +**This section describes potential future improvements that are not currently guaranteed and should not be relied upon.** + +These are areas where Formplayer might evolve as the project matures: + +### Potential Enhancements + +1. **Schema Pre-Validation for Unsupported Keywords** + - Early detection of `$data`, `$ref` to externals, and other unsupported features + - Compile-time rejection of out-of-contract schemas + - Clearer error messages pointing to contract violations + +2. **Stronger UI Schema Structural Validation** + - Deep validation of nested layouts (not just root level) + - Guaranteed `elements` array presence at all nesting levels + - Validation of rule condition scope existence + +3. **Defensive Guards in Renderers** + - More comprehensive optional chaining and fallbacks + - Graceful degradation instead of crashes + - Better error messages when assumptions fail + +4. **Formal Form Compilation Step** + - Pre-compilation of forms to validate contract compliance + - Optimization of renderer selection + - Static analysis of rule conditions + +5. **Extended JSON Schema Support** + - Potential support for `$data` references (with performance trade-offs) + - Support for external `$ref` resolution (with network requirements) + - More complex conditional schemas in rules + +**Important:** These are not commitments. Forms should be designed to work with the current contract. Future enhancements may expand the contract, but backward compatibility with the current contract will be maintained. + +**For maintainers:** When considering new features, evaluate them against: +- Performance impact on mobile devices +- Complexity for form authors +- Offline-first requirements +- Backward compatibility with existing forms + +--- + +## Version History + +- **v1.0** (2024): Initial contract document based on Formplayer codebase analysis + +--- + +## References + +- **JSON Forms Documentation:** https://jsonforms.io +- **JSON Schema Draft-07:** https://json-schema.org/draft-07/schema# +- **Ajv Validator:** https://ajv.js.org +- **Formplayer Source:** `ode/formulus-formplayer/src/` + +--- + +**End of Contract** + diff --git a/docs/reference/formplayer.md b/docs/reference/formplayer.md new file mode 100644 index 0000000..79a914e --- /dev/null +++ b/docs/reference/formplayer.md @@ -0,0 +1,506 @@ +--- +sidebar_position: 6 +--- + +# Formplayer Component Reference + +Complete technical reference for the Formplayer form rendering component. + +:::info[Authoritative Reference Available] + +For the complete, authoritative contract defining all supported JSON Schema features, UI schema structure, validation rules, and behavioral guarantees, see the **[Formplayer Contract](/docs/reference/formplayer-contract)**. + +::: + +## Overview + +Formplayer is a React web application that renders JSON Forms and provides the dynamic form interface for data collection. It runs within WebViews in the Formulus mobile app and communicates with the native app through a JavaScript bridge. + +## Supported Schema & UI Profile + +**Critical**: ODE Formplayer intentionally supports a safe, predictable subset of JSON Schema and JSON Forms. Forms outside this profile may load but are **not guaranteed to work**. + +### Supported JSON Schema Features + +| Feature | Supported | Notes | +|---------|-----------|-------| +| `type` | ✅ | `string`, `number`, `integer`, `boolean`, `object`, `array` | +| `properties` | ✅ | Object property definitions | +| `required` | ✅ | Array of required property names | +| `minimum` / `maximum` | ✅ | Literal numbers only (e.g., `"minimum": 0`) | +| `minLength` / `maxLength` | ✅ | String length constraints | +| `pattern` | ✅ | Regular expression patterns | +| `format` | ✅ | `email`, `date`, `date-time`, `uri`, `uuid` | +| `enum` | ✅ | Array of allowed values | +| `enumNames` | ✅ | Display names for enum values | +| `oneOf` | ✅ | Recommended for single-choice selections | +| `title` | ✅ | Field display title | +| `description` | ✅ | Field description/help text | +| `default` | ✅ | Default values | +| `const` | ✅ | Constant values (used in rules) | +| `$data` | ❌ | **Will crash** - not supported | +| `if` / `then` / `else` | ❌ | **Not supported** in rule conditions | +| `$ref` | ⚠️ | Limited support - use with caution | +| `allOf` / `anyOf` | ⚠️ | Limited support - use with caution | + +### Supported UI Schema Elements + +| Element | Required Fields | Notes | +|---------|----------------|-------| +| **SwipeLayout** | `type`, `elements[]` | Root layout (required or auto-wrapped) | +| **VerticalLayout** | `type`, `elements[]` | Vertical field arrangement | +| **HorizontalLayout** | `type`, `elements[]` | Horizontal field arrangement | +| **Group** | `type`, `label`, `elements[]` | Grouped fields with label | +| **Control** | `type`, `scope` | Field control (scope must exist in schema) | +| **Label** | `type`, `text` | Text label element | + +### Unsupported / Unsafe Features + +**❌ JSON Schema:** +- `$data` references (dynamic values) +- `if`/`then`/`else` conditional schemas +- Complex `$ref` resolution +- `allOf`/`anyOf` (limited support) + +**❌ UI Schema:** +- Missing `elements` array in layouts +- Invalid `scope` paths (referencing non-existent schema properties) +- Rules referencing missing fields +- Nested SwipeLayout +- `Categorization` layout (not recommended) + +**⚠️ Common Pitfalls:** +- Using `$data` in `minimum`/`maximum` (use literal numbers) +- Rule conditions with scopes that don't exist in schema +- Controls with scopes pointing to non-existent properties +- Missing `elements` array in SwipeLayout or other layouts + +### Safe Form Patterns + +**✅ Recommended Structure:** +```json +{ + "schema": { + "type": "object", + "properties": { + "field1": { "type": "string", "title": "Field 1" }, + "field2": { "type": "integer", "minimum": 0, "maximum": 100 } + }, + "required": ["field1"] + }, + "uischema": { + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/field1" + }, + { + "type": "Control", + "scope": "#/properties/field2" + } + ] + } + ] + } +} +``` + +**❌ Unsafe Patterns:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/missingField" // ❌ Field doesn't exist + } +} +``` + +```json +{ + "schema": { + "properties": { + "value": { + "type": "number", + "minimum": { "$data": "#/minValue" } // ❌ $data not supported + } + } + } +} +``` + +## Architecture + +### Technology Stack + +- **Framework**: React +- **Language**: TypeScript +- **Form Library**: JSON Forms +- **UI Framework**: Material-UI (via JSON Forms) +- **Build Tool**: Webpack/Vite + +### Component Structure + +``` +formulus-formplayer/ +├── src/ +│ ├── App.tsx # Main application component +│ ├── FormLayout.tsx # Form layout renderer +│ ├── QuestionShell.tsx # Question wrapper component +│ ├── *QuestionRenderer.tsx # Question type renderers +│ ├── FormulusInterface.ts # Bridge interface definition +│ └── theme.ts # Theming configuration +├── public/ +│ └── formulus-load.js # API loading script +└── build/ # Production build output +``` + +## Core Responsibilities + +Formplayer is responsible for: + +1. **Form Rendering**: Render forms based on JSON schema and UI schema +2. **Data Collection**: Capture user input through various question types +3. **Validation**: Validate form responses against schema rules +4. **Observation Management**: Create, edit, and delete observations +5. **Draft Management**: Save and load draft observations + +## Integration with Formulus + +### Initialization + +Formplayer is initialized by the Formulus app with: + +- **Renderers**: Container components for form layout +- **Cells**: Question type components (text, date, photo, etc.) +- **Form Specs**: JSON form specifications from server +- **Formulus API**: JavaScript interface to native app + +### Communication Model + +``` +┌─────────────────┐ ┌──────────────────┐ +│ Formulus │ │ Formplayer │ +│ (Native) │◄───────►│ (WebView) │ +│ │ │ │ +│ • Database │ │ • Form Render │ +│ • Sync Engine │ │ • Validation │ +│ • API Bridge │ │ • User Input │ +└─────────────────┘ └──────────────────┘ +``` + +## JavaScript Interface + +Formplayer exposes methods to custom applications and receives configuration from Formulus. + +### Available Methods + +#### addObservation(formType, initializationData) + +Open a form to create a new observation. + +```javascript +window.formulus.formplayer.addObservation('survey', { + participantId: '123', + location: 'Field Site A' +}); +``` + +**Parameters:** +- `formType` (string): Form type identifier +- `initializationData` (object): Optional pre-population data + +#### editObservation(formType, observationId) + +Open a form to edit an existing observation. + +```javascript +window.formulus.formplayer.editObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): Form type identifier +- `observationId` (string): Observation ID to edit + +#### deleteObservation(formType, observationId) + +Delete an observation. + +```javascript +window.formulus.formplayer.deleteObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): Form type identifier +- `observationId` (string): Observation ID to delete + +## Question Types + +Formplayer supports various question types through custom renderers: + +### Text Input + +- **Single-line text**: Standard text input +- **Multi-line text**: Textarea for longer responses +- **Email**: Email format validation +- **Phone**: Phone number format validation +- **URL**: URL format validation + +### Number Input + +- **Integer**: Whole numbers only +- **Decimal**: Floating-point numbers +- **Range**: Min/max value constraints + +### Date and Time + +- **Date**: Date picker +- **Time**: Time picker +- **DateTime**: Combined date and time picker + +### Selection + +- **Single Select**: Dropdown or radio buttons +- **Multi Select**: Checkboxes for multiple choices + +### Boolean + +- **Checkbox**: True/false or yes/no + +### Media Capture + +- **Photo**: Camera capture or gallery selection +- **Audio**: Voice recording +- **Video**: Video recording +- **File**: File attachment + +### Special Input + +- **GPS**: Location coordinates capture +- **Signature**: Digital signature pad +- **QR Code**: Barcode/QR code scanner + +## Form Rendering + +### Schema Processing + +Formplayer processes JSON schemas to: + +1. **Parse Schema**: Extract form structure and validation rules +2. **Process UI Schema**: Apply layout and presentation rules +3. **Generate Form**: Create form components based on schema +4. **Apply Validation**: Set up validation rules + +### Layout System + +Forms can use different layout strategies: + +- **Vertical Layout**: Fields stacked vertically +- **Horizontal Layout**: Fields arranged horizontally +- **Group Layout**: Fields grouped in sections +- **Categorization**: Fields organized in tabs or categories + +## Validation + +### Schema Validation + +Formplayer validates form responses against: + +- **Required Fields**: Ensure required fields are filled +- **Type Validation**: Verify data types match schema +- **Format Validation**: Check format constraints (email, URL, etc.) +- **Range Validation**: Verify numeric ranges +- **Pattern Validation**: Match against regex patterns + +### Custom Validation + +Custom validation rules can be added: + +- **Conditional Validation**: Rules based on other field values +- **Cross-field Validation**: Validation across multiple fields +- **Async Validation**: Server-side validation support + +## Draft Management + +### Saving Drafts + +Formplayer can save incomplete forms as drafts: + +1. **Auto-save**: Periodically save form state +2. **Manual Save**: User-triggered draft save +3. **Local Storage**: Drafts stored in WebView storage + +### Loading Drafts + +When editing an observation: + +1. **Load Data**: Fetch observation data from Formulus +2. **Populate Form**: Pre-fill form fields with data +3. **Restore State**: Restore form state and validation + +## Theming + +### Theme Configuration + +Formplayer uses a theme system for styling: + +- **Colors**: Primary, secondary, and accent colors +- **Typography**: Font families and sizes +- **Spacing**: Margins and padding +- **Components**: Component-specific styling + +### Custom Theming + +Themes can be customized: + +- **Brand Colors**: Match organization branding +- **Custom Styles**: Override default component styles +- **Responsive Design**: Adapt to different screen sizes + +## Building and Deployment + +### Development Build + +```bash +# Install dependencies +npm install + +# Start development server +npm start + +# Opens at http://localhost:3000 +``` + +### Production Build + +```bash +# Build for React Native (Formulus) +npm run build:rn + +# Build for web +npm run build +``` + +### Build Output + +The build process: + +1. **Compiles TypeScript**: Transpiles to JavaScript +2. **Bundles Assets**: Combines CSS and images +3. **Minifies Code**: Optimizes for production +4. **Copies to Formulus**: Copies build to Formulus app + +## Integration with Custom Applications + +### Loading Formplayer + +Custom applications can use Formplayer: + +```html + + +``` + +### Formplayer API + +The Formplayer API is injected into custom app WebViews: + +```javascript +// Access Formplayer methods +window.formulus.formplayer.addObservation('survey', {}); +``` + +## Question Type Renderers + +### Core Renderers + +Formplayer includes core question type renderers: + +- `TextQuestionRenderer`: Text input fields +- `NumberQuestionRenderer`: Numeric input fields +- `DateQuestionRenderer`: Date picker +- `SelectQuestionRenderer`: Dropdown selections +- `PhotoQuestionRenderer`: Camera capture +- `GPSQuestionRenderer`: Location capture +- `SignatureQuestionRenderer`: Digital signature +- `AudioQuestionRenderer`: Voice recording +- `VideoQuestionRenderer`: Video recording +- `FileQuestionRenderer`: File attachment +- `QrcodeQuestionRenderer`: QR code scanner + +### Custom Renderers + +Custom renderers can be added: + +1. **Create Renderer Component**: Implement question type component +2. **Register Renderer**: Add to Formplayer configuration +3. **Use in Forms**: Reference in form schema + +## Error Handling + +### Validation Errors + +Formplayer displays validation errors: + +- **Field-level Errors**: Shown below invalid fields +- **Form-level Errors**: Shown at form level +- **Error Messages**: User-friendly error messages + +### Runtime Errors + +Error handling for: + +- **API Errors**: Network and server errors +- **Data Errors**: Invalid data format +- **Render Errors**: Component rendering failures + +## Performance + +### Optimization Strategies + +- **Lazy Loading**: Load form components on demand +- **Code Splitting**: Split bundle for faster loading +- **Memoization**: Cache form components +- **Virtual Scrolling**: For long forms + +### Best Practices + +- **Minimize Form Size**: Keep forms focused +- **Optimize Images**: Compress images in forms +- **Efficient Validation**: Validate only when needed +- **Cache Form Specs**: Cache parsed form specifications + +## Development + +### Local Development + +1. **Start Dev Server**: `npm start` +2. **Open Browser**: Navigate to `http://localhost:3000` +3. **Hot Reload**: Changes reflect automatically + +### Testing + +- **Unit Tests**: Test individual components +- **Integration Tests**: Test form rendering +- **E2E Tests**: Test complete form workflows + +## Related Documentation + +- [Form Design Guide](/guides/form-design) - Creating form schemas +- [Custom Applications Guide](/guides/custom-applications) - Building custom apps +- [Formulus Reference](/reference/formulus) - Mobile app component +- [Form Specifications](/reference/form-specifications) - Schema format + diff --git a/docs/reference/formulus.md b/docs/reference/formulus.md new file mode 100644 index 0000000..bcfd467 --- /dev/null +++ b/docs/reference/formulus.md @@ -0,0 +1,367 @@ +--- +sidebar_position: 5 +--- + +# Formulus Component Reference + +Complete technical reference for the Formulus mobile application component. + +## Overview + +Formulus is a React Native mobile application that serves as the client-side component of ODE. It provides offline-first data collection capabilities, custom application hosting, and bidirectional synchronization with the Synkronus server. + +## Architecture + +### Technology Stack + +- **Framework**: React Native +- **Language**: TypeScript +- **Database**: WatermelonDB (SQLite-based) +- **State Management**: React Context API +- **Navigation**: React Navigation +- **WebView**: React Native WebView (for custom apps) + +### Component Structure + +``` +formulus/ +├── src/ +│ ├── api/ # Synkronus API client (auto-generated) +│ ├── components/ # React Native UI components +│ ├── contexts/ # React Context providers +│ ├── database/ # WatermelonDB schema and models +│ ├── hooks/ # Custom React hooks +│ ├── navigation/ # Navigation configuration +│ ├── screens/ # Screen components +│ ├── services/ # Business logic services +│ ├── webview/ # WebView integration and bridge +│ └── utils/ # Utility functions +├── android/ # Android native code +├── ios/ # iOS native code +└── assets/ # Static assets +``` + +## Core Features + +### Offline-First Data Storage + +Formulus uses WatermelonDB for local data storage: + +- **Observations**: All form submissions stored locally +- **Attachments**: Binary files stored in local filesystem +- **App Bundles**: Custom application files cached locally +- **Sync State**: Tracks synchronization status + +### Custom Application Hosting + +Formulus hosts custom web applications in WebViews: + +- **WebView Integration**: React Native WebView component +- **JavaScript Bridge**: Communication between native and web +- **Formulus API**: Injected JavaScript interface for custom apps +- **Asset Loading**: Serves app bundle files from local storage + +### Synchronization Engine + +Two-phase synchronization protocol: + +1. **Observation Sync**: JSON metadata synchronization +2. **Attachment Sync**: Binary file synchronization + +### Form Rendering + +Integration with Formplayer for form rendering: + +- **Formplayer WebView**: Renders forms using JSON Forms +- **Question Types**: Supports various input types +- **Validation**: Client-side and schema-based validation +- **Data Binding**: Connects form data to observations + +## JavaScript Interface + +Formulus exposes a JavaScript API to custom applications running in WebViews. + +### API Access + +The API is automatically injected into WebViews. Use the helper function to ensure it's ready: + +```javascript +// Wait for API to be ready +const api = await getFormulus(); + +// Now use the API +const version = await api.getVersion(); +``` + +### Core Methods + +#### getVersion() + +Get the Formulus host version. + +```javascript +const version = await api.getVersion(); +// Returns: "1.0.0" +``` + +#### addObservation(formType, initializationData) + +Create a new observation by opening a form. + +```javascript +await api.addObservation('survey', { + participantId: '123', + location: 'Field Site A' +}); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `initializationData` (object): Optional data to pre-populate the form + +**Returns:** Promise that resolves when form is opened + +#### editObservation(formType, observationId) + +Edit an existing observation. + +```javascript +await api.editObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `observationId` (string): The observation ID to edit + +**Returns:** Promise that resolves when form is opened + +#### deleteObservation(formType, observationId) + +Delete an observation. + +```javascript +await api.deleteObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `observationId` (string): The observation ID to delete + +**Returns:** Promise that resolves when deletion is complete + +#### getObservations(formType, filters) + +Query observations from local database. + +```javascript +const observations = await api.getObservations('survey', { + status: 'synced', + limit: 10 +}); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `filters` (object): Optional query filters + +**Returns:** Promise resolving to array of observations + +#### sync() + +Trigger manual synchronization. + +```javascript +await api.sync(); +``` + +**Returns:** Promise that resolves when sync completes + +## Database Schema + +### Observations Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | string | Unique observation identifier | +| `form_type` | string | Form type identifier | +| `data` | JSON | Observation data (form responses) | +| `created_at` | timestamp | Creation timestamp | +| `updated_at` | timestamp | Last update timestamp | +| `deleted` | boolean | Soft delete flag | +| `_status` | string | Sync status (created, updated, deleted) | +| `_changed` | string | Changed fields tracking | + +### Attachments Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | string | Unique attachment identifier | +| `observation_id` | string | Reference to observation | +| `file_path` | string | Local file path | +| `mime_type` | string | File MIME type | +| `size` | number | File size in bytes | +| `synced` | boolean | Sync status | + +## Synchronization Protocol + +### Two-Phase Sync + +#### Phase 1: Observation Sync + +1. **Pull**: Request changes from server since last sync +2. **Apply**: Apply server changes to local database +3. **Push**: Send local changes to server +4. **Resolve Conflicts**: Handle conflicts if any + +#### Phase 2: Attachment Sync + +1. **Download Manifest**: Get list of attachments to download +2. **Download Files**: Download missing attachments +3. **Upload Files**: Upload pending attachments +4. **Update Status**: Mark attachments as synced + +### Sync State Management + +The app maintains sync state: + +- **Last Sync Timestamp**: When last sync completed +- **Pending Observations**: Count of unsynced observations +- **Sync Status**: Current sync state (idle, syncing, error) + +## Configuration + +### Server Configuration + +Configured through Settings screen: + +- **Server URL**: Synkronus server address +- **Username**: User credentials +- **Password**: User password +- **Auto-login**: Enable automatic login + +### Sync Configuration + +- **Auto-sync**: Enable automatic synchronization +- **Sync Interval**: How often to check for sync +- **WiFi Only**: Sync only on WiFi connection + +## Development + +### Building from Source + +See [Formulus Development Guide](/development/formulus-development) for complete development setup. + +### Key Development Commands + +```bash +# Install dependencies +npm install + +# Start Metro bundler +npm start + +# Run on Android +npm run android + +# Run on iOS +npm run ios + +# Generate API client from OpenAPI spec +npm run generate:api + +# Generate WebView injection script +npm run generate +``` + +### Project Structure + +- **Android**: Native Android code in `android/` directory +- **iOS**: Native iOS code in `ios/` directory +- **Source**: TypeScript source in `src/` directory +- **Assets**: Static assets in `assets/` directory + +## Platform-Specific Features + +### Android + +- **File System Access**: Direct access to Android storage +- **Camera Integration**: Native camera API +- **GPS**: Android location services +- **Permissions**: Android permission system + +### iOS + +- **File System Access**: iOS file system access +- **Camera Integration**: Native camera API +- **GPS**: iOS Core Location +- **Permissions**: iOS permission system + +## Security + +### Authentication + +- **JWT Tokens**: Stored securely in device keychain/keystore +- **Token Refresh**: Automatic token refresh before expiration +- **Secure Storage**: Credentials stored using platform secure storage + +### Data Protection + +- **Local Encryption**: Sensitive data encrypted at rest +- **Secure Communication**: HTTPS for all server communication +- **Permission Management**: Granular permission requests + +## Performance + +### Optimization Strategies + +- **Lazy Loading**: Load forms and data on demand +- **Caching**: Aggressive caching of app bundles and assets +- **Background Sync**: Sync in background to avoid blocking UI +- **Database Indexing**: Optimized database queries + +### Memory Management + +- **Image Compression**: Compress images before storage +- **Attachment Cleanup**: Remove old attachments after sync +- **Cache Limits**: Limit cache size to prevent memory issues + +## Troubleshooting + +### Common Issues + +#### Sync Failures + +- Check network connectivity +- Verify server URL and credentials +- Review sync logs for errors +- Check server status + +#### App Crashes + +- Review crash logs +- Check memory usage +- Verify database integrity +- Update to latest version + +#### Performance Issues + +- Clear app cache +- Check database size +- Review attachment storage +- Optimize app bundle size + +## API Reference + +For complete API documentation, see: + +- [Synkronus API Reference](/reference/api) - Server API endpoints +- [Custom Applications Guide](/guides/custom-applications) - Building custom apps +- [Form Design Guide](/guides/form-design) - Creating forms + +## Related Documentation + +- [Formulus Features](/using/formulus-features) - User-facing features +- [Installing Formulus](/getting-started/installing-formulus) - Installation guide +- [Formulus Development](/development/formulus-development) - Development setup +- [Synchronization](/using/synchronization) - Sync protocol details + diff --git a/docs/reference/index.md b/docs/reference/index.md index 9c3d8be..6a3d53b 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,35 +1,132 @@ --- -sidebar_position: 1 +sidebar_position: 0 --- -# Reference - -API reference and technical specifications. - -## Overview - -Complete reference documentation for ODE APIs and configurations. - - - - - - - +# Reference Documentation +Complete technical reference for all ODE components, APIs, and specifications. + +## API & Components + +
+
+
+
+

API Reference

+
+
+

Complete REST API reference for the Synkronus server.

+ View API → +
+
+
+
+
+
+

Components

+
+
+

Overview of ODE components and their interactions.

+ View Components → +
+
+
+
+ +## ODE Components + +
+
+
+
+

Formulus

+
+
+

Mobile application for Android and iOS for data collection.

+ View Docs → +
+
+
+
+
+
+

Synkronus Server

+
+
+

Backend server for data synchronization and app bundle management.

+ View Docs → +
+
+
+
+
+
+

Synkronus CLI

+
+
+

Command-line interface for server management and administration.

+ View Docs → +
+
+
+
+
+
+

Formplayer

+
+
+

Web-based form rendering engine for interactive forms.

+ View Docs → +
+
+
+
+
+
+

Formplayer Contract

+
+
+

Authoritative reference for JSON Schema support, UI schema structure, and validation rules.

+ View Contract → +
+
+
+
+
+
+

Synkronus Portal

+
+
+

Web portal for managing users and viewing data.

+ View Docs → +
+
+
+
+ +## Specifications + +
+
+
+
+

Form Specifications

+
+
+

Complete reference for form schema definitions and validation rules.

+ View Specs → +
+
+
+
+
+
+

App Bundle Format

+
+
+

Documentation for app bundle structure and manifest format.

+ View Format → +
+
+
+
diff --git a/docs/reference/synkronus-cli.md b/docs/reference/synkronus-cli.md new file mode 100644 index 0000000..c7c6d7a --- /dev/null +++ b/docs/reference/synkronus-cli.md @@ -0,0 +1,476 @@ +--- +sidebar_position: 7 +--- + +# Synkronus CLI Reference + +Complete technical reference for the Synkronus command-line interface tool. + +## Overview + +Synkronus CLI (`synk`) is a command-line utility for interacting with the Synkronus API. It provides functionality for authentication, data synchronization, app bundle management, user administration, and data export. + +## Installation + +### From Source + +```bash +go install github.com/OpenDataEnsemble/ode/synkronus-cli/cmd/synkronus@latest +``` + +### Build from Source + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +### Verify Installation + +```bash +synk --version +``` + +## Configuration + +### Configuration File + +The CLI uses a YAML configuration file located at `$HOME/.synkronus.yaml` by default. + +**Example configuration:** + +```yaml +api: + url: http://localhost:8080 + version: 1.0.0 +``` + +### Multiple Endpoints + +You can manage multiple server endpoints: + +```bash +# Create separate config files +synk config init -o ~/.synkronus-dev.yaml +synk config init -o ~/.synkronus-prod.yaml + +# Set default config +synk config use ~/.synkronus-dev.yaml + +# Override for single command +synk --config ~/.synkronus-prod.yaml status +``` + +### Configuration Commands + +#### Initialize Config + +```bash +synk config init -o ~/.synkronus.yaml +``` + +#### Use Config + +```bash +synk config use ~/.synkronus.yaml +``` + +#### Show Current Config + +```bash +synk config show +``` + +## Authentication + +### Login + +Authenticate with the Synkronus server: + +```bash +synk login --username your-username +``` + +The CLI will prompt for password interactively. Authentication tokens are stored in the configuration file. + +### Check Status + +Verify authentication status: + +```bash +synk status +``` + +**Output:** +``` +Authenticated as: your-username +Server: http://localhost:8080 +API Version: 1.0.0 +Token expires: 2025-01-15 10:30:00 +``` + +### Logout + +Clear authentication: + +```bash +synk logout +``` + +## App Bundle Management + +### List Versions + +List all available app bundle versions: + +```bash +synk app-bundle versions +``` + +**Output:** +``` +Available app bundle versions: + 20250114-123456 * (current) + 20250113-120000 + 20250112-110000 +``` + +### Get Manifest + +Get the current app bundle manifest: + +```bash +synk app-bundle manifest +``` + +### Download Bundle + +Download the entire app bundle: + +```bash +synk app-bundle download --output ./app-bundle +``` + +### Download Specific File + +Download a specific file from the bundle: + +```bash +synk app-bundle download index.html +synk app-bundle download assets/css/styles.css +``` + +### Upload Bundle + +Upload a new app bundle (admin only): + +```bash +synk app-bundle upload bundle.zip +``` + +**Options:** +- `--activate`: Automatically activate the uploaded bundle +- `--skip-validation`: Skip bundle validation (not recommended) +- `--verbose`: Show detailed output + +**Example:** +```bash +synk app-bundle upload bundle.zip --activate --verbose +``` + +### Switch Version + +Switch to a specific app bundle version (admin only): + +```bash +synk app-bundle switch 20250114-123456 +``` + +## Data Synchronization + +### Pull Data + +Pull data from the server: + +```bash +synk sync pull output.json --client-id your-client-id +``` + +**Options:** +- `--client-id`: Client identifier (required) +- `--current-version`: Last known version number +- `--after-change-id`: Pull changes after specific change ID +- `--schema-types`: Filter by schema types (comma-separated) +- `--limit`: Maximum number of records to pull + +**Examples:** +```bash +# Pull all data +synk sync pull data.json --client-id client-123 + +# Pull with filters +synk sync pull data.json --client-id client-123 \ + --after-change-id 1234 \ + --schema-types survey,visit \ + --limit 100 +``` + +### Push Data + +Push data to the server: + +```bash +synk sync push data.json +``` + +The JSON file should contain observations in the sync format. + +## Data Export + +### Export Observations + +Export all observations as a Parquet ZIP archive: + +```bash +synk data export exports.zip +``` + +**Options:** +- `--output` or `-o`: Output file path +- `--format`: Export format (parquet, json, csv) + +**Examples:** +```bash +# Export to Parquet ZIP +synk data export observations.zip + +# Export to specific directory +synk data export ./backups/observations_20250114.zip + +# Export to JSON +synk data export observations.json --format json +``` + +## User Management + +### Create User + +Create a new user (admin only): + +```bash +synk user create --username newuser --password secret --role read-write +``` + +**Options:** +- `--username`: Username (required) +- `--password`: Password (required) +- `--role`: User role (read-only, read-write, admin) + +### List Users + +List all users: + +```bash +synk user list +``` + +### Get User + +Get user details: + +```bash +synk user get username +``` + +### Update User + +Update user information: + +```bash +synk user update username --password newpassword --role read-write +``` + +### Delete User + +Delete a user: + +```bash +synk user delete username +``` + +## QR Code Generation + +### Generate Login QR Code + +Generate a QR code for Formulus app configuration: + +```bash +synk qr login --output login-qr.png +``` + +**Options:** +- `--output` or `-o`: Output file path +- `--server-url`: Override server URL +- `--username`: Username to include +- `--password`: Password to include + +### Generate Admin QR Code + +Generate admin configuration QR code: + +```bash +synk qr admin --output admin-qr.png +``` + +## Shell Completion + +The CLI supports shell completion for bash, zsh, fish, and PowerShell. + + + + +```bash +# Current session +source <(synk completion bash) + +# Persistent (Linux) +sudo synk completion bash > /etc/bash_completion.d/synk + +# Persistent (macOS) +synk completion bash > /usr/local/etc/bash_completion.d/synk +``` + + + + +```bash +# Current session +source <(synk completion zsh) + +# Persistent +echo "[[ \$commands[synk] ]] && synk completion zsh > \"\${fpath[1]}/_synk\"" >> ~/.zshrc +``` + + + + +```bash +# Current session +synk completion fish | source + +# Persistent +synk completion fish > ~/.config/fish/completions/synk.fish +``` + + + + +```powershell +# Current session +synk completion powershell | Out-String | Invoke-Expression + +# Persistent (add to profile) +Add-Content -Path $PROFILE -Value "synk completion powershell | Out-String | Invoke-Expression" +``` + + + + +## Command Reference + +### Global Flags + +- `--config`: Specify configuration file +- `--verbose` or `-v`: Verbose output +- `--help` or `-h`: Show help +- `--version`: Show version + +### Command Structure + +``` +synk [global-flags] [command-flags] [arguments] +``` + +## Examples + +### Complete Workflow + +```bash +# 1. Configure CLI +synk config init -o ~/.synkronus.yaml + +# 2. Login +synk login --username admin + +# 3. Upload app bundle +synk app-bundle upload bundle.zip --activate + +# 4. Create user +synk user create --username fieldworker --password secret --role read-write + +# 5. Generate QR code for user +synk qr login --username fieldworker --password secret --output qr.png + +# 6. Export data +synk data export observations.zip +``` + +### Development Workflow + +```bash +# Switch to dev server +synk config use ~/.synkronus-dev.yaml + +# Test sync +synk sync pull test.json --client-id test-client + +# Upload test bundle +synk app-bundle upload test-bundle.zip --activate --verbose +``` + +## Error Handling + +### Common Errors + +**Authentication Failed:** +``` +Error: authentication failed: invalid credentials +``` +Solution: Verify username and password, check server is running + +**Server Unreachable:** +``` +Error: unable to connect to server +``` +Solution: Check server URL, verify network connectivity + +**Permission Denied:** +``` +Error: permission denied: admin role required +``` +Solution: Use admin account or request permissions + +### Debug Mode + +Enable verbose output for debugging: + +```bash +synk --verbose +``` + +## Best Practices + +1. **Use Configuration Files**: Store server URLs and settings in config files +2. **Version Control**: Track app bundle versions carefully +3. **Backup Data**: Regularly export data using `synk data export` +4. **Test First**: Test commands on dev server before production +5. **Use Shell Completion**: Enable completion for better UX + +## Related Documentation + +- [Synkronus Server Reference](/reference/synkronus-server) - Server API documentation +- [App Bundle Format](/reference/app-bundle-format) - Bundle structure +- [API Reference](/reference/api) - Complete API documentation +- [Deployment Guide](/guides/deployment) - Server deployment + diff --git a/docs/reference/synkronus-portal.md b/docs/reference/synkronus-portal.md new file mode 100644 index 0000000..05823f6 --- /dev/null +++ b/docs/reference/synkronus-portal.md @@ -0,0 +1,348 @@ +--- +sidebar_position: 9 +--- + +# Synkronus Portal Reference + +Complete technical reference for the Synkronus Portal web interface. + +## Overview + +Synkronus Portal is a web-based administrative interface for managing Synkronus server operations. It provides a user-friendly interface for app bundle management, user administration, observation viewing, and data export. The portal is built with React, TypeScript, and Vite. + +## Architecture + +### Technology Stack + +- **Framework**: React 19.2.0 +- **Language**: TypeScript +- **Build Tool**: Vite 7.2.4 +- **State Management**: React Context API +- **HTTP Client**: Fetch API with custom wrapper +- **Styling**: CSS (with ODE design tokens) + +### Project Structure + +``` +synkronus-portal/ +├── src/ +│ ├── main.tsx # Application entry point +│ ├── App.tsx # Root component +│ ├── types/ +│ │ └── auth.ts # TypeScript interfaces +│ ├── services/ +│ │ └── api.ts # HTTP client +│ ├── contexts/ +│ │ └── AuthContext.tsx # Authentication state +│ ├── components/ +│ │ ├── Login.tsx # Login component +│ │ └── ProtectedRoute.tsx # Route protection +│ └── pages/ +│ └── Dashboard.tsx # Main dashboard +├── index.html # HTML entry point +└── vite.config.ts # Vite configuration +``` + +## Core Features + +### Authentication + +- **JWT-based Auth**: Secure token authentication +- **Token Refresh**: Automatic token refresh +- **Session Management**: Persistent login sessions +- **Protected Routes**: Route-level access control + +### App Bundle Management + +- **View Versions**: List all app bundle versions +- **Upload Bundles**: Upload new app bundles +- **Switch Versions**: Activate specific bundle versions +- **Download Bundles**: Download bundle files +- **Version History**: View version history + +### User Management + +- **List Users**: View all users +- **Create Users**: Add new users +- **Edit Users**: Update user information +- **Delete Users**: Remove users +- **Role Management**: Assign user roles + +### Observation Management + +- **View Observations**: Browse collected observations +- **Filter Observations**: Filter by form type, date, etc. +- **Search**: Search observations +- **Export**: Export observations in various formats + +### Data Export + +- **Parquet Export**: Export as Parquet ZIP +- **JSON Export**: Export as JSON +- **CSV Export**: Export as CSV +- **Filtered Export**: Export filtered data + +## User Interface + +### Dashboard + +The main dashboard provides: + +- **Overview Statistics**: Total observations, users, bundles +- **Recent Activity**: Latest observations and changes +- **Quick Actions**: Common administrative tasks +- **System Status**: Server health and status + +### Navigation + +- **Sidebar Navigation**: Access to all sections +- **Breadcrumbs**: Current location indicator +- **User Menu**: User profile and logout +- **Notifications**: System notifications + +## API Integration + +### API Service + +The portal uses a centralized API service: + +```typescript +import { api } from './services/api' + +// Login +await api.login(username, password) + +// Get data +const users = await api.get('/users') + +// Post data +await api.post('/users/create', userData) +``` + +### Authentication Flow + +1. User enters credentials +2. Portal sends login request to `/auth/login` +3. Server returns JWT token +4. Token stored in localStorage +5. Token included in all subsequent requests + +### Error Handling + +The API service handles errors: + +- **Network Errors**: Connection failures +- **401 Unauthorized**: Invalid credentials +- **500+ Errors**: Server errors +- **API Errors**: Structured error responses + +## Configuration + +### Development Mode + +**Vite Dev Server:** +- Port: 5174 +- Hot Module Replacement: Enabled +- Proxy: `/api/*` → `http://localhost:8080/*` + +**Start Development:** +```bash +npm run dev +``` + +### Production Mode + +**Nginx Serving:** +- Port: 80 (exposed as 5173) +- Static files from `/usr/share/nginx/html` +- API proxy: `/api/*` → `http://synkronus:8080/*` + +**Build for Production:** +```bash +npm run build +``` + +### Environment Variables + +- `VITE_API_URL`: Override API base URL (optional) +- `DOCKER_ENV`: Set to `true` in Docker (optional) + +## Deployment + +### Docker Deployment + +The portal can be deployed with Docker: + +```bash +docker compose up -d +``` + +**Docker Compose Services:** +- `synkronus-portal`: Frontend portal +- `synkronus`: Backend API +- `postgres`: Database + +### Standalone Deployment + +Build and serve with any static file server: + +```bash +npm run build +# Serve dist/ directory +``` + +## Development + +### Local Development Setup + +1. **Install Dependencies:** + ```bash + npm install + ``` + +2. **Start Backend:** + ```bash + # In synkronus directory + go run cmd/synkronus/main.go + ``` + +3. **Start Portal:** + ```bash + npm run dev + ``` + +4. **Access Portal:** + - Open http://localhost:5174 + +### Adding New Features + +#### Add API Endpoint + +1. **Add TypeScript Types:** + ```typescript + // src/types/feature.ts + export interface FeatureRequest { + field: string + } + ``` + +2. **Add API Method:** + ```typescript + // src/services/api.ts + async getFeature(id: string): Promise { + return this.get(`/feature/${id}`) + } + ``` + +3. **Use in Component:** + ```typescript + const data = await api.getFeature('123') + ``` + +#### Add New Page + +1. **Create Component:** + ```typescript + // src/pages/NewPage.tsx + export function NewPage() { + return
New Page
+ } + ``` + +2. **Add to App.tsx:** + ```typescript + + + + ``` + +## Authentication Context + +### AuthContext Usage + +```typescript +import { useAuth } from './contexts/AuthContext' + +function MyComponent() { + const { user, isAuthenticated, login, logout } = useAuth() + + if (!isAuthenticated) { + return + } + + return
Welcome, {user?.username}
+} +``` + +### AuthContext Methods + +- `login(credentials)`: Authenticate user +- `logout()`: Clear authentication +- `refreshAuth()`: Refresh expired tokens + +## Styling + +### Design Tokens + +The portal uses ODE design tokens: + +- **Primary Color**: Green (#4F7F4E) +- **Secondary Color**: Gold (#E9B85B) +- **Typography**: Noto Sans +- **Spacing**: Consistent spacing scale + +### CSS Structure + +- **Global Styles**: `src/index.css` +- **Component Styles**: Component-specific CSS files +- **No Framework**: Plain CSS (can extend with CSS modules) + +## Best Practices + +### Code Organization + +1. **Types First**: Define TypeScript types +2. **API Service**: Centralize API calls +3. **Context for State**: Use Context for global state +4. **Component Separation**: Separate components and pages + +### Error Handling + +1. **Try-Catch Blocks**: Wrap API calls +2. **User-Friendly Messages**: Show clear error messages +3. **Error Logging**: Log errors for debugging +4. **Graceful Degradation**: Handle errors gracefully + +### Performance + +1. **Code Splitting**: Lazy load components +2. **Memoization**: Memoize expensive computations +3. **Optimistic Updates**: Update UI before server response +4. **Debouncing**: Debounce search and filter inputs + +## Troubleshooting + +### Common Issues + +**Portal Won't Load:** +- Check backend is running +- Verify API URL configuration +- Check browser console for errors + +**Authentication Fails:** +- Verify credentials +- Check JWT_SECRET on server +- Clear localStorage and retry + +**API Calls Fail:** +- Check network connectivity +- Verify CORS configuration +- Check server logs + +## Related Documentation + +- [Synkronus Server Reference](/reference/synkronus-server) - Backend API +- [Deployment Guide](/guides/deployment) - Production deployment +- [Configuration Guide](/guides/configuration) - Configuration options +- [API Reference](/reference/api) - Complete API documentation + diff --git a/docs/reference/synkronus-server.md b/docs/reference/synkronus-server.md new file mode 100644 index 0000000..5d8398c --- /dev/null +++ b/docs/reference/synkronus-server.md @@ -0,0 +1,484 @@ +--- +sidebar_position: 8 +--- + +# Synkronus Server Reference + +Complete technical reference for the Synkronus server component. + +## Overview + +Synkronus is a robust synchronization API server built with Go. It provides RESTful endpoints for data synchronization, app bundle management, attachment handling, user management, and form specifications. The server uses PostgreSQL for data storage and JWT for authentication. + +## Architecture + +### Technology Stack + +- **Language**: Go 1.22+ +- **Database**: PostgreSQL 12+ +- **Authentication**: JWT (JSON Web Tokens) +- **API**: RESTful HTTP API +- **Documentation**: OpenAPI 3.0 specification + +### Project Structure + +``` +synkronus/ +├── cmd/synkronus/ # Application entry point +├── internal/ # Private application code +│ ├── api/ # API definition and OpenAPI integration +│ ├── handlers/ # HTTP request handlers +│ ├── models/ # Domain models +│ ├── repository/ # Data access layer +│ └── services/ # Business logic +└── pkg/ # Public libraries + ├── auth/ # Authentication utilities + ├── database/ # Database connection and migrations + ├── logger/ # Structured logging + ├── middleware/ # HTTP middleware + └── openapi/ # OpenAPI generated code +``` + +## Core Features + +### Data Synchronization + +- **Bidirectional Sync**: Push and pull operations +- **Incremental Updates**: Only sync changed data +- **Conflict Resolution**: Version-based conflict handling +- **Client Tracking**: Track client sync state + +### App Bundle Management + +- **Version Control**: Multiple bundle versions +- **Activation**: Switch active bundle version +- **File Serving**: Serve bundle files to clients +- **Manifest Generation**: Automatic manifest creation + +### Attachment Handling + +- **Binary Storage**: Store attachments separately from observations +- **Immutable Attachments**: Once uploaded, cannot be modified +- **Efficient Transfer**: Optimized for large files +- **Manifest System**: Track attachment changes + +### User Management + +- **JWT Authentication**: Secure token-based auth +- **Role-Based Access**: read-only, read-write, admin roles +- **User CRUD**: Create, read, update, delete users +- **Password Management**: Secure password handling + +### Form Specifications + +- **Versioned Forms**: Multiple versions per form type +- **Schema Storage**: Store JSON schemas +- **UI Schema Support**: Store UI layout definitions +- **Version Negotiation**: Client requests specific versions + +## API Endpoints + +### Authentication + +#### POST /auth/login + +Authenticate user and receive JWT token. + +**Request:** +```json +{ + "username": "user", + "password": "password" +} +``` + +**Response:** +```json +{ + "token": "eyJhbGciOiJIUzI1NiIs...", + "refreshToken": "eyJhbGciOiJIUzI1NiIs...", + "expiresIn": 3600 +} +``` + +#### POST /auth/refresh + +Refresh expired JWT token. + +**Request:** +```json +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +### Synchronization + +#### POST /sync/pull + +Pull changes from server. + +**Request:** +```json +{ + "clientId": "client-123", + "currentVersion": 100, + "schemaTypes": ["survey", "visit"] +} +``` + +**Response:** +```json +{ + "changes": { + "observations": [...] + }, + "timestamp": 150 +} +``` + +#### POST /sync/push + +Push changes to server. + +**Request:** +```json +{ + "clientId": "client-123", + "changes": { + "observations": [...] + } +} +``` + +**Response:** +```json +{ + "timestamp": 150, + "conflicts": [] +} +``` + +### App Bundles + +#### GET /app-bundle/manifest + +Get current app bundle manifest. + +**Response:** +```json +{ + "version": "20250114-123456", + "files": [...], + "hash": "abc123..." +} +``` + +#### GET `/app-bundle/download/{path}` + +Download app bundle file. + +**Path Parameters:** +- `path`: File path within bundle + +#### POST /app-bundle/push + +Upload new app bundle (admin only). + +**Request:** Multipart form with `bundle` file + +**Response:** +```json +{ + "version": "20250114-123456", + "manifest": {...} +} +``` + +#### GET /app-bundle/versions + +List all app bundle versions. + +#### POST /app-bundle/switch + +Switch active bundle version (admin only). + +### Attachments + +#### GET /attachments/manifest + +Get attachment manifest. + +**Query Parameters:** +- `since`: Timestamp to get changes since + +#### GET `/attachments/{id}` + +Download attachment file. + +#### POST /attachments + +Upload attachment (multipart form). + +### Form Specifications + +#### GET `/formspecs/{formType}/{version}` + +Get form specification. + +**Path Parameters:** +- `formType`: Form type identifier +- `version`: Form version + +#### POST /formspecs + +Create form specification (admin only). + +### Users + +#### GET /users + +List all users (admin only). + +#### POST /users/create + +Create new user (admin only). + +#### GET `/users/{username}` + +Get user details. + +#### PUT `/users/{username}` + +Update user (admin only). + +#### DELETE `/users/{username}` + +Delete user (admin only). + +### Data Export + +#### GET /data/export + +Export observations as Parquet ZIP. + +**Query Parameters:** +- `format`: Export format (parquet, json, csv) + +## Configuration + +### Environment Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | HTTP server port | `8080` | No | +| `DB_CONNECTION` | PostgreSQL connection string | - | Yes | +| `JWT_SECRET` | Secret for JWT signing | - | Yes | +| `LOG_LEVEL` | Logging level (debug, info, warn, error) | `info` | No | +| `APP_BUNDLE_PATH` | Directory for app bundles | `./data/app-bundles` | No | +| `MAX_VERSIONS_KEPT` | Maximum bundle versions to keep | `5` | No | +| `ADMIN_USERNAME` | Initial admin username | `admin` | No | +| `ADMIN_PASSWORD` | Initial admin password | `admin` | No | + +### Example Configuration + +```bash +PORT=8080 +DB_CONNECTION=postgres://user:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=info +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin +``` + +## Database Schema + +### Observations Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | UUID | Primary key | +| `form_type` | VARCHAR | Form type identifier | +| `data` | JSONB | Observation data | +| `created_at` | TIMESTAMP | Creation timestamp | +| `updated_at` | TIMESTAMP | Last update timestamp | +| `deleted` | BOOLEAN | Soft delete flag | +| `version` | INTEGER | Version number (auto-increment) | + +### Users Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | UUID | Primary key | +| `username` | VARCHAR | Unique username | +| `password_hash` | VARCHAR | Hashed password | +| `role` | VARCHAR | User role (read-only, read-write, admin) | +| `created_at` | TIMESTAMP | Creation timestamp | + +### App Bundle Versions Table + +| Column | Type | Description | +|--------|------|-------------| +| `version` | VARCHAR | Version identifier | +| `is_active` | BOOLEAN | Active version flag | +| `created_at` | TIMESTAMP | Creation timestamp | + +## Synchronization Protocol + +### Two-Phase Sync + +#### Phase 1: Observation Sync + +1. Client requests changes via `/sync/pull` +2. Server returns observations changed since client's version +3. Client applies changes locally +4. Client pushes local changes via `/sync/push` +5. Server applies changes and returns new version + +#### Phase 2: Attachment Sync + +1. Client requests attachment manifest +2. Server returns list of attachments to download +3. Client downloads missing attachments +4. Client uploads pending attachments +5. Server confirms receipt + +### Conflict Resolution + +Conflicts are detected when: + +- Same observation modified on multiple clients +- Observation deleted on one client, modified on another + +Resolution strategy: + +- **Last Write Wins**: Most recent change wins +- **Version Tracking**: Version numbers prevent conflicts +- **Client Responsibility**: Clients handle conflict resolution + +## Security + +### Authentication + +- **JWT Tokens**: Secure token-based authentication +- **Token Expiration**: Tokens expire after configured time +- **Refresh Tokens**: Long-lived refresh tokens +- **Password Hashing**: bcrypt password hashing + +### Authorization + +- **Role-Based Access**: Three roles (read-only, read-write, admin) +- **Endpoint Protection**: Middleware protects admin endpoints +- **Token Validation**: All requests validate JWT tokens + +### Data Protection + +- **HTTPS**: Recommended for production +- **Input Validation**: All inputs validated +- **SQL Injection Prevention**: Parameterized queries +- **XSS Protection**: Input sanitization + +## Deployment + +### Docker Deployment + +See [Deployment Guide](/guides/deployment) for complete deployment instructions. + +### Quick Start + +```bash +docker compose up -d +``` + +### Production Setup + +1. Configure environment variables +2. Set up PostgreSQL database +3. Configure reverse proxy (Nginx) +4. Set up SSL/TLS certificates +5. Configure monitoring and logging + +## Monitoring + +### Health Check + +```bash +curl http://localhost:8080/health +``` + +Returns `OK` if server is healthy. + +### Logging + +Structured logging with levels: + +- **Debug**: Detailed debugging information +- **Info**: General informational messages +- **Warn**: Warning messages +- **Error**: Error messages + +### Metrics + +Key metrics to monitor: + +- Request rate +- Response times +- Error rates +- Database connection pool +- Active sync operations + +## Performance + +### Optimization Strategies + +- **Connection Pooling**: PostgreSQL connection pool +- **Query Optimization**: Indexed database queries +- **Caching**: Cache app bundle manifests +- **Compression**: Compress responses when possible + +### Scaling + +- **Horizontal Scaling**: Multiple server instances +- **Load Balancing**: Distribute requests across instances +- **Database Replication**: Read replicas for database +- **CDN**: Serve static files via CDN + +## Troubleshooting + +### Common Issues + +**Database Connection Errors:** +- Verify PostgreSQL is running +- Check connection string format +- Verify database exists +- Check user permissions + +**Authentication Failures:** +- Verify JWT_SECRET is set +- Check token expiration +- Verify user credentials + +**Sync Failures:** +- Check database connectivity +- Verify client version tracking +- Review server logs + +## API Versioning + +The API supports versioning via the `x-api-version` header: + +```http +x-api-version: 1.0.0 +``` + +Version negotiation allows clients to request specific API versions. + +## Related Documentation + +- [API Reference](/reference/api) - Complete API documentation +- [Deployment Guide](/guides/deployment) - Production deployment +- [Configuration Guide](/guides/configuration) - Configuration options +- [Synkronus CLI Reference](/reference/synkronus-cli) - CLI tool + diff --git a/docs/using/.gitkeep b/docs/using/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/using/app-bundles.md b/docs/using/app-bundles.md new file mode 100644 index 0000000..ef90524 --- /dev/null +++ b/docs/using/app-bundles.md @@ -0,0 +1,176 @@ +--- +sidebar_position: 4 +--- + +# Understanding App Bundles + +Learn how app bundles work in ODE and how they enable custom applications and forms. + +## What Are App Bundles? + +App bundles are packaged collections of files that define custom applications for ODE. They contain everything needed to run a custom data collection application within the Formulus mobile app: + +- **Custom web application** - HTML, CSS, and JavaScript files +- **Form specifications** - JSON schemas defining data collection forms +- **Configuration files** - Settings and metadata for the application +- **Assets** - Images, fonts, and other resources + +When you open Formulus and connect to a Synkronus server, the app automatically downloads the current app bundle. This bundle determines what forms are available, how they look, and what workflows you can use. + +## How App Bundles Work + +### Download Process + +1. **Initial Sync**: When you first log in to Formulus, the app checks the server for the current app bundle version +2. **Version Check**: The app compares the server's version with the version stored locally +3. **Download**: If a new version is available, the app downloads the bundle files +4. **Extraction**: The bundle is extracted and stored locally on your device +5. **Activation**: The new bundle becomes active, and you can use the updated forms and features + +### Automatic Updates + +App bundles update automatically: + +- **On Login**: When you log in, the app checks for updates +- **On Manual Sync**: When you tap "Sync Now" in the app +- **Periodically**: The app may check for updates in the background (if configured) + +### Version Management + +Each app bundle has a version identifier (typically a timestamp). This ensures: + +- **Consistency**: All devices use the same version of forms and workflows +- **Rollback**: Administrators can switch back to previous versions if needed +- **Tracking**: You can see which version of the app bundle you're using + +## What's Inside an App Bundle + +### Custom Application + +The main component is a custom web application that runs inside Formulus. This application: + +- **Provides Navigation**: Custom menus and screens for your workflow +- **Displays Forms**: Shows available forms and allows you to start data collection +- **Manages Data**: Lets you view, edit, and manage your observations +- **Custom Branding**: Can include your organization's logo and styling + +### Form Specifications + +App bundles include form definitions that specify: + +- **Data Fields**: What information to collect (name, age, location, etc.) +- **Field Types**: How to input data (text, number, date, photo, GPS, etc.) +- **Validation Rules**: Requirements for data entry (required fields, value ranges, etc.) +- **Layout**: How the form is organized and presented + +### Configuration + +Bundles may include configuration files that: + +- **Define Settings**: Application-specific settings and preferences +- **Specify Behavior**: How the application should behave in different scenarios +- **Set Permissions**: What features and data access the application needs + +## Using App Bundles + +### Accessing Your Application + +1. **Open Formulus** on your device +2. **Log in** to your account +3. **Wait for sync** to complete (if first time or after update) +4. **Your custom application** loads automatically as the main interface + +### Working with Forms + +Once the app bundle is loaded: + +1. **Navigate** through your custom application's interface +2. **Select a form** from the available forms list +3. **Fill out the form** with the required information +4. **Submit** the form to create an observation +5. **View observations** in your data management section + +### Updating App Bundles + +App bundles update automatically, but you can also manually trigger an update: + +1. **Open Formulus** → **Settings** → **Sync** +2. **Tap "Sync Now"** or **"Force Download"** +3. **Wait for download** to complete +4. **App restarts** or refreshes with the new bundle + +## Troubleshooting App Bundles + +### Bundle Not Downloading + +**Problem**: App bundle fails to download or sync. + +**Solutions**: +- Check internet connection +- Verify server is accessible +- Try manual sync: Settings → Sync → Sync Now +- Clear app cache: Settings → Storage → Clear Cache +- Restart the app + +### Forms Not Appearing + +**Problem**: After sync, forms don't appear in the app. + +**Solutions**: +- Verify bundle downloaded successfully (check Sync screen) +- Check bundle version matches server version +- Try force refresh: Settings → Sync → Force Download +- Clear app data and re-login (warning: deletes local data) + +### Outdated Forms + +**Problem**: Forms show old fields or structure. + +**Solutions**: +- Check if bundle update is available: Settings → Sync +- Force download latest bundle: Settings → Sync → Force Download +- Verify server has the latest bundle version +- Contact administrator if issue persists + +### Bundle Version Mismatch + +**Problem**: App shows different version than expected. + +**Solutions**: +- Force sync to get latest version +- Check server is serving the correct active version +- Verify you're connected to the correct server +- Contact administrator to verify bundle version + +## App Bundle Best Practices + +### For Users + +1. **Sync Regularly**: Keep your app bundle up to date by syncing regularly +2. **Check Version**: Verify you're using the latest bundle version +3. **Report Issues**: If forms or features don't work, report to your administrator +4. **Backup Data**: Ensure observations are synced before major updates + +### For Administrators + +1. **Test Before Deploy**: Test new bundles thoroughly before activating +2. **Version Control**: Use clear version identifiers (timestamps recommended) +3. **Rollback Plan**: Keep previous versions available for quick rollback +4. **Notify Users**: Inform users when major updates are deployed +5. **Monitor Usage**: Track which versions are in use across devices + +## Technical Details + +For technical information about app bundle structure, format, and development, see: + +- [App Bundle Format Reference](/reference/app-bundle-format) - Technical specification +- [Custom Applications Guide](/guides/custom-applications) - Building custom applications +- [Form Design Guide](/guides/form-design) - Creating form specifications + +## Related Documentation + +- [Your First Form](/using/your-first-form) - Get started with data collection +- [Synchronization](/using/synchronization) - Understand how data syncs +- [Formulus Features](/using/formulus-features) - Complete app feature guide +- [Working Offline](/using/working-offline) - Offline capabilities + diff --git a/docs/using/custom-applications.md b/docs/using/custom-applications.md new file mode 100644 index 0000000..13c208a --- /dev/null +++ b/docs/using/custom-applications.md @@ -0,0 +1,115 @@ +--- +sidebar_position: 3 +--- + +# Custom Applications + +Custom applications are web-based interfaces that run within the Formulus mobile app, providing specialized workflows and user experiences. + +## Overview + +Custom applications allow you to: + +- Create custom navigation and user interfaces +- Integrate with the ODE form system +- Access observation data through the Formulus JavaScript interface +- Build specialized workflows for specific use cases + +## How Custom Applications Work + +Custom applications are defined in app bundles, which include: + +- HTML, CSS, and JavaScript files +- Form specifications +- Custom renderers +- Configuration files + +The app bundle is uploaded to the Synkronus server and downloaded by mobile devices during synchronization. When a user opens a custom application, it runs in a WebView within the Formulus app. + +## Formulus JavaScript Interface + +Custom applications interact with Formulus through a JavaScript interface: + +```javascript +// Create a new observation +window.formulus.addObservation(formType, initializationData); + +// Edit an existing observation +window.formulus.editObservation(formType, observationId); + +// Delete an observation +window.formulus.deleteObservation(formType, observationId); +``` + +## Creating a Custom Application + +Custom applications are web-based interfaces that run within the Formulus mobile app. They integrate with ODE through the Formulus JavaScript interface. + +### Basic Structure + +A custom application consists of: + +1. **HTML file**: Main entry point (typically `index.html`) +2. **JavaScript**: Application logic using the Formulus API +3. **CSS**: Styling for the application +4. **Manifest**: Bundle metadata + +### Getting Started + +1. **Include Formulus Load Script**: + +```html + +``` + +2. **Initialize the API**: + +```javascript +async function init() { + const api = await getFormulus(); + // Use the API +} +``` + +3. **Use Formulus Methods**: + +```javascript +// Create observation +await api.addObservation('form-type', {}); + +// Edit observation +await api.editObservation('form-type', 'observation-id'); +``` + +### Packaging + +Package your application as a ZIP file with: +- `index.html` (or your entry point) +- `manifest.json` +- All assets (CSS, JS, images) + +### Deployment + +Upload the bundle to your Synkronus server: + +```bash +synk app-bundle upload bundle.zip --activate +``` + +See the [Custom Applications guide](/guides/custom-applications) for detailed instructions on building and deploying custom applications. + +## Use Cases + +Custom applications are suitable for: + +- Specialized data collection workflows +- Custom user interfaces that match organizational branding +- Integration with external systems +- Complex navigation requirements + +## Next Steps + +- Read the [Custom Applications guide](/guides/custom-apps/overview) for detailed information +- Learn about [app bundle structure](/guides/custom-apps/app-bundle-structure) +- Explore [custom renderers](/guides/custom-apps/custom-renderers) + diff --git a/docs/using/data-management.md b/docs/using/data-management.md new file mode 100644 index 0000000..75be39f --- /dev/null +++ b/docs/using/data-management.md @@ -0,0 +1,105 @@ +--- +sidebar_position: 4 +--- + +# Data Management + +This guide covers viewing, editing, and managing observations in ODE. + +## Viewing Observations + +Observations can be viewed in the Formulus app: + +1. Navigate to the observations list +2. Filter observations by form type, date, or sync status +3. Select an observation to view details +4. Review the form data and metadata + +## Editing Observations + +To edit an observation: + +1. Open the observation from the list +2. Select the edit option +3. Modify the form fields as needed +4. Save your changes + +Edited observations are marked for synchronization and will be updated on the server during the next sync operation. + +## Deleting Observations + +Observations can be deleted: + +1. Open the observation +2. Select the delete option +3. Confirm the deletion + +Deleted observations are marked as deleted locally and synchronized to the server. The server maintains a record of deleted observations for audit purposes. + +## Filtering and Searching + +The observations list supports filtering by: + +- Form type +- Date range +- Sync status (synced, pending, error) +- Custom criteria based on form data + +## Exporting Data + +Data can be exported from the server using multiple methods: + + + + +Export all observations as a Parquet ZIP archive: + +```bash +synk data export exports.zip +``` + +Export to different formats: + +```bash +# Parquet (default) +synk data export observations.zip + +# JSON +synk data export observations.json --format json + +# CSV +synk data export observations.csv --format csv +``` + + + + +```bash +curl -X GET http://your-server:8080/api/dataexport/parquet \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -o observations.zip +``` + + + + +1. Navigate to the Portal +2. Go to the "Data Export" section +3. Select export format (Parquet, JSON, CSV) +4. Click "Export" to download + + + + +The export includes all observations in the selected format, organized by schema type. See the [API Reference](/reference/api) for details. + +## Data Synchronization + +Observations are synchronized between devices and the server automatically. See [Synchronization](/using/synchronization) for details on how synchronization works. + +## Next Steps + +- Learn about [synchronization](/using/synchronization) in detail +- Review the [API Reference](/reference/api/endpoints) for programmatic access +- Explore [data export options](/reference/api/endpoints) for analysis + diff --git a/docs/using/formulus-features.md b/docs/using/formulus-features.md new file mode 100644 index 0000000..eda4e64 --- /dev/null +++ b/docs/using/formulus-features.md @@ -0,0 +1,374 @@ +--- +sidebar_position: 5 +--- + +# Formulus Features + +Complete user guide for the Formulus mobile application features and capabilities. + +## Overview + +Formulus is an offline-first mobile data collection application that enables field workers to collect structured data in environments with limited or no connectivity. The app works seamlessly with custom applications and provides comprehensive data collection capabilities. + +## Core Features + +### Offline-First Architecture + +Formulus is designed to work completely offline: + +- **Local Storage**: All data is stored locally on your device using WatermelonDB +- **Offline Forms**: Fill out forms without internet connection +- **Automatic Sync**: Data syncs automatically when connectivity is restored +- **No Data Loss**: Observations are saved locally even if sync fails + +### Custom Application Support + +Formulus can host custom web applications: + +- **Custom Interfaces**: Run specialized workflows and user interfaces +- **Branded Experience**: Display your organization's branding and styling +- **Flexible Navigation**: Custom menus and navigation structures +- **Integrated Forms**: Seamless integration with the form system + +### Rich Data Collection + +Support for various data types: + +- **Text Input**: Single and multi-line text fields +- **Numbers**: Integer and decimal numeric values +- **Dates and Times**: Date pickers, time selectors, and datetime fields +- **Selections**: Single choice dropdowns and multi-select checkboxes +- **Media Capture**: Photos, audio recordings, and video +- **Location**: GPS coordinates with accuracy information +- **Signatures**: Digital signature capture +- **Files**: File attachments and document uploads +- **Barcodes**: QR code and barcode scanning + +### Synchronization + +Bidirectional data synchronization: + +- **Automatic Sync**: Syncs when network becomes available +- **Manual Sync**: Trigger sync on demand +- **Incremental Updates**: Only syncs changed data +- **Conflict Resolution**: Automatically handles sync conflicts +- **Attachment Handling**: Separate sync for binary files + +## App Navigation + +### Main Screens + +| Screen | Description | Access | +|--------|-------------|--------| +| **Home/Dashboard** | Overview and quick actions | Default screen after login | +| **Forms** | List of available forms | Bottom tab or menu | +| **Observations** | Saved data records | Bottom tab or menu | +| **Sync** | Sync status and actions | Bottom tab or menu | +| **Settings** | App configuration | Menu or gear icon | + +### Navigation Elements + +- **Bottom Tabs**: Quick access to main sections +- **Menu Drawer**: Swipe from left or tap hamburger icon (☰) +- **Back Button**: Return to previous screen +- **Floating Action Button**: Quick actions (+ button) + +## Working with Forms + +### Viewing Available Forms + +1. Navigate to the **Forms** screen +2. View the list of available forms from the current app bundle +3. Each form displays: + - Form name/title + - Description (if available) + - Icon (if configured) + +### Starting a New Form + +1. Tap on a form from the list +2. Form opens in the form player +3. Fill in fields as required +4. Navigate through form sections using Next/Previous buttons or swiping + +### Form Field Types + +| Field Type | Description | Input Method | +|------------|-------------|--------------| +| **Text** | Single/multi-line text | Keyboard input | +| **Number** | Numeric values | Number keyboard | +| **Date** | Date selection | Date picker | +| **Time** | Time selection | Time picker | +| **Select** | Single choice | Dropdown/radio buttons | +| **Multi-select** | Multiple choices | Checkboxes | +| **Photo** | Camera capture | Camera interface | +| **GPS** | Location capture | GPS sensor | +| **Signature** | Digital signature | Touch drawing | +| **Audio** | Voice recording | Microphone | +| **Video** | Video recording | Camera | +| **File** | File attachment | File picker | +| **QR Code** | Scan QR/barcode | Camera scanner | + +### Form Navigation + +- **Swipe Left/Right**: Navigate between form sections +- **Next/Previous Buttons**: Move through form step by step +- **Progress Bar**: Shows completion status +- **Section Tabs**: Jump to specific sections (if enabled) + +### Saving Forms + +#### Save as Draft + +1. Tap **"Save Draft"** at any time while filling the form +2. Form is saved locally with current data +3. Resume later from the Observations screen +4. Draft status is shown in the observation list + +#### Submit/Finalize + +1. Complete all required fields +2. Tap **"Submit"** or **"Finalize"** +3. Validation runs automatically - errors shown if any +4. Observation saved as pending upload +5. Will sync when connection is available + +### Form Validation + +Forms may include validation rules: + +- **Required Fields**: Must be filled before submission +- **Format Validation**: Email, phone number, and other format checks +- **Range Validation**: Minimum and maximum values +- **Conditional Logic**: Fields shown based on other answers + +**Validation Errors:** +- Red highlight on invalid fields +- Error messages displayed below fields +- Cannot submit until all errors are resolved + +## Managing Observations + +### Viewing Observations + +1. Navigate to the **Observations** screen +2. View list of all saved observations +3. Each entry shows: + - Form name + - Date/time created + - Status (draft, pending, synced) + - Key data fields + +### Observation Status + +| Status | Description | +|--------|-------------| +| **Draft** | Incomplete, can be edited | +| **Pending** | Complete, awaiting sync | +| **Syncing** | Currently uploading to server | +| **Synced** | Successfully uploaded to server | +| **Error** | Sync failed, retry needed | + +### Editing Observations + +#### Edit Draft + +1. Tap on a draft observation +2. Form opens with saved data +3. Make changes as needed +4. Save or Submit + +#### Edit Pending (if allowed) + +1. Tap on a pending observation +2. May need to "Unlock" for editing +3. Make changes +4. Re-submit + +### Deleting Observations + +1. Long-press on observation or tap menu (⋮) +2. Select **"Delete"** +3. Confirm deletion + +**Note**: Synced observations may not be deletable locally depending on configuration. + +## Synchronization + +### Automatic Sync + +The app automatically syncs when: + +- Network connection becomes available +- App is opened (background sync) +- Periodically (if configured in settings) + +### Manual Sync + +1. Navigate to **Sync** screen +2. Tap **"Sync Now"** +3. Watch progress - upload count and status +4. Check results - success/failure messages + +### Sync Process + +1. **Pull**: Download new forms and server data +2. **Push**: Upload pending observations +3. **Attachments**: Upload photos, audio, and other files +4. **Confirmation**: Server acknowledges receipt + +### Sync Indicators + +- **Sync Icon**: Appears in status bar when syncing +- **Badge on Sync Tab**: Shows pending upload count +- **Last Sync Time**: Displayed on sync screen +- **Individual Status**: Each observation shows its sync status + +### Offline Mode + +When offline, you can: + +- ✅ Fill out forms +- ✅ Save observations locally +- ✅ View previously synced data +- ❌ Cannot upload new data +- ❌ Cannot download new forms + +Data syncs automatically when connection returns. + +## Attachments + +### Capturing Photos + +1. Tap photo field in form +2. Camera opens automatically +3. Take photo or select from gallery +4. Review and confirm +5. Photo is attached to observation + +### Capturing GPS Location + +1. Tap GPS field in form +2. Location permission requested (first time only) +3. Wait for GPS fix +4. Coordinates captured (latitude, longitude, accuracy) +5. May show map preview + +### Recording Audio + +1. Tap audio field in form +2. Microphone permission requested (first time only) +3. Tap record to start +4. Speak clearly +5. Tap stop when done +6. Review and confirm + +### Capturing Signatures + +1. Tap signature field in form +2. Signature pad opens +3. Sign with finger on screen +4. Tap "Done" to save +5. Tap "Clear" to retry + +### Recording Video + +1. Tap video field in form +2. Camera opens in video mode +3. Tap record to start +4. Tap stop when done +5. Review and confirm + +### Scanning QR Codes + +1. Tap QR code field in form +2. Camera opens in scanner mode +3. Point camera at QR code +4. Code is automatically scanned +5. Data is populated in the field + +## Settings + +### Server Settings + +- **Server URL**: Synkronus server address +- **Test Connection**: Verify connectivity to server +- **Auto-login**: Automatically log in on app start + +### User Settings + +- **Username**: Current logged-in user +- **Change Password**: Update account password +- **Logout**: Clear session and return to login + +### Sync Settings + +- **Auto-sync**: Enable/disable automatic synchronization +- **Sync on WiFi Only**: Save mobile data by syncing only on WiFi +- **Sync Interval**: How often to check for sync (if auto-sync enabled) + +### App Settings + +- **Notifications**: Enable/disable push notifications +- **Theme**: Light or dark mode +- **Language**: App language selection +- **Clear Cache**: Free up storage space +- **Storage Info**: View storage usage + +## Best Practices + +### Data Collection + +1. **Sync Before Fieldwork**: Get latest forms and updates +2. **Check Battery**: Ensure sufficient charge for field work +3. **Test GPS Outdoors**: Better accuracy in open areas +4. **Save Frequently**: Avoid data loss from app crashes +5. **Sync When Possible**: Don't wait too long between syncs + +### Offline Work + +1. **Sync Before Going Offline**: Download latest forms and data +2. **Save Observations as You Go**: Don't wait until the end +3. **Check Pending Count**: Verify all data before leaving field +4. **Sync Immediately**: When back online, sync right away + +### Data Quality + +1. **Fill All Required Fields**: Complete forms fully +2. **Double-Check Entries**: Verify accuracy before submitting +3. **Take Clear Photos**: Ensure photos are in focus and well-lit +4. **Verify GPS Accuracy**: Check location accuracy before submitting +5. **Review Before Submitting**: Review all data before final submission + +## Troubleshooting + +### Forms Not Loading + +- Check internet connection +- Verify server is accessible +- Try manual sync: Settings → Sync → Sync Now +- Clear app cache: Settings → Storage → Clear Cache + +### Sync Failures + +- Check internet connection +- Verify server URL is correct +- Check server status +- Try manual sync +- Review error messages in Sync screen + +### App Crashes + +- Restart the app +- Clear app cache +- Update to latest version +- Reinstall if issues persist + +## Related Documentation + +- [Installing Formulus](/getting-started/installing-formulus) - Installation guide +- [Your First Form](/using/your-first-form) - Getting started with forms +- [Synchronization](/using/synchronization) - Detailed sync information +- [Working Offline](/using/working-offline) - Offline capabilities +- [App Bundles](/using/app-bundles) - Understanding app bundles + diff --git a/docs/using/index.md b/docs/using/index.md new file mode 100644 index 0000000..53a3102 --- /dev/null +++ b/docs/using/index.md @@ -0,0 +1,100 @@ +--- +sidebar_position: 0 +--- + +# Using ODE + +Learn how to use ODE to create forms, manage data, and build custom applications. + +## Core Workflows + +
+
+
+
+

Your First Form

+
+
+

Create your first data collection form and learn the basics.

+ Get Started → +
+
+
+
+
+
+

Formulus Features

+
+
+

Explore the features and capabilities of Formulus.

+ Explore → +
+
+
+
+
+
+

App Bundles

+
+
+

Learn how to create, manage, and deploy app bundles.

+ Learn More → +
+
+
+
+
+
+

Data Management

+
+
+

Manage your collected data, export observations, and handle attachments.

+ View Guide → +
+
+
+
+
+
+

Synchronization

+
+
+

Understand how data synchronization works and manage conflicts.

+ Read More → +
+
+
+
+
+
+

Working Offline

+
+
+

Learn how ODE handles offline data collection.

+ Learn More → +
+
+
+
+
+
+

Custom Applications

+
+
+

Build custom applications that integrate with ODE.

+ View Guide → +
+
+
+
+
+
+

Troubleshooting

+
+
+

Common issues and solutions for problems you might encounter.

+ Get Help → +
+
+
+
diff --git a/docs/using/synchronization.md b/docs/using/synchronization.md new file mode 100644 index 0000000..ab56cf3 --- /dev/null +++ b/docs/using/synchronization.md @@ -0,0 +1,82 @@ +--- +sidebar_position: 5 +--- + +# Synchronization + +Synchronization is the process of exchanging data between mobile devices and the Synkronus server. + +## How Synchronization Works + +ODE uses a bidirectional synchronization protocol: + +1. **Push**: Local observations are sent to the server +2. **Pull**: New or updated observations are retrieved from the server +3. **Conflict Resolution**: Conflicts are resolved automatically using version numbers + +## Synchronization Process + +### Automatic Synchronization + +Synchronization occurs automatically when: + +- The app is opened and network connectivity is available +- A new observation is created or modified +- A manual sync is triggered by the user +- A scheduled sync interval elapses + +### Manual Synchronization + +Users can trigger manual synchronization: + +1. Open the app settings +2. Navigate to synchronization options +3. Select "Sync Now" +4. Wait for the sync to complete + +## Conflict Resolution + +When the same observation is modified on multiple devices, ODE resolves conflicts automatically: + +- Each observation has a version number +- The version number is incremented on each modification +- During sync, the observation with the highest version number is kept +- If versions are equal, the most recent modification timestamp is used + +## Sync Status + +Observations have a sync status that indicates their synchronization state: + +| Status | Description | +|--------|-------------| +| **Pending** | Observation is waiting to be synchronized | +| **Syncing** | Synchronization is in progress | +| **Synced** | Observation has been successfully synchronized | +| **Error** | Synchronization failed (will be retried) | + +## Attachment Synchronization + +Attachments (photos, audio, files) are synchronized separately from observation metadata: + +1. Observation metadata is synchronized first +2. Attachments are uploaded or downloaded in a separate phase +3. Attachment sync status is tracked independently + +See the [Technical Details](/development/technical/sync-protocol) section for more information on the sync protocol. + +## Troubleshooting Sync Issues + +If synchronization is not working: + +1. Check network connectivity +2. Verify server accessibility +3. Review authentication credentials +4. Check server logs for errors +5. Review the [Troubleshooting guide](/using/troubleshooting) + +## Next Steps + +- Learn about [working offline](/using/working-offline) +- Review [technical sync details](/development/technical/sync-protocol) +- Explore [API endpoints](/reference/api/endpoints) for sync operations + diff --git a/docs/using/troubleshooting.md b/docs/using/troubleshooting.md new file mode 100644 index 0000000..3df75bf --- /dev/null +++ b/docs/using/troubleshooting.md @@ -0,0 +1,502 @@ +--- +sidebar_position: 7 +--- + +# Troubleshooting + +Common issues and solutions when using ODE. + +## Connection Issues + +### App Cannot Connect to Server + +**Symptoms**: App shows connection error, cannot sync data + +**Solutions**: +- Verify server is running and accessible +- Check server URL in app settings +- Verify network connectivity +- Check firewall settings +- For Android emulator, use `10.0.2.2` instead of `localhost` +- For iOS simulator, use `localhost` or your machine's IP address + +### Authentication Failures + +**Symptoms**: Login fails, authentication errors + +**Solutions**: +- Verify username and password are correct +- Check that user account exists on server +- Verify JWT secret is configured correctly on server +- Review server logs for authentication errors + +## Synchronization Issues + +### Observations Not Syncing + +**Symptoms**: Observations remain in "pending" status + +**Solutions**: +- Check network connectivity +- Verify server is accessible +- Review authentication credentials +- Check server logs for sync errors +- Ensure observations were saved locally before sync attempt +- Try manual sync from app settings + +### Sync Conflicts + +**Symptoms**: Data conflicts during synchronization + +**Solutions**: +- ODE automatically resolves conflicts using version numbers +- Review conflict resolution in [Synchronization](/using/synchronization) +- Check that devices are using the same server +- Verify system clocks are synchronized + +## Form Issues + +### Forms Not Appearing + +**Symptoms**: Forms don't appear in app + +**Solutions**: +- Verify forms were uploaded to server +- Check that app has synchronized with server +- Review server logs for form upload errors +- Ensure form specifications are valid JSON +- Check form type and version match + +### Form Validation Errors + +**Symptoms**: Cannot submit form, validation errors + +**Solutions**: +- Review form schema for required fields +- Check that data types match schema definitions +- Verify validation rules are correctly defined +- Review error messages for specific issues + +## Formplayer Errors Explained + +Common Formplayer errors, their causes, and fixes. + +### ❌ "minimum value must be ['number']" + +**Error Message:** +``` +minimum value must be ['number'] +``` + +**Cause:** +Using `$data` reference or non-numeric value in `minimum` or `maximum` property. + +**Example (Unsafe):** +```json +{ + "type": "number", + "minimum": { "$data": "#/properties/minValue" } // ❌ $data not supported +} +``` + +**Fix:** +Use literal numeric values only: + +```json +{ + "type": "number", + "minimum": 0, // ✅ Literal number + "maximum": 100 +} +``` + +**Prevention:** +- Always use literal numbers for `minimum` and `maximum` +- Never use `$data` references in validation constraints +- Validate schema before deployment + +### ❌ "Cannot read properties of undefined (reading 'find')" + +**Error Message:** +``` +Cannot read properties of undefined (reading 'find') +TypeError: Cannot read properties of undefined (reading 'find') +``` + +**Cause:** +One of several issues: +1. Layout missing `elements` array +2. Invalid `scope` path (field doesn't exist in schema) +3. Rule referencing missing field + +**Example 1 - Missing elements:** +```json +{ + "type": "SwipeLayout" + // ❌ Missing elements array +} +``` + +**Fix:** +```json +{ + "type": "SwipeLayout", + "elements": [] // ✅ Always include elements array +} +``` + +**Example 2 - Invalid scope:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/missingField" // ❌ Field doesn't exist + } +} +``` + +**Fix:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "missingField": { "type": "string" } // ✅ Add field to schema + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/missingField" + } +} +``` + +**Example 3 - Rule referencing missing field:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/missingField", // ❌ Field doesn't exist + "schema": { "const": "value" } + } + } + } +} +``` + +**Fix:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "missingField": { "type": "string" } // ✅ Add referenced field + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/missingField", + "schema": { "const": "value" } + } + } + } +} +``` + +**Prevention:** +- Always include `elements: []` in layouts (can be empty) +- Validate all `scope` paths exist in schema +- Ensure all fields referenced in rules exist in schema +- Use form validation script before deployment + +### ❌ "Rule condition scope not found" + +**Error Message:** +``` +Rule condition scope "#/properties/field" not found in schema +``` + +**Cause:** +Rule condition references a field that doesn't exist in the JSON schema. + +**Example:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + // field2 doesn't exist + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/field2", // ❌ Field doesn't exist + "schema": { "const": "value" } + } + } + } +} +``` + +**Fix:** +Add the referenced field to the schema: + +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "field2": { "type": "string" } // ✅ Add referenced field + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/field2", + "schema": { "const": "value" } + } + } + } +} +``` + +**Prevention:** +- Always define all fields referenced in rules in the schema +- Use form validation script to catch these errors +- Test all conditional logic paths + +### ❌ "SwipeLayout missing required elements array" + +**Error Message:** +``` +SwipeLayout missing required "elements" array +``` + +**Cause:** +SwipeLayout (or other layout) is missing the `elements` property. + +**Example:** +```json +{ + "type": "SwipeLayout" + // ❌ Missing elements +} +``` + +**Fix:** +```json +{ + "type": "SwipeLayout", + "elements": [] // ✅ Always include elements array +} +``` + +**Prevention:** +- Always include `elements` array in layouts +- Use form validation script +- Test forms before deployment + +### ❌ "Invalid scope path" + +**Error Message:** +``` +Invalid scope path: "#/properties/nonexistent" +Scope does not exist in schema +``` + +**Cause:** +Control `scope` references a property that doesn't exist in the JSON schema. + +**Example:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/nonexistent" // ❌ Field doesn't exist + } +} +``` + +**Fix:** +Either add the field to schema or correct the scope: + +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "nonexistent": { "type": "string" } // ✅ Add field + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/nonexistent" + } +} +``` + +**Prevention:** +- Validate all scope paths before deployment +- Use form validation script +- Keep schema and UI schema in sync + +### ❌ "$data is not supported" + +**Error Message:** +``` +$data is not supported in Formplayer +``` + +**Cause:** +Using `$data` references in schema (e.g., in `minimum`, `maximum`, or validation rules). + +**Example:** +```json +{ + "type": "number", + "minimum": { "$data": "#/properties/minValue" } // ❌ Not supported +} +``` + +**Fix:** +Use literal values only: + +```json +{ + "type": "number", + "minimum": 0 // ✅ Literal value +} +``` + +**Prevention:** +- Never use `$data` references +- Use literal values for all constraints +- Refer to [Supported Schema Profile](/reference/formplayer#supported-schema--ui-profile) + +### ❌ "Form failed to render" + +**Symptoms:** +Form doesn't display, blank screen, or error in console. + +**Common Causes:** +1. Invalid JSON syntax +2. Missing required schema properties +3. Unsupported schema features +4. Invalid UI schema structure + +**Solutions:** +1. Validate JSON syntax +2. Check schema follows supported profile +3. Verify UI schema structure (SwipeLayout root, elements arrays) +4. Review browser console for specific errors +5. Test with form validation script + +### General Error Prevention + +1. **Use Validation Script**: Run `npm run validate:forms` before deployment +2. **Check Supported Features**: Refer to [Supported Schema Profile](/reference/formplayer#supported-schema--ui-profile) +3. **Test on Devices**: Test forms on actual mobile devices +4. **Review Error Messages**: Error messages often indicate the specific issue +5. **Validate Scope Paths**: Ensure all scope paths exist in schema +6. **Avoid Unsupported Features**: Don't use `$data`, `if/then/else`, etc. + +### Getting Help with Errors + +If you encounter errors not covered here: + +1. Check [Form Design Guide](/guides/form-design) for best practices +2. Review [Formplayer Reference](/reference/formplayer) for supported features +3. Validate your form using the validation script +4. Check browser/device console for detailed error messages +5. Report issues following [guidelines](/community/getting-help) + +## Data Issues + +### Observations Not Saving + +**Symptoms**: Observations disappear after creation + +**Solutions**: +- Check device storage space +- Verify database is accessible +- Review app logs for database errors +- Ensure app has necessary permissions + +### Data Loss + +**Symptoms**: Observations are missing + +**Solutions**: +- Check sync status to see if data is on server +- Review server logs for deletion records +- Verify backups are configured +- Check that observations weren't accidentally deleted + +## Performance Issues + +### Slow App Performance + +**Symptoms**: App is slow, forms take time to load + +**Solutions**: +- Check device storage space +- Review number of observations stored locally +- Clear app cache if needed +- Ensure app is updated to latest version +- Check device memory usage + +### Slow Synchronization + +**Symptoms**: Sync takes a long time + +**Solutions**: +- Check network speed and stability +- Review number of observations to sync +- Check server performance and load +- Verify attachment sizes are reasonable +- Consider syncing during off-peak hours + +## Getting Additional Help + +If you cannot resolve an issue: + +1. Review relevant documentation sections +2. Check [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) for similar problems +3. Search the [Community section](/community/getting-help) for solutions +4. Review [API Reference](/reference/api/overview) for technical details +5. Report issues following the [guidelines](/community/reporting-issues) + +## Next Steps + +- Review [Synchronization](/using/synchronization) for sync-related issues +- Check [Installation guides](/getting-started/installation/prerequisites) for setup problems +- Explore [API Reference](/reference/api/overview) for integration issues + diff --git a/docs/using/working-offline.md b/docs/using/working-offline.md new file mode 100644 index 0000000..426dffa --- /dev/null +++ b/docs/using/working-offline.md @@ -0,0 +1,71 @@ +--- +sidebar_position: 6 +--- + +# Working Offline + +ODE is designed to work seamlessly in offline conditions, allowing data collection to continue regardless of network connectivity. + +## Offline Capabilities + +When offline, you can: + +- Create new observations +- Edit existing observations +- Delete observations +- View all locally stored observations +- Fill out forms completely + +All changes are stored locally and synchronized when connectivity is restored. + +## Local Storage + +Observations are stored locally on the device using WatermelonDB, a reactive database optimized for React Native. This ensures: + +- Fast access to data +- Reliable storage +- Efficient querying +- Automatic conflict resolution + +## Synchronization When Online + +When network connectivity is restored: + +1. The app automatically detects connectivity +2. Synchronization begins automatically +3. Local changes are pushed to the server +4. New data is pulled from the server +5. Conflicts are resolved automatically + +## Offline Indicators + +The app provides visual indicators for offline status: + +- Connection status in the app header +- Sync status for individual observations +- Notification when sync completes + +## Best Practices + +When working offline: + +- Ensure sufficient device storage for observations +- Regularly sync when connectivity is available +- Monitor sync status to identify any issues +- Keep the app updated to ensure optimal offline performance + +## Limitations + +While offline, you cannot: + +- Access server-side features +- Download new forms (unless previously cached) +- Access real-time collaboration features +- View server-side analytics + +## Next Steps + +- Learn about [synchronization](/using/synchronization) in detail +- Review [troubleshooting](/using/troubleshooting) for offline issues +- Explore [data management](/using/data-management) features + diff --git a/docs/using/your-first-form.md b/docs/using/your-first-form.md new file mode 100644 index 0000000..41a2ff5 --- /dev/null +++ b/docs/using/your-first-form.md @@ -0,0 +1,123 @@ +--- +sidebar_position: 2 +--- + +# Your First Form + +This guide walks you through creating and submitting your first form using ODE. + +## Prerequisites + +Before starting, ensure you have: + +- Formulus app installed and configured +- Synkronus server running and accessible +- App connected to the server + +## Creating a Form + +Forms in ODE are defined using JSON schema. A basic form consists of a schema definition and optionally a UI schema. + +### Basic Form Example + +Here's a simple form that collects a name and age: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Full Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] +} +``` + +### Uploading the Form + +Upload the form to your Synkronus server using one of these methods: + + + + +```bash +curl -X POST http://your-server:8080/api/formspecs \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{ + "formType": "basic-survey", + "version": "1.0.0", + "schema": { + "type": "object", + "properties": { + "name": {"type": "string", "title": "Full Name"}, + "age": {"type": "integer", "title": "Age", "minimum": 0, "maximum": 120} + }, + "required": ["name", "age"] + } + }' +``` + + + + +```bash +# Create a form specification file (form.json) +synk formspec create form.json --form-type basic-survey --version 1.0.0 +``` + + + + +1. Navigate to the Portal +2. Go to "Form Specifications" +3. Click "Create New Form" +4. Enter form type and version +5. Paste or upload your JSON schema +6. Click "Save" + + + + +## Filling Out the Form + +1. Open the Formulus app on your device +2. Navigate to the forms list +3. Select your form from the list +4. Fill out the form fields +5. Review your entries +6. Submit the form + +## Submitting Observations + +When you submit a form, an observation is created. The observation contains: + +- The form data you entered +- Metadata such as creation timestamp +- A unique identifier +- Sync status + +Observations are stored locally on your device and synchronized to the server when connectivity is available. + +## Verifying Submission + +To verify that your observation was created: + +1. Check the app's observation list +2. Verify the observation appears with a "synced" status after synchronization +3. Query the server API to confirm the observation was received + +## Next Steps + +- Learn about [form design](/guides/forms/overview) to create more complex forms +- Explore [data management](/using/data-management) features +- Understand [synchronization](/using/synchronization) in detail + diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 837f960..bf9a94b 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -11,7 +11,7 @@ const config: Config = { baseUrl: '/', organizationName: 'opendataensemble', - projectName: 'docs', + projectName: 'ode-docs', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', @@ -27,41 +27,23 @@ const config: Config = { { docs: { sidebarPath: './sidebars.ts', - editUrl: 'https://github.com/opendataensemble/docs/tree/main/', + routeBasePath: '/docs', + editUrl: 'https://github.com/OpenDataEnsemble/ode/tree/main/ode-docs/', }, pages: { remarkPlugins: [], rehypePlugins: [], }, + blog: false, theme: { customCss: './src/css/custom.css', }, } satisfies Preset.Options, ], - [ - 'redocusaurus', - { - specs: [ - { - id: 'synkronus-api', - spec: 'synkronus.yaml', - route: '/api', - }, - ], - theme: { - primaryColor: '#4F7F4E', // ODE brand primary color - }, - }, - ], ], themeConfig: { image: 'img/docusaurus-social-card.jpg', - colorMode: { - defaultMode: 'light', - respectPrefersColorScheme: true, - disableSwitch: false, - }, navbar: { title: 'ODE', logo: { @@ -71,30 +53,35 @@ const config: Config = { hideOnScroll: false, items: [ { - type: 'doc', - docId: 'ODE', + type: 'docSidebar', + sidebarId: 'docs', label: 'Documentation', position: 'right', }, { - label: 'Product', + label: 'Components', position: 'right', items: [ { type: 'doc', - docId: 'documentation/synkronus/synkronus', - label: 'Synkronus', + docId: 'reference/formulus', + label: 'Formulus', }, { type: 'doc', - docId: 'documentation/formulus/formulus', - label: 'Formulus', + docId: 'reference/synkronus-server', + label: 'Synkronus Server', }, { type: 'doc', - docId: 'documentation/synkronus-cli/cli', + docId: 'reference/synkronus-cli', label: 'Synkronus CLI', }, + { + type: 'doc', + docId: 'reference/formplayer', + label: 'Formplayer', + }, ], }, { @@ -106,33 +93,44 @@ const config: Config = { href: 'https://forum.opendataensemble.org', }, { - label: 'Stack Overflow', - href: 'https://stackoverflow.com/questions/tagged/opendataensemble', + label: 'GitHub', + href: 'https://github.com/OpenDataEnsemble/ode', + }, + { + type: 'doc', + docId: 'community/getting-help', + label: 'Get Help', }, { - label: 'GitHub', - href: 'https://github.com/opendataensemble', + type: 'doc', + docId: 'community/examples', + label: 'Examples', }, ], }, { - label: 'About Us', + label: 'Resources', position: 'right', items: [ { type: 'doc', - docId: 'ODE', - label: 'Overview', + docId: 'getting-started/what-is-ode', + label: 'What is ODE?', }, { type: 'doc', - docId: 'quick-start/index', + docId: 'getting-started/quick-start', label: 'Quick Start', }, { type: 'doc', - docId: 'For-developers/docusaurus/intro', - label: 'For Developers', + docId: 'getting-started/installation', + label: 'Installation', + }, + { + type: 'doc', + docId: 'development/setup', + label: 'Development Setup', }, ], }, @@ -146,11 +144,19 @@ const config: Config = { items: [ { label: 'Overview', - to: '/docs/ODE', + to: '/docs', }, { - label: 'API Reference', - to: '/api', + label: 'Getting Started', + to: '/docs/getting-started', + }, + { + label: 'Using ODE', + to: '/docs/using', + }, + { + label: 'Reference', + to: '/docs/reference', }, ], }, @@ -167,7 +173,7 @@ const config: Config = { title: 'Contact', items: [ { - html: '', + html: '', }, { html: '', @@ -180,8 +186,15 @@ const config: Config = { prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula, + additionalLanguages: ['bash', 'json', 'yaml', 'typescript', 'javascript'], + }, + colorMode: { + defaultMode: 'light', + disableSwitch: false, + respectPrefersColorScheme: true, }, } satisfies Preset.ThemeConfig, }; export default config; + diff --git a/package-lock.json b/package-lock.json index a7e0ad4..372c51a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,74 +1,143 @@ { "name": "ode-docs", - "version": "0.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ode-docs", - "version": "0.0.0", + "version": "1.0.0", "dependencies": { - "@docusaurus/core": "^3.8.0", - "@docusaurus/preset-classic": "^3.8.0", + "@docusaurus/core": "^3.1.0", + "@docusaurus/preset-classic": "^3.1.0", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "redocusaurus": "^2.2.3" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.8.0", - "@docusaurus/tsconfig": "^3.8.0", - "@docusaurus/types": "^3.8.0", - "@types/node": "^20.0.0", - "ts-node": "^10.9.2", - "tsx": "^4.20.6", - "typescript": "~5.6.2" + "@docusaurus/module-type-aliases": "^3.1.0", + "@docusaurus/types": "^3.1.0", + "@types/node": "^20.19.27", + "tsx": "^4.21.0", + "typescript": "^5.2.2" }, "engines": { "node": ">=18.0" } }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz", - "integrity": "sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ==", - "license": "MIT", + "node_modules/@ai-sdk/gateway": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.23.tgz", + "integrity": "sha512-qmX7afPRszUqG5hryHF3UN8ITPIRSGmDW6VYCmByzjoUkgm3MekzSx2hMV1wr0P+llDeuXb378SjqUfpvWJulg==", + "license": "Apache-2.0", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.9", - "@algolia/autocomplete-shared": "1.17.9" + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.19", + "@vercel/oidc": "3.0.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz", - "integrity": "sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ==", - "license": "MIT", + "node_modules/@ai-sdk/provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", + "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "3.0.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.19.tgz", + "integrity": "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==", + "license": "Apache-2.0", "dependencies": { - "@algolia/autocomplete-shared": "1.17.9" + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "search-insights": ">= 1 < 3" + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "2.0.118", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.118.tgz", + "integrity": "sha512-K/5VVEGTIu9SWrdQ0s/11OldFU8IjprDzeE6TaC2fOcQWhG7dGVGl9H8Z32QBHzdfJyMhFUxEyFKSOgA2j9+VQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "3.0.19", + "ai": "5.0.116", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1", + "zod": "^3.25.76 || ^4.1.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.12.2.tgz", + "integrity": "sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" } }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz", - "integrity": "sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ==", + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.17.9" + "@algolia/autocomplete-shared": "1.19.2" }, "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" + "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz", - "integrity": "sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -76,99 +145,99 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.25.0.tgz", - "integrity": "sha512-1pfQulNUYNf1Tk/svbfjfkLBS36zsuph6m+B6gDkPEivFmso/XnRgwDvjAx80WNtiHnmeNjIXdF7Gos8+OLHqQ==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.46.2.tgz", + "integrity": "sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.25.0.tgz", - "integrity": "sha512-AFbG6VDJX/o2vDd9hqncj1B6B4Tulk61mY0pzTtzKClyTDlNP0xaUiEKhl6E7KO9I/x0FJF5tDCm0Hn6v5x18A==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.46.2.tgz", + "integrity": "sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.25.0.tgz", - "integrity": "sha512-il1zS/+Rc6la6RaCdSZ2YbJnkQC6W1wiBO8+SH+DE6CPMWBU6iDVzH0sCKSAtMWl9WBxoN6MhNjGBnCv9Yy2bA==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.46.2.tgz", + "integrity": "sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.25.0.tgz", - "integrity": "sha512-blbjrUH1siZNfyCGeq0iLQu00w3a4fBXm0WRIM0V8alcAPo7rWjLbMJMrfBtzL9X5ic6wgxVpDADXduGtdrnkw==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.46.2.tgz", + "integrity": "sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.25.0.tgz", - "integrity": "sha512-aywoEuu1NxChBcHZ1pWaat0Plw7A8jDMwjgRJ00Mcl7wGlwuPt5dJ/LTNcg3McsEUbs2MBNmw0ignXBw9Tbgow==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.46.2.tgz", + "integrity": "sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.25.0.tgz", - "integrity": "sha512-a/W2z6XWKjKjIW1QQQV8PTTj1TXtaKx79uR3NGBdBdGvVdt24KzGAaN7sCr5oP8DW4D3cJt44wp2OY/fZcPAVA==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.46.2.tgz", + "integrity": "sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.25.0.tgz", - "integrity": "sha512-9rUYcMIBOrCtYiLX49djyzxqdK9Dya/6Z/8sebPn94BekT+KLOpaZCuc6s0Fpfq7nx5J6YY5LIVFQrtioK9u0g==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.46.2.tgz", + "integrity": "sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" @@ -181,99 +250,86 @@ "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.25.0.tgz", - "integrity": "sha512-jJeH/Hk+k17Vkokf02lkfYE4A+EJX+UgnMhTLR/Mb+d1ya5WhE+po8p5a/Nxb6lo9OLCRl6w3Hmk1TX1e9gVbQ==", + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.46.2.tgz", + "integrity": "sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.25.0.tgz", - "integrity": "sha512-Ls3i1AehJ0C6xaHe7kK9vPmzImOn5zBg7Kzj8tRYIcmCWVyuuFwCIsbuIIz/qzUf1FPSWmw0TZrGeTumk2fqXg==", + "version": "1.46.2", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.46.2.tgz", + "integrity": "sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.25.0.tgz", - "integrity": "sha512-79sMdHpiRLXVxSjgw7Pt4R1aNUHxFLHiaTDnN2MQjHwJ1+o3wSseb55T9VXU4kqy3m7TUme3pyRhLk5ip/S4Mw==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.46.2.tgz", + "integrity": "sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "@algolia/client-common": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.25.0.tgz", - "integrity": "sha512-JLaF23p1SOPBmfEqozUAgKHQrGl3z/Z5RHbggBu6s07QqXXcazEsub5VLonCxGVqTv6a61AAPr8J1G5HgGGjEw==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.46.2.tgz", + "integrity": "sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0" + "@algolia/client-common": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.25.0.tgz", - "integrity": "sha512-rtzXwqzFi1edkOF6sXxq+HhmRKDy7tz84u0o5t1fXwz0cwx+cjpmxu/6OQKTdOJFS92JUYHsG51Iunie7xbqfQ==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.46.2.tgz", + "integrity": "sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0" + "@algolia/client-common": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.25.0.tgz", - "integrity": "sha512-ZO0UKvDyEFvyeJQX0gmZDQEvhLZ2X10K+ps6hViMo1HgE2V8em00SwNsQ+7E/52a+YiBkVWX61pJJJE44juDMQ==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.46.2.tgz", + "integrity": "sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.25.0" + "@algolia/client-common": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -289,30 +345,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -337,15 +393,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -390,17 +446,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "engines": { @@ -420,13 +476,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -446,29 +502,38 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -488,14 +553,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -582,9 +647,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -600,39 +665,39 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", - "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -642,13 +707,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -705,13 +770,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -836,14 +901,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", - "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -885,9 +950,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", - "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -916,12 +981,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -932,17 +997,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -968,12 +1033,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1044,10 +1110,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1138,9 +1220,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1200,15 +1282,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1295,15 +1377,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" }, "engines": { "node": ">=6.9.0" @@ -1344,9 +1427,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -1360,9 +1443,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1438,9 +1521,9 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz", - "integrity": "sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1503,9 +1586,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", - "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1549,16 +1632,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", - "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", + "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "engines": { @@ -1654,13 +1737,13 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", - "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" @@ -1736,63 +1819,64 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", - "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1805,10 +1889,10 @@ "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "engines": { @@ -1842,14 +1926,14 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", - "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" @@ -1862,16 +1946,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1881,21 +1965,21 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.4.tgz", - "integrity": "sha512-H7QhL0ucCGOObsUETNbB2PuzF4gAvN8p32P6r91bX7M/hk4bx+3yz2hTwHL9d/Efzwu1upeb4/cd7oSxCzup3w==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", + "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", "license": "MIT", "dependencies": { - "core-js-pure": "^3.30.2" + "core-js-pure": "^3.43.0" }, "engines": { "node": ">=6.9.0" @@ -1916,31 +2000,31 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1956,30 +2040,6 @@ "node": ">=0.1.90" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@csstools/cascade-layer-name-parser": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", @@ -2004,9 +2064,9 @@ } }, "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "funding": [ { "type": "github", @@ -2046,9 +2106,9 @@ } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "funding": [ { "type": "github", @@ -2061,7 +2121,7 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.0.2", + "@csstools/color-helpers": "^5.1.0", "@csstools/css-calc": "^2.1.4" }, "engines": { @@ -2136,10 +2196,39 @@ "@csstools/css-tokenizer": "^3.0.4" } }, + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", + "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", - "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", "funding": [ { "type": "github", @@ -2185,9 +2274,9 @@ } }, "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2198,9 +2287,38 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.10.tgz", - "integrity": "sha512-4dY0NBu7NVIpzxZRgh/Q/0GPSz/jLSw0i/u3LTUor0BkQcz/fNhN10mSWBDsL0p9nDb0Ky1PD6/dcGbhACuFTQ==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", + "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", + "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", "funding": [ { "type": "github", @@ -2213,10 +2331,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2227,9 +2345,9 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.10.tgz", - "integrity": "sha512-P0lIbQW9I4ShE7uBgZRib/lMTf9XMjJkFl/d6w4EMNHu2qvQ6zljJGEcBkw/NsBtq/6q3WrmgxSS8kHtPMkK4Q==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", + "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", "funding": [ { "type": "github", @@ -2242,10 +2360,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2256,9 +2374,9 @@ } }, "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.0.tgz", - "integrity": "sha512-Z5WhouTyD74dPFPrVE7KydgNS9VvnjB8qcdes9ARpCOItb4jTnm7cHp4FhxCRUoyhabD0WVv43wbkJ4p8hLAlQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", + "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", "funding": [ { "type": "github", @@ -2271,10 +2389,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2285,9 +2403,37 @@ } }, "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.6.tgz", - "integrity": "sha512-eRjLbOjblXq+byyaedQRSrAejKGNAFued+LcbzT+LCL78fabxHkxYjBbxkroONxHHYu2qxhFK2dBStTLPG3jpQ==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", + "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-contrast-color-function": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", + "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", "funding": [ { "type": "github", @@ -2300,9 +2446,10 @@ ], "license": "MIT-0", "dependencies": { + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2366,9 +2513,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.10.tgz", - "integrity": "sha512-QDGqhJlvFnDlaPAfCYPsnwVA6ze+8hhrwevYWlnUeSjkkZfBpcCO42SaUD8jiLlq7niouyLgvup5lh+f1qessg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", "funding": [ { "type": "github", @@ -2381,7 +2528,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" }, @@ -2393,9 +2540,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.10.tgz", - "integrity": "sha512-HHPauB2k7Oits02tKFUeVFEU2ox/H3OQVrP3fSOKDxvloOikSal+3dzlyTZmYsb9FlY9p5EUpBtz0//XBmy+aw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", + "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", "funding": [ { "type": "github", @@ -2408,10 +2555,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2422,9 +2569,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.10.tgz", - "integrity": "sha512-nOKKfp14SWcdEQ++S9/4TgRKchooLZL0TUFdun3nI4KPwCjETmhjta1QT4ICQcGVWQTvrsgMM/aLB5We+kMHhQ==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", + "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", "funding": [ { "type": "github", @@ -2437,10 +2584,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2451,9 +2598,9 @@ } }, "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.2.tgz", - "integrity": "sha512-lrK2jjyZwh7DbxaNnIUjkeDmU8Y6KyzRBk91ZkI5h8nb1ykEfZrtIVArdIjX4DHMIBGpdHrgP0n4qXDr7OHaKA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", + "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", "funding": [ { "type": "github", @@ -2466,7 +2613,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, @@ -2500,9 +2647,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", - "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", "funding": [ { "type": "github", @@ -2548,9 +2695,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2561,9 +2708,9 @@ } }, "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.9.tgz", - "integrity": "sha512-1tCZH5bla0EAkFAI2r0H33CDnIBeLUaJh1p+hvvsylJ4svsv2wOmJjJn+OXwUZLXef37GYbRIVKX+X+g6m+3CQ==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", + "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", "funding": [ { "type": "github", @@ -2578,7 +2725,7 @@ "dependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2812,9 +2959,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.10.tgz", - "integrity": "sha512-ZzZUTDd0fgNdhv8UUjGCtObPD8LYxMH+MJsW9xlZaWTV8Ppr4PtxlHYNMmF4vVWGl0T6f8tyWAKjoI6vePSgAg==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", + "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", "funding": [ { "type": "github", @@ -2827,10 +2974,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2840,10 +2987,10 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.1.0.tgz", - "integrity": "sha512-YrkI9dx8U4R8Sz2EJaoeD9fI7s7kmeEBfmO+UURNeL6lQI7VxF6sBE+rSqdCBn4onwqmxFdBU3lTwyYb/lCmxA==", + "node_modules/@csstools/postcss-position-area-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-position-area-property/-/postcss-position-area-property-1.0.0.tgz", + "integrity": "sha512-fUP6KR8qV2NuUZV3Cw8itx0Ep90aRjAZxAEzC3vrl6yjFv+pFsQbR18UuQctEKmA72K9O27CoYiKEgXxkqjg8Q==", "funding": [ { "type": "github", @@ -2855,9 +3002,6 @@ } ], "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, "engines": { "node": ">=18" }, @@ -2865,10 +3009,10 @@ "postcss": "^8.4" } }, - "node_modules/@csstools/postcss-random-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", - "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", + "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", "funding": [ { "type": "github", @@ -2881,9 +3025,60 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-property-rule-prelude-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-property-rule-prelude-list/-/postcss-property-rule-prelude-list-1.0.0.tgz", + "integrity": "sha512-IxuQjUXq19fobgmSSvUDO7fVwijDJaZMvWQugxfEUxmjBeDCVaDuMpsZ31MsTm5xbnhA+ElDi0+rQ7sQQGisFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" }, "engines": { "node": ">=18" @@ -2893,9 +3088,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.10.tgz", - "integrity": "sha512-8+0kQbQGg9yYG8hv0dtEpOMLwB9M+P7PhacgIzVzJpixxV4Eq9AUQtQw8adMmAJU1RBBmIlpmtmm3XTRd/T00g==", + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", + "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", "funding": [ { "type": "github", @@ -2908,10 +3103,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2947,9 +3142,9 @@ } }, "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -3013,10 +3208,61 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/postcss-syntax-descriptor-syntax-production": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-syntax-descriptor-syntax-production/-/postcss-syntax-descriptor-syntax-production-1.0.1.tgz", + "integrity": "sha512-GneqQWefjM//f4hJ/Kbox0C6f2T7+pi4/fqTqOFGTL3EjnvOReTqO1qUQ30CaUjkwjYq9qZ41hzarrAxCc4gow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-system-ui-font-family": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-system-ui-font-family/-/postcss-system-ui-font-family-1.0.0.tgz", + "integrity": "sha512-s3xdBvfWYfoPSBsikDXbuorcMG1nN1M6GdU0qBsGfcmNR0A/qhloQZpTxjA3Xsyrk1VJvwb2pOfiOT3at/DuIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.2.tgz", - "integrity": "sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", "funding": [ { "type": "github", @@ -3029,7 +3275,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/color-helpers": "^5.0.2", + "@csstools/color-helpers": "^5.1.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -3119,22 +3365,48 @@ "node": ">=10.0.0" } }, + "node_modules/@docsearch/core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.4.0.tgz", + "integrity": "sha512-kiwNo5KEndOnrf5Kq/e5+D9NBMCFgNsDoRpKQJ9o/xnSlheh6b8AXppMuuUVVdAUIhIfQFk/07VLjjk/fYyKmw==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/@docsearch/css": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.9.0.tgz", - "integrity": "sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.4.0.tgz", + "integrity": "sha512-e9vPgtih6fkawakmYo0Y6V4BKBmDV7Ykudn7ADWXUs5b6pmtBRwDbpSG/WiaUG63G28OkJDEnsMvgIAnZgGwYw==", "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.9.0.tgz", - "integrity": "sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.4.0.tgz", + "integrity": "sha512-z12zeg1mV7WD4Ag4pKSuGukETJLaucVFwszDXL/qLaEgRqxEaVacO9SR1qqnCXvZztlvz2rt7cMqryi/7sKfjA==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-core": "1.17.9", - "@algolia/autocomplete-preset-algolia": "1.17.9", - "@docsearch/css": "3.9.0", - "algoliasearch": "^5.14.2" + "@ai-sdk/react": "^2.0.30", + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/core": "4.4.0", + "@docsearch/css": "4.4.0", + "ai": "^5.0.30", + "algoliasearch": "^5.28.0", + "marked": "^16.3.0", + "zod": "^4.1.8" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -3158,9 +3430,9 @@ } }, "node_modules/@docusaurus/babel": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.8.0.tgz", - "integrity": "sha512-9EJwSgS6TgB8IzGk1L8XddJLhZod8fXT4ULYMx6SKqyCBqCFpVCEjR/hNXXhnmtVM2irDuzYoVLGWv7srG/VOA==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.2.tgz", + "integrity": "sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", @@ -3173,41 +3445,41 @@ "@babel/runtime": "^7.25.9", "@babel/runtime-corejs3": "^7.25.9", "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.8.0", - "@docusaurus/utils": "3.8.0", + "@docusaurus/logger": "3.9.2", + "@docusaurus/utils": "3.9.2", "babel-plugin-dynamic-import-node": "^2.3.3", "fs-extra": "^11.1.1", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@docusaurus/bundler": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.8.0.tgz", - "integrity": "sha512-Rq4Z/MSeAHjVzBLirLeMcjLIAQy92pF1OI+2rmt18fSlMARfTGLWRE8Vb+ljQPTOSfJxwDYSzsK6i7XloD2rNA==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.2.tgz", + "integrity": "sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.8.0", - "@docusaurus/cssnano-preset": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", + "@docusaurus/babel": "3.9.2", + "@docusaurus/cssnano-preset": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", "babel-loader": "^9.2.1", - "clean-css": "^5.3.2", + "clean-css": "^5.3.3", "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.8.1", + "css-loader": "^6.11.0", "css-minimizer-webpack-plugin": "^5.0.1", "cssnano": "^6.1.2", "file-loader": "^6.2.0", "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.1", + "mini-css-extract-plugin": "^2.9.2", "null-loader": "^4.0.1", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", - "postcss-preset-env": "^10.1.0", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", "terser-webpack-plugin": "^5.3.9", "tslib": "^2.6.0", "url-loader": "^4.1.1", @@ -3215,7 +3487,7 @@ "webpackbar": "^6.0.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "@docusaurus/faster": "*" @@ -3227,18 +3499,18 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.8.0.tgz", - "integrity": "sha512-c7u6zFELmSGPEP9WSubhVDjgnpiHgDqMh1qVdCB7rTflh4Jx0msTYmMiO91Ez0KtHj4sIsDsASnjwfJ2IZp3Vw==", - "license": "MIT", - "dependencies": { - "@docusaurus/babel": "3.8.0", - "@docusaurus/bundler": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/mdx-loader": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", + "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.9.2", + "@docusaurus/bundler": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/mdx-loader": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "boxen": "^6.2.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", @@ -3272,14 +3544,14 @@ "update-notifier": "^6.0.2", "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^4.15.2", + "webpack-dev-server": "^5.2.2", "webpack-merge": "^6.0.1" }, "bin": { "docusaurus": "bin/docusaurus.mjs" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "@mdx-js/react": "^3.0.0", @@ -3288,42 +3560,42 @@ } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.8.0.tgz", - "integrity": "sha512-UJ4hAS2T0R4WNy+phwVff2Q0L5+RXW9cwlH6AEphHR5qw3m/yacfWcSK7ort2pMMbDn8uGrD38BTm4oLkuuNoQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz", + "integrity": "sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==", "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.4.38", + "postcss": "^8.5.4", "postcss-sort-media-queries": "^5.2.0", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@docusaurus/logger": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.8.0.tgz", - "integrity": "sha512-7eEMaFIam5Q+v8XwGqF/n0ZoCld4hV4eCCgQkfcN9Mq5inoZa6PHHW9Wu6lmgzoK5Kx3keEeABcO2SxwraoPDQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.2.tgz", + "integrity": "sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.8.0.tgz", - "integrity": "sha512-mDPSzssRnpjSdCGuv7z2EIAnPS1MHuZGTaRLwPn4oQwszu4afjWZ/60sfKjTnjBjI8Vl4OgJl2vMmfmiNDX4Ng==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz", + "integrity": "sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/logger": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -3347,7 +3619,7 @@ "webpack": "^5.88.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3355,12 +3627,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.8.0.tgz", - "integrity": "sha512-/uMb4Ipt5J/QnD13MpnoC/A4EYAe6DKNWqTWLlGrqsPJwJv73vSwkA25xnYunwfqWk0FlUQfGv/Swdh5eCCg7g==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", + "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.8.0", + "@docusaurus/types": "3.9.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -3374,19 +3646,19 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.8.0.tgz", - "integrity": "sha512-0SlOTd9R55WEr1GgIXu+hhTT0hzARYx3zIScA5IzpdekZQesI/hKEa5LPHBd415fLkWMjdD59TaW/3qQKpJ0Lg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/mdx-loader": "3.8.0", - "@docusaurus/theme-common": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz", + "integrity": "sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/mdx-loader": "3.9.2", + "@docusaurus/theme-common": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", @@ -3399,7 +3671,7 @@ "webpack": "^5.88.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "@docusaurus/plugin-content-docs": "*", @@ -3408,20 +3680,20 @@ } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.8.0.tgz", - "integrity": "sha512-fRDMFLbUN6eVRXcjP8s3Y7HpAt9pzPYh1F/7KKXOCxvJhjjCtbon4VJW0WndEPInVz4t8QUXn5QZkU2tGVCE2g==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/mdx-loader": "3.8.0", - "@docusaurus/module-type-aliases": "3.8.0", - "@docusaurus/theme-common": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", + "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/mdx-loader": "3.9.2", + "@docusaurus/module-type-aliases": "3.9.2", + "@docusaurus/theme-common": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -3433,7 +3705,7 @@ "webpack": "^5.88.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3441,22 +3713,22 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.8.0.tgz", - "integrity": "sha512-39EDx2y1GA0Pxfion5tQZLNJxL4gq6susd1xzetVBjVIQtwpCdyloOfQBAgX0FylqQxfJrYqL0DIUuq7rd7uBw==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz", + "integrity": "sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/mdx-loader": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/mdx-loader": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3464,35 +3736,36 @@ } }, "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.8.0.tgz", - "integrity": "sha512-/VBTNymPIxQB8oA3ZQ4GFFRYdH4ZxDRRBECxyjRyv486mfUPXfcdk+im4S5mKWa6EK2JzBz95IH/Wu0qQgJ5yQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz", + "integrity": "sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.8.0.tgz", - "integrity": "sha512-teonJvJsDB9o2OnG6ifbhblg/PXzZvpUKHFgD8dOL1UJ58u0lk8o0ZOkvaYEBa9nDgqzoWrRk9w+e3qaG2mOhQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz", + "integrity": "sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", "fs-extra": "^11.1.1", "react-json-view-lite": "^2.3.0", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3500,18 +3773,18 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.8.0.tgz", - "integrity": "sha512-aKKa7Q8+3xRSRESipNvlFgNp3FNPELKhuo48Cg/svQbGNwidSHbZT03JqbW4cBaQnyyVchO1ttk+kJ5VC9Gx0w==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz", + "integrity": "sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3519,19 +3792,19 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.8.0.tgz", - "integrity": "sha512-ugQYMGF4BjbAW/JIBtVcp+9eZEgT9HRdvdcDudl5rywNPBA0lct+lXMG3r17s02rrhInMpjMahN3Yc9Cb3H5/g==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz", + "integrity": "sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3539,18 +3812,18 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.8.0.tgz", - "integrity": "sha512-9juRWxbwZD3SV02Jd9QB6yeN7eu+7T4zB0bvJLcVQwi+am51wAxn2CwbdL0YCCX+9OfiXbADE8D8Q65Hbopu/w==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz", + "integrity": "sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3558,23 +3831,23 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.8.0.tgz", - "integrity": "sha512-fGpOIyJvNiuAb90nSJ2Gfy/hUOaDu6826e5w5UxPmbpCIc7KlBHNAZ5g4L4ZuHhc4hdfq4mzVBsQSnne+8Ze1g==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz", + "integrity": "sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3582,22 +3855,22 @@ } }, "node_modules/@docusaurus/plugin-svgr": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.8.0.tgz", - "integrity": "sha512-kEDyry+4OMz6BWLG/lEqrNsL/w818bywK70N1gytViw4m9iAmoxCUT7Ri9Dgs7xUdzCHJ3OujolEmD88Wy44OA==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz", + "integrity": "sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "@svgr/core": "8.1.0", "@svgr/webpack": "^8.1.0", "tslib": "^2.6.0", "webpack": "^5.88.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3605,29 +3878,29 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.8.0.tgz", - "integrity": "sha512-qOu6tQDOWv+rpTlKu+eJATCJVGnABpRCPuqf7LbEaQ1mNY//N/P8cHQwkpAU+aweQfarcZ0XfwCqRHJfjeSV/g==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/plugin-content-blog": "3.8.0", - "@docusaurus/plugin-content-docs": "3.8.0", - "@docusaurus/plugin-content-pages": "3.8.0", - "@docusaurus/plugin-css-cascade-layers": "3.8.0", - "@docusaurus/plugin-debug": "3.8.0", - "@docusaurus/plugin-google-analytics": "3.8.0", - "@docusaurus/plugin-google-gtag": "3.8.0", - "@docusaurus/plugin-google-tag-manager": "3.8.0", - "@docusaurus/plugin-sitemap": "3.8.0", - "@docusaurus/plugin-svgr": "3.8.0", - "@docusaurus/theme-classic": "3.8.0", - "@docusaurus/theme-common": "3.8.0", - "@docusaurus/theme-search-algolia": "3.8.0", - "@docusaurus/types": "3.8.0" + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz", + "integrity": "sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.2", + "@docusaurus/plugin-content-blog": "3.9.2", + "@docusaurus/plugin-content-docs": "3.9.2", + "@docusaurus/plugin-content-pages": "3.9.2", + "@docusaurus/plugin-css-cascade-layers": "3.9.2", + "@docusaurus/plugin-debug": "3.9.2", + "@docusaurus/plugin-google-analytics": "3.9.2", + "@docusaurus/plugin-google-gtag": "3.9.2", + "@docusaurus/plugin-google-tag-manager": "3.9.2", + "@docusaurus/plugin-sitemap": "3.9.2", + "@docusaurus/plugin-svgr": "3.9.2", + "@docusaurus/theme-classic": "3.9.2", + "@docusaurus/theme-common": "3.9.2", + "@docusaurus/theme-search-algolia": "3.9.2", + "@docusaurus/types": "3.9.2" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3635,31 +3908,30 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.8.0.tgz", - "integrity": "sha512-nQWFiD5ZjoT76OaELt2n33P3WVuuCz8Dt5KFRP2fCBo2r9JCLsp2GJjZpnaG24LZ5/arRjv4VqWKgpK0/YLt7g==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/mdx-loader": "3.8.0", - "@docusaurus/module-type-aliases": "3.8.0", - "@docusaurus/plugin-content-blog": "3.8.0", - "@docusaurus/plugin-content-docs": "3.8.0", - "@docusaurus/plugin-content-pages": "3.8.0", - "@docusaurus/theme-common": "3.8.0", - "@docusaurus/theme-translations": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz", + "integrity": "sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/mdx-loader": "3.9.2", + "@docusaurus/module-type-aliases": "3.9.2", + "@docusaurus/plugin-content-blog": "3.9.2", + "@docusaurus/plugin-content-docs": "3.9.2", + "@docusaurus/plugin-content-pages": "3.9.2", + "@docusaurus/theme-common": "3.9.2", + "@docusaurus/theme-translations": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", - "copy-text-to-clipboard": "^3.2.0", "infima": "0.2.0-alpha.45", "lodash": "^4.17.21", "nprogress": "^0.2.0", - "postcss": "^8.4.26", + "postcss": "^8.5.4", "prism-react-renderer": "^2.3.0", "prismjs": "^1.29.0", "react-router-dom": "^5.3.4", @@ -3668,7 +3940,7 @@ "utility-types": "^3.10.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3676,15 +3948,15 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.8.0.tgz", - "integrity": "sha512-YqV2vAWpXGLA+A3PMLrOMtqgTHJLDcT+1Caa6RF7N4/IWgrevy5diY8oIHFkXR/eybjcrFFjUPrHif8gSGs3Tw==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", + "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", "license": "MIT", "dependencies": { - "@docusaurus/mdx-loader": "3.8.0", - "@docusaurus/module-type-aliases": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", + "@docusaurus/mdx-loader": "3.9.2", + "@docusaurus/module-type-aliases": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -3695,7 +3967,7 @@ "utility-types": "^3.10.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "@docusaurus/plugin-content-docs": "*", @@ -3704,21 +3976,21 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.8.0.tgz", - "integrity": "sha512-GBZ5UOcPgiu6nUw153+0+PNWvFKweSnvKIL6Rp04H9olKb475jfKjAwCCtju5D2xs5qXHvCMvzWOg5o9f6DtuQ==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "^3.9.0", - "@docusaurus/core": "3.8.0", - "@docusaurus/logger": "3.8.0", - "@docusaurus/plugin-content-docs": "3.8.0", - "@docusaurus/theme-common": "3.8.0", - "@docusaurus/theme-translations": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-validation": "3.8.0", - "algoliasearch": "^5.17.1", - "algoliasearch-helper": "^3.22.6", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz", + "integrity": "sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==", + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.9.0 || ^4.1.0", + "@docusaurus/core": "3.9.2", + "@docusaurus/logger": "3.9.2", + "@docusaurus/plugin-content-docs": "3.9.2", + "@docusaurus/theme-common": "3.9.2", + "@docusaurus/theme-translations": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-validation": "3.9.2", + "algoliasearch": "^5.37.0", + "algoliasearch-helper": "^3.26.0", "clsx": "^2.0.0", "eta": "^2.2.0", "fs-extra": "^11.1.1", @@ -3727,7 +3999,7 @@ "utility-types": "^3.10.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -3735,33 +4007,27 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.8.0.tgz", - "integrity": "sha512-1DTy/snHicgkCkryWq54fZvsAglTdjTx4qjOXgqnXJ+DIty1B+aPQrAVUu8LiM+6BiILfmNxYsxhKTj+BS3PZg==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz", + "integrity": "sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, - "node_modules/@docusaurus/tsconfig": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.8.0.tgz", - "integrity": "sha512-utLl48nNjSYBoq47RKukZ9fPLEX3nJWThzrujb0ndQQ1jc/gh4RhTRaAqItH9nImnsgGKmLMnyoMBpfGmoop+w==", - "dev": true, - "license": "MIT" - }, "node_modules/@docusaurus/types": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.8.0.tgz", - "integrity": "sha512-RDEClpwNxZq02c+JlaKLWoS13qwWhjcNsi2wG1UpzmEnuti/z1Wx4SGpqbUqRPNSd8QWWePR8Cb7DvG0VN/TtA==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", + "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", + "@types/mdast": "^4.0.2", "@types/react": "*", "commander": "^5.1.0", "joi": "^17.9.2", @@ -3790,14 +4056,14 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.8.0.tgz", - "integrity": "sha512-2wvtG28ALCN/A1WCSLxPASFBFzXCnP0YKCAFIPcvEb6imNu1wg7ni/Svcp71b3Z2FaOFFIv4Hq+j4gD7gA0yfQ==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz", + "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.8.0", - "@docusaurus/types": "3.8.0", - "@docusaurus/utils-common": "3.8.0", + "@docusaurus/logger": "3.9.2", + "@docusaurus/types": "3.9.2", + "@docusaurus/utils-common": "3.9.2", "escape-string-regexp": "^4.0.0", "execa": "5.1.1", "file-loader": "^6.2.0", @@ -3818,31 +4084,31 @@ "webpack": "^5.88.1" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@docusaurus/utils-common": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.8.0.tgz", - "integrity": "sha512-3TGF+wVTGgQ3pAc9+5jVchES4uXUAhAt9pwv7uws4mVOxL4alvU3ue/EZ+R4XuGk94pDy7CNXjRXpPjlfZXQfw==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.2.tgz", + "integrity": "sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.8.0", + "@docusaurus/types": "3.9.2", "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.8.0.tgz", - "integrity": "sha512-MrnEbkigr54HkdFeg8e4FKc4EF+E9dlVwsY3XQZsNkbv3MKZnbHQ5LsNJDIKDROFe8PBf5C4qCAg5TPBpsjrjg==", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz", + "integrity": "sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.8.0", - "@docusaurus/utils": "3.8.0", - "@docusaurus/utils-common": "3.8.0", + "@docusaurus/logger": "3.9.2", + "@docusaurus/utils": "3.9.2", + "@docusaurus/utils-common": "3.9.2", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -3850,34 +4116,13 @@ "tslib": "^2.6.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", - "license": "MIT" - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "license": "MIT" - }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -3892,9 +4137,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -3909,9 +4154,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -3926,9 +4171,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -3943,9 +4188,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -3960,9 +4205,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -3977,9 +4222,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -3994,9 +4239,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -4011,9 +4256,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -4028,9 +4273,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -4045,9 +4290,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -4062,9 +4307,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -4079,9 +4324,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -4096,9 +4341,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -4113,9 +4358,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -4130,9 +4375,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -4147,9 +4392,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -4164,9 +4409,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -4181,9 +4426,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -4198,9 +4443,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -4215,9 +4460,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -4232,9 +4477,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -4249,9 +4494,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -4266,9 +4511,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -4283,9 +4528,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -4300,9 +4545,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -4316,12 +4561,6 @@ "node": ">=18" } }, - "node_modules/@exodus/schemasafe": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", - "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==", - "license": "MIT" - }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -4367,17 +4606,23 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -4389,19 +4634,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -4409,21 +4645,135 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -4431,15 +4781,16 @@ "license": "MIT" }, "node_modules/@mdx-js/mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", - "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", @@ -4467,9 +4818,9 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", - "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", "license": "MIT", "dependencies": { "@types/mdx": "^2.0.0" @@ -4518,6 +4869,15 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -4565,76 +4925,6 @@ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "license": "MIT" }, - "node_modules/@redocly/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js-replace": "^1.0.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@redocly/config": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.2.tgz", - "integrity": "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==", - "license": "MIT" - }, - "node_modules/@redocly/openapi-core": { - "version": "1.34.3", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.3.tgz", - "integrity": "sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==", - "license": "MIT", - "dependencies": { - "@redocly/ajv": "^8.11.2", - "@redocly/config": "^0.22.0", - "colorette": "^1.2.0", - "https-proxy-agent": "^7.0.5", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "minimatch": "^5.0.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=18.17.0", - "npm": ">=9.5.0" - } - }, - "node_modules/@redocly/openapi-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@redocly/openapi-core/node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT" - }, - "node_modules/@redocly/openapi-core/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4685,6 +4975,12 @@ "micromark-util-symbol": "^1.0.1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -4963,38 +5259,10 @@ "node": ">=10.13.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "license": "MIT", "dependencies": { "@types/connect": "*", @@ -5059,9 +5327,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -5074,33 +5342,21 @@ } }, "node_modules/@types/express": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", - "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "4.19.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", + "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -5143,15 +5399,15 @@ "license": "MIT" }, "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -5215,18 +5471,18 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -5251,12 +5507,12 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", - "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-router": { @@ -5292,9 +5548,9 @@ } }, "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "license": "MIT" }, "node_modules/@types/sax": { @@ -5307,12 +5563,11 @@ } }, "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -5326,14 +5581,24 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/sockjs": { @@ -5345,19 +5610,6 @@ "@types/node": "*" } }, - "node_modules/@types/stylis": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", - "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", - "license": "MIT" - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -5374,9 +5626,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -5394,6 +5646,15 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@vercel/oidc": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz", + "integrity": "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 20" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -5596,9 +5857,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5607,6 +5868,18 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -5637,15 +5910,6 @@ "node": ">= 10.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -5659,6 +5923,24 @@ "node": ">=8" } }, + "node_modules/ai": { + "version": "5.0.116", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.116.tgz", + "integrity": "sha512-+2hYJ80/NcDWuv9K2/MLP3cTCFgwWHmHlS1tOpFUKKcmLbErAAlE/S2knsKboc3PNAu8pQkDr2N3K/Vle7ENgQ==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "2.0.23", + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.19", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -5705,33 +5987,34 @@ } }, "node_modules/algoliasearch": { - "version": "5.25.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.25.0.tgz", - "integrity": "sha512-n73BVorL4HIwKlfJKb4SEzAYkR3Buwfwbh+MYxg2mloFph2fFGV58E90QTzdbfzWrLn4HE5Czx/WTjI8fcHaMg==", - "license": "MIT", - "dependencies": { - "@algolia/client-abtesting": "5.25.0", - "@algolia/client-analytics": "5.25.0", - "@algolia/client-common": "5.25.0", - "@algolia/client-insights": "5.25.0", - "@algolia/client-personalization": "5.25.0", - "@algolia/client-query-suggestions": "5.25.0", - "@algolia/client-search": "5.25.0", - "@algolia/ingestion": "1.25.0", - "@algolia/monitoring": "1.25.0", - "@algolia/recommend": "5.25.0", - "@algolia/requester-browser-xhr": "5.25.0", - "@algolia/requester-fetch": "5.25.0", - "@algolia/requester-node-http": "5.25.0" + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.46.2.tgz", + "integrity": "sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.12.2", + "@algolia/client-abtesting": "5.46.2", + "@algolia/client-analytics": "5.46.2", + "@algolia/client-common": "5.46.2", + "@algolia/client-insights": "5.46.2", + "@algolia/client-personalization": "5.46.2", + "@algolia/client-query-suggestions": "5.46.2", + "@algolia/client-search": "5.46.2", + "@algolia/ingestion": "1.46.2", + "@algolia/monitoring": "1.46.2", + "@algolia/recommend": "5.46.2", + "@algolia/requester-browser-xhr": "5.46.2", + "@algolia/requester-fetch": "5.46.2", + "@algolia/requester-node-http": "5.46.2" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.25.0.tgz", - "integrity": "sha512-vQoK43U6HXA9/euCqLjvyNdM4G2Fiu/VFp4ae0Gau9sZeIKBPvUPnXfLYAe65Bg7PFuw03coeu5K6lTPSXRObw==", + "version": "3.26.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.1.tgz", + "integrity": "sha512-CAlCxm4fYBXtvc5MamDzP6Svu8rW4z9me4DCBY1rQ2UDJ0u0flWmusQ8M3nOExZsLLRcUwUPoRAPMrhzOG3erw==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -5882,9 +6165,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", "funding": [ { "type": "opencollective", @@ -5901,10 +6184,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -5945,13 +6227,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -5968,25 +6250,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6008,6 +6290,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6036,23 +6327,23 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -6122,9 +6413,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6144,9 +6435,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -6163,10 +6454,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -6181,6 +6473,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -6264,12 +6571,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "license": "MIT" - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6301,15 +6602,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6323,9 +6615,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "funding": [ { "type": "opencollective", @@ -6503,12 +6795,6 @@ "node": ">=8" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -6557,56 +6843,22 @@ "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "license": "MIT", "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^4.2.0" }, "engines": { - "node": ">=12" + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/cliui/node_modules/emoji-regex": { + "node_modules/cli-table3/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, - "node_modules/cliui/node_modules/string-width": { + "node_modules/cli-table3/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -6620,23 +6872,6 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -6756,16 +6991,16 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -6813,6 +7048,12 @@ "proto-list": "~1.2.1" } }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/configstore": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", @@ -6875,32 +7116,20 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, - "node_modules/copy-text-to-clipboard": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", - "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/copy-webpack-plugin": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", @@ -6969,9 +7198,9 @@ } }, "node_modules/core-js": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", - "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -6980,12 +7209,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", + "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.28.0" }, "funding": { "type": "opencollective", @@ -6993,9 +7222,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.42.0.tgz", - "integrity": "sha512-007bM04u91fF4kMgwom2I5cQxAFIy8jVulgr9eozILl/SZE53QOqnW/+vviC+wQWLv+AunBG+8Q0TLoeSsSxRQ==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", + "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -7035,13 +7264,6 @@ } } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7109,9 +7331,9 @@ } }, "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -7121,19 +7343,10 @@ "node": ">=4" } }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", - "license": "ISC", - "engines": { - "node": ">=4" - } - }, "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", + "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -7143,9 +7356,9 @@ } }, "node_modules/css-has-pseudo": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz", - "integrity": "sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", "funding": [ { "type": "github", @@ -7192,9 +7405,9 @@ } }, "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -7306,9 +7519,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -7321,17 +7534,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "license": "MIT", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -7346,9 +7548,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -7358,9 +7560,9 @@ } }, "node_modules/cssdb": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.3.0.tgz", - "integrity": "sha512-c7bmItIg38DgGjSwDPZOYF/2o0QU/sSgkWOMyl8votOfgFuyiFKWPesmCGEsrGLxEA9uL540cp8LdaGEjUGsZQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.6.0.tgz", + "integrity": "sha512-7ZrRi/Z3cRL1d5I8RuXEWAkRFP3J4GeQRiyVknI4KC70RAU8hT4LysUZDe0y+fYNOktCbxE8sOPUOhyR12UqGQ==", "funding": [ { "type": "opencollective", @@ -7516,9 +7718,9 @@ "license": "CC0-1.0" }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/debounce": { @@ -7528,9 +7730,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -7544,15 +7746,10 @@ } } }, - "node_modules/decko": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", - "integrity": "sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==" - }, "node_modules/decode-named-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", - "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -7607,16 +7804,32 @@ "node": ">=0.10.0" } }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "license": "BSD-2-Clause", + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "license": "MIT", "dependencies": { - "execa": "^5.0.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/defer-to-connect": { @@ -7735,16 +7948,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7769,166 +7972,6 @@ "node": ">=6" } }, - "node_modules/docusaurus-plugin-redoc": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/docusaurus-plugin-redoc/-/docusaurus-plugin-redoc-2.2.3.tgz", - "integrity": "sha512-uqzrKudLBNg1kOe/tDMo0Rt1zwDFUALAjttm/q+THe5m/up/nrSdRhlT5rkiyT4puVupJ9QEcN9aV3q7395Dyg==", - "license": "MIT", - "dependencies": { - "@redocly/openapi-core": "1.16.0", - "redoc": "2.4.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@docusaurus/utils": "^3.6.0" - } - }, - "node_modules/docusaurus-plugin-redoc/node_modules/@redocly/config": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.6.3.tgz", - "integrity": "sha512-hGWJgCsXRw0Ow4rplqRlUQifZvoSwZipkYnt11e3SeH1Eb23VUIDBcRuaQOUqy1wn0eevXkU2GzzQ8fbKdQ7Mg==", - "license": "MIT" - }, - "node_modules/docusaurus-plugin-redoc/node_modules/@redocly/openapi-core": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.16.0.tgz", - "integrity": "sha512-z06h+svyqbUcdAaePq8LPSwTPlm6Ig7j2VlL8skPBYnJvyaQ2IN7x/JkOvRL4ta+wcOCBdAex5JWnZbKaNktJg==", - "license": "MIT", - "dependencies": { - "@redocly/ajv": "^8.11.0", - "@redocly/config": "^0.6.0", - "colorette": "^1.2.0", - "https-proxy-agent": "^7.0.4", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "lodash.isequal": "^4.5.0", - "minimatch": "^5.0.1", - "node-fetch": "^2.6.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=14.19.0", - "npm": ">=7.0.0" - } - }, - "node_modules/docusaurus-plugin-redoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/docusaurus-plugin-redoc/node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT" - }, - "node_modules/docusaurus-plugin-redoc/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/docusaurus-theme-redoc": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/docusaurus-theme-redoc/-/docusaurus-theme-redoc-2.2.3.tgz", - "integrity": "sha512-n0pseml+Z4JD/nVehGxb8EZzrmI5krJc05P+B7JIV4NEGoLtUab455UxANXMGel1JnsNem0aplxWrY1fskccOw==", - "license": "MIT", - "dependencies": { - "@redocly/openapi-core": "1.16.0", - "clsx": "^1.2.1", - "lodash": "^4.17.21", - "mobx": "^6.12.4", - "postcss": "^8.4.45", - "postcss-prefix-selector": "^1.16.1", - "redoc": "2.4.0", - "styled-components": "^6.1.11" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@docusaurus/theme-common": "^3.6.0", - "webpack": "^5.0.0" - } - }, - "node_modules/docusaurus-theme-redoc/node_modules/@redocly/config": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.6.3.tgz", - "integrity": "sha512-hGWJgCsXRw0Ow4rplqRlUQifZvoSwZipkYnt11e3SeH1Eb23VUIDBcRuaQOUqy1wn0eevXkU2GzzQ8fbKdQ7Mg==", - "license": "MIT" - }, - "node_modules/docusaurus-theme-redoc/node_modules/@redocly/openapi-core": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.16.0.tgz", - "integrity": "sha512-z06h+svyqbUcdAaePq8LPSwTPlm6Ig7j2VlL8skPBYnJvyaQ2IN7x/JkOvRL4ta+wcOCBdAex5JWnZbKaNktJg==", - "license": "MIT", - "dependencies": { - "@redocly/ajv": "^8.11.0", - "@redocly/config": "^0.6.0", - "colorette": "^1.2.0", - "https-proxy-agent": "^7.0.4", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "lodash.isequal": "^4.5.0", - "minimatch": "^5.0.1", - "node-fetch": "^2.6.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=14.19.0", - "npm": ">=7.0.0" - } - }, - "node_modules/docusaurus-theme-redoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/docusaurus-theme-redoc/node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/docusaurus-theme-redoc/node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT" - }, - "node_modules/docusaurus-theme-redoc/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -7979,15 +8022,6 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -8069,9 +8103,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.155", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", - "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -8115,9 +8149,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -8140,9 +8174,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -8167,9 +8201,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -8184,12 +8218,6 @@ "node": ">= 0.4" } }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", - "license": "MIT" - }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", @@ -8223,9 +8251,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -8236,32 +8264,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -8428,9 +8456,9 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz", - "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", + "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -8519,6 +8547,15 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -8543,39 +8580,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -8676,16 +8713,10 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -8698,28 +8729,10 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.1.1" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -8868,17 +8881,17 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -8942,9 +8955,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -8961,12 +8974,6 @@ } } }, - "node_modules/foreach": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", - "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", - "license": "MIT" - }, "node_modules/form-data-encoder": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", @@ -8994,15 +9001,15 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -9016,9 +9023,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -9029,18 +9036,6 @@ "node": ">=14.14" } }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -9073,15 +9068,6 @@ "node": ">=6.9.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -9156,27 +9142,6 @@ "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", "license": "ISC" }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -9189,6 +9154,22 @@ "node": ">= 6" } }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -9204,28 +9185,10 @@ "ini": "2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globby": { @@ -9328,9 +9291,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -9532,15 +9495,15 @@ } }, "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", - "property-information": "^6.0.0", + "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" @@ -9550,16 +9513,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-parse5/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -9670,22 +9623,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "license": "MIT" - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -9745,9 +9682,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz", + "integrity": "sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==", "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -9838,19 +9775,23 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-parser-js": { @@ -9909,12 +9850,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http2-client": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", - "license": "MIT" - }, "node_modules/http2-wrapper": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", @@ -9928,19 +9863,6 @@ "node": ">=10.19.0" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -9950,6 +9872,15 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10047,17 +9978,6 @@ "node": ">=12" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -10065,15 +9985,18 @@ "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, "node_modules/invariant": { @@ -10086,9 +10009,9 @@ } }, "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "license": "MIT", "engines": { "node": ">= 10" @@ -10237,6 +10160,39 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", @@ -10253,10 +10209,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -10454,15 +10422,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10470,9 +10429,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -10505,14 +10464,11 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, - "node_modules/json-pointer": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", - "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", - "license": "MIT", - "dependencies": { - "foreach": "^2.0.4" - } + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -10533,9 +10489,9 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -10587,13 +10543,13 @@ } }, "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "license": "MIT", "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "node_modules/leven": { @@ -10624,12 +10580,16 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -10673,13 +10633,6 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", - "license": "MIT" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -10744,25 +10697,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "license": "MIT" - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "license": "MIT" - }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -10786,15 +10720,15 @@ } }, "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 12" + "node": ">= 20" } }, "node_modules/math-intrinsics": { @@ -11154,9 +11088,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -11224,15 +11158,21 @@ } }, "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "license": "Unlicense", + "version": "4.51.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.51.1.tgz", + "integrity": "sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==", + "license": "Apache-2.0", "dependencies": { - "fs-monkey": "^1.0.4" + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" }, - "engines": { - "node": ">= 4.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" } }, "node_modules/merge-descriptors": { @@ -13120,9 +13060,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", - "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", @@ -13166,66 +13106,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mobx": { - "version": "6.13.7", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", - "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - } - }, - "node_modules/mobx-react": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-9.2.0.tgz", - "integrity": "sha512-dkGWCx+S0/1mfiuFfHRH8D9cplmwhxOV5CkXMp38u6rQGG2Pv3FWYztS0M7ncR6TyPRQKaTG/pnitInoYE9Vrw==", - "license": "MIT", - "dependencies": { - "mobx-react-lite": "^4.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - }, - "peerDependencies": { - "mobx": "^6.9.0", - "react": "^16.8.0 || ^17 || ^18 || ^19" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/mobx-react-lite": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.0.tgz", - "integrity": "sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - }, - "peerDependencies": { - "mobx": "^6.9.0", - "react": "^16.8.0 || ^17 || ^18 || ^19" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -13312,60 +13192,19 @@ "node": ">=18" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch-h2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", - "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", - "license": "MIT", - "dependencies": { - "http2-client": "^1.2.5" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } }, - "node_modules/node-readfiles": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", - "integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==", - "license": "MIT", - "dependencies": { - "es6-promise": "^3.2.1" - } - }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, "node_modules/normalize-path": { @@ -13377,19 +13216,10 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", "license": "MIT", "engines": { "node": ">=14.16" @@ -13497,76 +13327,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/oas-kit-common": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", - "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", - "license": "BSD-3-Clause", - "dependencies": { - "fast-safe-stringify": "^2.0.7" - } - }, - "node_modules/oas-linter": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", - "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@exodus/schemasafe": "^1.0.0-rc.2", - "should": "^13.2.1", - "yaml": "^1.10.0" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/oas-resolver": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", - "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", - "license": "BSD-3-Clause", - "dependencies": { - "node-fetch-h2": "^2.3.0", - "oas-kit-common": "^1.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "resolve": "resolve.js" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/oas-schema-walker": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", - "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", - "license": "BSD-3-Clause", - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/oas-validator": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", - "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", - "license": "BSD-3-Clause", - "dependencies": { - "call-me-maybe": "^1.0.1", - "oas-kit-common": "^1.0.8", - "oas-linter": "^3.2.2", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.9", - "should": "^13.2.1", - "yaml": "^1.10.0" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -13636,23 +13396,14 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -13685,17 +13436,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-sampler": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.6.1.tgz", - "integrity": "sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.7", - "fast-xml-parser": "^4.5.0", - "json-pointer": "0.6.2" - } - }, "node_modules/opener": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", @@ -13785,16 +13525,20 @@ } }, "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "license": "MIT", "dependencies": { - "@types/retry": "0.12.0", + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { @@ -13924,9 +13668,9 @@ } }, "node_modules/parse5/node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -13954,12 +13698,6 @@ "tslib": "^2.0.3" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "license": "MIT" - }, "node_modules/path-exists": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", @@ -13969,15 +13707,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", @@ -14017,12 +13746,6 @@ "node": ">=8" } }, - "node_modules/perfect-scrollbar": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", - "integrity": "sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==", - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -14056,31 +13779,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/polished": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", - "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.17.8" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -14097,7 +13799,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -14131,9 +13833,9 @@ } }, "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14175,9 +13877,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.10.tgz", - "integrity": "sha512-k9qX+aXHBiLTRrWoCJuUFI6F1iF6QJQUXNVWJVSbqZgj57jDhBlOvD8gNUGl35tgqDivbGLhZeW3Ongz4feuKA==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", + "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", "funding": [ { "type": "github", @@ -14190,10 +13892,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -14318,9 +14020,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.5.tgz", - "integrity": "sha512-UWf/vhMapZatv+zOuqlfLmYXeOhhHLh8U8HAKGI2VJ00xLRYoAJh4xv8iX6FB6+TLXeDnm0DBLMi00E0hodbQw==", + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", "funding": [ { "type": "github", @@ -14375,9 +14077,9 @@ } }, "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14413,9 +14115,9 @@ } }, "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14489,9 +14191,9 @@ } }, "node_modules/postcss-double-position-gradients": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.2.tgz", - "integrity": "sha512-7qTqnL7nfLRyJK/AHSVrrXOuvDDzettC+wGoienURV8v2svNbu6zJC52ruZtHaO6mfcagFmuTGFdzRsJKB3k5Q==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", + "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", "funding": [ { "type": "github", @@ -14504,7 +14206,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, @@ -14541,9 +14243,9 @@ } }, "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14579,9 +14281,9 @@ } }, "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14649,9 +14351,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.10.tgz", - "integrity": "sha512-tqs6TCEv9tC1Riq6fOzHuHcZyhg4k3gIAMB8GGY/zA1ssGdm6puHMVE7t75aOSoFg7UD2wyrFFhbldiCMyyFTQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", + "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", "funding": [ { "type": "github", @@ -14664,10 +14366,10 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -14868,9 +14570,9 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14896,9 +14598,9 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14924,9 +14626,9 @@ } }, "node_modules/postcss-nesting": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", - "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", "funding": [ { "type": "github", @@ -14939,7 +14641,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, @@ -14951,9 +14653,9 @@ } }, "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", - "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", "funding": [ { "type": "github", @@ -14995,9 +14697,9 @@ } }, "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15237,19 +14939,10 @@ "postcss": "^8.4" } }, - "node_modules/postcss-prefix-selector": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/postcss-prefix-selector/-/postcss-prefix-selector-1.16.1.tgz", - "integrity": "sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==", - "license": "MIT", - "peerDependencies": { - "postcss": ">4 <9" - } - }, "node_modules/postcss-preset-env": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.2.0.tgz", - "integrity": "sha512-cl13sPBbSqo1Q7Ryb19oT5NZO5IHFolRbIMdgDq4f9w1MHYiL6uZS7uSsjXJ1KzRIcX5BMjEeyxmAevVXENa3Q==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.6.0.tgz", + "integrity": "sha512-+LzpUSLCGHUdlZ1YZP7lp7w1MjxInJRSG0uaLyk/V/BM17iU2B7xTO7I8x3uk0WQAcLLh/ffqKzOzfaBvG7Fdw==", "funding": [ { "type": "github", @@ -15262,20 +14955,23 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/postcss-cascade-layers": "^5.0.1", - "@csstools/postcss-color-function": "^4.0.10", - "@csstools/postcss-color-mix-function": "^3.0.10", - "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.0", - "@csstools/postcss-content-alt-text": "^2.0.6", + "@csstools/postcss-alpha-function": "^1.0.1", + "@csstools/postcss-cascade-layers": "^5.0.2", + "@csstools/postcss-color-function": "^4.0.12", + "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", + "@csstools/postcss-color-mix-function": "^3.0.12", + "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", + "@csstools/postcss-content-alt-text": "^2.0.8", + "@csstools/postcss-contrast-color-function": "^2.0.12", "@csstools/postcss-exponential-functions": "^2.0.9", "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.10", - "@csstools/postcss-gradients-interpolation-method": "^5.0.10", - "@csstools/postcss-hwb-function": "^4.0.10", - "@csstools/postcss-ic-unit": "^4.0.2", + "@csstools/postcss-gamut-mapping": "^2.0.11", + "@csstools/postcss-gradients-interpolation-method": "^5.0.12", + "@csstools/postcss-hwb-function": "^4.0.12", + "@csstools/postcss-ic-unit": "^4.0.4", "@csstools/postcss-initial": "^2.0.1", - "@csstools/postcss-is-pseudo-class": "^5.0.1", - "@csstools/postcss-light-dark-function": "^2.0.9", + "@csstools/postcss-is-pseudo-class": "^5.0.3", + "@csstools/postcss-light-dark-function": "^2.0.11", "@csstools/postcss-logical-float-and-clear": "^3.0.0", "@csstools/postcss-logical-overflow": "^2.0.0", "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", @@ -15285,40 +14981,44 @@ "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", "@csstools/postcss-nested-calc": "^4.0.0", "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.10", - "@csstools/postcss-progressive-custom-properties": "^4.1.0", + "@csstools/postcss-oklab-function": "^4.0.12", + "@csstools/postcss-position-area-property": "^1.0.0", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/postcss-property-rule-prelude-list": "^1.0.0", "@csstools/postcss-random-function": "^2.0.1", - "@csstools/postcss-relative-color-syntax": "^3.0.10", + "@csstools/postcss-relative-color-syntax": "^3.0.12", "@csstools/postcss-scope-pseudo-class": "^4.0.1", "@csstools/postcss-sign-functions": "^1.1.4", "@csstools/postcss-stepped-value-functions": "^4.0.9", - "@csstools/postcss-text-decoration-shorthand": "^4.0.2", + "@csstools/postcss-syntax-descriptor-syntax-production": "^1.0.1", + "@csstools/postcss-system-ui-font-family": "^1.0.0", + "@csstools/postcss-text-decoration-shorthand": "^4.0.3", "@csstools/postcss-trigonometric-functions": "^4.0.9", "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.21", - "browserslist": "^4.24.5", + "autoprefixer": "^10.4.23", + "browserslist": "^4.28.1", "css-blank-pseudo": "^7.0.1", - "css-has-pseudo": "^7.0.2", + "css-has-pseudo": "^7.0.3", "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.3.0", + "cssdb": "^8.6.0", "postcss-attribute-case-insensitive": "^7.0.1", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.10", + "postcss-color-functional-notation": "^7.0.12", "postcss-color-hex-alpha": "^10.0.0", "postcss-color-rebeccapurple": "^10.0.0", "postcss-custom-media": "^11.0.6", - "postcss-custom-properties": "^14.0.5", + "postcss-custom-properties": "^14.0.6", "postcss-custom-selectors": "^8.0.5", "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.2", + "postcss-double-position-gradients": "^6.0.4", "postcss-focus-visible": "^10.0.1", "postcss-focus-within": "^9.0.1", "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^6.0.0", "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.10", + "postcss-lab-function": "^7.0.12", "postcss-logical": "^8.1.0", - "postcss-nesting": "^13.0.1", + "postcss-nesting": "^13.0.2", "postcss-opacity-percentage": "^3.0.0", "postcss-overflow-shorthand": "^6.0.0", "postcss-page-break": "^3.0.4", @@ -15360,9 +15060,9 @@ } }, "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15453,9 +15153,9 @@ } }, "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -15661,9 +15361,9 @@ } }, "node_modules/pupa": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", "license": "MIT", "dependencies": { "escape-goat": "^4.0.0" @@ -15676,12 +15376,12 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -15741,15 +15441,15 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" @@ -15779,6 +15479,12 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -15789,24 +15495,28 @@ } }, "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { - "scheduler": "^0.26.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.1.0" + "react": "^18.3.1" } }, "node_modules/react-fast-compare": { @@ -15840,9 +15550,9 @@ "license": "MIT" }, "node_modules/react-json-view-lite": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.4.1.tgz", - "integrity": "sha512-fwFYknRIBxjbFm0kBDrzgBy1xa5tDg2LyXXBepC5f1b+MY3BUClMCsvanMPn089JbV1Eg3nZcrp0VCuH43aXnA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", "license": "MIT", "engines": { "node": ">=18" @@ -15931,19 +15641,6 @@ "react": ">=15" } }, - "node_modules/react-tabs": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.1.0.tgz", - "integrity": "sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==", - "license": "MIT", - "dependencies": { - "clsx": "^2.0.0", - "prop-types": "^15.5.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -15986,9 +15683,9 @@ } }, "node_modules/recma-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", - "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", "license": "MIT", "dependencies": { "acorn-jsx": "^5.0.0", @@ -16000,6 +15697,9 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/recma-parse": { @@ -16034,87 +15734,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/redoc": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.4.0.tgz", - "integrity": "sha512-rFlfzFVWS9XJ6aYAs/bHnLhHP5FQEhwAHDBVgwb9L2FqDQ8Hu8rQ1G84iwaWXxZfPP9UWn7JdWkxI6MXr2ZDjw==", - "license": "MIT", - "dependencies": { - "@redocly/openapi-core": "^1.4.0", - "classnames": "^2.3.2", - "decko": "^1.2.0", - "dompurify": "^3.0.6", - "eventemitter3": "^5.0.1", - "json-pointer": "^0.6.2", - "lunr": "^2.3.9", - "mark.js": "^8.11.1", - "marked": "^4.3.0", - "mobx-react": "^9.1.1", - "openapi-sampler": "^1.5.0", - "path-browserify": "^1.0.1", - "perfect-scrollbar": "^1.5.5", - "polished": "^4.2.2", - "prismjs": "^1.29.0", - "prop-types": "^15.8.1", - "react-tabs": "^6.0.2", - "slugify": "~1.4.7", - "stickyfill": "^1.1.1", - "swagger2openapi": "^7.0.8", - "url-template": "^2.0.8" - }, - "engines": { - "node": ">=6.9", - "npm": ">=3.0.0" - }, - "peerDependencies": { - "core-js": "^3.1.4", - "mobx": "^6.0.4", - "react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5" - } - }, - "node_modules/redoc/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/redoc/node_modules/slugify": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", - "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/redocusaurus": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/redocusaurus/-/redocusaurus-2.2.3.tgz", - "integrity": "sha512-WpmgC+/TS7yEtlV11TJarQU7xhD13HjOwWVwB2g2S3MsZ7KKcKt7eDMTqHWMQV7D4j4uvhzlGjut9knstN+Q9g==", - "license": "MIT", - "dependencies": { - "docusaurus-plugin-redoc": "2.2.3", - "docusaurus-theme-redoc": "2.2.3" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@docusaurus/theme-common": "^3.6.0", - "@docusaurus/utils": "^3.6.0" - } - }, - "node_modules/reftools": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", - "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", - "license": "BSD-3-Clause", - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -16122,9 +15741,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -16134,17 +15753,17 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -16184,29 +15803,17 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -16313,9 +15920,9 @@ } }, "node_modules/remark-mdx": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", - "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", "license": "MIT", "dependencies": { "mdast-util-mdx": "^3.0.0", @@ -16483,15 +16090,6 @@ "node": ">=0.10" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -16516,12 +16114,12 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -16600,22 +16198,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rtlcss": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", @@ -16634,6 +16216,18 @@ "node": ">=12.0.0" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -16684,16 +16278,19 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC" + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/schema-dts": { "version": "1.1.5", @@ -16702,9 +16299,9 @@ "license": "Apache-2.0" }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -16760,9 +16357,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -16787,24 +16384,24 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -16825,15 +16422,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/send/node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -16952,15 +16540,15 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -17029,9 +16617,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -17040,60 +16628,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "license": "MIT", - "dependencies": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" - } - }, - "node_modules/should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "license": "MIT", - "dependencies": { - "should-type": "^1.4.0" - } - }, - "node_modules/should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", - "license": "MIT", - "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" - } - }, - "node_modules/should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", - "license": "MIT" - }, - "node_modules/should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "license": "MIT", - "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" - } - }, - "node_modules/should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "license": "MIT" - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -17269,12 +16803,12 @@ } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -17364,25 +16898,20 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, - "node_modules/stickyfill": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", - "integrity": "sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==" - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -17410,9 +16939,9 @@ } }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -17422,9 +16951,9 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -17497,106 +17026,32 @@ "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/style-to-js": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", - "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.8" - } - }, - "node_modules/style-to-object": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", - "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.4" - } - }, - "node_modules/styled-components": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.18.tgz", - "integrity": "sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw==", - "license": "MIT", - "dependencies": { - "@emotion/is-prop-valid": "1.2.2", - "@emotion/unitless": "0.8.1", - "@types/stylis": "4.2.5", - "css-to-react-native": "3.2.0", - "csstype": "3.1.3", - "postcss": "8.4.49", - "shallowequal": "1.1.0", - "stylis": "4.3.2", - "tslib": "2.6.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0" - } - }, - "node_modules/styled-components/node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/styled-components/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "license": "0BSD" + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } }, "node_modules/stylehacks": { "version": "6.1.1", @@ -17614,12 +17069,6 @@ "postcss": "^8.4.31" } }, - "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", - "license": "MIT" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -17684,50 +17133,40 @@ "node": ">= 10" } }, - "node_modules/swagger2openapi": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", - "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", - "license": "BSD-3-Clause", + "node_modules/swr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.8.tgz", + "integrity": "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==", + "license": "MIT", "dependencies": { - "call-me-maybe": "^1.0.1", - "node-fetch": "^2.6.1", - "node-fetch-h2": "^2.3.0", - "node-readfiles": "^0.2.0", - "oas-kit-common": "^1.0.8", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "oas-validator": "^5.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "boast": "boast.js", - "oas-validate": "oas-validate.js", - "swagger2openapi": "swagger2openapi.js" + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.39.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.2.tgz", - "integrity": "sha512-yEPUmWve+VA78bI71BW70Dh0TuV4HHd+I5SHOAfS1+QBOmvmCiiffgjR8ryyEd3KIfvPGFqoADt8LdQ6XpXIvg==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -17739,9 +17178,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -17807,6 +17246,34 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/thingies": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -17826,9 +17293,9 @@ "license": "MIT" }, "node_modules/tinypool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", - "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -17864,11 +17331,21 @@ "node": ">=6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } }, "node_modules/trim-lines": { "version": "3.0.1", @@ -17890,57 +17367,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -17948,13 +17374,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.20.6", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", - "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.25.0", + "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -18023,9 +17449,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -18074,18 +17500,18 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", "engines": { "node": ">=4" @@ -18126,9 +17552,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -18193,9 +17619,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -18225,9 +17651,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -18317,9 +17743,9 @@ } }, "node_modules/update-notifier/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -18337,12 +17763,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uri-js-replace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", - "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", - "license": "MIT" - }, "node_modules/url-loader": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", @@ -18440,16 +17860,10 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", - "license": "BSD" - }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -18494,13 +17908,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -18545,9 +17952,9 @@ } }, "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -18559,9 +17966,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", + "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -18590,42 +17997,37 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, "node_modules/webpack": { - "version": "5.99.8", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz", - "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -18679,47 +18081,57 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", "license": "MIT", "dependencies": { "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } } }, "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/webpack-dev-middleware/node_modules/range-parser": { @@ -18732,54 +18144,52 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.7.4", "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", + "express": "^4.21.2", "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -18790,10 +18200,40 @@ } } }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -18826,9 +18266,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -18950,16 +18390,6 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -19014,9 +18444,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -19026,9 +18456,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -19038,9 +18468,9 @@ } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -19052,12 +18482,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", @@ -19091,6 +18515,36 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xdg-basedir": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", @@ -19115,97 +18569,16 @@ "xml-js": "bin/cli.js" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yaml-ast-parser": { - "version": "0.0.43", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", - "license": "Apache-2.0" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { "node": ">=12.20" @@ -19214,6 +18587,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", + "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index e9182f7..a1fc636 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ode-docs", - "version": "0.0.0", + "version": "1.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -17,23 +17,20 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "^3.8.0", - "@docusaurus/preset-classic": "^3.8.0", + "@docusaurus/core": "^3.1.0", + "@docusaurus/preset-classic": "^3.1.0", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "redocusaurus": "^2.2.3" + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.8.0", - "@docusaurus/tsconfig": "^3.8.0", - "@docusaurus/types": "^3.8.0", - "@types/node": "^20.0.0", - "ts-node": "^10.9.2", - "tsx": "^4.20.6", - "typescript": "~5.6.2" + "@docusaurus/module-type-aliases": "^3.1.0", + "@docusaurus/types": "^3.1.0", + "@types/node": "^20.19.27", + "tsx": "^4.21.0", + "typescript": "^5.2.2" }, "browserslist": { "production": [ @@ -44,7 +41,7 @@ "development": [ "last 3 chrome version", "last 3 firefox version", - "last 5 safari version" + "last 4 safari version" ] }, "engines": { diff --git a/scripts/validate-docs.ts b/scripts/validate-docs.ts old mode 100644 new mode 100755 diff --git a/sidebars.ts b/sidebars.ts index a9b4718..a750a8c 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -1,320 +1,118 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; /** - * Sidebar configuration for ODE Documentation + * Consolidated sidebar structure following documentation best practices: + * - User-focused content first + * - Related content grouped together + * - Avoid overcrowding with too many small pages + * - Clear separation between user and developer content */ const sidebars: SidebarsConfig = { docs: [ - 'ODE', { - type: 'category', - label: 'Quick Start', - items: [ - 'quick-start/index', - 'quick-start/prerequisites', - 'quick-start/setup-environment', - 'quick-start/deploy-local-instance', - 'quick-start/upload-test-data', - { - type: 'category', - label: 'Installation', - items: [ - 'quick-start/formulus-app', - 'quick-start/synkronus-server', - 'quick-start/custom-app', - ], - }, - 'quick-start/faq', - ], + type: 'doc', + id: 'index', + label: 'Introduction', }, { type: 'category', - label: 'Technical Overview', + label: 'Getting Started', + link: { + type: 'doc', + id: 'getting-started/index', + }, items: [ - 'technical-overview/index', - { - type: 'category', - label: 'Architecture', - items: [ - 'technical-overview/architecture/overview', - 'technical-overview/architecture/components', - 'technical-overview/architecture/data-flow', - ], - }, - { - type: 'category', - label: 'Concepts', - items: [ - 'technical-overview/concepts/overview', - 'technical-overview/concepts/offline-first', - 'technical-overview/concepts/json-forms', - 'technical-overview/concepts/app-bundles', - 'technical-overview/concepts/custom-apps', - ], - }, - { - type: 'category', - label: 'Database', - items: [ - 'technical-overview/database/overview', - 'technical-overview/database/schema', - ], - }, + 'getting-started/what-is-ode', + 'getting-started/why-ode', + 'getting-started/key-concepts', + 'getting-started/installation', + 'getting-started/installing-formulus', + 'getting-started/quick-start', + 'getting-started/faq', ], }, { type: 'category', - label: 'Build', + label: 'Using ODE', + link: { + type: 'doc', + id: 'using/index', + }, items: [ - 'build/index', - { - type: 'category', - label: 'Forms', - items: [ - 'build/forms/overview', - { - type: 'category', - label: 'Form Design', - items: [ - 'build/forms/design/schema-definition', - 'build/forms/design/ui-schema', - 'build/forms/design/validation', - 'build/forms/design/conditional-logic', - ], - }, - { - type: 'category', - label: 'Advanced Features', - items: [ - 'build/forms/advanced-features/multimedia', - 'build/forms/advanced-features/location', - 'build/forms/advanced-features/attachments', - ], - }, - 'build/forms/versioning', - ], - }, - { - type: 'category', - label: 'Custom Applications', - items: [ - 'build/custom-applications/overview', - 'build/custom-applications/building', - 'build/custom-applications/app-bundle-structure', - 'build/custom-applications/deployment', - 'build/custom-applications/custom-renderers', - ], - }, - { - type: 'category', - label: 'Data Management', - items: [ - 'build/data-management/overview', - 'build/data-management/observations', - 'build/data-management/attachments', - 'build/data-management/export', - 'build/data-management/import', - ], - }, - { - type: 'category', - label: 'Synchronization', - items: [ - 'build/synchronization/overview', - 'build/synchronization/sync-protocol', - 'build/synchronization/conflict-resolution', - 'build/synchronization/troubleshooting', - ], - }, - 'build/users-authentication', - 'build/translations', - 'build/branding', + 'using/your-first-form', + 'using/formulus-features', + 'using/app-bundles', + 'using/data-management', + 'using/synchronization', + 'using/custom-applications', + 'using/working-offline', + 'using/troubleshooting', ], }, { type: 'category', - label: 'Components', + label: 'Guides', + link: { + type: 'doc', + id: 'guides/index', + }, items: [ - { - type: 'category', - label: 'Formulus', - items: [ - 'components/formulus/overview', - 'components/formulus/installation', - 'components/formulus/configuration', - 'components/formulus/features', - 'components/formulus/troubleshooting', - 'components/formulus/integration', - ], - }, - { - type: 'category', - label: 'Synkronus', - items: [ - 'components/synkronus/overview', - 'components/synkronus/installation', - 'components/synkronus/configuration', - 'components/synkronus/api-reference', - ], - }, - { - type: 'category', - label: 'Synkronus CLI', - items: [ - 'components/synkronus-cli/overview', - 'components/synkronus-cli/installation', - 'components/synkronus-cli/commands-reference', - ], - }, - { - type: 'category', - label: 'Formplayer', - items: [ - 'components/formplayer/overview', - 'components/formplayer/integration', - ], - }, - { - type: 'category', - label: 'Documentation', - items: [ - 'documentation/formulus/formulus', - 'documentation/synkronus/synkronus', - 'documentation/synkronus/app-bundle', - 'documentation/synkronus-cli/cli', - ], - }, - ], - }, - { - type: 'category', - label: 'Host', - items: [ - 'host/index', - { - type: 'category', - label: 'Synkronus Server', - items: [ - 'host/synkronus-server/overview', - 'host/synkronus-server/requirements', - 'host/synkronus-server/production', - 'host/synkronus-server/docker', - 'host/synkronus-server/kubernetes', - 'host/synkronus-server/cloud', - 'host/synkronus-server/monitoring', - 'host/synkronus-server/backups', - ], - }, - { - type: 'category', - label: 'Monitoring & Alerting', - items: [ - 'host/monitoring/overview', - 'host/monitoring/setup', - 'host/monitoring/production', - ], - }, - 'host/data-management', - 'host/security', + 'guides/form-design', + 'guides/custom-applications', + 'guides/deployment', + 'guides/configuration', ], }, { type: 'category', label: 'Reference', + link: { + type: 'doc', + id: 'reference/index', + }, items: [ - 'reference/index', - { - type: 'category', - label: 'REST API', - items: [ - 'reference/rest-api/overview', - 'reference/rest-api/authentication', - 'reference/rest-api/app-bundle', - 'reference/rest-api/sync', - 'reference/rest-api/attachments', - ], - }, - { - type: 'category', - label: 'Configuration', - items: [ - 'reference/configuration/server', - 'reference/configuration/client', - ], - }, + 'reference/api', + 'reference/components', + 'reference/formulus', + 'reference/formplayer', + 'reference/formplayer-contract', + 'reference/synkronus-cli', + 'reference/synkronus-server', + 'reference/synkronus-portal', 'reference/form-specifications', 'reference/app-bundle-format', ], }, { type: 'category', - label: 'Tutorials', + label: 'Development', + link: { + type: 'doc', + id: 'development/index', + }, items: [ - 'tutorials/index', - { - type: 'category', - label: 'Getting Started', - items: [ - 'tutorials/getting-started/first-form', - 'tutorials/getting-started/first-custom-app', - 'tutorials/getting-started/connecting-everything', - ], - }, - { - type: 'category', - label: 'Advanced Topics', - items: [ - 'tutorials/advanced/complex-forms', - 'tutorials/advanced/custom-renderers', - 'tutorials/advanced/performance', - ], - }, + 'development/setup', + 'development/installing-formulus-dev', + 'development/architecture', + 'development/formulus-development', + 'development/formplayer-development', + 'development/synkronus-development', + 'development/synkronus-portal-development', + 'development/contributing', + 'development/building-testing', + 'development/extending', ], }, { type: 'category', label: 'Community', + link: { + type: 'doc', + id: 'community/index', + }, items: [ - 'community/index', - { - type: 'category', - label: 'Contribute', - items: [ - 'community/contribute/about', - 'community/contribute/first-time', - 'community/contribute/code-of-conduct', - ], - }, - { - type: 'category', - label: 'Support', - items: [ - 'community/support/getting-help', - 'community/support/reporting-issues', - ], - }, - { - type: 'category', - label: 'Resources', - items: [ - 'community/resources/examples', - 'community/resources/projects', - ], - }, - ], - }, - { - type: 'category', - label: 'For Developers', - items: [ - 'For-developers/docusaurus/intro', - { - type: 'category', - label: 'Tutorial Extras', - items: [ - 'For-developers/docusaurus/tutorial-extras/manage-docs-versions', - ], - }, + 'community/getting-help', + 'community/examples', ], }, ], diff --git a/src/components/AnnouncementBanner/index.tsx b/src/components/AnnouncementBanner/index.tsx index 1d9a80a..bac0fd5 100644 --- a/src/components/AnnouncementBanner/index.tsx +++ b/src/components/AnnouncementBanner/index.tsx @@ -10,7 +10,7 @@ export default function AnnouncementBanner(): React.ReactElement {
NEW Try the Formulus Android app pre-release! - + Install Now →
@@ -18,3 +18,4 @@ export default function AnnouncementBanner(): React.ReactElement { ); } + diff --git a/src/components/AnnouncementBanner/styles.module.css b/src/components/AnnouncementBanner/styles.module.css index 834394d..3c091df 100644 --- a/src/components/AnnouncementBanner/styles.module.css +++ b/src/components/AnnouncementBanner/styles.module.css @@ -1,10 +1,10 @@ .banner { - background: linear-gradient(135deg, var(--ode-color-brand-primary-500) 0%, var(--ode-color-brand-primary-600) 100%); - color: var(--ode-color-neutral-white); - padding: var(--ode-spacing-4) var(--ode-spacing-0); + background: linear-gradient(135deg, var(--ode-primary) 0%, var(--ode-primary-dark) 100%); + color: white; + padding: 1rem 0; position: relative; overflow: hidden; - box-shadow: var(--ode-shadow-sm); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); } .banner::before { @@ -14,9 +14,9 @@ right: -10%; width: 200px; height: 200px; - background: rgba(255, 255, 255, var(--ode-opacity-100)); - border-radius: var(--ode-border-radius-full); - animation: float 6s var(--ode-easing-ease-in-out) infinite; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + animation: float 6s ease-in-out infinite; } .banner::after { @@ -26,9 +26,9 @@ left: -5%; width: 150px; height: 150px; - background: rgba(255, 255, 255, var(--ode-opacity-80)); - border-radius: var(--ode-border-radius-full); - animation: float 8s var(--ode-easing-ease-in-out) infinite reverse; + background: rgba(255, 255, 255, 0.08); + border-radius: 50%; + animation: float 8s ease-in-out infinite reverse; } @keyframes float { @@ -41,13 +41,13 @@ } .container { - max-width: var(--ode-container-xl); + max-width: 1200px; margin: 0 auto; - padding: var(--ode-spacing-0) var(--ode-spacing-4); + padding: 0 1rem; display: flex; align-items: center; justify-content: center; - gap: var(--ode-spacing-4); + gap: 1rem; position: relative; z-index: 1; } @@ -55,7 +55,7 @@ .birdie { width: 50px; height: 50px; - animation: bounce 2s var(--ode-easing-ease-in-out) infinite; + animation: bounce 2s ease-in-out infinite; } @keyframes bounce { @@ -70,51 +70,51 @@ .content { display: flex; align-items: center; - gap: var(--ode-spacing-3); + gap: 0.75rem; flex-wrap: wrap; justify-content: center; } .badge { - background: rgba(255, 255, 255, var(--ode-opacity-30)); - padding: var(--ode-spacing-1) var(--ode-spacing-2); - border-radius: var(--ode-border-radius-xl); - font-size: var(--ode-font-size-xs); - font-weight: var(--ode-font-weight-bold); + background: rgba(255, 255, 255, 0.3); + padding: 0.25rem 0.5rem; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase; backdrop-filter: blur(10px); - border: var(--ode-border-width-thin) solid rgba(255, 255, 255, var(--ode-opacity-40)); + border: 1px solid rgba(255, 255, 255, 0.4); } .text { - font-size: var(--ode-font-size-base); - font-weight: var(--ode-font-weight-medium); + font-size: 1rem; + font-weight: 500; } .link { - color: var(--ode-color-neutral-white); + color: white; text-decoration: none; - font-weight: var(--ode-font-weight-semibold); - padding: var(--ode-spacing-1) var(--ode-spacing-4); - background: rgba(255, 255, 255, var(--ode-opacity-20)); - border-radius: var(--ode-border-radius-full); - transition: all var(--ode-duration-normal) var(--ode-easing-ease-out); - border: var(--ode-border-width-thin) solid rgba(255, 255, 255, var(--ode-opacity-30)); + font-weight: 600; + padding: 0.25rem 1rem; + background: rgba(255, 255, 255, 0.2); + border-radius: 999px; + transition: all 0.2s ease-out; + border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(10px); } .link:hover { - background: rgba(255, 255, 255, var(--ode-opacity-30)); - transform: translateY(calc(var(--ode-spacing-0_5) * -1)); - box-shadow: var(--ode-shadow-md); - color: var(--ode-color-neutral-white); + background: rgba(255, 255, 255, 0.3); + transform: translateY(-2px); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + color: white; text-decoration: none; } @media screen and (max-width: 768px) { .banner { - padding: var(--ode-spacing-3) var(--ode-spacing-0); + padding: 0.75rem 0; } .birdie { @@ -123,22 +123,23 @@ } .text { - font-size: var(--ode-font-size-sm); + font-size: 0.875rem; } .content { - gap: var(--ode-spacing-2); + gap: 0.5rem; } } @media screen and (max-width: 480px) { .container { flex-direction: column; - gap: var(--ode-spacing-2); + gap: 0.5rem; } .text { - font-size: var(--ode-font-size-sm); + font-size: 0.875rem; text-align: center; } } + diff --git a/src/components/HomepageFeatures/index.tsx b/src/components/HomepageFeatures/index.tsx index 62a02b2..4e9eda1 100644 --- a/src/components/HomepageFeatures/index.tsx +++ b/src/components/HomepageFeatures/index.tsx @@ -66,3 +66,4 @@ export default function HomepageFeatures(): ReactNode { ); } + diff --git a/src/components/HomepageFeatures/styles.module.css b/src/components/HomepageFeatures/styles.module.css index f582a15..48d6c30 100644 --- a/src/components/HomepageFeatures/styles.module.css +++ b/src/components/HomepageFeatures/styles.module.css @@ -9,40 +9,29 @@ width: 100%; } -.featureSvg { - height: 200px; - width: 200px; +.featureImg { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + margin-bottom: 1.5rem; } @media screen and (max-width: 996px) { .features { padding: 1.5rem 0; } - - .featureSvg { - height: 160px; - width: 160px; - } } @media screen and (max-width: 768px) { .features { padding: 1.25rem 0; } - - .featureSvg { - height: 140px; - width: 140px; - } } @media screen and (max-width: 576px) { .features { padding: 1rem 0; } - - .featureSvg { - height: 120px; - width: 120px; - } } + diff --git a/src/components/Newsletter/index.tsx b/src/components/Newsletter/index.tsx index c66b0c3..f16d30b 100644 --- a/src/components/Newsletter/index.tsx +++ b/src/components/Newsletter/index.tsx @@ -82,3 +82,4 @@ export default function Newsletter(): ReactNode { ); } + diff --git a/src/components/Newsletter/styles.module.css b/src/components/Newsletter/styles.module.css index 08e056b..55a964c 100644 --- a/src/components/Newsletter/styles.module.css +++ b/src/components/Newsletter/styles.module.css @@ -67,24 +67,6 @@ padding: 2rem; } -.formHeader { - margin-bottom: 1.5rem; -} - -.formHeader h3 { - color: var(--ifm-color-primary); - font-size: 1.5rem; - font-weight: 700; - margin: 0 0 0.5rem 0; -} - -.formHeader p { - color: var(--ifm-color-emphasis-700); - font-size: 1rem; - margin: 0; - line-height: 1.5; -} - .formFields { display: flex; flex-direction: column; @@ -225,10 +207,6 @@ padding: 1.25rem; } - .formHeader h3 { - font-size: 1.25rem; - } - .giraffeImage { max-width: 250px; } @@ -290,3 +268,4 @@ font-size: 0.9rem; } } + diff --git a/src/css/custom.css b/src/css/custom.css index 693446b..ecbff63 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,944 +1,491 @@ /** - * Global CSS for ODE Documentation Site + * ODE Documentation Site - Custom Styles * - * Import ODE Design System tokens first + * Professional styling using ODE Design System colors + * Primary: #4F7F4E (Green) + * Secondary: #E9B85B (Gold) */ -@import './ode-tokens.css'; -/* Self-hosted Josefin Sans font faces */ +/* Inter Font Face Declarations */ @font-face { - font-family: 'Josefin Sans'; + font-family: 'Inter'; + src: url('/fonts/Inter-Regular.woff2') format('woff2'); + font-weight: 400; font-style: normal; - font-weight: 300; font-display: swap; - src: url('/fonts/JosefinSans-Light.woff2') format('woff2'); } @font-face { - font-family: 'Josefin Sans'; - font-style: normal; + font-family: 'Inter'; + src: url('/fonts/Inter-Italic.woff2') format('woff2'); font-weight: 400; + font-style: italic; font-display: swap; - src: url('/fonts/JosefinSans-Regular.woff2') format('woff2'); } @font-face { - font-family: 'Josefin Sans'; + font-family: 'Inter'; + src: url('/fonts/Inter-Medium.woff2') format('woff2'); + font-weight: 500; font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter-MediumItalic.woff2') format('woff2'); font-weight: 500; + font-style: italic; font-display: swap; - src: url('/fonts/JosefinSans-Medium.woff2') format('woff2'); } @font-face { - font-family: 'Josefin Sans'; + font-family: 'Inter'; + src: url('/fonts/Inter-SemiBold.woff2') format('woff2'); + font-weight: 600; font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter-SemiBoldItalic.woff2') format('woff2'); font-weight: 600; + font-style: italic; font-display: swap; - src: url('/fonts/JosefinSans-SemiBold.woff2') format('woff2'); } @font-face { - font-family: 'Josefin Sans'; + font-family: 'Inter'; + src: url('/fonts/Inter-Bold.woff2') format('woff2'); + font-weight: 700; font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Inter'; + src: url('/fonts/Inter-BoldItalic.woff2') format('woff2'); font-weight: 700; + font-style: italic; font-display: swap; - src: url('/fonts/JosefinSans-SemiBold.woff2') format('woff2'); } :root { - /* Docusaurus theme variables are mapped in ode-tokens.css */ + /* ODE Brand Colors */ + --ode-primary: #4F7F4E; + --ode-primary-light: #6FA46E; + --ode-primary-dark: #3F6A3E; + --ode-secondary: #E9B85B; + --ode-secondary-light: #F0B84D; + --ode-secondary-dark: #D9A230; + + /* Neutral Colors */ + --ode-neutral-50: #FAFAFA; + --ode-neutral-100: #F5F5F5; + --ode-neutral-200: #EEEEEE; + --ode-neutral-300: #E0E0E0; + --ode-neutral-400: #BDBDBD; + --ode-neutral-500: #9E9E9E; + --ode-neutral-600: #757575; + --ode-neutral-700: #616161; + --ode-neutral-800: #424242; + --ode-neutral-900: #212121; + + /* Semantic Colors */ + --ode-success: #34C759; + --ode-error: #F44336; + --ode-warning: #FF9500; + --ode-info: #2196F3; +} + +/* Override Docusaurus theme colors */ +:root { + --ifm-color-primary: var(--ode-primary); + --ifm-color-primary-dark: var(--ode-primary-dark); + --ifm-color-primary-darker: #30552F; + --ifm-color-primary-darkest: #224021; + --ifm-color-primary-light: var(--ode-primary-light); + --ifm-color-primary-lighter: #90BD8F; + --ifm-color-primary-lightest: #B9D5B8; --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.08); - --navbar-height: 72px; - --navbar-font-family: var(--ode-font-family-sans); - --navbar-text-color: var(--ode-color-neutral-800); - --navbar-text-color-light: var(--ode-color-neutral-600); - --navbar-border-top: var(--ode-color-neutral-900); - --navbar-border-bottom: var(--ifm-color-primary); - - /* Set Josefin Sans as the default font for all body text */ - --ifm-font-family-base: var(--ode-font-family-sans); -} - + --ifm-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + --ifm-font-family-monospace: 'Courier New', Courier, monospace; + --ifm-font-size-base: 16px; + --ifm-line-height-base: 1.6; + --ifm-heading-font-weight: 500; + --ifm-heading-line-height: 1.25; + --ifm-h1-font-size: 2.5rem; + --ifm-h2-font-size: 2rem; + --ifm-h3-font-size: 1.5rem; + --ifm-h4-font-size: 1.25rem; + --ifm-h5-font-size: 1.125rem; + --ifm-h6-font-size: 1rem; + --ifm-navbar-height: 4rem; + --ifm-navbar-padding-horizontal: 1.5rem; + --ifm-navbar-background-color: #ffffff; + --ifm-navbar-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --ifm-footer-background-color: var(--ode-neutral-900); + --ifm-footer-color: var(--ode-neutral-200); + --ifm-footer-link-color: var(--ode-neutral-300); + --ifm-footer-link-hover-color: var(--ode-secondary); + --ifm-table-border-width: 1px; + --ifm-table-border-color: var(--ode-neutral-200); + --ifm-table-stripe-background: var(--ode-neutral-50); +} + +/* Dark mode overrides */ [data-theme='dark'] { - /* Dark mode colors are handled in ode-tokens.css */ - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); - --navbar-text-color: var(--ode-color-text-primary); - --navbar-text-color-light: var(--ode-color-text-secondary); - --navbar-border-top: var(--ode-color-border-default); -} - -/* Navigation */ -.navbar { - min-height: var(--navbar-height); - padding: 0; - background-color: var(--ode-color-neutral-white); - border-top: var(--ode-border-width-thin) solid var(--navbar-border-top); - border-bottom: var(--ode-border-width-thin) solid var(--navbar-border-bottom); - box-shadow: var(--ode-shadow-none); + --ifm-background-color: #1a1a1a; + --ifm-background-surface-color: #242424; + --ifm-color-content: var(--ode-neutral-200); + --ifm-color-content-secondary: var(--ode-neutral-400); + --ifm-navbar-background-color: #242424; + --ifm-footer-background-color: #1a1a1a; } -[data-theme='dark'] .navbar { - background-color: var(--ode-color-background-primary); - border-top-color: var(--navbar-border-top); - border-bottom-color: var(--navbar-border-bottom); -} - -.navbar__inner { - max-width: var(--ode-container-xl); - min-height: var(--navbar-height); - margin: 0 auto; - padding: 0 var(--ode-spacing-12) 0 var(--ode-spacing-6); - display: flex; - align-items: center; - justify-content: space-between; -} - -.navbar__brand { - display: flex; - align-items: center; - gap: 0.75rem; - margin-left: -0.5rem; - text-decoration: none; - font-weight: 500; - font-family: var(--navbar-font-family); -} - -.navbar__logo { - height: 56px; - width: auto; -} - -.navbar__title { - font-size: 0; - line-height: 1; - visibility: hidden; - display: flex; - align-items: center; - height: 100%; -} - -.navbar__title::before { - content: "OPEN DATA ENSEMBLE"; - display: inline-block; - font-size: var(--ode-font-size-xl); - font-weight: var(--ode-font-weight-semibold); - color: var(--navbar-text-color); - line-height: var(--ode-line-height-tight); - visibility: visible; - font-family: var(--navbar-font-family); - letter-spacing: 0.02em; -} - -.navbar__items { - display: flex; - align-items: center; - margin-left: 4rem; - gap: 0; -} - -.navbar__item { - font-weight: 400; - position: relative; -} - -.navbar__link { - font-size: var(--ode-font-size-sm); - padding: var(--ode-spacing-4) var(--ode-spacing-5); - color: var(--navbar-text-color); - font-weight: var(--ode-font-weight-regular); - font-family: var(--navbar-font-family); - display: inline-flex; - align-items: center; - transition: all var(--ode-duration-fast) var(--ode-easing-ease-out); - letter-spacing: 0.01em; - border-radius: var(--ode-border-radius-sm); -} - -.navbar__link:hover { - background-color: rgba(0, 0, 0, var(--ode-opacity-30)); -} - -[data-theme='dark'] .navbar__link:hover { - background-color: rgba(255, 255, 255, var(--ode-opacity-10)); -} - -.navbar__link:focus { - outline: var(--ode-border-width-medium) solid var(--ifm-color-primary); - outline-offset: var(--ode-spacing-0_5); +/* Professional table styling */ +table { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; + font-size: 0.9375rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + border-radius: 4px; + overflow: hidden; } -[data-theme='dark'] .navbar { - background-color: #111111; - border-top: 1px solid var(--navbar-border-top); - border-bottom: 1px solid var(--navbar-border-bottom); +table thead { + background-color: var(--ode-primary); + color: white; } -[data-theme='dark'] .navbar__link { - color: var(--navbar-text-color); +table thead th { + padding: 0.875rem 1rem; + text-align: left; + font-weight: 600; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.5px; } -[data-theme='dark'] .navbar__link:hover { - background-color: rgba(226, 232, 240, 0.08); +table tbody tr { + border-bottom: 1px solid var(--ode-neutral-200); + transition: background-color 0.2s ease; } -.navbar__link--active { - border-bottom: var(--ode-border-width-medium) solid var(--ifm-color-primary); - padding-bottom: calc(var(--ode-spacing-4) - var(--ode-border-width-medium)); - font-weight: var(--ode-font-weight-medium); +table tbody tr:hover { + background-color: var(--ode-neutral-50); } -.navbar__item--dropdown .navbar__link::after { - content: "▾"; - font-size: 0.4rem; - margin-left: var(--ode-spacing-1); - opacity: var(--ode-opacity-0); - transition: opacity var(--ode-duration-fast) var(--ode-easing-ease-out); +[data-theme='dark'] table tbody tr:hover { + background-color: var(--ifm-background-surface-color); } -.navbar__item--dropdown:hover .navbar__link::after { - opacity: var(--ode-opacity-50); +table tbody tr:last-child { + border-bottom: none; } -.dropdown { - position: relative; +table tbody td { + padding: 0.875rem 1rem; + vertical-align: top; } -.dropdown__menu { - position: absolute; - top: 100%; - left: 0; - min-width: 200px; - padding: var(--ode-spacing-3) var(--ode-spacing-0) var(--ode-spacing-2) var(--ode-spacing-0); - margin-top: var(--ode-spacing-1); - background: var(--ode-color-neutral-white); - border: var(--ode-border-width-thin) solid rgba(0, 0, 0, var(--ode-opacity-80)); - border-radius: var(--ode-border-radius-md); - box-shadow: var(--ode-shadow-md); - z-index: 1000; - animation: dropdownFadeIn var(--ode-duration-fast) var(--ode-easing-ease-out); +table tbody td code { + background-color: var(--ode-neutral-100); + padding: 0.125rem 0.375rem; + border-radius: 3px; + font-size: 0.875rem; } -[data-theme='dark'] .dropdown__menu { - background: var(--ode-color-background-secondary); - border-color: var(--ode-color-border-default); - box-shadow: var(--ode-shadow-lg); +[data-theme='dark'] table tbody td code { + background-color: var(--ifm-background-surface-color); } -[data-theme='dark'] .dropdown__menu { - background: #1a1a1a; - border: 1px solid rgba(226, 232, 240, 0.15); - box-shadow: 0 12px 25px rgba(0, 0, 0, 0.65); +/* Clean typography */ +h1, h2, h3, h4, h5, h6 { + color: var(--ode-neutral-900); + font-weight: 500; + margin-top: 2rem; + margin-bottom: 1rem; } -.navbar__item--dropdown:hover .dropdown__menu, -.dropdown:hover .dropdown__menu, -.dropdown__menu:hover { - display: block !important; - opacity: 1 !important; - visibility: visible !important; +[data-theme='dark'] h1, +[data-theme='dark'] h2, +[data-theme='dark'] h3, +[data-theme='dark'] h4, +[data-theme='dark'] h5, +[data-theme='dark'] h6 { + color: var(--ode-neutral-100); } -.navbar__item--dropdown .dropdown::before { - content: ''; - position: absolute; - top: 100%; - left: 0; - right: 0; - height: 0.5rem; - background: transparent; - z-index: 999; - pointer-events: none; +h1 { + font-size: 2.5rem; + border-bottom: 2px solid var(--ode-neutral-200); + padding-bottom: 0.5rem; } -.navbar__item--dropdown:hover .dropdown::before { - pointer-events: auto; +h2 { + font-size: 2rem; + margin-top: 2.5rem; } -@keyframes dropdownFadeIn { - from { - opacity: 0; - transform: translateY(-4px); - } - to { - opacity: 1; - transform: translateY(0); - } +h3 { + font-size: 1.5rem; + margin-top: 2rem; } -.dropdown__link { - padding: var(--ode-spacing-2_5) var(--ode-spacing-5); - font-size: var(--ode-font-size-sm); - color: var(--navbar-text-color); - font-weight: var(--ode-font-weight-regular); - transition: all var(--ode-duration-fast) var(--ode-easing-ease-out); +/* Professional code blocks */ +.prism-code { + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } -.dropdown__link:hover { - background-color: rgba(0, 0, 0, var(--ode-opacity-50)); - padding-left: var(--ode-spacing-6); +/* Clean navigation */ +.navbar { + box-shadow: var(--ifm-navbar-shadow); + border-bottom: 1px solid var(--ode-neutral-200); } -.dropdown__link:focus { - outline: var(--ode-border-width-medium) solid var(--ifm-color-primary); - outline-offset: calc(var(--ode-border-width-medium) * -1); - background-color: rgba(0, 0, 0, var(--ode-opacity-30)); +.navbar__brand { + font-weight: 600; + font-size: 1.25rem; } -[data-theme='dark'] .dropdown__link:hover { - background-color: rgba(255, 255, 255, var(--ode-opacity-100)); +.navbar__link { + font-weight: 500; } -[data-theme='dark'] .dropdown__link:focus { - background-color: rgba(255, 255, 255, var(--ode-opacity-100)); +.navbar__link--active { + color: var(--ode-primary); } -.navbar__items--right { - margin-left: auto; +/* Sidebar styling */ +.theme-doc-sidebar-container { + border-right: 1px solid var(--ode-neutral-200); } -.navbar__toggle { - color: var(--navbar-text-color); +.menu__link { + font-weight: 400; + transition: all 0.2s ease; } -.navbar__toggle:hover { - background-color: rgba(0, 0, 0, var(--ode-opacity-50)); - border-radius: var(--ode-border-radius-sm); +.menu__link--active { + color: var(--ode-primary); + font-weight: 500; + background-color: var(--ode-neutral-50); } -[data-theme='dark'] .navbar__toggle { - color: var(--navbar-text-color); +[data-theme='dark'] .menu__link--active { + background-color: var(--ifm-background-surface-color); } -[data-theme='dark'] .navbar__toggle:hover { - background-color: rgba(255, 255, 255, var(--ode-opacity-100)); +/* Button styling */ +.button { + border-radius: 6px; + font-weight: 500; + transition: all 0.2s ease; } -.navbar-sidebar { - background-color: var(--ode-color-neutral-white); - box-shadow: var(--ode-shadow-lg); +.button--primary { + background-color: var(--ode-primary); + border-color: var(--ode-primary); } -[data-theme='dark'] .navbar-sidebar { - background-color: var(--ode-color-background-secondary); +.button--primary:hover { + background-color: var(--ode-primary-dark); + border-color: var(--ode-primary-dark); } -.navbar-sidebar__brand { - padding: var(--ode-spacing-4) var(--ode-spacing-6); - border-bottom: var(--ode-border-width-thin) solid rgba(0, 0, 0, var(--ode-opacity-100)); +.button--secondary { + background-color: transparent; + border-color: var(--ode-primary); + color: var(--ode-primary); } -[data-theme='dark'] .navbar-sidebar__brand { - border-bottom: var(--ode-border-width-thin) solid var(--ode-color-border-light); +.button--secondary:hover { + background-color: var(--ode-primary); + color: white; } -.navbar-sidebar__items { - padding: var(--ode-spacing-4) var(--ode-spacing-0); +/* Alert boxes */ +.alert { + border-radius: 6px; + border-left-width: 4px; + font-size: 0.9375rem; } -.navbar-sidebar__link { - padding: var(--ode-spacing-3) var(--ode-spacing-6); - color: var(--navbar-text-color); - font-size: var(--ode-font-size-sm); - transition: all var(--ode-duration-fast) var(--ode-easing-ease-out); - display: block; +.alert--info { + border-left-color: var(--ode-info); } -.navbar-sidebar__link:hover { - background-color: rgba(0, 0, 0, var(--ode-opacity-50)); +.alert--success { + border-left-color: var(--ode-success); } -[data-theme='dark'] .navbar-sidebar__link:hover { - background-color: rgba(255, 255, 255, var(--ode-opacity-100)); +.alert--warning { + border-left-color: var(--ode-warning); } -.navbar-sidebar__link--active { - background-color: rgba(0, 0, 0, var(--ode-opacity-50)); - border-left: var(--ode-border-width-thick) solid var(--ifm-color-primary); - padding-left: calc(var(--ode-spacing-6) - var(--ode-border-width-thick)); - font-weight: var(--ode-font-weight-medium); +.alert--danger { + border-left-color: var(--ode-error); } -[data-theme='dark'] .navbar-sidebar__link--active { - background-color: rgba(255, 255, 255, var(--ode-opacity-100)); +/* Card styling for feature highlights */ +.card { + border: 1px solid var(--ode-neutral-200); + border-radius: 8px; + padding: 1.5rem; + transition: all 0.2s ease; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); } -.navbar-sidebar__item--dropdown .navbar-sidebar__link { - display: flex; - align-items: center; - justify-content: space-between; +.card:hover { + box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15); + transform: translateY(-2px); } -.navbar-sidebar__item--dropdown .navbar-sidebar__link::after { - content: "▼"; - font-size: 0.6rem; - opacity: 0.6; - transition: transform 0.2s ease; +/* Clean list styling */ +ul, ol { + padding-left: 1.5rem; } -.navbar-sidebar__item--dropdown--show .navbar-sidebar__link::after { - transform: rotate(180deg); +li { + margin-bottom: 0.5rem; + line-height: 1.6; } -.navbar-sidebar__dropdown { - background-color: rgba(0, 0, 0, 0.02); - padding: 0.5rem 0; +/* Link styling */ +a { + color: var(--ode-primary); + text-decoration: none; + transition: color 0.2s ease; } -[data-theme='dark'] .navbar-sidebar__dropdown { - background-color: rgba(255, 255, 255, 0.05); +a:hover { + color: var(--ode-primary-dark); + text-decoration: underline; } -.navbar-sidebar__dropdown .navbar-sidebar__link { - padding-left: 2.5rem; - font-size: 0.9rem; +/* Footer styling */ +.footer { + background-color: var(--ode-neutral-900); + color: var(--ode-neutral-200); } -@media screen and (max-width: 996px) { - .navbar__inner { - padding: 0 1.5rem 0 1rem; - } - - .navbar__brand { - gap: 0.5rem; - margin-right: 0; - } - - .navbar__logo { - height: 44px; - } - - .navbar__title::before { - font-size: 1.1rem; - } - - .navbar__items { - margin-left: 1rem; - } - - .navbar__link { - font-size: 0.85rem; - padding: 0.75rem; - } +.footer__link-item { + color: var(--ode-neutral-300); } -@media screen and (max-width: 768px) { - .navbar { - min-height: 60px; - } - - .navbar__inner { - padding: 0 1rem; - min-height: 60px; - } - - .navbar__brand { - gap: 0.5rem; - margin-left: -0.25rem; - flex: 1 1 auto; - min-width: 0; - max-width: calc(100% - 60px); - } - - .navbar__logo { - height: 40px; - flex-shrink: 0; - } - - .navbar__title::before { - font-size: 1rem; - white-space: nowrap; - overflow: visible; - max-width: none; - } - - .navbar__items { - margin-left: 0; - flex: 1 1 auto; - gap: 0.5rem; - } - - .navbar__items--right { - display: none; - } - - .navbar__item:has(.navbar__link[href*="/docs/ODE"]), - .navbar__link[href*="/docs/ODE"], - .navbar__item a[href="/docs/ODE"] { - display: none !important; - visibility: hidden !important; - } - - .navbar__toggle { - display: flex; - align-items: center; - justify-content: center; - width: 40px; - height: 40px; - padding: 0; - margin-left: auto; - border: none; - background: transparent; - cursor: pointer; - order: 999; - } +.footer__link-item:hover { + color: var(--ode-secondary); } -@media screen and (max-width: 576px) { - .navbar { - min-height: 56px; - } - - .navbar__inner { - padding: 0 0.75rem; - min-height: 56px; - } - - .navbar__brand { - gap: 0.375rem; - margin-left: -0.25rem; - max-width: calc(100% - 50px); +/* Responsive adjustments */ +@media (max-width: 996px) { + h1 { + font-size: 2rem; } - .navbar__logo { - height: 36px; + h2 { + font-size: 1.75rem; } - .navbar__title::before { - font-size: 0.95rem; - letter-spacing: 0.01em; + h3 { + font-size: 1.375rem; } - .navbar__item:has(.navbar__link[href*="/docs/ODE"]), - .navbar__link[href*="/docs/ODE"], - .navbar__item a[href="/docs/ODE"] { - display: none !important; - visibility: hidden !important; + table { + font-size: 0.875rem; } - .navbar__toggle { - width: 36px; - height: 36px; + table thead th, + table tbody td { + padding: 0.625rem 0.75rem; } } -/* Footer */ -.footer { - background-color: var(--ode-color-neutral-900); - border-top: var(--ode-border-width-thin) solid rgba(255, 255, 255, var(--ode-opacity-100)); - padding: var(--ode-spacing-6) var(--ode-spacing-0) var(--ode-spacing-4) var(--ode-spacing-0); -} - -[data-theme='dark'] .footer { - background-color: #0d1117; -} - -.footer__container { - max-width: var(--ode-container-xl); - margin: 0 auto; - padding: var(--ode-spacing-0) var(--ode-spacing-8); -} - -.footer__links { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: var(--ode-spacing-6); - margin-bottom: var(--ode-spacing-6); -} - -.footer__title { - font-size: var(--ode-font-size-sm); - font-weight: var(--ode-font-weight-semibold); - color: var(--ode-color-neutral-white); - margin-bottom: var(--ode-spacing-3); - letter-spacing: 0.02em; +/* Compact cards for index pages */ +.card--compact { + padding: 1rem !important; + height: 100%; } -.footer__item { +.card--compact .card__header { margin-bottom: 0.5rem; } -.footer__link { - color: rgba(255, 255, 255, var(--ode-opacity-70)); - font-size: var(--ode-font-size-sm); - text-decoration: none; - transition: color var(--ode-duration-fast) var(--ode-easing-ease-out); - display: inline-block; -} - -.footer__link:hover { - color: #ffffff; -} - -.footer__social-link { - color: rgba(255, 255, 255, 0.7); - font-size: 0.9rem; - text-decoration: none; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -.footer__social-link:hover { - color: #ffffff; +.card--compact .card__header h4 { + font-size: 1rem; + font-weight: 600; + margin: 0; + line-height: 1.3; } -.footer__social-link svg { - fill: currentColor; - opacity: 0.8; - transition: opacity 0.2s ease; +.card--compact .card__body { + font-size: 0.875rem; + line-height: 1.5; } -.footer__social-link:hover svg { - opacity: 1; +.card--compact .card__body p { + margin-bottom: 0.75rem; + font-size: 0.875rem; + color: var(--ode-neutral-700); } -.footer__copyright { - text-align: center; - padding-top: var(--ode-spacing-4); - border-top: var(--ode-border-width-thin) solid rgba(255, 255, 255, var(--ode-opacity-100)); - color: rgba(255, 255, 255, var(--ode-opacity-60)); - font-size: var(--ode-font-size-sm); - margin-top: var(--ode-spacing-4); +[data-theme='dark'] .card--compact .card__body p { + color: var(--ode-neutral-300); } -[data-theme='dark'] .footer__copyright { - color: rgba(255, 255, 255, 0.5); +.card--compact .button { + font-size: 0.875rem; + padding: 0.5rem 1rem; + margin-top: 0.5rem; } +/* Responsive columns for index pages */ @media screen and (max-width: 996px) { - .footer__container { - padding: 0 1.5rem; - } - - .footer__links { - grid-template-columns: repeat(2, 1fr); - } - - .footer { - padding: 1.5rem 0 0.75rem; + .col--6-tablet { + flex: 0 0 50% !important; + max-width: 50% !important; } } @media screen and (max-width: 768px) { - .footer__container { - padding: 0 1rem; - } - - .footer__links { - gap: 1.25rem; - margin-bottom: 1.25rem; + .col--12-mobile { + flex: 0 0 100% !important; + max-width: 100% !important; } - .footer__title { - font-size: 0.9rem; - margin-bottom: 0.625rem; + .col--6-tablet { + flex: 0 0 100% !important; + max-width: 100% !important; } - .footer__link { - font-size: 0.85rem; + .card--compact { + padding: 0.875rem !important; } - .footer__item { - margin-bottom: 0.375rem; + .card--compact .card__header h4 { + font-size: 0.9375rem; } - .footer__copyright { - font-size: 0.8rem; - padding-top: 0.875rem; - margin-top: 0.875rem; + .card--compact .card__body p { + font-size: 0.8125rem; } } -@media screen and (max-width: 576px) { - .footer__container { - padding: 0 0.75rem; - } - - .footer__links { - grid-template-columns: 1fr; - gap: 1.5rem; - margin-bottom: 1rem; - } - - .footer__title { - font-size: 0.875rem; - margin-bottom: 0.5rem; - } - - .footer__link, - .footer__social-link { - font-size: 0.8rem; - } - - .footer { - padding: 1.25rem 0 0.625rem; - } - - .footer__copyright { - font-size: 0.75rem; - padding-top: 0.75rem; - margin-top: 0.75rem; +/* Print styles */ +@media print { + .navbar, + .footer, + .theme-doc-sidebar-container { + display: none; } - .footer__social-link svg { - width: 16px; - height: 16px; + table { + box-shadow: none; + border: 1px solid var(--ode-neutral-300); } } -/* Table Styling */ -.theme-doc-markdown table { - width: 100%; - border-collapse: separate; - border-spacing: 0; - border: 1px solid rgba(15, 23, 42, 0.08); - border-radius: 12px; - overflow: hidden; - margin: 1.5rem 0; - box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06); -} - -[data-theme='dark'] .theme-doc-markdown table { - border-color: rgba(148, 163, 184, 0.25); - box-shadow: 0 20px 45px rgba(2, 6, 23, 0.65); -} - -.theme-doc-markdown table:not(.code-block table) th { - border-right-width: 0; - border-left-width: 0; - border-top: none; - text-transform: uppercase; - letter-spacing: 0.08em; - font-size: 0.85rem; - color: rgba(71, 85, 105, 0.95); - background-color: rgba(15, 23, 42, 0.02); -} - -[data-theme='dark'] .theme-doc-markdown table:not(.code-block table) th { - color: rgba(226, 232, 240, 0.85); - background-color: rgba(148, 163, 184, 0.08); -} - -.theme-doc-markdown table:not(.code-block table) td { - border: none; - font-size: 0.9rem; - color: rgba(15, 23, 42, 0.9); - padding: 0.85rem 1rem; -} - -[data-theme='dark'] .theme-doc-markdown table:not(.code-block table) td { - color: #f8fafc; -} - -.theme-doc-markdown table:not(.code-block table) tbody tr:nth-child(even) { - background-color: rgba(15, 23, 42, 0.04); -} - -.theme-doc-markdown table:not(.code-block table) tbody tr:nth-child(odd) { - background-color: transparent; -} - -[data-theme='dark'] .theme-doc-markdown table:not(.code-block table) tbody tr:nth-child(even) { - background-color: rgba(148, 163, 184, 0.12); -} - -[data-theme='dark'] .theme-doc-markdown table:not(.code-block table) tbody tr:nth-child(odd) { - background-color: transparent; -} - -/* Admonitions */ -.alert { - border-radius: 12px; - border-left: 6px solid var(--ifm-color-primary); - padding: 1.25rem 1.5rem; - box-shadow: 0 15px 35px rgba(15, 23, 42, 0.12); - background-color: rgba(60, 179, 113, 0.12); -} - -html[data-theme='dark'] .alert { - box-shadow: 0 25px 50px rgba(2, 6, 23, 0.6); - background-color: rgba(60, 179, 113, 0.22); -} - -.alert__heading { - text-transform: uppercase; - letter-spacing: 0.08em; - font-size: 0.85rem; - font-weight: 700; - color: inherit; -} - -.alert--note, -.alert--info { - border-color: #2563eb; - background-color: rgba(37, 99, 235, 0.08); -} - -[data-theme='dark'] .alert--note, -[data-theme='dark'] .alert--info { - background-color: rgba(59, 130, 246, 0.18); -} - -.alert--tip, -.alert--success { - border-color: #14b8a6; - background-color: rgba(20, 184, 166, 0.08); -} - -[data-theme='dark'] .alert--tip, -[data-theme='dark'] .alert--success { - background-color: rgba(45, 212, 191, 0.18); -} - -.alert--warning { - border-color: #fbbf24; - background-color: rgba(251, 191, 36, 0.16); -} - -[data-theme='dark'] .alert--warning { - background-color: rgba(251, 191, 36, 0.22); -} - -.alert--danger { - border-color: #f87171; - background-color: rgba(248, 113, 113, 0.16); -} - -[data-theme='dark'] .alert--danger { - background-color: rgba(248, 113, 113, 0.22); -} - -.theme-doc-markdown blockquote { - margin: 1.5rem 0; - padding: 1.25rem 1.5rem; - border-left: 4px solid var(--ifm-color-primary); - background-color: rgba(15, 23, 42, 0.04); - border-radius: 0 12px 12px 0; - box-shadow: inset 0 1px 0 rgba(15, 23, 42, 0.04); -} - -[data-theme='dark'] .theme-doc-markdown blockquote { - background-color: rgba(148, 163, 184, 0.12); - box-shadow: inset 0 1px 0 rgba(2, 6, 23, 0.3); -} - -/* Card Components */ -.markdown .cards { - margin: var(--ode-spacing-8) var(--ode-spacing-0); -} - -.markdown .card { - transition: all var(--ode-duration-fast) var(--ode-easing-ease-out); -} - -.markdown .card:hover { - transform: translateY(calc(var(--ode-spacing-0_5) * -1)); -} - -.markdown > .cards:first-child { - margin-top: 0; -} - -.markdown > .cards:last-child { - margin-bottom: 0; -} - -/* ============================================ - TYPOGRAPHY - Apply Josefin Sans to all body text - ============================================ */ - -/* Apply Josefin Sans to body and all text elements */ -body, -html { - font-family: var(--ode-font-family-sans); - font-weight: var(--ode-font-weight-body); -} - -/* Apply body text weight to regular text elements */ -p, -li, -td, -th, -span, -div, -label, -input, -textarea, -select { - font-family: var(--ode-font-family-sans); - font-weight: var(--ode-font-weight-body); -} - -/* Links use body weight */ -a { - font-family: var(--ode-font-family-sans); - font-weight: var(--ode-font-weight-link); -} - -/* Main headings (h1, h2, h3) use primary heading weight */ -h1, h2, h3 { - font-family: var(--ode-font-family-sans); - font-weight: var(--ode-font-weight-heading-primary); -} - -/* Subheadings (h4, h5, h6) use secondary heading weight */ -h4, h5, h6 { - font-family: var(--ode-font-family-sans); - font-weight: var(--ode-font-weight-heading-secondary); -} - -/* Buttons use button weight */ -button { - font-family: var(--ode-font-family-sans); - font-weight: var(--ode-font-weight-button); -} - -/* Ensure code elements always use monospace fonts */ -code, -pre, -kbd, -samp, -var, -.code-block, -.prism-code, -.token, -.hljs, -[class*="codeBlock"], -[class*="code-block"] { - font-family: var(--ode-font-family-mono) !important; -} - -/* Docusaurus specific code elements */ -.theme-doc-markdown code, -.theme-doc-markdown pre code, -.margin-top--md code, -.margin-bottom--md code { - font-family: var(--ode-font-family-mono) !important; -} - -/* Ensure Docusaurus markdown content uses token-based font weights */ -.theme-doc-markdown p, -.theme-doc-markdown li, -.theme-doc-markdown td, -.theme-doc-markdown span, -.theme-doc-markdown div, -.theme-doc-markdown a:not(.hash-link) { - font-weight: var(--ode-font-weight-body); -} - -/* Main headings in markdown */ -.theme-doc-markdown h1, -.theme-doc-markdown h2, -.theme-doc-markdown h3 { - font-weight: var(--ode-font-weight-heading-primary); -} - -/* Subheadings in markdown */ -.theme-doc-markdown h4, -.theme-doc-markdown h5, -.theme-doc-markdown h6 { - font-weight: var(--ode-font-weight-heading-secondary); -} diff --git a/src/pages/index.module.css b/src/pages/index.module.css index 236b74f..9154e0c 100644 --- a/src/pages/index.module.css +++ b/src/pages/index.module.css @@ -3,38 +3,38 @@ */ .heroBanner { - padding: var(--ode-spacing-24) var(--ode-spacing-0) var(--ode-spacing-16) var(--ode-spacing-0); + padding: 6rem 0 4rem 0; text-align: center; position: relative; overflow: hidden; - background: linear-gradient(135deg, var(--ode-color-neutral-100) 0%, var(--ode-color-neutral-200) 100%); + background: linear-gradient(135deg, var(--ode-neutral-100) 0%, var(--ode-neutral-200) 100%); } [data-theme='dark'] .heroBanner { - background: linear-gradient(135deg, var(--ode-color-background-primary) 0%, var(--ode-color-background-secondary) 100%); + background: linear-gradient(135deg, #1a1a1a 0%, #242424 100%); } .heroTitle { - font-size: var(--ode-font-size-4xl); - font-weight: var(--ode-font-weight-bold); - color: var(--ode-color-neutral-900); - margin-bottom: var(--ode-spacing-4); - line-height: var(--ode-line-height-tight); + font-size: 3rem; + font-weight: 700; + color: var(--ode-neutral-900); + margin-bottom: 1rem; + line-height: 1.2; } [data-theme='dark'] .heroTitle { - color: var(--ode-color-text-primary); + color: var(--ode-neutral-100); } .featureImg { max-width: 100%; height: auto; - border-radius: var(--ode-border-radius-md); - box-shadow: var(--ode-shadow-md); + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } [data-theme='dark'] .featureImg { - box-shadow: var(--ode-shadow-lg); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); } .mainSection, @@ -44,62 +44,62 @@ .opensourceSection, .getInvolvedSection, .newsletterSection { - padding: var(--ode-spacing-20) var(--ode-spacing-0); + padding: 5rem 0; } .mainSection, .featuresSection, .opensourceSection, .newsletterSection { - background: var(--ode-color-neutral-white); + background: var(--ode-neutral-50); } [data-theme='dark'] .mainSection, [data-theme='dark'] .featuresSection, [data-theme='dark'] .opensourceSection, [data-theme='dark'] .newsletterSection { - background: var(--ode-color-background-secondary); + background: #242424; } .showcaseSection, .communitySection, .getInvolvedSection { - background: var(--ode-color-neutral-50); + background: white; } [data-theme='dark'] .showcaseSection, [data-theme='dark'] .communitySection, [data-theme='dark'] .getInvolvedSection { - background: var(--ode-color-background-primary); + background: #1a1a1a; } .sectionTitle { - font-size: var(--ode-font-size-4xl); - font-weight: var(--ode-font-weight-bold); - color: var(--ode-color-neutral-900); - margin-bottom: var(--ode-spacing-6); + font-size: 2.5rem; + font-weight: 700; + color: var(--ode-neutral-900); + margin-bottom: 1.5rem; text-align: center; } [data-theme='dark'] .sectionTitle { - color: var(--ode-color-text-primary); + color: var(--ode-neutral-100); } .sectionContent { - max-width: var(--ode-container-lg); + max-width: 800px; margin: 0 auto; text-align: center; } .descriptionText { - font-size: var(--ode-font-size-lg); - line-height: var(--ode-line-height-relaxed); - color: var(--ode-color-neutral-700); - margin-bottom: var(--ode-spacing-6); + font-size: 1.125rem; + line-height: 1.75; + color: var(--ode-neutral-700); + margin-bottom: 1.5rem; } [data-theme='dark'] .descriptionText { - color: var(--ode-color-text-secondary); + color: var(--ode-neutral-300); } .descriptionText a { @@ -142,26 +142,26 @@ } .involvedCard { - background: var(--ode-color-neutral-white); - padding: var(--ode-spacing-8); - border-radius: var(--ode-border-radius-md); - box-shadow: var(--ode-shadow-sm); + background: white; + padding: 2rem; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } [data-theme='dark'] .involvedCard { - background: var(--ode-color-background-secondary); - box-shadow: var(--ode-shadow-md); + background: #242424; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); } .cardTitle { - font-size: var(--ode-font-size-2xl); - font-weight: var(--ode-font-weight-semibold); - color: var(--ode-color-neutral-900); - margin-bottom: var(--ode-spacing-4); + font-size: 1.5rem; + font-weight: 600; + color: var(--ode-neutral-900); + margin-bottom: 1rem; } [data-theme='dark'] .cardTitle { - color: var(--ode-color-text-primary); + color: var(--ode-neutral-100); } .involvedList { @@ -171,15 +171,15 @@ } .involvedList li { - padding: var(--ode-spacing-3) var(--ode-spacing-0); - color: var(--ode-color-neutral-700); - line-height: var(--ode-line-height-relaxed); - border-bottom: var(--ode-border-width-thin) solid var(--ode-color-neutral-200); + padding: 0.75rem 0; + color: var(--ode-neutral-700); + line-height: 1.75; + border-bottom: 1px solid var(--ode-neutral-200); } [data-theme='dark'] .involvedList li { - color: var(--ode-color-text-secondary); - border-bottom: var(--ode-border-width-thin) solid var(--ode-color-border-default); + color: var(--ode-neutral-300); + border-bottom: 1px solid #333; } .involvedList li:last-child { @@ -189,8 +189,8 @@ .involvedList li::before { content: "• "; color: var(--ifm-color-primary); - font-weight: var(--ode-font-weight-bold); - margin-right: var(--ode-spacing-2); + font-weight: 700; + margin-right: 0.5rem; } .communityLinks { @@ -351,3 +351,4 @@ font-size: 0.875rem; } } + diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a6d9ed3..b404ae9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -18,7 +18,7 @@ function HomepageHeader() { {siteConfig.tagline} - + Get Started @@ -66,7 +66,7 @@ export default function Home(): ReactNode {

Explore Documentation @@ -122,7 +122,7 @@ export default function Home(): ReactNode { > Join the Community - + View on GitHub @@ -145,11 +145,11 @@ export default function Home(): ReactNode {

Want to join us or build a new integration? Check out our{" "} - GitHub or join us on the{" "} + GitHub or join us on the{" "} Community Forum!

- + Technical Overview
@@ -170,10 +170,10 @@ export default function Home(): ReactNode {
  • - Help us improve the ODE documentation + Help us improve the ODE documentation
  • - Share a new idea or question on GitHub + Share a new idea or question on GitHub
  • Contribute to open source components
@@ -202,3 +202,4 @@ export default function Home(): ReactNode { ); } + diff --git a/src/theme/MDXComponents.tsx b/src/theme/MDXComponents.tsx index efede48..9417105 100644 --- a/src/theme/MDXComponents.tsx +++ b/src/theme/MDXComponents.tsx @@ -1,12 +1,19 @@ -import type { MDXComponents } from 'mdx/types'; -import Cards from '../components/Cards'; -import Card from '../components/Card'; +import React from 'react'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import Card from '@site/src/components/Card'; +import Cards from '@site/src/components/Cards'; +import type {MDXComponents} from 'mdx/types'; export default function useMDXComponents(components: MDXComponents): MDXComponents { return { - Cards, + // Make Tabs and TabItem available in MDX + Tabs, + TabItem, + // Make Card and Cards available in MDX (for both current and versioned docs) Card, + Cards, + // Spread other components ...components, }; } - diff --git a/static/fonts/Inter-Bold.woff2 b/static/fonts/Inter-Bold.woff2 new file mode 100644 index 0000000..b9e3cb3 Binary files /dev/null and b/static/fonts/Inter-Bold.woff2 differ diff --git a/static/fonts/Inter-BoldItalic.woff2 b/static/fonts/Inter-BoldItalic.woff2 new file mode 100644 index 0000000..31cd052 Binary files /dev/null and b/static/fonts/Inter-BoldItalic.woff2 differ diff --git a/static/fonts/Inter-Italic.woff2 b/static/fonts/Inter-Italic.woff2 new file mode 100644 index 0000000..9a1ad21 Binary files /dev/null and b/static/fonts/Inter-Italic.woff2 differ diff --git a/static/fonts/Inter-Medium.woff2 b/static/fonts/Inter-Medium.woff2 new file mode 100644 index 0000000..fdfdcc6 Binary files /dev/null and b/static/fonts/Inter-Medium.woff2 differ diff --git a/static/fonts/Inter-MediumItalic.woff2 b/static/fonts/Inter-MediumItalic.woff2 new file mode 100644 index 0000000..0dc5a30 Binary files /dev/null and b/static/fonts/Inter-MediumItalic.woff2 differ diff --git a/static/fonts/Inter-Regular.woff2 b/static/fonts/Inter-Regular.woff2 new file mode 100644 index 0000000..2bcd222 Binary files /dev/null and b/static/fonts/Inter-Regular.woff2 differ diff --git a/static/fonts/Inter-SemiBold.woff2 b/static/fonts/Inter-SemiBold.woff2 new file mode 100644 index 0000000..fbae113 Binary files /dev/null and b/static/fonts/Inter-SemiBold.woff2 differ diff --git a/static/fonts/Inter-SemiBoldItalic.woff2 b/static/fonts/Inter-SemiBoldItalic.woff2 new file mode 100644 index 0000000..d67d01c Binary files /dev/null and b/static/fonts/Inter-SemiBoldItalic.woff2 differ diff --git a/static/img/favicon.ico b/static/img/favicon.ico index 7a09c31..48cdce8 100644 Binary files a/static/img/favicon.ico and b/static/img/favicon.ico differ diff --git a/static/img/logo.svg b/static/img/logo.svg new file mode 100644 index 0000000..0963a87 --- /dev/null +++ b/static/img/logo.svg @@ -0,0 +1,5 @@ + + + ODE + + diff --git a/tsconfig.json b/tsconfig.json index 920d7a6..313d8d2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,19 @@ { - // This file is not used in compilation. It is here just for a nice editor experience. - "extends": "@docusaurus/tsconfig", "compilerOptions": { - "baseUrl": "." + "allowJs": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "react", + "lib": ["DOM", "ES2020"], + "module": "ESNext", + "moduleResolution": "node", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2020" }, - "exclude": [".docusaurus", "build"] + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } + diff --git a/versioned_docs/version-1.0/.gitkeep b/versioned_docs/version-1.0/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/For-developers/android/_category_.json b/versioned_docs/version-1.0/For-developers/android/_category_.json similarity index 100% rename from docs/For-developers/android/_category_.json rename to versioned_docs/version-1.0/For-developers/android/_category_.json diff --git a/docs/For-developers/android/adb-setup.md b/versioned_docs/version-1.0/For-developers/android/adb-setup.md similarity index 100% rename from docs/For-developers/android/adb-setup.md rename to versioned_docs/version-1.0/For-developers/android/adb-setup.md diff --git a/docs/For-developers/docusaurus/_category_.json b/versioned_docs/version-1.0/For-developers/docusaurus/_category_.json similarity index 100% rename from docs/For-developers/docusaurus/_category_.json rename to versioned_docs/version-1.0/For-developers/docusaurus/_category_.json diff --git a/docs/For-developers/docusaurus/intro.md b/versioned_docs/version-1.0/For-developers/docusaurus/intro.md similarity index 100% rename from docs/For-developers/docusaurus/intro.md rename to versioned_docs/version-1.0/For-developers/docusaurus/intro.md diff --git a/docs/For-developers/docusaurus/tutorial-extras/_category_.json b/versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/_category_.json similarity index 100% rename from docs/For-developers/docusaurus/tutorial-extras/_category_.json rename to versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/_category_.json diff --git a/docs/For-developers/docusaurus/tutorial-extras/img/docsVersionDropdown.png b/versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/img/docsVersionDropdown.png similarity index 100% rename from docs/For-developers/docusaurus/tutorial-extras/img/docsVersionDropdown.png rename to versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/img/docsVersionDropdown.png diff --git a/docs/For-developers/docusaurus/tutorial-extras/img/localeDropdown.png b/versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/img/localeDropdown.png similarity index 100% rename from docs/For-developers/docusaurus/tutorial-extras/img/localeDropdown.png rename to versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/img/localeDropdown.png diff --git a/docs/For-developers/docusaurus/tutorial-extras/manage-docs-versions.md b/versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/manage-docs-versions.md similarity index 100% rename from docs/For-developers/docusaurus/tutorial-extras/manage-docs-versions.md rename to versioned_docs/version-1.0/For-developers/docusaurus/tutorial-extras/manage-docs-versions.md diff --git a/docs/ODE.md b/versioned_docs/version-1.0/ODE.md similarity index 100% rename from docs/ODE.md rename to versioned_docs/version-1.0/ODE.md diff --git a/versioned_docs/version-1.0/community/.gitkeep b/versioned_docs/version-1.0/community/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/community/_category_.json b/versioned_docs/version-1.0/community/_category_.json new file mode 100644 index 0000000..d41a22d --- /dev/null +++ b/versioned_docs/version-1.0/community/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Community", + "position": 8 +} + diff --git a/versioned_docs/version-1.0/community/contribute/about.md b/versioned_docs/version-1.0/community/contribute/about.md new file mode 100644 index 0000000..2138d54 --- /dev/null +++ b/versioned_docs/version-1.0/community/contribute/about.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 1 +--- + +# About Contributions + +How to contribute to ODE. + +## Overview + +[Description placeholder] + +## Ways to Contribute + +[Description placeholder] + +## Contribution Process + +[Description placeholder] + +## Related Content + +- [First Time Contributors](/docs/community/contribute/first-time) +- [Code of Conduct](/docs/community/contribute/code-of-conduct) + diff --git a/versioned_docs/version-1.0/community/contribute/code-of-conduct.md b/versioned_docs/version-1.0/community/contribute/code-of-conduct.md new file mode 100644 index 0000000..c0a1d0a --- /dev/null +++ b/versioned_docs/version-1.0/community/contribute/code-of-conduct.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 3 +--- + +# Code of Conduct + +Code of conduct for the ODE community. + +## Overview + +[Description placeholder] + +## Our Standards + +[Description placeholder] + +## Enforcement + +[Description placeholder] + +## Related Content + +- [About Contributions](/docs/community/contribute/about) +- [First Time Contributors](/docs/community/contribute/first-time) + diff --git a/versioned_docs/version-1.0/community/contribute/first-time.md b/versioned_docs/version-1.0/community/contribute/first-time.md new file mode 100644 index 0000000..1557c22 --- /dev/null +++ b/versioned_docs/version-1.0/community/contribute/first-time.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 2 +--- + +# First Time Contributors + +Guide for first-time contributors to ODE. + +## Overview + +[Description placeholder] + +## Getting Started + +[Description placeholder] + +## Next Steps + +[Description placeholder] + +## Related Content + +- [About Contributions](/docs/community/contribute/about) +- [Code of Conduct](/docs/community/contribute/code-of-conduct) + diff --git a/versioned_docs/version-1.0/community/examples.md b/versioned_docs/version-1.0/community/examples.md new file mode 100644 index 0000000..46819f4 --- /dev/null +++ b/versioned_docs/version-1.0/community/examples.md @@ -0,0 +1,344 @@ +--- +sidebar_position: 2 +--- + +# Examples + +Example ODE applications, forms, and configurations to help you get started. + +## Form Examples + +### Basic Survey Form + +A simple form collecting name and age: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Full Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] +} +``` + +### Health Survey Form + +Example health survey form collecting patient information: + +```json +{ + "type": "object", + "properties": { + "patientId": { + "type": "string", + "title": "Patient ID" + }, + "visitDate": { + "type": "string", + "format": "date", + "title": "Visit Date" + }, + "symptoms": { + "type": "array", + "title": "Symptoms", + "items": { + "type": "string", + "enum": ["fever", "cough", "headache", "fatigue"] + } + }, + "temperature": { + "type": "number", + "title": "Temperature (°C)", + "minimum": 35, + "maximum": 42 + }, + "notes": { + "type": "string", + "title": "Clinical Notes" + } + }, + "required": ["patientId", "visitDate"] +} +``` + +### Research Data Collection Form + +Example research form for field data collection: + +```json +{ + "type": "object", + "properties": { + "siteId": { + "type": "string", + "title": "Site ID" + }, + "location": { + "type": "string", + "format": "gps", + "title": "Site Location" + }, + "sitePhoto": { + "type": "object", + "format": "photo", + "title": "Site Photo" + }, + "observations": { + "type": "string", + "title": "Field Observations" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "title": "Observation Time" + } + }, + "required": ["siteId", "location"] +} +``` + +## Custom Application Examples + +### Basic Custom App + +Simple custom application that lists available forms: + +```html + + + + Form Selector + + + + +

Available Forms

+
    + + + + +``` + +### Advanced Custom App + +Advanced custom applications can include complex workflows, custom navigation, and integration with external systems. Examples of advanced patterns will be added as they become available. For now, see the [Custom Applications guide](/guides/custom-applications) for detailed implementation guidance. + +## Configuration Examples + +### Server Configuration + +Example server configuration for development: + +```bash +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=debug +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +``` + +### Docker Compose Configuration + +Example Docker Compose configuration for production: + +```yaml +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_PASSWORD: ${DB_ROOT_PASSWORD} + volumes: + - postgres-data:/var/lib/postgresql/data + + synkronus: + image: ghcr.io/opendataensemble/synkronus:latest + environment: + DB_CONNECTION: postgres://synkronus:${DB_PASSWORD}@postgres:5432/synkronus?sslmode=disable + JWT_SECRET: ${JWT_SECRET} + LOG_LEVEL: info + depends_on: + - postgres + volumes: + - app-bundles:/app/data/app-bundles + + nginx: + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - synkronus + +volumes: + postgres-data: + app-bundles: +``` + +## Integration Examples + +### API Integration + +Example API integration using different methods: + + + + +```bash +# Authenticate +TOKEN=$(curl -X POST http://your-server:8080/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"user","password":"pass"}' \ + | jq -r '.token') + +# Pull data +curl -X POST http://your-server:8080/sync/pull \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"client_id":"my-client","since_change_id":0}' + +# Push data +curl -X POST http://your-server:8080/sync/push \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "transmission_id": "550e8400-e29b-41d4-a716-446655440000", + "client_id": "my-client", + "records": [...] + }' +``` + + + + +```bash +# Login (stores token automatically) +synk login --username user + +# Pull data +synk sync pull data.json --client-id my-client + +# Push data +synk sync push data.json +``` + + + + +```python +import requests + +# Authenticate +response = requests.post( + 'http://your-server:8080/auth/login', + json={'username': 'user', 'password': 'pass'} +) +token = response.json()['token'] + +# Pull data +response = requests.post( + 'http://your-server:8080/sync/pull', + headers={'Authorization': f'Bearer {token}'}, + json={'client_id': 'my-client', 'since_change_id': 0} +) +data = response.json() +``` + + + + +### Data Export Examples + +Export observations using different methods: + + + + +```bash +# Export all observations +synk data export observations.zip + +# Export to specific directory +synk data export ./backups/observations_$(date +%Y%m%d).zip + +# Export to JSON +synk data export observations.json --format json +``` + + + + +```bash +# Export to Parquet ZIP +curl -X GET http://your-server:8080/api/dataexport/parquet \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -o observations.zip +``` + + + + +1. Navigate to the Portal +2. Go to "Data Export" +3. Select format (Parquet, JSON, CSV) +4. Click "Export" to download + + + + +The exported ZIP contains Parquet files organized by schema type, suitable for analysis in tools like Python pandas, R, or data analysis platforms. + +## Community Projects + +Projects built by the ODE community will be showcased here as they become available. + +## Contributing Examples + +If you have examples you'd like to share, please: + +1. Create a pull request with your example +2. Include clear documentation +3. Follow the existing example format +4. Ensure examples are tested and working + +## Related Resources + +- [Form Design Guide](/guides/form-design) +- [Custom Applications Guide](/guides/custom-applications) +- [API Reference](/reference/api) +- [Getting Help](/community/getting-help) diff --git a/versioned_docs/version-1.0/community/getting-help.md b/versioned_docs/version-1.0/community/getting-help.md new file mode 100644 index 0000000..20f08b9 --- /dev/null +++ b/versioned_docs/version-1.0/community/getting-help.md @@ -0,0 +1,74 @@ +--- +sidebar_position: 1 +--- + +# Getting Help + +Resources and channels for getting help with ODE. + +## Support Channels + +### Documentation + +The first place to look for help is this documentation site. It covers: + +- Installation and setup guides +- Usage instructions +- API reference +- Troubleshooting guides +- FAQ section + +### GitHub + +- **Issues**: Report bugs, request features, or ask questions on [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- **Discussions**: Join discussions on [GitHub Discussions](https://github.com/OpenDataEnsemble/ode/discussions) +- **Repository**: Browse the source code at [GitHub Repository](https://github.com/OpenDataEnsemble/ode) + +### Email + +For general inquiries or to get involved: + +- **Email**: [hello@opendataensemble.org](mailto:hello@opendataensemble.org) + +## Before Asking for Help + +To get the most effective help, please: + +1. **Search existing resources**: Check the documentation, FAQ, and existing GitHub issues +2. **Provide context**: Include relevant information about your setup, error messages, and what you've tried +3. **Be specific**: Describe the problem clearly and include steps to reproduce if applicable + +## Common Issues + +Many common issues are covered in the [Troubleshooting guide](/using/troubleshooting). Check there first for: + +- Connection issues +- Synchronization problems +- Form-related errors +- Data management issues +- Performance problems + +## Reporting Issues + +When reporting issues on GitHub, please include: + +- **Description**: Clear description of the problem +- **Steps to reproduce**: If applicable, steps to reproduce the issue +- **Expected behavior**: What you expected to happen +- **Actual behavior**: What actually happened +- **Environment**: Operating system, ODE version, component versions +- **Error messages**: Any error messages or logs +- **Screenshots**: If applicable, screenshots showing the issue + +See [Reporting Issues on GitHub](https://github.com/OpenDataEnsemble/ode/issues/new) to create a new issue. + +## Contributing + +If you'd like to contribute to ODE, see the [Contributing guide](/development/contributing) for information on how to get started. + +## Related Resources + +- [FAQ](/getting-started/faq) +- [Troubleshooting Guide](/using/troubleshooting) +- [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- [Examples](/community/examples) diff --git a/versioned_docs/version-1.0/community/index.md b/versioned_docs/version-1.0/community/index.md new file mode 100644 index 0000000..a436d17 --- /dev/null +++ b/versioned_docs/version-1.0/community/index.md @@ -0,0 +1,34 @@ +--- +sidebar_position: 1 +--- + +# Community + +Get involved with the ODE community. + +## Overview + +Join the ODE community to contribute, get help, and stay updated. + + + + + + + diff --git a/versioned_docs/version-1.0/community/resources/examples.md b/versioned_docs/version-1.0/community/resources/examples.md new file mode 100644 index 0000000..9b849f8 --- /dev/null +++ b/versioned_docs/version-1.0/community/resources/examples.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 1 +--- + +# Examples + +Example ODE applications and configurations. + +## Overview + +[Description placeholder] + +## Example Forms + +[Description placeholder] + +## Example Applications + +[Description placeholder] + +## Related Content + +- [Community Projects](/docs/community/resources/projects) +- [Tutorials](/docs/1.0/tutorials/getting-started/first-form) + diff --git a/versioned_docs/version-1.0/community/resources/projects.md b/versioned_docs/version-1.0/community/resources/projects.md new file mode 100644 index 0000000..e874944 --- /dev/null +++ b/versioned_docs/version-1.0/community/resources/projects.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 2 +--- + +# Community Projects + +Projects built by the ODE community. + +## Overview + +[Description placeholder] + +## Featured Projects + +[Description placeholder] + +## Submit Your Project + +[Description placeholder] + +## Related Content + +- [Examples](/docs/community/resources/examples) +- [Contribute](/docs/community/contribute/about) + diff --git a/versioned_docs/version-1.0/community/support/getting-help.md b/versioned_docs/version-1.0/community/support/getting-help.md new file mode 100644 index 0000000..a0b7da7 --- /dev/null +++ b/versioned_docs/version-1.0/community/support/getting-help.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 1 +--- + +# Getting Help + +Where to get help with ODE. + +## Overview + +[Description placeholder] + +## Support Channels + +[Description placeholder] + +## Before Asking + +[Description placeholder] + +## Related Content + +- [Reporting Issues](/docs/community/support/reporting-issues) +- [FAQ](/docs/1.0/quick-start/faq) + diff --git a/versioned_docs/version-1.0/community/support/reporting-issues.md b/versioned_docs/version-1.0/community/support/reporting-issues.md new file mode 100644 index 0000000..f6e32fb --- /dev/null +++ b/versioned_docs/version-1.0/community/support/reporting-issues.md @@ -0,0 +1,29 @@ +--- +sidebar_position: 2 +--- + +# Reporting Issues + +How to report bugs and issues. + +## Overview + +[Description placeholder] + +## Issue Reporting Process + +[Description placeholder] + +## Bug Reports + +[Description placeholder] + +## Feature Requests + +[Description placeholder] + +## Related Content + +- [Getting Help](/docs/community/support/getting-help) +- [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) + diff --git a/docs/components/_category_.json b/versioned_docs/version-1.0/components/_category_.json similarity index 100% rename from docs/components/_category_.json rename to versioned_docs/version-1.0/components/_category_.json diff --git a/docs/components/formplayer/integration.md b/versioned_docs/version-1.0/components/formplayer/integration.md similarity index 100% rename from docs/components/formplayer/integration.md rename to versioned_docs/version-1.0/components/formplayer/integration.md diff --git a/docs/components/formplayer/overview.md b/versioned_docs/version-1.0/components/formplayer/overview.md similarity index 100% rename from docs/components/formplayer/overview.md rename to versioned_docs/version-1.0/components/formplayer/overview.md diff --git a/docs/components/formulus/configuration.md b/versioned_docs/version-1.0/components/formulus/configuration.md similarity index 100% rename from docs/components/formulus/configuration.md rename to versioned_docs/version-1.0/components/formulus/configuration.md diff --git a/docs/components/formulus/features.md b/versioned_docs/version-1.0/components/formulus/features.md similarity index 100% rename from docs/components/formulus/features.md rename to versioned_docs/version-1.0/components/formulus/features.md diff --git a/docs/components/formulus/installation.md b/versioned_docs/version-1.0/components/formulus/installation.md similarity index 100% rename from docs/components/formulus/installation.md rename to versioned_docs/version-1.0/components/formulus/installation.md diff --git a/docs/components/formulus/integration.md b/versioned_docs/version-1.0/components/formulus/integration.md similarity index 100% rename from docs/components/formulus/integration.md rename to versioned_docs/version-1.0/components/formulus/integration.md diff --git a/docs/components/formulus/overview.md b/versioned_docs/version-1.0/components/formulus/overview.md similarity index 100% rename from docs/components/formulus/overview.md rename to versioned_docs/version-1.0/components/formulus/overview.md diff --git a/docs/components/formulus/troubleshooting.md b/versioned_docs/version-1.0/components/formulus/troubleshooting.md similarity index 100% rename from docs/components/formulus/troubleshooting.md rename to versioned_docs/version-1.0/components/formulus/troubleshooting.md diff --git a/docs/components/synkronus-cli/commands-reference.md b/versioned_docs/version-1.0/components/synkronus-cli/commands-reference.md similarity index 100% rename from docs/components/synkronus-cli/commands-reference.md rename to versioned_docs/version-1.0/components/synkronus-cli/commands-reference.md diff --git a/docs/components/synkronus-cli/installation.md b/versioned_docs/version-1.0/components/synkronus-cli/installation.md similarity index 100% rename from docs/components/synkronus-cli/installation.md rename to versioned_docs/version-1.0/components/synkronus-cli/installation.md diff --git a/docs/components/synkronus-cli/overview.md b/versioned_docs/version-1.0/components/synkronus-cli/overview.md similarity index 100% rename from docs/components/synkronus-cli/overview.md rename to versioned_docs/version-1.0/components/synkronus-cli/overview.md diff --git a/docs/components/synkronus/api-reference.md b/versioned_docs/version-1.0/components/synkronus/api-reference.md similarity index 100% rename from docs/components/synkronus/api-reference.md rename to versioned_docs/version-1.0/components/synkronus/api-reference.md diff --git a/docs/components/synkronus/configuration.md b/versioned_docs/version-1.0/components/synkronus/configuration.md similarity index 100% rename from docs/components/synkronus/configuration.md rename to versioned_docs/version-1.0/components/synkronus/configuration.md diff --git a/docs/components/synkronus/installation.md b/versioned_docs/version-1.0/components/synkronus/installation.md similarity index 100% rename from docs/components/synkronus/installation.md rename to versioned_docs/version-1.0/components/synkronus/installation.md diff --git a/docs/components/synkronus/overview.md b/versioned_docs/version-1.0/components/synkronus/overview.md similarity index 100% rename from docs/components/synkronus/overview.md rename to versioned_docs/version-1.0/components/synkronus/overview.md diff --git a/versioned_docs/version-1.0/development/.gitkeep b/versioned_docs/version-1.0/development/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/development/architecture.md b/versioned_docs/version-1.0/development/architecture.md new file mode 100644 index 0000000..21b6e43 --- /dev/null +++ b/versioned_docs/version-1.0/development/architecture.md @@ -0,0 +1,263 @@ +--- +sidebar_position: 2 +--- + +# Architecture + +Complete architecture documentation for ODE, including system overview, components, data flow, and technical details. + +## System Overview + +ODE follows a client-server architecture designed for offline-first data collection: + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formulus │◄───────►│ Synkronus │◄───────►│ Formulus │ +│ (Mobile) │ Sync │ (Server) │ Sync │ (Mobile) │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formplayer │ │ Database │ │ Formplayer │ +│ (WebView) │ │ (PostgreSQL) │ │ (WebView) │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +## Components + +### Formulus + +React Native mobile application providing: + +- **Local Storage**: WatermelonDB for offline data storage +- **Sync Module**: Handles synchronization with server +- **WebView Hosts**: Runs Formplayer and custom applications +- **Native Features**: Camera, GPS, file system access + +**Technology Stack:** +- React Native +- WatermelonDB +- TypeScript + +### Synkronus + +Go backend server providing: + +- **REST API**: Comprehensive API for all operations +- **Synchronization**: Bidirectional sync protocol +- **Storage**: PostgreSQL database +- **Authentication**: JWT-based authentication +- **App Bundle Management**: Versioning and distribution + +**Technology Stack:** +- Go 1.22+ +- PostgreSQL +- Chi router +- JWT authentication + +### Formplayer + +React web application providing: + +- **Form Rendering**: JSON Forms-based form rendering +- **Question Types**: Various input types and renderers +- **Validation**: Client-side and schema validation +- **Integration**: JavaScript interface for custom apps + +**Technology Stack:** +- React +- JSON Forms +- Material-UI +- TypeScript + +### Synkronus CLI + +Go command-line utility providing: + +- **Server Management**: Administrative operations +- **Data Operations**: Sync, export, import +- **App Bundle Management**: Upload, download, version management + +**Technology Stack:** +- Go 1.22+ +- Cobra CLI framework + +## Data Flow + +### Observation Creation + +1. User fills out form in Formplayer (WebView) +2. Formplayer validates and submits to Formulus +3. Formulus creates observation in local database (WatermelonDB) +4. Observation is marked for synchronization + +### Synchronization + +1. **Pull Phase**: Formulus requests changes from server + - Server returns records with `change_id > client_last_seen` + - Client applies changes to local database + +2. **Push Phase**: Formulus sends local changes to server + - Client sends records with transmission ID for idempotency + - Server validates and stores records + - Server returns success/failure for each record + +3. **Attachment Sync**: Separate phase for binary files + - Upload: Client uploads attachments referenced in observations + - Download: Client downloads attachments referenced in new observations + +### Conflict Resolution + +Conflicts are detected using hash comparison: + +- Each record has a hash computed from data, schemaType, and schemaVersion +- If server hash ≠ client's last seen hash, conflict detected +- Server accepts overwrite with warning +- Previous version stored in conflicts table + +## Sync Protocol + +### Change Detection + +Uses cursor-based approach with `change_id`: + +- Each record has a strictly increasing `change_id` (server-assigned) +- Client stores last seen `change_id` per schemaType +- Pull returns all records where `change_id > last_seen` + +**Advantages:** +- No dependence on system clocks +- No ambiguity about ordering +- Enables clean pagination and deduplication + +### Record Model + +Each form submission is an entity: + +- `id`: Unique identifier +- `schemaType`: Form type identifier +- `schemaVersion`: Form version +- `data`: JSON data (form responses) +- `hash`: Computed hash for conflict detection +- `change_id`: Strictly increasing change identifier +- `last_modified`: Server-assigned timestamp +- `last_modified_by`: Username from JWT +- `deleted`: Soft delete flag +- `origin_client_id`: Client that created the record + +### Attachment Handling + +Attachments are managed separately: + +- **Immutable**: Once uploaded, cannot be modified +- **Referenced**: Observations reference attachments by ID +- **Separate Sync**: Uploaded/downloaded in separate phase +- **Manifest-based**: Server provides manifest of changes + +See the [Synchronization guide](/using/synchronization) for more details. + +## Database Design + +### Observations Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | UUID | Primary key | +| `schema_type` | String | Form type identifier | +| `schema_version` | String | Form version | +| `data` | JSONB | Form data | +| `hash` | String | Computed hash | +| `change_id` | Integer | Strictly increasing change ID | +| `last_modified` | Timestamp | Last modification time | +| `last_modified_by` | String | Username | +| `deleted` | Boolean | Soft delete flag | +| `origin_client_id` | String | Creating client ID | +| `created_at` | Timestamp | Creation time | + +### Attachments Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | String | Attachment ID (UUID) | +| `hash` | String | SHA-256 hash | +| `size` | Integer | File size in bytes | +| `mime_type` | String | MIME type | +| `change_id` | Integer | Change ID for sync | +| `last_modified` | Timestamp | Last modification time | +| `sync_state` | String | Sync state | + +## Security Architecture + +### Authentication + +- **JWT Tokens**: JSON Web Tokens for authentication +- **Token Refresh**: Refresh tokens for long-lived sessions +- **Role-Based Access**: `read-only`, `read-write`, `admin` roles + +### Authorization + +- **Endpoint Protection**: Middleware validates JWT on protected routes +- **Role Checks**: Endpoints check required roles +- **Resource Access**: Users can only access their own data (unless admin) + +### Data Security + +- **Transport**: HTTPS enforced in production +- **Storage**: Database encryption via PostgreSQL +- **Secrets**: Environment variables for sensitive data +- **Validation**: Input validation on all endpoints + +## Performance Considerations + +### Client-Side + +- **Local Database**: Fast queries using WatermelonDB +- **Incremental Sync**: Only sync changes since last sync +- **Lazy Loading**: Load attachments on demand +- **Caching**: Cache app bundles and form specifications + +### Server-Side + +- **Database Indexing**: Indexes on `change_id`, `schema_type`, `hash` +- **Connection Pooling**: Efficient database connection management +- **Pagination**: Cursor-based pagination for large datasets +- **Caching**: ETag support for efficient caching + +## Extension Points + +### Custom Renderers + +Create custom question type renderers: + +1. Define result interface in `FormulusInterfaceDefinition.ts` +2. Add interface method +3. Implement React component +4. Register renderer +5. Add mock implementation + +### Custom Applications + +Build custom web applications: + +1. Create HTML/CSS/JavaScript files +2. Include Formulus load script +3. Use Formulus JavaScript interface +4. Package as app bundle +5. Upload to server + +### Plugins + +ODE's plugin system allows for extending functionality without modifying core code. The plugin architecture is under active development and will be documented as it evolves. + +Current extension points: +- Custom renderers for question types +- Custom applications +- Server-side handlers (for advanced use cases) + +## Related Documentation + +- [Synchronization Details](/using/synchronization) +- [Sync Protocol Technical Details](/development/architecture) +- [Database Schema](/development/architecture) +- [API Reference](/reference/api) diff --git a/versioned_docs/version-1.0/development/building-testing.md b/versioned_docs/version-1.0/development/building-testing.md new file mode 100644 index 0000000..fb56592 --- /dev/null +++ b/versioned_docs/version-1.0/development/building-testing.md @@ -0,0 +1,359 @@ +--- +sidebar_position: 4 +--- + +# Building & Testing + +Complete guide to building ODE components from source and running tests. + +## Building from Source + +### Formulus + +Build the React Native mobile application: + + + + +```bash +cd formulus +npm install +npm run android # For Android +npm run ios # For iOS (macOS only) +``` + + + + +```bash +cd formulus/android +./gradlew assembleRelease +``` + +APK will be in `android/app/build/outputs/apk/release/` + + + + +```bash +cd formulus/ios +xcodebuild -workspace Formulus.xcworkspace -scheme Formulus -configuration Release +``` + +Or build from Xcode: +1. Open `Formulus.xcworkspace` in Xcode +2. Select "Any iOS Device" or specific device +3. Product → Archive +4. Distribute App + + + + +### Formplayer + +Build the React web form renderer: + +```bash +cd formulus-formplayer +npm install +npm run build +``` + +**Build for React Native:** + +```bash +npm run build:rn +``` + +This builds and copies the output to the Formulus app. + +### Synkronus + +Build the Go server: + +```bash +cd synkronus +go build -o bin/synkronus cmd/synkronus/main.go +``` + +**Cross-platform builds:** + + + + +```bash +GOOS=linux GOARCH=amd64 go build -o bin/synkronus-linux cmd/synkronus/main.go +``` + + + + +```powershell +$env:GOOS="windows"; $env:GOARCH="amd64"; go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + +Or using bash (Git Bash/WSL): + +```bash +GOOS=windows GOARCH=amd64 go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + + + + +```bash +GOOS=darwin GOARCH=amd64 go build -o bin/synkronus-macos cmd/synkronus/main.go +``` + + + + +### Synkronus CLI + +Build the CLI: + +```bash +cd synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +## Testing + +### Frontend Testing + +#### Formulus + +```bash +cd formulus +npm test +``` + +Runs Jest tests with React Native Testing Library. + +#### Formplayer + +```bash +cd formulus-formplayer +npm test +``` + +Runs Jest tests for React components. + +### Backend Testing + +#### Synkronus + +```bash +cd synkronus +go test ./... +``` + +Run all tests: + +```bash +# With coverage +go test -cover ./... + +# Verbose output +go test -v ./... + +# Specific package +go test ./internal/handlers +``` + +#### Integration Tests + +```bash +# Run integration tests (requires database) +go test -tags=integration ./... +``` + +### End-to-End Testing + +End-to-end testing infrastructure is under development. Current testing focuses on: + +- Unit tests for individual components +- Integration tests for API endpoints +- Component tests for React components + +E2E testing will be added as the testing infrastructure evolves. + +## Code Quality Checks + +### Linting + +**Frontend:** + +```bash +# Formulus +cd formulus +npm run lint +npm run lint:fix + +# Formplayer +cd formulus-formplayer +npm run lint +npm run lint:fix +``` + +**Backend:** + +```bash +# Synkronus +cd synkronus +golangci-lint run # If configured +``` + +### Formatting + +**Frontend:** + +```bash +# Format code +npm run format + +# Check formatting +npm run format:check +``` + +**Backend:** + +```bash +# Format Go code +go fmt ./... + +# Check with goimports +goimports -w . +``` + +## CI/CD Pipeline + +The project uses GitHub Actions for continuous integration: + +### Workflows + +**Synkronus Docker Build:** +- Triggers on push to `main` or PRs affecting `synkronus/` +- Builds Docker image +- Publishes to GitHub Container Registry +- Tags: `latest`, `v{version}`, `{branch-name}` + +**Frontend Quality Checks:** +- Runs on all PRs +- Checks linting and formatting +- Runs tests +- Builds components + +### Local CI Simulation + +Run CI checks locally: + +```bash +# Frontend +cd formulus && npm run lint && npm run format:check && npm test +cd formulus-formplayer && npm run lint && npm run format:check && npm test + +# Backend +cd synkronus && go test ./... && go fmt ./... +``` + +## Docker Builds + +### Synkronus Docker Image + +Build locally: + +```bash +cd synkronus +docker build -t synkronus:local . +``` + +**Multi-platform build:** + +```bash +docker buildx create --name multiplatform --use +docker buildx build --platform linux/amd64,linux/arm64 -t synkronus:local . +``` + +## Release Process + +### Versioning + +ODE follows semantic versioning (MAJOR.MINOR.PATCH): + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +### Creating a Release + +1. Update version numbers in: + - `package.json` (frontend projects) + - `versioninfo.json` (Go projects) + - Documentation + +2. Create release branch: + +```bash +git checkout -b release/v1.0.0 +``` + +3. Update changelog + +4. Create tag: + +```bash +git tag -a v1.0.0 -m "Release version 1.0.0" +git push origin v1.0.0 +``` + +5. CI/CD will automatically: + - Build Docker images + - Publish to container registry + - Create GitHub release + +## Troubleshooting Build Issues + +### Node Modules Issues + +```bash +# Clear and reinstall +rm -rf node_modules package-lock.json +npm install +``` + +### Go Module Issues + +```bash +# Clean module cache +go clean -modcache +go mod download +go mod tidy +``` + +### Android Build Issues + +```bash +# Clean Android build +cd android +./gradlew clean +cd .. +npm run android +``` + +### iOS Build Issues + +```bash +# Clean pods +cd ios +rm -rf Pods Podfile.lock +bundle exec pod install +cd .. +npm run ios +``` + +## Related Documentation + +- [Development Setup](/development/setup) +- [Contributing Guide](/development/contributing) +- [Architecture Overview](/development/architecture) diff --git a/versioned_docs/version-1.0/development/contributing.md b/versioned_docs/version-1.0/development/contributing.md new file mode 100644 index 0000000..a8582ca --- /dev/null +++ b/versioned_docs/version-1.0/development/contributing.md @@ -0,0 +1,252 @@ +--- +sidebar_position: 3 +--- + +# Contributing + +Guide to contributing to ODE, including contribution process, coding standards, and community guidelines. + +## Overview + +ODE is an open-source project and welcomes contributions from the community. We believe that diverse perspectives and varied skill sets make our project stronger. + +## Ways to Contribute + +You can contribute to ODE in many ways: + +- **Code Contributions**: Bug fixes, new features, improvements +- **Documentation**: Improve existing docs, add examples, fix typos +- **Testing**: Test the platform, report bugs, verify fixes +- **Community**: Help others, answer questions, share use cases +- **Design**: UI/UX improvements, design system contributions +- **Translation**: Help translate documentation and interfaces + +## Contribution Process + +### 1. Find Something to Work On + +- Browse [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) for open issues +- Look for issues labeled `good first issue` for beginners +- Check discussions for ideas and feature requests +- Review documentation for gaps or improvements + +### 2. Set Up Development Environment + +Follow the [Development Setup guide](/development/setup) to set up your local environment. + +### 3. Create a Branch + +```bash +git checkout -b feature/your-feature-name +# or +git checkout -b fix/your-bug-fix +``` + +Use descriptive branch names: +- `feature/` for new features +- `fix/` for bug fixes +- `docs/` for documentation +- `refactor/` for code refactoring + +### 4. Make Your Changes + +- Write clean, well-documented code +- Follow coding standards (see below) +- Add tests for new functionality +- Update documentation as needed + +### 5. Test Your Changes + +```bash +# Frontend projects +npm test +npm run lint +npm run format:check + +# Go projects +go test ./... +go fmt ./... +``` + +### 6. Commit Your Changes + +Write clear, descriptive commit messages: + +``` +feat: Add support for custom question types + +- Add interface for custom renderers +- Implement renderer registration +- Add documentation and examples +``` + +**Commit Message Format:** +- `feat:` for new features +- `fix:` for bug fixes +- `docs:` for documentation +- `style:` for formatting +- `refactor:` for code refactoring +- `test:` for tests +- `chore:` for maintenance + +### 7. Push and Create Pull Request + +```bash +git push origin feature/your-feature-name +``` + +Create a pull request on GitHub with: + +- Clear description of changes +- Reference to related issues +- Screenshots (if UI changes) +- Testing notes + +## Coding Standards + +### Frontend (React/React Native) + +**TypeScript:** +- Use TypeScript for all new code +- Enable strict type checking +- Define interfaces for data structures +- Avoid `any` type + +**Code Style:** +- Follow ESLint rules +- Use Prettier for formatting +- Use functional components with hooks +- Keep components small and focused + +**Example:** + +```typescript +interface User { + id: string; + username: string; + role: 'read-only' | 'read-write' | 'admin'; +} + +const UserProfile: React.FC<{user: User}> = ({user}) => { + return
    {user.username}
    ; +}; +``` + +### Backend (Go) + +**Code Style:** +- Follow `gofmt` formatting +- Use `golint` recommendations +- Write clear, descriptive function names +- Add godoc comments for exported functions + +**Example:** + +```go +// GetUser retrieves a user by username. +// Returns an error if the user is not found. +func (s *Service) GetUser(username string) (*User, error) { + // Implementation +} +``` + +**Error Handling:** +- Always handle errors explicitly +- Return descriptive error messages +- Use error wrapping for context + +### General Guidelines + +- **Write clear code**: Code should be self-documenting +- **Add comments**: Explain why, not what +- **Keep functions small**: Single responsibility principle +- **Use meaningful names**: Variables and functions should be descriptive +- **Avoid duplication**: DRY (Don't Repeat Yourself) +- **Test your code**: Write tests for new functionality + +## Code Quality Checks + +### Before Submitting + +Ensure your code passes all quality checks: + +```bash +# Frontend +npm run lint +npm run format:check +npm test + +# Backend +go test ./... +go fmt ./... +go vet ./... +``` + +### CI/CD Checks + +The CI pipeline automatically checks: + +- Linting (ESLint for frontend) +- Formatting (Prettier for frontend, gofmt for backend) +- Tests (Jest for frontend, go test for backend) +- Build (ensures code compiles) + +## Pull Request Guidelines + +### PR Description + +Include: + +- **Summary**: Brief description of changes +- **Motivation**: Why this change is needed +- **Changes**: What was changed +- **Testing**: How it was tested +- **Screenshots**: If UI changes +- **Related Issues**: Link to related issues + +### Review Process + +- Maintainers will review your PR +- Address feedback promptly +- Be open to suggestions +- Keep discussions constructive + +### After Approval + +- Maintainers will merge your PR +- Your contribution will be included in the next release +- Thank you for contributing! + +## Code of Conduct + +We are committed to providing a welcoming and inclusive environment. Please: + +- Be respectful and considerate +- Welcome newcomers and help them learn +- Focus on constructive feedback +- Respect different viewpoints and experiences + +## Getting Help + +If you need help: + +- Check the [documentation](/) +- Search [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- Ask in [GitHub Discussions](https://github.com/OpenDataEnsemble/ode/discussions) +- Contact maintainers + +## Recognition + +Contributors are recognized in: + +- GitHub contributors list +- Release notes +- Project documentation + +Thank you for contributing to ODE! + +## Related Documentation + +- [Development Setup](/development/setup) +- [Building & Testing](/development/building-testing) +- [Architecture Overview](/development/architecture) diff --git a/versioned_docs/version-1.0/development/extending.md b/versioned_docs/version-1.0/development/extending.md new file mode 100644 index 0000000..e223bf0 --- /dev/null +++ b/versioned_docs/version-1.0/development/extending.md @@ -0,0 +1,242 @@ +--- +sidebar_position: 5 +--- + +# Extending ODE + +Guide to extending ODE functionality through custom renderers, plugins, and internal APIs. + +## Overview + +ODE is designed to be extensible. You can extend functionality through: + +- **Custom Renderers**: Add new question types +- **Custom Applications**: Build specialized web applications +- **Internal APIs**: Extend server functionality (advanced) + +## Custom Renderers + +Custom renderers allow you to add new question types to the Formplayer. + +### Implementation Steps + +1. **Define Result Interface** + +In `FormulusInterfaceDefinition.ts`: + +```typescript +export interface MyCustomResultData { + type: 'mycustom'; + value: string; + timestamp: string; +} + +export type MyCustomResult = ActionResult; +``` + +2. **Add Interface Method** + +```typescript +export interface FormulusInterface { + requestMyCustom(fieldId: string): Promise; +} +``` + +3. **Create React Component** + +Create `MyCustomQuestionRenderer.tsx`: + +```typescript +import React, {useState, useCallback} from 'react'; +import {withJsonFormsControlProps} from '@jsonforms/react'; +import {FormulusClient} from './FormulusInterface'; + +const MyCustomQuestionRenderer: React.FC = ({ + data, + handleChange, + path, +}) => { + const [isLoading, setIsLoading] = useState(false); + const formulusClient = useRef(new FormulusClient()); + + const handleAction = useCallback(async () => { + setIsLoading(true); + try { + const result = await formulusClient.current.requestMyCustom(path); + if (result.status === 'success') { + handleChange(path, result.data.value); + } + } finally { + setIsLoading(false); + } + }, [path, handleChange]); + + return ( + + ); +}; + +export default withJsonFormsControlProps(MyCustomQuestionRenderer); +``` + +4. **Register Renderer** + +In `App.tsx`: + +```typescript +import MyCustomQuestionRenderer, { + myCustomQuestionTester, +} from './MyCustomQuestionRenderer'; + +const customRenderers = [ + ...materialRenderers, + {tester: myCustomQuestionTester, renderer: MyCustomQuestionRenderer}, +]; +``` + +5. **Add Mock Implementation** + +For development testing, add mock support in `webview-mock.ts`. + +6. **Implement Native Handler** + +In Formulus React Native code, implement the native handler in `FormulusMessageHandlers.ts`. + +See the [Form Design guide](/guides/form-design) for complete examples. + +## Custom Applications + +Custom applications are web-based interfaces that run within Formulus. + +### Creating a Custom App + +1. **Create HTML Structure** + +```html + + + + My Custom App + + + +

    My Custom App

    + + + +``` + +2. **Use Formulus API** + +```javascript +async function init() { + const api = await getFormulus(); + + // Use API methods + const forms = await api.getAvailableForms(); + // ... +} +``` + +3. **Package as App Bundle** + +Create a ZIP file with: +- `index.html` +- `manifest.json` +- Assets (CSS, JS, images) + +4. **Upload to Server** + +```bash +synk app-bundle upload bundle.zip --activate +``` + +See the [Custom Applications guide](/guides/custom-applications) for details. + +## Internal APIs + +For advanced extensions, you can extend the server functionality. + +### Adding New Endpoints + +1. **Define Handler** + +In `internal/handlers/`: + +```go +func (h *Handler) MyNewEndpoint(w http.ResponseWriter, r *http.Request) { + // Implementation +} +``` + +2. **Register Route** + +In `internal/api/api.go`: + +```go +r.Route("/my-endpoint", func(r chi.Router) { + r.Get("/", h.MyNewEndpoint) +}) +``` + +3. **Add Tests** + +Create tests in `internal/handlers/`: + +```go +func TestMyNewEndpoint(t *testing.T) { + // Test implementation +} +``` + +### Adding New Services + +1. **Create Service** + +In `internal/services/`: + +```go +type MyService struct { + // Dependencies +} + +func NewMyService(config *Config) (*MyService, error) { + // Initialization +} +``` + +2. **Integrate with Handlers** + +Pass service to handlers and use in endpoints. + +## Best Practices + +### Custom Renderers + +- Follow existing renderer patterns +- Handle loading and error states +- Provide fallback options (manual input) +- Test thoroughly in mock and native environments + +### Custom Applications + +- Always use `getFormulus()` before accessing API +- Handle offline scenarios gracefully +- Optimize for mobile devices +- Test on actual devices + +### Internal APIs + +- Follow Go best practices +- Add comprehensive tests +- Document endpoints +- Consider backward compatibility + +## Related Documentation + +- [Form Design Guide](/guides/form-design) +- [Custom Applications Guide](/guides/custom-applications) +- [API Reference](/reference/api) +- [Architecture Overview](/development/architecture) diff --git a/versioned_docs/version-1.0/development/formplayer-development.md b/versioned_docs/version-1.0/development/formplayer-development.md new file mode 100644 index 0000000..e5f106e --- /dev/null +++ b/versioned_docs/version-1.0/development/formplayer-development.md @@ -0,0 +1,95 @@ +--- +sidebar_position: 4 +--- + +# Formplayer Development + +Complete guide for developing the Formplayer form rendering component. + +## Overview + +Formplayer is a React web application that renders JSON Forms. It runs within WebViews in the Formulus mobile app. + +## Prerequisites + +- **Node.js** 18+ and npm +- **React** development experience + +## Local Development + +### Setup + +```bash +cd formulus-formplayer +npm install +``` + +### Development Server + +```bash +npm start +``` + +Opens at http://localhost:3000 + +### Development Features + +- **Hot Reload**: Changes reflect immediately +- **Source Maps**: Debug in browser DevTools +- **Error Overlay**: Errors shown in browser + +## Building + +### Build for React Native + +Build and copy to Formulus app: + +```bash +npm run build:rn +``` + +This: +1. Builds the React app +2. Copies build to Formulus app directory +3. Updates WebView assets + +### Build for Web + +Standard web build: + +```bash +npm run build +``` + +Output in `build/` directory. + +## Project Structure + +- `src/`: React source code +- `public/`: Static assets +- `build/`: Production build output + +## Adding Question Types + +1. **Create Renderer Component:** + ```typescript + // src/NewQuestionRenderer.tsx + export function NewQuestionRenderer(props) { + return + } + ``` + +2. **Register in Formplayer:** + Add to Formplayer configuration when initialized by Formulus. + +## Testing + +```bash +npm test +``` + +## Related Documentation + +- [Formplayer Reference](/reference/formplayer) - Component reference +- [Form Design Guide](/guides/form-design) - Creating forms + diff --git a/versioned_docs/version-1.0/development/formulus-development.md b/versioned_docs/version-1.0/development/formulus-development.md new file mode 100644 index 0000000..24e3fad --- /dev/null +++ b/versioned_docs/version-1.0/development/formulus-development.md @@ -0,0 +1,365 @@ +--- +sidebar_position: 3 +--- + +# Formulus Development + +Complete guide for developing the Formulus mobile application. + +## Overview + +This guide covers local development and production building for the Formulus React Native application. + +## Prerequisites + +### Required Tools + +- **Node.js** 18+ and npm +- **React Native CLI** or Expo CLI +- **Java Development Kit (JDK)** 11 or higher +- **Android Studio** (for Android development) +- **Xcode** (for iOS development, macOS only) +- **Android SDK** (via Android Studio) +- **CocoaPods** (for iOS, macOS only) + +### Platform-Specific Requirements + +#### Android + +- Android SDK Platform 33+ +- Android SDK Build Tools +- Android Emulator or physical device + +#### iOS (macOS only) + +- Xcode 14+ +- CocoaPods +- iOS Simulator or physical device + +## Local Development Setup + +### Step 1: Clone Repository + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/formulus +``` + +### Step 2: Install Dependencies + +```bash +npm install +``` + +### Step 3: Install iOS Dependencies + + + + +```bash +cd ios +bundle install +bundle exec pod install +cd .. +``` + + + + +iOS development is only available on macOS. Skip this step if you're developing for Android only. + + + + +### Step 4: Generate API Client + +Generate the Synkronus API client from OpenAPI spec: + +```bash +npm run generate:api +``` + +### Step 5: Generate WebView Injection Script + +Generate the JavaScript injection script: + +```bash +npm run generate +``` + +### Step 6: Start Metro Bundler + +```bash +npm start +``` + +Keep this terminal open. Metro is the JavaScript bundler. + +### Step 7: Run on Device/Emulator + + + + +```bash +npm run android +``` + +This will: +1. Build the Android app +2. Install it on your connected device/emulator +3. Start the app +4. Connect to Metro bundler + + + + +```bash +npm run ios +``` + +This will: +1. Build the iOS app +2. Install it on your iOS Simulator or connected device +3. Start the app +4. Connect to Metro bundler + + + + +## Development Workflow + +### Hot Reload + +Changes to JavaScript/TypeScript files automatically reload: + +- **Fast Refresh**: React components update without losing state +- **Live Reload**: Full app reload on some changes +- **Error Overlay**: Errors displayed in app + +### Debugging + +#### React Native Debugger + +1. Shake device or press `Ctrl+M` (Windows/Linux) or `Cmd+M` (macOS) +2. Select "Debug" +3. Chrome DevTools opens +4. Set breakpoints and inspect code + +#### Logging + +```typescript +import { console } from 'react-native' + +console.log('Debug message') +console.warn('Warning message') +console.error('Error message') +``` + +View logs: + + + + +```bash +adb logcat | grep -i formulus +``` + + + + +View logs in Xcode console: +1. Open Xcode +2. Run the app +3. View logs in the bottom panel + +Or use Console.app on macOS: +```bash +# Filter for your app +log stream --predicate 'processImagePath contains "Formulus"' +``` + + + + +For Android development on Windows: + +```powershell +adb logcat | Select-String formulus +``` + +Or using Git Bash/WSL: + +```bash +adb logcat | grep -i formulus +``` + +For iOS development, Windows is not supported. Use macOS with Xcode. + + + + +### Testing + +```bash +# Run tests +npm test + +# Run tests in watch mode +npm test -- --watch +``` + +## Building for Production + +### Android Production Build + +#### Step 1: Generate Signing Key + +```bash +keytool -genkeypair -v -storetype PKCS12 -keystore formulus-release.keystore -alias formulus -keyalg RSA -keysize 2048 -validity 10000 +``` + +#### Step 2: Configure Signing + +Edit `android/gradle.properties`: + +```properties +FORMULUS_RELEASE_STORE_FILE=formulus-release.keystore +FORMULUS_RELEASE_KEY_ALIAS=formulus +FORMULUS_RELEASE_STORE_PASSWORD=your-store-password +FORMULUS_RELEASE_KEY_PASSWORD=your-key-password +``` + +#### Step 3: Build APK + +```bash +cd android +./gradlew assembleRelease +``` + +APK location: `android/app/build/outputs/apk/release/app-release.apk` + +#### Step 4: Build AAB (for Play Store) + +```bash +cd android +./gradlew bundleRelease +``` + +AAB location: `android/app/build/outputs/bundle/release/app-release.aab` + +### iOS Production Build (macOS only) + +#### Step 1: Configure Signing + +1. Open `ios/Formulus.xcworkspace` in Xcode +2. Select project in navigator +3. Go to "Signing & Capabilities" +4. Select team and provisioning profile + +#### Step 2: Build Archive + +1. In Xcode: Product → Archive +2. Wait for build to complete +3. Distribute App in Organizer + +#### Step 3: Export IPA + +1. Select archive in Organizer +2. Click "Distribute App" +3. Choose distribution method (App Store, Ad Hoc, Enterprise) +4. Follow export wizard + +## Project Structure + +### Key Directories + +- `src/`: TypeScript source code +- `android/`: Android native code +- `ios/`: iOS native code +- `assets/`: Static assets (images, fonts, etc.) + +### Important Files + +- `App.tsx`: Main application component +- `package.json`: Dependencies and scripts +- `tsconfig.json`: TypeScript configuration +- `metro.config.js`: Metro bundler configuration +- `babel.config.js`: Babel configuration + +## Common Tasks + +### Adding Dependencies + +```bash +npm install package-name +``` + +For native dependencies, may need to: + +```bash +# Android +cd android && ./gradlew clean && cd .. + +# iOS +cd ios && pod install && cd .. +``` + +### Updating API Client + +When Synkronus API changes: + +```bash +npm run generate:api +``` + +### Updating WebView Interface + +When Formulus interface changes: + +```bash +npm run generate +``` + +### Cleaning Build + +```bash +# Android +cd android && ./gradlew clean && cd .. + +# iOS +cd ios && xcodebuild clean && cd .. + +# Remove node_modules +rm -rf node_modules +npm install +``` + +## Troubleshooting + +### Common Issues + +**Metro Bundler Won't Start:** +- Clear Metro cache: `npm start -- --reset-cache` +- Delete `node_modules` and reinstall + +**Build Fails:** +- Clean build: `cd android && ./gradlew clean` +- Check Java version: `java -version` (should be 11+) +- Verify Android SDK is installed + +**iOS Build Fails:** +- Run `pod install` in `ios/` directory +- Clean build folder in Xcode +- Check signing configuration + +**App Crashes on Launch:** +- Check logs: `adb logcat` or Xcode console +- Verify API client is generated +- Check WebView injection script is generated + +## Related Documentation + +- [Installing Formulus for Development](/development/installing-formulus-dev) - ADB/emulator setup +- [Formulus Reference](/reference/formulus) - Component reference +- [Building and Testing](/development/building-testing) - Build procedures + diff --git a/versioned_docs/version-1.0/development/index.md b/versioned_docs/version-1.0/development/index.md new file mode 100644 index 0000000..6b011a5 --- /dev/null +++ b/versioned_docs/version-1.0/development/index.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 0 +--- + +# Development + +Resources for developers who want to contribute to ODE or extend its functionality. + +## Getting Started with Development + +
    +
    +
    +
    +

    Setup

    +
    +
    +

    Set up your development environment and get the codebase running locally.

    + Setup Guide → +
    +
    +
    +
    +
    +
    +

    Architecture

    +
    +
    +

    Understand the architecture and design decisions behind ODE.

    + View Architecture → +
    +
    +
    +
    + +## Component Development + +
    +
    +
    +
    +

    Formulus Development

    +
    +
    +

    Develop and contribute to the Formulus mobile app (React Native).

    + View Docs → +
    +
    +
    +
    +
    +
    +

    Formplayer Development

    +
    +
    +

    Develop and contribute to the Formplayer form engine (React).

    + View Docs → +
    +
    +
    +
    +
    +
    +

    Synkronus Development

    +
    +
    +

    Develop and contribute to the Synkronus server backend (Go).

    + View Docs → +
    +
    +
    +
    +
    +
    +

    Synkronus Portal Development

    +
    +
    +

    Develop and contribute to the Synkronus web portal.

    + View Docs → +
    +
    +
    +
    + +## Contributing & Extending + +
    +
    +
    +
    +

    Contributing

    +
    +
    +

    Learn how to contribute to ODE, including code style and best practices.

    + Contribute → +
    +
    +
    +
    +
    +
    +

    Building & Testing

    +
    +
    +

    Build ODE components from source and run the test suite.

    + Build & Test → +
    +
    +
    +
    +
    +
    +

    Extending ODE

    +
    +
    +

    Extend ODE functionality with custom renderers and integrations.

    + Extend → +
    +
    +
    +
    +
    +
    +

    Installing Formulus Dev

    +
    +
    +

    Install and run the Formulus development build on your device.

    + Install → +
    +
    +
    +
    diff --git a/versioned_docs/version-1.0/development/installing-formulus-dev.md b/versioned_docs/version-1.0/development/installing-formulus-dev.md new file mode 100644 index 0000000..0156b8e --- /dev/null +++ b/versioned_docs/version-1.0/development/installing-formulus-dev.md @@ -0,0 +1,510 @@ +--- +sidebar_position: 2 +--- + +# Installing Formulus for Development + +Complete guide for developers to install Formulus on physical devices or emulators using ADB or development builds. + +## Overview + +Developers can install Formulus on Android devices or emulators using several methods: + +- **ADB Installation** - Install APK directly via Android Debug Bridge +- **Android Emulator** - Run and test on virtual devices +- **Development Build** - Build and run from source with hot reload + +This guide covers cross-platform commands for Linux, macOS, and Windows. + +## Prerequisites + +Before installing, ensure you have: + +| Requirement | Description | +|-------------|-------------| +| **Android SDK** | Android SDK Platform Tools (includes ADB) | +| **ADB** | Android Debug Bridge (part of SDK Platform Tools) | +| **USB Drivers** | Device-specific USB drivers (for physical devices) | +| **Developer Options** | Enabled on Android device (for physical devices) | +| **USB Debugging** | Enabled on Android device (for physical devices) | + +### Installing Android SDK Platform Tools + + + + +```bash +# Ubuntu/Debian +sudo apt-get update +sudo apt-get install android-tools-adb android-tools-fastboot + +# Fedora +sudo dnf install android-tools + +# Arch Linux +sudo pacman -S android-tools + +# Verify installation +adb version +``` + + + + +```bash +# Using Homebrew +brew install android-platform-tools + +# Verify installation +adb version +``` + + + + +1. **Download Android SDK Platform Tools** from [developer.android.com](https://developer.android.com/studio/releases/platform-tools) +2. **Extract the ZIP file** to a location like `C:\platform-tools` +3. **Add to PATH**: + - Open System Properties → Environment Variables + - Add `C:\platform-tools` to the PATH variable +4. **Verify installation**: + ```powershell + adb version + ``` + + + + +## Method 1: ADB Installation on Physical Device + +### Step 1: Enable Developer Options + +1. **Open Settings** on your Android device +2. **Navigate to About Phone** (or About Device) +3. **Find "Build Number"** (usually at the bottom) +4. **Tap "Build Number" 7 times** until you see "You are now a developer!" + +### Step 2: Enable USB Debugging + +1. **Go back to Settings** +2. **Open Developer Options** (now visible in Settings) +3. **Enable "USB Debugging"** +4. **Accept the warning** about USB debugging + +### Step 3: Connect Device + +1. **Connect your device** to your computer via USB cable +2. **On your device**, you may see a prompt: "Allow USB debugging?" +3. **Check "Always allow from this computer"** (optional but recommended) +4. **Tap "Allow"** + +### Step 4: Verify Device Connection + + + + +```bash +adb devices +``` + + + + +```powershell +adb devices +``` + + + + +**Expected output:** +``` +List of devices attached +ABC123XYZ456 device +``` + +If you see "unauthorized", check your device and accept the USB debugging prompt. + +### Step 5: Install Formulus APK + +#### Option A: Install from Local APK File + + + + +```bash +# Navigate to directory containing APK +cd /path/to/formulus/android/app/build/outputs/apk/debug + +# Install APK +adb install app-debug.apk +``` + + + + +```powershell +# Navigate to directory containing APK +cd C:\path\to\formulus\android\app\build\outputs\apk\debug + +# Install APK +adb install app-debug.apk +``` + + + + +#### Option B: Install from Remote URL + + + + +```bash +# Download and install in one command +curl -L https://github.com/OpenDataEnsemble/ode/releases/download/v1.0.0/formulus.apk -o /tmp/formulus.apk +adb install /tmp/formulus.apk +``` + + + + +```powershell +# Download and install +Invoke-WebRequest -Uri "https://github.com/OpenDataEnsemble/ode/releases/download/v1.0.0/formulus.apk" -OutFile "$env:TEMP\formulus.apk" +adb install "$env:TEMP\formulus.apk" +``` + + + + +### Step 6: Verify Installation + + + + +```bash +# Check if app is installed +adb shell pm list packages | grep formulus + +# Launch the app +adb shell am start -n com.opendataensemble.formulus/.MainActivity +``` + + + + +```powershell +# Check if app is installed +adb shell pm list packages | Select-String formulus + +# Launch the app +adb shell am start -n com.opendataensemble.formulus/.MainActivity +``` + + + + +## Method 2: Android Emulator Installation + +### Step 1: Set Up Android Emulator + +#### Using Android Studio + +1. **Install Android Studio** from [developer.android.com](https://developer.android.com/studio) +2. **Open Android Studio** → **Tools** → **Device Manager** +3. **Create Virtual Device** → Select a device (e.g., Pixel 5) +4. **Select System Image** (e.g., Android 11, API 30) +5. **Finish** and start the emulator + +#### Using Command Line (Linux/macOS) + +```bash +# Install emulator via SDK Manager +sdkmanager "emulator" "platform-tools" "platforms;android-30" + +# List available system images +sdkmanager --list | grep system-images + +# Create AVD (Android Virtual Device) +avdmanager create avd -n formulus_emulator -k "system-images;android-30;google_apis;x86_64" + +# Start emulator +emulator -avd formulus_emulator & +``` + +### Step 2: Verify Emulator Connection + + + + +```bash +# Wait for emulator to boot (may take 1-2 minutes) +adb devices + +# Should show emulator +List of devices attached +emulator-5554 device +``` + + + + +```powershell +adb devices +``` + + + + +### Step 3: Install Formulus on Emulator + + + + +```bash +# Build APK first (if not already built) +cd formulus +npm run android + +# Install on emulator +adb -s emulator-5554 install android/app/build/outputs/apk/debug/app-debug.apk +``` + + + + +```powershell +# Build APK first +cd formulus +npm run android + +# Install on emulator +adb -s emulator-5554 install android\app\build\outputs\apk\debug\app-debug.apk +``` + + + + +### Step 4: Important Notes for Emulator + +When configuring Formulus on an emulator to connect to a local server: + +- **Use `10.0.2.2` instead of `localhost`** - This is the special IP that the emulator uses to access the host machine +- **Example server URL**: `http://10.0.2.2:8080` +- **For network servers**: Use the actual IP address or domain name + +## Method 3: Development Build with Hot Reload + +### Prerequisites + +- **Node.js** 18+ and npm +- **React Native CLI** or **Expo CLI** +- **Java Development Kit (JDK)** 11 or higher +- **Android Studio** with Android SDK + +### Step 1: Install Dependencies + + + + +```bash +cd formulus +npm install +``` + + + + +### Step 2: Start Metro Bundler + + + + +```bash +# In formulus directory +npm start +``` + +Keep this terminal open. Metro is the JavaScript bundler for React Native. + + + + +### Step 3: Build and Run on Device/Emulator + + + + +```bash +# For Android device (connected via USB) +npm run android + +# For Android emulator (must be running) +npm run android +``` + + + + +This command will: +1. Build the Android app +2. Install it on your device/emulator +3. Start the app +4. Connect to Metro bundler for hot reload + +### Step 4: Development Features + +With development build, you get: + +- **Hot Reload** - Changes reflect immediately +- **Fast Refresh** - React components update without losing state +- **Debug Menu** - Shake device or press `Ctrl+M` (Windows/Linux) or `Cmd+M` (macOS) +- **React Native Debugger** - Debug JavaScript code in Chrome DevTools + +## Common ADB Commands + +### Useful Commands for Development + +**List connected devices:** +```bash +adb devices +``` + +**Install APK:** +```bash +adb install path/to/app.apk +``` + +**Uninstall app:** +```bash +adb uninstall com.opendataensemble.formulus +``` + +**Reinstall app (uninstall + install):** +```bash +adb install -r path/to/app.apk +``` + +**View app logs:** +```bash +# All logs +adb logcat + +# Filter for Formulus only +adb logcat | grep -i formulus + +# Clear logs +adb logcat -c +``` + +**Pull file from device:** +```bash +adb pull /path/on/device /path/on/computer +``` + +**Push file to device:** +```bash +adb push /path/on/computer /path/on/device +``` + +**Open app:** +```bash +adb shell am start -n com.opendataensemble.formulus/.MainActivity +``` + +**Stop app:** +```bash +adb shell am force-stop com.opendataensemble.formulus +``` + +**Clear app data:** +```bash +adb shell pm clear com.opendataensemble.formulus +``` + +## Troubleshooting + +### Device Not Detected + +**Problem**: `adb devices` shows no devices. + +**Solutions**: +- Check USB cable connection +- Try a different USB port +- Install device-specific USB drivers (Windows) +- Enable USB debugging on device +- Accept USB debugging prompt on device +- Restart ADB server: `adb kill-server && adb start-server` + +### Installation Fails + +**Problem**: `adb install` fails with error. + +**Solutions**: +- Uninstall existing version first: `adb uninstall com.opendataensemble.formulus` +- Use `-r` flag to reinstall: `adb install -r app.apk` +- Check device has enough storage +- Verify APK is not corrupted +- Check device is in file transfer mode (not charging only) + +### Emulator Connection Issues + +**Problem**: Cannot connect to local server from emulator. + +**Solutions**: +- Use `10.0.2.2` instead of `localhost` for server URL +- Check firewall isn't blocking connections +- Verify server is running and accessible +- Use actual IP address instead of localhost + +### Permission Denied Errors + +**Problem**: ADB commands fail with permission errors. + + + + +```bash +# Add user to plugdev group +sudo usermod -a -G plugdev $USER + +# Create udev rules +sudo nano /etc/udev/rules.d/51-android.rules +# Add: SUBSYSTEM=="usb", ATTR{idVendor}=="####", MODE="0664", GROUP="plugdev" + +# Reload udev rules +sudo udevadm control --reload-rules +sudo udevadm trigger + +# Log out and log back in +``` + + + + +macOS typically doesn't require special permissions for ADB. If you encounter permission issues: + +1. Check USB cable connection +2. Try a different USB port +3. Restart ADB: `adb kill-server && adb start-server` +4. Check System Preferences → Security & Privacy for blocked apps + + + + +Windows typically handles USB device permissions automatically. If you encounter issues: + +1. Install device-specific USB drivers from manufacturer +2. Check Device Manager for unrecognized devices +3. Try different USB port or cable +4. Restart ADB: `adb kill-server && adb start-server` + + + + +## Related Documentation + +- [Formulus Development Setup](/development/formulus-development) - Complete development environment setup +- [Formulus Component Reference](/reference/formulus) - Detailed component documentation +- [Building and Testing](/development/building-testing) - Build and test procedures + diff --git a/versioned_docs/version-1.0/development/setup.md b/versioned_docs/version-1.0/development/setup.md new file mode 100644 index 0000000..1a7c1fc --- /dev/null +++ b/versioned_docs/version-1.0/development/setup.md @@ -0,0 +1,306 @@ +--- +sidebar_position: 1 +--- + +# Development Setup + +Complete guide to setting up a development environment for ODE. + +## Prerequisites + +Before setting up the development environment, ensure you have: + +- **Node.js** 18.0 or higher +- **npm** 9.0 or higher +- **Go** 1.22 or higher (for server and CLI development) +- **PostgreSQL** 13.0 or higher (for server development) +- **Git** for version control +- **Docker** and **Docker Compose** (optional, for containerized development) + +## Repository Structure + +ODE is a monorepo containing multiple components: + +``` +ode/ +├── formulus/ # React Native mobile app +├── formulus-formplayer/ # React web form renderer +├── synkronus/ # Go backend server +├── synkronus-cli/ # Go command-line utility +├── synkronus-portal/ # React web portal +└── packages/ + └── tokens/ # Design tokens package +``` + +## Clone the Repository + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode +``` + +## Formulus Development + +### Setup + +```bash +cd formulus +npm install +``` + +### Running + +```bash +# Start Metro bundler +npm start + +# Run on Android +npm run android + +# Run on iOS (macOS only) +npm run ios +``` + +### Code Quality + +ODE enforces consistent formatting and linting: + +```bash +# Run linting +npm run lint + +# Run linting with auto-fix +npm run lint:fix + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check +``` + +### Generating Files + +```bash +# Generate WebView injection script +npm run generate + +# Generate API client from OpenAPI spec +npm run generate:api +``` + +## Formplayer Development + +### Setup + +```bash +cd formulus-formplayer +npm install +``` + +### Running + +```bash +# Development server +npm start + +# Build for React Native +npm run build:rn +``` + +### Code Quality + +```bash +# Run linting +npm run lint + +# Run linting with auto-fix +npm run lint:fix + +# Format code +npm run format + +# Check formatting (no writes) +npm run format:check +``` + +## Synkronus Development + +### Setup + +```bash +cd synkronus +go mod download +``` + +### Configuration + +Create a `.env` file: + +```bash +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-for-development +LOG_LEVEL=debug +APP_BUNDLE_PATH=./data/app-bundles +``` + +### Running + +```bash +# Build +go build -o bin/synkronus cmd/synkronus/main.go + +# Run +./bin/synkronus + +# Or run directly +go run cmd/synkronus/main.go +``` + +### Database Setup + +Ensure PostgreSQL is running and create a database: + +```sql +CREATE DATABASE synkronus; +``` + +The schema will be created automatically on first run. + +## Synkronus CLI Development + +### Setup + +```bash +cd synkronus-cli +go mod download +``` + +### Building + +```bash +# Build +go build -o bin/synk ./cmd/synkronus + +# Run +./bin/synk +``` + +## Development Workflow + +### 1. Create a Feature Branch + +```bash +git checkout -b feature/your-feature-name +``` + +### 2. Make Changes + +Make your code changes following the coding standards. + +### 3. Test Locally + +```bash +# Run tests +npm test # For frontend projects +go test ./... # For Go projects + +# Check code quality +npm run lint +npm run format:check +``` + +### 4. Commit Changes + +```bash +git add . +git commit -m "Description of changes" +``` + +### 5. Push and Create Pull Request + +```bash +git push origin feature/your-feature-name +``` + +Create a pull request on GitHub. + +## Code Quality Standards + +### Frontend (React/React Native) + +- **Linting**: ESLint with project-specific rules +- **Formatting**: Prettier with consistent configuration +- **TypeScript**: Strict type checking enabled +- **Testing**: Jest for unit tests + +### Backend (Go) + +- **Formatting**: `gofmt` or `goimports` +- **Linting**: `golangci-lint` (if configured) +- **Testing**: Standard Go testing package +- **Documentation**: Godoc comments for exported functions + +### CI/CD + +The CI pipeline automatically: + +- Runs linting and formatting checks +- Runs tests +- Builds components +- Publishes Docker images (for synkronus) + +## Development Tools + +### Recommended IDE Setup + +- **VS Code**: With extensions for TypeScript, Go, and React +- **IntelliJ IDEA**: With Go and JavaScript plugins +- **Android Studio**: For Android development +- **Xcode**: For iOS development (macOS only) + +### Useful Commands + +```bash +# Check all components +cd formulus && npm run lint && cd .. +cd formulus-formplayer && npm run lint && cd .. +cd synkronus && go test ./... && cd .. + +# Format all code +cd formulus && npm run format && cd .. +cd formulus-formplayer && npm run format && cd .. +cd synkronus && go fmt ./... && cd .. +``` + +## Troubleshooting + +### Node Modules Issues + +```bash +# Clear and reinstall +rm -rf node_modules package-lock.json +npm install +``` + +### Go Module Issues + +```bash +# Clean module cache +go clean -modcache +go mod download +``` + +### Database Connection Issues + +- Verify PostgreSQL is running +- Check connection string format +- Ensure database exists +- Verify user permissions + +## Related Documentation + +- [Architecture Overview](/development/architecture) +- [Contributing Guide](/development/contributing) +- [Building & Testing](/development/building-testing) diff --git a/versioned_docs/version-1.0/development/synkronus-development.md b/versioned_docs/version-1.0/development/synkronus-development.md new file mode 100644 index 0000000..ce542d4 --- /dev/null +++ b/versioned_docs/version-1.0/development/synkronus-development.md @@ -0,0 +1,183 @@ +--- +sidebar_position: 5 +--- + +# Synkronus Server Development + +Complete guide for developing the Synkronus server component. + +## Prerequisites + +- **Go** 1.22+ +- **PostgreSQL** 12+ +- **Git** + +## Local Development Setup + +### Step 1: Clone Repository + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus +``` + +### Step 2: Install Dependencies + +```bash +go mod download +``` + +### Step 3: Set Up Database + + + + +```bash +# Create database +createdb synkronus + +# Or using psql +psql -U postgres -c "CREATE DATABASE synkronus;" +``` + + + + +Using PowerShell or Command Prompt: + +```powershell +# Using psql +psql -U postgres -c "CREATE DATABASE synkronus;" +``` + +Or using Git Bash/WSL: + +```bash +# Create database +createdb synkronus + +# Or using psql +psql -U postgres -c "CREATE DATABASE synkronus;" +``` + + + + +### Step 4: Configure Environment + +Create `.env` file: + +```bash +PORT=8080 +DB_CONNECTION=postgres://user:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=dev-secret-change-in-production +LOG_LEVEL=debug +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin +``` + +### Step 5: Create Directories + +```bash +mkdir -p data/app-bundles +``` + +### Step 6: Run Server + +```bash +go run cmd/synkronus/main.go +``` + +Or build and run: + +```bash +go build -o bin/synkronus cmd/synkronus/main.go +./bin/synkronus +``` + +## Development Workflow + +### Hot Reload + +Use tools like `air` for hot reload: + +```bash +go install github.com/cosmtrek/air@latest +air +``` + +### Testing + +```bash +go test ./... +``` + +### API Documentation + +View OpenAPI docs: + +```bash +# Server must be running +open http://localhost:8080/openapi/swagger-ui.html +``` + +## Building for Production + +### Build Binary + +```bash +go build -o bin/synkronus cmd/synkronus/main.go +``` + +### Cross-Platform Builds + + + + +```bash +GOOS=linux GOARCH=amd64 go build -o bin/synkronus-linux cmd/synkronus/main.go +``` + + + + +```powershell +$env:GOOS="windows"; $env:GOARCH="amd64"; go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + +Or using bash (Git Bash/WSL): + +```bash +GOOS=windows GOARCH=amd64 go build -o bin/synkronus.exe cmd/synkronus/main.go +``` + + + + +```bash +GOOS=darwin GOARCH=amd64 go build -o bin/synkronus-macos cmd/synkronus/main.go +``` + + + + +## Docker Development + +### Using Docker Compose + +```bash +docker compose up -d +``` + +### Development with Hot Reload + +Mount source code for live updates. + +## Related Documentation + +- [Synkronus Server Reference](/reference/synkronus-server) - Component reference +- [Deployment Guide](/guides/deployment) - Production deployment +- [Configuration Guide](/guides/configuration) - Configuration options + diff --git a/versioned_docs/version-1.0/development/synkronus-portal-development.md b/versioned_docs/version-1.0/development/synkronus-portal-development.md new file mode 100644 index 0000000..fbfe969 --- /dev/null +++ b/versioned_docs/version-1.0/development/synkronus-portal-development.md @@ -0,0 +1,100 @@ +--- +sidebar_position: 6 +--- + +# Synkronus Portal Development + +Complete guide for developing the Synkronus Portal web interface. + +## Prerequisites + +- **Node.js** 20+ and npm +- **Go** 1.22+ (for backend) +- **PostgreSQL** 17+ (for backend) + +## Local Development Setup + + + + +#### Step 1: Set Up Backend + +See [Synkronus Development](/development/synkronus-development) for backend setup. + +#### Step 2: Set Up Frontend + +```bash +cd synkronus-portal +npm install +``` + +#### Step 3: Start Development Server + +```bash +npm run dev +``` + +Portal available at http://localhost:5174 + + + + +#### Start Backend Services + +```bash +docker compose up -d postgres synkronus +``` + +#### Start Frontend + +```bash +cd synkronus-portal +npm install +npm run dev +``` + +Portal available at http://localhost:5174 + + + + +## Development Features + +- **Hot Module Replacement**: Instant code updates +- **Fast Refresh**: React components update without losing state +- **Source Maps**: Debug in browser DevTools +- **Error Overlay**: Errors shown in browser + +## Building for Production + +### Build + +```bash +npm run build +``` + +Output in `dist/` directory. + +### Docker Production Build + +```bash +docker compose up -d --build +``` + +## Project Structure + +- `src/`: React source code +- `src/components/`: Reusable components +- `src/pages/`: Page components +- `src/services/`: API service +- `src/contexts/`: React contexts + +## Adding Features + +See [Synkronus Portal Reference](/reference/synkronus-portal) for detailed patterns. + +## Related Documentation + +- [Synkronus Portal Reference](/reference/synkronus-portal) - Component reference +- [Deployment Guide](/guides/deployment) - Production deployment + diff --git a/docs/documentation/formulus/formulus.md b/versioned_docs/version-1.0/documentation/formulus/formulus.md similarity index 100% rename from docs/documentation/formulus/formulus.md rename to versioned_docs/version-1.0/documentation/formulus/formulus.md diff --git a/docs/documentation/synkronus-cli/cli.md b/versioned_docs/version-1.0/documentation/synkronus-cli/cli.md similarity index 100% rename from docs/documentation/synkronus-cli/cli.md rename to versioned_docs/version-1.0/documentation/synkronus-cli/cli.md diff --git a/docs/documentation/synkronus/_category_.json b/versioned_docs/version-1.0/documentation/synkronus/_category_.json similarity index 100% rename from docs/documentation/synkronus/_category_.json rename to versioned_docs/version-1.0/documentation/synkronus/_category_.json diff --git a/docs/documentation/synkronus/app-bundle.md b/versioned_docs/version-1.0/documentation/synkronus/app-bundle.md similarity index 100% rename from docs/documentation/synkronus/app-bundle.md rename to versioned_docs/version-1.0/documentation/synkronus/app-bundle.md diff --git a/docs/documentation/synkronus/synkronus.md b/versioned_docs/version-1.0/documentation/synkronus/synkronus.md similarity index 100% rename from docs/documentation/synkronus/synkronus.md rename to versioned_docs/version-1.0/documentation/synkronus/synkronus.md diff --git a/versioned_docs/version-1.0/getting-started/.gitkeep b/versioned_docs/version-1.0/getting-started/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/getting-started/faq.md b/versioned_docs/version-1.0/getting-started/faq.md new file mode 100644 index 0000000..36a25c1 --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/faq.md @@ -0,0 +1,110 @@ +--- +sidebar_position: 5 +--- + +# Frequently Asked Questions + +Common questions about ODE installation, usage, and development. + +## General Questions + +### What is ODE? + +Open Data Ensemble (ODE) is a platform for mobile data collection and synchronization. It provides tools for creating forms, collecting data offline, and synchronizing data across devices and servers. + +### Is ODE free to use? + +Yes, ODE is open source and free to use. The code is available under the MIT license. + +### What platforms does ODE support? + +ODE supports Android and iOS mobile devices. The server component runs on Linux, macOS, and Windows. + +### Do I need internet connectivity to use ODE? + +No, ODE is designed to work offline. Data is stored locally and synchronized when connectivity is available. + +## Installation Questions + +### What are the system requirements? + +See the [Prerequisites](/getting-started/installation/prerequisites) page for detailed system requirements. + +### Can I run ODE in the cloud? + +Yes, ODE can be deployed to cloud platforms such as AWS, Google Cloud, or Azure. See the [Deployment guide](/guides/deployment/production) for details. + +### Do I need a database? + +Yes, ODE requires PostgreSQL for data storage. The database schema is created automatically on first run. + +## Usage Questions + +### How do I create forms? + +Forms are defined using JSON schema. See the [Form Design guide](/guides/forms/overview) for details. + +### Can I customize the user interface? + +Yes, ODE supports custom applications and renderers. See [Custom Applications](/guides/custom-apps/overview) for details. + +### How does synchronization work? + +ODE uses a bidirectional sync protocol that pushes local data to the server and pulls new data from the server. See [Synchronization](/using/synchronization) for details. + +### What happens if two devices modify the same data? + +ODE automatically resolves conflicts using a version-based approach. The most recent version takes precedence. + +## Development Questions + +### How do I contribute to ODE? + +See the [Contributing guide](/development/contributing/guide) for information on how to contribute. + +### Can I extend ODE functionality? + +Yes, ODE is designed to be extensible. See [Extending ODE](/development/extending/overview) for details. + +### What programming languages are used? + +ODE uses React Native for mobile apps, React for web components, and Go for the server. + +## Troubleshooting + +### The app won't connect to the server + +- Verify the server is running and accessible +- Check the server URL in app settings +- Verify firewall and network settings +- For Android emulator, use `10.0.2.2` instead of `localhost` + +### Forms are not appearing + +- Verify forms were uploaded to the server +- Check that the app has synchronized +- Review server logs for errors + +### Data is not synchronizing + +- Check network connectivity +- Verify authentication credentials +- Review server logs for sync errors +- Ensure observations were saved locally + +### Build errors + +- Ensure all prerequisites are installed +- Check that dependencies are up to date +- Review error messages for specific issues +- See component-specific documentation for build instructions + +## Getting Help + +If you cannot find an answer to your question: + +- Check the [Troubleshooting guide](/using/troubleshooting) +- Review the [API Reference](/reference/api/overview) +- Search [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) +- Ask questions in the [Community section](/community/getting-help) + diff --git a/versioned_docs/version-1.0/getting-started/index.md b/versioned_docs/version-1.0/getting-started/index.md new file mode 100644 index 0000000..329389c --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/index.md @@ -0,0 +1,85 @@ +--- +sidebar_position: 0 +--- + +# Getting Started + +Welcome to ODE! This section will help you understand what ODE is, why you should use it, and how to get up and running quickly. + +## What You'll Learn + +Whether you're a researcher, developer, or data practitioner, these guides will help you get started with ODE and start collecting data efficiently. + +
    +
    +
    +
    +

    What is ODE?

    +
    +
    +

    Learn about the Open Data Ensemble platform and its architecture.

    + Get Started → +
    +
    +
    +
    +
    +
    +

    Why ODE?

    +
    +
    +

    Discover the benefits and advantages of using ODE.

    + Learn More → +
    +
    +
    +
    +
    +
    +

    Key Concepts

    +
    +
    +

    Understand fundamental concepts and terminology.

    + Read Guide → +
    +
    +
    +
    +
    +
    +

    Installation

    +
    +
    +

    Step-by-step instructions to install ODE components.

    + Install Now → +
    +
    +
    +
    +
    +
    +

    Quick Start

    +
    +
    +

    Get up and running quickly with a simple example.

    + Start Here → +
    +
    +
    +
    +
    +
    +

    FAQ

    +
    +
    +

    Find answers to frequently asked questions.

    + View FAQ → +
    +
    +
    +
    + +## Next Steps + +Once you've completed the getting started guides, explore the [Using ODE](/docs/using/your-first-form) section to learn how to create forms and manage data. + diff --git a/versioned_docs/version-1.0/getting-started/installation.md b/versioned_docs/version-1.0/getting-started/installation.md new file mode 100644 index 0000000..197d762 --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/installation.md @@ -0,0 +1,420 @@ +--- +sidebar_position: 4 +--- + +# Installation + +Complete installation guide for all ODE components. This guide covers prerequisites, server setup, and mobile app installation. + +## Prerequisites + +Before installing ODE components, ensure your system meets the following requirements. + +### System Requirements + +#### For Mobile Development (Formulus) + +| Requirement | Minimum | Recommended | +|-------------|---------|-------------| +| **Operating System** | macOS 10.15, Windows 10, or Linux | Latest stable version | +| **Node.js** | 18.0 or higher | 20.0 or higher | +| **npm** | 9.0 or higher | 10.0 or higher | +| **Android Studio** | Latest stable | Latest stable | +| **Xcode** | 14.0 or higher (macOS only) | Latest stable | +| **Java Development Kit** | JDK 17 | JDK 17 or higher | + +#### For Server Deployment (Synkronus) + +| Requirement | Minimum | Recommended | +|-------------|---------|-------------| +| **Operating System** | Linux, macOS, or Windows | Linux | +| **Go** | 1.22 or higher | Latest stable | +| **PostgreSQL** | 13.0 or higher | 15.0 or higher | +| **Docker** | 20.10 or higher (optional) | Latest stable | +| **Memory** | 2 GB RAM | 4 GB RAM or more | +| **Storage** | 10 GB free space | 50 GB or more | + +### Development Tools + +**Required Tools:** +- Git: Version control system +- Code Editor: Visual Studio Code, IntelliJ IDEA, or similar +- Terminal: Command-line interface for running commands + +**Recommended Tools:** +- Postman or curl: For testing API endpoints +- pgAdmin or DBeaver: For database management +- Docker Desktop: For containerized development + +### Verification + +Verify your installation by running: + + + + +```bash +# Check Node.js version +node --version + +# Check npm version +npm --version + +# Check Go version +go version + +# Check PostgreSQL version +psql --version + +# Check Docker version (if using) +docker --version +``` + + + + +Using PowerShell: + +```powershell +# Check Node.js version +node --version + +# Check npm version +npm --version + +# Check Go version +go version + +# Check PostgreSQL version +psql --version + +# Check Docker version (if using) +docker --version +``` + +Or using Git Bash/WSL (same commands as Linux/macOS): + +```bash +node --version +npm --version +go version +psql --version +docker --version +``` + + + + +## Installing Synkronus Server + +Synkronus is the backend server component of ODE, responsible for data synchronization, storage, and API services. + + + + +The easiest way to run Synkronus is using Docker: + +```bash +# Pull the latest image +docker pull ghcr.io/opendataensemble/synkronus:latest + +# Run the container +docker run -d \ + --name synkronus \ + -p 8080:8080 \ + -e DB_CONNECTION="postgres://user:password@host:5432/synkronus" \ + -e JWT_SECRET="your-secret-key-here" \ + -v synkronus-bundles:/app/data/app-bundles \ + ghcr.io/opendataensemble/synkronus:latest +``` + + + + +For a complete setup with PostgreSQL: + +```bash +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Copy the example configuration +cp docker-compose.example.yml docker-compose.yml + +# Edit docker-compose.yml with your configuration +# Then start the services +docker compose up -d +``` + + + + +Build and run Synkronus from source: + + + + +```bash +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Build the application +go build -o bin/synkronus cmd/synkronus/main.go + +# Run the application +./bin/synkronus +``` + + + + +Using PowerShell: + +```powershell +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Build the application +go build -o bin/synkronus.exe cmd/synkronus/main.go + +# Run the application +.\bin\synkronus.exe +``` + +Or using Git Bash/WSL (same as Linux/macOS): + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus +go build -o bin/synkronus cmd/synkronus/main.go +./bin/synkronus +``` + + + + + + + +### Configuration + +Synkronus is configured using environment variables. Create a `.env` file or set environment variables: + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | HTTP server port | `8080` | No | +| `DB_CONNECTION` | PostgreSQL connection string | - | Yes | +| `JWT_SECRET` | Secret key for JWT signing | - | Yes | +| `LOG_LEVEL` | Logging level (debug, info, warn, error) | `info` | No | +| `APP_BUNDLE_PATH` | Directory for app bundles | `./data/app-bundles` | No | +| `MAX_VERSIONS_KEPT` | Maximum app bundle versions to keep | `5` | No | + +**Example Configuration:** + +```bash +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=info +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +``` + +### Database Setup + +Before running Synkronus, set up a PostgreSQL database: + + + + +```bash +# Connect to PostgreSQL +psql -U postgres +``` + +Then run SQL commands: + +```sql +-- Create database +CREATE DATABASE synkronus; + +-- Create user (optional) +CREATE USER synkronus WITH PASSWORD 'your-password'; +GRANT ALL PRIVILEGES ON DATABASE synkronus TO synkronus; +``` + + + + +Using PowerShell or Command Prompt: + +```powershell +# Connect to PostgreSQL +psql -U postgres +``` + +Then run SQL commands: + +```sql +-- Create database +CREATE DATABASE synkronus; + +-- Create user (optional) +CREATE USER synkronus WITH PASSWORD 'your-password'; +GRANT ALL PRIVILEGES ON DATABASE synkronus TO synkronus; +``` + +Or using Git Bash/WSL (same as Linux/macOS): + +```bash +psql -U postgres +``` + + + + +```bash +# Connect to PostgreSQL container +docker compose exec postgres psql -U postgres +``` + +Then run SQL commands: + +```sql +CREATE DATABASE synkronus; +CREATE USER synkronus WITH PASSWORD 'your-password'; +GRANT ALL PRIVILEGES ON DATABASE synkronus TO synkronus; +``` + + + + +The database schema will be created automatically on first run. + +### Verification + +Verify the installation: + +1. Check that the server is running: + ```bash + curl http://localhost:8080/health + ``` + +2. Check the API documentation: + ```bash + curl http://localhost:8080/api/docs + ``` + +## Installing Formulus App + +Formulus is the mobile application component of ODE, available for Android and iOS devices. + +For detailed installation instructions, see the [Installing Formulus guide](/getting-started/installing-formulus) which covers: + +- F-Droid installation (recommended for end users) +- Direct APK installation +- System requirements +- Post-installation setup + +For developers who want to install via ADB or emulator, see the [Development Installation guide](/development/installing-formulus-dev). + +### Quick Installation Summary + +#### For End Users + +1. **F-Droid** (Recommended): Install via F-Droid app store +2. **Direct APK**: Download and install APK file directly + +See [Installing Formulus](/getting-started/installing-formulus) for complete instructions. + +#### For Developers + +1. **ADB Installation**: Install via Android Debug Bridge +2. **Emulator**: Run on Android emulator +3. **Development Build**: Build from source with hot reload + +See [Installing Formulus for Development](/development/installing-formulus-dev) for complete instructions. + +### App Configuration + +After installation, configure the app to connect to your Synkronus server: + +1. Open the Formulus app +2. Navigate to Settings +3. Enter your server URL (e.g., `https://your-server.com`) +4. Enter your authentication credentials +5. Save the configuration + +## Installing Synkronus CLI + +The Synkronus CLI is a command-line utility for interacting with the Synkronus server. + +### Installation + +```bash +go install github.com/OpenDataEnsemble/ode/synkronus-cli/cmd/synkronus@latest +``` + +Or build from source: + +```bash +git clone https://github.com/OpenDataEnsemble/ode/synkronus-cli.git +cd synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +### Configuration + +By default, the CLI uses a configuration file located at `$HOME/.synkronus.yaml`. + +**Example configuration file:** + +```yaml +api: + url: http://localhost:8080 + version: 1.0.0 +``` + +You can override this per-command with `--config ` or use `synk config use ` to set a persistent default. + +## Troubleshooting + +### Server Not Accessible + +If the mobile app cannot connect to the server: + +- Verify the server is running: `curl http://localhost:8080/health` +- Check firewall settings +- For Android emulator, use `10.0.2.2` instead of `localhost` +- For iOS simulator, use `localhost` or your machine's IP address + +### Database Connection Issues + +**Problem**: Cannot connect to database + +**Solution**: Verify the `DB_CONNECTION` string format and ensure PostgreSQL is running and accessible. + +### Port Already in Use + +**Problem**: Port 8080 is already in use + +**Solution**: Change the `PORT` environment variable to use a different port. + +### Build Errors + +**Problem**: Build fails with errors + +**Solution**: +- Ensure all prerequisites are installed +- Check that dependencies are up to date +- Review error messages for specific issues +- See component-specific documentation for build instructions + +## Next Steps + +- Follow the [Quick Start guide](/getting-started/quick-start) for a complete setup +- Learn how to [use the app](/using/your-first-form) for data collection +- Review the [Deployment guide](/guides/deployment) for production setup + diff --git a/versioned_docs/version-1.0/getting-started/installation/.gitkeep b/versioned_docs/version-1.0/getting-started/installation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/getting-started/installing-formulus.md b/versioned_docs/version-1.0/getting-started/installing-formulus.md new file mode 100644 index 0000000..29d0297 --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/installing-formulus.md @@ -0,0 +1,232 @@ +--- +sidebar_position: 4 +--- + +# Installing Formulus App + +Complete guide for installing the Formulus mobile application on Android devices. + +## Overview + +Formulus is available for Android devices through multiple installation methods. Choose the method that best fits your needs: + +- **F-Droid** (Recommended for end users) - Official app store for free and open source Android apps +- **Direct APK** - Download and install the APK file directly +- **Development Build** - For developers who want to build from source + +## System Requirements + +Before installing, ensure your device meets these requirements: + +| Requirement | Minimum | +|-------------|---------| +| **Android Version** | Android 7.0 (API level 24) or higher | +| **Storage Space** | 50 MB free space | +| **Internet Connection** | Required for initial setup and synchronization | +| **Permissions** | Camera, Storage, Location (for form features) | + +## Installation Methods + +### Method 1: F-Droid (Recommended) + +F-Droid is the recommended installation method for end users. It provides automatic updates and ensures you're installing the official version. + +#### Step 1: Install F-Droid + +If you don't have F-Droid installed: + +1. **Download F-Droid** from [f-droid.org](https://f-droid.org/) +2. **Enable installation from unknown sources**: + - Go to Settings → Security → Unknown Sources + - Enable the option for your browser or file manager +3. **Install F-Droid** by opening the downloaded APK file + +#### Step 2: Add Formulus Repository + +1. **Open F-Droid** on your device +2. **Navigate to Settings** → **Repositories** +3. **Tap the "+" button** to add a new repository +4. **Enter the Formulus repository URL** (provided by your organization) +5. **Tap "Add"** to save the repository + +#### Step 3: Install Formulus + +1. **Open F-Droid** on your device +2. **Navigate to the Updates tab** or search for "Formulus" +3. **Find Formulus** in the app list +4. **Tap on Formulus** to open the app details +5. **Tap "Install"** to begin installation +6. **Wait for installation** to complete +7. **Tap "Open"** to launch the app + +#### Automatic Updates + +Once installed via F-Droid, Formulus will automatically update when new versions are available: + +1. **F-Droid checks for updates** periodically +2. **Notifications appear** when updates are available +3. **Tap the notification** or open F-Droid to update +4. **Updates install automatically** through F-Droid + +### Method 2: Direct APK Installation + +If F-Droid is not available or you prefer direct installation: + +#### Step 1: Download the APK + +1. **Download the latest APK** from the [releases page](https://github.com/OpenDataEnsemble/ode/releases) +2. **Save the file** to your device's Downloads folder + +#### Step 2: Enable Unknown Sources + +1. **Go to Settings** → **Security** (or **Apps** on newer Android versions) +2. **Enable "Install unknown apps"** or **"Unknown Sources"** +3. **Select your browser or file manager** and enable installation + +#### Step 3: Install the APK + +1. **Open your file manager** or Downloads app +2. **Navigate to the Downloads folder** +3. **Tap on the Formulus APK file** +4. **Review the permissions** requested by the app +5. **Tap "Install"** to begin installation +6. **Wait for installation** to complete +7. **Tap "Open"** to launch the app + +### Method 3: Development Build + +For developers who want to build and install from source, see the [Development Installation Guide](/development/formulus-development). + +## Post-Installation Setup + +After installing Formulus, you need to configure it to connect to your Synkronus server: + +### Initial Configuration + +1. **Open Formulus** on your device +2. **You'll see the welcome screen** with configuration options +3. **Choose your configuration method**: + - **QR Code Scan** (Recommended) - Scan a QR code with server details + - **Manual Entry** - Enter server URL and credentials manually + +### QR Code Configuration + +1. **Tap "Scan QR Code"** on the welcome screen +2. **Grant camera permission** if prompted +3. **Point the camera** at the QR code provided by your administrator +4. **Settings auto-populate** with server URL, username, and password +5. **Tap "Connect"** to verify and save the configuration + +### Manual Configuration + +1. **Tap "Manual Configuration"** on the welcome screen +2. **Enter Server URL**: `http://your-server-ip:8080` or `https://your-server-domain` +3. **Enter Username**: Your username provided by your administrator +4. **Enter Password**: Your password +5. **Tap "Test Connection"** to verify connectivity +6. **Tap "Save"** to store the configuration + +### First Login + +1. **After configuration**, you'll be prompted to log in +2. **Credentials should be pre-filled** (if using QR code) +3. **Tap "Login"** to authenticate +4. **Wait for authentication** - A token is stored locally for future sessions +5. **You'll be redirected** to the main app interface + +## Verification + +To verify that Formulus is installed correctly: + +1. **Check app icon** appears in your app drawer +2. **Open the app** and verify it launches without errors +3. **Check Settings** to confirm server configuration is saved +4. **Test connection** by tapping "Test Connection" in Settings +5. **Verify login** by logging in with your credentials + +## Troubleshooting Installation + +### Installation Fails + +**Problem**: APK installation fails with "App not installed" error. + +**Solutions**: +- Ensure you have enough storage space (at least 50 MB free) +- Check that "Unknown Sources" is enabled for your file manager +- Try downloading the APK again (file may be corrupted) +- Ensure your device meets minimum Android version requirements (7.0+) + +### App Crashes on Launch + +**Problem**: Formulus crashes immediately after opening. + +**Solutions**: +- Restart your device +- Clear app data: Settings → Apps → Formulus → Storage → Clear Data +- Uninstall and reinstall the app +- Check that your device has sufficient RAM available + +### Cannot Connect to Server + +**Problem**: App cannot connect to the Synkronus server. + +**Solutions**: +- Verify server URL is correct (check for typos) +- Ensure device has internet connection +- Check that server is running and accessible +- Verify firewall settings aren't blocking the connection +- For local development, use `10.0.2.2` instead of `localhost` on Android emulator + +### F-Droid Not Finding Updates + +**Problem**: F-Droid doesn't show Formulus updates. + +**Solutions**: +- Ensure the Formulus repository is added correctly +- Refresh F-Droid repositories: Settings → Repositories → Tap refresh +- Check that F-Droid has internet connection +- Verify repository URL is correct and accessible + +## Updating Formulus + +### Via F-Droid + +Updates are automatic when using F-Droid: + +1. **F-Droid checks for updates** automatically +2. **Notification appears** when updates are available +3. **Open F-Droid** and navigate to Updates tab +4. **Tap "Update"** next to Formulus +5. **Wait for download and installation** + +### Via Direct APK + +1. **Download the latest APK** from the releases page +2. **Install over existing installation** (no need to uninstall) +3. **App data is preserved** during update + +## Uninstalling Formulus + +To uninstall Formulus: + +1. **Go to Settings** → **Apps** (or **Application Manager**) +2. **Find Formulus** in the app list +3. **Tap on Formulus** +4. **Tap "Uninstall"** +5. **Confirm uninstallation** + +**Note**: Uninstalling will remove all local data, including: +- Saved observations (not yet synced) +- App configuration +- Cached app bundles +- Local database + +**Important**: Ensure all data is synced to the server before uninstalling. + +## Related Documentation + +- [Formulus Features](/using/formulus-features) - Learn about app features and usage +- [Your First Form](/using/your-first-form) - Get started with data collection +- [Synchronization](/using/synchronization) - Understand how data syncs work +- [Development Installation](/development/formulus-development) - For developers building from source + diff --git a/versioned_docs/version-1.0/getting-started/key-concepts.md b/versioned_docs/version-1.0/getting-started/key-concepts.md new file mode 100644 index 0000000..4a96564 --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/key-concepts.md @@ -0,0 +1,102 @@ +--- +sidebar_position: 3 +--- + +# Key Concepts + +Understanding these core concepts will help you work effectively with ODE. + +## Core Concepts + +### Forms + +Forms are the primary mechanism for data collection in ODE. A form consists of: + +- **Schema**: Defines the data structure and validation rules +- **UI Schema**: Defines how the form is presented to users +- **Question Types**: Define the input methods available (text, number, date, etc.) + +Forms are defined using JSON and follow the JSON Forms specification. See the [Form Design guide](/guides/forms/overview) for details. + +### Observations + +An observation is a single data record collected through a form. Each observation contains: + +- A unique identifier +- The form type used to collect it +- The data values entered by the user +- Metadata such as creation time and last modification time +- A sync status indicating whether it has been synchronized with the server + +### Synchronization + +Synchronization is the process of exchanging data between mobile devices and the server. ODE uses a bidirectional sync protocol that: + +- Pushes local observations to the server +- Pulls new or updated observations from the server +- Resolves conflicts when the same observation is modified on multiple devices +- Handles attachments separately from observation metadata + +See [Synchronization](/using/synchronization) for more details. + +### App Bundles + +An app bundle is a collection of resources that define a custom application. It includes: + +- Custom HTML, CSS, and JavaScript files +- Form specifications +- Custom renderers for question types +- Configuration files + +App bundles are uploaded to the server and downloaded by mobile devices during synchronization. See [Custom Applications](/guides/custom-apps/overview) for details. + +### Custom Applications + +Custom applications are web-based interfaces that run within the Formulus mobile app. They provide: + +- Custom navigation and user interfaces +- Integration with the ODE form system +- Access to observation data through the Formulus JavaScript interface + +Custom applications are defined in app bundles and can be tailored to specific use cases. + +## Data Flow + +The following diagram illustrates how data flows through the ODE system: + +``` +User Input → Form → Observation (Local) → Sync → Server → Database + ↓ + Sync → Other Devices +``` + +1. User fills out a form on a mobile device +2. An observation is created and stored locally +3. When connectivity is available, the observation is synchronized to the server +4. The server stores the observation in the database +5. Other devices can pull the observation during their sync operations + +## Terminology + +| Term | Definition | +|------|------------| +| **Form** | A data collection interface defined by schema and UI schema | +| **Observation** | A single data record collected through a form | +| **Schema** | JSON schema defining the structure and validation rules for a form | +| **UI Schema** | JSON schema defining the presentation of form fields | +| **Question Type** | A component that handles a specific type of input (text, number, etc.) | +| **Renderer** | A component that renders a question type in the form | +| **Sync** | The process of exchanging data between devices and server | +| **App Bundle** | A collection of resources defining a custom application | +| **Custom App** | A web-based application that runs within Formulus | +| **Formulus** | The mobile application component of ODE | +| **Synkronus** | The server component of ODE | +| **Formplayer** | The web-based form rendering component | + +## Related Documentation + +- [Form Design Guide](/guides/forms/overview) +- [Synchronization Details](/using/synchronization) +- [Custom Applications](/guides/custom-apps/overview) +- [Architecture Overview](/development/architecture/overview) + diff --git a/versioned_docs/version-1.0/getting-started/quick-start.md b/versioned_docs/version-1.0/getting-started/quick-start.md new file mode 100644 index 0000000..ede1381 --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/quick-start.md @@ -0,0 +1,195 @@ +--- +sidebar_position: 5 +--- + +# Quick Start + +Get up and running with ODE in approximately 15 minutes. + +## Overview + +This guide walks you through setting up a complete ODE environment and creating your first form. + +## Step 1: Start Synkronus Server + +Using Docker Compose is the fastest method: + +```bash +# Clone the repository +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus + +# Start the server with Docker Compose +docker compose up -d +``` + +The server will be available at `http://localhost:8080`. + +## Step 2: Install Formulus App + + + + +Download and install the APK from the [releases page](https://github.com/OpenDataEnsemble/ode/releases), or build from source: + +```bash +cd ode/formulus +npm install +npm run android +``` + + + + +Build from source (requires macOS and Xcode): + +```bash +cd ode/formulus +npm install +cd ios && bundle install && bundle exec pod install && cd .. +npm run ios +``` + + + + +## Step 3: Configure the App + +1. Open the Formulus app +2. Navigate to Settings +3. Enter server URL: `http://your-server-ip:8080` (or `http://localhost:8080` for emulator) +4. Enter your credentials (create a user account first if needed) +5. Save the configuration + +## Step 4: Create Your First Form + +Forms are defined using JSON schema. Create a simple form: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] +} +``` + +Upload this form to your Synkronus server using the API or CLI tool. + +## Step 5: Collect Data + +1. Open the Formulus app +2. Navigate to your form +3. Fill out the form fields +4. Submit the observation +5. The data will be stored locally and synchronized to the server + +## Step 6: Verify Data Collection + +Check that your observation was created: + + + + +```bash +curl http://localhost:8080/api/observations \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + + + + +```bash +synk observations list +``` + + + + +1. Navigate to the Portal +2. Go to "Observations" +3. View your submitted observations + + + + +## Next Steps + +Now that you have a working setup: + +- Learn about [form design](/guides/form-design) to create more complex forms +- Explore [custom applications](/guides/custom-applications) for specialized workflows +- Review the [API reference](/reference/api) for integration options +- Read about [synchronization](/using/synchronization) to understand data flow + +## Troubleshooting + +### Server Not Accessible + +If the mobile app cannot connect to the server: + + + + +Verify the server is running: `curl http://localhost:8080/health` + +Check firewall settings + +Use your machine's IP address: `http://192.168.1.100:8080` + +Ensure device and server are on the same network + + + + +Use `10.0.2.2` instead of `localhost`: `http://10.0.2.2:8080` + +Verify the server is running on the host machine + +Check that port 8080 is accessible + + + + +Use `localhost` or your machine's IP address: `http://localhost:8080` + +Verify the server is running + +Check firewall settings + + + + +### Forms Not Appearing + +If forms don't appear in the app: + +- Verify the form was uploaded correctly +- Check that the app has synchronized with the server +- Review server logs for errors + +### Synchronization Issues + +If data is not synchronizing: + +- Check network connectivity +- Verify authentication credentials +- Review server logs for sync errors +- Ensure the observation was saved locally before sync + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [Your First Form](/using/your-first-form) +- [Form Design Guide](/guides/form-design) + diff --git a/versioned_docs/version-1.0/getting-started/what-is-ode.md b/versioned_docs/version-1.0/getting-started/what-is-ode.md new file mode 100644 index 0000000..01fea78 --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/what-is-ode.md @@ -0,0 +1,92 @@ +--- +sidebar_position: 1 +--- + +# What is ODE? + +Open Data Ensemble (ODE) is a platform designed to simplify mobile data collection and management. It provides tools and infrastructure for creating forms, collecting data in the field, and synchronizing information across devices and servers. + +## Overview + +ODE addresses the challenges of data collection in environments where connectivity is unreliable or intermittent. The platform is built with an offline-first architecture, ensuring that data collection continues regardless of network availability. + +## Problem Statement + +Traditional data collection solutions often require constant internet connectivity, making them unsuitable for field work in remote areas. ODE solves this by: + +- Storing data locally on mobile devices +- Synchronizing data when connectivity is available +- Resolving conflicts automatically when multiple devices modify the same data +- Providing a flexible form design system that adapts to various use cases + +## Key Features + +### Offline-First Design + +All data is stored locally on the device using WatermelonDB, a reactive database optimized for React Native. This ensures that data collection continues even when the device is offline. + +### Flexible Form System + +Forms are defined using JSON schema, following the JSON Forms specification. This allows for complex validation rules, conditional logic, and custom question types. + +### Reliable Synchronization + +The synchronization protocol handles conflicts, ensures data integrity, and supports incremental updates to minimize bandwidth usage. + +### Cross-Platform Support + +ODE applications run on Android and iOS devices, with a web-based form player for preview and testing. + +### Extensible Architecture + +The platform supports custom applications and renderers, allowing organizations to tailor the user experience to their specific needs. + +## Use Cases + +ODE is suitable for various data collection scenarios: + +| Use Case | Description | +|----------|-------------| +| **Health Surveys** | Collect patient data, medical records, and health indicators | +| **Research Studies** | Gather research data in field conditions | +| **Monitoring & Evaluation** | Track program outcomes and indicators | +| **Asset Management** | Inventory and track assets in the field | +| **Quality Assurance** | Conduct inspections and quality checks | + +## Architecture Overview + +ODE follows a client-server architecture: + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formulus │◄───────►│ Synkronus │◄───────►│ Formulus │ +│ (Mobile) │ Sync │ (Server) │ Sync │ (Mobile) │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formplayer │ │ Database │ │ Formplayer │ +│ (WebView) │ │ (PostgreSQL) │ │ (WebView) │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +The mobile application (Formulus) communicates with the server (Synkronus) to synchronize data. Forms are rendered using the Formplayer component, which can be embedded in custom applications. + +## Technology Stack + +ODE is built using modern, open-source technologies: + +- **React Native** for mobile applications +- **React** for web-based form rendering +- **Go** for the backend server +- **PostgreSQL** for data storage +- **WatermelonDB** for local data storage on mobile devices +- **JSON Forms** for form rendering and validation + +## Next Steps + +- Learn about [Why ODE?](/getting-started/why-ode) to understand the benefits +- Review [Key Concepts](/getting-started/key-concepts) to understand the terminology +- Follow the [Installation guide](/getting-started/installation/prerequisites) to set up your environment + diff --git a/versioned_docs/version-1.0/getting-started/why-ode.md b/versioned_docs/version-1.0/getting-started/why-ode.md new file mode 100644 index 0000000..ca3d2de --- /dev/null +++ b/versioned_docs/version-1.0/getting-started/why-ode.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 2 +--- + +# Why ODE? + +ODE provides several advantages over traditional data collection solutions, particularly for organizations working in challenging environments with unreliable connectivity. + +## Advantages + +### Offline-First Architecture + +Unlike many data collection platforms that require constant connectivity, ODE is designed to work offline. Data is stored locally and synchronized when connectivity is available, ensuring that field work is never interrupted by network issues. + +### Conflict Resolution + +When multiple devices modify the same data, ODE automatically resolves conflicts using a version-based approach. This ensures data integrity without requiring manual intervention. + +### Flexible Form Design + +The JSON-based form system allows for complex validation rules, conditional logic, and custom question types. Forms can be updated without requiring application updates, enabling rapid iteration and adaptation. + +### Open Source + +ODE is fully open source, allowing organizations to audit the code, customize the platform, and contribute improvements. This transparency builds trust and enables community-driven development. + +### Cross-Platform Support + +Applications built with ODE run on both Android and iOS devices, reducing the need to maintain separate codebases for different platforms. + +### Extensible + +The platform supports custom applications and renderers, allowing organizations to create specialized workflows and user interfaces that match their specific needs. + +## Comparison with Alternatives + +| Feature | ODE | Traditional Solutions | +|--------|-----|----------------------| +| Offline Support | Full offline functionality | Limited or none | +| Conflict Resolution | Automatic | Manual or none | +| Form Updates | Without app updates | Requires app updates | +| Customization | High flexibility | Limited | +| Open Source | Yes | Often proprietary | +| Cost | Free and open source | Licensing fees | + +## When to Use ODE + +ODE is particularly well-suited for: + +- Organizations conducting field research or data collection in remote areas +- Projects requiring complex forms with validation and conditional logic +- Teams needing to customize the user experience +- Organizations prioritizing data privacy and security +- Projects requiring offline functionality + +## When to Consider Alternatives + +ODE may not be the best fit if: + +- Your use case requires real-time collaboration features +- You need extensive third-party integrations out of the box +- Your team lacks technical resources for deployment and maintenance +- Your data collection needs are very simple and don't require offline support + +## Next Steps + +- Review [Key Concepts](/getting-started/key-concepts) to understand ODE terminology +- Check [Prerequisites](/getting-started/installation/prerequisites) for system requirements +- Follow the [Installation guide](/getting-started/installation/quick-start) to get started + diff --git a/versioned_docs/version-1.0/guides/.gitkeep b/versioned_docs/version-1.0/guides/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/guides/configuration.md b/versioned_docs/version-1.0/guides/configuration.md new file mode 100644 index 0000000..cc29d14 --- /dev/null +++ b/versioned_docs/version-1.0/guides/configuration.md @@ -0,0 +1,360 @@ +--- +sidebar_position: 4 +--- + +# Configuration + +Complete configuration guide for ODE components including server settings, client settings, and environment variables. + +## Server Configuration (Synkronus) + +Synkronus is configured using environment variables. You can use either environment variables directly or a `.env` file for local development. + +### Configuration File Locations + +For local development, create a `.env` file in one of these locations (searched in order): + +1. Current working directory (where you run the command from) +2. Same directory as the executable +3. Parent directory of the executable + +### Required Configuration + +| Variable | Description | Example | +|----------|-------------|---------| +| `DB_CONNECTION` | PostgreSQL connection string | `postgres://user:password@localhost:5432/synkronus?sslmode=disable` | +| `JWT_SECRET` | Secret key for JWT token signing (minimum 32 characters) | Generate with `openssl rand -base64 32` | + +### Optional Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8080` | HTTP server port | +| `LOG_LEVEL` | `info` | Logging level: `debug`, `info`, `warn`, or `error` | +| `APP_BUNDLE_PATH` | `./data/app-bundles` | Directory path for app bundle storage | +| `MAX_VERSIONS_KEPT` | `5` | Maximum number of app bundle versions to retain | +| `ADMIN_USERNAME` | `admin` | Initial admin username | +| `ADMIN_PASSWORD` | `admin` | Initial admin password (must be changed in production) | + +### Example Configuration File + +```bash +# Server Configuration +PORT=8080 +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=info +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 + +# Admin Configuration +ADMIN_USERNAME=admin +ADMIN_PASSWORD=change-this-password +``` + +### Database Connection String Format + +The `DB_CONNECTION` string follows PostgreSQL connection URI format: + +``` +postgres://[user[:password]@][host][:port][/database][?param1=value1&...] +``` + +**Components:** +- `user`: Database username +- `password`: Database password +- `host`: Database hostname or IP address +- `port`: Database port (default: 5432) +- `database`: Database name +- `sslmode`: SSL mode (`disable`, `require`, `verify-full`, etc.) + +**Examples:** + +```bash +# Local development +DB_CONNECTION=postgres://synkronus:password@localhost:5432/synkronus?sslmode=disable + +# Remote database +DB_CONNECTION=postgres://synkronus:password@db.example.com:5432/synkronus?sslmode=require + +# Docker Compose +DB_CONNECTION=postgres://synkronus:password@postgres:5432/synkronus?sslmode=disable +``` + +## Client Configuration (Formulus) + +The Formulus mobile app is configured through the app settings interface. + +### Server URL + +Enter the URL of your Synkronus server: + + + + +**Physical Device**: `http://192.168.1.100:8080` (your machine's IP address) + +**Android Emulator**: `http://10.0.2.2:8080` (special IP for emulator) + +**iOS Simulator**: `http://localhost:8080` or your machine's IP address + + + + +**HTTPS URL**: `https://synkronus.your-domain.com` + +**Custom Port**: `https://synkronus.your-domain.com:8443` (if using non-standard port) + + + + +### Authentication + +Configure authentication credentials: + +1. **Username**: Your user account username +2. **Password**: Your user account password +3. **Server URL**: As described above + +The app stores credentials securely and uses them for API authentication. + +### Sync Settings + +Configure synchronization behavior: + +- **Auto-sync**: Enable automatic synchronization when connectivity is available +- **Sync interval**: How often to check for sync (default: every 15 minutes) +- **Sync on app start**: Automatically sync when the app is opened +- **Sync on observation save**: Sync immediately after saving an observation + +## Synkronus CLI Configuration + +The Synkronus CLI uses a configuration file located at `$HOME/.synkronus.yaml` by default. + +### Configuration File Format + +```yaml +api: + url: http://localhost:8080 + version: 1.0.0 +``` + +### Multiple Endpoints + +You can manage multiple endpoint configurations: + +```bash +# Create separate config files +synk config init -o ~/.synkronus-dev.yaml +synk config init -o ~/.synkronus-prod.yaml + +# Point CLI at dev by default +synk config use ~/.synkronus-dev.yaml + +# Point CLI at prod by default +synk config use ~/.synkronus-prod.yaml + +# Temporarily override for a single command +synk --config ~/.synkronus-dev.yaml status +``` + +### Authentication + +The CLI stores authentication tokens in the configuration file after login: + +```bash +# Login to the API +synk login --username your-username + +# Check authentication status +synk status + +# Logout +synk logout +``` + +## Docker Configuration + +### Docker Compose Configuration + +When using Docker Compose, configuration is set in the `docker-compose.yml` file: + +```yaml +services: + synkronus: + image: ghcr.io/opendataensemble/synkronus:latest + environment: + PORT: 8080 + DB_CONNECTION: postgres://synkronus:password@postgres:5432/synkronus?sslmode=disable + JWT_SECRET: your-secret-key + LOG_LEVEL: info + APP_BUNDLE_PATH: /app/data/app-bundles + MAX_VERSIONS_KEPT: 5 + volumes: + - app-bundles:/app/data/app-bundles + depends_on: + - postgres +``` + +### Environment File + +You can also use a `.env` file with Docker Compose: + +```bash +# .env file +DB_CONNECTION=postgres://synkronus:password@postgres:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key +LOG_LEVEL=info +``` + +Reference in `docker-compose.yml`: + +```yaml +services: + synkronus: + env_file: + - .env +``` + +## Security Configuration + +### JWT Secret + +Generate a strong JWT secret: + +```bash +# Using OpenSSL +openssl rand -base64 32 + +# Using PowerShell (Windows) +[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })) +``` + +**Important:** Use a different secret for each environment (development, staging, production). + +### Database Passwords + +Generate strong database passwords: + +```bash +# Generate random password +openssl rand -base64 24 +``` + +### Admin Password + +Change the default admin password immediately after deployment: + +```bash +# Via API +curl -X POST https://your-server.com/users/change-password \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"current_password":"admin","new_password":"new-secure-password"}' +``` + +## Logging Configuration + +### Log Levels + +| Level | Description | Use Case | +|-------|-------------|----------| +| `debug` | Detailed diagnostic information | Development, troubleshooting | +| `info` | General informational messages | Production (default) | +| `warn` | Warning messages | Production | +| `error` | Error messages only | Production (minimal logging) | + +### Log Output + +Logs are written to standard output (stdout) and can be captured by: + +- Docker logging drivers +- Systemd journal +- Log aggregation services (e.g., Fluentd, Logstash) + +### Docker Logging + +Configure Docker logging in `docker-compose.yml`: + +```yaml +services: + synkronus: + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" +``` + +## Performance Configuration + +### PostgreSQL Settings + +Optimize PostgreSQL for your workload: + +```yaml +services: + postgres: + command: + - "postgres" + - "-c" + - "max_connections=100" + - "-c" + - "shared_buffers=256MB" + - "-c" + - "effective_cache_size=1GB" +``` + +### Resource Limits + +Set resource limits in Docker Compose: + +```yaml +services: + synkronus: + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + reservations: + cpus: '0.5' + memory: 256M +``` + +## Configuration Validation + +### Verify Configuration + +Test your configuration: + +```bash +# Check environment variables are set +docker compose config + +# Test database connection +docker compose exec synkronus sh -c 'apk add postgresql-client && psql "$DB_CONNECTION"' + +# Check server health +curl http://localhost:8080/health +``` + +### Common Configuration Issues + +**Problem**: Database connection fails + +**Solution**: Verify `DB_CONNECTION` string format and database accessibility. + +**Problem**: JWT authentication fails + +**Solution**: Ensure `JWT_SECRET` is at least 32 characters and matches across all instances. + +**Problem**: App bundles not persisting + +**Solution**: Verify `APP_BUNDLE_PATH` is set and volume is properly mounted. + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [Deployment Guide](/guides/deployment) +- [API Reference](/reference/api) diff --git a/versioned_docs/version-1.0/guides/custom-applications.md b/versioned_docs/version-1.0/guides/custom-applications.md new file mode 100644 index 0000000..9055a4d --- /dev/null +++ b/versioned_docs/version-1.0/guides/custom-applications.md @@ -0,0 +1,196 @@ +--- +sidebar_position: 2 +--- + +# Custom Applications + +Complete guide to building and deploying custom applications that integrate with ODE. + +## Overview + +Custom applications are web-based interfaces that run within the Formulus mobile app, providing specialized workflows and user experiences. They allow you to create custom navigation, integrate with the ODE form system, and build specialized interfaces for specific use cases. + +## How Custom Applications Work + +Custom applications are defined in app bundles, which include: + +- HTML, CSS, and JavaScript files +- Form specifications +- Custom renderers for question types +- Configuration files + +The app bundle is uploaded to the Synkronus server and downloaded by mobile devices during synchronization. When a user opens a custom application, it runs in a WebView within the Formulus app. + +## Formulus JavaScript Interface + +Custom applications interact with Formulus through a JavaScript interface. The API is injected automatically when your app loads. + +### Getting the Formulus API + +Always use the `getFormulus()` helper function to ensure the API is ready: + +```html + + + + +``` + +### Available Methods + +The Formulus interface provides methods for: + +- **Form Operations**: Create, edit, and delete observations +- **Data Access**: Query observations and form specifications +- **Device Features**: Access camera, GPS, file system, etc. +- **Synchronization**: Trigger sync operations + +### Creating Observations + +```javascript +// Create a new observation +await formulus.addObservation(formType, initializationData); +``` + +### Editing Observations + +```javascript +// Edit an existing observation +await formulus.editObservation(formType, observationId); +``` + +### Deleting Observations + +```javascript +// Delete an observation +await formulus.deleteObservation(formType, observationId); +``` + +## App Bundle Structure + +An app bundle is a ZIP file containing: + +``` +app-bundle/ +├── index.html # Main entry point +├── assets/ +│ ├── css/ +│ ├── js/ +│ └── images/ +├── forms/ # Form specifications (optional) +└── manifest.json # Bundle metadata +``` + +### Manifest File + +The manifest defines bundle metadata: + +```json +{ + "version": "1.0.0", + "name": "My Custom App", + "description": "Description of the app", + "entryPoint": "index.html" +} +``` + +## Building Custom Applications + +### Development Setup + +1. **Reference the API**: Copy `formulus-api.js` into your project for autocompletion +2. **Include the Load Script**: Add `formulus-load.js` to your HTML +3. **Use getFormulus()**: Always await the API before using it + +### Example Application + +```html + + + + My Custom App + + + +

    My Custom App

    + + + + + +``` + +## Deployment + +### Uploading App Bundles + +Upload app bundles using the Synkronus CLI: + +```bash +synk app-bundle upload bundle.zip --activate +``` + +Or use the API: + +```bash +curl -X POST http://your-server:8080/api/app-bundle/upload \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "bundle=@bundle.zip" +``` + +### Version Management + +App bundles support versioning: + +- Each upload creates a new version +- Versions are identified by timestamp +- Switch between versions using the CLI or API +- Mobile devices download the active version during sync + +## Custom Renderers + +Custom applications can include custom renderers for question types. See the [Form Design guide](/guides/form-design) for details on creating custom renderers. + +## Best Practices + +1. **Wait for API**: Always use `getFormulus()` before accessing the API +2. **Error Handling**: Implement proper error handling for all API calls +3. **Offline Support**: Design apps to work gracefully when offline +4. **Performance**: Optimize for mobile devices and slower networks +5. **Testing**: Test thoroughly in both development and production environments + +## Related Documentation + +- [Form Design Guide](/guides/form-design) +- [Formulus Interface Documentation](https://github.com/OpenDataEnsemble/ode/tree/main/formulus/src/webview) +- [App Bundle Format Reference](/reference/app-bundle-format) + diff --git a/versioned_docs/version-1.0/guides/deployment.md b/versioned_docs/version-1.0/guides/deployment.md new file mode 100644 index 0000000..b72d7e0 --- /dev/null +++ b/versioned_docs/version-1.0/guides/deployment.md @@ -0,0 +1,560 @@ +--- +sidebar_position: 3 +--- + +# Deployment + +Complete guide to deploying ODE in production environments using Docker and Docker Compose. + +## Overview + +ODE can be deployed using Docker containers, which simplifies deployment and ensures consistency across environments. This guide covers production deployment with Docker Compose, including PostgreSQL, Nginx reverse proxy, and optional Cloudflared tunnel for secure external access. + +## Recommended Production Setup + +For production deployment, we recommend: + +- **Clean Linux server** (Ubuntu 22.04 LTS or Debian 12) +- **Docker & Docker Compose** installed +- **Cloudflared tunnel** for secure external access (no port forwarding needed) +- **PostgreSQL** database (dockerized via docker-compose) +- **Nginx** reverse proxy (included in docker-compose) +- **Persistent volumes** for data storage + +## Quick Start + +### Server Preparation + + + + +```bash +# Update system +sudo apt update && sudo apt upgrade -y + +# Install Docker +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh + +# Install Docker Compose +sudo apt install docker-compose-plugin -y + +# Verify installation +docker --version +docker compose version +``` + + + + +```bash +# Install Docker Desktop from https://www.docker.com/products/docker-desktop +# Or use Homebrew: +brew install --cask docker + +# Docker Compose is included with Docker Desktop +# Verify installation +docker --version +docker compose version +``` + + + + +1. **Install Docker Desktop** from [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop) +2. **Docker Compose is included** with Docker Desktop +3. **Verify installation** (PowerShell): + ```powershell + docker --version + docker compose version + ``` + + + + +### Deploy Synkronus + +```bash +# Create deployment directory +mkdir -p ~/synkronus +cd ~/synkronus + +# Download configuration files +wget https://raw.githubusercontent.com/opendataensemble/ode/main/synkronus/docker-compose.example.yml -O docker-compose.yml +wget https://raw.githubusercontent.com/opendataensemble/ode/main/synkronus/nginx.conf + +# Generate secure secrets +JWT_SECRET=$(openssl rand -base64 32) +DB_ROOT_PASSWORD=$(openssl rand -base64 24) +ADMIN_PASSWORD=$(openssl rand -base64 16) + +# Update docker-compose.yml with secrets +sed -i "s/CHANGE_THIS_PASSWORD/$DB_ROOT_PASSWORD/g" docker-compose.yml +sed -i "s/CHANGE_THIS_TO_RANDOM_32_CHAR_STRING/$JWT_SECRET/g" docker-compose.yml +sed -i "s/CHANGE_THIS_ADMIN_PASSWORD/$ADMIN_PASSWORD/g" docker-compose.yml + +# Start the stack +docker compose up -d + +# Verify it's running +curl http://localhost/health +``` + +### Database Setup + +Create a database and user for Synkronus: + + + + +```bash +# Open a psql shell into the Postgres container +docker compose exec postgres psql -U postgres +``` + +From the `psql` prompt: + +```sql +-- Create role and database +CREATE ROLE synkronus_user LOGIN PASSWORD 'CHANGE_THIS_APP_PASSWORD'; +CREATE DATABASE synkronus OWNER synkronus_user; +``` + + + + +```bash +# Connect to local PostgreSQL +psql -U postgres +``` + +From the `psql` prompt: + +```sql +-- Create role and database +CREATE ROLE synkronus_user LOGIN PASSWORD 'CHANGE_THIS_APP_PASSWORD'; +CREATE DATABASE synkronus OWNER synkronus_user; +``` + + + + +Update the `synkronus` service `DB_CONNECTION` in `docker-compose.yml`: + +```yaml +services: + synkronus: + environment: + DB_CONNECTION: "postgres://synkronus_user:CHANGE_THIS_APP_PASSWORD@postgres:5432/synkronus?sslmode=disable" +``` + +## Using Pre-built Images + +Pre-built images are automatically published to GitHub Container Registry (GHCR) via CI/CD. + +### Pull the Latest Image + +```bash +docker pull ghcr.io/opendataensemble/synkronus:latest +``` + +### Available Tags + +| Tag | Description | +|-----|-------------| +| `latest` | Latest stable release from main branch | +| `v1.0.0` | Specific version tags | +| `develop` | Development branch (pre-release) | +| `feature-xyz` | Feature branches (pre-release) | + +### Run Pre-built Image + +```bash +docker run -d \ + --name synkronus \ + -p 8080:8080 \ + -e DB_CONNECTION="postgres://user:password@host:5432/synkronus" \ + -e JWT_SECRET="your-secret-key" \ + -e APP_BUNDLE_PATH="/app/data/app-bundles" \ + -v synkronus-bundles:/app/data/app-bundles \ + ghcr.io/opendataensemble/synkronus:latest +``` + +## Cloudflared Tunnel Setup + +Cloudflared provides secure external access without exposing ports or managing SSL certificates. + +### Install Cloudflared + +```bash +# Download and install +wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb +sudo dpkg -i cloudflared-linux-amd64.deb + +# Verify installation +cloudflared --version +``` + +### Create Tunnel + +```bash +# Login to Cloudflare +cloudflared tunnel login + +# Create tunnel +cloudflared tunnel create synkronus + +# Note the tunnel ID from the output +``` + +### Configure Tunnel + +Create `~/.cloudflared/config.yml`: + +```yaml +tunnel: +credentials-file: /root/.cloudflared/.json + +ingress: + - hostname: synkronus.your-domain.com + service: http://localhost:80 + - service: http_status:404 +``` + +### Route DNS + +```bash +# Route your domain to the tunnel +cloudflared tunnel route dns synkronus synkronus.your-domain.com +``` + +### Run Tunnel as Service + +```bash +# Install as systemd service +sudo cloudflared service install + +# Start service +sudo systemctl start cloudflared +sudo systemctl enable cloudflared + +# Check status +sudo systemctl status cloudflared +``` + +Your Synkronus instance is now accessible at `https://synkronus.your-domain.com` with automatic SSL. + +## Environment Variables + +### Required Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `DB_CONNECTION` | PostgreSQL connection string | `postgres://user:pass@postgres:5432/synkronus` | +| `JWT_SECRET` | Secret key for JWT token signing | Generate with `openssl rand -base64 32` | + +### Optional Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8080` | HTTP server port | +| `LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) | +| `APP_BUNDLE_PATH` | `/app/data/app-bundles` | Path for app bundle storage | +| `MAX_VERSIONS_KEPT` | `5` | Number of app bundle versions to retain | +| `ADMIN_USERNAME` | `admin` | Initial admin username | +| `ADMIN_PASSWORD` | `admin` | Initial admin password (CHANGE THIS!) | + +## Volume Management + +### Persistent Volumes + +The docker-compose setup creates persistent volumes: + +1. **postgres-data**: PostgreSQL database files +2. **app-bundles**: Uploaded application bundles + +```bash +# List volumes +docker volume ls + +# Inspect volume +docker volume inspect synkronus_postgres-data + +# Backup volume +docker run --rm -v synkronus_postgres-data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz /data + +# Restore volume +docker run --rm -v synkronus_postgres-data:/data -v $(pwd):/backup alpine tar xzf /backup/postgres-backup.tar.gz -C / +``` + +### App Bundle Directory Permissions + +When bind-mounting a host directory for `app-bundles`, ensure proper permissions. The container runs as user `synkronus` with `uid=1000` and `gid=1000`: + +```bash +# Fix permissions on host directory +sudo chown -R 1000:1000 ~/server/app-bundles + +# Restart after fixing permissions +docker compose restart synkronus +``` + +## Monitoring and Maintenance + +### View Logs + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f synkronus +docker compose logs -f postgres +docker compose logs -f nginx + +# Last 100 lines +docker compose logs --tail=100 synkronus +``` + +### Health Checks + +```bash +# Check service status +docker compose ps + +# Test health endpoint +curl http://localhost/health + +# Via cloudflared tunnel +curl https://synkronus.your-domain.com/health +``` + +### Restart Services + +```bash +# Restart all services +docker compose restart + +# Restart specific service +docker compose restart synkronus + +# Reload nginx configuration +docker compose exec nginx nginx -s reload +``` + +### Update to Latest Version + +```bash +# Pull latest image +docker compose pull + +# Recreate containers with new image +docker compose up -d + +# Remove old images +docker image prune -f +``` + +## Backup and Restore + +### Database Backup + +```bash +# Create backup +docker compose exec postgres pg_dump -U synkronus_user synkronus > backup-$(date +%Y%m%d).sql + +# Automated daily backups (add to crontab) +0 2 * * * cd ~/synkronus && docker compose exec -T postgres pg_dump -U synkronus_user synkronus > /backups/synkronus-$(date +\%Y\%m\%d).sql +``` + +### Database Restore + +```bash +# Restore from backup +docker compose exec -T postgres psql -U synkronus_user synkronus < backup-20250114.sql +``` + +### Full System Backup + +```bash +# Backup everything +tar czf synkronus-full-backup-$(date +%Y%m%d).tar.gz \ + docker-compose.yml \ + nginx.conf \ + $(docker volume inspect synkronus_postgres-data --format '{{ .Mountpoint }}') \ + $(docker volume inspect synkronus_app-bundles --format '{{ .Mountpoint }}') +``` + +## Security Best Practices + +### 1. Use Strong Secrets + +```bash +# Generate strong JWT secret +openssl rand -base64 32 + +# Generate strong passwords +openssl rand -base64 24 +``` + +### 2. Change Default Admin Password + +After first deployment, change the admin password via API or CLI. + +### 3. Regular Updates + +```bash +# Update system packages +sudo apt update && sudo apt upgrade -y + +# Update Docker images +docker compose pull +docker compose up -d +``` + +### 4. Firewall Configuration + +If not using Cloudflared, configure firewall: + +```bash +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +``` + +## Performance Tuning + +### PostgreSQL Optimization + +Add to `docker-compose.yml` under postgres service: + +```yaml +command: + - "postgres" + - "-c" + - "max_connections=100" + - "-c" + - "shared_buffers=256MB" + - "-c" + - "effective_cache_size=1GB" +``` + +### Resource Limits + +Add to `docker-compose.yml` under each service: + +```yaml +deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + reservations: + cpus: '0.5' + memory: 256M +``` + +## Architecture + +The deployment architecture includes: + +``` +┌─────────────────────────────────────────┐ +│ Cloudflared Tunnel │ +│ (Optional - Cloudflare) │ +│ Automatic SSL/TLS │ +└──────────────┬──────────────────────────┘ + │ HTTPS + ▼ +┌─────────────────────────────────────────┐ +│ Nginx Reverse Proxy │ +│ Port 80/443 │ +│ - Load balancing │ +│ - Request routing │ +│ - Compression │ +└──────────────┬──────────────────────────┘ + │ HTTP + ▼ +┌─────────────────────────────────────────┐ +│ Synkronus Container │ +│ Port 8080 (internal) │ +│ - API endpoints │ +│ - Business logic │ +│ - File storage │ +└──────────────┬──────────────────────────┘ + │ PostgreSQL protocol + ▼ +┌─────────────────────────────────────────┐ +│ PostgreSQL Database │ +│ Port 5432 (internal) │ +│ - Data persistence │ +│ - Transactions │ +└─────────────────────────────────────────┘ +``` + +## Troubleshooting + +### Service Won't Start + +```bash +# Check logs +docker compose logs synkronus + +# Check environment variables +docker compose config + +# Verify database connection +docker compose exec synkronus sh +# Inside container: +apk add postgresql-client +psql "$DB_CONNECTION" +``` + +### Database Connection Issues + +```bash +# Check PostgreSQL is running +docker compose ps postgres + +# Check PostgreSQL logs +docker compose logs postgres + +# Test connection from synkronus container +docker compose exec synkronus sh -c 'apk add postgresql-client && psql "$DB_CONNECTION"' +``` + +### Nginx Issues + +```bash +# Test nginx configuration +docker compose exec nginx nginx -t + +# Reload nginx +docker compose exec nginx nginx -s reload + +# Check nginx logs +docker compose logs nginx +``` + +## Production Checklist + +Before going live: + +- [ ] Strong JWT secret generated +- [ ] Strong database password set +- [ ] Admin password changed from default +- [ ] Cloudflared tunnel configured (or SSL certificates installed) +- [ ] Backup strategy implemented +- [ ] Monitoring configured +- [ ] Health checks passing +- [ ] Firewall configured (if not using Cloudflared) +- [ ] Resource limits set +- [ ] Log rotation configured +- [ ] Documentation reviewed +- [ ] Test deployment verified + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [Configuration Guide](/guides/configuration) +- [API Reference](/reference/api) diff --git a/versioned_docs/version-1.0/guides/form-design.md b/versioned_docs/version-1.0/guides/form-design.md new file mode 100644 index 0000000..0672c88 --- /dev/null +++ b/versioned_docs/version-1.0/guides/form-design.md @@ -0,0 +1,1362 @@ +--- +sidebar_position: 1 +--- + +# Form Design + +Complete guide to designing forms in ODE using JSON schema and JSON Forms. + +## Overview + +Forms in ODE are defined using JSON schema, following the JSON Forms specification. A form consists of two main components: + +1. **Schema**: Defines the data structure and validation rules +2. **UI Schema**: Defines how the form is presented to users + +## How Forms Work in ODE + +Understanding the mental model of forms in ODE is essential for effective form design. + +### What is a "Form" in ODE? + +A form in ODE is a structured data collection interface that consists of: + +- **JSON Schema**: Defines what data can be collected, its structure, types, and validation rules +- **UI Schema**: Defines how the form is presented to users, including layout, field ordering, and conditional visibility +- **Formplayer**: The rendering engine that interprets these schemas and creates the interactive form interface + +### The Role of JSON Schema + +JSON Schema serves as the **data contract** for your form: + +- **Structure Definition**: Defines the shape of data (properties, types, nesting) +- **Validation Rules**: Enforces data quality (required fields, ranges, formats) +- **Type Safety**: Ensures data types match expectations (string, number, boolean, etc.) + +JSON Schema follows the [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) specification, but ODE Formplayer intentionally supports a **safe, predictable subset** of JSON Schema features. + +### The Role of UI Schema + +UI Schema (JSON Forms UI Schema) controls the **presentation layer**: + +- **Layout**: How fields are arranged (vertical, horizontal, grouped, paginated) +- **Ordering**: The sequence in which fields appear +- **Conditional Logic**: When fields are shown or hidden +- **Field Configuration**: Labels, placeholders, and display options + +### The Role of Formplayer + +Formplayer is the React-based rendering engine that: + +- **Interprets Schemas**: Reads JSON Schema and UI Schema to understand form structure +- **Renders Components**: Creates interactive form elements (inputs, selects, media capture, etc.) +- **Validates Input**: Enforces schema validation rules in real-time +- **Manages State**: Tracks form data, validation errors, and user interactions + +### Why ODE Supports a Subset of JSON Schema + +**Key Message**: ODE Formplayer intentionally supports a safe, predictable subset of JSON Schema and JSON Forms to ensure: + +1. **Reliability**: Forms work consistently across all devices and scenarios +2. **Performance**: Complex schema features don't slow down form rendering +3. **Predictability**: Form behavior is deterministic and easy to reason about +4. **Mobile Optimization**: Features work well in resource-constrained mobile environments + +**Important**: Forms that use unsupported JSON Schema features may load but are **not guaranteed to work**. Always refer to the [Formplayer Supported Schema & UI Profile](/reference/formplayer#supported-schema--ui-profile) for the definitive list of supported features. + +## Basic Form Structure + +Here's a simple form example: + +```json +{ + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] + }, + "uischema": { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/age" + } + ] + } +} +``` + +## Schema Definition + +The schema defines the data structure and validation rules for your form. It follows the JSON Schema specification. + +### Property Types + +ODE supports various property types: + +| Type | Description | Example | +|------|-------------|---------| +| `string` | Text input | Name, description | +| `integer` | Whole number | Age, count | +| `number` | Decimal number | Weight, temperature | +| `boolean` | True/false value | Consent, agreement | +| `array` | List of items | Multiple selections | +| `object` | Nested object | Complex data structures | + +### Validation Rules + +You can add validation rules to properties: + +```json +{ + "type": "string", + "title": "Email", + "format": "email", + "minLength": 5, + "maxLength": 100 +} +``` + +Common validation rules: + +- `minimum` / `maximum`: For numbers +- `minLength` / `maxLength`: For strings +- `pattern`: Regular expression pattern +- `format`: Predefined formats (email, date, etc.) +- `enum`: List of allowed values + +## Designing UI Schemas for ODE Formplayer + +The UI schema defines how form fields are presented to users. It controls layout, ordering, and presentation. ODE Formplayer has specific requirements and best practices for UI schema design. + +### Required Layout Structure + +**All ODE forms must use `SwipeLayout` as the root element.** This enables pagination and swipe navigation between form sections. + +#### SwipeLayout (Required Root) + +`SwipeLayout` is the root layout type that enables multi-page forms with swipe navigation. It automatically wraps other layout types if not explicitly specified. + +**Required Structure:** +```json +{ + "type": "SwipeLayout", + "elements": [ + // Each element becomes a swipeable page + ] +} +``` + +**Key Characteristics:** +- **Root Element**: Must be the top-level element in your UI schema +- **Pagination**: Each element in `elements[]` becomes a separate page +- **Swipe Navigation**: Users can swipe left/right to navigate between pages +- **Progress Tracking**: Shows progress bar indicating current page +- **Auto-wrapping**: If root is not SwipeLayout, Formplayer automatically wraps it + +**Safe Example:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/age" + } + ] + }, + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/email" + } + ] + } + ] +} +``` + +**Unsafe Example:** +```json +{ + "type": "VerticalLayout", // ❌ Not SwipeLayout - will be auto-wrapped + "elements": [...] +} +``` + +### Layout Types + +#### VerticalLayout + +Fields arranged vertically in a single column. + +**Required Fields:** +- `type`: `"VerticalLayout"` +- `elements`: Array of UI schema elements + +**Usage:** +```json +{ + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/field1" + }, + { + "type": "Control", + "scope": "#/properties/field2" + } + ] +} +``` + +#### HorizontalLayout + +Fields arranged horizontally in a row. + +**Required Fields:** +- `type`: `"HorizontalLayout"` +- `elements`: Array of UI schema elements + +**Usage:** +```json +{ + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/firstName" + }, + { + "type": "Control", + "scope": "#/properties/lastName" + } + ] +} +``` + +#### Group + +Groups related fields together with a label. + +**Required Fields:** +- `type`: `"Group"` +- `label`: Group title (required) +- `elements`: Array of UI schema elements + +**Usage:** +```json +{ + "type": "Group", + "label": "Personal Information", + "elements": [ + { + "type": "Control", + "scope": "#/properties/name" + }, + { + "type": "Control", + "scope": "#/properties/email" + } + ] +} +``` + +**Note**: Groups can be used as pages within SwipeLayout. Each Group element in a SwipeLayout's `elements[]` becomes a separate page. + +### Control Configuration + +Controls bind UI elements to schema properties. + +**Required Fields:** +- `type`: `"Control"` +- `scope`: JSON pointer to schema property (must exist in schema) + +**Optional Fields:** +- `label`: Override field label +- `options`: Additional configuration + +**Safe Example:** +```json +{ + "type": "Control", + "scope": "#/properties/name", // ✅ Scope exists in schema + "label": "Full Name", + "options": { + "placeholder": "Enter your name" + } +} +``` + +**Unsafe Example:** +```json +{ + "type": "Control", + "scope": "#/properties/nonexistent" // ❌ Scope doesn't exist - will cause error +} +``` + +### Label Element + +Displays text labels within forms. + +**Required Fields:** +- `type`: `"Label"` +- `text`: Label text content + +**Usage:** +```json +{ + "type": "Label", + "text": "Section Introduction" +} +``` + +### UI Schema Best Practices + +#### Pagination Patterns + +**Recommended**: Use SwipeLayout with VerticalLayout pages for multi-step forms: + +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { "type": "Label", "text": "Step 1: Basic Info" }, + { "type": "Control", "scope": "#/properties/name" }, + { "type": "Control", "scope": "#/properties/age" } + ] + }, + { + "type": "VerticalLayout", + "elements": [ + { "type": "Label", "text": "Step 2: Contact" }, + { "type": "Control", "scope": "#/properties/email" } + ] + } + ] +} +``` + +#### Grouping Best Practices + +**Use Groups for logical organization:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "Group", + "label": "Demographics", + "elements": [ + { "type": "Control", "scope": "#/properties/age" }, + { "type": "Control", "scope": "#/properties/gender" } + ] + }, + { + "type": "Group", + "label": "Health Information", + "elements": [ + { "type": "Control", "scope": "#/properties/height" }, + { "type": "Control", "scope": "#/properties/weight" } + ] + } + ] +} +``` + +#### Common Pitfalls + +**❌ Missing elements array:** +```json +{ + "type": "SwipeLayout" + // ❌ Missing elements - will cause error +} +``` + +**✅ Always include elements:** +```json +{ + "type": "SwipeLayout", + "elements": [] // ✅ Empty array is safe +} +``` + +**❌ Invalid scope paths:** +```json +{ + "type": "Control", + "scope": "#/properties/missingField" // ❌ Field doesn't exist in schema +} +``` + +**✅ Verify scope exists:** +```json +{ + "type": "Control", + "scope": "#/properties/existingField" // ✅ Field exists in schema +} +``` + +**❌ Nested SwipeLayout:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "SwipeLayout", // ❌ Nested SwipeLayout not supported + "elements": [...] + } + ] +} +``` + +**✅ Use VerticalLayout or Group inside SwipeLayout:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", // ✅ Use VerticalLayout for pages + "elements": [...] + } + ] +} +``` + +## Question Types + +ODE supports various question types through the Formplayer component. Question types are specified using the `format` property in the schema. + +### Basic Input Types + +**Text Input:** +```json +{ + "type": "string", + "title": "Name", + "format": "text" +} +``` + +**Number Input:** +```json +{ + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 +} +``` + +**Date and Time:** +```json +{ + "type": "string", + "title": "Date", + "format": "date" +} +``` + +**Selection:** +```json +{ + "type": "string", + "title": "Choice", + "enum": ["option1", "option2"], + "enumNames": ["Option 1", "Option 2"] +} +``` + +**Boolean:** +```json +{ + "type": "boolean", + "title": "Consent" +} +``` + +## Working with Media & Special Field Types + +ODE Formplayer supports specialized field types for capturing media, location, signatures, and scanning codes. Each type has specific schema requirements and platform constraints. + +### Photo Capture + +Captures photos using device camera or gallery selection. + +**Expected Schema:** +```json +{ + "type": "object", + "format": "photo", + "title": "Profile Photo", + "properties": { + "filename": { "type": "string" }, + "uri": { "type": "string" }, + "width": { "type": "integer" }, + "height": { "type": "integer" }, + "timestamp": { "type": "string", "format": "date-time" } + } +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/photo" +} +``` + +**Renderer Behavior:** +- Opens device camera or gallery picker +- Stores photo as attachment (synchronized separately) +- Returns metadata object with filename, URI, dimensions, timestamp + +**Platform Constraints:** +- Requires camera permission on mobile devices +- Photo files are stored locally and synced as attachments +- Large photos may be compressed automatically + +### GPS Location + +Captures GPS coordinates with accuracy information. + +**Expected Schema:** +```json +{ + "type": "object", + "format": "gps", + "title": "Current Location", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" }, + "altitude": { "type": "number" }, + "accuracy": { "type": "number" }, + "timestamp": { "type": "string", "format": "date-time" } + } +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/location" +} +``` + +**Renderer Behavior:** +- Requests location permission (first time) +- Captures current GPS coordinates +- Shows accuracy indicator +- May display map preview (platform dependent) + +**Platform Constraints:** +- Requires location permission +- GPS accuracy depends on device capabilities and environment +- Indoor locations may have poor accuracy +- Battery impact: GPS usage drains battery faster + +### Signature Capture + +Captures digital signatures using touch input. + +**Expected Schema:** +```json +{ + "type": "object", + "format": "signature", + "title": "Customer Signature", + "properties": { + "data": { "type": "string" }, // Base64 encoded image + "timestamp": { "type": "string", "format": "date-time" } + } +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/signature" +} +``` + +**Renderer Behavior:** +- Opens signature pad interface +- User draws signature with finger/stylus +- Stores as base64-encoded image +- Provides clear/retry functionality + +**Platform Constraints:** +- Works best on touch-enabled devices +- Signature quality depends on screen size and resolution +- Stored as image data (may be large) + +### Audio Recording + +Records audio using device microphone. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "audio", + "title": "Voice Note" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/audio" +} +``` + +**Renderer Behavior:** +- Opens audio recording interface +- Records audio using device microphone +- Stores audio file with metadata (duration, format, file size) +- Provides playback preview + +**Platform Constraints:** +- Requires microphone permission +- Audio files are stored as attachments +- File size depends on recording duration and quality +- Format: Platform-dependent (typically MP3, M4A, or WAV) + +### Video Recording + +Records video using device camera. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "video", + "title": "Instructional Video" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/video" +} +``` + +**Renderer Behavior:** +- Opens video recording interface +- Records video using device camera +- Stores video file with metadata (duration, resolution, format) +- Provides playback preview + +**Platform Constraints:** +- Requires camera permission +- Video files are large (stored as attachments) +- File size depends on duration and resolution +- Format: Platform-dependent (typically MP4) +- Battery and storage intensive + +### File Selection + +Allows users to select files from device storage. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "select_file", + "title": "Upload Document" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/document" +} +``` + +**Renderer Behavior:** +- Opens device file picker +- User selects file from storage +- Stores file reference and metadata +- File is uploaded as attachment + +**Platform Constraints:** +- File type restrictions depend on platform +- Large files may take time to upload +- Storage permissions required + +### QR Code / Barcode Scanner + +Scans QR codes and barcodes using device camera. + +**Expected Schema:** +```json +{ + "type": "string", + "format": "qrcode", + "title": "QR Code Scanner" +} +``` + +**Required UI Schema:** +```json +{ + "type": "Control", + "scope": "#/properties/qrCode" +} +``` + +**Renderer Behavior:** +- Opens camera scanner interface +- Automatically detects and scans codes +- Returns scanned data as string +- Supports multiple barcode formats + +**Supported Formats:** +- QR Code +- Code 128 +- Code 39 +- EAN-13 +- UPC-A +- Data Matrix +- PDF417 +- Aztec + +**Platform Constraints:** +- Requires camera permission +- Requires good lighting for reliable scanning +- Some formats may not be supported on all platforms + +### Best Practices for Media Fields + +1. **Consider File Sizes**: Media files are large - be mindful of storage and sync bandwidth +2. **Request Permissions Early**: Request camera/microphone/location permissions before users need them +3. **Provide Clear Instructions**: Users may not understand how to use specialized field types +4. **Handle Offline Scenarios**: Media capture works offline, but upload requires connectivity +5. **Test on Real Devices**: Media features behave differently on different devices +6. **Compress When Possible**: Large photos/videos should be compressed before storage +7. **Validate File Types**: Ensure selected files match expected formats + +## Conditional Logic in ODE Forms + +Conditional logic allows you to show or hide fields based on other field values. This is essential for creating dynamic, context-aware forms. + +### How Rules Work + +Rules are defined in the UI schema using the `rule` property on Control elements. Rules evaluate conditions and apply effects (SHOW or HIDE) based on the result. + +**Basic Rule Structure:** +```json +{ + "type": "Control", + "scope": "#/properties/targetField", + "rule": { + "effect": "SHOW", // or "HIDE" + "condition": { + "scope": "#/properties/sourceField", + "schema": { + // Condition schema + } + } + } +} +``` + +### Scope Resolution Rules + +**Critical Rule**: Rule condition scopes **must exist in the schema at all times**, even when the field is hidden. The scope is evaluated against the current form data. + +**Safe Pattern:** +```json +{ + "schema": { + "type": "object", + "properties": { + "contactMethod": { + "type": "string", + "enum": ["email", "phone"] + }, + "email": { + "type": "string", + "format": "email" + }, + "phone": { + "type": "string" + } + } + }, + "uischema": { + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/contactMethod" + }, + { + "type": "Control", + "scope": "#/properties/email", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", // ✅ Scope exists in schema + "schema": { + "const": "email" + } + } + } + }, + { + "type": "Control", + "scope": "#/properties/phone", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", // ✅ Scope exists in schema + "schema": { + "const": "phone" + } + } + } + } + ] + } + ] + } +} +``` + +### Safe Patterns + +#### Equality Check + +Show field when another field equals a specific value: + +```json +{ + "type": "Control", + "scope": "#/properties/email", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/contactMethod", + "schema": { + "const": "email" + } + } + } +} +``` + +#### Enum Value Check + +Show field when another field is one of several values: + +```json +{ + "type": "Control", + "scope": "#/properties/otherDetails", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/category", + "schema": { + "enum": ["category1", "category2"] + } + } + } +} +``` + +#### Boolean Check + +Show field when boolean is true: + +```json +{ + "type": "Control", + "scope": "#/properties/consentDetails", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/hasConsent", + "schema": { + "const": true + } + } + } +} +``` + +### Unsafe Patterns + +#### ❌ Rule Referencing Missing Field + +**Problem**: Rule condition scope doesn't exist in schema. + +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + // field2 doesn't exist + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/field2", // ❌ Field doesn't exist - will crash + "schema": { "const": "value" } + } + } + } +} +``` + +**Fix**: Ensure all fields referenced in rules exist in the schema. + +#### ❌ Using $data References + +**Problem**: `$data` references are not supported and will cause errors. + +```json +{ + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/field2", + "schema": { + "minimum": { "$data": "#/properties/minValue" } // ❌ $data not supported + } + } + } +} +``` + +**Fix**: Use literal values only: + +```json +{ + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/field2", + "schema": { + "minimum": 0 // ✅ Literal value + } + } + } +} +``` + +#### ❌ Using if/then/else + +**Problem**: JSON Schema `if/then/else` is not supported in rule conditions. + +```json +{ + "rule": { + "condition": { + "scope": "#/properties/field", + "schema": { + "if": { "type": "string" }, // ❌ Not supported + "then": { "const": "value" } + } + } + } +} +``` + +**Fix**: Use simple const or enum checks instead. + +### Best Practices for Conditional Logic + +1. **Always Define Referenced Fields**: Every field referenced in a rule condition must exist in the schema +2. **Use Simple Conditions**: Prefer `const` and `enum` over complex schema conditions +3. **Test All Conditions**: Verify rules work for all possible field values +4. **Avoid Circular Dependencies**: Don't create rules that depend on each other +5. **Document Complex Logic**: Comment complex conditional logic in your form specifications + +## Advanced Features + +### Multimedia Capture + +Forms can include fields for capturing photos, audio, and video. These are handled as attachments and synchronized separately from observation metadata. + +### Location Capture + +GPS coordinates can be captured automatically or manually entered. + +### File Attachments + +Files can be attached to observations and are synchronized separately from the observation data. + +## Form Versioning + +Forms support versioning to allow updates while maintaining compatibility with existing observations. When editing an observation, the form version used to create it is used. + +## Form Design Best Practices by Application Type + +Different application types have different requirements and constraints. Follow these best practices for your specific use case. + +### Research Data Collection + +**Characteristics**: Structured data collection, often longitudinal, requires high data quality. + +**Best Practices:** +1. **Use Clear Field Names**: Use descriptive, research-standard field names (e.g., `participant_id`, `visit_date`) +2. **Implement Validation**: Strict validation rules to ensure data quality +3. **Version Control**: Carefully version forms to maintain data consistency +4. **Minimize Required Fields**: Only require essential fields to reduce data entry burden +5. **Use Conditional Logic**: Show relevant fields based on participant characteristics +6. **Document Changes**: Maintain detailed changelog for form versions + +**Example Pattern:** +```json +{ + "schema": { + "properties": { + "participant_id": { + "type": "string", + "title": "Participant ID", + "pattern": "^P-[0-9]{4}$" + }, + "visit_number": { + "type": "integer", + "minimum": 1, + "maximum": 10 + } + }, + "required": ["participant_id", "visit_number"] + } +} +``` + +### Medical / Clinical Records + +**Characteristics**: Sensitive data, regulatory compliance, complex workflows. + +**Best Practices:** +1. **Consent Management**: Always include consent fields with clear labels +2. **Date/Time Precision**: Use precise date-time fields for clinical events +3. **Signature Requirements**: Use signature fields for consent and approvals +4. **Photo Documentation**: Use photo fields for wound tracking, skin conditions, etc. +5. **Structured Data**: Use enums for standardized values (e.g., diagnosis codes) +6. **Audit Trail**: Forms should capture who, what, when for audit purposes +7. **HIPAA/GDPR Compliance**: Ensure forms comply with data protection regulations + +**Example Pattern:** +```json +{ + "schema": { + "properties": { + "consent_obtained": { + "type": "boolean", + "title": "Patient consent obtained" + }, + "consent_signature": { + "type": "object", + "format": "signature", + "title": "Patient signature" + }, + "visit_date": { + "type": "string", + "format": "date-time", + "title": "Visit date and time" + } + }, + "required": ["consent_obtained", "consent_signature", "visit_date"] + } +} +``` + +### Surveys with Consent + +**Characteristics**: User-facing, requires clear consent, may be anonymous. + +**Best Practices:** +1. **Consent First**: Place consent fields at the beginning of the form +2. **Clear Language**: Use plain language, avoid jargon +3. **Progress Indicators**: Show progress to encourage completion +4. **Optional Fields**: Mark optional fields clearly +5. **Privacy Notice**: Include privacy information if collecting personal data +6. **Thank You Message**: Provide confirmation after submission + +**Example Pattern:** +```json +{ + "uischema": { + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Label", + "text": "Consent and Privacy" + }, + { + "type": "Control", + "scope": "#/properties/consent_read", + "label": "I have read and understood the consent form" + }, + { + "type": "Control", + "scope": "#/properties/consent_agreed", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/consent_read", + "schema": { "const": true } + } + } + } + ] + } + ] + } +} +``` + +### Longitudinal Forms + +**Characteristics**: Same form used multiple times, data comparison over time. + +**Best Practices:** +1. **Consistent Structure**: Maintain consistent field structure across versions +2. **Version Tracking**: Track which version was used for each observation +3. **Baseline Data**: Include baseline measurement fields +4. **Change Detection**: Use conditional logic to highlight changes +5. **Date Tracking**: Always include date/time fields for temporal analysis +6. **Backward Compatibility**: Ensure new versions don't break existing data + +**Example Pattern:** +```json +{ + "schema": { + "properties": { + "measurement_date": { + "type": "string", + "format": "date", + "title": "Measurement Date" + }, + "baseline_value": { + "type": "number", + "title": "Baseline Value" + }, + "current_value": { + "type": "number", + "title": "Current Value" + }, + "change_from_baseline": { + "type": "number", + "title": "Change from Baseline", + "readOnly": true // Calculated field + } + } + } +} +``` + +### General Best Practices + +**Form Structure:** +1. **Keep Forms Focused**: Each form should have a clear, single purpose +2. **Logical Grouping**: Group related fields together using Groups or separate pages +3. **Progressive Disclosure**: Use conditional logic to show fields only when relevant +4. **Clear Navigation**: Use SwipeLayout for multi-step forms with clear progress indicators + +**Field Design:** +1. **Use Clear Labels**: Field labels should be descriptive and unambiguous +2. **Provide Help Text**: Use descriptions or placeholders to guide users +3. **Validate Input**: Use validation rules to catch errors early +4. **Use Appropriate Types**: Choose the right field type for the data (date picker for dates, not text) + +**Data Quality:** +1. **Required Fields**: Only require truly essential fields +2. **Default Values**: Provide sensible defaults when appropriate +3. **Enum Constraints**: Use enums for fields with limited valid values +4. **Format Validation**: Use format validation (email, date, etc.) when applicable + +**Testing:** +1. **Test All Paths**: Test all conditional logic branches +2. **Test Validation**: Verify validation rules work correctly +3. **Test on Devices**: Test on actual mobile devices, not just emulators +4. **Test Offline**: Verify forms work correctly in offline mode +5. **User Testing**: Get feedback from actual users before deployment + +## Validating Forms Before Deployment + +Validating forms before deployment prevents errors and ensures forms work correctly in production. This section covers validation tools and workflows. + +### Validation Tools + +#### Form Validation Script + +ODE provides a validation script (`validate-forms.js`) that checks forms for common issues: + +**Usage:** +```bash +npm run validate:forms +``` + +**Checks Performed:** +- JSON Schema syntax validation +- UI Schema format validation +- Field reference validation (scope paths exist in schema) +- Required field validation +- Conditional logic validation (rule scopes exist) +- SwipeLayout structure validation + +**Common Validation Failures:** + +1. **Invalid JSON Schema:** + ``` + Error: Schema validation failed + - Property "minimum" must be a number + ``` + **Fix**: Ensure `minimum`/`maximum` use literal numbers, not `$data` references + +2. **Missing Scope:** + ``` + Error: Scope "#/properties/missingField" not found in schema + ``` + **Fix**: Add the referenced field to the schema or correct the scope path + +3. **Invalid UI Schema Structure:** + ``` + Error: SwipeLayout missing required "elements" array + ``` + **Fix**: Ensure SwipeLayout has an `elements` array (can be empty) + +4. **Rule Scope Error:** + ``` + Error: Rule condition scope "#/properties/field" not found + ``` + **Fix**: Ensure all fields referenced in rules exist in the schema + +### CI Integration + +Integrate form validation into your CI/CD pipeline: + +**GitHub Actions Example:** +```yaml +name: Validate Forms + +on: [push, pull_request] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '18' + - run: npm install + - run: npm run validate:forms +``` + +**GitLab CI Example:** +```yaml +validate_forms: + script: + - npm install + - npm run validate:forms +``` + +### Recommended Workflow + +1. **Local Validation**: Run validation script before committing changes +2. **Pre-commit Hooks**: Set up git hooks to validate forms automatically +3. **CI Validation**: Include validation in CI pipeline to catch errors early +4. **Manual Testing**: Test forms on actual devices before deployment +5. **Staging Deployment**: Deploy to staging environment and test thoroughly +6. **Production Deployment**: Only deploy validated, tested forms + +### Validation Checklist + +Before deploying a form, verify: + +- [ ] JSON Schema is valid JSON Schema Draft 7 +- [ ] All scope paths in UI schema exist in JSON schema +- [ ] SwipeLayout is root element (or will be auto-wrapped) +- [ ] All rule condition scopes exist in schema +- [ ] No `$data` references used +- [ ] No `if/then/else` in rule conditions +- [ ] All required fields are clearly marked +- [ ] Validation rules use literal values (not dynamic) +- [ ] Media field types have correct schema structure +- [ ] Form tested on actual mobile devices +- [ ] Form tested in offline mode +- [ ] All conditional logic paths tested + +### Troubleshooting Validation Errors + +**"minimum value must be ['number']"** +- **Cause**: Using `$data` or non-numeric value in `minimum` +- **Fix**: Use literal number: `"minimum": 0` + +**"Cannot read properties of undefined (reading 'find')"** +- **Cause**: Layout missing `elements` array, invalid scope, or rule referencing missing field +- **Fix**: + - Add `elements: []` to layouts + - Validate all scope paths exist + - Ensure rule scopes reference existing fields + +**"Rule condition scope not found"** +- **Cause**: Rule references field that doesn't exist in schema +- **Fix**: Add the referenced field to schema or correct the scope path + +## Related Documentation + +- [Form Specifications Reference](/reference/form-specifications) +- [Formplayer Supported Schema & UI Profile](/reference/formplayer#supported-schema--ui-profile) +- [Formplayer Errors Explained](/using/troubleshooting#formplayer-errors) +- [Your First Form](/using/your-first-form) +- [Custom Applications](/guides/custom-applications) + diff --git a/versioned_docs/version-1.0/guides/index.md b/versioned_docs/version-1.0/guides/index.md new file mode 100644 index 0000000..159b8f1 --- /dev/null +++ b/versioned_docs/version-1.0/guides/index.md @@ -0,0 +1,61 @@ +--- +sidebar_position: 0 +--- + +# Guides + +Step-by-step guides for common tasks and workflows in ODE. + +## Form Design & Configuration + +
    +
    +
    +
    +

    Form Design

    +
    +
    +

    Complete guide to designing forms, including schema definitions and validation rules.

    + View Guide → +
    +
    +
    +
    +
    +
    +

    Configuration

    +
    +
    +

    Configure ODE components, server settings, and client applications.

    + Configure → +
    +
    +
    +
    + +## Deployment & Customization + +
    +
    +
    +
    +

    Deployment

    +
    +
    +

    Deploy ODE to production environments, including Docker and cloud platforms.

    + Deploy → +
    +
    +
    +
    +
    +
    +

    Custom Applications

    +
    +
    +

    Build and deploy custom applications using the ODE platform.

    + Build Apps → +
    +
    +
    +
    diff --git a/docs/host/_category_.json b/versioned_docs/version-1.0/host/_category_.json similarity index 100% rename from docs/host/_category_.json rename to versioned_docs/version-1.0/host/_category_.json diff --git a/docs/host/data-management.md b/versioned_docs/version-1.0/host/data-management.md similarity index 100% rename from docs/host/data-management.md rename to versioned_docs/version-1.0/host/data-management.md diff --git a/docs/host/index.md b/versioned_docs/version-1.0/host/index.md similarity index 79% rename from docs/host/index.md rename to versioned_docs/version-1.0/host/index.md index 2001662..d05d5f8 100644 --- a/docs/host/index.md +++ b/versioned_docs/version-1.0/host/index.md @@ -12,24 +12,23 @@ This section provides guides for hosting Synkronus servers, monitoring, and mana - diff --git a/docs/host/monitoring/overview.md b/versioned_docs/version-1.0/host/monitoring/overview.md similarity index 100% rename from docs/host/monitoring/overview.md rename to versioned_docs/version-1.0/host/monitoring/overview.md diff --git a/docs/host/monitoring/production.md b/versioned_docs/version-1.0/host/monitoring/production.md similarity index 100% rename from docs/host/monitoring/production.md rename to versioned_docs/version-1.0/host/monitoring/production.md diff --git a/docs/host/monitoring/setup.md b/versioned_docs/version-1.0/host/monitoring/setup.md similarity index 100% rename from docs/host/monitoring/setup.md rename to versioned_docs/version-1.0/host/monitoring/setup.md diff --git a/docs/host/security.md b/versioned_docs/version-1.0/host/security.md similarity index 100% rename from docs/host/security.md rename to versioned_docs/version-1.0/host/security.md diff --git a/docs/host/synkronus-server/backups.md b/versioned_docs/version-1.0/host/synkronus-server/backups.md similarity index 100% rename from docs/host/synkronus-server/backups.md rename to versioned_docs/version-1.0/host/synkronus-server/backups.md diff --git a/docs/host/synkronus-server/cloud.md b/versioned_docs/version-1.0/host/synkronus-server/cloud.md similarity index 100% rename from docs/host/synkronus-server/cloud.md rename to versioned_docs/version-1.0/host/synkronus-server/cloud.md diff --git a/docs/host/synkronus-server/docker.md b/versioned_docs/version-1.0/host/synkronus-server/docker.md similarity index 100% rename from docs/host/synkronus-server/docker.md rename to versioned_docs/version-1.0/host/synkronus-server/docker.md diff --git a/docs/host/synkronus-server/kubernetes.md b/versioned_docs/version-1.0/host/synkronus-server/kubernetes.md similarity index 100% rename from docs/host/synkronus-server/kubernetes.md rename to versioned_docs/version-1.0/host/synkronus-server/kubernetes.md diff --git a/docs/host/synkronus-server/monitoring.md b/versioned_docs/version-1.0/host/synkronus-server/monitoring.md similarity index 100% rename from docs/host/synkronus-server/monitoring.md rename to versioned_docs/version-1.0/host/synkronus-server/monitoring.md diff --git a/docs/host/synkronus-server/overview.md b/versioned_docs/version-1.0/host/synkronus-server/overview.md similarity index 100% rename from docs/host/synkronus-server/overview.md rename to versioned_docs/version-1.0/host/synkronus-server/overview.md diff --git a/docs/host/synkronus-server/production.md b/versioned_docs/version-1.0/host/synkronus-server/production.md similarity index 100% rename from docs/host/synkronus-server/production.md rename to versioned_docs/version-1.0/host/synkronus-server/production.md diff --git a/docs/host/synkronus-server/requirements.md b/versioned_docs/version-1.0/host/synkronus-server/requirements.md similarity index 100% rename from docs/host/synkronus-server/requirements.md rename to versioned_docs/version-1.0/host/synkronus-server/requirements.md diff --git a/versioned_docs/version-1.0/index.md b/versioned_docs/version-1.0/index.md new file mode 100644 index 0000000..9d218a4 --- /dev/null +++ b/versioned_docs/version-1.0/index.md @@ -0,0 +1,91 @@ +--- +sidebar_position: 1 +--- + +# Open Data Ensemble + +Open Data Ensemble (ODE) is a comprehensive platform for mobile data collection and synchronization. Built for researchers, health professionals, implementers, and developers, ODE provides a robust solution for designing forms, managing data securely, and synchronizing seamlessly across devices, even in offline conditions. + +:::info Pre-release Available + +The source code for the pre-release version of ODE is now publicly available. While we're working toward the full 1.0 release, we welcome anyone willing to help with testing, development, or getting involved in the project. + +**Try the pre-release:** [Install Formulus on Android](/getting-started/installation) + +**Get involved:** Reach out to us at [hello@opendataensemble.org](mailto:hello@opendataensemble.org) - we'd love to hear from you! + +**Source Code:** Our monorepo on GitHub contains the source code for all components. Visit our repository: [https://github.com/OpenDataEnsemble/ode](https://github.com/OpenDataEnsemble/ode) + +::: + +## What is ODE? + +ODE is a modern toolkit that simplifies mobile data collection through: + +- **Simplicity & Efficiency**: Quickly create complex, validated forms using a powerful yet intuitive JSON-based approach +- **Advanced Offline Sync**: Reliable and conflict-resilient synchronization powered by WatermelonDB +- **Flexible & Extensible UI**: Customize form presentation and interaction effortlessly with JSON Forms, enabling rich, interactive user experiences +- **Cross-platform Support**: Applications run on Android, iOS, and web platforms + +## Current Members of the Ensemble + +Here's an overview of the current members of the ensemble: + +Component overview + +* [formulus](/reference/formulus): The Android and iOS app for data collection and form interaction. +* [synkronus](/reference/synkronus-server): The robust server backend managing synchronization and data storage. +* [synkronus-cli](/reference/synkronus-cli): Command-line interface for convenient server management and administrative tasks. + +## Key Components + +ODE consists of four main components that work together: + +| Component | Description | Technology | +|-----------|-------------|------------| +| **Formulus** | Mobile application for Android and iOS | React Native | +| **Formplayer** | Web-based form rendering engine | React | +| **Synkronus** | Backend server for data synchronization | Go | +| **Synkronus CLI** | Command-line utility for administration | Go | + +## Core Capabilities + +### Form Design + +Create sophisticated forms using JSON schema definitions. ODE supports various question types including text, numbers, dates, selections, multimedia capture, GPS coordinates, and custom renderers. + +### Offline Functionality + +Work seamlessly in areas with unreliable connectivity. Data is stored locally and synchronized when network connectivity is available. + +### Data Synchronization + +Reliable bidirectional synchronization ensures data consistency across devices and servers, with conflict resolution built into the protocol. + +### Custom Applications + +Build custom applications that integrate with the ODE platform, allowing for specialized workflows and user interfaces. + +## Getting Started + +New to ODE? Start with our [Getting Started guide](/getting-started/what-is-ode) to understand the platform and begin your first project. + +For developers looking to contribute or extend ODE, see the [Development section](/development/setup) for architecture details and contribution guidelines. + +## Documentation Structure + +This documentation is organized to help you find information quickly: + +- **Getting Started**: Introduction, installation, and basic concepts +- **Using ODE**: Practical guides for creating forms and managing data +- **Guides**: Step-by-step workflows for form design, custom apps, and deployment +- **Reference**: Complete API documentation and configuration options +- **Development**: Architecture details and contribution guidelines +- **Community**: Support resources and examples + +## Next Steps + +- Read [What is ODE?](/getting-started/what-is-ode) for a detailed overview +- Follow the [Installation guide](/getting-started/installation) to set up your environment +- Create your [first form](/using/your-first-form) to see ODE in action +- Explore the [API Reference](/reference/api) for technical details diff --git a/docs/quick-start/_category_.json b/versioned_docs/version-1.0/quick-start/_category_.json similarity index 100% rename from docs/quick-start/_category_.json rename to versioned_docs/version-1.0/quick-start/_category_.json diff --git a/docs/quick-start/custom-app.md b/versioned_docs/version-1.0/quick-start/custom-app.md similarity index 100% rename from docs/quick-start/custom-app.md rename to versioned_docs/version-1.0/quick-start/custom-app.md diff --git a/docs/quick-start/deploy-local-instance.md b/versioned_docs/version-1.0/quick-start/deploy-local-instance.md similarity index 100% rename from docs/quick-start/deploy-local-instance.md rename to versioned_docs/version-1.0/quick-start/deploy-local-instance.md diff --git a/docs/quick-start/faq.md b/versioned_docs/version-1.0/quick-start/faq.md similarity index 100% rename from docs/quick-start/faq.md rename to versioned_docs/version-1.0/quick-start/faq.md diff --git a/docs/quick-start/formulus-app.md b/versioned_docs/version-1.0/quick-start/formulus-app.md similarity index 100% rename from docs/quick-start/formulus-app.md rename to versioned_docs/version-1.0/quick-start/formulus-app.md diff --git a/docs/quick-start/index.md b/versioned_docs/version-1.0/quick-start/index.md similarity index 72% rename from docs/quick-start/index.md rename to versioned_docs/version-1.0/quick-start/index.md index e210f9f..32fb604 100644 --- a/docs/quick-start/index.md +++ b/versioned_docs/version-1.0/quick-start/index.md @@ -12,17 +12,17 @@ This section will help you set up your development environment and deploy your f @@ -32,18 +32,18 @@ This section will help you set up your development environment and deploy your f - \ No newline at end of file + diff --git a/docs/quick-start/prerequisites.md b/versioned_docs/version-1.0/quick-start/prerequisites.md similarity index 100% rename from docs/quick-start/prerequisites.md rename to versioned_docs/version-1.0/quick-start/prerequisites.md diff --git a/docs/quick-start/setup-environment.md b/versioned_docs/version-1.0/quick-start/setup-environment.md similarity index 100% rename from docs/quick-start/setup-environment.md rename to versioned_docs/version-1.0/quick-start/setup-environment.md diff --git a/docs/quick-start/synkronus-server.md b/versioned_docs/version-1.0/quick-start/synkronus-server.md similarity index 100% rename from docs/quick-start/synkronus-server.md rename to versioned_docs/version-1.0/quick-start/synkronus-server.md diff --git a/docs/quick-start/upload-test-data.md b/versioned_docs/version-1.0/quick-start/upload-test-data.md similarity index 100% rename from docs/quick-start/upload-test-data.md rename to versioned_docs/version-1.0/quick-start/upload-test-data.md diff --git a/docs/quick-start/verification-testing.md b/versioned_docs/version-1.0/quick-start/verification-testing.md similarity index 100% rename from docs/quick-start/verification-testing.md rename to versioned_docs/version-1.0/quick-start/verification-testing.md diff --git a/versioned_docs/version-1.0/reference/.gitkeep b/versioned_docs/version-1.0/reference/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/reference/_category_.json b/versioned_docs/version-1.0/reference/_category_.json new file mode 100644 index 0000000..09e8aa8 --- /dev/null +++ b/versioned_docs/version-1.0/reference/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Reference", + "position": 6 +} + diff --git a/versioned_docs/version-1.0/reference/api.md b/versioned_docs/version-1.0/reference/api.md new file mode 100644 index 0000000..1c7eb35 --- /dev/null +++ b/versioned_docs/version-1.0/reference/api.md @@ -0,0 +1,665 @@ +--- +sidebar_position: 1 +--- + +# API Reference + +Complete REST API reference for the Synkronus server. + +## Overview + +The Synkronus API provides endpoints for data synchronization, authentication, app bundle management, attachment handling, and user management. All endpoints use JSON for request and response bodies, except for binary file operations. + +## Base URL + +The API base URL depends on your deployment: + +- **Development**: `http://localhost:8080` +- **Production**: `https://synkronus.your-domain.com` + +All API endpoints are prefixed with `/api` for portal compatibility, though many endpoints are also available without the prefix. + +## Authentication + +The API uses JWT (JSON Web Tokens) for authentication. Most endpoints require authentication, except for health checks and version information. + +### Obtaining a Token + +Authenticate using the login endpoint: + + + + +```bash +curl -X POST http://localhost:8080/auth/login \ + -H "Content-Type: application/json" \ + -d '{ + "username": "your-username", + "password": "your-password" + }' +``` + +**Response:** + +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expires_in": 3600 +} +``` + + + + +```bash +synk login --username your-username +``` + +The CLI will prompt for password interactively and store the token automatically. + + + + +1. Navigate to the Portal login page +2. Enter your username and password +3. Click "Login" +4. Token is stored automatically in your browser session + + + + +### Using the Token + +Include the token in the Authorization header: + + + + +```bash +curl -X GET http://localhost:8080/api/endpoint \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + + + + +The CLI automatically includes the token after login. No manual token handling needed: + +```bash +synk status # Uses stored token automatically +``` + + + + +The Portal automatically includes the token in all API requests. No manual configuration needed. + + + + +### Refreshing Tokens + +Refresh an expired token: + + + + +```bash +curl -X POST http://localhost:8080/auth/refresh \ + -H "Content-Type: application/json" \ + -d '{ + "refresh_token": "your-refresh-token" + }' +``` + + + + +The CLI automatically refreshes tokens when needed. If refresh fails, you'll be prompted to login again: + +```bash +synk status # Automatically refreshes if needed +``` + + + + +The Portal automatically refreshes tokens in the background. If refresh fails, you'll be redirected to the login page. + + + + +### Roles + +The API supports role-based access control: + +| Role | Description | Permissions | +|------|-------------|-------------| +| `read-only` | Read-only access | Can pull data, view app bundles, download attachments | +| `read-write` | Read and write access | All read-only permissions plus push data, create observations | +| `admin` | Administrative access | All read-write permissions plus user management, app bundle management | + +## API Endpoints + +### Health and Version + +#### Health Check + +```http +GET /health +``` + +Returns the health status of the service. + +**Response:** + +```json +{ + "status": "ok", + "timestamp": "2025-01-14T10:30:00Z", + "version": "1.0.0" +} +``` + +#### Get Version + +```http +GET /version +GET /api/version +``` + +Returns detailed version information about the server. + +**Response:** + +```json +{ + "version": "1.0.0", + "build_time": "2025-01-14T08:00:00Z", + "git_commit": "abc123...", + "go_version": "go1.22.0" +} +``` + +### Synchronization + +#### Pull Data + +Pulls records that have changed since the specified `change_id`. + + + + +```bash +curl -X POST http://localhost:8080/sync/pull \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "client_id": "unique-client-identifier", + "since_change_id": 0, + "schema_types": ["observation"] + }' +``` + + + + +```bash +synk sync pull output.json --client-id unique-client-identifier +``` + + + + +The Portal provides a UI for viewing synchronized data. Use the Observations tab to view pulled data. + + + + +**Request Parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `client_id` | string | Yes | Unique identifier for the client | +| `since_change_id` | integer | No | Last change ID seen by client (default: 0) | +| `schema_types` | array | No | Filter by schema types | + +**Response:** + +```json +{ + "records": [ + { + "id": "obs-123", + "schema_type": "observation", + "schema_version": "1.0.0", + "data": {...}, + "change_id": 1234, + "last_modified": "2025-01-14T10:00:00Z", + "deleted": false + } + ], + "change_cutoff": 1234, + "next_page_token": "eyJ...", + "has_more": false +} +``` + +#### Push Data + +Pushes local records to the server. Requires `read-write` or `admin` role. + + + + +```bash +curl -X POST http://localhost:8080/sync/push \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "transmission_id": "550e8400-e29b-41d4-a716-446655440000", + "client_id": "unique-client-identifier", + "records": [ + { + "id": "obs-123", + "schema_type": "observation", + "schema_version": "1.0.0", + "data": {...} + } + ] + }' +``` + + + + +```bash +synk sync push data.json +``` + +The JSON file should contain observations in the sync format. + + + + +The Portal provides a UI for viewing and managing observations. Data is automatically pushed from Formulus app during sync. + + + + +**Request Parameters:** + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `transmission_id` | string (UUID) | Yes | Client-generated unique ID for idempotency | +| `client_id` | string | Yes | Unique identifier for the client | +| `records` | array | Yes | Array of records to push | + +**Response:** + +```json +{ + "successes": [ + { + "id": "obs-123", + "change_id": 1234 + } + ], + "failures": [], + "warnings": [], + "change_cutoff": 1234 +} +``` + +### App Bundle Management + +#### Get Manifest + +```http +GET /app-bundle/manifest +GET /api/app-bundle/manifest +Authorization: Bearer +``` + +Returns the current app bundle manifest. + +**Response:** + +```json +{ + "version": "20250114-123456", + "files": [ + { + "path": "index.html", + "hash": "abc123...", + "size": 1024 + } + ] +} +``` + +#### Download File + +```http +GET /app-bundle/download/{path} +GET /api/app-bundle/download/{path} +Authorization: Bearer +``` + +Downloads a specific file from the app bundle. + +#### List Versions + +```http +GET /app-bundle/versions +GET /api/app-bundle/versions +Authorization: Bearer +``` + +Lists all available app bundle versions. + +**Response:** + +```json +{ + "versions": [ + { + "version": "20250114-123456", + "created_at": "2025-01-14T10:00:00Z", + "active": true + } + ] +} +``` + +#### Upload Bundle + +Uploads a new app bundle. Requires `admin` role. + + + + +```bash +curl -X POST http://localhost:8080/api/app-bundle/push \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -F "bundle=@app-bundle.zip" \ + -F "activate=true" +``` + + + + +```bash +synk bundle push app-bundle.zip --activate +``` + + + + +1. Navigate to the Portal +2. Go to "App Bundles" +3. Click "Upload Bundle" +4. Select your ZIP file +5. Check "Activate immediately" if desired +6. Click "Upload" + + + + +#### Switch Version + +```http +POST /app-bundle/switch/{version} +POST /api/app-bundle/switch/{version} +Authorization: Bearer +``` + +Switches the active app bundle version. Requires `admin` role. + +### Attachments + +#### Get Manifest + +```http +GET /attachments/manifest +Authorization: Bearer + +?after_change_id=0 +``` + +Returns a manifest of attachments that have changed since the specified `change_id`. + +**Query Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `after_change_id` | integer | Only return attachments changed after this ID | + +**Response:** + +```json +{ + "operations": [ + { + "attachment_id": "att-123", + "operation": "upload", + "change_id": 1234 + } + ], + "change_cutoff": 1234 +} +``` + +#### Upload Attachment + +```http +PUT /attachments/{attachment_id} +Authorization: Bearer +Content-Type: application/octet-stream + + +``` + +Uploads an attachment file. Requires `read-write` or `admin` role. + +#### Download Attachment + +```http +GET /attachments/{attachment_id} +Authorization: Bearer + +?quality=medium +``` + +Downloads an attachment file. + +**Query Parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `quality` | string | For images: `original`, `large`, `medium`, `small` (default: `medium`) | + +### User Management + +#### Create User + +```http +POST /users +POST /users/create +POST /api/users +POST /api/users/create +Authorization: Bearer +Content-Type: application/json + +{ + "username": "newuser", + "password": "secure-password", + "role": "read-write" +} +``` + +Creates a new user. Requires `admin` role. + +#### List Users + +```http +GET /users +GET /api/users +Authorization: Bearer +``` + +Lists all users. Requires `admin` role. + +#### Delete User + +```http +DELETE /users/delete/{username} +DELETE /api/users/delete/{username} +Authorization: Bearer +``` + +Deletes a user. Requires `admin` role. + +#### Change Password + +```http +POST /users/change-password +POST /api/users/change-password +Authorization: Bearer +Content-Type: application/json + +{ + "current_password": "old-password", + "new_password": "new-password" +} +``` + +Changes the password for the authenticated user. + +#### Reset Password + +```http +POST /users/reset-password +POST /api/users/reset-password +Authorization: Bearer +Content-Type: application/json + +{ + "username": "target-user", + "new_password": "new-password" +} +``` + +Resets a user's password. Requires `admin` role. + +### Data Export + +#### Export to Parquet + +```http +GET /dataexport/parquet +GET /api/dataexport/parquet +Authorization: Bearer +``` + +Exports all observations as a Parquet ZIP archive. Requires `read-only` role or higher. + +**Response:** + +Returns a ZIP file containing Parquet files with observation data. + +## API Versioning + +The API supports versioning through the `x-api-version` header: + +```http +x-api-version: 1.0.0 +``` + +If omitted, the server defaults to the latest stable version. + +### Version Discovery + +```http +GET /api/versions +``` + +Returns all available API versions and their status. + +## Error Handling + +### Error Response Format + +Errors follow RFC 7807 (Problem Details for HTTP APIs): + +```json +{ + "type": "https://synkronus.org/docs/errors/validation", + "title": "Validation Error", + "status": 422, + "detail": "One or more records failed validation", + "errors": [ + { + "recordId": "abc-123", + "path": "data.age", + "message": "Age must be a positive integer", + "code": "TYPE_ERROR" + } + ] +} +``` + +### HTTP Status Codes + +| Code | Description | +|------|-------------| +| `200` | Success | +| `201` | Created | +| `400` | Bad Request | +| `401` | Unauthorized | +| `403` | Forbidden | +| `404` | Not Found | +| `409` | Conflict | +| `422` | Unprocessable Entity | +| `429` | Too Many Requests | +| `500` | Internal Server Error | +| `503` | Service Unavailable | + +## Rate Limiting + +The API implements rate limiting to prevent abuse. When rate limits are exceeded, the server returns `429 Too Many Requests` with a `Retry-After` header indicating when to retry. + +## Pagination + +List endpoints support cursor-based pagination: + +- Include `next_page_token` from previous response +- Server returns `has_more` flag indicating if more data is available +- Default page size: 50 records +- Maximum page size: 500 records + +## ETag Support + +Many endpoints support ETags for caching: + +```http +GET /app-bundle/manifest +If-None-Match: "abc123..." +``` + +If the resource hasn't changed, the server returns `304 Not Modified`. + +## OpenAPI Specification + +The complete OpenAPI specification is available at: + +``` +GET /openapi/synkronus.yaml +``` + +## Related Documentation + +- [Synchronization Guide](/using/synchronization) +- [Configuration Guide](/guides/configuration) +- [Components Reference](/reference/components) diff --git a/docs/build/custom-applications/building.md b/versioned_docs/version-1.0/reference/app-bundle-format.md similarity index 54% rename from docs/build/custom-applications/building.md rename to versioned_docs/version-1.0/reference/app-bundle-format.md index 855821a..6935551 100644 --- a/docs/build/custom-applications/building.md +++ b/versioned_docs/version-1.0/reference/app-bundle-format.md @@ -1,29 +1,29 @@ --- -sidebar_position: 2 +sidebar_position: 4 --- -# Building Custom Apps +# App Bundle Format -Step-by-step guide to building custom applications. +Technical specification for app bundle format. ## Overview [Description placeholder] -## Project Setup +## Bundle Structure [Description placeholder] -## Development +## Manifest Format [Description placeholder] -## Testing +## Versioning [Description placeholder] ## Related Content - [App Bundle Structure](/docs/build/custom-applications/app-bundle-structure) -- [Deployment](/docs/build/custom-applications/deployment) +- [App Bundle API](/docs/reference/rest-api/app-bundle) diff --git a/versioned_docs/version-1.0/reference/components.md b/versioned_docs/version-1.0/reference/components.md new file mode 100644 index 0000000..b4a1b64 --- /dev/null +++ b/versioned_docs/version-1.0/reference/components.md @@ -0,0 +1,179 @@ +--- +sidebar_position: 2 +--- + +# Components Reference + +Complete reference documentation for all ODE components. + +## Formulus + +Formulus is the mobile application component of ODE, available for Android and iOS devices. + +### Overview + +Formulus is a React Native application that provides offline-first data collection capabilities. It integrates with the Synkronus server for data synchronization and supports custom applications through WebView integration. + +### Key Features + +- **Offline-first architecture**: Data is stored locally using WatermelonDB +- **Automatic synchronization**: Syncs with server when connectivity is available +- **Custom application support**: Runs custom web applications in WebViews +- **JSON Forms integration**: Renders forms using JSON Forms specification +- **Multimedia capture**: Supports photos, audio, video, GPS, and signatures +- **Conflict resolution**: Automatically resolves sync conflicts + +### Installation + +- **End Users**: See [Installing Formulus](/getting-started/installing-formulus) +- **Developers**: See [Installing Formulus for Development](/development/installing-formulus-dev) + +### Configuration + +Configure the app through Settings: + +- **Server URL**: Your Synkronus server address +- **Authentication**: Username and password +- **Sync settings**: Auto-sync, sync interval, sync triggers + +### JavaScript Interface + +Formulus exposes a JavaScript interface for custom applications: + +```javascript +// Get the Formulus API +const api = await getFormulus(); + +// Create observation +await api.addObservation(formType, initializationData); + +// Edit observation +await api.editObservation(formType, observationId); + +// Delete observation +await api.deleteObservation(formType, observationId); +``` + +### Documentation + +- **User Guide**: [Formulus Features](/using/formulus-features) +- **Component Reference**: [Formulus Reference](/reference/formulus) +- **Development**: [Formulus Development](/development/formulus-development) + +## Synkronus + +Synkronus is the server component of ODE, built in Go, that handles data synchronization, storage, and API services. + +### Overview + +Synkronus provides a RESTful API for data synchronization, app bundle management, attachment handling, and user management. It uses PostgreSQL for data storage and JWT for authentication. + +### Key Features + +- **RESTful API**: Comprehensive API for all operations +- **PostgreSQL integration**: Robust data storage +- **Conflict resolution**: Version-based conflict detection and resolution +- **Versioning system**: Supports schema and app bundle versioning +- **Security**: JWT-based authentication with role-based access control +- **Attachment management**: Separate handling of binary files +- **API versioning**: Supports multiple API versions + +### Installation + +See the [Installation guide](/getting-started/installation) for detailed installation instructions. + +### Configuration + +Configure using environment variables: + +- `DB_CONNECTION`: PostgreSQL connection string +- `JWT_SECRET`: Secret for JWT token signing +- `PORT`: HTTP server port (default: 8080) +- `LOG_LEVEL`: Logging level (default: info) + +See the [Configuration guide](/guides/configuration) for complete configuration options. + +### API + +See the [API Reference](/reference/api) for complete API documentation. + +## Synkronus CLI + +Synkronus CLI is a command-line utility for interacting with the Synkronus server. + +### Overview + +The CLI provides convenient access to server operations including authentication, app bundle management, data synchronization, and data export. + +### Installation + +```bash +go install github.com/OpenDataEnsemble/ode/synkronus-cli/cmd/synkronus@latest +``` + +Or build from source: + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +### Key Features + +- **Authentication**: Login, logout, token management +- **App bundle management**: Upload, download, version management +- **Data synchronization**: Push and pull operations +- **Data export**: Export observations as Parquet ZIP archives +- **Configuration management**: Multiple endpoint support + +### Documentation + +- **Complete Reference**: [Synkronus CLI Reference](/reference/synkronus-cli) +- **Common Commands**: See CLI reference for all commands + +## Formplayer + +Formplayer is the web-based form rendering component of ODE. + +### Overview + +Formplayer is a React application that renders JSON Forms and communicates with the Formulus mobile app. It provides the dynamic form interface that powers data collection workflows. + +### Key Features + +- **JSON Forms rendering**: Renders forms based on JSON schema +- **Question type support**: Supports various input types including text, numbers, dates, multimedia, GPS, signatures +- **Validation**: Client-side and schema-based validation +- **Custom renderers**: Support for custom question type renderers +- **Integration**: Seamlessly integrates with Formulus mobile app + +### Documentation + +- **Complete Reference**: [Formplayer Reference](/reference/formplayer) +- **Development**: [Formplayer Development](/development/formplayer-development) + +## Component Integration + +All components work together to provide a complete data collection solution: + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formulus │◄───────►│ Synkronus │◄───────►│ Formulus │ +│ (Mobile) │ Sync │ (Server) │ Sync │ (Mobile) │ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ │ + │ │ │ + ▼ ▼ ▼ +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ Formplayer │ │ Database │ │ Formplayer │ +│ (WebView) │ │ (PostgreSQL) │ │ (WebView) │ +└─────────────┘ └──────────────┘ └─────────────┘ +``` + +## Related Documentation + +- [Installation Guide](/getting-started/installation) +- [API Reference](/reference/api) +- [Form Design Guide](/guides/form-design) +- [Custom Applications Guide](/guides/custom-applications) diff --git a/versioned_docs/version-1.0/reference/configuration/client.md b/versioned_docs/version-1.0/reference/configuration/client.md new file mode 100644 index 0000000..df7b6c5 --- /dev/null +++ b/versioned_docs/version-1.0/reference/configuration/client.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 2 +--- + +# Client Configuration + +Configuration options for ODE clients. + +## Overview + +[Description placeholder] + +## Formulus Configuration + +[Description placeholder] + +## Custom Application Configuration + +[Description placeholder] + +## Related Content + +- [Formulus Configuration](/docs/1.0/components/formulus/configuration) +- [Custom Applications](/docs/1.0/build/custom-applications/overview) + diff --git a/versioned_docs/version-1.0/reference/configuration/server.md b/versioned_docs/version-1.0/reference/configuration/server.md new file mode 100644 index 0000000..51d45b8 --- /dev/null +++ b/versioned_docs/version-1.0/reference/configuration/server.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 1 +--- + +# Server Configuration + +Configuration options for Synkronus server. + +## Overview + +[Description placeholder] + +## Environment Variables + +[Description placeholder] + +## Configuration Files + +[Description placeholder] + +## Related Content + +- [Synkronus Configuration](/docs/1.0/components/synkronus/configuration) +- [Production Deployment](/docs/1.0/host/synkronus-server/production) + diff --git a/docs/build/forms/advanced-features/multimedia.md b/versioned_docs/version-1.0/reference/form-specifications.md similarity index 50% rename from docs/build/forms/advanced-features/multimedia.md rename to versioned_docs/version-1.0/reference/form-specifications.md index 7d928db..21b8154 100644 --- a/docs/build/forms/advanced-features/multimedia.md +++ b/versioned_docs/version-1.0/reference/form-specifications.md @@ -1,29 +1,29 @@ --- -sidebar_position: 1 +sidebar_position: 3 --- -# Multimedia in Forms +# Form Specifications -Add images, videos, and other media to forms. +Technical specifications for ODE forms. ## Overview [Description placeholder] -## Image Capture +## Schema Format [Description placeholder] -## Video Recording +## UI Schema Format [Description placeholder] -## File Upload +## Core Fields [Description placeholder] ## Related Content -- [File Attachments](/docs/build/forms/advanced-features/attachments) - [Form Design](/docs/build/forms/design/schema-definition) +- [JSON Forms](/docs/technical-overview/concepts/json-forms) diff --git a/versioned_docs/version-1.0/reference/formplayer-contract.md b/versioned_docs/version-1.0/reference/formplayer-contract.md new file mode 100644 index 0000000..62d5836 --- /dev/null +++ b/versioned_docs/version-1.0/reference/formplayer-contract.md @@ -0,0 +1,1537 @@ +--- +sidebar_position: 7 +--- + +# Formplayer Contract + +**Version:** 1.0 +**Last Updated:** 2024 +**Status:** Authoritative Reference + +This document defines the complete contract for ODE Formplayer, including supported JSON Schema features, UI schema structure, validation rules, and behavioral guarantees. Use this as the definitive reference when creating forms for ODE. + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Stability Guarantees](#stability-guarantees) +3. [Why Formplayer Restricts JSON Schema](#why-formplayer-restricts-json-schema) +4. [Entry Point & Initialization](#entry-point--initialization) +5. [JSON Schema Support](#json-schema-support) +6. [UI Schema Structure](#ui-schema-structure) +7. [Validation Layer](#validation-layer) +8. [Custom Formats & Renderers](#custom-formats--renderers) +9. [Rules & Conditional Logic](#rules--conditional-logic) +10. [Renderer Boundaries](#renderer-boundaries) +11. [Error Patterns & Solutions](#error-patterns--solutions) +12. [Safe vs Unsafe Patterns](#safe-vs-unsafe-patterns) +13. [Examples](#examples) +14. [Future Evolution (Non-Contractual)](#future-evolution-non-contractual) + +--- + +## Overview + +**Formplayer** is a React-based form rendering engine that uses: +- **JSON Forms 3.5.1** for form rendering +- **JSON Schema Draft-07** for data validation +- **Ajv 8.17.1** for schema validation +- **Material UI** for component rendering + +**Key Characteristics:** +- All forms are normalized to `SwipeLayout` root (auto-wrapped if needed) +- `Finalize` element is automatically appended to all forms +- Validation occurs both pre-deployment (static) and runtime (dynamic) +- Custom formats require custom renderers registered in the renderer chain + +--- + +## Stability Guarantees + +**Formplayer guarantees correct behavior only when the contract in this document is followed.** + +Forms that violate the contract may: +- Crash at runtime (e.g., "Cannot read properties of undefined") +- Render partially or incorrectly +- Exhibit undefined rule behavior +- Pass validation but fail during rendering +- Produce unexpected validation errors (e.g., "minimum value must be ['number']") + +**Such behavior is considered out of contract and will not be treated as a Formplayer bug.** + +This contract defines the supported subset of JSON Schema and JSON Forms. Forms that exceed these boundaries operate at their own risk. Maintainers should reject forms that violate the contract during pre-deployment validation. + +**For maintainers:** When investigating form-related crashes or errors, first verify the form adheres to this contract. Issues arising from contract violations are not bugs in Formplayer itself. + +--- + +## Why Formplayer Restricts JSON Schema + +Although Formplayer uses JSON Schema Draft-07, it intentionally supports only a subset of the full specification. + +**Reasons for Restrictions:** + +1. **Mobile Performance Constraints** + - Complex schema evaluation (e.g., `$data` references, dynamic `$ref` resolution) is computationally expensive + - Mobile devices need predictable, fast form rendering + - Offline-first execution requires static schema analysis + +2. **Predictable Rendering** + - Formplayer needs to know renderer selection at schema load time + - Dynamic schema features make renderer selection ambiguous + - Predictable structure enables better error handling and user experience + +3. **Offline-First Execution** + - Forms must work without network connectivity + - External `$ref` resolution requires network access + - Static schema validation can be done pre-deployment + +4. **Simpler Mental Model for Form Authors** + - Research teams, medical staff, and field workers need straightforward form design + - Complex JSON Schema features create cognitive overhead + - Clear contract reduces support burden + +5. **Avoiding Runtime Schema Evaluation** + - `$data` references require runtime schema evaluation + - This creates unpredictable validation behavior + - Literal values enable compile-time validation + +**This is a design choice, not a limitation of JSON Schema itself.** + +JSON Schema Draft-07 supports many features that Formplayer intentionally does not. This restriction is by design to ensure reliable, performant form rendering across diverse use cases (research tools, medical records, CHT-like flows, bespoke apps). + +--- + +## Entry Point & Initialization + +### Entry Files + +- **`src/index.tsx`**: React app entry point +- **`src/App.tsx`**: Main component with form initialization logic + +### Initialization Flow + +```typescript +// 1. Form data received via window.onFormInit +// 2. initializeForm() called with FormInitData +// 3. Schema normalization: + setSchema(formSchema) + const swipeLayoutUISchema = ensureSwipeLayoutRoot(uiSchema) + const processedUISchema = processUISchemaWithFinalize(swipeLayoutUISchema) + setUISchema(processedUISchema) +// 4. JsonForms component renders with normalized schemas +``` + +### Auto-Wrapping Behavior + +**Function:** `ensureSwipeLayoutRoot()` (App.tsx:61-103) + +The UI schema root is **always** normalized to `SwipeLayout`: + +| Input | Output | +|-------|--------| +| `null` or `undefined` | `{ type: 'SwipeLayout', elements: [] }` | +| `{ type: 'SwipeLayout', ... }` | Returns as-is | +| `{ type: 'Group', ... }` | Wrapped: `{ type: 'SwipeLayout', elements: [original] }` | +| `{ type: 'VerticalLayout', ... }` | Wrapped: `{ type: 'SwipeLayout', elements: [original] }` | +| `{ type: 'HorizontalLayout', ... }` | Wrapped: `{ type: 'SwipeLayout', elements: [original] }` | +| Array of elements | Wrapped: `{ type: 'SwipeLayout', elements: array }` | + +**Guarantee:** The root UI schema will always be `SwipeLayout` before rendering. + +### Finalize Element Injection + +**Function:** `processUISchemaWithFinalize()` (App.tsx:106-148) + +**Behavior:** +- Removes any existing `Finalize` elements (with warning) +- Always appends `{ type: 'Finalize' }` as the last element +- If no `elements` array exists, creates `VerticalLayout` with just `Finalize` + +**Guarantee:** Every form will have exactly one `Finalize` element as the last page. + +### Validation vs Rendering + +**Pre-Deployment Validation:** +- Static validation via `validate-forms.js` scripts +- Checks schema structure, UI schema format, field references +- **Fails early** - prevents invalid forms from being deployed + +**Runtime Validation:** +- Dynamic validation via Ajv during form interaction +- Validates data against schema (`validationMode="ValidateAndShow"`) +- **Fails late** - shows errors as user interacts with form + +**Rendering:** +- Occurs after normalization +- Uses `JsonForms` component with custom renderers +- Assumes normalized structure (SwipeLayout root, Finalize present) + +--- + +## JSON Schema Support + +### Supported JSON Schema Draft-07 Features + +#### ✅ Fully Supported + +**Root Properties:** +- `$schema`: Must be `"http://json-schema.org/draft-07/schema#"` +- `type`: Must be `"object"` at root level +- `properties`: Required object containing field definitions +- `required`: Array of required field names +- `title`: Form title (displayed in UI) +- `description`: Form description + +**Data Types:** +- `string`, `integer`, `number`, `boolean`, `object`, `array` + +**String Validation:** +- `minLength`: Minimum string length (integer) +- `maxLength`: Maximum string length (integer) +- `pattern`: Regex pattern (string) +- `format`: Built-in formats (see [Custom Formats](#custom-formats--renderers)) + +**Number Validation:** +- `minimum`: Minimum value (**literal number only**) +- `maximum`: Maximum value (**literal number only**) +- `exclusiveMinimum`: Exclusive minimum (**literal number only**) +- `exclusiveMaximum`: Exclusive maximum (**literal number only**) +- `multipleOf`: Must be multiple of value (**literal number only**) + +**Array Validation:** +- `items`: Schema for array items +- `minItems`: Minimum array length +- `maxItems`: Maximum array length +- `uniqueItems`: Boolean (enforces uniqueness) + +**Selection Fields:** +- `enum`: Array of allowed values +- `oneOf`: Array of `{ const: value, title: "Label" }` objects (**recommended**) +- `const`: Single constant value + +**Object Nesting:** +- `type: "object"` with `properties` object +- Nested objects require explicit UI schema (see [Object Handling](#object-handling)) + +**Advanced Features:** +- `allOf`: Array of schemas (all must match) +- `if/then/else`: Conditional schema validation (supported in schema, not in rule conditions) + +#### ❌ NOT Supported / Unsafe + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes all validation keywords (`minimum`, `maximum`, etc.) contain literal values. +> `$data` references or dynamic values will cause Ajv validation errors at runtime. +> Always use literal numbers, strings, or arrays for validation constraints. + +**Unsupported Features:** +- `$data` references: **Will cause runtime errors** + - Error: `"minimum value must be ['number']"` + - Use literal values only +- `$ref` to external schemas: Not validated or resolved +- `anyOf`: Not explicitly supported (use `oneOf` instead) +- `$defs`/`definitions`: Not resolved +- Dynamic constraints: All validation values must be literals + +**Unsafe Patterns:** +```json +// ❌ UNSAFE - $data reference +{ + "type": "integer", + "minimum": { "$data": "/otherField" } +} + +// ✅ SAFE - Literal value +{ + "type": "integer", + "minimum": 0 +} +``` + +### Validator Configuration + +**Ajv Setup** (App.tsx:537-542): +```typescript +const ajv = new Ajv({ + allErrors: true, // Collect all errors, not just first + strictTypes: false, // Allow custom formats without strict type checking +}); +addErrors(ajv); // Enhanced error messages +addFormats(ajv); // Standard format validators (date, email, etc.) +// Custom formats registered separately (see Custom Formats section) +``` + +--- + +## UI Schema Structure + +### Root Layout Types + +**Valid Root Types** (before auto-wrapping): +- `SwipeLayout` (recommended, explicit) +- `VerticalLayout` (auto-wrapped to SwipeLayout) +- `HorizontalLayout` (auto-wrapped to SwipeLayout) +- `Group` (auto-wrapped to SwipeLayout) + +**After Normalization:** +- Always `SwipeLayout` at root + +### Element Types + +#### SwipeLayout + +**Structure:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { "type": "Group", "label": "Page 1", "elements": [...] }, + { "type": "Group", "label": "Page 2", "elements": [...] } + ] +} +``` + +**Required:** +- `type`: `"SwipeLayout"` +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Creates swipeable pages +- Each element in `elements` becomes a page +- Progress indicator shows current page +- Navigation buttons (Previous/Next) provided automatically + +#### VerticalLayout + +**Structure:** +```json +{ + "type": "VerticalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" }, + { "type": "Control", "scope": "#/properties/field2" } + ] +} +``` + +**Required:** +- `type`: `"VerticalLayout"` +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Arranges elements vertically +- Scrollable if content exceeds viewport +- Commonly used inside SwipeLayout pages + +#### HorizontalLayout + +**Structure:** +```json +{ + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" }, + { "type": "Control", "scope": "#/properties/field2" } + ] +} +``` + +**Required:** +- `type`: `"HorizontalLayout"` +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Arranges elements horizontally +- Useful for side-by-side fields + +#### Group + +**Structure:** +```json +{ + "type": "Group", + "label": "Section Title", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" } + ] +} +``` + +**Required:** +- `type`: `"Group"` +- `label`: String (section header) +- `elements`: Array of child elements (can be empty, but must exist) + +**Behavior:** +- Groups related controls +- Displays label as section header +- Can be used as SwipeLayout page (auto-converted) + +#### Control + +**Structure:** +```json +{ + "type": "Control", + "scope": "#/properties/fieldName", + "label": "Custom Label", // Optional, overrides schema title + "options": { // Optional renderer options + "multi": true + }, + "rule": { // Optional conditional logic + "effect": "SHOW", + "condition": { ... } + } +} +``` + +**Required:** +- `type`: `"Control"` +- `scope`: JSON Pointer to schema property (must start with `#/properties/`) + +**Optional:** +- `label`: String or `false` (to hide label) +- `options`: Object with renderer-specific options +- `rule`: Conditional display/enable rule + +**Scope Format:** +- Must start with `#/properties/` +- Examples: + - `"#/properties/name"` - Root property + - `"#/properties/person/properties/age"` - Nested property (requires explicit UI schema) + +#### Label + +**Structure:** +```json +{ + "type": "Label", + "text": "Instructions or information" +} +``` + +**Required:** +- `type`: `"Label"` +- `text`: String to display + +**Behavior:** +- Static text display +- No `elements` array needed +- Useful for instructions or section headers + +### Critical Requirements + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes every layout element has an `elements` array. +> Missing this will cause runtime crashes in some render paths (e.g., "Cannot read properties of undefined (reading 'find')"). +> Always include `elements: []` even if the layout is empty. + +**All Layouts Must Have `elements` Array:** +```json +// ❌ UNSAFE - Missing elements +{ + "type": "Group", + "label": "Section" +} + +// ✅ SAFE - elements array present (even if empty) +{ + "type": "Group", + "label": "Section", + "elements": [] +} +``` + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes all `Control.scope` paths exist in the schema. +> Invalid scopes may cause runtime crashes or silent rendering failures. +> Always validate scope paths exist before deployment. + +**All Scopes Must Exist in Schema:** +- Every `Control.scope` must reference a property in `schema.properties` +- Validation script checks this pre-deployment +- Runtime crashes if scope doesn't exist + +**Nested Objects Require Explicit UI Schema:** +- Object-typed properties don't auto-render nested fields +- Must provide UI schema for each nested property +- See [Object Handling](#object-handling) section + +--- + +## Validation Layer + +### Pre-Deployment Validation + +**Script:** `validate-forms.js` (found in app directories) + +**What's Enforced:** + +#### Schema Validation + +1. **Structure Checks:** + - `$schema` must be `"http://json-schema.org/draft-07/schema#"` + - Root `type` must be `"object"` + - Must have `properties` object + +2. **Compilation Check:** + - Schema must compile with Ajv (structural validity) + - Catches syntax errors, invalid keywords + +#### UI Schema Validation + +1. **Root Type:** + - Must be `SwipeLayout`, `VerticalLayout`, or `HorizontalLayout` + - (Note: Auto-wrapping happens at runtime, but validation checks input) + +2. **Element Types:** + - Valid types: `Control`, `Label`, `VerticalLayout`, `HorizontalLayout`, `SwipeLayout` + - Invalid types are flagged + +3. **Control Elements:** + - Must have `scope` property + - `scope` must start with `#/properties/` + +4. **Layout Elements:** + - `VerticalLayout`, `HorizontalLayout`, `SwipeLayout` must have `elements` array + - Missing `elements` is flagged as error + +5. **Rules:** + - `rule.effect` must be `SHOW`, `HIDE`, `ENABLE`, or `DISABLE` + - `rule.condition.scope` must start with `#/properties/` + +#### Field Reference Validation + +- All `Control.scope` paths must exist in schema properties +- All `rule.condition.scope` paths must exist in schema properties +- Prevents runtime crashes from invalid references + +### Runtime Validation + +**When:** During form interaction (`validationMode="ValidateAndShow"`) + +**What's Validated:** +- Data values against schema constraints +- Required fields +- Type constraints (string, number, etc.) +- Format validators (date, email, etc.) +- Custom format validators (photo, gps, etc.) + +**What's NOT Validated:** +- Schema structure (assumed valid from pre-deployment) +- UI schema structure (assumed valid from pre-deployment) +- Field existence (assumed valid from pre-deployment) + +### What's NOT Enforced (Runtime Only) + +These issues are **not caught** by validation scripts but **will cause runtime errors**: + +1. **`$data` References:** + - Validation script doesn't check for `$data` + - Runtime error: `"minimum value must be ['number']"` + +2. **Missing `elements` on Nested Layouts:** + - Validation only checks root and direct children + - Deeply nested missing `elements` may not be caught + - Runtime error: `"Cannot read properties of undefined (reading 'find')"` + +3. **Invalid Rule Scopes:** + - Validation checks scope format, not existence in all cases + - May cause unexpected rule behavior + +--- + +## Custom Formats & Renderers + +### Supported Custom Formats + +All custom formats are registered in Ajv and have corresponding custom renderers: + +| Format | Schema Type | Renderer | Description | +|--------|-------------|----------|-------------| +| `photo` | `object` | `PhotoQuestionRenderer` | Camera capture | +| `gps` | `string` or `object` | `GPSQuestionRenderer` | GPS coordinates | +| `signature` | `object` | `SignatureQuestionRenderer` | Signature pad | +| `qrcode` | `string` or `object` | `QrcodeQuestionRenderer` | Barcode scanner | +| `audio` | `string` or `object` | `AudioQuestionRenderer` | Audio recording | +| `video` | `string` or `object` | `VideoQuestionRenderer` | Video recording | +| `select_file` | `string` or `object` | `FileQuestionRenderer` | File picker | + +### Format Registration + +**Ajv Registration** (App.tsx:544-551): +```typescript +ajv.addFormat('photo', () => true); // Accepts any value +ajv.addFormat('qrcode', () => true); +ajv.addFormat('signature', () => true); +ajv.addFormat('select_file', () => true); +ajv.addFormat('audio', () => true); +ajv.addFormat('gps', () => true); +ajv.addFormat('video', () => true); +``` + +**Renderer Registration** (App.tsx:164-175): +```typescript +export const customRenderers = [ + { tester: photoQuestionTester, renderer: PhotoQuestionRenderer }, + { tester: qrcodeQuestionTester, renderer: QrcodeQuestionRenderer }, + { tester: signatureQuestionTester, renderer: SignatureQuestionRenderer }, + { tester: fileQuestionTester, renderer: FileQuestionRenderer }, + { tester: audioQuestionTester, renderer: AudioQuestionRenderer }, + { tester: gpsQuestionTester, renderer: GPSQuestionRenderer }, + { tester: videoQuestionTester, renderer: VideoQuestionRenderer }, +]; +``` + +### Format Requirements + +**Photo:** +```json +// Schema +{ + "patient_photo": { + "type": "object", + "format": "photo", + "title": "Patient Photo" + } +} + +// UI Schema +{ + "type": "Control", + "scope": "#/properties/patient_photo" +} +``` + +**GPS:** +```json +// Schema (string format) +{ + "location": { + "type": "string", + "format": "gps", + "title": "Location" + } +} + +// OR (object format) +{ + "location": { + "type": "object", + "format": "gps", + "title": "Location" + } +} + +// UI Schema +{ + "type": "Control", + "scope": "#/properties/location" +} +``` + +**Key Points:** +- ✅ No special UI schema required (standard `Control` is sufficient) +- ✅ Format must be specified in schema +- ✅ Type can be `string` or `object` (depends on format) +- ✅ Custom renderer handles all UI and interaction + +--- + +## Rules & Conditional Logic + +### Rule Structure + +```json +{ + "type": "Control", + "scope": "#/properties/targetField", + "rule": { + "effect": "SHOW|HIDE|ENABLE|DISABLE", + "condition": { + "scope": "#/properties/sourceField", + "schema": { + // Condition schema (const, enum, etc.) + } + } + } +} +``` + +### Rule Effects + +| Effect | Behavior | +|--------|----------| +| `SHOW` | Element is hidden until condition is true | +| `HIDE` | Element is shown until condition is true | +| `ENABLE` | Element is disabled until condition is true | +| `DISABLE` | Element is enabled until condition is true | + +### Condition Schema + +**Supported Condition Types:** + +1. **Constant Match:** +```json +{ + "condition": { + "scope": "#/properties/field", + "schema": { "const": "value" } + } +} +``` + +2. **Enum Match:** +```json +{ + "condition": { + "scope": "#/properties/field", + "schema": { "enum": ["value1", "value2"] } + } +} +``` + +3. **Boolean Match:** +```json +{ + "condition": { + "scope": "#/properties/field", + "schema": { "const": true } + } +} +``` + +### Rule Evaluation + +**How Rules Work:** +- Rules are evaluated by JSON Forms core (not custom Formplayer code) +- Condition is evaluated by validating the referenced field's value against `condition.schema` +- If validation passes, effect is applied +- Evaluation happens on every data change + +**Undefined Scope Behavior:** +- JSON Forms treats undefined scopes as condition success (by default) +- Formplayer doesn't add defensive checks +- **Best Practice:** Ensure all rule condition scopes exist in schema + +> ⚠️ **HARD RUNTIME ASSUMPTION** +> +> Formplayer assumes all rule condition scopes exist in the schema. +> Invalid scopes may cause rules to fail silently or exhibit undefined behavior. +> JSON Forms treats undefined scopes as condition success by default, which may not match intended behavior. + +### Critical Requirements + +**Rule Condition Scope Must Exist:** +```json +// ❌ UNSAFE - Scope doesn't exist +{ + "rule": { + "condition": { + "scope": "#/properties/nonexistent", // Field doesn't exist + "schema": { "const": "value" } + } + } +} + +// ✅ SAFE - Scope exists in schema +{ + "schema": { + "properties": { + "hasConsent": { "type": "boolean" } + } + }, + "uischema": { + "rule": { + "condition": { + "scope": "#/properties/hasConsent", // Field exists + "schema": { "const": true } + } + } + } +} +``` + +**Rule Condition Schema Must Be Simple:** +```json +// ❌ UNSAFE - Complex schema not supported +{ + "condition": { + "schema": { + "if": { "type": "string" }, + "then": { "const": "value" } + } + } +} + +// ✅ SAFE - Simple const or enum +{ + "condition": { + "schema": { "const": "value" } + } +} +``` + +### Rule Examples + +**Show When Value Equals:** +```json +{ + "type": "Control", + "scope": "#/properties/detailField", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/showDetail", + "schema": { "const": true } + } + } +} +``` + +**Hide When Value Equals:** +```json +{ + "type": "Control", + "scope": "#/properties/skipReason", + "rule": { + "effect": "HIDE", + "condition": { + "scope": "#/properties/completed", + "schema": { "const": true } + } + } +} +``` + +**Show When One of Multiple Values:** +```json +{ + "type": "Control", + "scope": "#/properties/referralForm", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/testResult", + "schema": { "enum": ["positive", "inconclusive"] } + } + } +} +``` + +**Group-Level Rules:** +```json +{ + "type": "Group", + "label": "TB Screening", + "elements": [...], + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/screeningType", + "schema": { "const": "tb" } + } + } +} +``` + +--- + +## Renderer Boundaries + +### SwipeLayoutRenderer + +**File:** `SwipeLayoutRenderer.tsx` + +**Critical Assumptions:** +- `uischema.elements` exists (defensive: `|| []` at line 53) +- `layouts[currentPage]` exists (uses optional chaining at line 89) +- `layouts.length > 0` checked before rendering (line 126) + +**What Must Exist:** +- `uischema.type` (checked at line 48) +- `uischema.elements` (defensive fallback to `[]`) + +**Where Undefined Causes Crashes:** +- If `uischema` is `null` or `undefined` (shouldn't happen after normalization) +- If `layouts[currentPage]` is accessed when `currentPage >= layouts.length` (guarded by length check) + +### FinalizeRenderer + +**File:** `FinalizeRenderer.tsx` + +**Critical Assumptions:** +- `fullUISchema.elements` exists (guarded at line 139) +- `screen.elements` exists before iteration (guarded at line 153) +- `fullSchema.properties` exists (guarded at line 177) + +**What Must Exist:** +- `fullUISchema` and `fullUISchema.elements` (guarded) +- `screen.elements` for each screen (guarded with `'elements' in screen`) +- `fullSchema.properties` (guarded) + +**Where Undefined Causes Crashes:** +- `screen.elements.find()` if `screen.elements` is undefined (guarded at line 153) +- `fullUISchema.elements.forEach()` if `elements` is missing (guarded at line 139) +- `schema.properties[key]` if `properties` is missing (guarded at line 177) + +### Structural Assumptions + +**Guaranteed by Normalization:** +1. Root is always `SwipeLayout` (via `ensureSwipeLayoutRoot()`) +2. `Finalize` element always present (via `processUISchemaWithFinalize()`) +3. Root has `elements` array (created if missing) + +**Not Guaranteed (Must Be Enforced):** +1. Nested layouts have `elements` arrays (validation script checks, but deep nesting may be missed) +2. All `Control.scope` paths exist (validation script checks) +3. All `rule.condition.scope` paths exist (validation script checks format, not always existence) + +**Defensive Checks in Code:** +- Most renderers use optional chaining (`?.`) and fallbacks (`|| []`) +- Some code paths assume structure exists (crashes if assumption fails) +- **Best Practice:** Always include `elements: []` even if empty + +--- + +## Error Patterns & Solutions + +### Error: "minimum value must be ['number']" + +**Cause:** +- Using `$data` reference or non-numeric value for `minimum`/`maximum` +- Ajv expects literal numbers, not references + +**Example:** +```json +// ❌ CAUSES ERROR +{ + "type": "integer", + "minimum": { "$data": "/otherField" } +} + +// ✅ FIX +{ + "type": "integer", + "minimum": 0 +} +``` + +**Solution:** +- Use literal numbers only for `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` +- If dynamic constraints needed, handle at application level, not schema level + +### Error: "Cannot read properties of undefined (reading 'find')" + +**Cause:** +- Accessing `.find()` or other array methods on undefined `elements` array +- Layout element missing `elements` property + +**Example:** +```json +// ❌ CAUSES ERROR +{ + "type": "Group", + "label": "Section" + // Missing "elements" array +} + +// ✅ FIX +{ + "type": "Group", + "label": "Section", + "elements": [] // Always include, even if empty +} +``` + +**Solution:** +- Always include `elements: []` for all layout types +- Validation script should catch this, but check nested layouts manually +- Defensive code exists in some renderers, but not all paths are protected + +### Error: Rule Condition Fails Unexpectedly + +**Cause:** +- Rule condition scope doesn't exist in schema +- Rule condition scope points to undefined data +- Complex condition schema not supported + +**Example:** +```json +// ❌ CAUSES ISSUES +{ + "rule": { + "condition": { + "scope": "#/properties/nonexistent", // Field doesn't exist + "schema": { "const": "value" } + } + } +} + +// ✅ FIX +// Ensure field exists in schema: +{ + "schema": { + "properties": { + "hasConsent": { "type": "boolean" } + } + }, + "uischema": { + "rule": { + "condition": { + "scope": "#/properties/hasConsent", // Field exists + "schema": { "const": true } + } + } + } +} +``` + +**Solution:** +- Validate all rule condition scopes exist in schema +- Use simple condition schemas (`const` or `enum`) +- Test rules with missing data to verify behavior + +### Error: Object Properties Not Rendering + +**Cause:** +- Object-typed property without explicit UI schema for nested fields +- JSON Forms doesn't auto-render nested object properties + +**Example:** +```json +// ❌ NESTED FIELDS WON'T RENDER +{ + "schema": { + "properties": { + "gps_location": { + "type": "object", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" } + } + } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/gps_location" + // Missing UI schema for nested fields + } +} + +// ✅ FIX - Explicit UI Schema +{ + "uischema": { + "type": "Group", + "label": "GPS Location", + "elements": [ + { "type": "Control", "scope": "#/properties/gps_location/properties/latitude" }, + { "type": "Control", "scope": "#/properties/gps_location/properties/longitude" } + ] + } +} +``` + +**Solution:** +- Provide explicit UI schema for nested object properties +- Use `Group` or `VerticalLayout` to organize nested fields +- Exception: Custom format objects (photo, gps, etc.) don't need nested UI schema + +--- + +## Safe vs Unsafe Patterns + +### Schema Patterns + +#### ✅ Safe: Literal Validation Values +```json +{ + "type": "integer", + "minimum": 0, + "maximum": 100 +} +``` + +#### ❌ Unsafe: $data References +```json +{ + "type": "integer", + "minimum": { "$data": "/otherField" } +} +``` + +#### ✅ Safe: oneOf with const + title +```json +{ + "type": "string", + "oneOf": [ + { "const": "value1", "title": "Display 1" }, + { "const": "value2", "title": "Display 2" } + ] +} +``` + +#### ⚠️ Works but Less Flexible: enum +```json +{ + "type": "string", + "enum": ["value1", "value2"] +} +``` + +### UI Schema Patterns + +#### ✅ Safe: Complete Layout Structure +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "Group", + "label": "Section", + "elements": [ + { "type": "Control", "scope": "#/properties/field1" } + ] + } + ] +} +``` + +#### ❌ Unsafe: Missing elements Array +```json +{ + "type": "Group", + "label": "Section" + // Missing "elements" +} +``` + +#### ✅ Safe: Valid Scope References +```json +{ + "type": "Control", + "scope": "#/properties/existingField" +} +``` + +#### ❌ Unsafe: Invalid Scope References +```json +{ + "type": "Control", + "scope": "#/properties/nonexistentField" +} +``` + +### Rule Patterns + +#### ✅ Safe: Rule with Existing Scope +```json +{ + "schema": { + "properties": { + "hasConsent": { "type": "boolean" } + } + }, + "uischema": { + "rule": { + "condition": { + "scope": "#/properties/hasConsent", + "schema": { "const": true } + } + } + } +} +``` + +#### ❌ Unsafe: Rule with Missing Scope +```json +{ + "rule": { + "condition": { + "scope": "#/properties/nonexistent", + "schema": { "const": "value" } + } + } +} +``` + +#### ✅ Safe: Simple Condition Schema +```json +{ + "condition": { + "schema": { "const": "value" } + } +} +``` + +#### ❌ Unsafe: Complex Condition Schema +```json +{ + "condition": { + "schema": { + "if": { "type": "string" }, + "then": { "const": "value" } + } + } +} +``` + +### Object Handling Patterns + +#### ✅ Safe: Custom Format Object (No Nested UI Needed) +```json +{ + "schema": { + "patient_photo": { + "type": "object", + "format": "photo" + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/patient_photo" + } +} +``` + +#### ✅ Safe: Regular Object with Explicit UI Schema +```json +{ + "schema": { + "gps_location": { + "type": "object", + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" } + } + } + }, + "uischema": { + "type": "Group", + "label": "GPS Location", + "elements": [ + { "type": "Control", "scope": "#/properties/gps_location/properties/latitude" }, + { "type": "Control", "scope": "#/properties/gps_location/properties/longitude" } + ] + } +} +``` + +#### ❌ Unsafe: Regular Object without UI Schema +```json +{ + "schema": { + "gps_location": { + "type": "object", + "properties": { + "latitude": { "type": "number" } + } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/gps_location" + // Nested fields won't render + } +} +``` + +--- + +## Examples + +### Example 1: Complete Working Form + +**File:** `demos/demo_malaria_screening/forms/registration/` + +**Schema (`schema.json`):** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Patient Registration", + "type": "object", + "properties": { + "full_name": { + "type": "string", + "title": "Full Name" + }, + "gender": { + "type": "integer", + "title": "Gender", + "oneOf": [ + { "const": 1, "title": "Male" }, + { "const": 2, "title": "Female" } + ] + }, + "age_years": { + "type": "integer", + "title": "Age in years", + "minimum": 0 + }, + "temperature_c": { + "type": "number", + "title": "Temperature (°C)", + "exclusiveMinimum": 25, + "exclusiveMaximum": 46 + } + }, + "required": ["full_name", "gender"] +} +``` + +**UI Schema (`ui.json`):** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/full_name" + }, + { + "type": "Control", + "scope": "#/properties/gender" + }, + { + "type": "Control", + "scope": "#/properties/age_years" + } + ] + }, + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/temperature_c" + } + ] + } + ] +} +``` + +**Why This Works:** +- ✅ Proper `$schema` declaration +- ✅ Root `type: "object"` with `properties` +- ✅ Literal validation values (`minimum`, `exclusiveMinimum`) +- ✅ `oneOf` with `const` + `title` for choices +- ✅ `SwipeLayout` root with `elements` arrays +- ✅ All `Control.scope` paths exist in schema +- ✅ All layouts have `elements` arrays + +### Example 2: Form with Conditional Logic + +**Schema:** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Health Screening", + "type": "object", + "properties": { + "has_cough": { + "type": "string", + "title": "Does patient have cough?", + "oneOf": [ + { "const": "yes", "title": "Yes" }, + { "const": "no", "title": "No" } + ] + }, + "cough_duration": { + "type": "string", + "title": "Cough duration", + "oneOf": [ + { "const": "less_2_weeks", "title": "Less than 2 weeks" }, + { "const": "2_weeks_plus", "title": "2 weeks or more" } + ] + } + }, + "required": ["has_cough"] +} +``` + +**UI Schema:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/has_cough" + }, + { + "type": "Control", + "scope": "#/properties/cough_duration", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/has_cough", + "schema": { "const": "yes" } + } + } + } + ] + } + ] +} +``` + +**Why This Works:** +- ✅ Rule condition scope (`has_cough`) exists in schema +- ✅ Simple condition schema (`const`) +- ✅ Proper rule structure with `effect` and `condition` + +### Example 3: Form with Custom Format + +**Schema:** +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Patient Intake", + "type": "object", + "properties": { + "patient_photo": { + "type": "object", + "format": "photo", + "title": "Patient Photo" + }, + "location": { + "type": "string", + "format": "gps", + "title": "Location" + } + } +} +``` + +**UI Schema:** +```json +{ + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/patient_photo" + }, + { + "type": "Control", + "scope": "#/properties/location" + } + ] + } + ] +} +``` + +**Why This Works:** +- ✅ Custom formats registered in Ajv +- ✅ Custom renderers registered in renderer chain +- ✅ Standard `Control` elements (no special UI schema needed) +- ✅ Format specified in schema + +--- + +## Summary Checklist + +When creating a form, ensure: + +### Schema Checklist +- [ ] `$schema` is `"http://json-schema.org/draft-07/schema#"` +- [ ] Root `type` is `"object"` +- [ ] Has `properties` object +- [ ] All validation values are literals (no `$data`) +- [ ] Use `oneOf` with `const` + `title` for choices (recommended) +- [ ] Nested objects have explicit UI schema (unless custom format) + +### UI Schema Checklist +- [ ] Root is `SwipeLayout` (or will be auto-wrapped) +- [ ] All layouts have `elements` arrays (even if empty) +- [ ] All `Control.scope` paths exist in schema +- [ ] All `Group` elements have `label` +- [ ] Rules reference existing fields +- [ ] Rule condition schemas are simple (`const` or `enum`) + +### Validation Checklist +- [ ] Run `validate-forms.js` script before deployment +- [ ] All field references validated +- [ ] No `$data` references +- [ ] All `elements` arrays present +- [ ] All scopes valid + +--- + +## Future Evolution (Non-Contractual) + +**This section describes potential future improvements that are not currently guaranteed and should not be relied upon.** + +These are areas where Formplayer might evolve as the project matures: + +### Potential Enhancements + +1. **Schema Pre-Validation for Unsupported Keywords** + - Early detection of `$data`, `$ref` to externals, and other unsupported features + - Compile-time rejection of out-of-contract schemas + - Clearer error messages pointing to contract violations + +2. **Stronger UI Schema Structural Validation** + - Deep validation of nested layouts (not just root level) + - Guaranteed `elements` array presence at all nesting levels + - Validation of rule condition scope existence + +3. **Defensive Guards in Renderers** + - More comprehensive optional chaining and fallbacks + - Graceful degradation instead of crashes + - Better error messages when assumptions fail + +4. **Formal Form Compilation Step** + - Pre-compilation of forms to validate contract compliance + - Optimization of renderer selection + - Static analysis of rule conditions + +5. **Extended JSON Schema Support** + - Potential support for `$data` references (with performance trade-offs) + - Support for external `$ref` resolution (with network requirements) + - More complex conditional schemas in rules + +**Important:** These are not commitments. Forms should be designed to work with the current contract. Future enhancements may expand the contract, but backward compatibility with the current contract will be maintained. + +**For maintainers:** When considering new features, evaluate them against: +- Performance impact on mobile devices +- Complexity for form authors +- Offline-first requirements +- Backward compatibility with existing forms + +--- + +## Version History + +- **v1.0** (2024): Initial contract document based on Formplayer codebase analysis + +--- + +## References + +- **JSON Forms Documentation:** https://jsonforms.io +- **JSON Schema Draft-07:** https://json-schema.org/draft-07/schema# +- **Ajv Validator:** https://ajv.js.org +- **Formplayer Source:** `ode/formulus-formplayer/src/` + +--- + +**End of Contract** + diff --git a/versioned_docs/version-1.0/reference/formplayer.md b/versioned_docs/version-1.0/reference/formplayer.md new file mode 100644 index 0000000..79a914e --- /dev/null +++ b/versioned_docs/version-1.0/reference/formplayer.md @@ -0,0 +1,506 @@ +--- +sidebar_position: 6 +--- + +# Formplayer Component Reference + +Complete technical reference for the Formplayer form rendering component. + +:::info[Authoritative Reference Available] + +For the complete, authoritative contract defining all supported JSON Schema features, UI schema structure, validation rules, and behavioral guarantees, see the **[Formplayer Contract](/docs/reference/formplayer-contract)**. + +::: + +## Overview + +Formplayer is a React web application that renders JSON Forms and provides the dynamic form interface for data collection. It runs within WebViews in the Formulus mobile app and communicates with the native app through a JavaScript bridge. + +## Supported Schema & UI Profile + +**Critical**: ODE Formplayer intentionally supports a safe, predictable subset of JSON Schema and JSON Forms. Forms outside this profile may load but are **not guaranteed to work**. + +### Supported JSON Schema Features + +| Feature | Supported | Notes | +|---------|-----------|-------| +| `type` | ✅ | `string`, `number`, `integer`, `boolean`, `object`, `array` | +| `properties` | ✅ | Object property definitions | +| `required` | ✅ | Array of required property names | +| `minimum` / `maximum` | ✅ | Literal numbers only (e.g., `"minimum": 0`) | +| `minLength` / `maxLength` | ✅ | String length constraints | +| `pattern` | ✅ | Regular expression patterns | +| `format` | ✅ | `email`, `date`, `date-time`, `uri`, `uuid` | +| `enum` | ✅ | Array of allowed values | +| `enumNames` | ✅ | Display names for enum values | +| `oneOf` | ✅ | Recommended for single-choice selections | +| `title` | ✅ | Field display title | +| `description` | ✅ | Field description/help text | +| `default` | ✅ | Default values | +| `const` | ✅ | Constant values (used in rules) | +| `$data` | ❌ | **Will crash** - not supported | +| `if` / `then` / `else` | ❌ | **Not supported** in rule conditions | +| `$ref` | ⚠️ | Limited support - use with caution | +| `allOf` / `anyOf` | ⚠️ | Limited support - use with caution | + +### Supported UI Schema Elements + +| Element | Required Fields | Notes | +|---------|----------------|-------| +| **SwipeLayout** | `type`, `elements[]` | Root layout (required or auto-wrapped) | +| **VerticalLayout** | `type`, `elements[]` | Vertical field arrangement | +| **HorizontalLayout** | `type`, `elements[]` | Horizontal field arrangement | +| **Group** | `type`, `label`, `elements[]` | Grouped fields with label | +| **Control** | `type`, `scope` | Field control (scope must exist in schema) | +| **Label** | `type`, `text` | Text label element | + +### Unsupported / Unsafe Features + +**❌ JSON Schema:** +- `$data` references (dynamic values) +- `if`/`then`/`else` conditional schemas +- Complex `$ref` resolution +- `allOf`/`anyOf` (limited support) + +**❌ UI Schema:** +- Missing `elements` array in layouts +- Invalid `scope` paths (referencing non-existent schema properties) +- Rules referencing missing fields +- Nested SwipeLayout +- `Categorization` layout (not recommended) + +**⚠️ Common Pitfalls:** +- Using `$data` in `minimum`/`maximum` (use literal numbers) +- Rule conditions with scopes that don't exist in schema +- Controls with scopes pointing to non-existent properties +- Missing `elements` array in SwipeLayout or other layouts + +### Safe Form Patterns + +**✅ Recommended Structure:** +```json +{ + "schema": { + "type": "object", + "properties": { + "field1": { "type": "string", "title": "Field 1" }, + "field2": { "type": "integer", "minimum": 0, "maximum": 100 } + }, + "required": ["field1"] + }, + "uischema": { + "type": "SwipeLayout", + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/field1" + }, + { + "type": "Control", + "scope": "#/properties/field2" + } + ] + } + ] + } +} +``` + +**❌ Unsafe Patterns:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/missingField" // ❌ Field doesn't exist + } +} +``` + +```json +{ + "schema": { + "properties": { + "value": { + "type": "number", + "minimum": { "$data": "#/minValue" } // ❌ $data not supported + } + } + } +} +``` + +## Architecture + +### Technology Stack + +- **Framework**: React +- **Language**: TypeScript +- **Form Library**: JSON Forms +- **UI Framework**: Material-UI (via JSON Forms) +- **Build Tool**: Webpack/Vite + +### Component Structure + +``` +formulus-formplayer/ +├── src/ +│ ├── App.tsx # Main application component +│ ├── FormLayout.tsx # Form layout renderer +│ ├── QuestionShell.tsx # Question wrapper component +│ ├── *QuestionRenderer.tsx # Question type renderers +│ ├── FormulusInterface.ts # Bridge interface definition +│ └── theme.ts # Theming configuration +├── public/ +│ └── formulus-load.js # API loading script +└── build/ # Production build output +``` + +## Core Responsibilities + +Formplayer is responsible for: + +1. **Form Rendering**: Render forms based on JSON schema and UI schema +2. **Data Collection**: Capture user input through various question types +3. **Validation**: Validate form responses against schema rules +4. **Observation Management**: Create, edit, and delete observations +5. **Draft Management**: Save and load draft observations + +## Integration with Formulus + +### Initialization + +Formplayer is initialized by the Formulus app with: + +- **Renderers**: Container components for form layout +- **Cells**: Question type components (text, date, photo, etc.) +- **Form Specs**: JSON form specifications from server +- **Formulus API**: JavaScript interface to native app + +### Communication Model + +``` +┌─────────────────┐ ┌──────────────────┐ +│ Formulus │ │ Formplayer │ +│ (Native) │◄───────►│ (WebView) │ +│ │ │ │ +│ • Database │ │ • Form Render │ +│ • Sync Engine │ │ • Validation │ +│ • API Bridge │ │ • User Input │ +└─────────────────┘ └──────────────────┘ +``` + +## JavaScript Interface + +Formplayer exposes methods to custom applications and receives configuration from Formulus. + +### Available Methods + +#### addObservation(formType, initializationData) + +Open a form to create a new observation. + +```javascript +window.formulus.formplayer.addObservation('survey', { + participantId: '123', + location: 'Field Site A' +}); +``` + +**Parameters:** +- `formType` (string): Form type identifier +- `initializationData` (object): Optional pre-population data + +#### editObservation(formType, observationId) + +Open a form to edit an existing observation. + +```javascript +window.formulus.formplayer.editObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): Form type identifier +- `observationId` (string): Observation ID to edit + +#### deleteObservation(formType, observationId) + +Delete an observation. + +```javascript +window.formulus.formplayer.deleteObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): Form type identifier +- `observationId` (string): Observation ID to delete + +## Question Types + +Formplayer supports various question types through custom renderers: + +### Text Input + +- **Single-line text**: Standard text input +- **Multi-line text**: Textarea for longer responses +- **Email**: Email format validation +- **Phone**: Phone number format validation +- **URL**: URL format validation + +### Number Input + +- **Integer**: Whole numbers only +- **Decimal**: Floating-point numbers +- **Range**: Min/max value constraints + +### Date and Time + +- **Date**: Date picker +- **Time**: Time picker +- **DateTime**: Combined date and time picker + +### Selection + +- **Single Select**: Dropdown or radio buttons +- **Multi Select**: Checkboxes for multiple choices + +### Boolean + +- **Checkbox**: True/false or yes/no + +### Media Capture + +- **Photo**: Camera capture or gallery selection +- **Audio**: Voice recording +- **Video**: Video recording +- **File**: File attachment + +### Special Input + +- **GPS**: Location coordinates capture +- **Signature**: Digital signature pad +- **QR Code**: Barcode/QR code scanner + +## Form Rendering + +### Schema Processing + +Formplayer processes JSON schemas to: + +1. **Parse Schema**: Extract form structure and validation rules +2. **Process UI Schema**: Apply layout and presentation rules +3. **Generate Form**: Create form components based on schema +4. **Apply Validation**: Set up validation rules + +### Layout System + +Forms can use different layout strategies: + +- **Vertical Layout**: Fields stacked vertically +- **Horizontal Layout**: Fields arranged horizontally +- **Group Layout**: Fields grouped in sections +- **Categorization**: Fields organized in tabs or categories + +## Validation + +### Schema Validation + +Formplayer validates form responses against: + +- **Required Fields**: Ensure required fields are filled +- **Type Validation**: Verify data types match schema +- **Format Validation**: Check format constraints (email, URL, etc.) +- **Range Validation**: Verify numeric ranges +- **Pattern Validation**: Match against regex patterns + +### Custom Validation + +Custom validation rules can be added: + +- **Conditional Validation**: Rules based on other field values +- **Cross-field Validation**: Validation across multiple fields +- **Async Validation**: Server-side validation support + +## Draft Management + +### Saving Drafts + +Formplayer can save incomplete forms as drafts: + +1. **Auto-save**: Periodically save form state +2. **Manual Save**: User-triggered draft save +3. **Local Storage**: Drafts stored in WebView storage + +### Loading Drafts + +When editing an observation: + +1. **Load Data**: Fetch observation data from Formulus +2. **Populate Form**: Pre-fill form fields with data +3. **Restore State**: Restore form state and validation + +## Theming + +### Theme Configuration + +Formplayer uses a theme system for styling: + +- **Colors**: Primary, secondary, and accent colors +- **Typography**: Font families and sizes +- **Spacing**: Margins and padding +- **Components**: Component-specific styling + +### Custom Theming + +Themes can be customized: + +- **Brand Colors**: Match organization branding +- **Custom Styles**: Override default component styles +- **Responsive Design**: Adapt to different screen sizes + +## Building and Deployment + +### Development Build + +```bash +# Install dependencies +npm install + +# Start development server +npm start + +# Opens at http://localhost:3000 +``` + +### Production Build + +```bash +# Build for React Native (Formulus) +npm run build:rn + +# Build for web +npm run build +``` + +### Build Output + +The build process: + +1. **Compiles TypeScript**: Transpiles to JavaScript +2. **Bundles Assets**: Combines CSS and images +3. **Minifies Code**: Optimizes for production +4. **Copies to Formulus**: Copies build to Formulus app + +## Integration with Custom Applications + +### Loading Formplayer + +Custom applications can use Formplayer: + +```html + + +``` + +### Formplayer API + +The Formplayer API is injected into custom app WebViews: + +```javascript +// Access Formplayer methods +window.formulus.formplayer.addObservation('survey', {}); +``` + +## Question Type Renderers + +### Core Renderers + +Formplayer includes core question type renderers: + +- `TextQuestionRenderer`: Text input fields +- `NumberQuestionRenderer`: Numeric input fields +- `DateQuestionRenderer`: Date picker +- `SelectQuestionRenderer`: Dropdown selections +- `PhotoQuestionRenderer`: Camera capture +- `GPSQuestionRenderer`: Location capture +- `SignatureQuestionRenderer`: Digital signature +- `AudioQuestionRenderer`: Voice recording +- `VideoQuestionRenderer`: Video recording +- `FileQuestionRenderer`: File attachment +- `QrcodeQuestionRenderer`: QR code scanner + +### Custom Renderers + +Custom renderers can be added: + +1. **Create Renderer Component**: Implement question type component +2. **Register Renderer**: Add to Formplayer configuration +3. **Use in Forms**: Reference in form schema + +## Error Handling + +### Validation Errors + +Formplayer displays validation errors: + +- **Field-level Errors**: Shown below invalid fields +- **Form-level Errors**: Shown at form level +- **Error Messages**: User-friendly error messages + +### Runtime Errors + +Error handling for: + +- **API Errors**: Network and server errors +- **Data Errors**: Invalid data format +- **Render Errors**: Component rendering failures + +## Performance + +### Optimization Strategies + +- **Lazy Loading**: Load form components on demand +- **Code Splitting**: Split bundle for faster loading +- **Memoization**: Cache form components +- **Virtual Scrolling**: For long forms + +### Best Practices + +- **Minimize Form Size**: Keep forms focused +- **Optimize Images**: Compress images in forms +- **Efficient Validation**: Validate only when needed +- **Cache Form Specs**: Cache parsed form specifications + +## Development + +### Local Development + +1. **Start Dev Server**: `npm start` +2. **Open Browser**: Navigate to `http://localhost:3000` +3. **Hot Reload**: Changes reflect automatically + +### Testing + +- **Unit Tests**: Test individual components +- **Integration Tests**: Test form rendering +- **E2E Tests**: Test complete form workflows + +## Related Documentation + +- [Form Design Guide](/guides/form-design) - Creating form schemas +- [Custom Applications Guide](/guides/custom-applications) - Building custom apps +- [Formulus Reference](/reference/formulus) - Mobile app component +- [Form Specifications](/reference/form-specifications) - Schema format + diff --git a/versioned_docs/version-1.0/reference/formulus.md b/versioned_docs/version-1.0/reference/formulus.md new file mode 100644 index 0000000..bcfd467 --- /dev/null +++ b/versioned_docs/version-1.0/reference/formulus.md @@ -0,0 +1,367 @@ +--- +sidebar_position: 5 +--- + +# Formulus Component Reference + +Complete technical reference for the Formulus mobile application component. + +## Overview + +Formulus is a React Native mobile application that serves as the client-side component of ODE. It provides offline-first data collection capabilities, custom application hosting, and bidirectional synchronization with the Synkronus server. + +## Architecture + +### Technology Stack + +- **Framework**: React Native +- **Language**: TypeScript +- **Database**: WatermelonDB (SQLite-based) +- **State Management**: React Context API +- **Navigation**: React Navigation +- **WebView**: React Native WebView (for custom apps) + +### Component Structure + +``` +formulus/ +├── src/ +│ ├── api/ # Synkronus API client (auto-generated) +│ ├── components/ # React Native UI components +│ ├── contexts/ # React Context providers +│ ├── database/ # WatermelonDB schema and models +│ ├── hooks/ # Custom React hooks +│ ├── navigation/ # Navigation configuration +│ ├── screens/ # Screen components +│ ├── services/ # Business logic services +│ ├── webview/ # WebView integration and bridge +│ └── utils/ # Utility functions +├── android/ # Android native code +├── ios/ # iOS native code +└── assets/ # Static assets +``` + +## Core Features + +### Offline-First Data Storage + +Formulus uses WatermelonDB for local data storage: + +- **Observations**: All form submissions stored locally +- **Attachments**: Binary files stored in local filesystem +- **App Bundles**: Custom application files cached locally +- **Sync State**: Tracks synchronization status + +### Custom Application Hosting + +Formulus hosts custom web applications in WebViews: + +- **WebView Integration**: React Native WebView component +- **JavaScript Bridge**: Communication between native and web +- **Formulus API**: Injected JavaScript interface for custom apps +- **Asset Loading**: Serves app bundle files from local storage + +### Synchronization Engine + +Two-phase synchronization protocol: + +1. **Observation Sync**: JSON metadata synchronization +2. **Attachment Sync**: Binary file synchronization + +### Form Rendering + +Integration with Formplayer for form rendering: + +- **Formplayer WebView**: Renders forms using JSON Forms +- **Question Types**: Supports various input types +- **Validation**: Client-side and schema-based validation +- **Data Binding**: Connects form data to observations + +## JavaScript Interface + +Formulus exposes a JavaScript API to custom applications running in WebViews. + +### API Access + +The API is automatically injected into WebViews. Use the helper function to ensure it's ready: + +```javascript +// Wait for API to be ready +const api = await getFormulus(); + +// Now use the API +const version = await api.getVersion(); +``` + +### Core Methods + +#### getVersion() + +Get the Formulus host version. + +```javascript +const version = await api.getVersion(); +// Returns: "1.0.0" +``` + +#### addObservation(formType, initializationData) + +Create a new observation by opening a form. + +```javascript +await api.addObservation('survey', { + participantId: '123', + location: 'Field Site A' +}); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `initializationData` (object): Optional data to pre-populate the form + +**Returns:** Promise that resolves when form is opened + +#### editObservation(formType, observationId) + +Edit an existing observation. + +```javascript +await api.editObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `observationId` (string): The observation ID to edit + +**Returns:** Promise that resolves when form is opened + +#### deleteObservation(formType, observationId) + +Delete an observation. + +```javascript +await api.deleteObservation('survey', 'obs-123'); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `observationId` (string): The observation ID to delete + +**Returns:** Promise that resolves when deletion is complete + +#### getObservations(formType, filters) + +Query observations from local database. + +```javascript +const observations = await api.getObservations('survey', { + status: 'synced', + limit: 10 +}); +``` + +**Parameters:** +- `formType` (string): The form type identifier +- `filters` (object): Optional query filters + +**Returns:** Promise resolving to array of observations + +#### sync() + +Trigger manual synchronization. + +```javascript +await api.sync(); +``` + +**Returns:** Promise that resolves when sync completes + +## Database Schema + +### Observations Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | string | Unique observation identifier | +| `form_type` | string | Form type identifier | +| `data` | JSON | Observation data (form responses) | +| `created_at` | timestamp | Creation timestamp | +| `updated_at` | timestamp | Last update timestamp | +| `deleted` | boolean | Soft delete flag | +| `_status` | string | Sync status (created, updated, deleted) | +| `_changed` | string | Changed fields tracking | + +### Attachments Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | string | Unique attachment identifier | +| `observation_id` | string | Reference to observation | +| `file_path` | string | Local file path | +| `mime_type` | string | File MIME type | +| `size` | number | File size in bytes | +| `synced` | boolean | Sync status | + +## Synchronization Protocol + +### Two-Phase Sync + +#### Phase 1: Observation Sync + +1. **Pull**: Request changes from server since last sync +2. **Apply**: Apply server changes to local database +3. **Push**: Send local changes to server +4. **Resolve Conflicts**: Handle conflicts if any + +#### Phase 2: Attachment Sync + +1. **Download Manifest**: Get list of attachments to download +2. **Download Files**: Download missing attachments +3. **Upload Files**: Upload pending attachments +4. **Update Status**: Mark attachments as synced + +### Sync State Management + +The app maintains sync state: + +- **Last Sync Timestamp**: When last sync completed +- **Pending Observations**: Count of unsynced observations +- **Sync Status**: Current sync state (idle, syncing, error) + +## Configuration + +### Server Configuration + +Configured through Settings screen: + +- **Server URL**: Synkronus server address +- **Username**: User credentials +- **Password**: User password +- **Auto-login**: Enable automatic login + +### Sync Configuration + +- **Auto-sync**: Enable automatic synchronization +- **Sync Interval**: How often to check for sync +- **WiFi Only**: Sync only on WiFi connection + +## Development + +### Building from Source + +See [Formulus Development Guide](/development/formulus-development) for complete development setup. + +### Key Development Commands + +```bash +# Install dependencies +npm install + +# Start Metro bundler +npm start + +# Run on Android +npm run android + +# Run on iOS +npm run ios + +# Generate API client from OpenAPI spec +npm run generate:api + +# Generate WebView injection script +npm run generate +``` + +### Project Structure + +- **Android**: Native Android code in `android/` directory +- **iOS**: Native iOS code in `ios/` directory +- **Source**: TypeScript source in `src/` directory +- **Assets**: Static assets in `assets/` directory + +## Platform-Specific Features + +### Android + +- **File System Access**: Direct access to Android storage +- **Camera Integration**: Native camera API +- **GPS**: Android location services +- **Permissions**: Android permission system + +### iOS + +- **File System Access**: iOS file system access +- **Camera Integration**: Native camera API +- **GPS**: iOS Core Location +- **Permissions**: iOS permission system + +## Security + +### Authentication + +- **JWT Tokens**: Stored securely in device keychain/keystore +- **Token Refresh**: Automatic token refresh before expiration +- **Secure Storage**: Credentials stored using platform secure storage + +### Data Protection + +- **Local Encryption**: Sensitive data encrypted at rest +- **Secure Communication**: HTTPS for all server communication +- **Permission Management**: Granular permission requests + +## Performance + +### Optimization Strategies + +- **Lazy Loading**: Load forms and data on demand +- **Caching**: Aggressive caching of app bundles and assets +- **Background Sync**: Sync in background to avoid blocking UI +- **Database Indexing**: Optimized database queries + +### Memory Management + +- **Image Compression**: Compress images before storage +- **Attachment Cleanup**: Remove old attachments after sync +- **Cache Limits**: Limit cache size to prevent memory issues + +## Troubleshooting + +### Common Issues + +#### Sync Failures + +- Check network connectivity +- Verify server URL and credentials +- Review sync logs for errors +- Check server status + +#### App Crashes + +- Review crash logs +- Check memory usage +- Verify database integrity +- Update to latest version + +#### Performance Issues + +- Clear app cache +- Check database size +- Review attachment storage +- Optimize app bundle size + +## API Reference + +For complete API documentation, see: + +- [Synkronus API Reference](/reference/api) - Server API endpoints +- [Custom Applications Guide](/guides/custom-applications) - Building custom apps +- [Form Design Guide](/guides/form-design) - Creating forms + +## Related Documentation + +- [Formulus Features](/using/formulus-features) - User-facing features +- [Installing Formulus](/getting-started/installing-formulus) - Installation guide +- [Formulus Development](/development/formulus-development) - Development setup +- [Synchronization](/using/synchronization) - Sync protocol details + diff --git a/versioned_docs/version-1.0/reference/index.md b/versioned_docs/version-1.0/reference/index.md new file mode 100644 index 0000000..7fdf85f --- /dev/null +++ b/versioned_docs/version-1.0/reference/index.md @@ -0,0 +1,34 @@ +--- +sidebar_position: 1 +--- + +# Reference + +API reference and technical specifications. + +## Overview + +Complete reference documentation for ODE APIs and configurations. + + + + + + + diff --git a/versioned_docs/version-1.0/reference/rest-api/app-bundle.md b/versioned_docs/version-1.0/reference/rest-api/app-bundle.md new file mode 100644 index 0000000..43ca5ae --- /dev/null +++ b/versioned_docs/version-1.0/reference/rest-api/app-bundle.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 3 +--- + +# App Bundle API + +API endpoints for managing app bundles. + +## Overview + +[Description placeholder] + +## Endpoints + +[Description placeholder] + +## Examples + +[Description placeholder] + +## Related Content + +- [REST API Overview](/docs/reference/rest-api/overview) +- [App Bundle Structure](/docs/1.0/build/custom-applications/app-bundle-structure) + diff --git a/versioned_docs/version-1.0/reference/rest-api/attachments.md b/versioned_docs/version-1.0/reference/rest-api/attachments.md new file mode 100644 index 0000000..f18c009 --- /dev/null +++ b/versioned_docs/version-1.0/reference/rest-api/attachments.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 5 +--- + +# Attachments API + +API endpoints for managing file attachments. + +## Overview + +[Description placeholder] + +## Endpoints + +[Description placeholder] + +## Examples + +[Description placeholder] + +## Related Content + +- [REST API Overview](/docs/reference/rest-api/overview) +- [Data Management](/docs/1.0/build/data-management/attachments) + diff --git a/versioned_docs/version-1.0/reference/rest-api/authentication.md b/versioned_docs/version-1.0/reference/rest-api/authentication.md new file mode 100644 index 0000000..b2d8f1e --- /dev/null +++ b/versioned_docs/version-1.0/reference/rest-api/authentication.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 2 +--- + +# Authentication API + +API endpoints for authentication. + +## Overview + +[Description placeholder] + +## Endpoints + +[Description placeholder] + +## Examples + +[Description placeholder] + +## Related Content + +- [REST API Overview](/docs/reference/rest-api/overview) +- [Users & Authentication](/docs/1.0/build/users-authentication) + diff --git a/versioned_docs/version-1.0/reference/rest-api/overview.md b/versioned_docs/version-1.0/reference/rest-api/overview.md new file mode 100644 index 0000000..3fc295b --- /dev/null +++ b/versioned_docs/version-1.0/reference/rest-api/overview.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 1 +--- + +# REST API Overview + +Complete REST API reference for Synkronus. + +## Overview + +[Description placeholder] + +## Base URL + +[Description placeholder] + +## Authentication + +[Description placeholder] + +## API Endpoints + +- [Authentication](/docs/reference/rest-api/authentication) +- [App Bundle](/docs/reference/rest-api/app-bundle) +- [Sync](/docs/reference/rest-api/sync) +- [Attachments](/docs/reference/rest-api/attachments) + +## Related Content + +- [OpenAPI Specification](/api) +- [Synkronus API Reference](/docs/1.0/components/synkronus/api-reference) + diff --git a/versioned_docs/version-1.0/reference/rest-api/sync.md b/versioned_docs/version-1.0/reference/rest-api/sync.md new file mode 100644 index 0000000..2f5ba47 --- /dev/null +++ b/versioned_docs/version-1.0/reference/rest-api/sync.md @@ -0,0 +1,25 @@ +--- +sidebar_position: 4 +--- + +# Sync API + +API endpoints for data synchronization. + +## Overview + +[Description placeholder] + +## Endpoints + +[Description placeholder] + +## Examples + +[Description placeholder] + +## Related Content + +- [REST API Overview](/docs/reference/rest-api/overview) +- [Synchronization Protocol](/docs/1.0/build/synchronization/sync-protocol) + diff --git a/versioned_docs/version-1.0/reference/synkronus-cli.md b/versioned_docs/version-1.0/reference/synkronus-cli.md new file mode 100644 index 0000000..c7c6d7a --- /dev/null +++ b/versioned_docs/version-1.0/reference/synkronus-cli.md @@ -0,0 +1,476 @@ +--- +sidebar_position: 7 +--- + +# Synkronus CLI Reference + +Complete technical reference for the Synkronus command-line interface tool. + +## Overview + +Synkronus CLI (`synk`) is a command-line utility for interacting with the Synkronus API. It provides functionality for authentication, data synchronization, app bundle management, user administration, and data export. + +## Installation + +### From Source + +```bash +go install github.com/OpenDataEnsemble/ode/synkronus-cli/cmd/synkronus@latest +``` + +### Build from Source + +```bash +git clone https://github.com/OpenDataEnsemble/ode.git +cd ode/synkronus-cli +go build -o bin/synk ./cmd/synkronus +``` + +### Verify Installation + +```bash +synk --version +``` + +## Configuration + +### Configuration File + +The CLI uses a YAML configuration file located at `$HOME/.synkronus.yaml` by default. + +**Example configuration:** + +```yaml +api: + url: http://localhost:8080 + version: 1.0.0 +``` + +### Multiple Endpoints + +You can manage multiple server endpoints: + +```bash +# Create separate config files +synk config init -o ~/.synkronus-dev.yaml +synk config init -o ~/.synkronus-prod.yaml + +# Set default config +synk config use ~/.synkronus-dev.yaml + +# Override for single command +synk --config ~/.synkronus-prod.yaml status +``` + +### Configuration Commands + +#### Initialize Config + +```bash +synk config init -o ~/.synkronus.yaml +``` + +#### Use Config + +```bash +synk config use ~/.synkronus.yaml +``` + +#### Show Current Config + +```bash +synk config show +``` + +## Authentication + +### Login + +Authenticate with the Synkronus server: + +```bash +synk login --username your-username +``` + +The CLI will prompt for password interactively. Authentication tokens are stored in the configuration file. + +### Check Status + +Verify authentication status: + +```bash +synk status +``` + +**Output:** +``` +Authenticated as: your-username +Server: http://localhost:8080 +API Version: 1.0.0 +Token expires: 2025-01-15 10:30:00 +``` + +### Logout + +Clear authentication: + +```bash +synk logout +``` + +## App Bundle Management + +### List Versions + +List all available app bundle versions: + +```bash +synk app-bundle versions +``` + +**Output:** +``` +Available app bundle versions: + 20250114-123456 * (current) + 20250113-120000 + 20250112-110000 +``` + +### Get Manifest + +Get the current app bundle manifest: + +```bash +synk app-bundle manifest +``` + +### Download Bundle + +Download the entire app bundle: + +```bash +synk app-bundle download --output ./app-bundle +``` + +### Download Specific File + +Download a specific file from the bundle: + +```bash +synk app-bundle download index.html +synk app-bundle download assets/css/styles.css +``` + +### Upload Bundle + +Upload a new app bundle (admin only): + +```bash +synk app-bundle upload bundle.zip +``` + +**Options:** +- `--activate`: Automatically activate the uploaded bundle +- `--skip-validation`: Skip bundle validation (not recommended) +- `--verbose`: Show detailed output + +**Example:** +```bash +synk app-bundle upload bundle.zip --activate --verbose +``` + +### Switch Version + +Switch to a specific app bundle version (admin only): + +```bash +synk app-bundle switch 20250114-123456 +``` + +## Data Synchronization + +### Pull Data + +Pull data from the server: + +```bash +synk sync pull output.json --client-id your-client-id +``` + +**Options:** +- `--client-id`: Client identifier (required) +- `--current-version`: Last known version number +- `--after-change-id`: Pull changes after specific change ID +- `--schema-types`: Filter by schema types (comma-separated) +- `--limit`: Maximum number of records to pull + +**Examples:** +```bash +# Pull all data +synk sync pull data.json --client-id client-123 + +# Pull with filters +synk sync pull data.json --client-id client-123 \ + --after-change-id 1234 \ + --schema-types survey,visit \ + --limit 100 +``` + +### Push Data + +Push data to the server: + +```bash +synk sync push data.json +``` + +The JSON file should contain observations in the sync format. + +## Data Export + +### Export Observations + +Export all observations as a Parquet ZIP archive: + +```bash +synk data export exports.zip +``` + +**Options:** +- `--output` or `-o`: Output file path +- `--format`: Export format (parquet, json, csv) + +**Examples:** +```bash +# Export to Parquet ZIP +synk data export observations.zip + +# Export to specific directory +synk data export ./backups/observations_20250114.zip + +# Export to JSON +synk data export observations.json --format json +``` + +## User Management + +### Create User + +Create a new user (admin only): + +```bash +synk user create --username newuser --password secret --role read-write +``` + +**Options:** +- `--username`: Username (required) +- `--password`: Password (required) +- `--role`: User role (read-only, read-write, admin) + +### List Users + +List all users: + +```bash +synk user list +``` + +### Get User + +Get user details: + +```bash +synk user get username +``` + +### Update User + +Update user information: + +```bash +synk user update username --password newpassword --role read-write +``` + +### Delete User + +Delete a user: + +```bash +synk user delete username +``` + +## QR Code Generation + +### Generate Login QR Code + +Generate a QR code for Formulus app configuration: + +```bash +synk qr login --output login-qr.png +``` + +**Options:** +- `--output` or `-o`: Output file path +- `--server-url`: Override server URL +- `--username`: Username to include +- `--password`: Password to include + +### Generate Admin QR Code + +Generate admin configuration QR code: + +```bash +synk qr admin --output admin-qr.png +``` + +## Shell Completion + +The CLI supports shell completion for bash, zsh, fish, and PowerShell. + + + + +```bash +# Current session +source <(synk completion bash) + +# Persistent (Linux) +sudo synk completion bash > /etc/bash_completion.d/synk + +# Persistent (macOS) +synk completion bash > /usr/local/etc/bash_completion.d/synk +``` + + + + +```bash +# Current session +source <(synk completion zsh) + +# Persistent +echo "[[ \$commands[synk] ]] && synk completion zsh > \"\${fpath[1]}/_synk\"" >> ~/.zshrc +``` + + + + +```bash +# Current session +synk completion fish | source + +# Persistent +synk completion fish > ~/.config/fish/completions/synk.fish +``` + + + + +```powershell +# Current session +synk completion powershell | Out-String | Invoke-Expression + +# Persistent (add to profile) +Add-Content -Path $PROFILE -Value "synk completion powershell | Out-String | Invoke-Expression" +``` + + + + +## Command Reference + +### Global Flags + +- `--config`: Specify configuration file +- `--verbose` or `-v`: Verbose output +- `--help` or `-h`: Show help +- `--version`: Show version + +### Command Structure + +``` +synk [global-flags] [command-flags] [arguments] +``` + +## Examples + +### Complete Workflow + +```bash +# 1. Configure CLI +synk config init -o ~/.synkronus.yaml + +# 2. Login +synk login --username admin + +# 3. Upload app bundle +synk app-bundle upload bundle.zip --activate + +# 4. Create user +synk user create --username fieldworker --password secret --role read-write + +# 5. Generate QR code for user +synk qr login --username fieldworker --password secret --output qr.png + +# 6. Export data +synk data export observations.zip +``` + +### Development Workflow + +```bash +# Switch to dev server +synk config use ~/.synkronus-dev.yaml + +# Test sync +synk sync pull test.json --client-id test-client + +# Upload test bundle +synk app-bundle upload test-bundle.zip --activate --verbose +``` + +## Error Handling + +### Common Errors + +**Authentication Failed:** +``` +Error: authentication failed: invalid credentials +``` +Solution: Verify username and password, check server is running + +**Server Unreachable:** +``` +Error: unable to connect to server +``` +Solution: Check server URL, verify network connectivity + +**Permission Denied:** +``` +Error: permission denied: admin role required +``` +Solution: Use admin account or request permissions + +### Debug Mode + +Enable verbose output for debugging: + +```bash +synk --verbose +``` + +## Best Practices + +1. **Use Configuration Files**: Store server URLs and settings in config files +2. **Version Control**: Track app bundle versions carefully +3. **Backup Data**: Regularly export data using `synk data export` +4. **Test First**: Test commands on dev server before production +5. **Use Shell Completion**: Enable completion for better UX + +## Related Documentation + +- [Synkronus Server Reference](/reference/synkronus-server) - Server API documentation +- [App Bundle Format](/reference/app-bundle-format) - Bundle structure +- [API Reference](/reference/api) - Complete API documentation +- [Deployment Guide](/guides/deployment) - Server deployment + diff --git a/versioned_docs/version-1.0/reference/synkronus-portal.md b/versioned_docs/version-1.0/reference/synkronus-portal.md new file mode 100644 index 0000000..05823f6 --- /dev/null +++ b/versioned_docs/version-1.0/reference/synkronus-portal.md @@ -0,0 +1,348 @@ +--- +sidebar_position: 9 +--- + +# Synkronus Portal Reference + +Complete technical reference for the Synkronus Portal web interface. + +## Overview + +Synkronus Portal is a web-based administrative interface for managing Synkronus server operations. It provides a user-friendly interface for app bundle management, user administration, observation viewing, and data export. The portal is built with React, TypeScript, and Vite. + +## Architecture + +### Technology Stack + +- **Framework**: React 19.2.0 +- **Language**: TypeScript +- **Build Tool**: Vite 7.2.4 +- **State Management**: React Context API +- **HTTP Client**: Fetch API with custom wrapper +- **Styling**: CSS (with ODE design tokens) + +### Project Structure + +``` +synkronus-portal/ +├── src/ +│ ├── main.tsx # Application entry point +│ ├── App.tsx # Root component +│ ├── types/ +│ │ └── auth.ts # TypeScript interfaces +│ ├── services/ +│ │ └── api.ts # HTTP client +│ ├── contexts/ +│ │ └── AuthContext.tsx # Authentication state +│ ├── components/ +│ │ ├── Login.tsx # Login component +│ │ └── ProtectedRoute.tsx # Route protection +│ └── pages/ +│ └── Dashboard.tsx # Main dashboard +├── index.html # HTML entry point +└── vite.config.ts # Vite configuration +``` + +## Core Features + +### Authentication + +- **JWT-based Auth**: Secure token authentication +- **Token Refresh**: Automatic token refresh +- **Session Management**: Persistent login sessions +- **Protected Routes**: Route-level access control + +### App Bundle Management + +- **View Versions**: List all app bundle versions +- **Upload Bundles**: Upload new app bundles +- **Switch Versions**: Activate specific bundle versions +- **Download Bundles**: Download bundle files +- **Version History**: View version history + +### User Management + +- **List Users**: View all users +- **Create Users**: Add new users +- **Edit Users**: Update user information +- **Delete Users**: Remove users +- **Role Management**: Assign user roles + +### Observation Management + +- **View Observations**: Browse collected observations +- **Filter Observations**: Filter by form type, date, etc. +- **Search**: Search observations +- **Export**: Export observations in various formats + +### Data Export + +- **Parquet Export**: Export as Parquet ZIP +- **JSON Export**: Export as JSON +- **CSV Export**: Export as CSV +- **Filtered Export**: Export filtered data + +## User Interface + +### Dashboard + +The main dashboard provides: + +- **Overview Statistics**: Total observations, users, bundles +- **Recent Activity**: Latest observations and changes +- **Quick Actions**: Common administrative tasks +- **System Status**: Server health and status + +### Navigation + +- **Sidebar Navigation**: Access to all sections +- **Breadcrumbs**: Current location indicator +- **User Menu**: User profile and logout +- **Notifications**: System notifications + +## API Integration + +### API Service + +The portal uses a centralized API service: + +```typescript +import { api } from './services/api' + +// Login +await api.login(username, password) + +// Get data +const users = await api.get('/users') + +// Post data +await api.post('/users/create', userData) +``` + +### Authentication Flow + +1. User enters credentials +2. Portal sends login request to `/auth/login` +3. Server returns JWT token +4. Token stored in localStorage +5. Token included in all subsequent requests + +### Error Handling + +The API service handles errors: + +- **Network Errors**: Connection failures +- **401 Unauthorized**: Invalid credentials +- **500+ Errors**: Server errors +- **API Errors**: Structured error responses + +## Configuration + +### Development Mode + +**Vite Dev Server:** +- Port: 5174 +- Hot Module Replacement: Enabled +- Proxy: `/api/*` → `http://localhost:8080/*` + +**Start Development:** +```bash +npm run dev +``` + +### Production Mode + +**Nginx Serving:** +- Port: 80 (exposed as 5173) +- Static files from `/usr/share/nginx/html` +- API proxy: `/api/*` → `http://synkronus:8080/*` + +**Build for Production:** +```bash +npm run build +``` + +### Environment Variables + +- `VITE_API_URL`: Override API base URL (optional) +- `DOCKER_ENV`: Set to `true` in Docker (optional) + +## Deployment + +### Docker Deployment + +The portal can be deployed with Docker: + +```bash +docker compose up -d +``` + +**Docker Compose Services:** +- `synkronus-portal`: Frontend portal +- `synkronus`: Backend API +- `postgres`: Database + +### Standalone Deployment + +Build and serve with any static file server: + +```bash +npm run build +# Serve dist/ directory +``` + +## Development + +### Local Development Setup + +1. **Install Dependencies:** + ```bash + npm install + ``` + +2. **Start Backend:** + ```bash + # In synkronus directory + go run cmd/synkronus/main.go + ``` + +3. **Start Portal:** + ```bash + npm run dev + ``` + +4. **Access Portal:** + - Open http://localhost:5174 + +### Adding New Features + +#### Add API Endpoint + +1. **Add TypeScript Types:** + ```typescript + // src/types/feature.ts + export interface FeatureRequest { + field: string + } + ``` + +2. **Add API Method:** + ```typescript + // src/services/api.ts + async getFeature(id: string): Promise { + return this.get(`/feature/${id}`) + } + ``` + +3. **Use in Component:** + ```typescript + const data = await api.getFeature('123') + ``` + +#### Add New Page + +1. **Create Component:** + ```typescript + // src/pages/NewPage.tsx + export function NewPage() { + return
    New Page
    + } + ``` + +2. **Add to App.tsx:** + ```typescript + + + + ``` + +## Authentication Context + +### AuthContext Usage + +```typescript +import { useAuth } from './contexts/AuthContext' + +function MyComponent() { + const { user, isAuthenticated, login, logout } = useAuth() + + if (!isAuthenticated) { + return + } + + return
    Welcome, {user?.username}
    +} +``` + +### AuthContext Methods + +- `login(credentials)`: Authenticate user +- `logout()`: Clear authentication +- `refreshAuth()`: Refresh expired tokens + +## Styling + +### Design Tokens + +The portal uses ODE design tokens: + +- **Primary Color**: Green (#4F7F4E) +- **Secondary Color**: Gold (#E9B85B) +- **Typography**: Noto Sans +- **Spacing**: Consistent spacing scale + +### CSS Structure + +- **Global Styles**: `src/index.css` +- **Component Styles**: Component-specific CSS files +- **No Framework**: Plain CSS (can extend with CSS modules) + +## Best Practices + +### Code Organization + +1. **Types First**: Define TypeScript types +2. **API Service**: Centralize API calls +3. **Context for State**: Use Context for global state +4. **Component Separation**: Separate components and pages + +### Error Handling + +1. **Try-Catch Blocks**: Wrap API calls +2. **User-Friendly Messages**: Show clear error messages +3. **Error Logging**: Log errors for debugging +4. **Graceful Degradation**: Handle errors gracefully + +### Performance + +1. **Code Splitting**: Lazy load components +2. **Memoization**: Memoize expensive computations +3. **Optimistic Updates**: Update UI before server response +4. **Debouncing**: Debounce search and filter inputs + +## Troubleshooting + +### Common Issues + +**Portal Won't Load:** +- Check backend is running +- Verify API URL configuration +- Check browser console for errors + +**Authentication Fails:** +- Verify credentials +- Check JWT_SECRET on server +- Clear localStorage and retry + +**API Calls Fail:** +- Check network connectivity +- Verify CORS configuration +- Check server logs + +## Related Documentation + +- [Synkronus Server Reference](/reference/synkronus-server) - Backend API +- [Deployment Guide](/guides/deployment) - Production deployment +- [Configuration Guide](/guides/configuration) - Configuration options +- [API Reference](/reference/api) - Complete API documentation + diff --git a/versioned_docs/version-1.0/reference/synkronus-server.md b/versioned_docs/version-1.0/reference/synkronus-server.md new file mode 100644 index 0000000..5d8398c --- /dev/null +++ b/versioned_docs/version-1.0/reference/synkronus-server.md @@ -0,0 +1,484 @@ +--- +sidebar_position: 8 +--- + +# Synkronus Server Reference + +Complete technical reference for the Synkronus server component. + +## Overview + +Synkronus is a robust synchronization API server built with Go. It provides RESTful endpoints for data synchronization, app bundle management, attachment handling, user management, and form specifications. The server uses PostgreSQL for data storage and JWT for authentication. + +## Architecture + +### Technology Stack + +- **Language**: Go 1.22+ +- **Database**: PostgreSQL 12+ +- **Authentication**: JWT (JSON Web Tokens) +- **API**: RESTful HTTP API +- **Documentation**: OpenAPI 3.0 specification + +### Project Structure + +``` +synkronus/ +├── cmd/synkronus/ # Application entry point +├── internal/ # Private application code +│ ├── api/ # API definition and OpenAPI integration +│ ├── handlers/ # HTTP request handlers +│ ├── models/ # Domain models +│ ├── repository/ # Data access layer +│ └── services/ # Business logic +└── pkg/ # Public libraries + ├── auth/ # Authentication utilities + ├── database/ # Database connection and migrations + ├── logger/ # Structured logging + ├── middleware/ # HTTP middleware + └── openapi/ # OpenAPI generated code +``` + +## Core Features + +### Data Synchronization + +- **Bidirectional Sync**: Push and pull operations +- **Incremental Updates**: Only sync changed data +- **Conflict Resolution**: Version-based conflict handling +- **Client Tracking**: Track client sync state + +### App Bundle Management + +- **Version Control**: Multiple bundle versions +- **Activation**: Switch active bundle version +- **File Serving**: Serve bundle files to clients +- **Manifest Generation**: Automatic manifest creation + +### Attachment Handling + +- **Binary Storage**: Store attachments separately from observations +- **Immutable Attachments**: Once uploaded, cannot be modified +- **Efficient Transfer**: Optimized for large files +- **Manifest System**: Track attachment changes + +### User Management + +- **JWT Authentication**: Secure token-based auth +- **Role-Based Access**: read-only, read-write, admin roles +- **User CRUD**: Create, read, update, delete users +- **Password Management**: Secure password handling + +### Form Specifications + +- **Versioned Forms**: Multiple versions per form type +- **Schema Storage**: Store JSON schemas +- **UI Schema Support**: Store UI layout definitions +- **Version Negotiation**: Client requests specific versions + +## API Endpoints + +### Authentication + +#### POST /auth/login + +Authenticate user and receive JWT token. + +**Request:** +```json +{ + "username": "user", + "password": "password" +} +``` + +**Response:** +```json +{ + "token": "eyJhbGciOiJIUzI1NiIs...", + "refreshToken": "eyJhbGciOiJIUzI1NiIs...", + "expiresIn": 3600 +} +``` + +#### POST /auth/refresh + +Refresh expired JWT token. + +**Request:** +```json +{ + "refreshToken": "eyJhbGciOiJIUzI1NiIs..." +} +``` + +### Synchronization + +#### POST /sync/pull + +Pull changes from server. + +**Request:** +```json +{ + "clientId": "client-123", + "currentVersion": 100, + "schemaTypes": ["survey", "visit"] +} +``` + +**Response:** +```json +{ + "changes": { + "observations": [...] + }, + "timestamp": 150 +} +``` + +#### POST /sync/push + +Push changes to server. + +**Request:** +```json +{ + "clientId": "client-123", + "changes": { + "observations": [...] + } +} +``` + +**Response:** +```json +{ + "timestamp": 150, + "conflicts": [] +} +``` + +### App Bundles + +#### GET /app-bundle/manifest + +Get current app bundle manifest. + +**Response:** +```json +{ + "version": "20250114-123456", + "files": [...], + "hash": "abc123..." +} +``` + +#### GET `/app-bundle/download/{path}` + +Download app bundle file. + +**Path Parameters:** +- `path`: File path within bundle + +#### POST /app-bundle/push + +Upload new app bundle (admin only). + +**Request:** Multipart form with `bundle` file + +**Response:** +```json +{ + "version": "20250114-123456", + "manifest": {...} +} +``` + +#### GET /app-bundle/versions + +List all app bundle versions. + +#### POST /app-bundle/switch + +Switch active bundle version (admin only). + +### Attachments + +#### GET /attachments/manifest + +Get attachment manifest. + +**Query Parameters:** +- `since`: Timestamp to get changes since + +#### GET `/attachments/{id}` + +Download attachment file. + +#### POST /attachments + +Upload attachment (multipart form). + +### Form Specifications + +#### GET `/formspecs/{formType}/{version}` + +Get form specification. + +**Path Parameters:** +- `formType`: Form type identifier +- `version`: Form version + +#### POST /formspecs + +Create form specification (admin only). + +### Users + +#### GET /users + +List all users (admin only). + +#### POST /users/create + +Create new user (admin only). + +#### GET `/users/{username}` + +Get user details. + +#### PUT `/users/{username}` + +Update user (admin only). + +#### DELETE `/users/{username}` + +Delete user (admin only). + +### Data Export + +#### GET /data/export + +Export observations as Parquet ZIP. + +**Query Parameters:** +- `format`: Export format (parquet, json, csv) + +## Configuration + +### Environment Variables + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `PORT` | HTTP server port | `8080` | No | +| `DB_CONNECTION` | PostgreSQL connection string | - | Yes | +| `JWT_SECRET` | Secret for JWT signing | - | Yes | +| `LOG_LEVEL` | Logging level (debug, info, warn, error) | `info` | No | +| `APP_BUNDLE_PATH` | Directory for app bundles | `./data/app-bundles` | No | +| `MAX_VERSIONS_KEPT` | Maximum bundle versions to keep | `5` | No | +| `ADMIN_USERNAME` | Initial admin username | `admin` | No | +| `ADMIN_PASSWORD` | Initial admin password | `admin` | No | + +### Example Configuration + +```bash +PORT=8080 +DB_CONNECTION=postgres://user:password@localhost:5432/synkronus?sslmode=disable +JWT_SECRET=your-secret-key-change-this-in-production +LOG_LEVEL=info +APP_BUNDLE_PATH=./data/app-bundles +MAX_VERSIONS_KEPT=5 +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin +``` + +## Database Schema + +### Observations Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | UUID | Primary key | +| `form_type` | VARCHAR | Form type identifier | +| `data` | JSONB | Observation data | +| `created_at` | TIMESTAMP | Creation timestamp | +| `updated_at` | TIMESTAMP | Last update timestamp | +| `deleted` | BOOLEAN | Soft delete flag | +| `version` | INTEGER | Version number (auto-increment) | + +### Users Table + +| Column | Type | Description | +|--------|------|-------------| +| `id` | UUID | Primary key | +| `username` | VARCHAR | Unique username | +| `password_hash` | VARCHAR | Hashed password | +| `role` | VARCHAR | User role (read-only, read-write, admin) | +| `created_at` | TIMESTAMP | Creation timestamp | + +### App Bundle Versions Table + +| Column | Type | Description | +|--------|------|-------------| +| `version` | VARCHAR | Version identifier | +| `is_active` | BOOLEAN | Active version flag | +| `created_at` | TIMESTAMP | Creation timestamp | + +## Synchronization Protocol + +### Two-Phase Sync + +#### Phase 1: Observation Sync + +1. Client requests changes via `/sync/pull` +2. Server returns observations changed since client's version +3. Client applies changes locally +4. Client pushes local changes via `/sync/push` +5. Server applies changes and returns new version + +#### Phase 2: Attachment Sync + +1. Client requests attachment manifest +2. Server returns list of attachments to download +3. Client downloads missing attachments +4. Client uploads pending attachments +5. Server confirms receipt + +### Conflict Resolution + +Conflicts are detected when: + +- Same observation modified on multiple clients +- Observation deleted on one client, modified on another + +Resolution strategy: + +- **Last Write Wins**: Most recent change wins +- **Version Tracking**: Version numbers prevent conflicts +- **Client Responsibility**: Clients handle conflict resolution + +## Security + +### Authentication + +- **JWT Tokens**: Secure token-based authentication +- **Token Expiration**: Tokens expire after configured time +- **Refresh Tokens**: Long-lived refresh tokens +- **Password Hashing**: bcrypt password hashing + +### Authorization + +- **Role-Based Access**: Three roles (read-only, read-write, admin) +- **Endpoint Protection**: Middleware protects admin endpoints +- **Token Validation**: All requests validate JWT tokens + +### Data Protection + +- **HTTPS**: Recommended for production +- **Input Validation**: All inputs validated +- **SQL Injection Prevention**: Parameterized queries +- **XSS Protection**: Input sanitization + +## Deployment + +### Docker Deployment + +See [Deployment Guide](/guides/deployment) for complete deployment instructions. + +### Quick Start + +```bash +docker compose up -d +``` + +### Production Setup + +1. Configure environment variables +2. Set up PostgreSQL database +3. Configure reverse proxy (Nginx) +4. Set up SSL/TLS certificates +5. Configure monitoring and logging + +## Monitoring + +### Health Check + +```bash +curl http://localhost:8080/health +``` + +Returns `OK` if server is healthy. + +### Logging + +Structured logging with levels: + +- **Debug**: Detailed debugging information +- **Info**: General informational messages +- **Warn**: Warning messages +- **Error**: Error messages + +### Metrics + +Key metrics to monitor: + +- Request rate +- Response times +- Error rates +- Database connection pool +- Active sync operations + +## Performance + +### Optimization Strategies + +- **Connection Pooling**: PostgreSQL connection pool +- **Query Optimization**: Indexed database queries +- **Caching**: Cache app bundle manifests +- **Compression**: Compress responses when possible + +### Scaling + +- **Horizontal Scaling**: Multiple server instances +- **Load Balancing**: Distribute requests across instances +- **Database Replication**: Read replicas for database +- **CDN**: Serve static files via CDN + +## Troubleshooting + +### Common Issues + +**Database Connection Errors:** +- Verify PostgreSQL is running +- Check connection string format +- Verify database exists +- Check user permissions + +**Authentication Failures:** +- Verify JWT_SECRET is set +- Check token expiration +- Verify user credentials + +**Sync Failures:** +- Check database connectivity +- Verify client version tracking +- Review server logs + +## API Versioning + +The API supports versioning via the `x-api-version` header: + +```http +x-api-version: 1.0.0 +``` + +Version negotiation allows clients to request specific API versions. + +## Related Documentation + +- [API Reference](/reference/api) - Complete API documentation +- [Deployment Guide](/guides/deployment) - Production deployment +- [Configuration Guide](/guides/configuration) - Configuration options +- [Synkronus CLI Reference](/reference/synkronus-cli) - CLI tool + diff --git a/docs/technical-overview/_category_.json b/versioned_docs/version-1.0/technical-overview/_category_.json similarity index 100% rename from docs/technical-overview/_category_.json rename to versioned_docs/version-1.0/technical-overview/_category_.json diff --git a/docs/technical-overview/architecture/components.md b/versioned_docs/version-1.0/technical-overview/architecture/components.md similarity index 100% rename from docs/technical-overview/architecture/components.md rename to versioned_docs/version-1.0/technical-overview/architecture/components.md diff --git a/docs/technical-overview/architecture/data-flow.md b/versioned_docs/version-1.0/technical-overview/architecture/data-flow.md similarity index 100% rename from docs/technical-overview/architecture/data-flow.md rename to versioned_docs/version-1.0/technical-overview/architecture/data-flow.md diff --git a/docs/technical-overview/architecture/overview.md b/versioned_docs/version-1.0/technical-overview/architecture/overview.md similarity index 100% rename from docs/technical-overview/architecture/overview.md rename to versioned_docs/version-1.0/technical-overview/architecture/overview.md diff --git a/docs/technical-overview/concepts/app-bundles.md b/versioned_docs/version-1.0/technical-overview/concepts/app-bundles.md similarity index 100% rename from docs/technical-overview/concepts/app-bundles.md rename to versioned_docs/version-1.0/technical-overview/concepts/app-bundles.md diff --git a/docs/technical-overview/concepts/custom-apps.md b/versioned_docs/version-1.0/technical-overview/concepts/custom-apps.md similarity index 100% rename from docs/technical-overview/concepts/custom-apps.md rename to versioned_docs/version-1.0/technical-overview/concepts/custom-apps.md diff --git a/docs/technical-overview/concepts/json-forms.md b/versioned_docs/version-1.0/technical-overview/concepts/json-forms.md similarity index 100% rename from docs/technical-overview/concepts/json-forms.md rename to versioned_docs/version-1.0/technical-overview/concepts/json-forms.md diff --git a/docs/technical-overview/concepts/offline-first.md b/versioned_docs/version-1.0/technical-overview/concepts/offline-first.md similarity index 100% rename from docs/technical-overview/concepts/offline-first.md rename to versioned_docs/version-1.0/technical-overview/concepts/offline-first.md diff --git a/docs/technical-overview/concepts/overview.md b/versioned_docs/version-1.0/technical-overview/concepts/overview.md similarity index 100% rename from docs/technical-overview/concepts/overview.md rename to versioned_docs/version-1.0/technical-overview/concepts/overview.md diff --git a/docs/technical-overview/database/overview.md b/versioned_docs/version-1.0/technical-overview/database/overview.md similarity index 100% rename from docs/technical-overview/database/overview.md rename to versioned_docs/version-1.0/technical-overview/database/overview.md diff --git a/docs/technical-overview/database/schema.md b/versioned_docs/version-1.0/technical-overview/database/schema.md similarity index 100% rename from docs/technical-overview/database/schema.md rename to versioned_docs/version-1.0/technical-overview/database/schema.md diff --git a/docs/technical-overview/index.md b/versioned_docs/version-1.0/technical-overview/index.md similarity index 74% rename from docs/technical-overview/index.md rename to versioned_docs/version-1.0/technical-overview/index.md index 13ce79e..d261f30 100644 --- a/docs/technical-overview/index.md +++ b/versioned_docs/version-1.0/technical-overview/index.md @@ -8,19 +8,18 @@ Overview and architecture of ODE components. - diff --git a/docs/tutorials/_category_.json b/versioned_docs/version-1.0/tutorials/_category_.json similarity index 100% rename from docs/tutorials/_category_.json rename to versioned_docs/version-1.0/tutorials/_category_.json diff --git a/docs/tutorials/advanced/complex-forms.md b/versioned_docs/version-1.0/tutorials/advanced/complex-forms.md similarity index 100% rename from docs/tutorials/advanced/complex-forms.md rename to versioned_docs/version-1.0/tutorials/advanced/complex-forms.md diff --git a/docs/tutorials/advanced/custom-renderers.md b/versioned_docs/version-1.0/tutorials/advanced/custom-renderers.md similarity index 100% rename from docs/tutorials/advanced/custom-renderers.md rename to versioned_docs/version-1.0/tutorials/advanced/custom-renderers.md diff --git a/docs/tutorials/advanced/performance.md b/versioned_docs/version-1.0/tutorials/advanced/performance.md similarity index 100% rename from docs/tutorials/advanced/performance.md rename to versioned_docs/version-1.0/tutorials/advanced/performance.md diff --git a/docs/tutorials/getting-started/connecting-everything.md b/versioned_docs/version-1.0/tutorials/getting-started/connecting-everything.md similarity index 100% rename from docs/tutorials/getting-started/connecting-everything.md rename to versioned_docs/version-1.0/tutorials/getting-started/connecting-everything.md diff --git a/docs/tutorials/getting-started/first-custom-app.md b/versioned_docs/version-1.0/tutorials/getting-started/first-custom-app.md similarity index 100% rename from docs/tutorials/getting-started/first-custom-app.md rename to versioned_docs/version-1.0/tutorials/getting-started/first-custom-app.md diff --git a/docs/tutorials/getting-started/first-form.md b/versioned_docs/version-1.0/tutorials/getting-started/first-form.md similarity index 100% rename from docs/tutorials/getting-started/first-form.md rename to versioned_docs/version-1.0/tutorials/getting-started/first-form.md diff --git a/docs/tutorials/index.md b/versioned_docs/version-1.0/tutorials/index.md similarity index 70% rename from docs/tutorials/index.md rename to versioned_docs/version-1.0/tutorials/index.md index 824afd1..da77fb9 100644 --- a/docs/tutorials/index.md +++ b/versioned_docs/version-1.0/tutorials/index.md @@ -12,34 +12,33 @@ Learn ODE through hands-on tutorials. - diff --git a/versioned_docs/version-1.0/using/.gitkeep b/versioned_docs/version-1.0/using/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/versioned_docs/version-1.0/using/app-bundles.md b/versioned_docs/version-1.0/using/app-bundles.md new file mode 100644 index 0000000..ef90524 --- /dev/null +++ b/versioned_docs/version-1.0/using/app-bundles.md @@ -0,0 +1,176 @@ +--- +sidebar_position: 4 +--- + +# Understanding App Bundles + +Learn how app bundles work in ODE and how they enable custom applications and forms. + +## What Are App Bundles? + +App bundles are packaged collections of files that define custom applications for ODE. They contain everything needed to run a custom data collection application within the Formulus mobile app: + +- **Custom web application** - HTML, CSS, and JavaScript files +- **Form specifications** - JSON schemas defining data collection forms +- **Configuration files** - Settings and metadata for the application +- **Assets** - Images, fonts, and other resources + +When you open Formulus and connect to a Synkronus server, the app automatically downloads the current app bundle. This bundle determines what forms are available, how they look, and what workflows you can use. + +## How App Bundles Work + +### Download Process + +1. **Initial Sync**: When you first log in to Formulus, the app checks the server for the current app bundle version +2. **Version Check**: The app compares the server's version with the version stored locally +3. **Download**: If a new version is available, the app downloads the bundle files +4. **Extraction**: The bundle is extracted and stored locally on your device +5. **Activation**: The new bundle becomes active, and you can use the updated forms and features + +### Automatic Updates + +App bundles update automatically: + +- **On Login**: When you log in, the app checks for updates +- **On Manual Sync**: When you tap "Sync Now" in the app +- **Periodically**: The app may check for updates in the background (if configured) + +### Version Management + +Each app bundle has a version identifier (typically a timestamp). This ensures: + +- **Consistency**: All devices use the same version of forms and workflows +- **Rollback**: Administrators can switch back to previous versions if needed +- **Tracking**: You can see which version of the app bundle you're using + +## What's Inside an App Bundle + +### Custom Application + +The main component is a custom web application that runs inside Formulus. This application: + +- **Provides Navigation**: Custom menus and screens for your workflow +- **Displays Forms**: Shows available forms and allows you to start data collection +- **Manages Data**: Lets you view, edit, and manage your observations +- **Custom Branding**: Can include your organization's logo and styling + +### Form Specifications + +App bundles include form definitions that specify: + +- **Data Fields**: What information to collect (name, age, location, etc.) +- **Field Types**: How to input data (text, number, date, photo, GPS, etc.) +- **Validation Rules**: Requirements for data entry (required fields, value ranges, etc.) +- **Layout**: How the form is organized and presented + +### Configuration + +Bundles may include configuration files that: + +- **Define Settings**: Application-specific settings and preferences +- **Specify Behavior**: How the application should behave in different scenarios +- **Set Permissions**: What features and data access the application needs + +## Using App Bundles + +### Accessing Your Application + +1. **Open Formulus** on your device +2. **Log in** to your account +3. **Wait for sync** to complete (if first time or after update) +4. **Your custom application** loads automatically as the main interface + +### Working with Forms + +Once the app bundle is loaded: + +1. **Navigate** through your custom application's interface +2. **Select a form** from the available forms list +3. **Fill out the form** with the required information +4. **Submit** the form to create an observation +5. **View observations** in your data management section + +### Updating App Bundles + +App bundles update automatically, but you can also manually trigger an update: + +1. **Open Formulus** → **Settings** → **Sync** +2. **Tap "Sync Now"** or **"Force Download"** +3. **Wait for download** to complete +4. **App restarts** or refreshes with the new bundle + +## Troubleshooting App Bundles + +### Bundle Not Downloading + +**Problem**: App bundle fails to download or sync. + +**Solutions**: +- Check internet connection +- Verify server is accessible +- Try manual sync: Settings → Sync → Sync Now +- Clear app cache: Settings → Storage → Clear Cache +- Restart the app + +### Forms Not Appearing + +**Problem**: After sync, forms don't appear in the app. + +**Solutions**: +- Verify bundle downloaded successfully (check Sync screen) +- Check bundle version matches server version +- Try force refresh: Settings → Sync → Force Download +- Clear app data and re-login (warning: deletes local data) + +### Outdated Forms + +**Problem**: Forms show old fields or structure. + +**Solutions**: +- Check if bundle update is available: Settings → Sync +- Force download latest bundle: Settings → Sync → Force Download +- Verify server has the latest bundle version +- Contact administrator if issue persists + +### Bundle Version Mismatch + +**Problem**: App shows different version than expected. + +**Solutions**: +- Force sync to get latest version +- Check server is serving the correct active version +- Verify you're connected to the correct server +- Contact administrator to verify bundle version + +## App Bundle Best Practices + +### For Users + +1. **Sync Regularly**: Keep your app bundle up to date by syncing regularly +2. **Check Version**: Verify you're using the latest bundle version +3. **Report Issues**: If forms or features don't work, report to your administrator +4. **Backup Data**: Ensure observations are synced before major updates + +### For Administrators + +1. **Test Before Deploy**: Test new bundles thoroughly before activating +2. **Version Control**: Use clear version identifiers (timestamps recommended) +3. **Rollback Plan**: Keep previous versions available for quick rollback +4. **Notify Users**: Inform users when major updates are deployed +5. **Monitor Usage**: Track which versions are in use across devices + +## Technical Details + +For technical information about app bundle structure, format, and development, see: + +- [App Bundle Format Reference](/reference/app-bundle-format) - Technical specification +- [Custom Applications Guide](/guides/custom-applications) - Building custom applications +- [Form Design Guide](/guides/form-design) - Creating form specifications + +## Related Documentation + +- [Your First Form](/using/your-first-form) - Get started with data collection +- [Synchronization](/using/synchronization) - Understand how data syncs +- [Formulus Features](/using/formulus-features) - Complete app feature guide +- [Working Offline](/using/working-offline) - Offline capabilities + diff --git a/versioned_docs/version-1.0/using/custom-applications.md b/versioned_docs/version-1.0/using/custom-applications.md new file mode 100644 index 0000000..13c208a --- /dev/null +++ b/versioned_docs/version-1.0/using/custom-applications.md @@ -0,0 +1,115 @@ +--- +sidebar_position: 3 +--- + +# Custom Applications + +Custom applications are web-based interfaces that run within the Formulus mobile app, providing specialized workflows and user experiences. + +## Overview + +Custom applications allow you to: + +- Create custom navigation and user interfaces +- Integrate with the ODE form system +- Access observation data through the Formulus JavaScript interface +- Build specialized workflows for specific use cases + +## How Custom Applications Work + +Custom applications are defined in app bundles, which include: + +- HTML, CSS, and JavaScript files +- Form specifications +- Custom renderers +- Configuration files + +The app bundle is uploaded to the Synkronus server and downloaded by mobile devices during synchronization. When a user opens a custom application, it runs in a WebView within the Formulus app. + +## Formulus JavaScript Interface + +Custom applications interact with Formulus through a JavaScript interface: + +```javascript +// Create a new observation +window.formulus.addObservation(formType, initializationData); + +// Edit an existing observation +window.formulus.editObservation(formType, observationId); + +// Delete an observation +window.formulus.deleteObservation(formType, observationId); +``` + +## Creating a Custom Application + +Custom applications are web-based interfaces that run within the Formulus mobile app. They integrate with ODE through the Formulus JavaScript interface. + +### Basic Structure + +A custom application consists of: + +1. **HTML file**: Main entry point (typically `index.html`) +2. **JavaScript**: Application logic using the Formulus API +3. **CSS**: Styling for the application +4. **Manifest**: Bundle metadata + +### Getting Started + +1. **Include Formulus Load Script**: + +```html + +``` + +2. **Initialize the API**: + +```javascript +async function init() { + const api = await getFormulus(); + // Use the API +} +``` + +3. **Use Formulus Methods**: + +```javascript +// Create observation +await api.addObservation('form-type', {}); + +// Edit observation +await api.editObservation('form-type', 'observation-id'); +``` + +### Packaging + +Package your application as a ZIP file with: +- `index.html` (or your entry point) +- `manifest.json` +- All assets (CSS, JS, images) + +### Deployment + +Upload the bundle to your Synkronus server: + +```bash +synk app-bundle upload bundle.zip --activate +``` + +See the [Custom Applications guide](/guides/custom-applications) for detailed instructions on building and deploying custom applications. + +## Use Cases + +Custom applications are suitable for: + +- Specialized data collection workflows +- Custom user interfaces that match organizational branding +- Integration with external systems +- Complex navigation requirements + +## Next Steps + +- Read the [Custom Applications guide](/guides/custom-apps/overview) for detailed information +- Learn about [app bundle structure](/guides/custom-apps/app-bundle-structure) +- Explore [custom renderers](/guides/custom-apps/custom-renderers) + diff --git a/versioned_docs/version-1.0/using/data-management.md b/versioned_docs/version-1.0/using/data-management.md new file mode 100644 index 0000000..75be39f --- /dev/null +++ b/versioned_docs/version-1.0/using/data-management.md @@ -0,0 +1,105 @@ +--- +sidebar_position: 4 +--- + +# Data Management + +This guide covers viewing, editing, and managing observations in ODE. + +## Viewing Observations + +Observations can be viewed in the Formulus app: + +1. Navigate to the observations list +2. Filter observations by form type, date, or sync status +3. Select an observation to view details +4. Review the form data and metadata + +## Editing Observations + +To edit an observation: + +1. Open the observation from the list +2. Select the edit option +3. Modify the form fields as needed +4. Save your changes + +Edited observations are marked for synchronization and will be updated on the server during the next sync operation. + +## Deleting Observations + +Observations can be deleted: + +1. Open the observation +2. Select the delete option +3. Confirm the deletion + +Deleted observations are marked as deleted locally and synchronized to the server. The server maintains a record of deleted observations for audit purposes. + +## Filtering and Searching + +The observations list supports filtering by: + +- Form type +- Date range +- Sync status (synced, pending, error) +- Custom criteria based on form data + +## Exporting Data + +Data can be exported from the server using multiple methods: + + + + +Export all observations as a Parquet ZIP archive: + +```bash +synk data export exports.zip +``` + +Export to different formats: + +```bash +# Parquet (default) +synk data export observations.zip + +# JSON +synk data export observations.json --format json + +# CSV +synk data export observations.csv --format csv +``` + + + + +```bash +curl -X GET http://your-server:8080/api/dataexport/parquet \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -o observations.zip +``` + + + + +1. Navigate to the Portal +2. Go to the "Data Export" section +3. Select export format (Parquet, JSON, CSV) +4. Click "Export" to download + + + + +The export includes all observations in the selected format, organized by schema type. See the [API Reference](/reference/api) for details. + +## Data Synchronization + +Observations are synchronized between devices and the server automatically. See [Synchronization](/using/synchronization) for details on how synchronization works. + +## Next Steps + +- Learn about [synchronization](/using/synchronization) in detail +- Review the [API Reference](/reference/api/endpoints) for programmatic access +- Explore [data export options](/reference/api/endpoints) for analysis + diff --git a/versioned_docs/version-1.0/using/formulus-features.md b/versioned_docs/version-1.0/using/formulus-features.md new file mode 100644 index 0000000..eda4e64 --- /dev/null +++ b/versioned_docs/version-1.0/using/formulus-features.md @@ -0,0 +1,374 @@ +--- +sidebar_position: 5 +--- + +# Formulus Features + +Complete user guide for the Formulus mobile application features and capabilities. + +## Overview + +Formulus is an offline-first mobile data collection application that enables field workers to collect structured data in environments with limited or no connectivity. The app works seamlessly with custom applications and provides comprehensive data collection capabilities. + +## Core Features + +### Offline-First Architecture + +Formulus is designed to work completely offline: + +- **Local Storage**: All data is stored locally on your device using WatermelonDB +- **Offline Forms**: Fill out forms without internet connection +- **Automatic Sync**: Data syncs automatically when connectivity is restored +- **No Data Loss**: Observations are saved locally even if sync fails + +### Custom Application Support + +Formulus can host custom web applications: + +- **Custom Interfaces**: Run specialized workflows and user interfaces +- **Branded Experience**: Display your organization's branding and styling +- **Flexible Navigation**: Custom menus and navigation structures +- **Integrated Forms**: Seamless integration with the form system + +### Rich Data Collection + +Support for various data types: + +- **Text Input**: Single and multi-line text fields +- **Numbers**: Integer and decimal numeric values +- **Dates and Times**: Date pickers, time selectors, and datetime fields +- **Selections**: Single choice dropdowns and multi-select checkboxes +- **Media Capture**: Photos, audio recordings, and video +- **Location**: GPS coordinates with accuracy information +- **Signatures**: Digital signature capture +- **Files**: File attachments and document uploads +- **Barcodes**: QR code and barcode scanning + +### Synchronization + +Bidirectional data synchronization: + +- **Automatic Sync**: Syncs when network becomes available +- **Manual Sync**: Trigger sync on demand +- **Incremental Updates**: Only syncs changed data +- **Conflict Resolution**: Automatically handles sync conflicts +- **Attachment Handling**: Separate sync for binary files + +## App Navigation + +### Main Screens + +| Screen | Description | Access | +|--------|-------------|--------| +| **Home/Dashboard** | Overview and quick actions | Default screen after login | +| **Forms** | List of available forms | Bottom tab or menu | +| **Observations** | Saved data records | Bottom tab or menu | +| **Sync** | Sync status and actions | Bottom tab or menu | +| **Settings** | App configuration | Menu or gear icon | + +### Navigation Elements + +- **Bottom Tabs**: Quick access to main sections +- **Menu Drawer**: Swipe from left or tap hamburger icon (☰) +- **Back Button**: Return to previous screen +- **Floating Action Button**: Quick actions (+ button) + +## Working with Forms + +### Viewing Available Forms + +1. Navigate to the **Forms** screen +2. View the list of available forms from the current app bundle +3. Each form displays: + - Form name/title + - Description (if available) + - Icon (if configured) + +### Starting a New Form + +1. Tap on a form from the list +2. Form opens in the form player +3. Fill in fields as required +4. Navigate through form sections using Next/Previous buttons or swiping + +### Form Field Types + +| Field Type | Description | Input Method | +|------------|-------------|--------------| +| **Text** | Single/multi-line text | Keyboard input | +| **Number** | Numeric values | Number keyboard | +| **Date** | Date selection | Date picker | +| **Time** | Time selection | Time picker | +| **Select** | Single choice | Dropdown/radio buttons | +| **Multi-select** | Multiple choices | Checkboxes | +| **Photo** | Camera capture | Camera interface | +| **GPS** | Location capture | GPS sensor | +| **Signature** | Digital signature | Touch drawing | +| **Audio** | Voice recording | Microphone | +| **Video** | Video recording | Camera | +| **File** | File attachment | File picker | +| **QR Code** | Scan QR/barcode | Camera scanner | + +### Form Navigation + +- **Swipe Left/Right**: Navigate between form sections +- **Next/Previous Buttons**: Move through form step by step +- **Progress Bar**: Shows completion status +- **Section Tabs**: Jump to specific sections (if enabled) + +### Saving Forms + +#### Save as Draft + +1. Tap **"Save Draft"** at any time while filling the form +2. Form is saved locally with current data +3. Resume later from the Observations screen +4. Draft status is shown in the observation list + +#### Submit/Finalize + +1. Complete all required fields +2. Tap **"Submit"** or **"Finalize"** +3. Validation runs automatically - errors shown if any +4. Observation saved as pending upload +5. Will sync when connection is available + +### Form Validation + +Forms may include validation rules: + +- **Required Fields**: Must be filled before submission +- **Format Validation**: Email, phone number, and other format checks +- **Range Validation**: Minimum and maximum values +- **Conditional Logic**: Fields shown based on other answers + +**Validation Errors:** +- Red highlight on invalid fields +- Error messages displayed below fields +- Cannot submit until all errors are resolved + +## Managing Observations + +### Viewing Observations + +1. Navigate to the **Observations** screen +2. View list of all saved observations +3. Each entry shows: + - Form name + - Date/time created + - Status (draft, pending, synced) + - Key data fields + +### Observation Status + +| Status | Description | +|--------|-------------| +| **Draft** | Incomplete, can be edited | +| **Pending** | Complete, awaiting sync | +| **Syncing** | Currently uploading to server | +| **Synced** | Successfully uploaded to server | +| **Error** | Sync failed, retry needed | + +### Editing Observations + +#### Edit Draft + +1. Tap on a draft observation +2. Form opens with saved data +3. Make changes as needed +4. Save or Submit + +#### Edit Pending (if allowed) + +1. Tap on a pending observation +2. May need to "Unlock" for editing +3. Make changes +4. Re-submit + +### Deleting Observations + +1. Long-press on observation or tap menu (⋮) +2. Select **"Delete"** +3. Confirm deletion + +**Note**: Synced observations may not be deletable locally depending on configuration. + +## Synchronization + +### Automatic Sync + +The app automatically syncs when: + +- Network connection becomes available +- App is opened (background sync) +- Periodically (if configured in settings) + +### Manual Sync + +1. Navigate to **Sync** screen +2. Tap **"Sync Now"** +3. Watch progress - upload count and status +4. Check results - success/failure messages + +### Sync Process + +1. **Pull**: Download new forms and server data +2. **Push**: Upload pending observations +3. **Attachments**: Upload photos, audio, and other files +4. **Confirmation**: Server acknowledges receipt + +### Sync Indicators + +- **Sync Icon**: Appears in status bar when syncing +- **Badge on Sync Tab**: Shows pending upload count +- **Last Sync Time**: Displayed on sync screen +- **Individual Status**: Each observation shows its sync status + +### Offline Mode + +When offline, you can: + +- ✅ Fill out forms +- ✅ Save observations locally +- ✅ View previously synced data +- ❌ Cannot upload new data +- ❌ Cannot download new forms + +Data syncs automatically when connection returns. + +## Attachments + +### Capturing Photos + +1. Tap photo field in form +2. Camera opens automatically +3. Take photo or select from gallery +4. Review and confirm +5. Photo is attached to observation + +### Capturing GPS Location + +1. Tap GPS field in form +2. Location permission requested (first time only) +3. Wait for GPS fix +4. Coordinates captured (latitude, longitude, accuracy) +5. May show map preview + +### Recording Audio + +1. Tap audio field in form +2. Microphone permission requested (first time only) +3. Tap record to start +4. Speak clearly +5. Tap stop when done +6. Review and confirm + +### Capturing Signatures + +1. Tap signature field in form +2. Signature pad opens +3. Sign with finger on screen +4. Tap "Done" to save +5. Tap "Clear" to retry + +### Recording Video + +1. Tap video field in form +2. Camera opens in video mode +3. Tap record to start +4. Tap stop when done +5. Review and confirm + +### Scanning QR Codes + +1. Tap QR code field in form +2. Camera opens in scanner mode +3. Point camera at QR code +4. Code is automatically scanned +5. Data is populated in the field + +## Settings + +### Server Settings + +- **Server URL**: Synkronus server address +- **Test Connection**: Verify connectivity to server +- **Auto-login**: Automatically log in on app start + +### User Settings + +- **Username**: Current logged-in user +- **Change Password**: Update account password +- **Logout**: Clear session and return to login + +### Sync Settings + +- **Auto-sync**: Enable/disable automatic synchronization +- **Sync on WiFi Only**: Save mobile data by syncing only on WiFi +- **Sync Interval**: How often to check for sync (if auto-sync enabled) + +### App Settings + +- **Notifications**: Enable/disable push notifications +- **Theme**: Light or dark mode +- **Language**: App language selection +- **Clear Cache**: Free up storage space +- **Storage Info**: View storage usage + +## Best Practices + +### Data Collection + +1. **Sync Before Fieldwork**: Get latest forms and updates +2. **Check Battery**: Ensure sufficient charge for field work +3. **Test GPS Outdoors**: Better accuracy in open areas +4. **Save Frequently**: Avoid data loss from app crashes +5. **Sync When Possible**: Don't wait too long between syncs + +### Offline Work + +1. **Sync Before Going Offline**: Download latest forms and data +2. **Save Observations as You Go**: Don't wait until the end +3. **Check Pending Count**: Verify all data before leaving field +4. **Sync Immediately**: When back online, sync right away + +### Data Quality + +1. **Fill All Required Fields**: Complete forms fully +2. **Double-Check Entries**: Verify accuracy before submitting +3. **Take Clear Photos**: Ensure photos are in focus and well-lit +4. **Verify GPS Accuracy**: Check location accuracy before submitting +5. **Review Before Submitting**: Review all data before final submission + +## Troubleshooting + +### Forms Not Loading + +- Check internet connection +- Verify server is accessible +- Try manual sync: Settings → Sync → Sync Now +- Clear app cache: Settings → Storage → Clear Cache + +### Sync Failures + +- Check internet connection +- Verify server URL is correct +- Check server status +- Try manual sync +- Review error messages in Sync screen + +### App Crashes + +- Restart the app +- Clear app cache +- Update to latest version +- Reinstall if issues persist + +## Related Documentation + +- [Installing Formulus](/getting-started/installing-formulus) - Installation guide +- [Your First Form](/using/your-first-form) - Getting started with forms +- [Synchronization](/using/synchronization) - Detailed sync information +- [Working Offline](/using/working-offline) - Offline capabilities +- [App Bundles](/using/app-bundles) - Understanding app bundles + diff --git a/versioned_docs/version-1.0/using/index.md b/versioned_docs/version-1.0/using/index.md new file mode 100644 index 0000000..53a3102 --- /dev/null +++ b/versioned_docs/version-1.0/using/index.md @@ -0,0 +1,100 @@ +--- +sidebar_position: 0 +--- + +# Using ODE + +Learn how to use ODE to create forms, manage data, and build custom applications. + +## Core Workflows + +
    +
    +
    +
    +

    Your First Form

    +
    +
    +

    Create your first data collection form and learn the basics.

    + Get Started → +
    +
    +
    +
    +
    +
    +

    Formulus Features

    +
    +
    +

    Explore the features and capabilities of Formulus.

    + Explore → +
    +
    +
    +
    +
    +
    +

    App Bundles

    +
    +
    +

    Learn how to create, manage, and deploy app bundles.

    + Learn More → +
    +
    +
    +
    +
    +
    +

    Data Management

    +
    +
    +

    Manage your collected data, export observations, and handle attachments.

    + View Guide → +
    +
    +
    +
    +
    +
    +

    Synchronization

    +
    +
    +

    Understand how data synchronization works and manage conflicts.

    + Read More → +
    +
    +
    +
    +
    +
    +

    Working Offline

    +
    +
    +

    Learn how ODE handles offline data collection.

    + Learn More → +
    +
    +
    +
    +
    +
    +

    Custom Applications

    +
    +
    +

    Build custom applications that integrate with ODE.

    + View Guide → +
    +
    +
    +
    +
    +
    +

    Troubleshooting

    +
    +
    +

    Common issues and solutions for problems you might encounter.

    + Get Help → +
    +
    +
    +
    diff --git a/versioned_docs/version-1.0/using/synchronization.md b/versioned_docs/version-1.0/using/synchronization.md new file mode 100644 index 0000000..ab56cf3 --- /dev/null +++ b/versioned_docs/version-1.0/using/synchronization.md @@ -0,0 +1,82 @@ +--- +sidebar_position: 5 +--- + +# Synchronization + +Synchronization is the process of exchanging data between mobile devices and the Synkronus server. + +## How Synchronization Works + +ODE uses a bidirectional synchronization protocol: + +1. **Push**: Local observations are sent to the server +2. **Pull**: New or updated observations are retrieved from the server +3. **Conflict Resolution**: Conflicts are resolved automatically using version numbers + +## Synchronization Process + +### Automatic Synchronization + +Synchronization occurs automatically when: + +- The app is opened and network connectivity is available +- A new observation is created or modified +- A manual sync is triggered by the user +- A scheduled sync interval elapses + +### Manual Synchronization + +Users can trigger manual synchronization: + +1. Open the app settings +2. Navigate to synchronization options +3. Select "Sync Now" +4. Wait for the sync to complete + +## Conflict Resolution + +When the same observation is modified on multiple devices, ODE resolves conflicts automatically: + +- Each observation has a version number +- The version number is incremented on each modification +- During sync, the observation with the highest version number is kept +- If versions are equal, the most recent modification timestamp is used + +## Sync Status + +Observations have a sync status that indicates their synchronization state: + +| Status | Description | +|--------|-------------| +| **Pending** | Observation is waiting to be synchronized | +| **Syncing** | Synchronization is in progress | +| **Synced** | Observation has been successfully synchronized | +| **Error** | Synchronization failed (will be retried) | + +## Attachment Synchronization + +Attachments (photos, audio, files) are synchronized separately from observation metadata: + +1. Observation metadata is synchronized first +2. Attachments are uploaded or downloaded in a separate phase +3. Attachment sync status is tracked independently + +See the [Technical Details](/development/technical/sync-protocol) section for more information on the sync protocol. + +## Troubleshooting Sync Issues + +If synchronization is not working: + +1. Check network connectivity +2. Verify server accessibility +3. Review authentication credentials +4. Check server logs for errors +5. Review the [Troubleshooting guide](/using/troubleshooting) + +## Next Steps + +- Learn about [working offline](/using/working-offline) +- Review [technical sync details](/development/technical/sync-protocol) +- Explore [API endpoints](/reference/api/endpoints) for sync operations + diff --git a/versioned_docs/version-1.0/using/troubleshooting.md b/versioned_docs/version-1.0/using/troubleshooting.md new file mode 100644 index 0000000..3df75bf --- /dev/null +++ b/versioned_docs/version-1.0/using/troubleshooting.md @@ -0,0 +1,502 @@ +--- +sidebar_position: 7 +--- + +# Troubleshooting + +Common issues and solutions when using ODE. + +## Connection Issues + +### App Cannot Connect to Server + +**Symptoms**: App shows connection error, cannot sync data + +**Solutions**: +- Verify server is running and accessible +- Check server URL in app settings +- Verify network connectivity +- Check firewall settings +- For Android emulator, use `10.0.2.2` instead of `localhost` +- For iOS simulator, use `localhost` or your machine's IP address + +### Authentication Failures + +**Symptoms**: Login fails, authentication errors + +**Solutions**: +- Verify username and password are correct +- Check that user account exists on server +- Verify JWT secret is configured correctly on server +- Review server logs for authentication errors + +## Synchronization Issues + +### Observations Not Syncing + +**Symptoms**: Observations remain in "pending" status + +**Solutions**: +- Check network connectivity +- Verify server is accessible +- Review authentication credentials +- Check server logs for sync errors +- Ensure observations were saved locally before sync attempt +- Try manual sync from app settings + +### Sync Conflicts + +**Symptoms**: Data conflicts during synchronization + +**Solutions**: +- ODE automatically resolves conflicts using version numbers +- Review conflict resolution in [Synchronization](/using/synchronization) +- Check that devices are using the same server +- Verify system clocks are synchronized + +## Form Issues + +### Forms Not Appearing + +**Symptoms**: Forms don't appear in app + +**Solutions**: +- Verify forms were uploaded to server +- Check that app has synchronized with server +- Review server logs for form upload errors +- Ensure form specifications are valid JSON +- Check form type and version match + +### Form Validation Errors + +**Symptoms**: Cannot submit form, validation errors + +**Solutions**: +- Review form schema for required fields +- Check that data types match schema definitions +- Verify validation rules are correctly defined +- Review error messages for specific issues + +## Formplayer Errors Explained + +Common Formplayer errors, their causes, and fixes. + +### ❌ "minimum value must be ['number']" + +**Error Message:** +``` +minimum value must be ['number'] +``` + +**Cause:** +Using `$data` reference or non-numeric value in `minimum` or `maximum` property. + +**Example (Unsafe):** +```json +{ + "type": "number", + "minimum": { "$data": "#/properties/minValue" } // ❌ $data not supported +} +``` + +**Fix:** +Use literal numeric values only: + +```json +{ + "type": "number", + "minimum": 0, // ✅ Literal number + "maximum": 100 +} +``` + +**Prevention:** +- Always use literal numbers for `minimum` and `maximum` +- Never use `$data` references in validation constraints +- Validate schema before deployment + +### ❌ "Cannot read properties of undefined (reading 'find')" + +**Error Message:** +``` +Cannot read properties of undefined (reading 'find') +TypeError: Cannot read properties of undefined (reading 'find') +``` + +**Cause:** +One of several issues: +1. Layout missing `elements` array +2. Invalid `scope` path (field doesn't exist in schema) +3. Rule referencing missing field + +**Example 1 - Missing elements:** +```json +{ + "type": "SwipeLayout" + // ❌ Missing elements array +} +``` + +**Fix:** +```json +{ + "type": "SwipeLayout", + "elements": [] // ✅ Always include elements array +} +``` + +**Example 2 - Invalid scope:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/missingField" // ❌ Field doesn't exist + } +} +``` + +**Fix:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "missingField": { "type": "string" } // ✅ Add field to schema + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/missingField" + } +} +``` + +**Example 3 - Rule referencing missing field:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/missingField", // ❌ Field doesn't exist + "schema": { "const": "value" } + } + } + } +} +``` + +**Fix:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "missingField": { "type": "string" } // ✅ Add referenced field + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "condition": { + "scope": "#/properties/missingField", + "schema": { "const": "value" } + } + } + } +} +``` + +**Prevention:** +- Always include `elements: []` in layouts (can be empty) +- Validate all `scope` paths exist in schema +- Ensure all fields referenced in rules exist in schema +- Use form validation script before deployment + +### ❌ "Rule condition scope not found" + +**Error Message:** +``` +Rule condition scope "#/properties/field" not found in schema +``` + +**Cause:** +Rule condition references a field that doesn't exist in the JSON schema. + +**Example:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + // field2 doesn't exist + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/field2", // ❌ Field doesn't exist + "schema": { "const": "value" } + } + } + } +} +``` + +**Fix:** +Add the referenced field to the schema: + +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "field2": { "type": "string" } // ✅ Add referenced field + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/field1", + "rule": { + "effect": "SHOW", + "condition": { + "scope": "#/properties/field2", + "schema": { "const": "value" } + } + } + } +} +``` + +**Prevention:** +- Always define all fields referenced in rules in the schema +- Use form validation script to catch these errors +- Test all conditional logic paths + +### ❌ "SwipeLayout missing required elements array" + +**Error Message:** +``` +SwipeLayout missing required "elements" array +``` + +**Cause:** +SwipeLayout (or other layout) is missing the `elements` property. + +**Example:** +```json +{ + "type": "SwipeLayout" + // ❌ Missing elements +} +``` + +**Fix:** +```json +{ + "type": "SwipeLayout", + "elements": [] // ✅ Always include elements array +} +``` + +**Prevention:** +- Always include `elements` array in layouts +- Use form validation script +- Test forms before deployment + +### ❌ "Invalid scope path" + +**Error Message:** +``` +Invalid scope path: "#/properties/nonexistent" +Scope does not exist in schema +``` + +**Cause:** +Control `scope` references a property that doesn't exist in the JSON schema. + +**Example:** +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" } + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/nonexistent" // ❌ Field doesn't exist + } +} +``` + +**Fix:** +Either add the field to schema or correct the scope: + +```json +{ + "schema": { + "properties": { + "field1": { "type": "string" }, + "nonexistent": { "type": "string" } // ✅ Add field + } + }, + "uischema": { + "type": "Control", + "scope": "#/properties/nonexistent" + } +} +``` + +**Prevention:** +- Validate all scope paths before deployment +- Use form validation script +- Keep schema and UI schema in sync + +### ❌ "$data is not supported" + +**Error Message:** +``` +$data is not supported in Formplayer +``` + +**Cause:** +Using `$data` references in schema (e.g., in `minimum`, `maximum`, or validation rules). + +**Example:** +```json +{ + "type": "number", + "minimum": { "$data": "#/properties/minValue" } // ❌ Not supported +} +``` + +**Fix:** +Use literal values only: + +```json +{ + "type": "number", + "minimum": 0 // ✅ Literal value +} +``` + +**Prevention:** +- Never use `$data` references +- Use literal values for all constraints +- Refer to [Supported Schema Profile](/reference/formplayer#supported-schema--ui-profile) + +### ❌ "Form failed to render" + +**Symptoms:** +Form doesn't display, blank screen, or error in console. + +**Common Causes:** +1. Invalid JSON syntax +2. Missing required schema properties +3. Unsupported schema features +4. Invalid UI schema structure + +**Solutions:** +1. Validate JSON syntax +2. Check schema follows supported profile +3. Verify UI schema structure (SwipeLayout root, elements arrays) +4. Review browser console for specific errors +5. Test with form validation script + +### General Error Prevention + +1. **Use Validation Script**: Run `npm run validate:forms` before deployment +2. **Check Supported Features**: Refer to [Supported Schema Profile](/reference/formplayer#supported-schema--ui-profile) +3. **Test on Devices**: Test forms on actual mobile devices +4. **Review Error Messages**: Error messages often indicate the specific issue +5. **Validate Scope Paths**: Ensure all scope paths exist in schema +6. **Avoid Unsupported Features**: Don't use `$data`, `if/then/else`, etc. + +### Getting Help with Errors + +If you encounter errors not covered here: + +1. Check [Form Design Guide](/guides/form-design) for best practices +2. Review [Formplayer Reference](/reference/formplayer) for supported features +3. Validate your form using the validation script +4. Check browser/device console for detailed error messages +5. Report issues following [guidelines](/community/getting-help) + +## Data Issues + +### Observations Not Saving + +**Symptoms**: Observations disappear after creation + +**Solutions**: +- Check device storage space +- Verify database is accessible +- Review app logs for database errors +- Ensure app has necessary permissions + +### Data Loss + +**Symptoms**: Observations are missing + +**Solutions**: +- Check sync status to see if data is on server +- Review server logs for deletion records +- Verify backups are configured +- Check that observations weren't accidentally deleted + +## Performance Issues + +### Slow App Performance + +**Symptoms**: App is slow, forms take time to load + +**Solutions**: +- Check device storage space +- Review number of observations stored locally +- Clear app cache if needed +- Ensure app is updated to latest version +- Check device memory usage + +### Slow Synchronization + +**Symptoms**: Sync takes a long time + +**Solutions**: +- Check network speed and stability +- Review number of observations to sync +- Check server performance and load +- Verify attachment sizes are reasonable +- Consider syncing during off-peak hours + +## Getting Additional Help + +If you cannot resolve an issue: + +1. Review relevant documentation sections +2. Check [GitHub Issues](https://github.com/OpenDataEnsemble/ode/issues) for similar problems +3. Search the [Community section](/community/getting-help) for solutions +4. Review [API Reference](/reference/api/overview) for technical details +5. Report issues following the [guidelines](/community/reporting-issues) + +## Next Steps + +- Review [Synchronization](/using/synchronization) for sync-related issues +- Check [Installation guides](/getting-started/installation/prerequisites) for setup problems +- Explore [API Reference](/reference/api/overview) for integration issues + diff --git a/versioned_docs/version-1.0/using/working-offline.md b/versioned_docs/version-1.0/using/working-offline.md new file mode 100644 index 0000000..426dffa --- /dev/null +++ b/versioned_docs/version-1.0/using/working-offline.md @@ -0,0 +1,71 @@ +--- +sidebar_position: 6 +--- + +# Working Offline + +ODE is designed to work seamlessly in offline conditions, allowing data collection to continue regardless of network connectivity. + +## Offline Capabilities + +When offline, you can: + +- Create new observations +- Edit existing observations +- Delete observations +- View all locally stored observations +- Fill out forms completely + +All changes are stored locally and synchronized when connectivity is restored. + +## Local Storage + +Observations are stored locally on the device using WatermelonDB, a reactive database optimized for React Native. This ensures: + +- Fast access to data +- Reliable storage +- Efficient querying +- Automatic conflict resolution + +## Synchronization When Online + +When network connectivity is restored: + +1. The app automatically detects connectivity +2. Synchronization begins automatically +3. Local changes are pushed to the server +4. New data is pulled from the server +5. Conflicts are resolved automatically + +## Offline Indicators + +The app provides visual indicators for offline status: + +- Connection status in the app header +- Sync status for individual observations +- Notification when sync completes + +## Best Practices + +When working offline: + +- Ensure sufficient device storage for observations +- Regularly sync when connectivity is available +- Monitor sync status to identify any issues +- Keep the app updated to ensure optimal offline performance + +## Limitations + +While offline, you cannot: + +- Access server-side features +- Download new forms (unless previously cached) +- Access real-time collaboration features +- View server-side analytics + +## Next Steps + +- Learn about [synchronization](/using/synchronization) in detail +- Review [troubleshooting](/using/troubleshooting) for offline issues +- Explore [data management](/using/data-management) features + diff --git a/versioned_docs/version-1.0/using/your-first-form.md b/versioned_docs/version-1.0/using/your-first-form.md new file mode 100644 index 0000000..41a2ff5 --- /dev/null +++ b/versioned_docs/version-1.0/using/your-first-form.md @@ -0,0 +1,123 @@ +--- +sidebar_position: 2 +--- + +# Your First Form + +This guide walks you through creating and submitting your first form using ODE. + +## Prerequisites + +Before starting, ensure you have: + +- Formulus app installed and configured +- Synkronus server running and accessible +- App connected to the server + +## Creating a Form + +Forms in ODE are defined using JSON schema. A basic form consists of a schema definition and optionally a UI schema. + +### Basic Form Example + +Here's a simple form that collects a name and age: + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Full Name" + }, + "age": { + "type": "integer", + "title": "Age", + "minimum": 0, + "maximum": 120 + } + }, + "required": ["name", "age"] +} +``` + +### Uploading the Form + +Upload the form to your Synkronus server using one of these methods: + + + + +```bash +curl -X POST http://your-server:8080/api/formspecs \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{ + "formType": "basic-survey", + "version": "1.0.0", + "schema": { + "type": "object", + "properties": { + "name": {"type": "string", "title": "Full Name"}, + "age": {"type": "integer", "title": "Age", "minimum": 0, "maximum": 120} + }, + "required": ["name", "age"] + } + }' +``` + + + + +```bash +# Create a form specification file (form.json) +synk formspec create form.json --form-type basic-survey --version 1.0.0 +``` + + + + +1. Navigate to the Portal +2. Go to "Form Specifications" +3. Click "Create New Form" +4. Enter form type and version +5. Paste or upload your JSON schema +6. Click "Save" + + + + +## Filling Out the Form + +1. Open the Formulus app on your device +2. Navigate to the forms list +3. Select your form from the list +4. Fill out the form fields +5. Review your entries +6. Submit the form + +## Submitting Observations + +When you submit a form, an observation is created. The observation contains: + +- The form data you entered +- Metadata such as creation timestamp +- A unique identifier +- Sync status + +Observations are stored locally on your device and synchronized to the server when connectivity is available. + +## Verifying Submission + +To verify that your observation was created: + +1. Check the app's observation list +2. Verify the observation appears with a "synced" status after synchronization +3. Query the server API to confirm the observation was received + +## Next Steps + +- Learn about [form design](/guides/forms/overview) to create more complex forms +- Explore [data management](/using/data-management) features +- Understand [synchronization](/using/synchronization) in detail + diff --git a/versioned_sidebars/version-1.0-sidebars.json b/versioned_sidebars/version-1.0-sidebars.json new file mode 100644 index 0000000..f7f5a82 --- /dev/null +++ b/versioned_sidebars/version-1.0-sidebars.json @@ -0,0 +1,110 @@ +{ + "docs": [ + { + "type": "doc", + "id": "index", + "label": "Introduction" + }, + { + "type": "category", + "label": "Getting Started", + "link": { + "type": "doc", + "id": "getting-started/index" + }, + "items": [ + "getting-started/what-is-ode", + "getting-started/why-ode", + "getting-started/key-concepts", + "getting-started/installation", + "getting-started/installing-formulus", + "getting-started/quick-start", + "getting-started/faq" + ] + }, + { + "type": "category", + "label": "Using ODE", + "link": { + "type": "doc", + "id": "using/index" + }, + "items": [ + "using/your-first-form", + "using/formulus-features", + "using/app-bundles", + "using/data-management", + "using/synchronization", + "using/custom-applications", + "using/working-offline", + "using/troubleshooting" + ] + }, + { + "type": "category", + "label": "Guides", + "link": { + "type": "doc", + "id": "guides/index" + }, + "items": [ + "guides/form-design", + "guides/custom-applications", + "guides/deployment", + "guides/configuration" + ] + }, + { + "type": "category", + "label": "Reference", + "link": { + "type": "doc", + "id": "reference/index" + }, + "items": [ + "reference/api", + "reference/components", + "reference/formulus", + "reference/formplayer", + "reference/formplayer-contract", + "reference/synkronus-cli", + "reference/synkronus-server", + "reference/synkronus-portal", + "reference/form-specifications", + "reference/app-bundle-format" + ] + }, + { + "type": "category", + "label": "Development", + "link": { + "type": "doc", + "id": "development/index" + }, + "items": [ + "development/setup", + "development/installing-formulus-dev", + "development/architecture", + "development/formulus-development", + "development/formplayer-development", + "development/synkronus-development", + "development/synkronus-portal-development", + "development/contributing", + "development/building-testing", + "development/extending" + ] + }, + { + "type": "category", + "label": "Community", + "link": { + "type": "doc", + "id": "community/index" + }, + "items": [ + "community/getting-help", + "community/examples" + ] + } + ] +} diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..9a369ed --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +[ + "1.0" +]