diff --git a/.aws/ami/README.md b/.aws/ami/README.md index 4792ad6db9..b16d36f818 100644 --- a/.aws/ami/README.md +++ b/.aws/ami/README.md @@ -28,7 +28,8 @@ This assumes you installed Packer to your PATH. ``` $ cd ./.aws/ami -$ packer build packer.json +$ packer init packer.json.pkr.hcl +$ packer build packer.json.pkr.hcl ``` When finished, you will have a new AMI named `nodejs-docker-ubuntu20-{{timestamp}}` in your AWS account. diff --git a/.aws/ami/packer.json b/.aws/ami/packer.json deleted file mode 100644 index 38de661806..0000000000 --- a/.aws/ami/packer.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "min_packer_version": "0.12.0", - "variables": { - "aws_region": "us-east-1", - "node_version": "18.17.1" - }, - "builders": [ - { - "name": "ubuntu20-ami", - "ami_name": "nodejs-docker-ubuntu20-{{timestamp}}", - "ami_description": "A Ubuntu 20.04 AMI that has Node.js and Docker", - "instance_type": "t4g.micro", - "region": "{{user `aws_region`}}", - "type": "amazon-ebs", - "source_ami_filter": { - "filters": { - "virtualization-type": "hvm", - "architecture": "arm64", - "name": "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-arm64-server-*", - "block-device-mapping.volume-type": "gp2", - "root-device-type": "ebs" - }, - "owners": ["099720109477"], - "most_recent": true - }, - "ssh_username": "ubuntu", - "profile": "casualsimulation" - } - ], - "provisioners": [ - { - "type": "shell", - "script": "{{template_dir}}/install-aws-cli.sh" - }, - { - "type": "shell", - "script": "{{template_dir}}/install-docker.sh" - }, - { - "type": "shell", - "environment_vars": ["NODE_VERSION={{user `node_version`}}"], - "script": "{{template_dir}}/install-nodejs.sh" - } - ] -} diff --git a/.aws/ami/packer.json.pkr.hcl b/.aws/ami/packer.json.pkr.hcl new file mode 100644 index 0000000000..2cb4eec8df --- /dev/null +++ b/.aws/ami/packer.json.pkr.hcl @@ -0,0 +1,63 @@ +packer { + required_version = ">= 0.12.0" + required_plugins { + amazon = { + source = "github.com/hashicorp/amazon" + version = "~> 1" + } + } +} + +variable "aws_region" { + type = string + default = "us-east-1" +} + +variable "node_version" { + type = string + default = "20.18.3" +} + +data "amazon-ami" "autogenerated_1" { + filters = { + architecture = "arm64" + "block-device-mapping.volume-type" = "gp2" + name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-arm64-server-*" + root-device-type = "ebs" + virtualization-type = "hvm" + } + most_recent = true + owners = ["099720109477"] + profile = "casualsimulation" + region = "${var.aws_region}" +} + +locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } + +source "amazon-ebs" "ubuntu20-ami" { + ami_description = "A Ubuntu 20.04 AMI that has Node.js and Docker" + ami_name = "nodejs-docker-ubuntu20-${local.timestamp}" + instance_type = "t4g.micro" + profile = "casualsimulation" + region = "${var.aws_region}" + source_ami = "${data.amazon-ami.autogenerated_1.id}" + ssh_username = "ubuntu" +} + +build { + sources = ["source.amazon-ebs.ubuntu20-ami"] + + provisioner "shell" { + script = "${path.root}/install-aws-cli.sh" + } + + provisioner "shell" { + script = "${path.root}/install-docker.sh" + } + + provisioner "shell" { + environment_vars = ["NODE_VERSION=${var.node_version}"] + script = "${path.root}/install-nodejs.sh" + } + +} diff --git a/.github/ISSUE_TEMPLATE/fix-request.yml b/.github/ISSUE_TEMPLATE/fix-request.yml new file mode 100644 index 0000000000..5c239535ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/fix-request.yml @@ -0,0 +1,37 @@ +name: šŸ› ļø Fix Request +description: Request a bug fix or change that Claude can auto-pick up +labels: ['fix', 'claude'] +body: + - type: markdown + attributes: + value: | + Thanks for filing a fix request! + šŸ‘‰ To have Claude auto-create a branch & PR, make sure to **comment** with: + + ``` + @claude please fix this + ``` + + Claude will: + - Read this issue + - Create a branch + - Apply changes + - Run tests/linters if available + - Open a pull request tied to this issue + + - type: input + id: summary + attributes: + label: āœļø Summary + description: A short description of the problem + placeholder: 'Login button throws 500 when email already exists' + + - type: textarea + id: steps + attributes: + label: āœ… Steps to Reproduce / Notes + description: Steps, logs, or extra info Claude should see + placeholder: | + 1. Go to /signup + 2. Enter an already-registered email + 3. Submit form → error 500 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..e027fd5e95 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,226 @@ +# CasualOS Copilot Coding Agent Instructions + +## Repository Overview + +**CasualOS** is a set of web-based tools designed to facilitate the creation of real-time, multi-user, context-aware interactive experiences. This is a large-scale TypeScript/JavaScript monorepo (36+ packages) using PNPM for package management, built with modern web technologies including Vue.js, Three.js, and Node.js. + +**Repository Statistics:** + +- 36 workspace packages total (34 in src/ + docs + root) +- Primary languages: TypeScript (80%), JavaScript, Vue, HTML, CSS +- Package manager: pnpm v10.10.0+ (managed by corepack) +- Node.js: v20.18+ required +- Build tool: TypeScript compiler + esbuild + Vite +- Test framework: Jest +- Linting: ESLint v9 with TypeScript ESLint +- Docker support for development services + +## Build and Development Workflow + +### Prerequisites and Environment Setup + +**ALWAYS run these commands in this exact order before any other operations:** + +1. **Enable corepack** (critical for pnpm): + + ```bash + corepack enable + ``` + +2. **Install dependencies** (use frozen lockfile in CI, regular install for development): + + ```bash + npm run bootstrap # Production: pnpm install --frozen-lockfile + # OR for development without network restrictions: + npm run bootstrap -- --ignore-scripts + ``` + +3. **Start development services** (required for full functionality): + ```bash + # Choose one based on your Docker setup: + npm run docker:dev # If using standard docker + npm run nerdctl:dev # If using nerdctl/Rancher Desktop + ``` + +### Build Commands - Execution Order and Timing + +**CRITICAL: Build commands must be run in this specific order:** + +1. **Build Full Project** (5-8 minutes total): + ```bash + npm run build # Complete build including server, proxy, CLI + ``` + +**Known Issues & Workarounds:** + +- **Prisma Generation Failure**: Bootstrap may fail with "binaries.prisma.sh ENOTFOUND" - use `--ignore-scripts` flag and generate Prisma separately +- **Memory Issues**: Use `--max_old_space_size=4096` for Node.js in memory-intensive operations +- **Git Tags Missing**: Some builds (CLI) require git tags for version info - expect failures in fresh clones without release tags +- **Build Dependencies**: Libraries MUST be built before server/web components +- **Docker Services**: MongoDB, Redis, and other services in docker-compose.dev.yml are required for full functionality, but should not be required for building. +- **Windows-specific**: Special node-gyp configuration required (see CI workflow) + +### Testing Commands + +**Test Execution Order:** + +```bash +# Run all tests (10-15 minutes): +npm test # Equivalent: jest --verbose + +# Watch mode for development: +npm run test:watch + +# CI testing (with proper flags): +npm run test:ci # Includes --no-cache --ci flags +``` + +**Common Test Issues:** + +- Some tests may have network dependencies and console warnings (expected) +- Use `--detectOpenHandles --forceExit` flags for complete test runs + +### Linting and Code Quality + +**Linting Commands:** + +```bash +npm run lint # Full repository lint +npm run lint:common # Individual package linting +npm run lint:server # Server-specific linting +``` + +**Key Linting Rules:** + +- Header comments required on most TypeScript files +- Consistent type imports: Use `import type` for type-only imports (auto-fixable) +- No unused imports (auto-fixable) +- Vue component specific rules in components directories + +## Project Architecture and Layout + +### Core Directory Structure + +``` +/ +ā”œā”€ā”€ .github/ # GitHub workflows, templates, instructions +ā”œā”€ā”€ docs/ # Documentation site (Docusaurus) +ā”œā”€ā”€ script/ # Build helpers, deployment scripts +ā”œā”€ā”€ docker/ # Development services configuration +ā”œā”€ā”€ src/ # All source packages (34 packages) +│ ā”œā”€ā”€ aux-common/ # Core shared utilities and types +│ ā”œā”€ā”€ aux-server/ # Main web application server +│ │ ā”œā”€ā”€ aux-backend/ # Backend API configuration (includes Prisma) +│ │ ā”œā”€ā”€ aux-web/ # Frontend Vue.js applications +│ ā”œā”€ā”€ aux-vm/ # Virtual machine abstraction +│ ā”œā”€ā”€ aux-vm-browser/ # Browser VM implementation +│ ā”œā”€ā”€ aux-vm-client/ # Client VM implementation +│ ā”œā”€ā”€ aux-vm-node/ # Node.js VM implementation +│ ā”œā”€ā”€ aux-records/ # Backend API implementation +│ ā”œā”€ā”€ aux-runtime/ # Runtime execution engine (user scripts & functions, compilers, etc.) +│ ā”œā”€ā”€ casualos-cli/ # Command-line interface +│ └── [21 other packages] # Supporting libraries (crypto, websocket, etc.) +ā”œā”€ā”€ package.json # Root package.json with scripts +ā”œā”€ā”€ lerna.json # Lerna configuration +ā”œā”€ā”€ tsconfig.json # TypeScript project references +└── pnpm-workspace.yaml # PNPM workspace configuration +``` + +### Key Configuration Files + +- **eslint.config.mjs**: ESLint v9 flat config with TypeScript rules +- **tsconfig.json**: TypeScript project references for all packages +- **jest.config.js**: Jest testing configuration with ts-jest +- **docker/docker-compose.dev.yml**: Development services (MongoDB, Redis, MinIO, etc.) + +### CI/CD Pipeline (GitHub Actions) + +**Continuous Integration (.github/workflows/continuous-integration-workflow.yml):** + +- Triggers: All branches except master/staging/release +- Matrix: ubuntu/macOS/windows with Node 20.x +- Steps: bootstrap → test → build → lint → docs build +- **Critical**: Uses `corepack enable` and specific node-gyp setup for Windows + +**Release Pipeline (.github/workflows/release.yml):** + +- Triggers: master, staging, release/\* branches +- Steps: test → build → npm publish → docs deploy → Docker build/push +- Publishes to NPM, DockerHub, and GitHub Container Registry + +**Known CI Issues:** + +- Windows requires special node-gyp configuration +- Tests use `--max_old_space_size=4096` for memory management +- Docker builds have ARM32/ARM64 variants via AWS CodeBuild + +## Development Best Practices + +### Code Style Guidelines (from existing .github/instructions) + +- **Naming**: camelCase (variables/functions), PascalCase (classes), ALL_CAPS (constants) +- **Private members**: prefix with `_` and use `private` keyword +- **Imports**: Use ES6 modules, prefer `import type` for types +- **Documentation**: JSDoc with required `@dochash` and `@docid` tags +- **Error handling**: try/catch for void functions, always log errors with class/method names + +### TypeScript Project References + +The repository uses TypeScript project references for efficient incremental builds. Each package in src/ has its own tsconfig.json that references dependencies. + +### Package Dependencies + +- **Critical**: aux-common is foundational - many packages depend on it +- **Build order**: Libraries (aux-common, aux-vm, etc.) → Server → Applications +- **Circular dependencies**: Avoided through careful architecture + +### Release Notes + +Make sure to update CHANGELOG.md with a summary of the changes you've made. + +## Quick Start for Common Tasks + +### Making Code Changes + +1. `corepack enable && npm run bootstrap` +2. `npm run docker:dev` (start services) +3. Make your changes +4. `npm run build` (full build) +5. `npm test` (verify changes) +6. `npm run lint` (check code quality) +7. Update CHANGELOG.md + +### Working with Specific Packages + +```bash +# Build specific package: +npm run build:server +npm run build:proxy +npm run build:cli + +# Lint specific package: +npm run lint:common +npm run lint:server +npm run lint:components +``` + +### Development Server + +```bash +npm run watch # Starts Vite in watch mode + nodemon +# Server available at http://localhost:3000 and http://localhost:3002 +``` + +### Documentation Updates + +```bash +cd docs +npm start # Development server +npm run build # Production build +``` + +## Trust These Instructions + +These instructions are based on comprehensive analysis of the codebase, successful testing of build/test/lint commands, and examination of CI/CD pipelines. **Only search for additional information if these instructions are incomplete or incorrect for your specific task.** The commands and workflows documented here have been validated and account for the major dependencies and timing requirements of this complex monorepo. + +When in doubt, follow the exact command sequences provided, especially for bootstrap → build → test workflows which are critical for this repository's build system. diff --git a/.github/instructions/documentation.instructions.md b/.github/instructions/documentation.instructions.md new file mode 100644 index 0000000000..0bfd3b5b19 --- /dev/null +++ b/.github/instructions/documentation.instructions.md @@ -0,0 +1,86 @@ +--- +applyTo: 'src/*.ts' +description: This file contains JSDoc-specific coding standards and information for TypeScript files. +--- + +# JSDoc comments + +- Use JSDoc comments to document functions, classes, and interfaces. +- Use `@param` to document function parameters. +- Use `@returns` to document function return values. +- (Required) Use `@dochash` to specify the page that the documentation should be generated into. + - The following hashes are available: + - `actions/ai` + - `actions/bot-filters` + - `actions/bytes` + - `actions/crypto` + - `actions/data` + - `actions/debuggers` + - `actions/experimental` + - `actions/loom` + - `actions/math` + - `actions/mods` + - `actions/os/animations` + - `actions/os/app` + - `actions/os/audio` + - `actions/os/barcodes` + - `actions/os/camera` + - `actions/os/clipboard` + - `actions/os/event` + - `actions/os/files` + - `actions/os/geolocation` + - `actions/os/image-classification` + - `actions/os/input` + - `actions/os/ldraw` + - `actions/os/media` + - `actions/os/moderation` + - `actions/os/portals` + - `actions/os/spaces` + - `actions/os/system` + - `actions/os/records` + - `actions/os/remotes` + - `actions/os/rooms` + - `actions/os/time` + - `actions/os/xr` + - `actions/web` + - `types/ai` + - `types/core` + - `types/debuggers/common` + - `types/debuggers/debugger` + - `types/debuggers/pausable-debugger` + - `types/error` + - `types/experimental` + - `types/math/vectors` + - `types/math/rotations` + - `types/os/animations` + - `types/os/audio` + - `types/os/barcodes` + - `types/os/camera` + - `types/os/clipboard` + - `types/os/event` + - `types/os/files` + - `types/os/geolocation` + - `types/os/image-classification` + - `types/os/input` + - `types/os/media` + - `types/os/moderation` + - `types/os/portals` + - `types/os/spaces` + - `types/os/system` + - `types/os/xr` + - `types/records/key` + - `types/records/data` + - `types/records/files` + - `types/records/events` + - `types/records/roles` + - `types/records/policies` + - `types/records/extra` + - `types/web` +- (Required) Use `@docid` is used to specify the ID of the function/class in the documentation. +- `@docname` is used to specify the name that should be displayed in the documentation. + - This is useful for functions/classes that are exported with a different name than the one used in the code. + - Use this for functions in `AuxLibrary.ts`. For example, the `toast()` function is exported as `os.toast()`. +- Use `{@link}` to link to other documentation entries. + - Specify the `@docid` of the entry you want to link to. +- Use `{@tag [tag]}` to link to tags. +- Use `{@tag @[listener]}` to link to listen tags. diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md new file mode 100644 index 0000000000..17997d172b --- /dev/null +++ b/.github/instructions/typescript.instructions.md @@ -0,0 +1,33 @@ +--- +applyTo: '**/*.ts' +description: This file contains TypeScript-specific coding standards and documentation guidelines. +--- + +# Project general coding standards + +## Naming conventions + +- Use camelCase for variable and function names. +- Use PascalCase for class names. +- Prefix private class members with an underscore (`_`). +- Use ALL_CAPS for constants. + +## Code style + +- Use ES6 modules for imports and exports. +- Use const for variables that are not reassigned. +- Use let for variables that are reassigned. +- Use arrow functions for anonymous functions. +- Use template literals for string concatenation. +- Use async/await for asynchronous code. + +## Error handling + +- Use try/catch for error handling in functions that return void. +- Always log errors to the console. +- Include the class name and method name in error messages. + +## Documentation + +- Use JSDoc comments for public APIs. +- Use eslint and prettier for code formatting and linting. diff --git a/.github/workflows/claude-fix.yml b/.github/workflows/claude-fix.yml new file mode 100644 index 0000000000..697d8698b6 --- /dev/null +++ b/.github/workflows/claude-fix.yml @@ -0,0 +1,36 @@ +name: Claude Assistant + +on: + issue_comment: + types: [created] + workflow_dispatch: {} + +permissions: + id-token: write + contents: write + pull-requests: write + issues: read + +jobs: + claude: + runs-on: ubuntu-latest + environment: ai-api-keys + + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issue_comment' && + contains(github.event.comment.body, '@claude')) + + steps: + - uses: actions/checkout@v4 + + - name: Run Claude Code via Anthropic API + uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + model: ${{ secrets.CLAUDE_MODEL_1 }} + # To allow PR creation, enable GitHub tool: + allowed_tools: mcp__github__create_pull_request + # Optionally, set a custom prompt or use default: + direct_prompt: 'Read the issue and apply a fix; then open a PR.' + # `anthropic_api_key` is passed so Claude can authenticate diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index ce57b06e99..978eaa0275 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - node-version: [18.x] + node-version: [20.x] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 @@ -48,7 +48,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - node-version: [18.x] + node-version: [20.x] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 @@ -74,7 +74,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - node-version: [18.x] + node-version: [20.x] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 @@ -105,7 +105,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [18.x] + node-version: [20.x] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000000..037d4c4f80 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,39 @@ +name: 'Copilot Setup Steps' + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Install JavaScript dependencies + run: | + corepack enable + pnpm bootstrap diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b59e496e51..4b3f74f5ad 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,10 +18,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Use Node.js 18.x + - name: Use Node.js 20.x uses: actions/setup-node@v2 with: - node-version: 18.x + node-version: 20.x registry-url: 'https://registry.npmjs.org' - name: Corepack Enable run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ccb05316f..3bd0982916 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,10 +15,10 @@ jobs: CI: true steps: - uses: actions/checkout@v1 - - name: Use Node.js 18.x + - name: Use Node.js 20.x uses: actions/setup-node@v2 with: - node-version: 18.x + node-version: 20.x registry-url: 'https://registry.npmjs.org' - name: Fix node-gyp (Windows Only) if: matrix.os == 'windows-latest' diff --git a/.gitignore b/.gitignore index 5e50561af9..58b36d7a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ xpexchange extensions infra .qodo +.devdbrc \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index dd4268e690..205ebaf711 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,2 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx lint-staged -npx pretty-quick --staged +pnpm lint-staged +pnpm pretty-quick --staged diff --git a/.nvmrc b/.nvmrc index b6fb44b881..12419e9c57 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.17.1 \ No newline at end of file +v20.18.1 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 2368d2d786..bb38a13c1f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "**/.DS_Store": true, "**/*.js": { "when": "$(basename).ts" }, "**/*.js.map": true, + "**/*.tsbuildinfo": true, "src/aux-common/**/*.d.ts": { "when": "$(basename).ts" }, "src/aux-components/**/*.d.ts": { "when": "$(basename).ts" }, "src/aux-vm/**/*.d.ts": { "when": "$(basename).ts" }, @@ -57,6 +58,7 @@ "editor.insertSpaces": true, "editor.tabSize": 4, "editor.detectIndentation": false, + "javascript.preferences.importModuleSpecifier": "relative", "javascript.preferences.quoteStyle": "single", "typescript.preferences.quoteStyle": "single", "workbench.colorCustomizations": {}, @@ -64,5 +66,6 @@ "jest.autoEnable": false, "typescript.tsdk": "node_modules\\typescript\\lib", "typescript.tsserver.maxTsServerMemory": 8192, - "jest.autoRun": "off" + "jest.autoRun": "off", + "cSpell.words": ["KASP"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac8dce11f..8b2d73b85f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,513 @@ # CasualOS Changelog +## V3.8.0 + +#### Date: 10/14/2025 + +### :rocket: Features + +- Added the `os.eraseInst(recordKeyOrName, instName, options?)` function to delete insts programmatically. +- Added the `ai.listChatModels()` function to list the available chat models that the user can use based on their subscription. +- Changed webhooks to record logs to the same record that the webhook is stored in. +- Add the `os.syncConfigBotTagsToURL(tags, fullHistory?)` function. + - Tells CasualOS to sync the given list of config bot tags in the URL query. + - `tags` - The tags that should be synced to the URL. + - `fullHistory` - Whether the a history entry should be created for every change to these tags. If false, then the URL will be updated but no additional history entries will be created. If true, then each change to the parameters will create a new history entry. Defaults to true. + +### :bug: Bug Fixes + +- Fixed an issue where rapidly refreshing browser tabs could create duplicate remote connections that would persist in the `os.remotes()` list. The system now properly cleans up all remote device registrations when a connection is lost and prevents registering the current connection as a remote. +- Fixed an issue where tag masks in the shared space might be ignored after the server runs some cleanup. +- Fixed an issue where the `from` position values in `onDrag`, `onDrop`, `onAnyBotDrag`, and `onAnyBotDrop` events were being incorrectly rounded to integers, causing non-integer bot positions (e.g., 0.5, 0.4) to be rounded before being passed to event handlers. +- Fixed an issue where clicking the sheetPortal button in the systemPortal would only infer the dimension from gridPortal. Now it checks mapPortal first, then gridPortal, before requesting user input. +- Fixed an issue where it was not possible to publish packages to studios. +- Changed to ensure that the "Sign In" dialog is shown to unauthenticated users who are trying to access a public inst on Privo-enabled deployments. +- Static inst deletion now properly removes all associated data from browser storage. Previously, deleting a local inst would only remove it from the list while leaving bot data behind, causing old data to reappear when creating a new inst with the same name. + +## V3.7.1 + +#### Date: 9/23/2025 + +### :rocket: Features + +- Added the `os.createRecord(name)` function ([#668](https://github.com/casual-simulation/casualos/pull/668)). +- Added the `os.listSudioRecords(studioId)` function ([#666](https://github.com/casual-simulation/casualos/pull/666)). +- Added the the ability to specify markers when loading a shared document. ([#663](https://github.com/casual-simulation/casualos/pull/663)) +- Added the ability to view your notification subscriptions on the auth site. ([#680](https://github.com/casual-simulation/casualos/pull/680)) +- Added support for database records. + - Database records add the ability to run arbitrary SQL queries on SQLite database instances. + - Database records can be accessed via the following functions: + - `os.recordDatabase(request, options?)` - Creates or updates a database record. + - `os.eraseDatabase(recordName, address, options?)` - Deletes a database record and associated data. + - `os.listDatabases(recordName, startingAddress?, options?)` - Lists the databases that are stored in a record. + - `os.listDatabasesByMarker(recordName, marker, startingAddress?, options?)` - Lists the databases which have the given marker. + - `os.getDatabase(recordName, address)` - Gets a database instance that can be used to run queries against a database. Returns an object which has the following functions: + - `query` - A [tagged template literal function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) that can query the database. Only allows read-only queries. + - `execute` - A tagged template literal function that can execute SQL code on the database. Allows read-write. + - `batch(statements, readonly?)` - Accepts an array of statements which are all executed in a [transaction](https://sqlite.org/lang_transaction.html). If any one fails, then none of the statements will have any effect. + - `sql` - A tagged template literal function that constructs a statement from the given SQL and parameters. + - `run(statement, readonly?)` - Accepts a statement and executes it. + - `raw` - Gets an object that is able to interface with the database without any special mapping of rows. + - By default, database features are not enabled. To configure database features, specify the `databases` key in your `SERVER_CONFIG`: + - To configure SQLite: + ```json + { + "databases": { + "provider": { + "type": "sqlite", + "folderPath": "/path/to/sqlite/databases/folder" + } + } + } + ``` + - To configure Turso: + ```json + { + "databases": { + "provider": { + "type": "turso", + "organization": "YOUR_ORGANIZATION", + "token": "YOUR_API_TOKEN", + "group": "YOUR_DATABASE_GROUP" + } + } + } + ``` + +### :bug: Bug Fixes + +- Fixed an issue where the `Sec-Websocket-Protocol` header wasn't supported on API Gateway. +- Fixed an issue where the background color for comID logos would not be displayed at the same time as the comID logo. +- Fixed an issue where input events would not be handled correctly on newly updated Meta Quest devices. +- Reworked OpenID Connect sign-in to use redirects instead of popups, eliminating issues with browser popup blockers ([#652](https://github.com/casual-simulation/casualos/issues/652)). +- Fixed an issue where listing data by marker would force login. + +## V3.7.0 + +#### Date: 8/22/2025 + +### :boom: Breaking Changes + +- Changed local insts (`?staticInst` in the URL query) to no longer require separate permission for performing records actions. + - Previously, public (sometimes called free) insts and local insts had to be granted separate permission for actions that manipulated data. + - This led to a large number of uses of `os.grantInstAdminPermission()`, which was a workaround to allow people to grant an inst permissions. + - The goal was to replace this mechanism by encouraging switching to packages and entitlements which are able to automatically grant insts permissions based on the entitlement grants that users have given indiviaual packages. + - Unfortunately, packages aren't a good solution for scenarios when actively developing AUXes and (as a result) developers are forced to continue using `os.grantInstAdminPermission()` in development. + - Additionally, many developers prefer to use local insts instead of studio insts (which automatically get some permissions), seeing them as temporary and disposable unlike private insts. + - Now, local insts no longer need to be granted special permissions for records actions. + - The downside to this change is that local insts now pose a greater risk and attack vector for cross site scripting (XSS). In such a scenario, an attacker would give a user a malicious AUX that steals their data. If the user decides to run it in an inst, then their data might get stolen by the attacker. + +### :rocket: Features + +- Improved the `pack-aux` and `unpack-aux` commands in the CLI to replace bot IDs with a placeholder by default. + - This helps prevent version control churn if the AUX files are being packed and repacked a lot. +- Added additional properties to `@onClick` and `@onAnyBotClicked` for code tool bots: + - `codeBot` - The bot that is currently being displayed in the code editor. + - `codeTag` - The tag that is currently being displayed in the code editor. + - `codeTagSpace` - The space of the tag that is currently being displayed in the code editor. +- Added search records. + - Search records allow you to utilize [Typesense](https://typesense.org/) to easily search over a large number of documents. Each "Search record" maps to a Typesense [collection](https://typesense.org/docs/29.0/api/collections.html), which can store many documents. Documents are just JSON (kinda like data records). + - `os.recordSearchCollection(request)` - Creates or updates a Search collection. Each collection exists at an address and is assigned its own unique collection name. + - `os.getSearchCollection(recordName, address)` - Gets information about a search collection. + - `os.eraseSearchCollection(recordName, address)` - Deletes a search collection. + - `os.listSearchCollections(recordName, startingAddress?)` - Lists search collections in a record. + - `os.listSearchCollectionsByMarker(recordName, marker, startingAddress?)` - Lists search collections by marker. + - `os.recordSearchDocument(request)` - Creates a document inside a search collection. + - `os.eraseSearchDocument(recordName, address, documentId)` - Deletes a document from a search collection. + - To enable search records, you need to configure the [`typesense` object](./src/aux-records/ServerConfig.ts#L177) in the server config. + - Search records have the ability to be automatically synced from data records, but there is currently no API for this and needs to be setup on a case-by-case basis (for now). +- Added the ability to allow scripts to import some of the libraries that CasualOS itself uses. + - This now also makes it possible for other libraries to share some libraries with CasualOS itself. + - The following libraries can now be imported by scripts directly: + - `yjs` - The [YJS](https://yjs.dev/) CRDT library. + - `luxon` - The [Luxon](https://github.com/moment/luxon#readme) date and time library. + - `preact` - The [Preact](https://preactjs.com/) JSX rendering library. + - `preact/compat` - The `preact/compat` export of Preact. + - `preact/jsx-runtime` - The `preact/jsx-runtime` export of Preact. + - This change should now make it possible to use libraries that utilize React by setting up an import map (only on deployments which support full DOM features): + - ```typescript + const casualOsImportMap = + document.getElementById('default-import-map'); + if (!casualOsImportMap) { + console.error('CasualOS import map not found!'); + return; + } + const defaultImportMap = JSON.parse(casualOsImportMap.textContent); + const mapScript = document.createElement('script'); + mapScript.id = 'react-import-map'; + mapScript.type = 'importmap'; + mapScript.textContent = JSON.stringify({ + imports: { + react: defaultImportMap.imports['preact/compat'], + 'react-dom': defaultImportMap.imports['preact/compat'], + 'react/jsx-runtime': + defaultImportMap.imports['preact/jsx-runtime'], + }, + }); + document.head.append(mapScript); + ``` +- Added support for SQLite database backends. + - This now means that it is a bit easier to host CasualOS on singular nodes. +- Greatly reduced the initial bundle size by replacing Lodash with es-toolkit. + +### :bug: Bug Fixes + +- Fixed an issue in the `unpack-aux` CLI command where tags that failed to be written would be omitted from the bot AUX file. +- Fixed an issue where strings like `e123` would be recognized as numbers. +- Fixed an issue where strings that look like numbers could cause labels to render differently from their strings. +- Fixed an issue where `os.installPackage()` and `os.listInstalledPackages()` would require the user to login first. +- Fixed an issue where calling `os.getSharedDocument()` with a record key would sometimes fail. +- Fixed the "Document Actions" not being shown in the documentation sidebar. +- Fixed issues with inconsistent display of user subscriptions. + +## V3.6.0 + +#### Date: 7/28/2025 + +### :boom: Breaking Changes + +- Prevented the loading screen from being hidden until one of two things happens: + 1. One of the following portals is displayed: + - `gridPortal` + - `sheetPortal` + - `systemPortal` + - `miniGridPortal` + - `mapPortal` + - `miniMapPortal` + - `meetPortal` + - `tagPortal` + 2. `os.hideLoadingScreen()` is called. + - This change makes the loading experience able to be more deeply customized on a per-experience basis. +- comId's which have a configured `logoUrl` will now display the logo in fullscreen like a "splash screen". + - This gives more prominence to their branding and simplifies our white-labeling story moving forward. + +### :rocket: Features + +- Added the ability to prevent the `gridPortal` from loading automatically by setting the `noGridPortal` query parameter. +- Added the ability to display a fullscreen "splash screen" + - Can be controlled by the following environment variables: + - `LOGO_URL` - The URL to the logo that should be displayed. If set, then the logo will be displayed full screen. + - `LOGO_TITLE` - The descriptive title for the logo. Used as alt text for the logo. Additionally can be specified on its own to display a title in the regular loading dialog. + - `LOGO_BACKGROUND_COLOR` - The [color](https://developer.mozilla.org/en-US/docs/Web/CSS/background-color) that should be displayed for the splash screen background. +- Added the `os.hideLoadingScreen()` function. + - This function can be used to trigger hiding the loading screen by a script. + +### :bug: Bug Fixes + +- Actually fixed the issue where serverless AWS deployments of CasualOS wouldn't have permissions to send emails via [SES](https://aws.amazon.com/ses/). +- Fixed an issue with the CLI where `pack-aux` would not read files with non-standard extensions. + +## V3.5.6 + +#### Date: 7/23/2025 + +### :bug: Bug Fixes + +- Fixed an issue where serverless AWS deployments of CasualOS wouldn't have permissions to send emails via [SES](https://aws.amazon.com/ses/). + +## V3.5.5 + +#### Date: 7/22/2025 + +### :bug: Bug Fixes + +- Fixed an issue where the CLI would add `.txt` to tag names that already have an extension when unpacking an `.aux` file. +- Fixed an issue where duplicate bots could make the CLI try to write a tag to both the tag file and an additional `.txt` file. + +## V3.5.4 + +#### Date: 7/22/2025 + +### :bug: Bug Fixes + +- Fixed an issue where it was impossible to set listeners on bots immediately after they were created. +- Fixed an issue where it was impossible to delete listener overrides using the `delete` keyword. + +## V3.5.3 + +#### Date: 7/21/2025 + +### :boom: Breaking Changes + +- Changed the default mapPortal and miniMapPortal basemap to `dark-gray-vector` from `dark-gray` + - This may cause some things like labels and street markings to appear different. + +### :rocket: Features + +- Added the `Sec-Websocket-Protocol=casualos.records` header for websocket requests made to the records system. + - This can help load balancers differentiate between records requests and inst requests. +- Added a better date of birth input to the "Sign Up" page. +- Added the `os.calculateScreenCoordinatesFromPosition()` function. +- Added batching for events sent from the main thread to the worker thread. +- Added the `mapPortalKind` and `mapPortalGridKind` tags for the `mapPortalBot` and `miniMapPortalBot`. + - `mapPortalKind` can be used to cause the mapPortal or miniMapPortal to display the map as a flat plane instead of a sphere. + - `globe` - The Earth is displayed as a globe. (Default) + - `plane` - The Earth is displayed as a flat plane by using the Mercator projection. + - `mapPortalGridKind` can be used to cause the mapPortal or miniMapPortal use a grid that matches the globe or plane settings from `mapPortalKind`. + - `null` - The grid matches the `mapPortalKind`. (Default) + - `globe` - The grid aligns best with the `globe` `mapPortalKind`. + - `plane` - The grid aligns best with the `plane` `mapPortalKind`. +- Improved `mapPortalBasemap` to support [web tile URLs](https://developers.arcgis.com/javascript/latest/api-reference/esri-layers-WebTileLayer.html#urlTemplate). + - This allows you to set a custom basemap in the mapPortal or miniMapPortal. + - The URL should follow one of the following templates: + - `https://some.domain.com/{level}/{col}/{row}.png` + - `https://some.domain.com/{z}/{x}/{y}.png` + - Where anything in curly braces `{}` represents a template parameter that the mapPortal/miniMapPortal will fill with the corresponding coordinate information for the requested tile. +- Added the `os.addMapLayer(portal, layer)` and `os.removeMapLayer(layerId)` functions. + - These functions allow you to visualize and render [GeoJSON](https://geojson.org/) data in the mapPortal or miniMapPortal. + - `os.addMapLayer(portal, layer)` - Adds a map layer to the given portal. Returns a promise that resolves with string with the ID of the layer that was added. It accepts the following arguments: + - `portal` - The portal that the layer should be rendered in. Can either be `map` or `miniMap`. + - `layer` - The layer that should be displayed. Should be an object with the following structure: + - `type` - The type of the layer. Currently only `geojson` is supported. + - `url` - (Optional) The URL that the GeoJSON data can be downloaded from. + - `data` - (Optional) The GeoJSON data that is contained in the layer. Required if `url` is not specified. + - `os.removeMapLayer(layerId)` - Removes a map layer from the portal it is in. Returns a promise that resolves once the layer has been removed. + - `layerId` - The ID of the layer that should be removed. You can get this from the resolved value from `os.addMapLayer()`. +- Added the `os.addBotListener(bot, tag, listener)` and `os.removeBotListener(bot, tag, listener)` functions. + - `os.addBotListener(bot, tag, listener)` Adds the given listener to the given bot for the given tag. + - It can be used to add arbitrary functions to be triggered when a listener would be triggered on a bot. + - This function only adds listeners, it cannot override existing listeners. + - Listeners added by this function will not be called if the bot is not listening. + - `bot` is the bot that the lister should be added to. + - `tag` is the [listen tag](https://docs.casualos.com/tags/listen/). + - `listener` is the function that should be called when the listen tag is triggered. It should be a function that accepts the following arguments: + - `that` - the `that` argument of the listener. + - `bot` - The bot that the listener was triggered on. (Same as `bot` above) + - `tag` - The name of the tag. (Same as `tag` above) + - `os.removeBotListener(bot, tag, listener)` Removes the given listener from the given bot and tag. + - This function can only be used to remove listeners which have been added by `os.addBotListener()`. + - `bot` is the bot that the listener should be removed from. + - `tag` is the [listen tag](https://docs.casualos.com/tags/listen/). + - `listener` is the function that should be removed. +- Added the ability to add/override bot listen tags by setting `bot.listeners.tag`. + + - For example, the following code will override the `onClick` listen tag to toast "overridden" when the bot is clicked: + + ```typescript + bot.tags.onClick = `@os.toast("default")`; + bot.listeners.onClick = () => os.toast('overridden'); + + // when bot is clicked, "overridden" will be toasted instead of "default". + ``` + + - Just like the `masks` property, the `listeners` property allows you to override the default tags. + - The difference is that `listeners` is all about listen tags and functions. You can only set functions, and `listeners` are always `tempLocal`. + - Functions set on the `listeners` property will override listen tags set by tags and tag masks. + - It will only override the listener, not the tag data. In the example above, `bot.tags.onClick` will continue to return `@os.toast("default")`. + +- Improved the CLI to support packing and unpacking AUX files into a file and folder structure. + - See [#605](https://github.com/casual-simulation/casualos/issues/605) + - The following commands are now available: + - `pack-aux` - Takes a folder and builds an `.aux` file from it. + - `unpack-aux` - Takes a `.aux` file and builds a folder from it. + - See the CLI documentation for more information. + +### :bug: Bug Fixes + +- Improved error handling for `ai.stream.chat()` and `ai.generateImage()`. +- Added documentation for `crypto.decrypt()`. + +## V3.5.2 + +#### Date: 6/13/2025 + +### :boom: Breaking Changes + +- Made the VM iframe unable to process pointer events while the game view (gridPortal, mapPortal, etc.) is visible. The VM iframe is now always shown. + + - This is a breaking change because the VM iframe (DOM) used to be always interactable behind the game view. + - Now, it will always be shown, but only be interactable when all the game portals are hidden (set to null). + - The previous breaking change was a workaround to fix a bug in MacOS Chrome that causes iframes to intercept some events from the main window. + - This change is a another workaround to fix a bug in MacOS Chrome that causes the game portals frame rate to be severly reduced while the VM iframe is hidden. + - To preserve the old behavior, you can use the following code: + + ```typescript + await os.registerApp('alwaysShowIframe', thisBot); + + const css = ` + .vm-iframe-container iframe:first-child { + pointer-events: auto !important; + } + `; + + os.compileApp( + 'alwaysShowIframe', +
+ +
+ ); + ``` + +## V3.5.1 + +#### Date: 6/13/2025 + +### :boom: Breaking Changes + +- Made the VM iframe be hidden while the game view (gridPortal, mapPortal, etc.) is visible. + + - This is a breaking change because the VM iframe (DOM) used to be always visible behind the game view. + - Now, it will only be visible when all the game portals are hidden (set to null). + - This change is a workaround to fix a bug in MacOS Chrome that causes iframes to intercept some events from the main window. In particular, this bug broke the CasualOS `@onFileUpload` shout. + - To preserve the old behavior, you can use the following code: + + ```typescript + await os.registerApp('alwaysShowIframe', thisBot); + + const css = ` + .vm-iframe-container iframe:first-child { + display: block !important; + } + `; + + os.compileApp( + 'alwaysShowIframe', +
+ +
+ ); + ``` + +### :rocket: Features + +- Improved `os.loadInst()` and `os.unloadInst()` to accept a configuration object that can specify more information on the kind of inst to load. + - Loading insts in this manner will not add them to the URL. + - The following properties are supported: + - `staticInst`: Equivalent to the [`staticInst` tag](https://docs.casualos.com/tags/config-bot#staticinst). + - `inst`: Equivalent to the [`inst` tag](https://docs.casualos.com/tags/config-bot#inst) + - `record`: Equivalent to the [`record` tag](https://docs.casualos.com/tags/config-bot#record) + - `owner`: Equivalent to the [`owner` tag](https://docs.casualos.com/tags/config-bot#owner). + - For example: + ```typescript + os.loadInst({ + staticInst: 'myInst', + }); + ``` + will load the `myInst` static (local) inst just like using the `?staticInst=myInst` query parameter in the URL. +- Added a [Fiduciary License Agreement](https://gist.github.com/KallynGowdy/5cbc3a6da651e88838c02b734d3b7e80) for CasualOS to help ensure that Casual Simulation has proper licensing agreements with individual contributors. + - Uses [cla-assistant](https://cla-assistant.io/) to collect signatures to the FLA. +- Improved the menuPortal to support scrolling when the bots would exceed the size of the screen. +- Added the `@onPackageInstalled` shout. + - Sent once `os.installPackage()` completes successfully. + - `that` includes information about the package which was installed. + +### :bug: Bug Fixes + +- Fixed an issue where the DOM was not able to be interacted with. +- Fixed an issue where CasualOS may break during initialization if a `define_global_bot` event is processed before the initial state update. +- Fixed an issue where providing an invalid endpoint to a records function would cause the function to never resolve. +- Fixed an issue where package versions could not be recorded on Privo-enabled deployments. + +## V3.5.0 + +#### Date: 6/6/2025 + +### :boom: Breaking Changes + +- Updated to Node 20.18 and PNPM 10.10. + - Updating PNPM required updating the `pnpm-lock.yaml` file. +- Changed `@onKeyDown` and `@onKeyUp` to whisper when a user is typing in a menu bot. +- Moved `#app-game-container` ID to the DIV that directly contains the gridPortal canvas. + - This will make it easier to control the sizing of the gridPortal and mapPortal by applying custom styles that affect `#app-game-container`. + - This may break existing custom apps which apply custom styles to `#app-game-container`. +- Reset the built-in (default) version of ab-1 to a much older version that respects CasualOS defaults behaviors. + - This only affects environments which do not configure an `AB1_BOOTSTRAP_URL`. + +### :rocket: Features + +- Added Package Records + - Packages make it easy to save and load different versions of AUX files. + - Packages are organized by record name, address, and then key. + - Keys help differentiate between package versions and they look like this: `v1.0.0` + - Each key has four components: + - `major` number + - `minor` number + - `patch` number + - `tag` string + - When formatted, a key looks like this: `v{major}.{minor}.{patch}`. + - When a tag is specified, then the key looks like this: `v{major}.{minor}.{patch}-{tag}`. + - Packages are easy to install, and scripts have the ability to request a particular version of a package to install. + - Packages can request entitlements, which will make it easier to manage permissions for insts. + - Once recorded, the contents of a package version cannot be updated. +- Added the following functions: + - `os.grantEntitlements(request)`: Requests that a package be granted a list of entitlement features for a record. + - `os.parseVersionKey(key)`: Parses the given string into a package version key. + - `os.formatVersionKey(key)`: Formats the given version key as a string. + - `os.recordPackageVersion(request)`: Records a package version to the given record name, address, and key. + - `os.listPackageVersions(recordName, address)`: Gets the list of package versions that are available for the given record name and address. + - `os.getPackageVersion(recordName, address, key?)`: Gets the package version with the given key. If the key is omitted, then the latest package version will be retrieved. The returned package includes information about the file that should be downloaded to get the package contents. + - `os.erasePackageVersion(recordName, address, key)`: Deletes the given package version. + - `os.recordPackageContainer(recordName, address, markers?)`: Creates a new container for package versions. The given marker can be used to restrict who is able to list package versions. + - `os.erasePackageContainer(recordName, address)`: Deletes the given package container and all packages stored in it. + - `os.listPackageContainers(recordName, address?)`: Lists package containers in the given record. + - `os.listPackageContainersByMarker(recordName, marker, address?)`: Lists package containers with the given marker. + - `os.getPackageContainer(recordName, address)`: Gets a package container by address. + - `os.installPackage(recordName, address, key?)`: Installs the specified package version in the current inst. If key is omitted, then the latest package version will be installed. + - `os.listInstalledPackages()`: Gets the list of packages that have been installed in this inst. + - `os.installAuxFile(state, mode?)`: Installs the given AUX file in this inst. `mode` can be set to "copy" to force CasualOS to always produce new copies of bots. + - `os.getAuxFileForBots(bots, options?)`: Gets an AUX file that represents the given bots. `options` can be used to specify which version of the AUX file format you want to receive. This function works like `os.downloadBots()`, but it returns the AUX file instead of downloading it to the user's device. +- Added support for multi-server websocket deployments of CasualOS. + - Uses Redis pub/sub as a message broker. + - Individual CasualOS deployments are still stateless: they only need to be able to access Redis and the DB. +- Improved `experiment.createStaticHtmlFromBots()` to return a smaller file and also better support alternative enviroments. + - There is no background thread when bots are running in static HTML, so they have full access to everything in CasualOS and also the main thread. + - Static HTML also doesn't have access to every CasualOS feature. Non-essential features have been removed, like: + - The multi-line code editor. + - Some built-in fonts like `noto-sans-kr`. + - Some built-in GLTF models (like VR controller models). + - Some icons (some CasualOS-custom icons in the sheet portal and system portal). +- Added support for the `map` form. + - Set `formAddress` to a [Vector](https://docs.casualos.com/tags#vector-tags) to center the map on a particular longitude and latitude. + - `formMapProvider` can be set to one of the following values: + - `bing`, `bingmaps` to use [Bing Maps](https://www.bing.com/maps). + - `openstreetmap`, `openstreetmaps`, `osm` to use [OpenStreetMap](https://www.openstreetmap.org/). + - `mapbox` to use [MapBox](https://www.mapbox.com/) + - `google`, `googlemaps` to use [Google Maps](https://maps.google.com/) + - `maptiler` to use [MapTiler](https://www.maptiler.com/) + - `openmaptiles`, `openmaptile`, `omt` to use [OpenMapTiles](https://openmaptiles.org/) + - `here`, `heremap`, `heremaps` to use [Here Maps](https://www.here.com/) + - A custom URL of the format: `https://{s}.example.com/{z}/{x}/{y}.png` + - `formMapProviderAPIKey` can be provided to set an API key for map providers. + - `formMapLOD` can be used to set the zoom level (higher values appear more zoomed in). + - `formMapHeightProvider` can be set to `maptiler` to load elevation data from the MapTiler API. + - `formMapHeightProviderAPIKey` can be set to use a specific API key for the `formMapHeightProvider`. + - `formMapHeightOffset` can be set to offset the displayed heights when a `formMapHeightProvider` is provided. +- Updated lambda functions to use 1024 MB instead of 256 MB. +- Added a note for `@onBotChanged` to the documentation that it will only be triggered if the bot was not just created. +- Added the `formInputMultiline` tag. + - Set to `true` to allow multiple lines in `input` form menu bots. + - Set to `false` to disallow multiple lines. + - Set to `null` to allow multiple lines by using Shift+Enter. + +### :bug: Bug Fixes + +- Fixed an issue where quickly typing in an input element could cause the cursor to jump to the end of the content in the input box. +- Fixed an issue where it was possible for the `@onInstJoined` shout to be sent before all initial tag mask have been processed. +- Fixed an issue where updating a new bot could cause the new bot to not be properly synchronized throughout the system. +- Fixed an issue where it was impossible to upload files to records that have spaces in their names. +- Fixed an issue where bots in the `tempShared` space might not re-appear on other devices after reconnecting. +- Fixed an issue where it was impossible to report an inst. + +## V3.4.5 + +#### Date: 4/18/2025 + +### :bug: Bug Fixes + +- Fixed an issue where the `os.calculateViewportCoordinatesFromPosition()` function returned incorrect coordinate values when used in the mapPortal. + - Function now properly converts longitude/latitude coordinates to accurate viewport positions, ensuring consistent behavior between gridPortal and mapPortal. +- Fixed an issue with custom apps where setting the `selected` property on an `