diff --git a/.all-contributorsrc b/.all-contributorsrc index ced3b155b..a2d13c2fc 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,6 +1,7 @@ { "files": [ - "website/src/pages/credits.mdx" + "website/src/pages/credits.mdx", + "docs/src/assets/contributors.html" ], "imageSize": 75, "commit": false, diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..5edf609fa --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,71 @@ +language: en-US +tone_instructions: '' +early_access: false +enable_free_tier: true +reviews: + profile: chill + request_changes_workflow: false + high_level_summary: true + high_level_summary_placeholder: '@coderabbitai summary' + auto_title_placeholder: '@coderabbitai' + review_status: true + poem: true + collapse_walkthrough: false + sequence_diagrams: true + path_filters: [] + path_instructions: [] + abort_on_close: true + auto_review: + enabled: true + auto_incremental_review: true + ignore_title_keywords: [] + labels: [] + drafts: false + base_branches: ['v3-alpha', 'master'] + tools: + shellcheck: + enabled: true + ruff: + enabled: true + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + enabled_only: false + level: default + biome: + enabled: true + hadolint: + enabled: true + swiftlint: + enabled: true + phpstan: + enabled: true + level: default + golangci-lint: + enabled: true + yamllint: + enabled: true + gitleaks: + enabled: true + checkov: + enabled: true + detekt: + enabled: true + eslint: + enabled: true +chat: + auto_reply: true +knowledge_base: + opt_out: false + learnings: + scope: auto + issues: + scope: auto + jira: + project_keys: [] + linear: + team_keys: [] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 84b7cb6dc..c8ace9747 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,6 +7,8 @@ body: - type: markdown attributes: value: | + ***Please note: No bug reports are currently being accepted for Wails v3*** + ***Please note: No bug reports are currently being accepted for Wails v3*** ***Please note: No bug reports are currently being accepted for Wails v3*** Before submitting this issue, please do the following: - Do a web search for your error. This usually leads to a much better understanding of the issue. diff --git a/.github/file-labeler.yml b/.github/file-labeler.yml new file mode 100644 index 000000000..69494cbae --- /dev/null +++ b/.github/file-labeler.yml @@ -0,0 +1,44 @@ +# File path specific labels +v2-only: + - 'v2/**/*' + +v3-alpha: + - 'v3/**/*' + +windows: + - '**/*_windows.go' + - 'v2/internal/frontend/desktop/windows/**/*' + +macos: + - '**/*_darwin.go' + - 'v2/internal/frontend/desktop/darwin/**/*' + +linux: + - '**/*_linux.go' + - 'v2/internal/frontend/desktop/linux/**/*' + +cli: + - 'v2/cmd/**/*' + - 'v3/cmd/**/*' + - '**/cli/**/*' + - '**/commands/**/*' + +documentation: + - '**/*.md' + - 'docs/**/*' + - 'website/**/*' + - 'mkdocs-website/**/*' + +templates: + - '**/templates/**/*' + - '**/template/**/*' + +runtime: + - '**/runtime/**/*' + - 'v2/internal/runtime/**/*' + - 'v3/internal/runtime/**/*' + +bindings: + - 'v2/internal/binding/**/*' + - 'v3/internal/generator/**/*' + diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml new file mode 100644 index 000000000..0a7949051 --- /dev/null +++ b/.github/issue-labeler.yml @@ -0,0 +1,144 @@ +# Version labels +v2-only: + - '\[v2\]' + - '\(v2\)' + - 'v2:' + - 'version 2' + - 'wails v2' + - 'using v2' + - 'master branch' + +v3-alpha: + - '\[v3\]' + - '\(v3\)' + - 'v3:' + - '\[v3-alpha\]' + - '\(v3-alpha\)' + - 'version 3' + - 'wails v3' + - 'using v3' + - 'v3-alpha branch' + +# Component labels +webview2: + - 'webview2' + - 'windows' + - 'microsoft edge' + - 'edge browser' + - 'IE' + - 'Explorer' + - 'browser crashes' + +macos: + - 'macOS' + - 'mac OS' + - 'OS X' + - 'darwin' + - 'cocoa' + - 'Safari' + - 'Catalyst' + - 'Ventura' + - 'Sonoma' + - 'apple' + +linux: + - 'linux' + - 'ubuntu' + - 'debian' + - 'fedora' + - 'gtk' + - 'webkitgtk' + - 'webkit2gtk' + - 'gnome' + - 'x11' + - 'wayland' + +cli: + - 'cli' + - 'command line' + - 'wails doctor' + - 'wails init' + - 'wails build' + - 'wails dev' + - 'template' + - 'scaffolding' + +# Type labels +bug: + - 'bug' + - 'crash' + - 'broken' + - 'failure' + - 'error' + - 'failed' + - 'panic' + - 'segfault' + - 'issue' + - 'not working' + - 'problem' + +enhancement: + - 'feature' + - 'enhancement' + - 'request' + - 'add' + - 'new' + - 'improve' + - 'functionality' + - 'support for' + - 'please add' + - 'would be nice' + +documentation: + - 'docs' + - 'documentation' + - 'readme' + - 'example' + - 'tutorial' + - 'guide' + - 'explanation' + - 'clarification' + - 'instructions' + +security: + - 'security' + - 'vulnerability' + - 'exploit' + - 'hack' + - 'CVE' + - 'secure' + - 'encryption' + - 'hardening' + +performance: + - 'performance' + - 'slow' + - 'speed' + - 'memory leak' + - 'cpu usage' + - 'high memory' + - 'lag' + - 'freeze' + - 'optimization' + +# Priority labels +high-priority: + - 'urgent' + - 'critical' + - 'security' + - 'high priority' + - 'important' + - 'production' + - 'blocker' + - 'blocking' + +question: + - 'how to' + - 'how do i' + - 'can I' + - 'is it possible' + - 'question' + - 'help me' + - 'need help' + - 'assistance' + - 'confused' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bf3d8de39..9f8d049ba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,20 @@ # Description @@ -14,7 +25,7 @@ Fixes # (issue) ## Type of change -Please delete options that are not relevant. +Please select the option that is relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) @@ -28,6 +39,8 @@ Please describe the tests that you ran to verify your changes. Provide instructi - [ ] Windows - [ ] macOS - [ ] Linux + +If you checked Linux, please specify the distro and version. ## Test Configuration @@ -35,7 +48,7 @@ Please paste the output of `wails doctor`. If you are unable to run this command # Checklist: -- [ ] I have updated `website/src/pages/changelog.mdx` with details of this PR +- [ ] I have updated `v3/UNRELEASED_CHANGELOG.md` with details of this PR - [ ] My code follows the general coding style of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas diff --git a/.github/stale.yml b/.github/stale.yml index 805bd589d..d8bcc83ec 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Number of days of inactivity before an issue becomes stale -daysUntilStale: 30 +daysUntilStale: 45 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 10 # Issues with these labels will never be considered stale exemptLabels: - pinned @@ -9,14 +9,28 @@ exemptLabels: - onhold - inprogress - "Selected For Development" + - bug + - enhancement + - v3-alpha + - high-priority # Label to use when marking an issue as stale -staleLabel: "Wont Fix" +staleLabel: "stale" # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + recent activity. It will be closed if no further activity occurs within the next 10 days. + + If this issue is still relevant, please add a comment to keep it open. + Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false +closeComment: > + This issue has been automatically closed due to lack of activity. + Please feel free to reopen it if it's still relevant. exemptMilestones: true exemptAssignees: true +# Only mark issues (not PRs) +only: issues +# Exempt issues created before a certain date +exemptCreatedBefore: "2024-01-01T00:00:00Z" +# Starts checking issues only after the specified date +startDate: "2025-06-01T00:00:00Z" diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml new file mode 100644 index 000000000..097eba533 --- /dev/null +++ b/.github/workflows/auto-label-issues.yml @@ -0,0 +1,33 @@ +name: Auto Label Issues + +on: + issues: + types: [opened, edited, reopened] + pull_request_target: + types: [opened, edited, reopened, synchronize] + +jobs: + auto-label: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Label issues and PRs by content + uses: github/issue-labeler@v3.4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: .github/issue-labeler.yml + enable-versioned-regex: 0 + include-title: 1 + + - name: Label issues and PRs by file paths + uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + configuration-path: .github/file-labeler.yml + sync-labels: true diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml new file mode 100644 index 000000000..e88f54eb9 --- /dev/null +++ b/.github/workflows/automated-releases.yml @@ -0,0 +1,372 @@ +name: Automated Nightly Releases + +on: + workflow_dispatch: + inputs: + force_release: + description: 'Force release even if no changes detected' + required: false + default: false + type: boolean + dry_run: + description: 'Run in dry-run mode (no actual releases)' + required: false + default: false + type: boolean + # schedule: + # Run at 2 AM UTC every day - DISABLED for safety until ready + # - cron: '0 2 * * *' + +env: + GO_VERSION: '1.24' + +jobs: + check-permissions: + name: Check Release Permissions + runs-on: ubuntu-latest + outputs: + authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized for releases + id: check + run: | + # Only allow specific users to trigger releases + AUTHORIZED_USERS="leaanthony" + + if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then + echo "✅ User ${{ github.actor }} is authorized for releases" + echo "authorized=true" >> $GITHUB_OUTPUT + else + echo "❌ User ${{ github.actor }} is not authorized for releases" + echo "authorized=false" >> $GITHUB_OUTPUT + fi + + detect-v2-changes: + name: Detect v2 Changes + runs-on: ubuntu-latest + needs: check-permissions + if: needs.check-permissions.outputs.authorized == 'true' + outputs: + has_changes: ${{ steps.changes.outputs.has_changes }} + commits_since_last: ${{ steps.changes.outputs.commits_since_last }} + last_release_tag: ${{ steps.changes.outputs.last_release_tag }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check for v2 changes since last release + id: changes + run: | + echo "🔍 Checking for v2 changes since last release..." + + # Find the last v2 release tag + LAST_TAG=$(git tag -l "v2.*" --sort=-version:refname | head -n 1) + if [ -z "$LAST_TAG" ]; then + echo "No previous v2 tags found, assuming first release" + LAST_TAG=$(git rev-list --max-parents=0 HEAD) + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "commits_since_last=999" >> $GITHUB_OUTPUT + echo "last_release_tag=none" >> $GITHUB_OUTPUT + else + echo "Last v2 release tag: $LAST_TAG" + echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT + + # Count commits since last release affecting v2 or root files + COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v2/ website/ README.md CHANGELOG.md || echo "0") + echo "Commits since last v2 release: $COMMITS_COUNT" + echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT + + if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then + echo "✅ Changes detected or forced release" + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "ℹ️ No changes detected since last release" + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + fi + + detect-v3-changes: + name: Detect v3-alpha Changes + runs-on: ubuntu-latest + needs: check-permissions + if: needs.check-permissions.outputs.authorized == 'true' + outputs: + has_changes: ${{ steps.changes.outputs.has_changes }} + commits_since_last: ${{ steps.changes.outputs.commits_since_last }} + last_release_tag: ${{ steps.changes.outputs.last_release_tag }} + steps: + - name: Checkout v3-alpha branch + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check for v3-alpha changes since last release + id: changes + run: | + echo "🔍 Checking for v3-alpha changes since last release..." + + # Find the last v3 alpha release tag + LAST_TAG=$(git tag -l "v3.*-alpha.*" --sort=-version:refname | head -n 1) + if [ -z "$LAST_TAG" ]; then + echo "No previous v3-alpha tags found, assuming first release" + LAST_TAG=$(git rev-list --max-parents=0 HEAD) + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "commits_since_last=999" >> $GITHUB_OUTPUT + echo "last_release_tag=none" >> $GITHUB_OUTPUT + else + echo "Last v3-alpha release tag: $LAST_TAG" + echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT + + # Count commits since last release affecting v3 or docs + COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v3/ docs/ || echo "0") + echo "Commits since last v3-alpha release: $COMMITS_COUNT" + echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT + + if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then + echo "✅ Changes detected or forced release" + echo "has_changes=true" >> $GITHUB_OUTPUT + else + echo "ℹ️ No changes detected since last release" + echo "has_changes=false" >> $GITHUB_OUTPUT + fi + fi + + release-v2: + name: Create v2 Release + runs-on: ubuntu-latest + needs: [check-permissions, detect-v2-changes] + if: | + needs.check-permissions.outputs.authorized == 'true' && + needs.detect-v2-changes.outputs.has_changes == 'true' + outputs: + version: ${{ steps.release.outputs.version }} + release_notes: ${{ steps.release.outputs.release_notes }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run v2 release script and extract notes + id: release + run: | + echo "🚀 Running v2 release script..." + cd v2/tools/release + + # Run release script and capture output + RELEASE_OUTPUT=$(go run release.go 2>&1) + echo "$RELEASE_OUTPUT" + + # Extract version from output or version file + NEW_VERSION=$(cat ../../cmd/wails/internal/version.txt) + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + # Extract release notes from delimited output + RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d') + + # Save release notes to file for multiline output + echo "$RELEASE_NOTES" > ../../../release_notes_v2.md + + # Set output (escape for GitHub Actions) + { + echo "release_notes<> $GITHUB_OUTPUT + + echo "✅ v2 release script completed - version: $NEW_VERSION" + + - name: Create v2 git tag and release + if: github.event.inputs.dry_run != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.release.outputs.version }}" + echo "📝 Creating v2 release: $VERSION" + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Commit the changelog changes + git add website/src/pages/changelog.mdx v2/cmd/wails/internal/version.txt + git commit -m "chore: release $VERSION + + Automated release created by GitHub Actions + + 🤖 Generated with [Claude Code](https://claude.ai/code) + + Co-Authored-By: Claude " + + # Create and push tag + git tag -a "$VERSION" -m "Release $VERSION" + git push origin master + git push origin "$VERSION" + + # Create GitHub release with notes + gh release create "$VERSION" \ + --title "Release $VERSION" \ + --notes-file release_notes_v2.md \ + --target master + + - name: Log dry-run results for v2 + if: github.event.inputs.dry_run == 'true' + run: | + echo "🧪 DRY RUN - Would have created v2 release:" + echo "Version: ${{ steps.release.outputs.version }}" + echo "Release Notes:" + cat release_notes_v2.md + + release-v3: + name: Create v3-alpha Release + runs-on: ubuntu-latest + needs: [check-permissions, detect-v3-changes] + if: | + needs.check-permissions.outputs.authorized == 'true' && + needs.detect-v3-changes.outputs.has_changes == 'true' + outputs: + version: ${{ steps.release.outputs.version }} + release_notes: ${{ steps.release.outputs.release_notes }} + steps: + - name: Checkout v3-alpha branch + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run v3 release script and extract notes + id: release + run: | + echo "🚀 Running v3-alpha release script..." + cd v3/tasks/release + + # Run release script and capture output + RELEASE_OUTPUT=$(go run release.go 2>&1) + echo "$RELEASE_OUTPUT" + + # Extract version from output or version file + NEW_VERSION=$(cat ../../internal/version/version.txt) + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + # Extract release notes from delimited output + RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d') + + # Save release notes to file for multiline output + echo "$RELEASE_NOTES" > ../../../release_notes_v3.md + + # Set output (escape for GitHub Actions) + { + echo "release_notes<> $GITHUB_OUTPUT + + echo "✅ v3-alpha release script completed - version: $NEW_VERSION" + + - name: Create v3-alpha git tag and release + if: github.event.inputs.dry_run != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.release.outputs.version }}" + echo "📝 Creating v3-alpha release: $VERSION" + + # Configure git + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Commit the changelog changes + git add docs/src/content/docs/changelog.mdx v3/internal/version/version.txt + git commit -m "chore: release $VERSION + + Automated v3-alpha release created by GitHub Actions + + 🤖 Generated with [Claude Code](https://claude.ai/code) + + Co-Authored-By: Claude " + + # Create and push tag + git tag -a "$VERSION" -m "Release $VERSION" + git push origin v3-alpha + git push origin "$VERSION" + + # Create GitHub release with notes + gh release create "$VERSION" \ + --title "Release $VERSION" \ + --notes-file release_notes_v3.md \ + --target v3-alpha \ + --prerelease + + - name: Log dry-run results for v3-alpha + if: github.event.inputs.dry_run == 'true' + run: | + echo "🧪 DRY RUN - Would have created v3-alpha release:" + echo "Version: ${{ steps.release.outputs.version }}" + echo "Release Notes:" + cat release_notes_v3.md + + summary: + name: Release Summary + runs-on: ubuntu-latest + needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3] + if: always() && needs.check-permissions.outputs.authorized == 'true' + steps: + - name: Create release summary + run: | + echo "# 🚀 Automated Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Repository**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY + echo "**Triggered by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "**Dry Run Mode**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # v2 Summary + echo "## v2 Release" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.detect-v2-changes.outputs.has_changes }}" == "true" ]; then + if [ "${{ needs.release-v2.result }}" == "success" ]; then + echo "✅ **v2 Release**: Created successfully" >> $GITHUB_STEP_SUMMARY + echo " - Version: ${{ needs.release-v2.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **v2 Release**: Failed" >> $GITHUB_STEP_SUMMARY + fi + else + echo "⏭️ **v2 Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + fi + + # v3 Summary + echo "## v3-alpha Release" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.detect-v3-changes.outputs.has_changes }}" == "true" ]; then + if [ "${{ needs.release-v3.result }}" == "success" ]; then + echo "✅ **v3-alpha Release**: Created successfully" >> $GITHUB_STEP_SUMMARY + echo " - Version: ${{ needs.release-v3.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **v3-alpha Release**: Failed" >> $GITHUB_STEP_SUMMARY + fi + else + echo "⏭️ **v3-alpha Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY + echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "🤖 **Automated Release System** | Generated with [Claude Code](https://claude.ai/code)" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml new file mode 100644 index 000000000..a1b482e11 --- /dev/null +++ b/.github/workflows/build-and-test-v3.yml @@ -0,0 +1,256 @@ +name: Build + Test v3 + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - v3-alpha + paths: + - 'v3/**' + pull_request_review: + types: [submitted] + branches: + - v3-alpha + +jobs: + check_approval: + name: Check PR Approval + runs-on: ubuntu-latest + if: github.base_ref == 'v3-alpha' + outputs: + approved: ${{ steps.check.outputs.approved }} + steps: + - name: Check if PR is approved + id: check + run: | + if [[ "${{ github.event.review.state }}" == "approved" || "${{ github.event.pull_request.approved }}" == "true" ]]; then + echo "approved=true" >> $GITHUB_OUTPUT + else + echo "approved=false" >> $GITHUB_OUTPUT + fi + + test_js: + name: Run JS Tests + needs: check_approval + runs-on: ubuntu-latest + if: github.base_ref == 'v3-alpha' + strategy: + matrix: + node-version: [20.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - name: Type-check runtime + working-directory: v3 + run: task runtime:check + + - name: Test runtime + working-directory: v3 + run: task runtime:test + + - name: Check that the bundled runtime builds + working-directory: v3 + run: task runtime:build + + - name: Check that the npm package builds + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run build + + - name: Store runtime build artifacts + uses: actions/upload-artifact@v4 + with: + name: runtime-build-artifacts + path: | + v3/internal/runtime/desktop/@wailsio/runtime/dist/ + v3/internal/runtime/desktop/@wailsio/runtime/types/ + v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.tsbuildinfo + + test_go: + name: Run Go Tests v3 + needs: [check_approval, test_js] + runs-on: ${{ matrix.os }} + if: github.base_ref == 'v3-alpha' + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + go-version: [1.24] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install linux dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + if: matrix.os == 'ubuntu-latest' + with: + packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk + version: 1.0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: "v3/go.sum" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Retrieve runtime build artifacts + uses: actions/download-artifact@v4 + with: + name: runtime-build-artifacts + path: v3/internal/runtime/desktop/@wailsio/runtime/ + + - name: Build Examples + working-directory: v3 + run: | + echo "Starting example compilation tests..." + task test:examples + echo "Example compilation tests completed successfully" + + - name: Run tests (mac) + if: matrix.os == 'macos-latest' + env: + CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13 + working-directory: v3 + run: go test -v ./... + + - name: Run tests (windows) + if: matrix.os == 'windows-latest' + working-directory: v3 + run: go test -v ./... + + - name: Run tests (ubuntu) + if: matrix.os == 'ubuntu-latest' + working-directory: v3 + run: > + xvfb-run --auto-servernum + sh -c ' + dbus-update-activation-environment --systemd --all && + go test -v ./... + ' + + - name: Typecheck binding generator output + working-directory: v3 + run: task generator:test:check + + cleanup: + name: Cleanup build artifacts + if: always() + needs: [test_js, test_go] + runs-on: ubuntu-latest + steps: + - uses: geekyeggo/delete-artifact@v5 + with: + name: runtime-build-artifacts + failOnError: false + + test_templates: + name: Test Templates + needs: test_go + runs-on: ${{ matrix.os }} + if: github.base_ref == 'v3-alpha' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + template: + - svelte + - svelte-ts + - vue + - vue-ts + - react + - react-ts + - preact + - preact-ts + - lit + - lit-ts + - vanilla + - vanilla-ts + go-version: [1.24] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install linux dependencies + uses: awalsh128/cache-apt-pkgs-action@latest + if: matrix.os == 'ubuntu-latest' + with: + packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config + version: 1.0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true + cache-dependency-path: "v3/go.sum" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build Wails3 CLI + working-directory: v3 + run: | + task install + wails3 doctor + + - name: Generate template '${{ matrix.template }}' + run: | + mkdir -p ./test-${{ matrix.template }} + cd ./test-${{ matrix.template }} + wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }} + cd ${{ matrix.template }} + wails3 build + + build_results: + if: ${{ always() }} + runs-on: ubuntu-latest + name: v3 Build Results + needs: [test_go, test_js, test_templates] + steps: + - run: | + go_result="${{ needs.test_go.result }}" + js_result="${{ needs.test_js.result }}" + templates_result="${{ needs.test_templates.result }}" + + if [[ $go_result == "success" || $go_result == "skipped" ]] && \ + [[ $js_result == "success" || $js_result == "skipped" ]] && \ + [[ $templates_result == "success" || $templates_result == "skipped" ]]; then + echo "All required jobs succeeded or were skipped" + exit 0 + else + echo "One or more required jobs failed" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/changelog-validation-v3.yml b/.github/workflows/changelog-validation-v3.yml new file mode 100644 index 000000000..8d4b97726 --- /dev/null +++ b/.github/workflows/changelog-validation-v3.yml @@ -0,0 +1,74 @@ +name: Changelog Validation (v3) + +on: + pull_request: + branches: [ v3-alpha ] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to validate (for manual testing)' + required: true + type: string + +jobs: + validate-changelog: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' || github.event.inputs.pr_number + + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || format('refs/pull/{0}/head', github.event.inputs.pr_number) }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + + - name: Get PR information + id: pr_info + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT + echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT + else + echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT + echo "base_ref=v3-alpha" >> $GITHUB_OUTPUT + fi + + - name: Check if changelog was modified + id: changelog_check + run: | + git fetch origin ${{ steps.pr_info.outputs.base_ref }} + if git diff --name-only origin/${{ steps.pr_info.outputs.base_ref }}..HEAD | grep -q "v3/UNRELEASED_CHANGELOG.md"; then + echo "changelog_modified=true" >> $GITHUB_OUTPUT + echo "✅ UNRELEASED_CHANGELOG.md was modified in this PR" + else + echo "changelog_modified=false" >> $GITHUB_OUTPUT + echo "⚠️ UNRELEASED_CHANGELOG.md was not modified" + fi + + - name: Comment on PR about missing changelog + if: steps.changelog_check.outputs.changelog_modified == 'false' && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const author = context.payload.pull_request.user.login; + const message = '## ⚠️ Missing Changelog Update\n\n' + + `Hi @${author}, please update \`v3/UNRELEASED_CHANGELOG.md\` with a description of your changes.\n\n` + + 'This helps us keep track of changes for the next release.'; + + await github.rest.issues.createComment({ + issue_number: ${{ steps.pr_info.outputs.pr_number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }); + diff --git a/.github/workflows/generate-sponsor-image.yml b/.github/workflows/generate-sponsor-image.yml index b8bb0c4ac..585d7e19f 100644 --- a/.github/workflows/generate-sponsor-image.yml +++ b/.github/workflows/generate-sponsor-image.yml @@ -29,7 +29,12 @@ jobs: with: commit-message: "chore: update sponsors.svg" add-paths: "website/static/img/sponsors.svg" - title: Update Sponsor Image - body: Generated new image + title: "chore: update sponsors.svg" + body: | + Auto-generated by the sponsor image workflow + + [skip ci] [skip actions] branch: update-sponsors + base: master delete-branch: true + draft: false diff --git a/.github/workflows/issue-triage-automation.yml b/.github/workflows/issue-triage-automation.yml new file mode 100644 index 000000000..4a827d527 --- /dev/null +++ b/.github/workflows/issue-triage-automation.yml @@ -0,0 +1,77 @@ +name: Issue Triage Automation + +on: + issues: + types: [opened, reopened, labeled, unlabeled] + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + steps: + # Request more info for unclear bug reports + - name: Request more info + uses: actions/github-script@v6 + if: | + contains(github.event.issue.labels.*.name, 'bug') && + !contains(github.event.issue.body, 'wails doctor') && + !contains(github.event.issue.body, 'reproduction') + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `👋 Thanks for reporting this issue! To help us investigate, could you please: + + 1. Add the output of \`wails doctor\` if not already included + 2. Provide clear steps to reproduce the issue + 3. If possible, create a minimal reproduction of the issue + + This will help us resolve your issue much faster. Thank you!` + }); + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['awaiting feedback'] + }); + + # Prioritize security issues + - name: Prioritize security issues + uses: actions/github-script@v6 + if: contains(github.event.issue.labels.*.name, 'security') + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['high-priority'] + }); + + # Tag version-specific issues for project boards + - name: Add to v2 project + uses: actions/github-script@v6 + if: | + contains(github.event.issue.labels.*.name, 'v2-only') && + !contains(github.event.issue.labels.*.name, 'v3-alpha') + with: + script: | + // Replace PROJECT_ID with your actual GitHub project ID + // This is a placeholder as the actual implementation would require + // GraphQL API calls to add to a project board + console.log('Would add to v2 project board'); + + # Tag version-specific issues for project boards + - name: Add to v3 project + uses: actions/github-script@v6 + if: contains(github.event.issue.labels.*.name, 'v3-alpha') + with: + script: | + // Replace PROJECT_ID with your actual GitHub project ID + // This is a placeholder as the actual implementation would require + // GraphQL API calls to add to a project board + console.log('Would add to v3 project board'); diff --git a/.github/workflows/nightly-release-v3.yml b/.github/workflows/nightly-release-v3.yml new file mode 100644 index 000000000..ae56ba7bc --- /dev/null +++ b/.github/workflows/nightly-release-v3.yml @@ -0,0 +1,210 @@ +name: Nightly Release v3-alpha + +on: + schedule: + - cron: '0 2 * * *' # 2 AM UTC daily + workflow_dispatch: + inputs: + force_release: + description: 'Force release even if no changes detected' + required: false + default: false + type: boolean + dry_run: + description: 'Run in dry-run mode (no actual release)' + required: false + default: true + type: boolean + +jobs: + nightly-release: + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: read + actions: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.24' + cache: true + cache-dependency-path: 'v3/go.sum' + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + # Configure git to use the token for authentication + git config --global url."https://x-access-token:${{ secrets.WAILS_REPO_TOKEN || github.token }}@github.com/".insteadOf "https://github.com/" + + - name: Check for existing release tag + id: check_tag + run: | + if git describe --tags --exact-match HEAD 2>/dev/null; then + echo "has_tag=true" >> $GITHUB_OUTPUT + echo "tag=$(git describe --tags --exact-match HEAD)" >> $GITHUB_OUTPUT + else + echo "has_tag=false" >> $GITHUB_OUTPUT + echo "tag=" >> $GITHUB_OUTPUT + fi + + - name: Check for unreleased changelog content + id: changelog_check + run: | + echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..." + + # Run the release script in check mode to see if there's content + cd v3/tasks/release + + # Use the release script itself to check for content + if go run release.go --check-only 2>/dev/null; then + echo "has_unreleased_content=true" >> $GITHUB_OUTPUT + echo "✅ Found unreleased changelog content" + else + echo "has_unreleased_content=false" >> $GITHUB_OUTPUT + echo "ℹ️ No unreleased changelog content found" + fi + + - name: Quick change detection and early exit + id: quick_check + run: | + echo "🔍 Quick check for changes to determine if we should continue..." + + # First check if we have unreleased changelog content + if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then + echo "✅ Found unreleased changelog content, proceeding with release" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + echo "reason=Found unreleased changelog content" >> $GITHUB_OUTPUT + exit 0 + fi + + # If no unreleased changelog content, check for git changes as fallback + echo "No unreleased changelog content found, checking for git changes..." + + # Check if current commit has a release tag + if git describe --tags --exact-match HEAD 2>/dev/null; then + CURRENT_TAG=$(git describe --tags --exact-match HEAD) + echo "Current commit has release tag: $CURRENT_TAG" + + # For tagged commits, check if there are changes since the tag + COMMIT_COUNT=$(git rev-list ${CURRENT_TAG}..HEAD --count) + if [ "$COMMIT_COUNT" -eq 0 ]; then + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "should_continue=false" >> $GITHUB_OUTPUT + echo "reason=No changes since existing tag $CURRENT_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + fi + else + # No current tag, check against latest release + LATEST_TAG=$(git tag --list "v3.0.0-alpha.*" | sort -V | tail -1) + if [ -z "$LATEST_TAG" ]; then + echo "No previous release found, proceeding with release" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + else + COMMIT_COUNT=$(git rev-list ${LATEST_TAG}..HEAD --count) + if [ "$COMMIT_COUNT" -gt 0 ]; then + echo "Found $COMMIT_COUNT commits since $LATEST_TAG" + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "should_continue=true" >> $GITHUB_OUTPUT + else + echo "has_changes=false" >> $GITHUB_OUTPUT + echo "should_continue=false" >> $GITHUB_OUTPUT + echo "reason=No changes since latest release $LATEST_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT + fi + fi + fi + + - name: Early exit - No changes detected + if: | + steps.quick_check.outputs.should_continue == 'false' && + github.event.inputs.force_release != 'true' + run: | + echo "🛑 EARLY EXIT: ${{ steps.quick_check.outputs.reason }}" + echo "" + echo "ℹ️ No changes detected since last release and force_release is not enabled." + echo " Workflow will exit early to save resources." + echo "" + echo " To force a release anyway, run this workflow with 'force_release=true'" + echo "" + echo "## 🛑 Early Exit Summary" >> $GITHUB_STEP_SUMMARY + echo "**Reason:** ${{ steps.quick_check.outputs.reason }}" >> $GITHUB_STEP_SUMMARY + echo "**Action:** Workflow exited early to save resources" >> $GITHUB_STEP_SUMMARY + echo "**Force Release:** Set 'force_release=true' to override this behavior" >> $GITHUB_STEP_SUMMARY + exit 0 + + - name: Continue with release process + if: | + steps.quick_check.outputs.should_continue == 'true' || + github.event.inputs.force_release == 'true' + run: | + echo "✅ Proceeding with release process..." + if [ "${{ github.event.inputs.force_release }}" == "true" ]; then + echo "🔨 FORCE RELEASE: Overriding change detection" + fi + + - name: Run release script + id: release + if: | + steps.quick_check.outputs.should_continue == 'true' || + github.event.inputs.force_release == 'true' + env: + WAILS_REPO_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + run: | + cd v3/tasks/release + ARGS=() + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + ARGS+=(--dry-run) + fi + go run release.go "${ARGS[@]}" + + - name: Summary + if: always() + run: | + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + echo "## 🧪 DRY RUN Release Summary" >> $GITHUB_STEP_SUMMARY + else + echo "## 🚀 Nightly Release Summary" >> $GITHUB_STEP_SUMMARY + fi + echo "================================" >> $GITHUB_STEP_SUMMARY + + if [ -n "${{ steps.release.outputs.release_version }}" ]; then + echo "- **Version:** ${{ steps.release.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag:** ${{ steps.release.outputs.release_tag }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status:** ${{ steps.release.outcome == 'success' && '✅ Success' || '⚠️ Failed' }}" >> $GITHUB_STEP_SUMMARY + echo "- **Mode:** ${{ steps.release.outputs.release_dry_run == 'true' && '🧪 Dry Run' || '🚀 Live release' }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ steps.release.outputs.release_url }}" ]; then + echo "- **Release URL:** ${{ steps.release.outputs.release_url }}" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Changelog" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then + echo "✅ Unreleased changelog processed and reset." >> $GITHUB_STEP_SUMMARY + else + echo "ℹ️ No unreleased changelog content detected." >> $GITHUB_STEP_SUMMARY + fi + else + echo "- Release script did not run (skipped or failed before execution)." >> $GITHUB_STEP_SUMMARY + fi + diff --git a/.github/workflows/pr.yml b/.github/workflows/pr-master.yml similarity index 66% rename from .github/workflows/pr.yml rename to .github/workflows/pr-master.yml index c70050276..1f9fe5622 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr-master.yml @@ -1,20 +1,23 @@ -name: PR Checks +name: PR Checks (master) on: pull_request: + branches: + - master pull_request_review: types: [submitted] - + branches: + - master jobs: check_docs: name: Check Docs - if: ${{github.repository == 'wailsapp/wails' && contains(github.head_ref,'feature/')}} + if: ${{github.repository == 'wailsapp/wails' && github.base_ref == 'master'}} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Verify Changed files - uses: tj-actions/verify-changed-files@v17 + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 id: verify-changed-files with: files: | @@ -26,47 +29,28 @@ jobs: run: | echo "::warning::Feature branch does not contain any changes to the website." -# lint_go: -# name: Run Go Linters -# runs-on: ubuntu-latest -# steps: -# - name: Checkout code -# uses: actions/checkout@v4 -# -# - name: Setup Go -# uses: actions/setup-go@v4 -# with: -# go-version: "1.21" -# -# - name: Update go modules -# working-directory: ./v2 -# run: go mod tidy -# -# - name: Run Linter -# uses: golangci/golangci-lint-action@v3 -# with: -# version: v1.54 -# working-directory: ./v2 -# args: --timeout=10m0s --config ./.golangci.yml - test_go: name: Run Go Tests runs-on: ${{ matrix.os }} - if: github.event.review.state == 'approved' + if: > + github.event.review.state == 'approved' && + github.repository == 'wailsapp/wails' && + github.base_ref == 'master' && + github.event.pull_request.head.ref != 'update-sponsors' strategy: matrix: os: [ubuntu-22.04, windows-latest, macos-latest, ubuntu-24.04] - go-version: ['1.21'] + go-version: ['1.23'] steps: - name: Checkout code uses: actions/checkout@v3 - - name: Install linux dependencies ( 22.04 ) + - name: Install linux dependencies (22.04) if: matrix.os == 'ubuntu-22.04' run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config - - name: Install linux dependencies ( 24.04 ) + - name: Install linux dependencies (24.04) if: matrix.os == 'ubuntu-24.04' run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 000000000..3ebd2f282 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,119 @@ +on: + push: + branches: ['v3-alpha'] + workflow_dispatch: + +concurrency: + group: publish-npm-v3 + cancel-in-progress: true + +jobs: + detect: + name: Detect committed changes + if: github.event_name != 'workflow_dispatch' + outputs: + changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: 'true' + + - name: Detect committed package.json changes + id: package-json-changes + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 + with: + files: | + v3/internal/runtime/desktop/@wailsio/runtime/package.json + v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json + + - name: Detect committed source changes + if: >- + steps.package-json-changes.outputs.any_modified != 'true' + id: source-changes + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 + with: + files: | + v3/internal/runtime/Taskfile.yaml + v3/internal/runtime/desktop/@wailsio/compiled/main.js + v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json + v3/internal/runtime/desktop/@wailsio/runtime/src/** + v3/pkg/events/events.txt + v3/tasks/events/** + + rebuild_and_publish: + name: Rebuild and publish + needs: [detect] + if: >- + !failure() && !cancelled() + && (github.event_name == 'workflow_dispatch' || needs.detect.outputs.changed == 'true') + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + pull-requests: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: 'v3-alpha' + token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + + - name: Configure git + run: | + git config --global user.email "github-actions@github.com" + git config --global user.name "GitHub Actions" + git config --global url."https://x-access-token:${{ secrets.WAILS_REPO_TOKEN || github.token }}@github.com/".insteadOf "https://github.com/" + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Use Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install dependencies + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm ci + npx --yes esbuild@latest --version + + - name: Clean build artifacts + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: npm run clean + + - name: Build bundled runtime + working-directory: v3 + run: task runtime:build + + - name: Test+Build npm package + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + npm test + npm run build + + - name: Bump version + id: bump-version + working-directory: v3/internal/runtime/desktop/@wailsio/runtime + run: | + echo "version=$(npm --no-git-tag-version --force version prerelease)" >> "$GITHUB_OUTPUT" + + - name: Commit changes + run: | + git add . + git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.bump-version.outputs.version }}" + git push + + - name: Publish npm package + uses: JS-DevTools/npm-publish@v3 + with: + package: v3/internal/runtime/desktop/@wailsio/runtime + access: public + token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 453e4cb85..a59818660 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -5,6 +5,7 @@ on: branches: - main - master + - v3-alpha paths: - .github/workflows/semgrep.yml schedule: @@ -14,7 +15,7 @@ name: Semgrep jobs: semgrep: name: semgrep/ci - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} container: diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml new file mode 100644 index 000000000..c4ffd25fe --- /dev/null +++ b/.github/workflows/stale-issues.yml @@ -0,0 +1,57 @@ +name: Mark and Close Stale Issues + +on: + schedule: + - cron: '0 1 * * *' # Run at 1 AM UTC every day + workflow_dispatch: # Allow manual triggering + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v9 + with: + # General settings + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 45 + days-before-close: 10 + stale-issue-label: 'stale' + operations-per-run: 250 # Increased from 50 to 250 + + # Issue specific settings + stale-issue-message: | + This issue has been automatically marked as stale because it has not had recent activity. + It will be closed if no further activity occurs within the next 10 days. + + If this issue is still relevant, please add a comment to keep it open. + Thank you for your contributions. + + close-issue-message: | + This issue has been automatically closed due to lack of activity. + Please feel free to reopen it if it's still relevant. + + # PR specific settings - We will not mark PRs as stale + days-before-pr-stale: -1 # Disable PR staling + days-before-pr-close: -1 # Disable PR closing + + # Exemptions + exempt-issue-labels: 'pinned,security,onhold,inprogress,Selected For Development,bug,enhancement,v3-alpha,high-priority' + exempt-all-issue-milestones: true + exempt-all-issue-assignees: true + + # Protection for existing issues + exempt-issue-created-before: '2024-01-01T00:00:00Z' + start-date: '2025-06-01T00:00:00Z' # Don't start checking until June 1, 2025 + + # Only process issues, not PRs + only-labels: '' + any-of-labels: '' + remove-stale-when-updated: true + + # Debug options + debug-only: false # Set to true to test without actually marking issues + ascending: true # Process older issues first diff --git a/.github/workflows/test-nightly-releases.yml b/.github/workflows/test-nightly-releases.yml new file mode 100644 index 000000000..63df09935 --- /dev/null +++ b/.github/workflows/test-nightly-releases.yml @@ -0,0 +1,216 @@ +name: Test Nightly Releases (Dry Run) + +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no actual releases)' + required: false + default: true + type: boolean + test_branch: + description: 'Branch to test against' + required: false + default: 'master' + type: string + +env: + GO_VERSION: '1.24' + +jobs: + test-permissions: + name: Test Release Permissions + runs-on: ubuntu-latest + outputs: + authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized + id: check + run: | + # Test authorization logic + AUTHORIZED_USERS="leaanthony" + + if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then + echo "✅ User ${{ github.actor }} is authorized" + echo "authorized=true" >> $GITHUB_OUTPUT + else + echo "❌ User ${{ github.actor }} is not authorized" + echo "authorized=false" >> $GITHUB_OUTPUT + fi + + test-changelog-extraction: + name: Test Changelog Extraction + runs-on: ubuntu-latest + needs: test-permissions + if: needs.test-permissions.outputs.authorized == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.test_branch }} + fetch-depth: 0 + + - name: Test v2 changelog extraction + run: | + echo "🧪 Testing v2 changelog extraction..." + CHANGELOG_FILE="website/src/pages/changelog.mdx" + + if [ ! -f "$CHANGELOG_FILE" ]; then + echo "❌ v2 changelog file not found" + exit 1 + fi + + # Extract unreleased section + awk ' + /^## \[Unreleased\]/ { found=1; next } + found && /^## / { exit } + found && !/^$/ { print } + ' $CHANGELOG_FILE > v2_release_notes.md + + echo "📝 v2 changelog content (first 10 lines):" + head -10 v2_release_notes.md || echo "No content found" + echo "Total lines: $(wc -l < v2_release_notes.md)" + + - name: Test v3 changelog extraction (if accessible) + run: | + echo "🧪 Testing v3 changelog extraction..." + + if git show v3-alpha:docs/src/content/docs/changelog.mdx > /dev/null 2>&1; then + echo "✅ v3 changelog accessible" + + git show v3-alpha:docs/src/content/docs/changelog.mdx | awk ' + /^## \[Unreleased\]/ { found=1; next } + found && /^## / { exit } + found && !/^$/ { print } + ' > v3_release_notes.md + + echo "📝 v3 changelog content (first 10 lines):" + head -10 v3_release_notes.md || echo "No content found" + echo "Total lines: $(wc -l < v3_release_notes.md)" + else + echo "⚠️ v3 changelog not accessible from current context" + fi + + test-version-detection: + name: Test Version Detection + runs-on: ubuntu-latest + needs: test-permissions + if: needs.test-permissions.outputs.authorized == 'true' + outputs: + v2_current_version: ${{ steps.versions.outputs.v2_current }} + v2_next_patch: ${{ steps.versions.outputs.v2_next_patch }} + v2_next_minor: ${{ steps.versions.outputs.v2_next_minor }} + v2_next_major: ${{ steps.versions.outputs.v2_next_major }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Test version detection logic + id: versions + run: | + echo "🧪 Testing version detection..." + + # Test v2 version parsing + if [ -f "v2/cmd/wails/internal/version.txt" ]; then + CURRENT_V2=$(cat v2/cmd/wails/internal/version.txt | sed 's/^v//') + echo "Current v2 version: v$CURRENT_V2" + echo "v2_current=v$CURRENT_V2" >> $GITHUB_OUTPUT + + # Parse and increment + IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_V2" + MAJOR=${VERSION_PARTS[0]} + MINOR=${VERSION_PARTS[1]} + PATCH=${VERSION_PARTS[2]} + + PATCH_VERSION="v$MAJOR.$MINOR.$((PATCH + 1))" + MINOR_VERSION="v$MAJOR.$((MINOR + 1)).0" + MAJOR_VERSION="v$((MAJOR + 1)).0.0" + + echo "v2_next_patch=$PATCH_VERSION" >> $GITHUB_OUTPUT + echo "v2_next_minor=$MINOR_VERSION" >> $GITHUB_OUTPUT + echo "v2_next_major=$MAJOR_VERSION" >> $GITHUB_OUTPUT + + echo "✅ Patch: v$CURRENT_V2 → $PATCH_VERSION" + echo "✅ Minor: v$CURRENT_V2 → $MINOR_VERSION" + echo "✅ Major: v$CURRENT_V2 → $MAJOR_VERSION" + else + echo "❌ v2 version file not found" + fi + + test-commit-analysis: + name: Test Commit Analysis + runs-on: ubuntu-latest + needs: test-permissions + if: needs.test-permissions.outputs.authorized == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Test commit analysis + run: | + echo "🧪 Testing commit analysis..." + + # Get recent commits for testing + echo "Recent commits:" + git log --oneline -10 + + # Test conventional commit detection + RECENT_COMMITS=$(git log --oneline --since="7 days ago") + echo "Commits from last 7 days:" + echo "$RECENT_COMMITS" + + # Analyze for release type + RELEASE_TYPE="patch" + if echo "$RECENT_COMMITS" | grep -q "feat!\|fix!\|BREAKING CHANGE:"; then + RELEASE_TYPE="major" + elif echo "$RECENT_COMMITS" | grep -q "feat\|BREAKING CHANGE"; then + RELEASE_TYPE="minor" + fi + + echo "✅ Detected release type: $RELEASE_TYPE" + + test-summary: + name: Test Summary + runs-on: ubuntu-latest + needs: [test-permissions, test-changelog-extraction, test-version-detection, test-commit-analysis] + if: always() + steps: + - name: Print test results + run: | + echo "# 🧪 Nightly Release Workflow Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.test-permissions.result }}" == "success" ]; then + echo "✅ **Permissions Test**: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Permissions Test**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-changelog-extraction.result }}" == "success" ]; then + echo "✅ **Changelog Extraction**: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Changelog Extraction**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-version-detection.result }}" == "success" ]; then + echo "✅ **Version Detection**: Passed" >> $GITHUB_STEP_SUMMARY + echo " - Current v2: ${{ needs.test-version-detection.outputs.v2_current_version }}" >> $GITHUB_STEP_SUMMARY + echo " - Next patch: ${{ needs.test-version-detection.outputs.v2_next_patch }}" >> $GITHUB_STEP_SUMMARY + echo " - Next minor: ${{ needs.test-version-detection.outputs.v2_next_minor }}" >> $GITHUB_STEP_SUMMARY + echo " - Next major: ${{ needs.test-version-detection.outputs.v2_next_major }}" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Version Detection**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.test-commit-analysis.result }}" == "success" ]; then + echo "✅ **Commit Analysis**: Passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Commit Analysis**: Failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Note**: This was a dry-run test. No actual releases were created." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/test-simple.yml b/.github/workflows/test-simple.yml new file mode 100644 index 000000000..8c4c88295 --- /dev/null +++ b/.github/workflows/test-simple.yml @@ -0,0 +1,11 @@ +name: Test Simple + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Test + run: echo "Hello World" \ No newline at end of file diff --git a/.github/workflows/unreleased-changelog-trigger.yml b/.github/workflows/unreleased-changelog-trigger.yml new file mode 100644 index 000000000..381023052 --- /dev/null +++ b/.github/workflows/unreleased-changelog-trigger.yml @@ -0,0 +1,128 @@ +name: Auto Release on Changelog Update + +on: + push: + branches: + - v3-alpha + paths: + - 'v3/UNRELEASED_CHANGELOG.md' + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no actual release)' + required: false + default: false + type: boolean + +jobs: + check-permissions: + name: Check Release Permissions + runs-on: ubuntu-latest + outputs: + authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized for releases + id: check + run: | + # Only allow specific users to trigger releases + AUTHORIZED_USERS="leaanthony" + + if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then + echo "✅ User ${{ github.actor }} is authorized for releases" + echo "authorized=true" >> $GITHUB_OUTPUT + else + echo "❌ User ${{ github.actor }} is not authorized for releases" + echo "authorized=false" >> $GITHUB_OUTPUT + fi + + trigger-release: + name: Trigger v3-alpha Release + permissions: + contents: read + runs-on: ubuntu-latest + needs: check-permissions + if: needs.check-permissions.outputs.authorized == 'true' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: v3-alpha + fetch-depth: 0 + token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + + - name: Check for unreleased changelog content + id: changelog_check + run: | + echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..." + + cd v3 + # Check if UNRELEASED_CHANGELOG.md has actual content beyond the template + if [ -f "UNRELEASED_CHANGELOG.md" ]; then + # Use a simple check for actual content (bullet points starting with -) + CONTENT_LINES=$(grep -E "^\s*-\s+[^[:space:]]" UNRELEASED_CHANGELOG.md | wc -l) + if [ "$CONTENT_LINES" -gt 0 ]; then + echo "✅ Found $CONTENT_LINES content lines in UNRELEASED_CHANGELOG.md" + echo "has_content=true" >> $GITHUB_OUTPUT + else + echo "ℹ️ No actual content found in UNRELEASED_CHANGELOG.md" + echo "has_content=false" >> $GITHUB_OUTPUT + fi + else + echo "❌ UNRELEASED_CHANGELOG.md not found" + echo "has_content=false" >> $GITHUB_OUTPUT + fi + + - name: Trigger nightly release workflow + if: steps.changelog_check.outputs.has_content == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.WAILS_REPO_TOKEN || github.token }} + script: | + const response = await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'nightly-release-v3.yml', + ref: 'v3-alpha', + inputs: { + force_release: 'true', + dry_run: '${{ github.event.inputs.dry_run || "false" }}' + } + }); + + console.log('🚀 Successfully triggered nightly release workflow'); + console.log(`Workflow dispatch response status: ${response.status}`); + + // Create a summary + core.summary + .addHeading('🚀 Auto Release Triggered') + .addRaw('The v3-alpha release workflow has been automatically triggered due to changes in UNRELEASED_CHANGELOG.md') + .addTable([ + [{data: 'Trigger', header: true}, {data: 'Value', header: true}], + ['Repository', context.repo.repo], + ['Branch', 'v3-alpha'], + ['Actor', context.actor], + ['Dry Run', '${{ github.event.inputs.dry_run || "false" }}'], + ['Force Release', 'true'] + ]) + .addRaw('\n---\n*This release was automatically triggered by the unreleased-changelog-trigger workflow*') + .write(); + + - name: No content found + if: steps.changelog_check.outputs.has_content == 'false' + run: | + echo "ℹ️ No content found in UNRELEASED_CHANGELOG.md, skipping release trigger" + echo "## ℹ️ No Release Triggered" >> $GITHUB_STEP_SUMMARY + echo "**Reason:** UNRELEASED_CHANGELOG.md does not contain actual changelog content" >> $GITHUB_STEP_SUMMARY + echo "**Action:** No release workflow was triggered" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To trigger a release, add actual changelog entries to the UNRELEASED_CHANGELOG.md file." >> $GITHUB_STEP_SUMMARY + + - name: Unauthorized user + if: needs.check-permissions.outputs.authorized == 'false' + run: | + echo "❌ User ${{ github.actor }} is not authorized to trigger releases" + echo "## ❌ Unauthorized Release Attempt" >> $GITHUB_STEP_SUMMARY + echo "**User:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "**Action:** Release trigger was blocked due to insufficient permissions" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Only authorized users can trigger automatic releases via changelog updates." >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.github/workflows/upload-source-documents.yml b/.github/workflows/upload-source-documents.yml index df15246fc..69d6c3e48 100644 --- a/.github/workflows/upload-source-documents.yml +++ b/.github/workflows/upload-source-documents.yml @@ -15,7 +15,7 @@ jobs: - name: Verify Changed files id: changed-files - uses: tj-actions/changed-files@v41 + uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1 with: files: | website/**/*.mdx diff --git a/.github/workflows/v3-docs.yml b/.github/workflows/v3-docs.yml new file mode 100644 index 000000000..7552d43ab --- /dev/null +++ b/.github/workflows/v3-docs.yml @@ -0,0 +1,42 @@ +name: Deploy to GitHub Pages + +on: + # Trigger the workflow every time you push to the `main` branch + # Using a different branch name? Replace `main` with your branch’s name + push: + branches: [v3-alpha] + # Allows you to run this workflow manually from the Actions tab on GitHub. + workflow_dispatch: + +# Allow this job to clone the repo and create a page deployment +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + steps: + - name: Checkout your repository using git + uses: actions/checkout@v4 + - name: Install, build, and upload your site output + uses: withastro/action@v2 + with: + path: docs + node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 18. (optional) + # package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 +env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e7888b44a..c4543ceab 100644 --- a/.gitignore +++ b/.gitignore @@ -37,5 +37,57 @@ v2/cmd/wails/internal/commands/initialise/templates/testtemplates/ /websitev3/site/ /v3/examples/plugins/bin/testapp +# V3 Example binaries - ignore executables that match directory names +/v3/examples/badge-custom/badge-custom +/v3/examples/badge/badge +/v3/examples/binding/binding +/v3/examples/cancel-async/cancel-async +/v3/examples/cancel-chaining/cancel-chaining +/v3/examples/clipboard/clipboard +/v3/examples/contextmenus/contextmenus +/v3/examples/dev/dev +/v3/examples/dialogs-basic/dialogs-basic +/v3/examples/dialogs/dialogs +/v3/examples/drag-n-drop/drag-n-drop +/v3/examples/environment/environment +/v3/examples/events-bug/events-bug +/v3/examples/events/events +/v3/examples/file-association/file-association +/v3/examples/frameless/frameless +/v3/examples/gin-example/gin-example +/v3/examples/gin-routing/gin-routing +/v3/examples/gin-service/gin-service +/v3/examples/hide-window/hide-window +/v3/examples/html-dnd-api/html-dnd-api +/v3/examples/ignore-mouse/ignore-mouse +/v3/examples/keybindings/keybindings +/v3/examples/menu/menu +/v3/examples/notifications/notifications +/v3/examples/panic-handling/panic-handling +/v3/examples/plain/plain +/v3/examples/raw-message/raw-message +/v3/examples/screen/screen +/v3/examples/services/services +/v3/examples/show-macos-toolbar/show-macos-toolbar +/v3/examples/single-instance/single-instance +/v3/examples/systray-basic/systray-basic +/v3/examples/systray-custom/systray-custom +/v3/examples/systray-menu/systray-menu +/v3/examples/video/video +/v3/examples/window-api/window-api +/v3/examples/window-call/window-call +/v3/examples/window-menu/window-menu +/v3/examples/window/window +/v3/examples/wml/wml + +# Common binary names in examples +/v3/examples/*/main +/v3/examples/*/app +/v3/examples/*/changeme +/v3/examples/*/testbuild-* + # Temporary called mkdocs, should be renamed to more standard -website or similar -/mkdocs-website/site +/docs/site +.aider* +.cache +.local diff --git a/.prettierrc.yml b/.prettierrc.yml index 685d8b6e7..1e2d2dada 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -1,6 +1,7 @@ overrides: - files: - "**/*.md" + - "**/*.mdx" options: printWidth: 80 proseWrap: always diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..365060493 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +golang 1.25.1 +nodejs 24.8.0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..45843f4ed --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Repository Guidelines + +## Project Structure & Module Organization +The repository hosts the current Wails tooling in `v3/`, the maintained v2 runtime in `v2/`, and shared assets under `assets/` and `pkg/`. CLI entrypoints live in `v3/cmd/wails3`, while reusable runtime code is grouped under `v3/internal/*` (platform integrations, packaging, templates) and `v3/pkg/*` (application, services, UI helpers). Example applications used for testing and documentation reside in `v3/examples`, with Docker build scripts and harnesses in `v3/test` and targeted regression suites in `v3/tests`. Documentation and site content are stored in `docs/` and `website/`; keep tutorial updates alongside related code changes. + +## Build, Test, and Development Commands +Run `task v3:install` to compile and install the v3 CLI locally, and `task v3:precommit` before submitting changes to execute `go test ./...` and apply repository formatters. Use `task v3:test:examples` for a full platform-agnostic build of every example, or scope work with `task v3:test:example:windows DIR=badge` (replace `badge` as needed). Markdown formatting is standardized with `task format:md`, and changelog automation is driven via `task contributors:update` when contributor metadata changes. + +## Coding Style & Naming Conventions +Go code must remain `gofmt`-clean with idiomatic package names (lowercase, no underscores) and exported API names following Go's PascalCase. Match existing directory naming by grouping platform-specific code under clearly named packages (e.g., `mac`, `w32`) and keep generator templates in `internal/templates`. Frontend or documentation updates should respect Prettier defaults; run `npx prettier --write "**/*.md"` or the Taskfile wrapper to stay consistent. + +## Testing Guidelines +Unit and integration tests accompany Go sources inside the nearest package; add new suites under `v3/tests` for regression coverage. Cross-platform build validation relies on the Taskfile matrix—`task test:examples:all` exercises macOS, Windows, and Linux Docker pipelines, while `task test:example:linux:docker DIR=` verifies a single sample with architecture auto-detection. Prefer deterministic tests and clean up generated binaries (`testbuild-*`) after manual runs. + +## Commit & Pull Request Guidelines +Adopt conventional commits recognized by the release tooling (`feat:`, `fix:`, `chore:`, with `!` or `BREAKING CHANGE:` when necessary) so nightly pipelines classify versions correctly. Reference issues or discussion threads in the body, and update `v3/UNRELEASED_CHANGELOG.md` with concise entries under the appropriate heading. Pull requests should summarize scope, list validation commands executed (e.g., `task v3:precommit`), and include platform-specific notes or screenshots for UI-affecting work. Ensure CI passes and that new docs or samples link from the relevant navigation files in `docs/src/content` before requesting review. diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md new file mode 100644 index 000000000..1f3de79f5 --- /dev/null +++ b/DEVELOPER_GUIDE.md @@ -0,0 +1,317 @@ +# Wails v3 Developer Guide (Top-Level Overview) + +This guide provides a technical, developer-focused overview of the Wails v3 codebase located under the `v3` directory. It is the start of a comprehensive series that will later dive into each package and file in detail. For now, it establishes orientation, architecture, responsibilities, and workflows so contributors have a reliable high-level map before we document every component thoroughly. + +Scope: Only the `v3` directory is in scope for this guide. + +Last updated: 2025-09-24 + + +## Goals of Wails v3 + +Wails lets you build desktop applications using Go for the backend and web technologies (HTML/CSS/JS) for the frontend, packaging them into a native desktop experience across platforms. Version 3 focuses on modular internal packages, improved runtime and tooling, and clearer boundaries between build-time and run-time responsibilities. + + +## High-Level Architecture + +- Command-line tooling (CLI): developer-facing entry points for building, generating, packaging, and inspecting Wails apps. +- Internal libraries: build orchestration, runtime integration, OS abstractions, packaging, capabilities, templates, and utilities. These are mostly under `v3/internal` and not intended as public API. +- Public Go packages: simple APIs for applications to use (e.g., `v3/pkg/application`, `v3/pkg/ui`, `v3/pkg/services`, etc.). +- Runtime (desktop): the bridge between the Go app and the web frontend, with bindings, window control, events, and platform integrations. +- Examples and tests: reference implementations and validation. + + +## Directory Map (top-level under v3) + +- `cmd/` — CLI entrypoints. + - `wails3/` — The main Wails v3 CLI. Includes `main.go`, usage docs, and a prebuilt binary (when present in repo artifacts). + +- `examples/` — Working sample apps demonstrating various capabilities. + - Examples include, among others: drag-and-drop, raw message handling, etc. These show best practices and can be used to validate changes. + +- `internal/` — Internal (non-public) packages used by the CLI and by build/runtime components. + - `assetserver/` — Static file serving logic for development or build steps. + - `buildinfo/` — Build metadata and version stamping. + - `capabilities/` — Capabilities gating or feature flags across subsystems. + - `changelog/` — Utilities for changelog parsing/validation. + - `commands/` — Implementation of CLI subcommands and orchestration logic. + - `dbus/` — D-Bus integration for Linux desktop features. + - `debug/` — Debug utilities or dev-mode helpers. + - `doctor/` — Environment diagnostics (e.g., checking toolchains). + - `fileexplorer/` — File chooser/dialog logic beyond platform primitives. + - `flags/` — Centralized command-line flag definitions and parsing helpers. + - `generator/` — Code and project generators (scaffolding, bindings, etc.). + - `github/` — GitHub integration (release automation or metadata helpers). + - `go-common-file-dialog/` — Common file dialog wrappers (likely cross-platform helpers). + - `hash/` — Hashing utilities for caching, integrity, or content addressing. + - `operatingsystem/` — OS detection and OS-specific helpers. + - `packager/` — Packaging/build pipelines to produce platform-specific bundles. + - `runtime/` — Runtime glue for the desktop environment. + - `desktop/` — Desktop runtime resources including the browser window bridge and TS/JS runtime. + - `@wailsio/runtime/` — TypeScript source for the frontend runtime bridge (window and app bindings, event bus, etc.). + - `s/` — Small internal helpers or shared primitives. + - `service/` — Service abstractions and service lifecycle helpers used internally. + - `signal/` — Signal handling (OS process signals). + - `templates/` — Project and code templates (used by the generator and packager). + - `term/`, `term2/` — Terminal rendering, progress, and rich text helpers. + - `version/` — Versioning helpers shared across commands. + +- `pkg/` — Public packages intended for application developers. + - `application/` — Main application abstraction (app lifecycle, window management hooks, startup/shutdown). + - `events/` — Event bus/interfaces for app<->frontend communication. + - `icons/` — Icon helpers/types for application and window icons. + - `mac/` — macOS-specific helpers. + - `mcp/` — Platform or protocol utilities (exact scope to be detailed in the deep dive). + - `services/` — Public service interfaces and service registration. + - `ui/` — UI helpers and types exposed to apps. + - `w32/` — Windows-specific helpers and bindings. + +- `scripts/` — Maintenance or developer scripts used in CI or local workflows. + - `validate-changelog.go` — Validates changelog format and rules. + +- `tasks/` — Task-based workflow helpers (used by maintainers and CI). + - `cleanup/`, `contribs/`, `events/`, `fix-bindings/`, `release/`, `sed/` — Per-task utilities and scripts. + +- `test/`, `tests/`, `tooltest/` — Test suites, fixtures, and tools used for verifying functionality. + +- `.task/`, `wep/` — Project tooling/configuration directories (to be detailed later as needed). + + +## Build and Development Overview + +- CLI usage: The `v3/cmd/wails3` command is the primary interface. Typical workflows include: + - Project creation via templates (generator). + - Development server and live reload for the frontend (assetserver, runtime dev helpers). + - Building a production bundle and packaging for the target OS (packager). +- Internal packages are not part of the public API contract and may change. Public Go APIs under `v3/pkg/` are maintained for application developers. +- The desktop runtime bridges Go and the Web frontend via a JS/TS runtime (`internal/runtime/desktop/@wailsio/runtime`). This provides window controls, eventing, and invocation bridges to Go. + + +## Key Data Flows (Conceptual) + +1. Developer runs a CLI command (wails3) → CLI delegates to `internal/commands` and uses helpers under `internal/*` for build, generate, package, etc. +2. The application code (using `v3/pkg/*`) defines the Go-side application, services, and event handlers. +3. At runtime, the desktop bridge injects a JS runtime into the web app so frontend can call Go methods and subscribe to events. The Go-side emits events to the frontend via the runtime channel. +4. Packaging uses `internal/packager` to produce OS-native bundles, using OS-specific helpers from `pkg/mac`, `pkg/w32`, etc., as needed. + + +## Contributing Workflow (High-Level) + +- Make small, focused changes. For cross-platform features, ensure Linux, macOS, and Windows implications are considered. +- Run example apps under `v3/examples` to validate changes. +- Use scripts under `v3/scripts` and tasks under `v3/tasks` where applicable (e.g., changelog validation or release workflows). +- When modifying the runtime bridge, ensure TypeScript builds for `@wailsio/runtime` are up-to-date and integration-tested against example apps. + + +## Next Steps in This Guide + +This is the top-level overview. In subsequent iterations, we will deep-dive into every package and file in the `v3` directory with: +- Detailed per-package responsibilities and structure. +- File-by-file documentation including key types, functions, and data flow. +- Build, test, and troubleshooting instructions for each component. + +If you need a specific package documented next, please indicate which one, and we’ll start the in-depth coverage there. + + +# Deep Dive: pkg/application + +Scope: This section documents every file under v3/pkg/application and explains the end-to-end data flows for how an application is initialised and then run. It complements the top-level overview above. + +Last reviewed: 2025-09-24 + + +Overview of the application package +- Purpose: Provides the public API and core runtime glue for building and running a Wails desktop application. It owns the App type and orchestrates windows, menus, dialogs, system trays, eventing, and platform-specific main loops. +- Key types: + - App: The core application object exposed to developers. + - Window/WebviewWindow: Represents a browser-hosted UI Window (WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux). + - Managers: Subsystems that manage windows, menus, dialogs, events, clipboard, screens, environment, system trays, etc. + - Options: Configuration used to create the App and its initial windows and services. + - EventProcessor and event contexts: Abstractions for custom events and application/window event flows. + + +Lifecycle and data flow (initialisation -> run -> shutdown) +1) Construction via New(options) + - File: application.go (New) + - Actions: + - Merge defaults (mergeApplicationDefaults) and normalize Options (application_options.go). + - Construct the App: allocate fields, set logger, prepare asset serving (middleware, asset handlers), store any initial services from options. + - Prepare developer tooling (dev vs production variants in application_dev.go/application_production.go). In dev, static server may proxy to dev server; in production, use bundled assets. + - Defer platform-specific initialization; platform-specific App (platformApp) is created later in Run() via newPlatformApp(...) implemented per-OS. + +2) App.init() + - File: application.go (init method on App) + - Actions: + - Create root context (a.ctx) and cancellation function. + - Instantiate and wire all managers: + - newWindowManager, newContextMenuManager, newKeyBindingManager, newBrowserManager, newEnvironmentManager, newDialogManager, newEventManager, newMenuManager, newScreenManager, newClipboardManager, newSystemTrayManager. + - Initialize internal maps: windows, systemTrays, contextMenus, keyBindings, listener registries. + +3) Pre-run hooks and service startup + - File: application.go (Run -> preRun -> service startup) + - Actions: + - preRun() executes any post-creation hooks (e.g., build/runtime settling, menu prep, assetserver finalization). If this fails, Run aborts. + - Services provided in Options.Services are started in order (startupService). On failure, an error is returned and started services are tracked for orderly shutdown. + +4) Platform App creation + - File: application.go (Run) + - Calls newPlatformApp(a) which selects implementation based on GOOS: + - application_windows.go -> windowsApp + - application_darwin.go -> darwinApp (plus associated Objective-C glue .m/.h for Cocoa/WKWebView) + - application_linux.go -> gtkApp (with CGO or purego variants) + - The platformApp implements methods like run(), init(), setApplicationMenu(), setIcon(), show/hide, main-thread dispatch, window/tray registration, etc. + +5) Event pump setup (concurrency and channels) + - File: application.go (Run) + - Starts goroutines to drain and dispatch internal channels: + - applicationEvents -> a.Event.handleApplicationEvent + - windowEvents -> a.handleWindowEvent + - webviewRequests -> a.handleWebViewRequest + - windowMessageBuffer -> a.handleWindowMessage + - windowKeyEvents -> a.handleWindowKeyEvent + - windowDragAndDropBuffer -> a.handleDragAndDropMessage + - menuItemClicked -> a.Menu.handleMenuItemClicked + - These goroutines run for the lifetime of the app and route messages from the platform layer and WebView bridges into App managers and windows. + +6) Final run + - File: application.go (Run) + - Marks a.running = true, drains any a.pendingRun tasks scheduled before the run loop started, sets the application menu (darwin) and icon (if provided), and then calls a.impl.run() which enters the platform event loop (message pump on Windows, CFRunLoop on macOS, GTK main loop on Linux). + +7) Shutdown + - File: application.go (cleanup, shutdownServices, Quit) + - Cancel root context, tear down windows/system trays/menus, stop services in reverse order, and exit the platform loop. Fatal errors use handleFatalError to log and os.Exit(1). + + +File-by-file guide (v3/pkg/application) +Note: Brief purpose for each file and how it participates in init/run and data flows. + +Core App and options +- application.go: Defines App and its lifecycle. Key functions/methods: New, init (method), Run, cleanup, Quit, event and message handlers, Hide/Show, SetIcon, runOrDeferToAppRun. Creates managers and coordinates inter-manager communication via channels and callbacks. +- application_options.go: Defines Options for configuring the App: window defaults, asset options, logger, middlewares, platform-specific options (MacOptions, WindowsOptions, LinuxOptions), and asset server helpers. Contains middleware chaining and helpers for serving assets from fs.FS. +- services.go: Declares Service interface and service lifecycle integration with App (startupService, shutdownServices) and registration via App.RegisterService. +- environment.go / environment_manager.go: Detects and exposes environment info to the app (dev/prod, capabilities, platform variables), often referenced when preparing runtime flags and window defaults. +- errors.go: Error types, including FatalError used by handleFatalError in App. +- panic_handler.go: Centralized panic recovery where goroutines defer handlePanic() to avoid crashing the app silently. +- mainthread*.go: Utilities to ensure code runs on the GUI main thread when required by the OS toolkit; used by platformApp implementations and App.dispatchOnMainThread. +- messageprocessor*.go: Message router between WebView frontend and Go backend; different files focus on different domains (application, window, dialogs, clipboard, context menus, screens, system). They parse inbound messages (e.g., from JS runtime) and call into managers/App methods. +- bindings.go: Handles Go<->JS binding registration and call routing, including returning values/errors to the frontend via WebView. + +Platform-specific app implementations +- application_windows.go: windowsApp implements platformApp for Windows. Responsibilities: + - init(): OS integration (DPI awareness, COM init as needed via WebView2 loader), building application menu, creating hidden “application” window for message routing if needed. + - run(): Enters the Windows message loop, dispatches messages to wndProc, and integrates with WebView2 (go-webview2). + - wndProc(): Translates native window messages into Wails events: focus, resize, menu select, accelerator keys, system tray clicks, drag/drop, etc., forwarding into channels consumed by App.Run goroutines. + - setApplicationMenu(), setIcon(), show/hide, registration of windows and system trays. + - logPlatformInfo() and platformEnvironment() provide environment metadata used during startup. +- application_darwin.go and related Objective-C files (.h/.m): darwinApp uses Cocoa and WKWebView. Objective-C delegate files integrate with NSApplication, NSWindow, menus, and app activation policy; bridge callbacks enqueue events into Go channels. +- application_linux.go plus linux_cgo.go/linux_purego.go: GTK-based implementation; CGO variant integrates with GTK main loop and dialogs; purego variant offers a fallback depending on build tags. +- application_dev.go / application_production.go: Build-tagged files that adjust behavior for development vs production (e.g., logger defaults, asset server wiring, runtime flags). logger_dev*.go/logger_prod.go customize logging sinks by target OS and mode. + +Windows/macOS/Linux specific helpers +- keys*.go: Keyboard key codes and modifier mappings per OS; used to parse accelerators and handle key bindings. +- dialogs*.go: Platform-specific dialog implementations (message box, file open/save) backing the cross-platform dialogs.go facade. +- clipboard*.go and clipboard_manager.go: Clipboard APIs per OS. +- screen_*.go and screenmanager.go: Screen enumeration, DPI/scale factors, active screen selection; used when positioning windows. +- systemtray_*.go and system_tray_manager.go: System tray icon/menu management; events funneled through channels to App and Menu managers. + +Windowing: webview window and options +- webview_window.go: The core window abstraction backed by a webviewWindowImpl (implemented per-OS). Responsibilities: + - Window creation via NewWindow(options) after App is created. + - Eventing: WindowEvent and listener registration (OnWindowEvent, RegisterHook), mapping of events to callbacks. + - Lifecycle methods: Show/Hide, Run (window-level), Close/Destroy, Focus, Resize/Move, Fullscreen/Maximise/Minimise, Zoom, ContentProtection, Frameless toggling, DevTools (per OS), ExecJS, SetHTML/URL, printing. + - Input handling: processKeyBinding and HandleKeyEvent for accelerators; integrates with menu accelerator mappings. + - Messaging bridge: HandleMessage (routes call responses and RPC from JS), DialogResponse/CallResponse, drag-and-drop (HandleDragAndDropMessage), context menus. + - Internal queues: dispatchWindowEvent, isDestroyed checks, destroyed state management. +- webview_window_options.go: Defines WebviewWindowOptions, styling and behavior (title, size, min/max constraints, position, background color, frameless, URL/HTML/Assets, debug flags, dev tools, zoom policy, theme, user agent, drag regions, etc.). Also includes GPU policy and per-OS defaults. Options feed directly into platform window creation. +- webview_window_* per-OS files: Implement webviewWindowImpl for each platform. They translate cross-platform window API to native toolkit calls and send back events via channels (e.g., windowEvents, windowMessageBuffer, windowKeyEvents). +- window.go and window_manager.go: The common Window interface and its manager. The manager tracks windows by ID, exposes Find/All/Focused, and routes broadcast events. + +Menus, roles, and accelerators +- menu.go/menu_darwin.go/menu_linux.go/menu_windows.go: Cross-platform Menu representation with platform renderers. Supports app menu (macOS), window menus, and menu bar visibility. +- menuitem.go and family: MenuItem definition, roles, selectors (macOS), per-OS renderers. menuitem_roles.go defines standard roles (Copy, Paste, Quit, etc.). +- roles*.go: Dev vs production role sets. +- popupmenu_windows.go and context_menu_manager.go: Context menu helpers and manager lifecycle. +- key_binding_manager.go: Manages registration of accelerators (e.g., "CmdOrCtrl+Shift+P") and dispatches to window/menu handlers. + +Dialogs +- dialogs.go: Cross-platform API for dialogs. Provides MessageDialog, OpenFileDialog, SaveFileDialog with builder-style options; delegates to per-OS implementations via messageDialogImpl/openFileDialogImpl/saveFileDialogImpl. +- dialogs_* per-OS: Bridge to native dialog toolkits (NSAlert/NSSavePanel on macOS, IFileDialog/MessageBox on Windows, GTK dialogs on Linux). + +Eventing +- events.go: Custom event system for the application package. Provides: + - CustomEvent (name, data, cancellation) and ApplicationEvent/WindowEvent wrappers with Context() access to typed event contexts. + - EventProcessor to register listeners (On/Once/OnMultiple), hooks, and to Emit events. Safe for concurrent use via sync primitives. + - Bridges to pkg/events for enum types (ApplicationEventType, WindowEventType) used throughout. +- context_application_event.go, context_window_event.go: Define typed context payloads for app/window events passed to listeners (e.g., theme change info, file open URL, window IDs). +- events_common_*.go and event_manager.go: Glue code that maps platform events to high-level events and exposes App.Event API used by apps and internal subsystems. + +Miscellaneous utilities +- browser_manager.go: Tracks browser/webview state across windows; used by certain operations that need to coordinate with the web runtime. +- image.go: Image helpers (e.g., RGBA), icons. +- path.go: Small helpers for path normalization when interacting with OS file dialogs or asset paths. +- urlvalidator.go: Validates URLs provided for window content (security and correctness). Unit tests in urlvalidator_test.go. +- single_instance*.go: Optional single-instance enforcement per OS; integrates with platform IPC or file locks. +- systemtray.go: Cross-platform SystemTray API given to app developers; platform-specific implementations in systemtray_*.go. +- clipboard.go: Cross-platform Clipboard API facade; platform-specific glue in clipboard_*.go. +- logger_dev*.go/logger_prod.go: Logging helpers per build mode and OS. +- TODO.md: Notes for maintainers. + + +Detailed init/run sequence with call graph highlights +- App creation: + 1. application.New(options) + - Merges defaults; stores Options; sets up assets/middleware stack; chooses dev/prod behaviors; prepares logger. + - Does NOT start platform loop yet. + 2. App.init() (method) is called by New internally to initialise managers and state. + +- Run startup (application.go: Run): + 3. Guard against double Run; mark starting; arrange context cancellation on failure. + 4. preRun(): perform any post-construction checks and assetserver finalisation. + 5. Create platform app: a.impl = newPlatformApp(a) -> application_{os}.go + - Platform impl may do platform-specific pre-main-loop init in impl.init(). + 6. Start services: for each options.Service, a.startupService(service) and record for shutdown order. + 7. Start event pumps: spin goroutines to drain internal channels and dispatch to managers and windows (see channels list above). + 8. Flip a.running = true and execute any pending runnables queued before Run (runOrDeferToAppRun). + 9. Apply app-level UI state: set application menu (macOS), set icon if provided. + 10. Enter platform loop: return a.impl.run(). From here, the native main loop owns the thread and dispatches native events that are bridged back into Go via callbacks and channels. + +- Runtime interactions: + - Window creation: NewWindow(options) constructs a WebviewWindow, invokes platform creation, and registers it with WindowManager and platformApp. Events from native layer (resize, focus, key) become messages in windowEvents/windowMessageBuffer/windowKeyEvents and are dispatched by App handlers to per-window callbacks. + - Dialogs: The public dialog builders call through to per-OS implementations; responses are sent back via DialogResponse/DialogError into the originating Window. + - Menus and accelerators: Menu items with accelerators are registered with KeyBindingManager. Native key events are turned into accelerator strings per-OS (keys_*.go) and routed back to WebviewWindow.processKeyBinding. + - Custom events: App.Event.Emit(CustomEvent) dispatches to registered listeners and also to windows (DispatchWailsEvent) so frontends subscribed via the JS runtime receive them. + +- Shutdown: + - Triggered by App.Quit(), window close that results in shouldQuit() returning true, or fatal errors. + - App.cleanup() tears down windows, trays, menus, and services in order; cancels root context; releases native resources via platformApp.destroy(). + + +Data channels and queues (core ones referenced in application.go) +- applicationEvents: carries ApplicationEvent to App.Event handler. +- windowEvents: carries internal windowEvent to App.handleWindowEvent which locates the Window and emits a typed WindowEvent. +- webviewRequests: transports webview asset HTTP requests to the asset server pipeline (headers, middleware, handlers). +- windowMessageBuffer: messages from webview to window (RPC responses, console/log, menu clicks, etc.). +- windowKeyEvents: key accelerator strings per window; processed by WebviewWindow to run actions or menu accelerators. +- windowDragAndDropBuffer: drag-and-drop messages containing filenames and drop-zone details; dispatched to the destination window. +- menuItemClicked: menu item selection IDs enqueued by platform menu handlers and dispatched to Menu manager. + + +How to trace the flow yourself +- Set breakpoints in application.go Run() and platform newPlatformApp(...) to observe startup. +- On Windows, observe application_windows.go run() and wndProc() to see message dispatch into channels. +- Create a simple Example (see v3/examples) and instrument NewWindow() and WebviewWindow.HandleMessage() to see frontend<->backend calls. +- Enable dev logging (logger_dev*.go) to see debug lines emitted when events and drag-and-drop messages are processed. + + +Practical usage pattern (developer API surface) +- Create and configure the app: + - app := application.New(application.Options{ /* set windows, assets, menu, services, etc. */ }) + - mainWindow := application.NewWindow(webview_window_options) + - mainWindow.Show() + - return app.Run() +- Register services via app.RegisterService(...) before Run(). +- Use app.Event.On/Once to register for application events (ApplicationStarted, ThemeChanged, etc.). +- Use window.OnWindowEvent to subscribe to per-window events. +- Use dialogs via application.InfoDialog()/OpenFileDialog()/SaveFileDialog(), etc. + +This deep dive should equip you to understand and trace how an application is initialised and run across platforms within the v3/pkg/application package. \ No newline at end of file diff --git a/DND_ANALYSIS.md b/DND_ANALYSIS.md new file mode 100644 index 000000000..75b697aac --- /dev/null +++ b/DND_ANALYSIS.md @@ -0,0 +1,45 @@ +# Drag-and-Drop Analysis (Windows Focus) + +## Overview +- Wails v3 supports two drag-and-drop surfaces when `EnableDragAndDrop` is set: a native window-level file drop channel and HTML drag/drop within the embedded webview. You can see the end-to-end flow in `v3/pkg/application/webview_window_windows.go` (native bridge) and `v3/internal/runtime/desktop/@wailsio/runtime/src/window.ts` (runtime dispatcher). +- Native drops rely on a custom `IDropTarget` implementation that gathers files/coordinates from Win32, pushes them through the Wails event system, and fan out as `events.Common.WindowDropZoneFilesDropped` with enriched context (`DropZoneDetails`, attribute map, coordinates). +- Pure HTML drag/drop (e.g. `v3/examples/html-dnd-api`) is handled entirely by the browser layer; Wails only needs to stay out of the way so standard DOM APIs operate normally. + +## Native/Go Pipeline +1. When a window is created with `EnableDragAndDrop`, line ~1948 of `v3/pkg/application/webview_window_windows.go` instantiates `w32.NewDropTarget()` and (optionally) calls `chromium.AllowExternalDrag(false)` to disable WebView2’s built-in file handling. +2. `EnumChildWindows` registers the COM drop target against every current child HWND. The callbacks (`OnEnter`, `OnOver`, `OnLeave`, `OnDrop`) emit Windows-specific events (`events.Windows.WindowDragEnter` etc.) via `w.parent.emit`, so listeners can react even before files are delivered. +3. `DropTarget.Drop` (see `v3/pkg/w32/idroptarget.go:69-140`) extracts filenames from the `IDataObject`, then hands control back to the window impl. Coordinates arrive as screen pixels (`POINT`), so `OnDrop` converts to window-relative coordinates and then calls `convertWindowToWebviewCoordinates` (lines ~1908-1990). Finally `InitiateFrontendDropProcessing` (in `v3/pkg/application/webview_window.go:1484-1515`) formats a JS call: `window.wails.Window.HandlePlatformFileDrop([...], x, y)`. +4. `MessageProcessor` case `WindowDropZoneDropped` (`v3/pkg/application/messageprocessor_window.go:430-488`) decodes the payload that the runtime posts back, wraps it in `DropZoneDetails`, and pushes it through the buffered `windowDragAndDropBuffer`. `App.handleDragAndDropMessage` picks it up and forwards to `WebviewWindow.HandleDragAndDropMessage`, which attaches dropped files + drop-zone metadata to the `WindowEventContext`. +5. The consumer API is the `events.Common.WindowDropZoneFilesDropped` event. The drag-n-drop example shows how to subscribe (`v3/examples/drag-n-drop/main.go:109-158`) and propagate to the frontend via custom events. + +## Runtime/JS Behaviour +- `@wailsio/runtime/src/window.ts:538-680` controls the frontend side. `HandlePlatformFileDrop` locates a drop target using `document.elementFromPoint` and `closest([data-wails-dropzone])`; if nothing qualifies it returns early, so native drops that miss a registered dropzone never reach Go. +- The runtime maintains hover styling by tracking `dragenter/over/leave` on `document.documentElement` and toggling `wails-dropzone-hover`. This is how the example achieves live highlighting. +- A legacy helper still exists: `System.HandlePlatformFileDrop` in `@wailsio/runtime/src/system.ts:159-184` marshals a different payload and calls method id `ApplicationFilesDroppedWithContext`, but there is no matching handler in `messageprocessor_application.go`. That means any codepath that invokes it would receive an HTTP 400/500. +- The window runtime bundles `drag.ts`, which manages `--wails-draggable` regions so window dragging/resizing does not swallow pointer events. Developers must ensure dropzones are not also marked draggable. + +## Example Insights +- `v3/examples/drag-n-drop/assets/index.html` annotates folders with `data-wails-dropzone` and demonstrates how attributes flow through to Go (`DropZoneDetails.Attributes`). It also shows that the frontend expects `dropX/dropY` in CSS pixels, which helps when validating coordinate transforms. +- `v3/examples/html-dnd-api` confirms standard HTML DnD works without the native bridge; it is a useful regression test when tweaking `drag.ts` so pointer suppression does not break DOM events. + +## Potential Bug Hotspots +- **Window-level drops ignored** - `window.ts:554-569` bails if no dropzone is discovered, so the documented `WindowFilesDropped` event never fires. Users expecting “drop anywhere” support lose file payloads entirely. +- **Coordinate scaling** - `convertWindowToWebviewCoordinates` (`webview_window_windows.go:1908-1994`) computes offsets using physical pixels but never converts to WebView2 DIPs. On mixed-DPI or >100% scaling setups, `elementFromPoint` will query the wrong DOM position. +- **Drop target lifecycle** - `EnumChildWindows` runs only once during initialisation. WebView2 can spawn new `Chrome_RenderWidgetHostHWND` instances on navigation or GPU process resets, leaving them unregistered and breaking drops until the app restarts. +- **OLE cleanup** - `DropTarget.Drop` never calls `w32.DragFinish` after `DragQueryFile`. Windows docs recommend doing so to release HDROP resources; skipping it risks leaks on repeated drops. +- **Stale runtime API** - `System.HandlePlatformFileDrop` references a non-existent backend method (`messageprocessor_application.go` lacks case 100). Any future JS that follows the generated docs will fail at runtime. +- **Backpressure risk** - `windowDragAndDropBuffer` (channel size 5) blocks the HTTP handler if event consumers stall. Heavy processing in listeners could cause the runtime call to hang and, on Windows, freeze the drag cursor until the fetch resolves. + +## Improvement Opportunities +1. Emit a fallback `WindowFilesDropped` event directly from `DropTarget.OnDrop` when `HandlePlatformFileDrop` declines the payload, preserving drop-anywhere behaviour. +2. Introduce DPI-aware coordinate conversion (use `globalApplication.Screen.PhysicalToDipPoint`) before invoking `elementFromPoint`. +3. Re-run `RegisterDragDrop` whenever a new WebView child window appears (CoreWebView2 `FrameCreated` / `NewBrowserVersionAvailable` callbacks) and on navigation completions. +4. Either wire up the `ApplicationFilesDroppedWithContext` method or remove the `System.HandlePlatformFileDrop` export to avoid misleading integrators. +5. Add integration tests that exercise drops on high-DPI displays and across multiple monitors using the drag-n-drop example as a harness. +6. Consider surfacing drop-target state (e.g., active element id) via diagnostic logging so Windows reports can be correlated without attaching a debugger. + +## Open Questions +- Do we need to respect WebView2’s native `AllowExternalDrop(true)` when `EnableDragAndDrop` is disabled, or should we expose both behaviours concurrently? +- How should conflicting CSS states (`--wails-draggable` vs `data-wails-dropzone`) be resolved? Current logic leaves it up to the developer, but documenting or enforcing precedence could prevent accidental suppression. +- Can we guarantee that `elementFromPoint` is safe when overlays or transparent windows are involved, or do we need hit-testing improvements using `elementsFromPoint`? +- Would it be safer to move drop processing entirely to Go (dispatching both window-wide and targeted events) and keep JS solely for hover styling? diff --git a/DND_ISSUES.md b/DND_ISSUES.md new file mode 100644 index 000000000..fb59bb612 --- /dev/null +++ b/DND_ISSUES.md @@ -0,0 +1,246 @@ +# Wails Drag and Drop Issues Report + +## Summary +This report compiles all drag and drop related issues found in Wails v3 GitHub repository. The issues span across multiple platforms (Windows, macOS, Linux) and various versions of Wails. + +--- + +## Issue #1: [V3] Drag-and-drop broken on Windows since alpha 19 + +**Title:** [V3] Drag-and-drop broken on windows since alpha 19 +**Platform:** Windows 10 +**Reported:** 2024-08-09 +**Location:** GitHub +**Reporter:** xob0t +**Link:** https://github.com/wailsapp/wails/issues/4489 + +**Summary:** No drag and drop events are triggered on the Go side in Wails v3 alpha 20+. The issue appears to have been introduced by PR #4318. + +**Reproduction:** +1. Run dnd example on Windows +2. Observe that no DnD events are triggered + +**System:** Windows 10 Pro, Wails v3.0.0-alpha.20, AMD Ryzen 5 5600X, NVIDIA GeForce RTX 3070 + +--- + +## Issue #2: Windows 10 doesn't receive drag and drop events on the backend + +**Title:** Windows 10 doesn't receive drag and drop events on the backend +**Platform:** Windows 10 +**Reported:** 2025-01-07 +**Location:** GitHub +**Reporter:** makew0rld +**Link:** https://github.com/wailsapp/wails/issues/3985 + +**Summary:** Backend drop handler registered with `runtime.OnFileDrop` never gets executed when dropping files on Windows 10, though JavaScript drop events do fire. The event is not being propagated to the backend properly. + +**Reproduction:** +1. Clone https://github.com/makew0rld/wails-issue-3985 +2. Build for Windows: `wails build -platform windows/amd64 -windowsconsole -debug` +3. Open the .exe in Windows +4. Open web console using right-click menu +5. Drag in a file +6. Observe nothing appears in backend console, but drop event appears in web console + +**System:** Built on Arch Linux, tested on Windows 10 VM, Wails v2.9.2 + +--- + +## Issue #3: [v3] [mac] drag n drop does not work as expected after resizing window + +**Title:** [v3] [mac] drag n drop does not work as expected after resizing window +**Platform:** macOS +**Reported:** 2024-09-12 +**Location:** GitHub +**Reporter:** Etesam913 +**Link:** https://github.com/wailsapp/wails/issues/3743 + +**Summary:** On macOS, after resizing the window, drag and drop usually gets rejected when trying to drag a file into the window. + +**Reproduction:** +1. `git checkout v3-alpha` +2. `cd v3/examples/drag-n-drop` +3. `go run main.go` +4. Resize window +5. Drag file into window +6. File gets rejected + +**System:** macOS 15.0, Apple M2, Wails v3.0.0-alpha.6 + +--- + +## Issue #4: Webview failed to set AllowExternalDrag to false on Win 10 + +**Title:** Webview failed to set AllowExternalDrag to false on Win 10 (after cross compile on macOS) +**Platform:** Windows 10 +**Reported:** 2024-09-26 +**Location:** GitHub +**Reporter:** barats +**Link:** https://github.com/wailsapp/wails/issues/3782 + +**Summary:** Two main issues: 1) WebView fails to set AllowExternalDrag to false, causing WebView to automatically open dragged files. 2) Backend `runtime.OnFileDrop` doesn't work - no events are triggered. + +**Reproduction:** +1. Build windows/amd64 app on macOS +2. Move .exe files to Win10 and run +3. Drag and drop files into app window +4. WebView DnD not disabled, backend events not called + +**System:** Built on macOS 15.0, tested on Windows 10, Wails v2.9.2 + +--- + +## Issue #5: [v2] Files DragAndDrop bugs + +**Title:** [v2] Files DragAndDrop bugs +**Platform:** Linux (Ubuntu) +**Reported:** 2024-06-23 +**Location:** GitHub +**Reporter:** Vovan-VE +**Link:** https://github.com/wailsapp/wails/issues/3563 + +**Summary:** Two issues: 1) When only `EnableFileDrop: true`, every D'n'D triggers an `OnDomReady` event. 2) When `EnableFileDrop: true, DisableWebViewDrop: true`, D'n'D doesn't work at all for files. + +**Reproduction:** +```go +func (a *App) domReady(ctx context.Context) { + runtime.OnFileDropOff(ctx) + runtime.LogInfof(ctx, "DOM READY -------------------------------\n%+v", errors.New("WTF?")) + runtime.OnFileDrop(ctx, a.onFileDrop) +} + +func (a *App) onFileDrop(x, y int, paths []string) { + runtime.LogInfof(a.ctx, "drop files: %#v\n%+v", paths, errors.New("WTF?")) +} +``` + +**System:** Ubuntu 22.04, AMD Ryzen 9 7950X, Wails v2.9.1 and v2.9.2 + +--- + +## Issue #6: File drag and drop displays file in Linux WebKit + +**Title:** File drag and drop displays file in Linux WebKit +**Platform:** Linux +**Reported:** 2024-08-17 +**Location:** GitHub +**Reporter:** makew0rld +**Link:** https://github.com/wailsapp/wails/issues/3686 + +**Summary:** When dragging and dropping a file onto the Window, the `OnFileDrop` handler runs successfully, but the entire GUI is replaced with a view of the file if the browser supports it (e.g., images, PDFs). This makes the dropped file unusable as all UI is gone. + +**Reproduction:** +1. Enable file dropping and add a simple handler +2. Try and drop an image file with WebKit on Linux +3. Observe the UI get replaced with a view of the image + +**System:** Arch Linux, Intel Core i7-1165G7, Wails v2.9.1 + +**Note:** Issue was closed on 2025-08-02 with a workaround involving preventDefault() on drop and dragover events. + +--- + +## Issue #7: Drag And Drop "wails-drop-target-active" class issue + +**Title:** Drag And Drop "wails-drop-target-active" class is only applied if cssDropProperty is set via raw `style` +**Platform:** Windows +**Reported:** 2025-07-13 +**Location:** GitHub +**Reporter:** riannucci +**Link:** https://github.com/wailsapp/wails/issues/4419 + +**Summary:** `OnFileDrop` does not apply the `wails-drop-target-active` class unless `--wails-drop-target: drop` is set directly via the element's `style` attribute, even though the `OnFileDrop` callback does fire. + +**Reproduction:** +1. Set up a new wails project +2. Set EnableFileDrop in Application Options +3. Add CSS with `--wails-drop-target: drop` in stylesheet +4. Run app and drag file to target element +5. Base path gets set but element doesn't change styles + +**System:** Windows 10/11 Pro, AMD Ryzen 7 5800X3D, Wails v2.10.2 + +**Note:** Issue was closed on 2025-08-02. Problem was in draganddrop.js using element.style instead of getComputedStyle. + +--- + +## Issue #8: Use a user-agent that is more recognizable by front-end code + +**Title:** Use a user-agent that is more recognizable by front-end code +**Platform:** macOS +**Reported:** 2024-01-31 +**Location:** GitHub +**Reporter:** hsiafan +**Link:** https://github.com/wailsapp/wails/issues/3226 + +**Summary:** SortableJS (drag-and-drop library) doesn't work properly in Wails' webview because the userAgent doesn't contain standard browser identifiers like 'Safari/605.1.15', causing library detection to fail. + +**Reproduction:** +Libraries like SortableJS check userAgent for specific strings ('Edge', 'Firefox', 'Chrome', 'Safari') and fail to work properly when these are not present. + +**System:** macOS, affects drag-and-drop libraries + +--- + +## Issue #9: Application soft crashes when using vue-virtual-scroller on Linux + +**Title:** Application soft crashes when using vue-virtual-scroller on Linux +**Platform:** Linux (Ubuntu) +**Reported:** 2024-10-22 +**Location:** GitHub +**Reporter:** ysmilda +**Link:** https://github.com/wailsapp/wails/issues/3849 + +**Summary:** Using vue-virtual-scroller component causes the application to crash on Linux builds, rendering a grey screen and hanging the inspector window. Works fine on Windows and in dev view. + +**Reproduction:** +1. Create new wails instance with `wails init -n myproject -t vue-ts` +2. Add vue-virtual-scroller component +3. Create Linux build (`wails build -tags webkit2_41`) +4. Observe crash while rendering + +**System:** Ubuntu 24.04, Intel Core i7-12700, Wails v2.9.2 + +--- + +## Issue #10: [DragAndDrop] panic when dropping non-file objects + +**Title:** [DragAndDrop] panic when dropping non-file objects +**Platform:** Windows +**Reported:** 2024-07-09 +**Location:** GitHub +**Reporter:** Alpa-1 +**Link:** https://github.com/wailsapp/wails/issues/3596 + +**Summary:** A panic occurs when dropping non-file objects (like strings or browser tabs) into the webview. The application crashes with a nil pointer dereference. + +**Reproduction:** +1. Enable DragAndDrop +2. Add a drop target on frontend with `--wails-drop-target` +3. Register a callback with `OnFileDrop((x,y,f) => { console.log(f) }, true)` +4. Drop a Browser Tab onto the element +5. Application panics + +**System:** Windows 10 Enterprise, AMD Ryzen 7 PRO 4750U, Wails v2.9.1 + +**Note:** Issue was closed on 2024-08-18. A simple nil check fix was provided. + +--- + +## Common Patterns Identified: + +1. **Platform-specific issues:** Windows has the most reported issues, particularly with events not reaching the backend +2. **Version regression:** Multiple reports indicate v3 alpha 19+ broke drag and drop on Windows +3. **WebView configuration:** Several issues relate to DisableWebViewDrop not working correctly +4. **Cross-platform builds:** Issues when building for Windows on other platforms +5. **Frontend/Backend disconnect:** Common pattern of JavaScript events firing but Go handlers not receiving events +6. **UI replacement on Linux:** Specific issue where dropped files replace the entire UI in WebKit + +## Recommendations: + +1. Focus on Windows platform issues first as they are most prevalent +2. Investigate the changes in PR #4318 which allegedly broke Windows DnD +3. Review the event propagation mechanism between frontend and backend +4. Test cross-platform builds more thoroughly +5. Improve documentation on DragAndDrop configuration options \ No newline at end of file diff --git a/Taskfile.yaml b/Taskfile.yaml index 7cc165825..051da3e42 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -16,6 +16,11 @@ includes: dir: v3 optional: true + docs: + taskfile: docs + dir: docs + optional: true + tasks: contributors:check: cmds: diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 000000000..51a2f1d0a --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,6 @@ +import { defineConfig } from 'astro/config'; +import d2 from 'astro-d2'; +// https://astro.build/config +export default defineConfig({ + integrations: [d2()] +}); \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..6240da8b1 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,21 @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/docs/.npmrc b/docs/.npmrc new file mode 100644 index 000000000..cffe8cdef --- /dev/null +++ b/docs/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json new file mode 100644 index 000000000..22a15055d --- /dev/null +++ b/docs/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + "recommendations": ["astro-build.astro-vscode"], + "unwantedRecommendations": [] +} diff --git a/docs/.vscode/launch.json b/docs/.vscode/launch.json new file mode 100644 index 000000000..d64220976 --- /dev/null +++ b/docs/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "command": "./node_modules/.bin/astro dev", + "name": "Development server", + "request": "launch", + "type": "node-terminal" + } + ] +} diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 000000000..23caa94af --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +v3alpha.wails.io \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..108b2a1fa --- /dev/null +++ b/docs/README.md @@ -0,0 +1,65 @@ +# Wails v3 Documentation + +[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) + +World-class documentation for Wails v3, redesigned following Netflix documentation principles. + +## 📚 Documentation Redesign (2025-10-01) + +This documentation has been completely redesigned to follow the **Netflix approach** to developer documentation: + +- **Problem-first framing** - Start with why, not what +- **Progressive disclosure** - Multiple entry points for different skill levels +- **Real production examples** - No toy code +- **Story-Code-Context pattern** - Why → How → When +- **Scannable content** - Clear structure, visual aids + +**Status:** Foundation complete (~20%), ready for content migration + +See [IMPLEMENTATION_SUMMARY.md](./IMPLEMENTATION_SUMMARY.md) for full details. + +## 🚀 Project Structure + +Inside of your Astro + Starlight project, you'll see the following folders and +files: + +```sh +. +├── public/ +├── src/ +│ ├── assets/ +│ ├── content/ +│ │ ├── docs/ +│ │ └── config.ts +│ └── env.d.ts +├── astro.config.mjs +├── package.json +└── tsconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. +Each file is exposed as a route based on its file name. + +Images can be added to `src/assets/` and embedded in Markdown with a relative +link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Check out [Starlight’s docs](https://starlight.astro.build/), read +[the Astro documentation](https://docs.astro.build), or jump into the +[Astro Discord server](https://astro.build/chat). diff --git a/docs/Taskfile.yml b/docs/Taskfile.yml new file mode 100644 index 000000000..844483c5a --- /dev/null +++ b/docs/Taskfile.yml @@ -0,0 +1,38 @@ +# https://taskfile.dev + +version: '3' + +vars: + # Change this to switch package managers: bun, npm, pnpm, yarn + PKG_MANAGER: bun + +tasks: + + setup: + summary: Setup the project (including D2 diagram tool) + preconditions: + - sh: '{{.PKG_MANAGER}} --version' + msg: "Looks like {{.PKG_MANAGER}} isn't installed." + - sh: 'go version' + msg: "Go is not installed. Install from https://go.dev/dl/" + cmds: + - '{{.PKG_MANAGER}} install' + - go install oss.terrastruct.com/d2@latest + - echo "✓ Setup complete. D2 installed to $(go env GOPATH)/bin/d2" + + dev: + summary: Run the dev server + preconditions: + - sh: '{{.PKG_MANAGER}} --version' + msg: "Looks like {{.PKG_MANAGER}} isn't installed." + cmds: + - '{{.PKG_MANAGER}} run dev' + + build: + summary: Build the docs + preconditions: + - sh: '{{.PKG_MANAGER}} --version' + msg: "Looks like {{.PKG_MANAGER}} isn't installed." + cmds: + - '{{.PKG_MANAGER}} run build' + diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 000000000..7334dbd0a --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,342 @@ +// @ts-check +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import sitemap from "@astrojs/sitemap"; +import starlightLinksValidator from "starlight-links-validator"; +import starlightImageZoom from "starlight-image-zoom"; +import starlightBlog from "starlight-blog"; +import { authors } from "./src/content/authors"; +import d2 from 'astro-d2'; + +// https://astro.build/config +export default defineConfig({ + site: "https://wails.io", + trailingSlash: "ignore", + compressHTML: true, + output: "static", + build: { format: "directory" }, + devToolbar: { enabled: true }, + integrations: [ + d2(), + sitemap(), + starlight({ + title: "", + titleDelimiter: "", + logo: { + dark: "./src/assets/wails-logo-horizontal-dark.svg", + light: "./src/assets/wails-logo-horizontal-light.svg", + }, + favicon: "./public/favicon.svg", + description: "Build beautiful desktop applications using Go and modern web technologies.", + pagefind: true, + customCss: ["./src/stylesheets/extra.css"], + lastUpdated: true, + pagination: true, + editLink: { + baseUrl: "https://github.com/wailsapp/wails/edit/v3-alpha/docs", + }, + social: { + github: "https://github.com/wailsapp/wails", + discord: "https://discord.gg/JDdSxwjhGf", + "x.com": "https://x.com/wailsapp", + }, + head: [ + { + tag: 'script', + content: ` + document.addEventListener('DOMContentLoaded', () => { + const socialLinks = document.querySelector('.social-icons'); + if (socialLinks) { + const sponsorLink = document.createElement('a'); + sponsorLink.href = 'https://github.com/sponsors/leaanthony'; + sponsorLink.className = 'sl-flex'; + sponsorLink.title = 'Sponsor'; + sponsorLink.innerHTML = 'Sponsor'; + socialLinks.appendChild(sponsorLink); + } + }); + `, + }, + ], + defaultLocale: "root", + locales: { + root: { label: "English", lang: "en", dir: "ltr" }, + }, + plugins: [ + starlightImageZoom(), + starlightBlog({ + title: "Wails Blog", + authors: authors, + }), + ], + sidebar: [ + { label: "Home", link: "/" }, + + // Progressive Onboarding - Netflix Principle: Start with the problem + { label: "Why Wails?", link: "/quick-start/why-wails" }, + + { + label: "Quick Start", + collapsed: false, + items: [ + { label: "Installation", link: "/quick-start/installation" }, + { label: "Your First App", link: "/quick-start/first-app" }, + { label: "Next Steps", link: "/quick-start/next-steps" }, + ], + }, + + // Tutorials + { + label: "Tutorials", + collapsed: true, + autogenerate: { directory: "tutorials" }, + }, + + // Core Concepts + { + label: "Core Concepts", + collapsed: true, + items: [ + { label: "How Wails Works", link: "/concepts/architecture" }, + { label: "Manager API", link: "/concepts/manager-api" }, + { label: "Application Lifecycle", link: "/concepts/lifecycle" }, + { label: "Go-Frontend Bridge", link: "/concepts/bridge" }, + { label: "Build System", link: "/concepts/build-system" }, + ], + }, + + { + label: "Features", + collapsed: true, + items: [ + { + label: "Windows", + collapsed: true, + items: [ + { label: "Window Basics", link: "/features/windows/basics" }, + { label: "Window Options", link: "/features/windows/options" }, + { label: "Multiple Windows", link: "/features/windows/multiple" }, + { label: "Frameless Windows", link: "/features/windows/frameless" }, + { label: "Window Events", link: "/features/windows/events" }, + ], + }, + { + label: "Menus", + collapsed: true, + items: [ + { label: "Application Menus", link: "/features/menus/application" }, + { label: "Context Menus", link: "/features/menus/context" }, + { label: "System Tray Menus", link: "/features/menus/systray" }, + { label: "Menu Reference", link: "/features/menus/reference" }, + ], + }, + { + label: "Bindings & Services", + collapsed: true, + items: [ + { label: "Method Binding", link: "/features/bindings/methods" }, + { label: "Services", link: "/features/bindings/services" }, + { label: "Advanced Binding", link: "/features/bindings/advanced" }, + { label: "Best Practices", link: "/features/bindings/best-practices" }, + ], + }, + { + label: "Events", + collapsed: true, + items: [ + { label: "Event System", link: "/features/events/system" }, + { label: "Application Events", link: "/features/events/application" }, + { label: "Window Events", link: "/features/events/window" }, + { label: "Custom Events", link: "/features/events/custom" }, + ], + }, + { + label: "Dialogs", + collapsed: true, + items: [ + { label: "File Dialogs", link: "/features/dialogs/file" }, + { label: "Message Dialogs", link: "/features/dialogs/message" }, + { label: "Custom Dialogs", link: "/features/dialogs/custom" }, + ], + }, + { + label: "Clipboard", + collapsed: true, + autogenerate: { directory: "features/clipboard" }, + }, + { + label: "Browser", + collapsed: true, + autogenerate: { directory: "features/browser" }, + }, + { label: "Drag & Drop", link: "/features/drag-drop" }, + { + label: "Keyboard", + collapsed: true, + autogenerate: { directory: "features/keyboard" }, + }, + { + label: "Notifications", + collapsed: true, + autogenerate: { directory: "features/notifications" }, + }, + { + label: "Screens", + collapsed: true, + autogenerate: { directory: "features/screens" }, + }, + { + label: "Environment", + collapsed: true, + autogenerate: { directory: "features/environment" }, + }, + { + label: "Platform-Specific", + collapsed: true, + autogenerate: { directory: "features/platform" }, + }, + ], + }, + + // Guides - Task-oriented patterns (Netflix: When to use it, when not to use it) + { + label: "Guides", + collapsed: true, + items: [ + { + label: "Development", + collapsed: true, + items: [ + { label: "Project Structure", link: "/guides/dev/project-structure" }, + { label: "Development Workflow", link: "/guides/dev/workflow" }, + { label: "Debugging", link: "/guides/dev/debugging" }, + { label: "Testing", link: "/guides/dev/testing" }, + ], + }, + { + label: "Building & Packaging", + collapsed: true, + items: [ + { label: "Building Applications", link: "/guides/build/building" }, + { label: "Build Customization", link: "/guides/build/customization" }, + { label: "Cross-Platform Builds", link: "/guides/build/cross-platform" }, + { label: "Code Signing", link: "/guides/build/signing" }, + { label: "Windows Packaging", link: "/guides/build/windows" }, + { label: "macOS Packaging", link: "/guides/build/macos" }, + { label: "Linux Packaging", link: "/guides/build/linux" }, + { label: "MSIX Packaging", link: "/guides/build/msix" }, + ], + }, + { + label: "Distribution", + collapsed: true, + items: [ + { label: "Auto-Updates", link: "/guides/distribution/auto-updates" }, + { label: "File Associations", link: "/guides/distribution/file-associations" }, + { label: "Custom Protocols", link: "/guides/distribution/custom-protocols" }, + { label: "Single Instance", link: "/guides/distribution/single-instance" }, + ], + }, + { + label: "Integration Patterns", + collapsed: true, + items: [ + { label: "Using Gin Router", link: "/guides/patterns/gin-routing" }, + { label: "Gin Services", link: "/guides/patterns/gin-services" }, + { label: "Database Integration", link: "/guides/patterns/database" }, + { label: "REST APIs", link: "/guides/patterns/rest-api" }, + ], + }, + { + label: "Advanced Topics", + collapsed: true, + items: [ + { label: "Custom Templates", link: "/guides/advanced/custom-templates" }, + { label: "WML (Wails Markup)", link: "/guides/advanced/wml" }, + { label: "Panic Handling", link: "/guides/advanced/panic-handling" }, + { label: "Security Best Practices", link: "/guides/advanced/security" }, + ], + }, + ], + }, + + // Reference - Comprehensive API docs (Netflix: Complete technical reference) + { + label: "API Reference", + collapsed: true, + items: [ + { label: "Overview", link: "/reference/overview" }, + { label: "Application", link: "/reference/application" }, + { label: "Window", link: "/reference/window" }, + { label: "Menu", link: "/reference/menu" }, + { label: "Events", link: "/reference/events" }, + { label: "Dialogs", link: "/reference/dialogs" }, + { label: "Frontend Runtime", link: "/reference/frontend-runtime" }, + { label: "CLI", link: "/reference/cli" }, + ], + }, + + // Contributing + { + label: "Contributing", + collapsed: true, + items: [ + { label: "Getting Started", link: "/contributing/getting-started" }, + { label: "Development Setup", link: "/contributing/setup" }, + { label: "Coding Standards", link: "/contributing/standards" }, + ], + }, + + // Migration & Troubleshooting + { + label: "Migration", + collapsed: true, + items: [ + { label: "From v2 to v3", link: "/migration/v2-to-v3" }, + { label: "From Electron", link: "/migration/from-electron" }, + ], + }, + + { + label: "Troubleshooting", + collapsed: true, + autogenerate: { directory: "troubleshooting" }, + }, + + // Community & Resources + { + label: "Community", + collapsed: true, + items: [ + { label: "Links", link: "/community/links" }, + { label: "Templates", link: "/community/templates" }, + { + label: "Showcase", + collapsed: true, + items: [ + { label: "Overview", link: "/community/showcase" }, + { + label: "Applications", + autogenerate: { + directory: "community/showcase", + collapsed: true, + }, + }, + ], + }, + ], + }, + + { label: "What's New", link: "/whats-new" }, + { label: "Status", link: "/status" }, + { label: "Changelog", link: "/changelog" }, + { + label: "Sponsor", + link: "https://github.com/sponsors/leaanthony", + badge: { text: "❤️" }, + }, + { label: "Credits", link: "/credits" }, + ], + }), + ], +}); diff --git a/docs/build-output.txt b/docs/build-output.txt new file mode 100644 index 000000000..60299a9c3 --- /dev/null +++ b/docs/build-output.txt @@ -0,0 +1,23 @@ +$ astro build +10:08:53 [WARN] [starlight-blog-plugin] It looks like you already have a `MarkdownContent` component override in your Starlight configuration. +10:08:53 [WARN] [starlight-blog-plugin] To use `starlight-blog`, either remove your override or update it to render the content from `starlight-blog/overrides/MarkdownContent.astro`. +10:08:54 [content] Syncing content +10:08:54 [WARN] [glob-loader] The base directory "E:\wails\docs\src\content\i18n\" does not exist. +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +10:08:55 [WARN] [astro-expressive-code] Error while loading code block language "pseudo" in document "E:\wails\docs\src\content\docs\DEVELOPER_GUIDE.md". Using "txt" instead. You can add custom languages using the "langs" config option. Error details: Unknown language "pseudo" +bad indentation of a mapping entry + Location: + E:\wails\docs\src\content\docs\learn\context-menu.mdx:3:7 + Stack trace: + at generateError (file:///E:/wails/docs/node_modules/js-yaml/dist/js-yaml.mjs:1273:10) + at readBlockMapping (file:///E:/wails/docs/node_modules/js-yaml/dist/js-yaml.mjs:2272:7) + at readDocument (file:///E:/wails/docs/node_modules/js-yaml/dist/js-yaml.mjs:2715:3) + at load$1 (file:///E:/wails/docs/node_modules/js-yaml/dist/js-yaml.mjs:2804:19) + at safeParseFrontmatter (file:///E:/wails/docs/node_modules/@astrojs/mdx/dist/utils.js:33:12) +error: script "astro" exited with code 1 diff --git a/docs/bun.lock b/docs/bun.lock new file mode 100644 index 000000000..95cc0fbb7 --- /dev/null +++ b/docs/bun.lock @@ -0,0 +1,1560 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "wails-docs", + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/react": "^4.1.0", + "@astrojs/starlight": "^0.30.0", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "astro": "^5.0.0", + "astro-d2": "^0.5.0", + "framer-motion": "^11.14.4", + "motion": "^11.14.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sharp": "^0.33.5", + "starlight-blog": "^0.15.0", + "starlight-image-zoom": "^0.9.0", + "starlight-links-validator": "^0.13.4", + "starlight-showcases": "^0.2.0", + "typescript": "^5.7.2", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@astro-community/astro-embed-twitter": ["@astro-community/astro-embed-twitter@0.5.8", "", { "dependencies": { "@astro-community/astro-embed-utils": "^0.1.0" }, "peerDependencies": { "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" } }, "sha512-O2ptQPw+DfipukK8czjJcTcyVgDsrs3OmrHbc3YmWRglaUTOpSTImzPo076POyNBSWjLaRKloul81DFiAMNjTA=="], + + "@astro-community/astro-embed-utils": ["@astro-community/astro-embed-utils@0.1.3", "", { "dependencies": { "linkedom": "^0.14.26" } }, "sha512-eiMO+vfCdE9GtW6qE7X5Xl6YCKZDCoXJEWqRofQcoC3GHjqN2/WhJlnaxNVRq3demSO03UNtho57Em5p7o7AOA=="], + + "@astro-community/astro-embed-youtube": ["@astro-community/astro-embed-youtube@0.5.6", "", { "dependencies": { "lite-youtube-embed": "^0.3.3" }, "peerDependencies": { "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" } }, "sha512-/mRfCl/eTBUz0kmjD1psOy0qoDDBorVp0QumUacjFcIkBullYtbeFQ2ZGZ+3N/tA6cR/OIyzr2QA4dQXlY6USg=="], + + "@astrojs/check": ["@astrojs/check@0.9.4", "", { "dependencies": { "@astrojs/language-server": "^2.15.0", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "dist/bin.js" } }, "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA=="], + + "@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="], + + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.3", "", {}, "sha512-6Pl0bQEIChuW5wqN7jdKrzWfCscW2rG/Cz+fzt4PhSQX2ivBpnhXgFUCs0M3DCYvjYHnPVG2W36X5rmFjZ62sw=="], + + "@astrojs/language-server": ["@astrojs/language-server@2.15.4", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/yaml2ts": "^0.2.2", "@jridgewell/sourcemap-codec": "^1.4.15", "@volar/kit": "~2.4.7", "@volar/language-core": "~2.4.7", "@volar/language-server": "~2.4.7", "@volar/language-service": "~2.4.7", "fast-glob": "^3.2.12", "muggle-string": "^0.4.1", "volar-service-css": "0.0.62", "volar-service-emmet": "0.0.62", "volar-service-html": "0.0.62", "volar-service-prettier": "0.0.62", "volar-service-typescript": "0.0.62", "volar-service-typescript-twoslash-queries": "0.0.62", "volar-service-yaml": "0.0.62", "vscode-html-languageservice": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "bin/nodeServer.js" } }, "sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A=="], + + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.7", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.3", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.12.2", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-KXGdq6/BC18doBCYXp08alHlWChH0hdD2B1qv9wIyOHbvwI5K6I7FhSta8dq1hBQNdun8YkKPR013D/Hm8xd0g=="], + + "@astrojs/mdx": ["@astrojs/mdx@4.3.6", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.7", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-jH04tYgaqLfq3To42+z1oEcXrXUzo3BxZ4fTkb+7BEmOJkQ9/c3iIixFEC+x0GgE8lJb4SuEDGldpAv7+1yY8A=="], + + "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], + + "@astrojs/react": ["@astrojs/react@4.1.0", "", { "dependencies": { "@vitejs/plugin-react": "^4.3.4", "ultrahtml": "^1.5.3", "vite": "^6.0.1" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-8F0ncvcCexVeQZMwPouLSFuzCK1KXUIYQ57lW3ZG2p7B5DGAajXGanb/CGF7MMSpX8Z0t9sELQqLHOCV/+78Ig=="], + + "@astrojs/rss": ["@astrojs/rss@4.0.5", "", { "dependencies": { "fast-xml-parser": "^4.2.7", "kleur": "^4.1.5" } }, "sha512-IyJVL6z09AQtxbgLaAwebT3T5YKe4oTHDesqydJv1KLHw+zEzzMCFuuNsEyxjiqu7df9+DDCpDXLj/WRiEUXvw=="], + + "@astrojs/sitemap": ["@astrojs/sitemap@3.2.1", "", { "dependencies": { "sitemap": "^8.0.0", "stream-replace-string": "^2.0.0", "zod": "^3.23.8" } }, "sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA=="], + + "@astrojs/starlight": ["@astrojs/starlight@0.30.6", "", { "dependencies": { "@astrojs/mdx": "^4.0.1", "@astrojs/sitemap": "^3.1.6", "@pagefind/default-ui": "^1.0.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", "@types/mdast": "^4.0.4", "astro-expressive-code": "^0.38.3", "bcp-47": "^2.1.0", "hast-util-from-html": "^2.0.1", "hast-util-select": "^6.0.2", "hast-util-to-string": "^3.0.0", "hastscript": "^9.0.0", "i18next": "^23.11.5", "js-yaml": "^4.1.0", "mdast-util-directive": "^3.0.0", "mdast-util-to-markdown": "^2.1.0", "mdast-util-to-string": "^4.0.0", "pagefind": "^1.0.3", "rehype": "^13.0.1", "rehype-format": "^5.0.0", "remark-directive": "^3.0.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vfile": "^6.0.2" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-/AoLXjPPD1MqixkTd2Lp3qahSzfCejePWHZQ3+fDjj1CuXI7Gjrr5bR3zNV0b9tynloPAIBM0HOyBNEGAo9uAQ=="], + + "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="], + + "@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.2", "", { "dependencies": { "yaml": "^2.5.0" } }, "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ=="], + + "@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], + + "@babel/compat-data": ["@babel/compat-data@7.26.3", "", {}, "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g=="], + + "@babel/core": ["@babel/core@7.26.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", "@babel/generator": "^7.26.0", "@babel/helper-compilation-targets": "^7.25.9", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.0", "@babel/parser": "^7.26.0", "@babel/template": "^7.25.9", "@babel/traverse": "^7.25.9", "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg=="], + + "@babel/generator": ["@babel/generator@7.26.3", "", { "dependencies": { "@babel/parser": "^7.26.3", "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.25.9", "", { "dependencies": { "@babel/compat-data": "^7.25.9", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.25.9", "", {}, "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="], + + "@babel/helpers": ["@babel/helpers@7.26.0", "", { "dependencies": { "@babel/template": "^7.25.9", "@babel/types": "^7.26.0" } }, "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw=="], + + "@babel/parser": ["@babel/parser@7.26.3", "", { "dependencies": { "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg=="], + + "@babel/runtime": ["@babel/runtime@7.26.0", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw=="], + + "@babel/template": ["@babel/template@7.25.9", "", { "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg=="], + + "@babel/traverse": ["@babel/traverse@7.26.4", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.3", "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w=="], + + "@babel/types": ["@babel/types@7.26.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA=="], + + "@capsizecss/unpack": ["@capsizecss/unpack@2.4.0", "", { "dependencies": { "blob-to-buffer": "^1.2.8", "cross-fetch": "^3.0.4", "fontkit": "^2.0.2" } }, "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q=="], + + "@ctrl/tinycolor": ["@ctrl/tinycolor@4.1.0", "", {}, "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ=="], + + "@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="], + + "@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="], + + "@emmetio/css-parser": ["@emmetio/css-parser@0.4.0", "", { "dependencies": { "@emmetio/stream-reader": "^2.2.0", "@emmetio/stream-reader-utils": "^0.1.0" } }, "sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw=="], + + "@emmetio/html-matcher": ["@emmetio/html-matcher@1.3.0", "", { "dependencies": { "@emmetio/scanner": "^1.0.0" } }, "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ=="], + + "@emmetio/scanner": ["@emmetio/scanner@1.0.4", "", {}, "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA=="], + + "@emmetio/stream-reader": ["@emmetio/stream-reader@2.2.0", "", {}, "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw=="], + + "@emmetio/stream-reader-utils": ["@emmetio/stream-reader-utils@0.1.0", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="], + + "@expressive-code/core": ["@expressive-code/core@0.38.3", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-s0/OtdRpBONwcn23O8nVwDNQqpBGKscysejkeBkwlIeHRLZWgiTVrusT5Idrdz1d8cW5wRk9iGsAIQmwDPXgJg=="], + + "@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3" } }, "sha512-qL2oC6FplmHNQfZ8ZkTR64/wKo9x0c8uP2WDftR/ydwN/yhe1ed7ZWYb8r3dezxsls+tDokCnN4zYR594jbpvg=="], + + "@expressive-code/plugin-shiki": ["@expressive-code/plugin-shiki@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3", "shiki": "^1.22.2" } }, "sha512-kqHnglZeesqG3UKrb6e9Fq5W36AZ05Y9tCREmSN2lw8LVTqENIeCIkLDdWtQ5VoHlKqwUEQFTVlRehdwoY7Gmw=="], + + "@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3" } }, "sha512-dPK3+BVGTbTmGQGU3Fkj3jZ3OltWUAlxetMHI6limUGCWBCucZiwoZeFM/WmqQa71GyKRzhBT+iEov6kkz2xVA=="], + + "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.5", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "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", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], + + "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pHnPL2rm4xbe0LqV376g84hUIsVdy4PK6o2ACveo0DSGoC40eOIwPUPftnUPUinSdDWkkySaL5FT5r9hsXk0ZQ=="], + + "@pagefind/darwin-x64": ["@pagefind/darwin-x64@1.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-q2tcnfvcRyx0GnrJoUQJ5bRpiFNtI8DZWM6a4/k8sNJxm2dbM1BnY5hUeo4MbDfpb64Qc1wRMcvBUSOaMKBjfg=="], + + "@pagefind/default-ui": ["@pagefind/default-ui@1.2.0", "", {}, "sha512-MDSbm34veKpzFP5eJMh/pcPdrOc4FZKUsbpDsbdjSLC2ZeuTjsfDBNu9MGZaNUvGKUdlKk5JozQkVO/dzdSxrQ=="], + + "@pagefind/linux-arm64": ["@pagefind/linux-arm64@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wVtLOlF9AUrwLovP9ZSEKOYnwIVrrxId4I2Mz02Zxm3wbUIJyx8wHf6LyEf7W7mJ6rEjW5jtavKAbngKCAaicg=="], + + "@pagefind/linux-x64": ["@pagefind/linux-x64@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Lo5aO2bA++sQTeEWzK5WKr3KU0yzVH5OnTY88apZfkgL4AVfXckH2mrOU8ouYKCLNPseIYTLFEdj0V5xjHQSwQ=="], + + "@pagefind/windows-x64": ["@pagefind/windows-x64@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-tGQcwQAb5Ndv7woc7lhH9iAdxOnTNsgCz8sEBbsASPB2A0uI8BWBmVdf2GFLQkYHqnnqYuun63sa+UOzB7Ah3g=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.3", "", { "os": "none", "cpu": "arm64" }, "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA=="], + + "@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="], + + "@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="], + + "@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="], + + "@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], + + "@types/acorn": ["@types/acorn@4.0.6", "", { "dependencies": { "@types/estree": "*" } }, "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.6.8", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.20.6", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/fontkit": ["@types/fontkit@2.0.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], + + "@types/ms": ["@types/ms@0.7.34", "", {}, "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="], + + "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], + + "@types/node": ["@types/node@22.10.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ=="], + + "@types/picomatch": ["@types/picomatch@2.3.3", "", {}, "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg=="], + + "@types/react": ["@types/react@19.0.1", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ=="], + + "@types/react-dom": ["@types/react-dom@19.0.2", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg=="], + + "@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.2.1", "", {}, "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.3.4", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug=="], + + "@volar/kit": ["@volar/kit@2.4.10", "", { "dependencies": { "@volar/language-service": "2.4.10", "@volar/typescript": "2.4.10", "typesafe-path": "^0.2.2", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "typescript": "*" } }, "sha512-ul+rLeO9RlFDgkY/FhPWMnpFqAsjvjkKz8VZeOY5YCJMwTblmmSBlNJtFNxSBx9t/k1q80nEthLyxiJ50ZbIAg=="], + + "@volar/language-core": ["@volar/language-core@2.4.10", "", { "dependencies": { "@volar/source-map": "2.4.10" } }, "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA=="], + + "@volar/language-server": ["@volar/language-server@2.4.10", "", { "dependencies": { "@volar/language-core": "2.4.10", "@volar/language-service": "2.4.10", "@volar/typescript": "2.4.10", "path-browserify": "^1.0.1", "request-light": "^0.7.0", "vscode-languageserver": "^9.0.1", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-odQsgrJh8hOXfxkSj/BSnpjThb2/KDhbxZnG/XAEx6E3QGDQv4hAOz9GWuKoNs0tkjgwphQGIwDMT1JYaTgRJw=="], + + "@volar/language-service": ["@volar/language-service@2.4.10", "", { "dependencies": { "@volar/language-core": "2.4.10", "vscode-languageserver-protocol": "^3.17.5", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" } }, "sha512-VxUiWS11rnRzakkqw5x1LPhsz+RBfD0CrrFarLGW2/voliYXEdCuSOM3r8JyNRvMvP4uwhD38ccAdTcULQEAIQ=="], + + "@volar/source-map": ["@volar/source-map@2.4.10", "", {}, "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA=="], + + "@volar/typescript": ["@volar/typescript@2.4.10", "", { "dependencies": { "@volar/language-core": "2.4.10", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw=="], + + "@vscode/emmet-helper": ["@vscode/emmet-helper@2.11.0", "", { "dependencies": { "emmet": "^2.4.3", "jsonc-parser": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.15.1", "vscode-uri": "^3.0.8" } }, "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw=="], + + "@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="], + + "astring": ["astring@1.9.0", "", { "bin": "bin/astring" }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], + + "astro": ["astro@5.14.1", "", { "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.7.3", "@astrojs/markdown-remark": "6.3.7", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.2.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.1", "deterministic-object-hash": "^2.0.2", "devalue": "^5.3.2", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.18", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.3.0", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.2", "shiki": "^3.12.0", "smol-toml": "^1.4.2", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.5.2", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.0", "vfile": "^6.0.3", "vite": "^6.3.6", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.24.6", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-gPa8NY7/lP8j8g81iy8UwANF3+aukKRWS68IlthZQNgykpg80ne6lbHOp6FErYycxQ1TUhgEfkXVDQZAoJx8Bg=="], + + "astro-d2": ["astro-d2@0.5.2", "", { "dependencies": { "unist-util-visit": "5.0.0" }, "peerDependencies": { "astro": ">=4.0.0" } }, "sha512-JN5kHalh/dQIZD6JG8y+WXY/j+K9NigyW7dLa+VbPeQnoNkRbvVms6847gqU5czojR7uzpArL7ug27vKae4lQg=="], + + "astro-expressive-code": ["astro-expressive-code@0.38.3", "", { "dependencies": { "rehype-expressive-code": "^0.38.3" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" } }, "sha512-Tvdc7RV0G92BbtyEOsfJtXU35w41CkM94fOAzxbQP67Wj5jArfserJ321FO4XA7WG9QMV0GIBmQq77NBIRDzpQ=="], + + "astro-remote": ["astro-remote@0.3.2", "", { "dependencies": { "entities": "^4.5.0", "marked": "^12.0.0", "marked-footnote": "^1.2.2", "marked-smartypants": "^1.1.6", "ultrahtml": "^1.5.3" } }, "sha512-Xwm6Y+ldQEnDB2l1WwVqeUs3QvUX8LtJWnovpXlf8xhpicPu159jXOhDbHZS9wilGO/+/nR67A1qskF8pDvdGQ=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], + + "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], + + "blob-to-buffer": ["blob-to-buffer@1.2.9", "", {}, "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="], + + "browserslist": ["browserslist@4.24.2", "", { "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.1" }, "bin": "cli.js" }, "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg=="], + + "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001687", "", {}, "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "chokidar": ["chokidar@4.0.1", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA=="], + + "ci-info": ["ci-info@4.3.0", "", {}, "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ=="], + + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], + + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + + "cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="], + + "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], + + "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="], + + "css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="], + + "css-selector-parser": ["css-selector-parser@3.0.5", "", {}, "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g=="], + + "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + + "css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "cssom": ["cssom@0.5.0", "", {}, "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.0.2", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], + + "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], + + "devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dfa": ["dfa@1.2.0", "", {}, "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q=="], + + "diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], + + "direction": ["direction@2.0.1", "", { "bin": "cli.js" }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="], + + "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.1.0", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA=="], + + "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.71", "", {}, "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA=="], + + "emmet": ["emmet@2.4.11", "", { "dependencies": { "@emmetio/abbreviation": "^2.3.3", "@emmetio/css-abbreviation": "^2.1.8" } }, "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], + + "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="], + + "esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], + + "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "estree-util-scope": ["estree-util-scope@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0" } }, "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ=="], + + "estree-util-to-js": ["estree-util-to-js@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "astring": "^1.8.0", "source-map": "^0.7.0" } }, "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg=="], + + "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "expressive-code": ["expressive-code@0.38.3", "", { "dependencies": { "@expressive-code/core": "^0.38.3", "@expressive-code/plugin-frames": "^0.38.3", "@expressive-code/plugin-shiki": "^0.38.3", "@expressive-code/plugin-text-markers": "^0.38.3" } }, "sha512-COM04AiUotHCKJgWdn7NtW2lqu8OW8owAidMpkXt1qxrZ9Q2iC7+tok/1qIn2ocGnczvr9paIySgGnEwFeEQ8Q=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="], + + "fast-uri": ["fast-uri@3.0.3", "", {}, "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw=="], + + "fast-xml-parser": ["fast-xml-parser@4.5.0", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg=="], + + "fastq": ["fastq@1.17.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], + + "fontace": ["fontace@0.3.0", "", { "dependencies": { "@types/fontkit": "^2.0.8", "fontkit": "^2.0.4" } }, "sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg=="], + + "fontkit": ["fontkit@2.0.4", "", { "dependencies": { "@swc/helpers": "^0.5.12", "brotli": "^1.3.2", "clone": "^2.1.2", "dfa": "^1.2.0", "fast-deep-equal": "^3.1.3", "restructure": "^3.0.0", "tiny-inflate": "^1.0.3", "unicode-properties": "^1.4.0", "unicode-trie": "^2.0.0" } }, "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g=="], + + "framer-motion": ["framer-motion@11.14.4", "", { "dependencies": { "motion-dom": "^11.14.3", "motion-utils": "^11.14.3", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-NQuzr9JbeJDMQmy0FFLhLzk9h1kAjVC1tGE/HY4ubF02B95EBm2lpA21LE3Od/OpXqXgp0zl5Hdqu25hliBRsA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="], + + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + + "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], + + "h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="], + + "hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="], + + "hast-util-format": ["hast-util-format@1.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0", "hast-util-phrasing": "^3.0.0", "hast-util-whitespace": "^3.0.0", "html-whitespace-sensitive-tag-names": "^3.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA=="], + + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^6.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A=="], + + "hast-util-has-property": ["hast-util-has-property@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA=="], + + "hast-util-is-body-ok-link": ["hast-util-is-body-ok-link@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + + "hast-util-minify-whitespace": ["hast-util-minify-whitespace@1.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-is-element": "^3.0.0", "hast-util-whitespace": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-phrasing": ["hast-util-phrasing@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-has-property": "^3.0.0", "hast-util-is-body-ok-link": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ=="], + + "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + + "hast-util-select": ["hast-util-select@6.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "bcp-47-match": "^2.0.0", "comma-separated-tokens": "^2.0.0", "css-selector-parser": "^3.0.0", "devlop": "^1.0.0", "direction": "^2.0.0", "hast-util-has-property": "^3.0.0", "hast-util-to-string": "^3.0.0", "hast-util-whitespace": "^3.0.0", "nth-check": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ=="], + + "hast-util-to-estree": ["hast-util-to-estree@3.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "style-to-object": "^0.4.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.2", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "style-to-object": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg=="], + + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="], + + "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], + + "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@9.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="], + + "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "html-whitespace-sensitive-tag-names": ["html-whitespace-sensitive-tag-names@3.0.1", "", {}, "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA=="], + + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + + "is-absolute-url": ["is-absolute-url@4.0.1", "", {}, "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], + + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": "cli.js" }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": "cli.js" }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.0.2", "", { "bin": "bin/jsesc" }, "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="], + + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "linkedom": ["linkedom@0.14.26", "", { "dependencies": { "css-select": "^5.1.0", "cssom": "^0.5.0", "html-escaper": "^3.0.3", "htmlparser2": "^8.0.1", "uhyphen": "^0.2.0" } }, "sha512-mK6TrydfFA7phrnp+1j57ycBwFI5bGSW6YXlw9acHoqF+mP/y+FooEYYyniOt5Ot57FSKB3iwmnuQ1UUyNLm5A=="], + + "lite-youtube-embed": ["lite-youtube-embed@0.3.3", "", {}, "sha512-gFfVVnj6NRjxVfJKo3qoLtpi0v5mn3AcR4eKD45wrxQuxzveFJUb+7Cr6uV6n+DjO8X3p0UzPPquhGt0H/y+NA=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + + "magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="], + + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "marked": ["marked@12.0.2", "", { "bin": "bin/marked.js" }, "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="], + + "marked-footnote": ["marked-footnote@1.2.4", "", { "peerDependencies": { "marked": ">=7.0.0" } }, "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q=="], + + "marked-plaintify": ["marked-plaintify@1.0.1", "", { "peerDependencies": { "marked": ">=7.0.0" } }, "sha512-KQhxtuVWf3Ij3YMiW4ArlgNOVmzOAlP0o/upsu2+h7Q4TCAwG4UvkYTteZF2sDDomXQnNSLmfyAhoR0gx2Orgw=="], + + "marked-smartypants": ["marked-smartypants@1.1.9", "", { "dependencies": { "smartypants": "^0.2.2" }, "peerDependencies": { "marked": ">=4 <16" } }, "sha512-VPeuaUr5IWptI7nJdgQ9ugrLWYGv13NdzEXTtKY3cmB4aRWOI2RzhLlf+xQp6Wnob9SAPO2sNVlfSJr+nflk/A=="], + + "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], + + "mdast-util-directive": ["mdast-util-directive@3.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.1.3", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromark": ["micromark@4.0.1", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.2", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w=="], + + "micromark-extension-directive": ["micromark-extension-directive@3.0.2", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ=="], + + "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.1", "", { "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg=="], + + "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="], + + "micromark-extension-mdxjs": ["micromark-extension-mdxjs@3.0.0", "", { "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", "micromark-extension-mdx-expression": "^3.0.0", "micromark-extension-mdx-jsx": "^3.0.0", "micromark-extension-mdx-md": "^2.0.0", "micromark-extension-mdxjs-esm": "^3.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ=="], + + "micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.2", "", { "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.0.3", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.1", "", {}, "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "motion": ["motion@11.14.4", "", { "dependencies": { "framer-motion": "^11.14.4", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-ZIaw6ko88B8rSmBEFzqbTCQMbo9xMu8f4PSXSGdb9DTDy8R0sXcbwMEKmTEYkrj9TmZ4n+Ebd0KYjtqHgzRkRQ=="], + + "motion-dom": ["motion-dom@11.14.3", "", {}, "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA=="], + + "motion-utils": ["motion-utils@11.14.3", "", {}, "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="], + + "nanoid": ["nanoid@3.3.8", "", { "bin": "bin/nanoid.cjs" }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], + + "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], + + "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "node-mock-http": ["node-mock-http@1.0.3", "", {}, "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog=="], + + "node-releases": ["node-releases@2.0.18", "", {}, "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "ofetch": ["ofetch@1.4.1", "", { "dependencies": { "destr": "^2.0.3", "node-fetch-native": "^1.6.4", "ufo": "^1.5.4" } }, "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.3", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg=="], + + "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], + + "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], + + "p-timeout": ["p-timeout@6.1.3", "", {}, "sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw=="], + + "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="], + + "pagefind": ["pagefind@1.2.0", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.2.0", "@pagefind/darwin-x64": "1.2.0", "@pagefind/linux-arm64": "1.2.0", "@pagefind/linux-x64": "1.2.0", "@pagefind/windows-x64": "1.2.0" }, "bin": "lib/runner/bin.cjs" }, "sha512-sFVv5/x73qCp9KlLHv8/uWDv7rG1tsWcG9MuXc5YTrXIrb8c1Gshm9oc5rMLXNZILXUWai8WczqaK4jjroEzng=="], + + "pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + + "parse-entities": ["parse-entities@4.0.1", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w=="], + + "parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], + + "parse5": ["parse5@7.2.1", "", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="], + + "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="], + + "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], + + "prettier": ["prettier@2.8.7", "", { "bin": "bin-prettier.js" }, "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], + + "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="], + + "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="], + + "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], + + "readdirp": ["readdirp@4.0.2", "", {}, "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA=="], + + "recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="], + + "recma-jsx": ["recma-jsx@1.0.0", "", { "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" } }, "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q=="], + + "recma-parse": ["recma-parse@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "esast-util-from-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ=="], + + "recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="], + + "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + + "regex": ["regex@6.0.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="], + + "rehype-expressive-code": ["rehype-expressive-code@0.38.3", "", { "dependencies": { "expressive-code": "^0.38.3" } }, "sha512-RYSSDkMBikoTbycZPkcWp6ELneANT4eTpND1DSRJ6nI2eVFUwTBDCvE2vO6jOOTaavwnPiydi4i/87NRyjpdOA=="], + + "rehype-format": ["rehype-format@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-format": "^1.0.0" } }, "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ=="], + + "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], + + "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + + "rehype-recma": ["rehype-recma@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" } }, "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw=="], + + "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], + + "remark-directive": ["remark-directive@3.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^3.0.0", "unified": "^11.0.0" } }, "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-mdx": ["remark-mdx@3.1.0", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-smartypants": ["remark-smartypants@3.0.2", "", { "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "request-light": ["request-light@0.7.0", "", {}, "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="], + + "retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="], + + "retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="], + + "retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="], + + "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], + + "reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="], + + "rollup": ["rollup@4.28.1", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.28.1", "@rollup/rollup-android-arm64": "4.28.1", "@rollup/rollup-darwin-arm64": "4.28.1", "@rollup/rollup-darwin-x64": "4.28.1", "@rollup/rollup-freebsd-arm64": "4.28.1", "@rollup/rollup-freebsd-x64": "4.28.1", "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", "@rollup/rollup-linux-arm-musleabihf": "4.28.1", "@rollup/rollup-linux-arm64-gnu": "4.28.1", "@rollup/rollup-linux-arm64-musl": "4.28.1", "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", "@rollup/rollup-linux-riscv64-gnu": "4.28.1", "@rollup/rollup-linux-s390x-gnu": "4.28.1", "@rollup/rollup-linux-x64-gnu": "4.28.1", "@rollup/rollup-linux-x64-musl": "4.28.1", "@rollup/rollup-win32-arm64-msvc": "4.28.1", "@rollup/rollup-win32-ia32-msvc": "4.28.1", "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" }, "bin": "dist/bin/rollup" }, "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="], + + "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + + "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + + "shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="], + + "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "sitemap": ["sitemap@8.0.0", "", { "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.2.4" }, "bin": "dist/cli.js" }, "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A=="], + + "smartypants": ["smartypants@0.2.2", "", { "bin": { "smartypants": "bin/smartypants.js", "smartypantsu": "bin/smartypantsu.js" } }, "sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q=="], + + "smol-toml": ["smol-toml@1.4.2", "", {}, "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "starlight-blog": ["starlight-blog@0.15.0", "", { "dependencies": { "@astrojs/mdx": "3.1.9", "@astrojs/rss": "4.0.5", "astro-remote": "0.3.2", "github-slugger": "2.0.0", "marked": "12.0.2", "marked-plaintify": "1.0.1", "ultrahtml": "1.5.3" }, "peerDependencies": { "@astrojs/starlight": ">=0.28.3" } }, "sha512-zNs8Z6eJAqurarD3up8kosfAXNDiZEaweI7S7vRfeTN6eOZijPa3QVifJbYK8n6sdX4W3liEqPkDF+PIz3TxxQ=="], + + "starlight-image-zoom": ["starlight-image-zoom@0.9.0", "", { "dependencies": { "rehype-raw": "7.0.0", "unist-util-visit": "5.0.0", "unist-util-visit-parents": "6.0.1" }, "peerDependencies": { "@astrojs/starlight": ">=0.22.0" } }, "sha512-XG87T80g5hsT6dvtNk9OKx0gD+M8lsloVTApQYnxdc3JD8lQBfu2kCsrwkyrwXZRtV7JRyd0PDHwx1UFmGmI1g=="], + + "starlight-links-validator": ["starlight-links-validator@0.13.4", "", { "dependencies": { "@types/picomatch": "2.3.3", "github-slugger": "2.0.0", "hast-util-from-html": "2.0.1", "hast-util-has-property": "3.0.0", "is-absolute-url": "4.0.1", "kleur": "4.1.5", "mdast-util-to-string": "4.0.0", "picomatch": "4.0.2", "unist-util-visit": "5.0.0" }, "peerDependencies": { "@astrojs/starlight": ">=0.15.0", "astro": ">=4.0.0" } }, "sha512-LdmLbJyPHVrSUhcuxiP3pJNnW8zRcOg/32C996Ic0LOCKbB8vylqHLvAMdIhT67FvEV4eAROun+2wTVU2J156A=="], + + "starlight-showcases": ["starlight-showcases@0.2.0", "", { "dependencies": { "@astro-community/astro-embed-twitter": "^0.5.4", "@astro-community/astro-embed-youtube": "^0.5.2" }, "peerDependencies": { "@astrojs/starlight": ">=0.23.0" } }, "sha512-YWJuTqArkUdVJV85VKZJ0BvKCQRu1SKtH/Cr5t6G/oIfI4IptWc92E7BmiuNnpuQ2U7TczTRidCYurPrbgQQVA=="], + + "stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], + + "strnum": ["strnum@1.0.5", "", {}, "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="], + + "style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-fest": ["type-fest@4.30.0", "", {}, "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA=="], + + "typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="], + + "typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="], + + "typescript-auto-import-cache": ["typescript-auto-import-cache@0.3.5", "", { "dependencies": { "semver": "^7.3.8" } }, "sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "uhyphen": ["uhyphen@0.2.0", "", {}, "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="], + + "ultrahtml": ["ultrahtml@1.5.3", "", {}, "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg=="], + + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "unicode-properties": ["unicode-properties@1.4.1", "", { "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" } }, "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg=="], + + "unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="], + + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="], + + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], + + "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], + + "unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + + "unstorage": ["unstorage@1.17.1", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.4.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + + "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="], + + "vite": ["vite@6.0.3", "", { "dependencies": { "esbuild": "^0.24.0", "postcss": "^8.4.49", "rollup": "^4.23.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx"], "bin": "bin/vite.js" }, "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw=="], + + "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], + + "volar-service-css": ["volar-service-css@0.0.62", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg=="], + + "volar-service-emmet": ["volar-service-emmet@0.0.62", "", { "dependencies": { "@emmetio/css-parser": "^0.4.0", "@emmetio/html-matcher": "^1.3.0", "@vscode/emmet-helper": "^2.9.3", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ=="], + + "volar-service-html": ["volar-service-html@0.0.62", "", { "dependencies": { "vscode-html-languageservice": "^5.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ=="], + + "volar-service-prettier": ["volar-service-prettier@0.0.62", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0", "prettier": "^2.2 || ^3.0" }, "optionalPeers": ["prettier"] }, "sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w=="], + + "volar-service-typescript": ["volar-service-typescript@0.0.62", "", { "dependencies": { "path-browserify": "^1.0.1", "semver": "^7.6.2", "typescript-auto-import-cache": "^0.3.3", "vscode-languageserver-textdocument": "^1.0.11", "vscode-nls": "^5.2.0", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g=="], + + "volar-service-typescript-twoslash-queries": ["volar-service-typescript-twoslash-queries@0.0.62", "", { "dependencies": { "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng=="], + + "volar-service-yaml": ["volar-service-yaml@0.0.62", "", { "dependencies": { "vscode-uri": "^3.0.8", "yaml-language-server": "~1.15.0" }, "peerDependencies": { "@volar/language-service": "~2.4.0" } }, "sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig=="], + + "vscode-css-languageservice": ["vscode-css-languageservice@6.3.2", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "3.17.5", "vscode-uri": "^3.0.8" } }, "sha512-GEpPxrUTAeXWdZWHev1OJU9lz2Q2/PPBxQ2TIRmLGvQiH3WZbqaNoute0n0ewxlgtjzTW3AKZT+NHySk5Rf4Eg=="], + + "vscode-html-languageservice": ["vscode-html-languageservice@5.3.1", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.0.8" } }, "sha512-ysUh4hFeW/WOWz/TO9gm08xigiSsV/FOAZ+DolgJfeLftna54YdmZ4A+lIn46RbdO3/Qv5QHTn1ZGqmrXQhZyA=="], + + "vscode-json-languageservice": ["vscode-json-languageservice@4.1.8", "", { "dependencies": { "jsonc-parser": "^3.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2" } }, "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg=="], + + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], + + "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], + + "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + + "vscode-nls": ["vscode-nls@5.2.0", "", {}, "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng=="], + + "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], + + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + + "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], + + "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yaml": ["yaml@2.6.1", "", { "bin": "bin.mjs" }, "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg=="], + + "yaml-language-server": ["yaml-language-server@1.15.0", "", { "dependencies": { "ajv": "^8.11.0", "lodash": "4.17.21", "request-light": "^0.5.7", "vscode-json-languageservice": "4.1.8", "vscode-languageserver": "^7.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.2", "yaml": "2.2.2" }, "optionalDependencies": { "prettier": "2.8.7" }, "bin": "bin/yaml-language-server" }, "sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw=="], + + "yargs": ["yargs@17.7.2", "", { "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" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@1.1.1", "", {}, "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g=="], + + "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], + + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="], + + "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@astrojs/language-server/@astrojs/compiler": ["@astrojs/compiler@2.10.3", "", {}, "sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw=="], + + "@astrojs/sitemap/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], + + "@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/traverse/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "@expressive-code/core/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="], + + "@expressive-code/plugin-shiki/shiki": ["shiki@1.24.1", "", { "dependencies": { "@shikijs/core": "1.24.1", "@shikijs/engine-javascript": "1.24.1", "@shikijs/engine-oniguruma": "1.24.1", "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "sha512-/qByWMg05+POb63c/OvnrU17FcCUa34WU4F6FCrd/mjDPEDPl8YUNRkRMbo8l3iYMLydfCgxi1r37JFoSw8A4A=="], + + "@mdx-js/mdx/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "astro/sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="], + + "astro/ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], + + "astro/vite": ["vite@6.3.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA=="], + + "boxen/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "esast-util-from-js/acorn": ["acorn@8.14.0", "", { "bin": "bin/acorn" }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + + "estree-util-to-js/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], + + "gray-matter/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], + + "hast-util-to-estree/style-to-object": ["style-to-object@0.4.4", "", { "dependencies": { "inline-style-parser": "0.1.1" } }, "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg=="], + + "hast-util-to-html/property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "magic-string/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "micromark/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "micromark-extension-mdxjs/acorn": ["acorn@8.14.0", "", { "bin": "bin/acorn" }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "rehype-stringify/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="], + + "sharp/semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "sitemap/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], + + "starlight-blog/@astrojs/mdx": ["@astrojs/mdx@3.1.9", "", { "dependencies": { "@astrojs/markdown-remark": "5.3.0", "@mdx-js/mdx": "^3.1.0", "acorn": "^8.14.0", "es-module-lexer": "^1.5.4", "estree-util-visit": "^2.0.0", "gray-matter": "^4.0.3", "hast-util-to-html": "^9.0.3", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "remark-smartypants": "^3.0.2", "source-map": "^0.7.4", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^4.8.0" } }, "sha512-3jPD4Bff6lIA20RQoonnZkRtZ9T3i0HFm6fcDF7BMsKIZ+xBP2KXzQWiuGu62lrVCmU612N+SQVGl5e0fI+zWg=="], + + "starlight-links-validator/hast-util-from-html": ["hast-util-from-html@2.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g=="], + + "starlight-links-validator/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "typescript-auto-import-cache/semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "unstorage/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "vite/esbuild": ["esbuild@0.24.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.0", "@esbuild/android-arm": "0.24.0", "@esbuild/android-arm64": "0.24.0", "@esbuild/android-x64": "0.24.0", "@esbuild/darwin-arm64": "0.24.0", "@esbuild/darwin-x64": "0.24.0", "@esbuild/freebsd-arm64": "0.24.0", "@esbuild/freebsd-x64": "0.24.0", "@esbuild/linux-arm": "0.24.0", "@esbuild/linux-arm64": "0.24.0", "@esbuild/linux-ia32": "0.24.0", "@esbuild/linux-loong64": "0.24.0", "@esbuild/linux-mips64el": "0.24.0", "@esbuild/linux-ppc64": "0.24.0", "@esbuild/linux-riscv64": "0.24.0", "@esbuild/linux-s390x": "0.24.0", "@esbuild/linux-x64": "0.24.0", "@esbuild/netbsd-x64": "0.24.0", "@esbuild/openbsd-arm64": "0.24.0", "@esbuild/openbsd-x64": "0.24.0", "@esbuild/sunos-x64": "0.24.0", "@esbuild/win32-arm64": "0.24.0", "@esbuild/win32-ia32": "0.24.0", "@esbuild/win32-x64": "0.24.0" }, "bin": "bin/esbuild" }, "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ=="], + + "volar-service-typescript/semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], + + "vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], + + "yaml-language-server/vscode-languageserver": ["vscode-languageserver@7.0.0", "", { "dependencies": { "vscode-languageserver-protocol": "3.16.0" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw=="], + + "yaml-language-server/vscode-languageserver-types": ["vscode-languageserver-types@3.16.0", "", {}, "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="], + + "yaml-language-server/yaml": ["yaml@2.2.2", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@1.24.1", "", { "dependencies": { "@shikijs/engine-javascript": "1.24.1", "@shikijs/engine-oniguruma": "1.24.1", "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.3" } }, "sha512-3q/9oarMVcLqJ+NQOdKL40dJVq/UKCsiWXz3QRQPBglHqa8dDJ0p6TuMuk2gHphy5FZcvFtg4UHBgpW0JtZ8+A=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.24.1", "", { "dependencies": { "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0", "oniguruma-to-es": "0.7.0" } }, "sha512-lNgUSHYDYaQ6daj4lJJqcY2Ru9LgHwpFoposJkRVRPh21Yg4kaPFRhzaWoSg3PliwcDOpDuMy3xsmQaJp201Fg=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.24.1", "", { "dependencies": { "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0" } }, "sha512-KdrTIBIONWd+Xs61eh8HdIpfigtrseat9dpARvaOe2x0g/FNTbwbkGr3y92VSOVD1XotzEskh3v/nCzyWjkf7g=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@1.24.1", "", { "dependencies": { "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "sha512-ZwZFbShFY/APfKNt3s9Gv8rhTm29GodSKsOW66X6N+HGsZuaHalE1VUEX4fv93UXHTZTLjb3uxn63F96RhGfXw=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@9.3.0", "", {}, "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA=="], + + "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "astro/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="], + + "astro/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="], + + "astro/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="], + + "astro/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="], + + "astro/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="], + + "astro/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="], + + "astro/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="], + + "astro/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="], + + "astro/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="], + + "astro/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="], + + "astro/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="], + + "astro/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="], + + "astro/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="], + + "astro/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="], + + "astro/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="], + + "astro/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="], + + "astro/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.4", "", { "dependencies": { "@emnapi/runtime": "^1.5.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="], + + "astro/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="], + + "astro/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.4", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="], + + "astro/sharp/detect-libc": ["detect-libc@2.1.1", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="], + + "astro/vite/postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "astro/vite/rollup": ["rollup@4.52.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.3", "@rollup/rollup-android-arm64": "4.52.3", "@rollup/rollup-darwin-arm64": "4.52.3", "@rollup/rollup-darwin-x64": "4.52.3", "@rollup/rollup-freebsd-arm64": "4.52.3", "@rollup/rollup-freebsd-x64": "4.52.3", "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", "@rollup/rollup-linux-arm-musleabihf": "4.52.3", "@rollup/rollup-linux-arm64-gnu": "4.52.3", "@rollup/rollup-linux-arm64-musl": "4.52.3", "@rollup/rollup-linux-loong64-gnu": "4.52.3", "@rollup/rollup-linux-ppc64-gnu": "4.52.3", "@rollup/rollup-linux-riscv64-gnu": "4.52.3", "@rollup/rollup-linux-riscv64-musl": "4.52.3", "@rollup/rollup-linux-s390x-gnu": "4.52.3", "@rollup/rollup-linux-x64-gnu": "4.52.3", "@rollup/rollup-linux-x64-musl": "4.52.3", "@rollup/rollup-openharmony-arm64": "4.52.3", "@rollup/rollup-win32-arm64-msvc": "4.52.3", "@rollup/rollup-win32-ia32-msvc": "4.52.3", "@rollup/rollup-win32-x64-gnu": "4.52.3", "@rollup/rollup-win32-x64-msvc": "4.52.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A=="], + + "boxen/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + + "boxen/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "hast-util-to-estree/style-to-object/inline-style-parser": ["inline-style-parser@0.1.1", "", {}, "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@5.3.0", "", { "dependencies": { "@astrojs/prism": "3.1.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^1.22.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg=="], + + "starlight-blog/@astrojs/mdx/@mdx-js/mdx": ["@mdx-js/mdx@3.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw=="], + + "starlight-blog/@astrojs/mdx/acorn": ["acorn@8.14.0", "", { "bin": "bin/acorn" }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + + "starlight-blog/@astrojs/mdx/es-module-lexer": ["es-module-lexer@1.5.4", "", {}, "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw=="], + + "starlight-blog/@astrojs/mdx/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="], + + "starlight-blog/@astrojs/mdx/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], + + "starlight-blog/@astrojs/mdx/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], + + "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw=="], + + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.0", "", { "os": "android", "cpu": "arm" }, "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew=="], + + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.0", "", { "os": "android", "cpu": "arm64" }, "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w=="], + + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.0", "", { "os": "android", "cpu": "x64" }, "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ=="], + + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw=="], + + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA=="], + + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA=="], + + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ=="], + + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.0", "", { "os": "linux", "cpu": "arm" }, "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw=="], + + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g=="], + + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA=="], + + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.0", "", { "os": "linux", "cpu": "none" }, "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g=="], + + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.0", "", { "os": "linux", "cpu": "none" }, "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA=="], + + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ=="], + + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.0", "", { "os": "linux", "cpu": "none" }, "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw=="], + + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g=="], + + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.0", "", { "os": "linux", "cpu": "x64" }, "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA=="], + + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.0", "", { "os": "none", "cpu": "x64" }, "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg=="], + + "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg=="], + + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q=="], + + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA=="], + + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA=="], + + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw=="], + + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA=="], + + "widest-line/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + + "widest-line/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], + + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "yaml-language-server/vscode-languageserver/vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.16.0", "", { "dependencies": { "vscode-jsonrpc": "6.0.0", "vscode-languageserver-types": "3.16.0" } }, "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@0.7.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.0.2", "regex-recursion": "^4.3.0" } }, "sha512-HRaRh09cE0gRS3+wi2zxekB+I5L8C/gN60S+vb11eADHUaB/q4u8wGGOX3GvwvitG8ixaeycZfeoyruKQzUgNg=="], + + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "astro/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + + "astro/vite/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "astro/vite/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.3", "", { "os": "android", "cpu": "arm" }, "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw=="], + + "astro/vite/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.3", "", { "os": "android", "cpu": "arm64" }, "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw=="], + + "astro/vite/rollup/@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg=="], + + "astro/vite/rollup/@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A=="], + + "astro/vite/rollup/@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ=="], + + "astro/vite/rollup/@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A=="], + + "astro/vite/rollup/@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.3", "", { "os": "linux", "cpu": "arm" }, "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA=="], + + "astro/vite/rollup/@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.3", "", { "os": "linux", "cpu": "arm" }, "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA=="], + + "astro/vite/rollup/@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ=="], + + "astro/vite/rollup/@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw=="], + + "astro/vite/rollup/@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.3", "", { "os": "linux", "cpu": "none" }, "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg=="], + + "astro/vite/rollup/@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg=="], + + "astro/vite/rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA=="], + + "astro/vite/rollup/@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.3", "", { "os": "linux", "cpu": "x64" }, "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw=="], + + "astro/vite/rollup/@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA=="], + + "astro/vite/rollup/@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g=="], + + "astro/vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA=="], + + "astro/vite/rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.1.0", "", { "dependencies": { "prismjs": "^1.29.0" } }, "sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/remark-rehype": ["remark-rehype@11.1.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki": ["shiki@1.24.1", "", { "dependencies": { "@shikijs/core": "1.24.1", "@shikijs/engine-javascript": "1.24.1", "@shikijs/engine-oniguruma": "1.24.1", "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "sha512-/qByWMg05+POb63c/OvnrU17FcCUa34WU4F6FCrd/mjDPEDPl8YUNRkRMbo8l3iYMLydfCgxi1r37JFoSw8A4A=="], + + "starlight-blog/@astrojs/mdx/@mdx-js/mdx/remark-rehype": ["remark-rehype@11.1.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="], + + "widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "yaml-language-server/vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@6.0.0", "", {}, "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ=="], + + "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@4.3.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism/prismjs": ["prismjs@1.29.0", "", {}, "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@1.24.1", "", { "dependencies": { "@shikijs/engine-javascript": "1.24.1", "@shikijs/engine-oniguruma": "1.24.1", "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.3" } }, "sha512-3q/9oarMVcLqJ+NQOdKL40dJVq/UKCsiWXz3QRQPBglHqa8dDJ0p6TuMuk2gHphy5FZcvFtg4UHBgpW0JtZ8+A=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.24.1", "", { "dependencies": { "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0", "oniguruma-to-es": "0.7.0" } }, "sha512-lNgUSHYDYaQ6daj4lJJqcY2Ru9LgHwpFoposJkRVRPh21Yg4kaPFRhzaWoSg3PliwcDOpDuMy3xsmQaJp201Fg=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.24.1", "", { "dependencies": { "@shikijs/types": "1.24.1", "@shikijs/vscode-textmate": "^9.3.0" } }, "sha512-KdrTIBIONWd+Xs61eh8HdIpfigtrseat9dpARvaOe2x0g/FNTbwbkGr3y92VSOVD1XotzEskh3v/nCzyWjkf7g=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@1.24.1", "", { "dependencies": { "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, "sha512-ZwZFbShFY/APfKNt3s9Gv8rhTm29GodSKsOW66X6N+HGsZuaHalE1VUEX4fv93UXHTZTLjb3uxn63F96RhGfXw=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@9.3.0", "", {}, "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@0.7.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.0.2", "regex-recursion": "^4.3.0" } }, "sha512-HRaRh09cE0gRS3+wi2zxekB+I5L8C/gN60S+vb11eADHUaB/q4u8wGGOX3GvwvitG8ixaeycZfeoyruKQzUgNg=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ=="], + + "starlight-blog/@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@4.3.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A=="], + } +} diff --git a/docs/create-dirs.ps1 b/docs/create-dirs.ps1 new file mode 100644 index 000000000..c0ce83c15 --- /dev/null +++ b/docs/create-dirs.ps1 @@ -0,0 +1,33 @@ +$dirs = @( + "src/content/docs/quick-start", + "src/content/docs/concepts", + "src/content/docs/features/windows", + "src/content/docs/features/menus", + "src/content/docs/features/bindings", + "src/content/docs/features/events", + "src/content/docs/features/dialogs", + "src/content/docs/features/platform", + "src/content/docs/guides/dev", + "src/content/docs/guides/build", + "src/content/docs/guides/distribution", + "src/content/docs/guides/patterns", + "src/content/docs/guides/advanced", + "src/content/docs/reference/application", + "src/content/docs/reference/window", + "src/content/docs/reference/menu", + "src/content/docs/reference/events", + "src/content/docs/reference/dialogs", + "src/content/docs/reference/runtime", + "src/content/docs/reference/cli", + "src/content/docs/contributing/architecture", + "src/content/docs/contributing/codebase", + "src/content/docs/contributing/workflows", + "src/content/docs/migration" +) + +foreach ($dir in $dirs) { + New-Item -ItemType Directory -Force -Path $dir | Out-Null + Write-Host "Created: $dir" +} + +Write-Host "`nAll directories created successfully!" diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 000000000..abca7acf2 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,9344 @@ +{ + "name": "wails-docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "wails-docs", + "version": "0.0.1", + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/react": "^4.1.0", + "@astrojs/starlight": "^0.30.0", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "astro": "^5.0.0", + "astro-d2": "^0.5.0", + "framer-motion": "^11.14.4", + "motion": "^11.14.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sharp": "^0.33.5", + "starlight-blog": "^0.15.0", + "starlight-image-zoom": "^0.9.0", + "starlight-links-validator": "^0.13.4", + "starlight-showcases": "^0.2.0", + "typescript": "^5.7.2" + } + }, + "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/@astro-community/astro-embed-twitter": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-twitter/-/astro-embed-twitter-0.5.8.tgz", + "integrity": "sha512-O2ptQPw+DfipukK8czjJcTcyVgDsrs3OmrHbc3YmWRglaUTOpSTImzPo076POyNBSWjLaRKloul81DFiAMNjTA==", + "license": "MIT", + "dependencies": { + "@astro-community/astro-embed-utils": "^0.1.0" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/@astro-community/astro-embed-utils": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-utils/-/astro-embed-utils-0.1.3.tgz", + "integrity": "sha512-eiMO+vfCdE9GtW6qE7X5Xl6YCKZDCoXJEWqRofQcoC3GHjqN2/WhJlnaxNVRq3demSO03UNtho57Em5p7o7AOA==", + "license": "MIT", + "dependencies": { + "linkedom": "^0.14.26" + } + }, + "node_modules/@astro-community/astro-embed-youtube": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@astro-community/astro-embed-youtube/-/astro-embed-youtube-0.5.6.tgz", + "integrity": "sha512-/mRfCl/eTBUz0kmjD1psOy0qoDDBorVp0QumUacjFcIkBullYtbeFQ2ZGZ+3N/tA6cR/OIyzr2QA4dQXlY6USg==", + "license": "MIT", + "dependencies": { + "lite-youtube-embed": "^0.3.3" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/@astrojs/check": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.9.4.tgz", + "integrity": "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA==", + "license": "MIT", + "dependencies": { + "@astrojs/language-server": "^2.15.0", + "chokidar": "^4.0.1", + "kleur": "^4.1.5", + "yargs": "^17.7.2" + }, + "bin": { + "astro-check": "dist/bin.js" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.0.tgz", + "integrity": "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.4.tgz", + "integrity": "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw==", + "license": "MIT" + }, + "node_modules/@astrojs/language-server": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@astrojs/language-server/-/language-server-2.15.4.tgz", + "integrity": "sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.10.3", + "@astrojs/yaml2ts": "^0.2.2", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@volar/kit": "~2.4.7", + "@volar/language-core": "~2.4.7", + "@volar/language-server": "~2.4.7", + "@volar/language-service": "~2.4.7", + "fast-glob": "^3.2.12", + "muggle-string": "^0.4.1", + "volar-service-css": "0.0.62", + "volar-service-emmet": "0.0.62", + "volar-service-html": "0.0.62", + "volar-service-prettier": "0.0.62", + "volar-service-typescript": "0.0.62", + "volar-service-typescript-twoslash-queries": "0.0.62", + "volar-service-yaml": "0.0.62", + "vscode-html-languageservice": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "bin": { + "astro-ls": "bin/nodeServer.js" + }, + "peerDependencies": { + "prettier": "^3.0.0", + "prettier-plugin-astro": ">=0.11.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + } + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz", + "integrity": "sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg==", + "license": "MIT", + "dependencies": { + "@astrojs/prism": "3.1.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.1", + "remark-smartypants": "^3.0.2", + "shiki": "^1.22.0", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-3.1.9.tgz", + "integrity": "sha512-3jPD4Bff6lIA20RQoonnZkRtZ9T3i0HFm6fcDF7BMsKIZ+xBP2KXzQWiuGu62lrVCmU612N+SQVGl5e0fI+zWg==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "5.3.0", + "@mdx-js/mdx": "^3.1.0", + "acorn": "^8.14.0", + "es-module-lexer": "^1.5.4", + "estree-util-visit": "^2.0.0", + "gray-matter": "^4.0.3", + "hast-util-to-html": "^9.0.3", + "kleur": "^4.1.5", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.0", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=21.0.0" + }, + "peerDependencies": { + "astro": "^4.8.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.1.0.tgz", + "integrity": "sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.29.0" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=21.0.0" + } + }, + "node_modules/@astrojs/react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.1.0.tgz", + "integrity": "sha512-8F0ncvcCexVeQZMwPouLSFuzCK1KXUIYQ57lW3ZG2p7B5DGAajXGanb/CGF7MMSpX8Z0t9sELQqLHOCV/+78Ig==", + "license": "MIT", + "dependencies": { + "@vitejs/plugin-react": "^4.3.4", + "ultrahtml": "^1.5.3", + "vite": "^6.0.1" + }, + "engines": { + "node": "^18.17.1 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", + "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@astrojs/rss": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.5.tgz", + "integrity": "sha512-IyJVL6z09AQtxbgLaAwebT3T5YKe4oTHDesqydJv1KLHw+zEzzMCFuuNsEyxjiqu7df9+DDCpDXLj/WRiEUXvw==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^4.2.7", + "kleur": "^4.1.5" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.2.1.tgz", + "integrity": "sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.30.6.tgz", + "integrity": "sha512-/AoLXjPPD1MqixkTd2Lp3qahSzfCejePWHZQ3+fDjj1CuXI7Gjrr5bR3zNV0b9tynloPAIBM0HOyBNEGAo9uAQ==", + "license": "MIT", + "dependencies": { + "@astrojs/mdx": "^4.0.1", + "@astrojs/sitemap": "^3.1.6", + "@pagefind/default-ui": "^1.0.3", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.38.3", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.0.3", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/starlight/node_modules/@astrojs/markdown-remark": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.8.tgz", + "integrity": "sha512-uFNyFWadnULWK2cOw4n0hLKeu+xaVWeuECdP10cQ3K2fkybtTlhb7J7TcScdjmS8Yps7oje9S/ehYMfZrhrgCg==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.4", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.13.0", + "smol-toml": "^1.4.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/starlight/node_modules/@astrojs/mdx": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.10.tgz", + "integrity": "sha512-2T5+XIr7PMqMeXhRofXY5NlY4lA0Km+wkfsqmr9lq5KXUHpGlKPQ9dlDZJP9E/CtljJyEBNS17zq66LrIJ1tiQ==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.8", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.15.0", + "es-module-lexer": "^1.7.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "picocolors": "^1.1.1", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/starlight/node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/starlight/node_modules/@shikijs/core": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz", + "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@astrojs/starlight/node_modules/@shikijs/engine-javascript": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", + "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@astrojs/starlight/node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@astrojs/starlight/node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@astrojs/starlight/node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@astrojs/starlight/node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/@astrojs/starlight/node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@astrojs/starlight/node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@astrojs/starlight/node_modules/shiki": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz", + "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.15.0", + "@shikijs/engine-javascript": "3.15.0", + "@shikijs/engine-oniguruma": "3.15.0", + "@shikijs/langs": "3.15.0", + "@shikijs/themes": "3.15.0", + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/yaml2ts": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz", + "integrity": "sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.5.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "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" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "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.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "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.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@capsizecss/unpack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-3.0.1.tgz", + "integrity": "sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg==", + "license": "MIT", + "dependencies": { + "fontkit": "^2.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", + "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emmetio/abbreviation": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", + "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", + "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", + "license": "MIT", + "dependencies": { + "@emmetio/scanner": "^1.0.4" + } + }, + "node_modules/@emmetio/css-parser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.0.tgz", + "integrity": "sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw==", + "license": "MIT", + "dependencies": { + "@emmetio/stream-reader": "^2.2.0", + "@emmetio/stream-reader-utils": "^0.1.0" + } + }, + "node_modules/@emmetio/html-matcher": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", + "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", + "license": "ISC", + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz", + "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", + "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", + "license": "MIT" + }, + "node_modules/@emmetio/stream-reader-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", + "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", + "license": "MIT" + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz", + "integrity": "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.38.3.tgz", + "integrity": "sha512-s0/OtdRpBONwcn23O8nVwDNQqpBGKscysejkeBkwlIeHRLZWgiTVrusT5Idrdz1d8cW5wRk9iGsAIQmwDPXgJg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.38.3.tgz", + "integrity": "sha512-qL2oC6FplmHNQfZ8ZkTR64/wKo9x0c8uP2WDftR/ydwN/yhe1ed7ZWYb8r3dezxsls+tDokCnN4zYR594jbpvg==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.38.3.tgz", + "integrity": "sha512-kqHnglZeesqG3UKrb6e9Fq5W36AZ05Y9tCREmSN2lw8LVTqENIeCIkLDdWtQ5VoHlKqwUEQFTVlRehdwoY7Gmw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3", + "shiki": "^1.22.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.38.3.tgz", + "integrity": "sha512-dPK3+BVGTbTmGQGU3Fkj3jZ3OltWUAlxetMHI6limUGCWBCucZiwoZeFM/WmqQa71GyKRzhBT+iEov6kkz2xVA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "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/sourcemap-codec": { + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mdx-js/mdx": { + "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", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.2.0.tgz", + "integrity": "sha512-pHnPL2rm4xbe0LqV376g84hUIsVdy4PK6o2ACveo0DSGoC40eOIwPUPftnUPUinSdDWkkySaL5FT5r9hsXk0ZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.2.0.tgz", + "integrity": "sha512-q2tcnfvcRyx0GnrJoUQJ5bRpiFNtI8DZWM6a4/k8sNJxm2dbM1BnY5hUeo4MbDfpb64Qc1wRMcvBUSOaMKBjfg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.2.0.tgz", + "integrity": "sha512-MDSbm34veKpzFP5eJMh/pcPdrOc4FZKUsbpDsbdjSLC2ZeuTjsfDBNu9MGZaNUvGKUdlKk5JozQkVO/dzdSxrQ==", + "license": "MIT" + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.2.0.tgz", + "integrity": "sha512-wVtLOlF9AUrwLovP9ZSEKOYnwIVrrxId4I2Mz02Zxm3wbUIJyx8wHf6LyEf7W7mJ6rEjW5jtavKAbngKCAaicg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.2.0.tgz", + "integrity": "sha512-Lo5aO2bA++sQTeEWzK5WKr3KU0yzVH5OnTY88apZfkgL4AVfXckH2mrOU8ouYKCLNPseIYTLFEdj0V5xjHQSwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.2.0.tgz", + "integrity": "sha512-tGQcwQAb5Ndv7woc7lhH9iAdxOnTNsgCz8sEBbsASPB2A0uI8BWBmVdf2GFLQkYHqnnqYuun63sa+UOzB7Ah3g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.24.1.tgz", + "integrity": "sha512-3q/9oarMVcLqJ+NQOdKL40dJVq/UKCsiWXz3QRQPBglHqa8dDJ0p6TuMuk2gHphy5FZcvFtg4UHBgpW0JtZ8+A==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.24.1", + "@shikijs/engine-oniguruma": "1.24.1", + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.24.1.tgz", + "integrity": "sha512-lNgUSHYDYaQ6daj4lJJqcY2Ru9LgHwpFoposJkRVRPh21Yg4kaPFRhzaWoSg3PliwcDOpDuMy3xsmQaJp201Fg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-es": "0.7.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.1.tgz", + "integrity": "sha512-KdrTIBIONWd+Xs61eh8HdIpfigtrseat9dpARvaOe2x0g/FNTbwbkGr3y92VSOVD1XotzEskh3v/nCzyWjkf7g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/langs/node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/langs/node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@shikijs/themes": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/themes/node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/themes/node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@shikijs/types": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.1.tgz", + "integrity": "sha512-ZwZFbShFY/APfKNt3s9Gv8rhTm29GodSKsOW66X6N+HGsZuaHalE1VUEX4fv93UXHTZTLjb3uxn63F96RhGfXw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz", + "integrity": "sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "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": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fontkit": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/fontkit/-/fontkit-2.0.8.tgz", + "integrity": "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/picomatch": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.3.tgz", + "integrity": "sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.1.tgz", + "integrity": "sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz", + "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@volar/kit": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/kit/-/kit-2.4.10.tgz", + "integrity": "sha512-ul+rLeO9RlFDgkY/FhPWMnpFqAsjvjkKz8VZeOY5YCJMwTblmmSBlNJtFNxSBx9t/k1q80nEthLyxiJ50ZbIAg==", + "license": "MIT", + "dependencies": { + "@volar/language-service": "2.4.10", + "@volar/typescript": "2.4.10", + "typesafe-path": "^0.2.2", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", + "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.10" + } + }, + "node_modules/@volar/language-server": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-server/-/language-server-2.4.10.tgz", + "integrity": "sha512-odQsgrJh8hOXfxkSj/BSnpjThb2/KDhbxZnG/XAEx6E3QGDQv4hAOz9GWuKoNs0tkjgwphQGIwDMT1JYaTgRJw==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.10", + "@volar/language-service": "2.4.10", + "@volar/typescript": "2.4.10", + "path-browserify": "^1.0.1", + "request-light": "^0.7.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/language-service": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/language-service/-/language-service-2.4.10.tgz", + "integrity": "sha512-VxUiWS11rnRzakkqw5x1LPhsz+RBfD0CrrFarLGW2/voliYXEdCuSOM3r8JyNRvMvP4uwhD38ccAdTcULQEAIQ==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.10", + "vscode-languageserver-protocol": "^3.17.5", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", + "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", + "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.10", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", + "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", + "license": "MIT", + "dependencies": { + "emmet": "^2.4.3", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "license": "MIT" + }, + "node_modules/acorn": { + "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" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/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/ansi-align/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/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "5.15.6", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.6.tgz", + "integrity": "sha512-luLcw+FGkeUHYTfbmYjIWHB4T0D+3VSjCy8DKTXglJ2O3lU40AbwmPVBcnqhRnA1SneKzP5V5pzqjsHzUZ1+Rg==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.13.0", + "@astrojs/internal-helpers": "0.7.4", + "@astrojs/markdown-remark": "6.3.8", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^3.0.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "acorn": "^8.15.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.3.1", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.0.2", + "cssesc": "^3.0.0", + "debug": "^4.4.3", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.4.2", + "diff": "^5.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.7.0", + "esbuild": "^0.25.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.3.1", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.0", + "magic-string": "^0.30.21", + "magicast": "^0.5.1", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.1", + "package-manager-detector": "^1.5.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.3", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.3", + "shiki": "^3.15.0", + "smol-toml": "^1.4.2", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tsconfck": "^3.1.6", + "ultrahtml": "^1.6.0", + "unifont": "~0.6.0", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.17.2", + "vfile": "^6.0.3", + "vite": "^6.4.1", + "vitefu": "^1.1.1", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.3", + "zod": "^3.25.76", + "zod-to-json-schema": "^3.24.6", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0" + } + }, + "node_modules/astro-d2": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/astro-d2/-/astro-d2-0.5.2.tgz", + "integrity": "sha512-JN5kHalh/dQIZD6JG8y+WXY/j+K9NigyW7dLa+VbPeQnoNkRbvVms6847gqU5czojR7uzpArL7ug27vKae4lQg==", + "license": "MIT", + "dependencies": { + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "astro": ">=4.0.0" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.38.3.tgz", + "integrity": "sha512-Tvdc7RV0G92BbtyEOsfJtXU35w41CkM94fOAzxbQP67Wj5jArfserJ321FO4XA7WG9QMV0GIBmQq77NBIRDzpQ==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.38.3" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + } + }, + "node_modules/astro-remote": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/astro-remote/-/astro-remote-0.3.2.tgz", + "integrity": "sha512-Xwm6Y+ldQEnDB2l1WwVqeUs3QvUX8LtJWnovpXlf8xhpicPu159jXOhDbHZS9wilGO/+/nR67A1qskF8pDvdGQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0", + "marked": "^12.0.0", + "marked-footnote": "^1.2.2", + "marked-smartypants": "^1.1.6", + "ultrahtml": "^1.5.3" + }, + "engines": { + "node": ">=18.14.1" + } + }, + "node_modules/astro/node_modules/@astrojs/markdown-remark": { + "version": "6.3.8", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.8.tgz", + "integrity": "sha512-uFNyFWadnULWK2cOw4n0hLKeu+xaVWeuECdP10cQ3K2fkybtTlhb7J7TcScdjmS8Yps7oje9S/ehYMfZrhrgCg==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.4", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.2.0", + "js-yaml": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.13.0", + "smol-toml": "^1.4.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3" + } + }, + "node_modules/astro/node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/astro/node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/astro/node_modules/@shikijs/core": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz", + "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/astro/node_modules/@shikijs/engine-javascript": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", + "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/astro/node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/astro/node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/astro/node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/astro/node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/astro/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/astro/node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/astro/node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/astro/node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/astro/node_modules/shiki": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz", + "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.15.0", + "@shikijs/engine-javascript": "3.15.0", + "@shikijs/engine-oniguruma": "3.15.0", + "@shikijs/langs": "3.15.0", + "@shikijs/themes": "3.15.0", + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/astro/node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/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": { + "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/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "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": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "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==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", + "integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "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==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "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" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", + "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", + "license": "ISC" + }, + "node_modules/emmet": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "license": "MIT", + "workspaces": [ + "./packages/scanner", + "./packages/abbreviation", + "./packages/css-abbreviation", + "./" + ], + "dependencies": { + "@emmetio/abbreviation": "^2.3.3", + "@emmetio/css-abbreviation": "^2.1.8" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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==", + "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", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "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" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "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/expressive-code": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.38.3.tgz", + "integrity": "sha512-COM04AiUotHCKJgWdn7NtW2lqu8OW8owAidMpkXt1qxrZ9Q2iC7+tok/1qIn2ocGnczvr9paIySgGnEwFeEQ8Q==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.38.3", + "@expressive-code/plugin-frames": "^0.38.3", + "@expressive-code/plugin-shiki": "^0.38.3", + "@expressive-code/plugin-text-markers": "^0.38.3" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.1.tgz", + "integrity": "sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg==", + "license": "MIT", + "dependencies": { + "@types/fontkit": "^2.0.8", + "fontkit": "^2.0.4" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/framer-motion": { + "version": "11.14.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.14.4.tgz", + "integrity": "sha512-NQuzr9JbeJDMQmy0FFLhLzk9h1kAjVC1tGE/HY4ubF02B95EBm2lpA21LE3Od/OpXqXgp0zl5Hdqu25hliBRsA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.14.3", + "motion-utils": "^11.14.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "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-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", + "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.3.tgz", + "integrity": "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", + "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree/node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-estree/node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html/node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", + "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "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-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "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==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "license": "MIT" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/linkedom": { + "version": "0.14.26", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.14.26.tgz", + "integrity": "sha512-mK6TrydfFA7phrnp+1j57ycBwFI5bGSW6YXlw9acHoqF+mP/y+FooEYYyniOt5Ot57FSKB3iwmnuQ1UUyNLm5A==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^8.0.1", + "uhyphen": "^0.2.0" + } + }, + "node_modules/lite-youtube-embed": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lite-youtube-embed/-/lite-youtube-embed-0.3.3.tgz", + "integrity": "sha512-gFfVVnj6NRjxVfJKo3qoLtpi0v5mn3AcR4eKD45wrxQuxzveFJUb+7Cr6uV6n+DjO8X3p0UzPPquhGt0H/y+NA==", + "license": "Apache-2.0" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-footnote": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/marked-footnote/-/marked-footnote-1.2.4.tgz", + "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0" + } + }, + "node_modules/marked-plaintify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/marked-plaintify/-/marked-plaintify-1.0.1.tgz", + "integrity": "sha512-KQhxtuVWf3Ij3YMiW4ArlgNOVmzOAlP0o/upsu2+h7Q4TCAwG4UvkYTteZF2sDDomXQnNSLmfyAhoR0gx2Orgw==", + "license": "MIT", + "peerDependencies": { + "marked": ">=7.0.0" + } + }, + "node_modules/marked-smartypants": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/marked-smartypants/-/marked-smartypants-1.1.9.tgz", + "integrity": "sha512-VPeuaUr5IWptI7nJdgQ9ugrLWYGv13NdzEXTtKY3cmB4aRWOI2RzhLlf+xQp6Wnob9SAPO2sNVlfSJr+nflk/A==", + "license": "MIT", + "dependencies": { + "smartypants": "^0.2.2" + }, + "peerDependencies": { + "marked": ">=4 <16" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", + "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", + "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", + "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", + "license": "MIT", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", + "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz", + "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/motion": { + "version": "11.14.4", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.14.4.tgz", + "integrity": "sha512-ZIaw6ko88B8rSmBEFzqbTCQMbo9xMu8f4PSXSGdb9DTDy8R0sXcbwMEKmTEYkrj9TmZ4n+Ebd0KYjtqHgzRkRQ==", + "license": "MIT", + "dependencies": { + "framer-motion": "^11.14.4", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz", + "integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==", + "license": "MIT" + }, + "node_modules/motion-utils": { + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz", + "integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", + "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.7.0.tgz", + "integrity": "sha512-HRaRh09cE0gRS3+wi2zxekB+I5L8C/gN60S+vb11eADHUaB/q4u8wGGOX3GvwvitG8ixaeycZfeoyruKQzUgNg==", + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.0.2", + "regex-recursion": "^4.3.0" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", + "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.2.0.tgz", + "integrity": "sha512-sFVv5/x73qCp9KlLHv8/uWDv7rG1tsWcG9MuXc5YTrXIrb8c1Gshm9oc5rMLXNZILXUWai8WczqaK4jjroEzng==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.2.0", + "@pagefind/darwin-x64": "1.2.0", + "@pagefind/linux-arm64": "1.2.0", + "@pagefind/linux-x64": "1.2.0", + "@pagefind/windows-x64": "1.2.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regex": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.0.2.tgz", + "integrity": "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-4.3.0.tgz", + "integrity": "sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.38.3.tgz", + "integrity": "sha512-RYSSDkMBikoTbycZPkcWp6ELneANT4eTpND1DSRJ6nI2eVFUwTBDCvE2vO6jOOTaavwnPiydi4i/87NRyjpdOA==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.38.3" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", + "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/request-light": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.7.0.tgz", + "integrity": "sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==", + "license": "MIT" + }, + "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", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "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" + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shiki": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.24.1.tgz", + "integrity": "sha512-/qByWMg05+POb63c/OvnrU17FcCUa34WU4F6FCrd/mjDPEDPl8YUNRkRMbo8l3iYMLydfCgxi1r37JFoSw8A4A==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.24.1", + "@shikijs/engine-javascript": "1.24.1", + "@shikijs/engine-oniguruma": "1.24.1", + "@shikijs/types": "1.24.1", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.0.tgz", + "integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smartypants": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/smartypants/-/smartypants-0.2.2.tgz", + "integrity": "sha512-TzobUYoEft/xBtb2voRPryAUIvYguG0V7Tt3de79I1WfXgCwelqVsGuZSnu3GFGRZhXR90AeEYIM+icuB/S06Q==", + "license": "BSD-3-Clause", + "bin": { + "smartypants": "bin/smartypants.js", + "smartypantsu": "bin/smartypantsu.js" + } + }, + "node_modules/smol-toml": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.0.tgz", + "integrity": "sha512-Jjsa8LZ+DyLbZ7gVi9d18bS8oxq0PQrTlVDfvYXgh7gxLwbW9QWgvakHD+hBLUtr5NahfStd8LQLGSPchaEJ8Q==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "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": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/starlight-blog": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/starlight-blog/-/starlight-blog-0.15.0.tgz", + "integrity": "sha512-zNs8Z6eJAqurarD3up8kosfAXNDiZEaweI7S7vRfeTN6eOZijPa3QVifJbYK8n6sdX4W3liEqPkDF+PIz3TxxQ==", + "license": "MIT", + "dependencies": { + "@astrojs/mdx": "3.1.9", + "@astrojs/rss": "4.0.5", + "astro-remote": "0.3.2", + "github-slugger": "2.0.0", + "marked": "12.0.2", + "marked-plaintify": "1.0.1", + "ultrahtml": "1.5.3" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.28.3" + } + }, + "node_modules/starlight-image-zoom": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/starlight-image-zoom/-/starlight-image-zoom-0.9.0.tgz", + "integrity": "sha512-XG87T80g5hsT6dvtNk9OKx0gD+M8lsloVTApQYnxdc3JD8lQBfu2kCsrwkyrwXZRtV7JRyd0PDHwx1UFmGmI1g==", + "license": "MIT", + "dependencies": { + "rehype-raw": "7.0.0", + "unist-util-visit": "5.0.0", + "unist-util-visit-parents": "6.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.22.0" + } + }, + "node_modules/starlight-links-validator": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/starlight-links-validator/-/starlight-links-validator-0.13.4.tgz", + "integrity": "sha512-LdmLbJyPHVrSUhcuxiP3pJNnW8zRcOg/32C996Ic0LOCKbB8vylqHLvAMdIhT67FvEV4eAROun+2wTVU2J156A==", + "license": "MIT", + "dependencies": { + "@types/picomatch": "2.3.3", + "github-slugger": "2.0.0", + "hast-util-from-html": "2.0.1", + "hast-util-has-property": "3.0.0", + "is-absolute-url": "4.0.1", + "kleur": "4.1.5", + "mdast-util-to-string": "4.0.0", + "picomatch": "4.0.2", + "unist-util-visit": "5.0.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.15.0", + "astro": ">=4.0.0" + } + }, + "node_modules/starlight-links-validator/node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/starlight-showcases": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/starlight-showcases/-/starlight-showcases-0.2.0.tgz", + "integrity": "sha512-YWJuTqArkUdVJV85VKZJ0BvKCQRu1SKtH/Cr5t6G/oIfI4IptWc92E7BmiuNnpuQ2U7TczTRidCYurPrbgQQVA==", + "license": "MIT", + "dependencies": { + "@astro-community/astro-embed-twitter": "^0.5.4", + "@astro-community/astro-embed-youtube": "^0.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@astrojs/starlight": ">=0.23.0" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT" + }, + "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/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz", + "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typesafe-path": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", + "integrity": "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-auto-import-cache": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.5.tgz", + "integrity": "sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.8" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + }, + "node_modules/ultrahtml": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz", + "integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.6.0.tgz", + "integrity": "sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0", + "ofetch": "^1.4.1", + "ohash": "^2.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.2.tgz", + "integrity": "sha512-cKEsD6iBWJgOMJ6vW1ID/SYuqNf8oN4yqRk8OYqaVQ3nnkJXOT1PSpaMh2QfzLs78UN5kSNRD2c/mgjT8tX7+w==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.0", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/unstorage/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/volar-service-css": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.62.tgz", + "integrity": "sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==", + "license": "MIT", + "dependencies": { + "vscode-css-languageservice": "^6.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-emmet": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.62.tgz", + "integrity": "sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==", + "license": "MIT", + "dependencies": { + "@emmetio/css-parser": "^0.4.0", + "@emmetio/html-matcher": "^1.3.0", + "@vscode/emmet-helper": "^2.9.3", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-html": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.62.tgz", + "integrity": "sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==", + "license": "MIT", + "dependencies": { + "vscode-html-languageservice": "^5.3.0", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-prettier": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-prettier/-/volar-service-prettier-0.0.62.tgz", + "integrity": "sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0", + "prettier": "^2.2 || ^3.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + }, + "prettier": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.62.tgz", + "integrity": "sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==", + "license": "MIT", + "dependencies": { + "path-browserify": "^1.0.1", + "semver": "^7.6.2", + "typescript-auto-import-cache": "^0.3.3", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-nls": "^5.2.0", + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-typescript-twoslash-queries": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.62.tgz", + "integrity": "sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/volar-service-yaml": { + "version": "0.0.62", + "resolved": "https://registry.npmjs.org/volar-service-yaml/-/volar-service-yaml-0.0.62.tgz", + "integrity": "sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==", + "license": "MIT", + "dependencies": { + "vscode-uri": "^3.0.8", + "yaml-language-server": "~1.15.0" + }, + "peerDependencies": { + "@volar/language-service": "~2.4.0" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/vscode-css-languageservice": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.2.tgz", + "integrity": "sha512-GEpPxrUTAeXWdZWHev1OJU9lz2Q2/PPBxQ2TIRmLGvQiH3WZbqaNoute0n0ewxlgtjzTW3AKZT+NHySk5Rf4Eg==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-html-languageservice": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.3.1.tgz", + "integrity": "sha512-ysUh4hFeW/WOWz/TO9gm08xigiSsV/FOAZ+DolgJfeLftna54YdmZ4A+lIn46RbdO3/Qv5QHTn1ZGqmrXQhZyA==", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/vscode-json-languageservice": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", + "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "engines": { + "npm": ">=7.0.0" + } + }, + "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-nls": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", + "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "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": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yaml-language-server": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.15.0.tgz", + "integrity": "sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "lodash": "4.17.21", + "request-light": "^0.5.7", + "vscode-json-languageservice": "4.1.8", + "vscode-languageserver": "^7.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2", + "yaml": "2.2.2" + }, + "bin": { + "yaml-language-server": "bin/yaml-language-server" + }, + "optionalDependencies": { + "prettier": "2.8.7" + } + }, + "node_modules/yaml-language-server/node_modules/prettier": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", + "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/yaml-language-server/node_modules/request-light": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", + "integrity": "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/vscode-jsonrpc": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", + "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "license": "MIT", + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", + "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.16.0" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver-protocol": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", + "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "6.0.0", + "vscode-languageserver-types": "3.16.0" + } + }, + "node_modules/yaml-language-server/node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "license": "MIT" + }, + "node_modules/yaml-language-server/node_modules/yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", + "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "license": "ISC", + "engines": { + "node": ">= 14" + } + }, + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..d144466a2 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,31 @@ +{ + "name": "wails-docs", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/react": "^4.1.0", + "@astrojs/starlight": "^0.30.0", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.2", + "astro": "^5.0.0", + "astro-d2": "^0.5.0", + "framer-motion": "^11.14.4", + "motion": "^11.14.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sharp": "^0.33.5", + "starlight-blog": "^0.15.0", + "starlight-image-zoom": "^0.9.0", + "starlight-links-validator": "^0.13.4", + "starlight-showcases": "^0.2.0", + "typescript": "^5.7.2" + } +} diff --git a/docs/public/d2/docs/DEVELOPER_GUIDE-0.svg b/docs/public/d2/docs/DEVELOPER_GUIDE-0.svg new file mode 100644 index 000000000..a9b379378 --- /dev/null +++ b/docs/public/d2/docs/DEVELOPER_GUIDE-0.svg @@ -0,0 +1,188 @@ +DeveloperCLI Layer(cmd/wails3)Command Handlers(internal/commands)Task Runner(internal/commands/task.go)Taskfile(v3/Taskfile.yaml)Build/Packaging(internal/*)Templates(internal/templates)Runtime Assets(internal/runtime)Application Core(pkg/application)Asset Server(internal/assetserver)Message Processor(pkg/application/messageprocessor*.go)WebView Implementations(pkg/application/webview_window_*.go)Services & Bindings(pkg/services/*)Platform Layers(pkg/mac, pkg/w32, pkg/events, pkg/ui) runs wails3registers subcommandswraps build/package/devexecutes tasksinvokes packagers & generatorsrenders scaffoldsbuilds runtime JSembedded assetsserves HTTProutes runtime callsbridge over WebViewbinds Go servicesdispatches to OSfeeds frontendexpose methods + + + + + + + + + + + + + + + + + diff --git a/docs/public/d2/docs/DEVELOPER_GUIDE-1.svg b/docs/public/d2/docs/DEVELOPER_GUIDE-1.svg new file mode 100644 index 000000000..ae97cb156 --- /dev/null +++ b/docs/public/d2/docs/DEVELOPER_GUIDE-1.svg @@ -0,0 +1,181 @@ +App.Run dispatch hubapplicationEventsEventManagerwindowEventsWindowswebviewRequestsAssetServerwindowMessagesMessageHandlerswindowKeyEventsKeyBindingdragDropBuffermenuItemClickedMenuManager goroutineEvent.handleApplicationEventgoroutinehandleWindowEventServeWebViewRequestHandleMessage / RawMessageHandlerHandleKeyEventHandleDragAndDrop + + + + + + + + + + diff --git a/docs/public/d2/docs/quick-start/why-wails-0.svg b/docs/public/d2/docs/quick-start/why-wails-0.svg new file mode 100644 index 000000000..656e3e069 --- /dev/null +++ b/docs/public/d2/docs/quick-start/why-wails-0.svg @@ -0,0 +1,181 @@ +ApplicationGo BackendNative WebViewYour UI(React/Vue/etc) Bindings & Events + + + diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 000000000..e682e4453 --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/public/missing.png b/docs/public/missing.png new file mode 100644 index 000000000..05d2bafc3 Binary files /dev/null and b/docs/public/missing.png differ diff --git a/docs/public/showcase-images/bboard.webp b/docs/public/showcase-images/bboard.webp new file mode 100644 index 000000000..463d25de0 Binary files /dev/null and b/docs/public/showcase-images/bboard.webp differ diff --git a/docs/public/showcase-images/cfntracker.webp b/docs/public/showcase-images/cfntracker.webp new file mode 100644 index 000000000..6a2288a5c Binary files /dev/null and b/docs/public/showcase-images/cfntracker.webp differ diff --git a/docs/public/showcase-images/edex-ui.webp b/docs/public/showcase-images/edex-ui.webp new file mode 100644 index 000000000..110ca1acf Binary files /dev/null and b/docs/public/showcase-images/edex-ui.webp differ diff --git a/docs/public/showcase-images/emailit.webp b/docs/public/showcase-images/emailit.webp new file mode 100644 index 000000000..fc1b9a51a Binary files /dev/null and b/docs/public/showcase-images/emailit.webp differ diff --git a/docs/public/showcase-images/encrypteasy.webp b/docs/public/showcase-images/encrypteasy.webp new file mode 100644 index 000000000..c0789a3e3 Binary files /dev/null and b/docs/public/showcase-images/encrypteasy.webp differ diff --git a/docs/public/showcase-images/filehound.webp b/docs/public/showcase-images/filehound.webp new file mode 100644 index 000000000..92769ca8e Binary files /dev/null and b/docs/public/showcase-images/filehound.webp differ diff --git a/docs/public/showcase-images/gamestacker.webp b/docs/public/showcase-images/gamestacker.webp new file mode 100644 index 000000000..432e6eaed Binary files /dev/null and b/docs/public/showcase-images/gamestacker.webp differ diff --git a/docs/public/showcase-images/hiposter.webp b/docs/public/showcase-images/hiposter.webp new file mode 100644 index 000000000..7c7510ea1 Binary files /dev/null and b/docs/public/showcase-images/hiposter.webp differ diff --git a/docs/public/showcase-images/mchat.webp b/docs/public/showcase-images/mchat.webp new file mode 100644 index 000000000..393b6f77b Binary files /dev/null and b/docs/public/showcase-images/mchat.webp differ diff --git a/docs/public/showcase-images/minecraft-mod-updater.webp b/docs/public/showcase-images/minecraft-mod-updater.webp new file mode 100644 index 000000000..c8e011cf3 Binary files /dev/null and b/docs/public/showcase-images/minecraft-mod-updater.webp differ diff --git a/docs/public/showcase-images/minesweeper-xp.webp b/docs/public/showcase-images/minesweeper-xp.webp new file mode 100644 index 000000000..b3c5ca26b Binary files /dev/null and b/docs/public/showcase-images/minesweeper-xp.webp differ diff --git a/docs/public/showcase-images/modalfilemanager.webp b/docs/public/showcase-images/modalfilemanager.webp new file mode 100644 index 000000000..2fdf219fc Binary files /dev/null and b/docs/public/showcase-images/modalfilemanager.webp differ diff --git a/docs/public/showcase-images/mollywallet.webp b/docs/public/showcase-images/mollywallet.webp new file mode 100644 index 000000000..11641f8ca Binary files /dev/null and b/docs/public/showcase-images/mollywallet.webp differ diff --git a/docs/public/showcase-images/october.webp b/docs/public/showcase-images/october.webp new file mode 100644 index 000000000..ceec1c573 Binary files /dev/null and b/docs/public/showcase-images/october.webp differ diff --git a/docs/public/showcase-images/optimus.webp b/docs/public/showcase-images/optimus.webp new file mode 100644 index 000000000..0aac84058 Binary files /dev/null and b/docs/public/showcase-images/optimus.webp differ diff --git a/docs/public/showcase-images/portfall.webp b/docs/public/showcase-images/portfall.webp new file mode 100644 index 000000000..12f8d6e5b Binary files /dev/null and b/docs/public/showcase-images/portfall.webp differ diff --git a/docs/public/showcase-images/resizem.webp b/docs/public/showcase-images/resizem.webp new file mode 100644 index 000000000..aaee1c806 Binary files /dev/null and b/docs/public/showcase-images/resizem.webp differ diff --git a/docs/public/showcase-images/riftshare-main.webp b/docs/public/showcase-images/riftshare-main.webp new file mode 100644 index 000000000..2d6a8fb3a Binary files /dev/null and b/docs/public/showcase-images/riftshare-main.webp differ diff --git a/docs/public/showcase-images/scriptbar.webp b/docs/public/showcase-images/scriptbar.webp new file mode 100644 index 000000000..92463fdb9 Binary files /dev/null and b/docs/public/showcase-images/scriptbar.webp differ diff --git a/docs/public/showcase-images/tiny-rdm1.webp b/docs/public/showcase-images/tiny-rdm1.webp new file mode 100644 index 000000000..11b375580 Binary files /dev/null and b/docs/public/showcase-images/tiny-rdm1.webp differ diff --git a/docs/public/showcase-images/tiny-rdm2.webp b/docs/public/showcase-images/tiny-rdm2.webp new file mode 100644 index 000000000..9de730fb4 Binary files /dev/null and b/docs/public/showcase-images/tiny-rdm2.webp differ diff --git a/docs/public/showcase-images/varly2.webp b/docs/public/showcase-images/varly2.webp new file mode 100644 index 000000000..6dbe0c9bf Binary files /dev/null and b/docs/public/showcase-images/varly2.webp differ diff --git a/docs/public/showcase-images/wailsterm.webp b/docs/public/showcase-images/wailsterm.webp new file mode 100644 index 000000000..6d4251a75 Binary files /dev/null and b/docs/public/showcase-images/wailsterm.webp differ diff --git a/docs/public/showcase-images/wally.webp b/docs/public/showcase-images/wally.webp new file mode 100644 index 000000000..150c98c74 Binary files /dev/null and b/docs/public/showcase-images/wally.webp differ diff --git a/docs/public/showcase-images/wombat.webp b/docs/public/showcase-images/wombat.webp new file mode 100644 index 000000000..97f965834 Binary files /dev/null and b/docs/public/showcase-images/wombat.webp differ diff --git a/docs/public/showcase-images/ytd.webp b/docs/public/showcase-images/ytd.webp new file mode 100644 index 000000000..c7988cee5 Binary files /dev/null and b/docs/public/showcase-images/ytd.webp differ diff --git a/docs/public/sponsors/jetbrains-grayscale.webp b/docs/public/sponsors/jetbrains-grayscale.webp new file mode 100644 index 000000000..be39c4856 Binary files /dev/null and b/docs/public/sponsors/jetbrains-grayscale.webp differ diff --git a/docs/public/sponsors/sponsors.svg b/docs/public/sponsors/sponsors.svg new file mode 100644 index 000000000..1a9985e59 --- /dev/null +++ b/docs/public/sponsors/sponsors.svg @@ -0,0 +1,197 @@ + + + + +Champion + Masato Miura + +Bronze Sponsors + Cody Bentley + + + + Kazuya Gokita + + + + Simon Thomas + + + + CodeRabbit + +Covering Costs + Nick + + + + Marcus + + + + John + + + + Matt Holt + + + + Iain + + + + Julien + + + + Andrei + + + + Michael + +Buying Breakfast + tc-hib + + + + Tai Groot + + + + Tom Wu + + + + Arden + + + + igops + + + + vaaski + +Buying Coffee + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Helpers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/sponsors/zsa.png b/docs/public/sponsors/zsa.png new file mode 100644 index 000000000..507e983fa Binary files /dev/null and b/docs/public/sponsors/zsa.png differ diff --git a/docs/src/assets/blog-images/browser.webp b/docs/src/assets/blog-images/browser.webp new file mode 100644 index 000000000..a19d5b036 Binary files /dev/null and b/docs/src/assets/blog-images/browser.webp differ diff --git a/docs/src/assets/blog-images/build-cross-windows.webp b/docs/src/assets/blog-images/build-cross-windows.webp new file mode 100644 index 000000000..ba1a538f6 Binary files /dev/null and b/docs/src/assets/blog-images/build-cross-windows.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-amd.webp b/docs/src/assets/blog-images/build-darwin-amd.webp new file mode 100644 index 000000000..6126129ca Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-amd.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-arm.webp b/docs/src/assets/blog-images/build-darwin-arm.webp new file mode 100644 index 000000000..47fcf4583 Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-arm.webp differ diff --git a/docs/src/assets/blog-images/build-darwin-universal.webp b/docs/src/assets/blog-images/build-darwin-universal.webp new file mode 100644 index 000000000..c95d92ccb Binary files /dev/null and b/docs/src/assets/blog-images/build-darwin-universal.webp differ diff --git a/docs/src/assets/blog-images/devtools.png b/docs/src/assets/blog-images/devtools.png new file mode 100644 index 000000000..e56a0d304 Binary files /dev/null and b/docs/src/assets/blog-images/devtools.png differ diff --git a/docs/src/assets/blog-images/linux-build-cross-windows.webp b/docs/src/assets/blog-images/linux-build-cross-windows.webp new file mode 100644 index 000000000..cbed7585b Binary files /dev/null and b/docs/src/assets/blog-images/linux-build-cross-windows.webp differ diff --git a/docs/src/assets/blog-images/montage.png b/docs/src/assets/blog-images/montage.png new file mode 100644 index 000000000..ddd771851 Binary files /dev/null and b/docs/src/assets/blog-images/montage.png differ diff --git a/docs/src/assets/blog-images/multiwindow.webp b/docs/src/assets/blog-images/multiwindow.webp new file mode 100644 index 000000000..746a1d7f2 Binary files /dev/null and b/docs/src/assets/blog-images/multiwindow.webp differ diff --git a/docs/src/assets/blog-images/remote-linux.webp b/docs/src/assets/blog-images/remote-linux.webp new file mode 100644 index 000000000..25ad11ea3 Binary files /dev/null and b/docs/src/assets/blog-images/remote-linux.webp differ diff --git a/docs/src/assets/blog-images/remote-mac.webp b/docs/src/assets/blog-images/remote-mac.webp new file mode 100644 index 000000000..bf4758e1a Binary files /dev/null and b/docs/src/assets/blog-images/remote-mac.webp differ diff --git a/docs/src/assets/blog-images/remote.webp b/docs/src/assets/blog-images/remote.webp new file mode 100644 index 000000000..3968c5ddc Binary files /dev/null and b/docs/src/assets/blog-images/remote.webp differ diff --git a/docs/src/assets/blog-images/vscode.webp b/docs/src/assets/blog-images/vscode.webp new file mode 100644 index 000000000..156fa3078 Binary files /dev/null and b/docs/src/assets/blog-images/vscode.webp differ diff --git a/docs/src/assets/blog-images/wails-linux.webp b/docs/src/assets/blog-images/wails-linux.webp new file mode 100644 index 000000000..45c76ed0e Binary files /dev/null and b/docs/src/assets/blog-images/wails-linux.webp differ diff --git a/docs/src/assets/blog-images/wails-mac.webp b/docs/src/assets/blog-images/wails-mac.webp new file mode 100644 index 000000000..26ee0be13 Binary files /dev/null and b/docs/src/assets/blog-images/wails-mac.webp differ diff --git a/docs/src/assets/blog-images/wails-menus-linux.webp b/docs/src/assets/blog-images/wails-menus-linux.webp new file mode 100644 index 000000000..391225a35 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus-linux.webp differ diff --git a/docs/src/assets/blog-images/wails-menus-mac.webp b/docs/src/assets/blog-images/wails-menus-mac.webp new file mode 100644 index 000000000..033f68d35 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus-mac.webp differ diff --git a/docs/src/assets/blog-images/wails-menus.webp b/docs/src/assets/blog-images/wails-menus.webp new file mode 100644 index 000000000..ba64152b4 Binary files /dev/null and b/docs/src/assets/blog-images/wails-menus.webp differ diff --git a/docs/src/assets/blog-images/wails.webp b/docs/src/assets/blog-images/wails.webp new file mode 100644 index 000000000..777f4d05c Binary files /dev/null and b/docs/src/assets/blog-images/wails.webp differ diff --git a/docs/src/assets/contributors.html b/docs/src/assets/contributors.html new file mode 100644 index 000000000..465a64801 --- /dev/null +++ b/docs/src/assets/contributors.html @@ -0,0 +1,246 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lea Anthony
Lea Anthony

💻 🤔 🎨 🖋 💡 🧑‍🏫 📆 🔧 🐛 📝 🚧 📦 👀 💬 🔬 ⚠️ 📢 👀 📖
stffabi
stffabi

💻 🤔 🎨 🐛 🚧 📦 👀 💬 🔬 👀 📖 ⚠️
Travis McLane
Travis McLane

💻 🔬 📦 🤔 🐛 👀 ⚠️ 💬 📖
Misite Bao
Misite Bao

📖 🌍 🔬 🚧
Byron Chris
Byron Chris

💻 🔬 🚧 🐛 👀 ⚠️ 💬 🤔 🎨 📦 🚇
konez2k
konez2k

💻 📦 🤔
Dario Emerson
Dario Emerson

💻 🐛 🤔 ⚠️
Ian M. Jones
Ian M. Jones

💻 🐛 🤔 ⚠️ 👀 📦
marktohark
marktohark

💻
Ryan H
Ryan H

💻
Cody Bentley
Cody Bentley

💻 📦 🤔 💵
Florent
Florent

💻 🐛
Alexander Hudek
Alexander Hudek

💻 💵
Tim Kipp
Tim Kipp

💻
Altynbek Kaliakbarov
Altynbek Kaliakbarov

💻
Nikolai Zimmermann
Nikolai Zimmermann

💻
k-muchmore
k-muchmore

💻
Snider
Snider

💻 🤔 📖 💵
Albert Sun
Albert Sun

💻 ⚠️
Ariel
Ariel

💻 🐛
Ilgıt Yıldırım
Ilgıt Yıldırım

💻 🐛 💵
Toyam Cox
Toyam Cox

💻 📦 🐛
hi019
hi019

💻 🐛
Arthur Wiebe
Arthur Wiebe

💻 🐛
Balakrishna Prasad Ganne
Balakrishna Prasad Ganne

💻
BillBuilt
BillBuilt

💻 📦 🤔 💬 💵
Eng Zer Jun
Eng Zer Jun

🚧 💻
LGiki
LGiki

📖
Lontten
Lontten

📖
Lukas Crepaz
Lukas Crepaz

💻 🐛
Marcus Crane
Marcus Crane

🐛 📖 💵
Qais Patankar
Qais Patankar

📖
Wakeful-Cloud
Wakeful-Cloud

💻 🐛
Zámbó, Levente
Zámbó, Levente

💻 📦 🐛 ⚠️
Ironpark
Ironpark

💻 🤔
mondy
mondy

💻 📖
Benjamin Ryan
Benjamin Ryan

🐛
fallendusk
fallendusk

📦 💻
Mat Ryer
Mat Ryer

💻 🤔 🐛
Abtin
Abtin

💻 🐛
Adrian Lanzafame
Adrian Lanzafame

📦 💻
Aleksey Polyakov
Aleksey Polyakov

🐛 💻
Alexander Matviychuk
Alexander Matviychuk

💻 📦
AlienRecall
AlienRecall

💻 📦
Aman
Aman

📖
Amaury Tobias Quiroz
Amaury Tobias Quiroz

💻 🐛
Andreas Wenk
Andreas Wenk

📖
Antonio Stanković
Antonio Stanković

💻 📦
Arpit Jain
Arpit Jain

📖
Austin Schey
Austin Schey

💻 🐛
Benjamin Thomas
Benjamin Thomas

💻 📦 🤔
Bertram Truong
Bertram Truong

💻 🐛
Blake Bourque
Blake Bourque

📖
Denis
Denis

📖
diogox
diogox

💻 📦
Dmitry Gomzyakov
Dmitry Gomzyakov

💻 📦
Edward Browncross
Edward Browncross

💻
Elie Grenon
Elie Grenon

💻
Florian Didron
Florian Didron

💻 🐛 🤔 ⚠️ 👀 📦
GargantuaX
GargantuaX

💵
Igor Minin
Igor Minin

💻 🐛
Jae-Sung Lee
Jae-Sung Lee

💻 🤔
Jarek
Jarek

💻 📦
Junker
Junker

📖
Kris Raney
Kris Raney

💻 🐛
Luken
Luken

📖
Mark Stenglein
Mark Stenglein

💻 🐛
buddyabaddon
buddyabaddon

💻
MikeSchaap
MikeSchaap

💻 🐛
NYSSEN Michaël
NYSSEN Michaël

💻 🐛
Nan0
Nan0

💻 🤔 ⚠️ 👀
oskar
oskar

📖
Pierre Joye
Pierre Joye

💻 🐛 🤔 ⚠️
Reuben Thomas-Davis
Reuben Thomas-Davis

💻 🐛
Robin
Robin

💻 🐛
Sebastian Bauer
Sebastian Bauer

💻 🤔 ⚠️ 👀 💬
Sidharth Rathi
Sidharth Rathi

📖 🐛
Sithembiso Khumalo
Sithembiso Khumalo

💻 🐛
Soheib El-Harrache
Soheib El-Harrache

💻 🐛 💵
Sophie Au
Sophie Au

💻 🐛
Stefanos Papadakis
Stefanos Papadakis

💻 🐛
Steve Chung
Steve Chung

💻 🐛
Timm Ortloff
Timm Ortloff

📖
Tom
Tom

💻
Valentin Trinqué
Valentin Trinqué

💻 🐛
mattn
mattn

💻 🐛
bearsh
bearsh

💻 🤔 📖
chenxiao
chenxiao

💻 🤔 📖
fengweiqiang
fengweiqiang

💻 📦
flin7
flin7

📖
fred21O4
fred21O4

📖
gardc
gardc

📖
rayshoo
rayshoo

📖
Ishiyama Yuzuki
Ishiyama Yuzuki

💻 🐛
佰阅
佰阅

💻
刀刀
刀刀

📖 🐛
归位
归位

💻 🐛
skamensky
skamensky

💻 🤔 📖
dependabot[bot]
dependabot[bot]

💻 🚧
Damian Sieradzki
Damian Sieradzki

💵
John Dorman
John Dorman

💵
Ian Sinnott
Ian Sinnott

💵
Arden Shackelford
Arden Shackelford

💵
Bironou
Bironou

💵
CharlieGo_
CharlieGo_

💵
overnet
overnet

💵
jugglingjsons
jugglingjsons

💵
Selvin Ortiz
Selvin Ortiz

💵
ZanderCodes
ZanderCodes

💵
Michael Voronov
Michael Voronov

💵
letheanVPN
letheanVPN

💵
Tai Groot
Tai Groot

💵
easy-web-it
easy-web-it

💵
Michael Olson
Michael Olson

💵
EdenNetwork Italia
EdenNetwork Italia

💵
ondoki
ondoki

💵
QuEST Rail LLC
QuEST Rail LLC

💵
Gilgameš
Gilgameš

💵
Bernt-Johan Bergshaven
Bernt-Johan Bergshaven

💵
Liam Bigelow
Liam Bigelow

💵
Nick Arellano
Nick Arellano

💵
Frank Chiarulli Jr.
Frank Chiarulli Jr.

💵
Tyler
Tyler

💵
Trea Hauet
Trea Hauet

💵
Kent 'picat' Gruber
Kent 'picat' Gruber

💵
tc-hib
tc-hib

💵
Antonio
Antonio

📖
MyNameIsAres
MyNameIsAres

📖
Maicarons J
Maicarons J

📖
kiddov
kiddov

📖 💵 ⚠️ 🤔
Nicolas Coutin
Nicolas Coutin

💵
Parvin Eyvazov
Parvin Eyvazov

📖
github-actions[bot]
github-actions[bot]

💻
Oleg Gulevskyy
Oleg Gulevskyy

💻 📖 🚧 📦
Richard Guay
Richard Guay

📖
Adam Tenderholt
Adam Tenderholt

💻
JulioDRF
JulioDRF

💻
Scott Opell
Scott Opell

💻
Vadim Shchepotev
Vadim Shchepotev

💻
Will Andrews
Will Andrews

💻
Gwyn
Gwyn

💻 👀 💬 🔬
希嘉嘉
希嘉嘉

💻
ALMAS
ALMAS

💻
Alex
Alex

💻
Arif Ali
Arif Ali

💻
Artur Siarohau
Artur Siarohau

💻
Binyamin Aron Green
Binyamin Aron Green

💻
Brian Dwyer
Brian Dwyer

💻
Christian Kilb
Christian Kilb

💻
David Florness
David Florness

📖
David Walton
David Walton

💻
Debdut Karmakar
Debdut Karmakar

💻
Dieter Zhu
Dieter Zhu

💻
Fredrik Holmqvist
Fredrik Holmqvist

💻
Giovanni Palma
Giovanni Palma

💻
Hao
Hao

💻
Igor Sementsov
Igor Sementsov

💻
Johannes Haseitl
Johannes Haseitl

💻
Joshua Hull
Joshua Hull

💻
Joshua Mangiola
Joshua Mangiola

📖
Kevin MacMartin
Kevin MacMartin

💻
Liang Li
Liang Li

💻
Marvin Collins Hosea
Marvin Collins Hosea

💻
Matt Holt
Matt Holt

💻
Niklas
Niklas

💻
Andy Hsu
Andy Hsu

💻
NullCode
NullCode

💻
Oussama Sethoum
Oussama Sethoum

💻
ParkourLiu
ParkourLiu

💻
Rachel Chen
Rachel Chen

💻
Rob Nice
Rob Nice

💻
Ryo TAGAMI
Ryo TAGAMI

💻
Sam Hennessy
Sam Hennessy

💻
Sean
Sean

💻
Sean Gosiaco
Sean Gosiaco

💻
Eric P Sheets
Eric P Sheets

💻
Supian M
Supian M

💻
Watson-Sei
Watson-Sei

💻 📖
Yuki Shindo
Yuki Shindo

💻
cuigege
cuigege

💻
cybertramp
cybertramp

💻
hiroki yagi
hiroki yagi

💻
imgbot[bot]
imgbot[bot]

💻
juju
juju

💻
Michael Eatherly
Michael Eatherly

💻
tk
tk

💻
allcontributors[bot]
allcontributors[bot]

📖
wander
wander

📖
+ + +
\ No newline at end of file diff --git a/docs/src/assets/menus/context-menu.png b/docs/src/assets/menus/context-menu.png new file mode 100644 index 000000000..dd43c0c7f --- /dev/null +++ b/docs/src/assets/menus/context-menu.png @@ -0,0 +1 @@ +iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTIzODU1NjI4RjU1MTFFQjg5QzVCNTY1QjY1NjY1QjYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTIzODU1NjM4RjU1MTFFQjg5QzVCNTY1QjY1NjY1QjYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMjM4NTU2MDhGNTUxMUVCODlDNUI1NjVCNjU2NjVCNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMjM4NTU2MThGNTUxMUVCODlDNUI1NjVCNjU2NjVCNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAAALAAAAAAsASwBAAL/hI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8YhMKpfMpvMJjUqn1Kr1is1qt9yu9wsOi8fksvmMTqvX7Lb7DY/L5/S6/Y7P6/f8vv8PGCg4SFhoeIiYqLjI2Oj4CBkpOUlZaXmJmam5ydnp+QkaKjpKWmp6ipqqusra6voKGys7S1tre4ubq7vL2+v7CxwsPExcbHyMnKy8zNzs/AwdLT1NXW19jZ2tvc3d7f0NHi4+Tl5ufo6err7O3u7+Dh8vP09fb3+Pn6+/z9/v/w8woMCBBAsaPIgwocKFDBs6fAgxosSJFCtavIgxo8aN/xw7evwIMqTIkSRLmjyJMqXKlSxbunwJM6bMmTRr2ryJM6fOnTx7+vwJNKjQoUSLGj2KNKnSpUybOn0KNarUqVSrWr2KNavWrVy7ev0KNqzYsWTLmj2LNq3atWzbun0LN67cuXTr2r2LN6/evXz7+v0LOLDgwYQLGz6MOLHixYwbO34MObLkyZQrW76MObPmzZw7e/4MOrTo0aRLmz6NOrXq1axbu34NO7bs2bRr276NO7fu3bx7+/4NPLjw4cSLGz+OPLny5cybO38OPbr06dSrW7+OPbv27dy7e/8OPrz48eTLmz+PPr369ezbu38PP778+fTr27+PP7/+/fz7+9OPX7//fwAGKOCABBZo4IEIJqjgggw26OCDEEYo4YQUVmjhhRhmqOGGHHbo4YcghijiiCSWaOKJKKao4oostujiizDGKOOMNNZo44045qjjjjz26OOPQAYp5JBEFmnkkUgmqeSSTDbp5JNQRinllFRWaeWVWGap5ZZcdunll2CGKeaYZJZp5plopqnmmmy26eabcMYp55x01mnnnXjmqeeefPbp55+ABirooIQWauihiCaq6KKMNuroo5BGKumklFZq6aWYZqrpppx26umnoIYq6qiklmrqqaimquqqrLbq6quwxirrqxEAADs= diff --git a/docs/src/assets/notes-app.png b/docs/src/assets/notes-app.png new file mode 100644 index 000000000..4e4953204 Binary files /dev/null and b/docs/src/assets/notes-app.png differ diff --git a/docs/src/assets/qr1.png b/docs/src/assets/qr1.png new file mode 100644 index 000000000..69885acbe Binary files /dev/null and b/docs/src/assets/qr1.png differ diff --git a/docs/src/assets/qr2.png b/docs/src/assets/qr2.png new file mode 100644 index 000000000..e2ce2a5f1 Binary files /dev/null and b/docs/src/assets/qr2.png differ diff --git a/docs/src/assets/showcase-images/bboard.webp b/docs/src/assets/showcase-images/bboard.webp new file mode 100644 index 000000000..463d25de0 Binary files /dev/null and b/docs/src/assets/showcase-images/bboard.webp differ diff --git a/docs/src/assets/showcase-images/cfntracker.webp b/docs/src/assets/showcase-images/cfntracker.webp new file mode 100644 index 000000000..6a2288a5c Binary files /dev/null and b/docs/src/assets/showcase-images/cfntracker.webp differ diff --git a/docs/src/assets/showcase-images/clave.png b/docs/src/assets/showcase-images/clave.png new file mode 100644 index 000000000..3ee29c303 Binary files /dev/null and b/docs/src/assets/showcase-images/clave.png differ diff --git a/docs/src/assets/showcase-images/emailit.webp b/docs/src/assets/showcase-images/emailit.webp new file mode 100644 index 000000000..fc1b9a51a Binary files /dev/null and b/docs/src/assets/showcase-images/emailit.webp differ diff --git a/docs/src/assets/showcase-images/encrypteasy.webp b/docs/src/assets/showcase-images/encrypteasy.webp new file mode 100644 index 000000000..c0789a3e3 Binary files /dev/null and b/docs/src/assets/showcase-images/encrypteasy.webp differ diff --git a/docs/src/assets/showcase-images/esp-studio.png b/docs/src/assets/showcase-images/esp-studio.png new file mode 100644 index 000000000..9cb7d39fa Binary files /dev/null and b/docs/src/assets/showcase-images/esp-studio.png differ diff --git a/docs/src/assets/showcase-images/filehound.webp b/docs/src/assets/showcase-images/filehound.webp new file mode 100644 index 000000000..92769ca8e Binary files /dev/null and b/docs/src/assets/showcase-images/filehound.webp differ diff --git a/docs/src/assets/showcase-images/hiposter.webp b/docs/src/assets/showcase-images/hiposter.webp new file mode 100644 index 000000000..7c7510ea1 Binary files /dev/null and b/docs/src/assets/showcase-images/hiposter.webp differ diff --git a/docs/src/assets/showcase-images/mac-app.png b/docs/src/assets/showcase-images/mac-app.png new file mode 100644 index 000000000..1f4c85304 Binary files /dev/null and b/docs/src/assets/showcase-images/mac-app.png differ diff --git a/docs/src/assets/showcase-images/mchat.png b/docs/src/assets/showcase-images/mchat.png new file mode 100644 index 000000000..0942c482b Binary files /dev/null and b/docs/src/assets/showcase-images/mchat.png differ diff --git a/docs/src/assets/showcase-images/minecraft-mod-updater.webp b/docs/src/assets/showcase-images/minecraft-mod-updater.webp new file mode 100644 index 000000000..c8e011cf3 Binary files /dev/null and b/docs/src/assets/showcase-images/minecraft-mod-updater.webp differ diff --git a/docs/src/assets/showcase-images/minesweeper-xp.webp b/docs/src/assets/showcase-images/minesweeper-xp.webp new file mode 100644 index 000000000..b3c5ca26b Binary files /dev/null and b/docs/src/assets/showcase-images/minesweeper-xp.webp differ diff --git a/docs/src/assets/showcase-images/modalfilemanager.webp b/docs/src/assets/showcase-images/modalfilemanager.webp new file mode 100644 index 000000000..2fdf219fc Binary files /dev/null and b/docs/src/assets/showcase-images/modalfilemanager.webp differ diff --git a/docs/src/assets/showcase-images/mollywallet.webp b/docs/src/assets/showcase-images/mollywallet.webp new file mode 100644 index 000000000..11641f8ca Binary files /dev/null and b/docs/src/assets/showcase-images/mollywallet.webp differ diff --git a/docs/src/assets/showcase-images/october.webp b/docs/src/assets/showcase-images/october.webp new file mode 100644 index 000000000..ceec1c573 Binary files /dev/null and b/docs/src/assets/showcase-images/october.webp differ diff --git a/docs/src/assets/showcase-images/optimus.webp b/docs/src/assets/showcase-images/optimus.webp new file mode 100644 index 000000000..0aac84058 Binary files /dev/null and b/docs/src/assets/showcase-images/optimus.webp differ diff --git a/docs/src/assets/showcase-images/portfall.webp b/docs/src/assets/showcase-images/portfall.webp new file mode 100644 index 000000000..12f8d6e5b Binary files /dev/null and b/docs/src/assets/showcase-images/portfall.webp differ diff --git a/docs/src/assets/showcase-images/resizem.webp b/docs/src/assets/showcase-images/resizem.webp new file mode 100644 index 000000000..aaee1c806 Binary files /dev/null and b/docs/src/assets/showcase-images/resizem.webp differ diff --git a/docs/src/assets/showcase-images/restic-browser-2.png b/docs/src/assets/showcase-images/restic-browser-2.png new file mode 100644 index 000000000..b986bfe68 Binary files /dev/null and b/docs/src/assets/showcase-images/restic-browser-2.png differ diff --git a/docs/src/assets/showcase-images/riftshare-main.webp b/docs/src/assets/showcase-images/riftshare-main.webp new file mode 100644 index 000000000..2d6a8fb3a Binary files /dev/null and b/docs/src/assets/showcase-images/riftshare-main.webp differ diff --git a/docs/src/assets/showcase-images/scriptbar.webp b/docs/src/assets/showcase-images/scriptbar.webp new file mode 100644 index 000000000..92463fdb9 Binary files /dev/null and b/docs/src/assets/showcase-images/scriptbar.webp differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png b/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png new file mode 100644 index 000000000..0f34e4f53 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-add-snippet.png differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png b/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png new file mode 100644 index 000000000..d87878c29 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-search-and-paste.png differ diff --git a/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png b/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png new file mode 100644 index 000000000..b82468c80 Binary files /dev/null and b/docs/src/assets/showcase-images/snippetexpandergui-select-snippet.png differ diff --git a/docs/src/assets/showcase-images/surge.png b/docs/src/assets/showcase-images/surge.png new file mode 100644 index 000000000..d732fabe8 Binary files /dev/null and b/docs/src/assets/showcase-images/surge.png differ diff --git a/docs/src/assets/showcase-images/tiny-rdm1.webp b/docs/src/assets/showcase-images/tiny-rdm1.webp new file mode 100644 index 000000000..11b375580 Binary files /dev/null and b/docs/src/assets/showcase-images/tiny-rdm1.webp differ diff --git a/docs/src/assets/showcase-images/tiny-rdm2.webp b/docs/src/assets/showcase-images/tiny-rdm2.webp new file mode 100644 index 000000000..9de730fb4 Binary files /dev/null and b/docs/src/assets/showcase-images/tiny-rdm2.webp differ diff --git a/docs/src/assets/showcase-images/varly2.webp b/docs/src/assets/showcase-images/varly2.webp new file mode 100644 index 000000000..6dbe0c9bf Binary files /dev/null and b/docs/src/assets/showcase-images/varly2.webp differ diff --git a/docs/src/assets/showcase-images/wailsterm.webp b/docs/src/assets/showcase-images/wailsterm.webp new file mode 100644 index 000000000..6d4251a75 Binary files /dev/null and b/docs/src/assets/showcase-images/wailsterm.webp differ diff --git a/docs/src/assets/showcase-images/wally.webp b/docs/src/assets/showcase-images/wally.webp new file mode 100644 index 000000000..150c98c74 Binary files /dev/null and b/docs/src/assets/showcase-images/wally.webp differ diff --git a/docs/src/assets/showcase-images/warmine1.png b/docs/src/assets/showcase-images/warmine1.png new file mode 100644 index 000000000..38441d99d Binary files /dev/null and b/docs/src/assets/showcase-images/warmine1.png differ diff --git a/docs/src/assets/showcase-images/warmine2.png b/docs/src/assets/showcase-images/warmine2.png new file mode 100644 index 000000000..4713462da Binary files /dev/null and b/docs/src/assets/showcase-images/warmine2.png differ diff --git a/docs/src/assets/showcase-images/wombat.webp b/docs/src/assets/showcase-images/wombat.webp new file mode 100644 index 000000000..97f965834 Binary files /dev/null and b/docs/src/assets/showcase-images/wombat.webp differ diff --git a/docs/src/assets/showcase-images/ytd.webp b/docs/src/assets/showcase-images/ytd.webp new file mode 100644 index 000000000..c7988cee5 Binary files /dev/null and b/docs/src/assets/showcase-images/ytd.webp differ diff --git a/docs/src/assets/todo-app.png b/docs/src/assets/todo-app.png new file mode 100644 index 000000000..319cc8fbf Binary files /dev/null and b/docs/src/assets/todo-app.png differ diff --git a/docs/src/assets/wails-logo-dark.svg b/docs/src/assets/wails-logo-dark.svg new file mode 100644 index 000000000..dfc54485c --- /dev/null +++ b/docs/src/assets/wails-logo-dark.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/wails-logo-horizontal-dark.svg b/docs/src/assets/wails-logo-horizontal-dark.svg new file mode 100644 index 000000000..01808dca5 --- /dev/null +++ b/docs/src/assets/wails-logo-horizontal-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/wails-logo-horizontal-light.svg b/docs/src/assets/wails-logo-horizontal-light.svg new file mode 100644 index 000000000..465bc5cf9 --- /dev/null +++ b/docs/src/assets/wails-logo-horizontal-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/wails-logo-light.svg b/docs/src/assets/wails-logo-light.svg new file mode 100644 index 000000000..ab02c827a --- /dev/null +++ b/docs/src/assets/wails-logo-light.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/wails_build.mp4 b/docs/src/assets/wails_build.mp4 new file mode 100644 index 000000000..2d1ee783b Binary files /dev/null and b/docs/src/assets/wails_build.mp4 differ diff --git a/docs/src/assets/wails_dev.mp4 b/docs/src/assets/wails_dev.mp4 new file mode 100644 index 000000000..29c2a45a4 Binary files /dev/null and b/docs/src/assets/wails_dev.mp4 differ diff --git a/docs/src/assets/wails_init.mp4 b/docs/src/assets/wails_init.mp4 new file mode 100644 index 000000000..337bc0f5a Binary files /dev/null and b/docs/src/assets/wails_init.mp4 differ diff --git a/docs/src/components/CardAnimation.astro b/docs/src/components/CardAnimation.astro new file mode 100644 index 000000000..5d3de111b --- /dev/null +++ b/docs/src/components/CardAnimation.astro @@ -0,0 +1,62 @@ +--- +--- + diff --git a/docs/src/content/authors.ts b/docs/src/content/authors.ts new file mode 100644 index 000000000..0500490f3 --- /dev/null +++ b/docs/src/content/authors.ts @@ -0,0 +1,11 @@ +import type { StarlightBlogUserConfig } from "starlight-blog"; + +type Authors = NonNullable["authors"]; +export const authors: Authors = { + leaanthony: { + name: "Lea Anthony", + title: "Maintainer of Wails", + url: "https://github.com/leaanthony", + picture: "https://github.com/leaanthony.png", + }, +}; diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts new file mode 100644 index 000000000..dae3197fe --- /dev/null +++ b/docs/src/content/config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from "astro:content"; +import { docsSchema, i18nSchema } from "@astrojs/starlight/schema"; +import { blogSchema } from "starlight-blog/schema"; + +export const collections = { + i18n: defineCollection({ type: "data", schema: i18nSchema() }), + docs: defineCollection({ + schema: docsSchema({ extend: (context) => blogSchema(context) }), + }), +}; diff --git a/docs/src/content/docs/DEVELOPER_GUIDE.md b/docs/src/content/docs/DEVELOPER_GUIDE.md new file mode 100644 index 000000000..b494e5473 --- /dev/null +++ b/docs/src/content/docs/DEVELOPER_GUIDE.md @@ -0,0 +1,542 @@ +--- +title: Wails v3 Developer Guide +description: Comprehensive onboarding for engineering and debugging the Wails v3 codebase +slug: developer-guide +sidebar: + label: Developer Guide + order: 50 +--- + +## How to Use This Guide +- **Scope**: Everything documented here applies to the `v3/` branch of Wails, tracking the current workspace state (commit you have checked out) and intentionally ignores the legacy `v2/` runtime. +- **Audience**: Senior Go + desktop developers who need to become productive at fixing bugs or extending the v3 runtime, CLI, or build tooling. +- **Format**: Top-down description → component drill-down → pseudocode for critical flows → teardown guidance → reference tables. +- **Navigation**: Skim the diagrams first, then expand sections relevant to the subsystem you are editing. + +```d2 +direction: down +Developer: "Developer" +CLI: "CLI Layer\n(cmd/wails3)" +Commands: "Command Handlers\n(internal/commands)" +TaskRunner: "Task Runner\n(internal/commands/task.go)" +Taskfile: "Taskfile\n(v3/Taskfile.yaml)" +BuildPipeline: "Build/Packaging\n(internal/*)" +Templates: "Templates\n(internal/templates)" +RuntimeGen: "Runtime Assets\n(internal/runtime)" +Application: "Application Core\n(pkg/application)" +AssetServer: "Asset Server\n(internal/assetserver)" +MessageBridge: "Message Processor\n(pkg/application/messageprocessor*.go)" +Webview: "WebView Implementations\n(pkg/application/webview_window_*.go)" +Services: "Services & Bindings\n(pkg/services/*)" +Platform: "Platform Layers\n(pkg/mac, pkg/w32, pkg/events, pkg/ui)" + +Developer -> CLI: "runs wails3" +CLI -> Commands: "registers subcommands" +Commands -> TaskRunner: "wraps build/package/dev" +TaskRunner -> Taskfile: "executes tasks" +Commands -> BuildPipeline: "invokes packagers & generators" +BuildPipeline -> Templates: "renders scaffolds" +Commands -> RuntimeGen: "builds runtime JS" +RuntimeGen -> Application: "embedded assets" +Application -> AssetServer: "serves HTTP" +Application -> MessageBridge: "routes runtime calls" +MessageBridge -> Webview: "bridge over WebView" +Application -> Services: "binds Go services" +Application -> Platform: "dispatches to OS" +AssetServer -> Webview: "feeds frontend" +Services -> MessageBridge: "expose methods" +``` + +--- + +## Repository Layout (v3 only) +| Path | Purpose | Highlights | +| --- | --- | --- | +| `v3/cmd/wails3` | CLI entrypoint | Command registration, build info capture (`main.go`). | +| `v3/internal/commands` | Implementation for each CLI subcommand | Includes build/package/dev tooling, template generators, diagnostics. | +| `v3/internal/flags` | Typed flag definitions for CLI commands | Shared flag structs (e.g. `Build`, `Package`, `ServiceInit`). | +| `v3/internal/assetserver` | Embedded HTTP server powering the runtime | Request routing, middleware, webview handoff. | +| `v3/internal/runtime` | Generates runtime JS payload | Platform-specific flag injection, dev/prod toggles. | +| `v3/internal/templates` | Project/frontend scaffolding | Multiple frontend stacks with shared `_common` assets. | +| `v3/internal/service` | Service template generator | Creates Go service skeletons. | +| `v3/internal/packager` | Wrapper around nfpm for Linux packaging | Generates `.deb`, `.rpm`, `.apk`, etc. | +| `v3/internal/term`, `term2` | Console styling helpers | Shared across commands. | +| `v3/pkg/application` | Core runtime used by user apps | Windowing, events, bindings, asset hosting, shutdown. | +| `v3/pkg/events` | Enumerates runtime event IDs | Shared constants used on Go and JS sides. | +| `v3/pkg/services` | Built-in services (badge, notifications, sqlite, etc.) | Reference implementations for custom services. | +| `v3/pkg/icons`, `v3/pkg/ui` | Icon tooling & UI examples | Support packages used by CLI utilities. | +| `v3/pkg/mac`, `v3/pkg/w32`, `v3/pkg/mcp` | Platform bindings | cgo / pure Go glue for OS integrations. | +| `v3/scripts`, `v3/tasks`, `v3/test`, `v3/tests` | Automation helpers | Task runners, Docker harnesses, regression suites. | +| `v3/Taskfile.yaml` | Root Taskfile consumed by CLI wrappers | Defines build/test workflows referenced by commands. | + +--- + +## CLI and Tooling Layer +### Entry Point: `v3/cmd/wails3/main.go` +The CLI uses `github.com/leaanthony/clir` to declaratively register subcommands. Build metadata is captured in `init()` by reading `debug.ReadBuildInfo` and storing it into `internal/commands.BuildSettings` for later introspection (`wails3 tool buildinfo`). + +```pseudo +func main(): + app := clir.NewCli("wails", "The Wails3 CLI", "v3") + register simple actions (docs, sponsor) -> open browser + register functional subcommands that delegate to internal/commands + init -> commands.Init + build -> commands.Build (wraps task) + dev -> commands.Dev (starts watcher) + package -> commands.Package + doctor -> commands.Doctor + releasenotes -> commands.ReleaseNotes + task -> commands.RunTask (Taskfile wrapper) + generate subtree -> {build-assets, icons, syso, runtime, template, ...} + update subtree -> {build-assets, cli} + service -> {init} + tool subtree -> {checkport, watcher, cp, buildinfo, package, version} + version -> commands.Version + defer printFooter() (prints docs/sponsor hints unless disabled) + on error -> log with pterm and exit 1 +``` +Key takeaways: +- All heavy lifting happens inside `internal/commands`. The CLI just constructs flag structs and hands them off. +- Build/package commands are aliases for `wails3 task `; real work is defined in `v3/Taskfile.yaml`. +- `commands.DisableFooter` prevents duplicate footer output when commands open browsers or produce their own footers. + +### Flag Definitions: `v3/internal/flags` +- Each subcommand gets a struct tagged with descriptions/defaults used by `clir`. +- Example: `flags.Build` holds options for `wails3 build`, while `flags.GenerateBindingsOptions` describes binding generator inputs. +- Flag structs double as configuration objects passed into command implementations, so the same struct layout must be respected by tests. + +### Command Implementations: Highlights +| File | Responsibility | +| --- | --- | +| `internal/commands/init.go` | Scaffolds new projects from templates, resolves template metadata, writes config (`wails.json`). | +| `internal/commands/dev.go` | Sets up the dev server port, populates `FRONTEND_DEVSERVER_URL`, and forwards to the file watcher. | +| `internal/commands/watcher.go` | Loads `taskfile`-compatible YAML (`dev_mode`), instantiates `refresh/engine`, registers signal handlers, and keeps the engine alive until interrupted. | +| `internal/commands/build-assets.go` | Materializes packaging assets from embedded templates (`gosod` extractor). Handles defaults, path normalization, and YAML config ingestion. | +| `internal/commands/generate_template.go` | Creates template stubs by reading `internal/templates`. | +| `internal/commands/tool_*` | Misc utilities: port checks, file copy, semantic version bump (`tool_version.go`), etc. | +| `internal/commands/task_wrapper.go` | Implements `wails3 build/package` alias behavior by re-invoking the `task` subcommand with rewired `os.Args`. | + +```pseudo +func wrapTask(command string, otherArgs []string): + warn("alias for task") + newArgs := ["wails3", "task", command] + otherArgs + os.Args = newArgs + return RunTask(&RunTaskOptions{Name: command}, otherArgs) +``` + +### Task Runner Integration: `internal/commands/task.go` +- Wraps `github.com/wailsapp/task/v3` for cross-platform task execution. +- Accepts both positional task names (`wails3 task dev -- `) and `--name` flags. +- Validates mutually exclusive options (e.g., `--dir` vs `--taskfile`). +- Supports list/status/JSON output inherited from upstream Task library. +- Uses `BuildSettings` to print the bundled Task version when `--version` is passed. + +### Dev Mode Flow (`wails3 dev`) +1. Resolve Vite port preference (`--port`, `WAILS_VITE_PORT`, default `9245`). +2. Check that the port is free by attempting to `net.Listen` and immediately closing. +3. Export `WAILS_VITE_PORT` and set `FRONTEND_DEVSERVER_URL` (`http` vs `https` based on `--secure`). +4. Invoke `Watcher` with the configured Taskfile path (defaults to `./build/config.yml`). + +### Packaging & Distribution Commands +- `GenerateSyso` (Windows resource) writes `.syso` files for icon/resource embedding. +- `GenerateIcons` converts SVG/PNGs into platform icon bundles. +- `ToolPackage` uses `internal/packager` to drive `nfpm` for Linux packages. +- `GenerateAppImage`, `generate_webview2`, and `package/msix.go` provide OS-specific installers. + +--- + +## Template & Asset Generation +### Project Templates: `v3/internal/templates` +- Each frontend stack lives under its own directory (React, Vue, Svelte, Solid, Qwik, Vanilla, Lit, Preact) with `-ts` variants. +- `_common` contains shared scaffolding (Go module layout, `wails.json`, default assets). +- `generate template` extracts template files via `gosod` into the target project directory, honoring parameters collected during `wails3 init`. + +### Build Assets: `internal/commands/build-assets.go` +Embedded assets define packaging metadata such as installers, file associations, protocol handlers, and Windows installer scripts. + +```pseudo +func GenerateBuildAssets(opts): + opts.Dir = abs(opts.Dir); mkdir if missing + fill empty fields (ProductComments, Identifier, BinaryName, etc.) with sensible defaults + load `build_assets` FS subtree and render via gosod using opts (includes file associations + protocols) + render `updatable_build_assets` on top so user edits stay intact +``` + +### Runtime Bundles: `internal/runtime` +- `Core()` concatenates `runtimeInit + flags + invoke + environment` to produce the JS bootstrap injected into every window. +- Platform-specific files (`runtime_windows.go`, `runtime_linux.go`, etc.) define how Go exposes `window._wails.invoke` and system flags (resize handle sizes on Windows via `pkg/w32`). +- `GenerateRuntime` command serializes pre-built runtime assets for embedding. + +--- + +## Runtime Boot Sequence (pkg/application) +### Application Construction: `pkg/application/application.go` +A singleton `App` is created via `application.New(options)` and stored in `globalApplication`. + +```pseudo +func New(options): + if globalApplication exists -> return it (enforces singleton) + mergeApplicationDefaults(options) + app := newApplication(options) // debug vs production build tags + globalApplication = app + fatalHandler(app.handleFatalError) + configure Logger (debug -> DefaultLogger, prod -> discard) + install default signal handler unless disabled (ctrl+c, SIGTERM -> App.Quit) + log startup + platform info + customEventProcessor := NewWailsEventProcessor(app.Event.dispatch) + messageProc := NewMessageProcessor(app.Logger) + assetOpts := assetserver.Options { + Handler: options.Assets.Handler (default BundledAssetFileServer) + Middleware: Chain(user middleware, internal middleware for /wails endpoints) + Logger: app.Logger (or discard when DisableLogging) + } + asset server intercepts: + /wails/runtime.js -> bundled runtime asset + /wails/runtime -> messageProc.ServeHTTP + /wails/capabilities -> emits capabilities JSON + /wails/flags -> marshals platform flags via impl.GetFlags + app.assets = AssetServer(assetOpts) + app.bindings = NewBindings(options.MarshalError, options.BindAliases) + app.options.Services = clone(options.Services) + process key bindings if provided + register OnShutdown hook if provided + if SingleInstance configured -> newSingleInstanceManager + return app +``` + +Key supporting structures: +- `Options` (`application_options.go`) configures assets, services, platform-specific knobs, signal handling, keybindings, custom marshaling, and single-instance behavior. +- `signal.NewSignalHandler` (`internal/signal`) watches OS signals, invoking `App.Quit` while printing customizable exit messages. + +### Run Loop & Lifecycle +`App.Run()` orchestrates startup, service lifecycle, channel fan-out, and the platform event loop. + +```pseudo +func (a *App) Run(): + lock runLock; guard against double runs + defer cancel app.Context (ensures goroutines exit on failure) + execute a.preRun() (noop in production, debug logging otherwise) + a.impl = newPlatformApp(a) // windowsApp, darwinApp, linuxApp depending on GOOS + defer a.shutdownServices() + services := clone(options.Services); options.Services = nil + for service in services: + startupService(service) // binds methods, registers HTTP routes, calls ServiceStartup + append to options.Services so shutdown order is reversed + spawn goroutines reading buffered channels: + applicationEvents -> EventManager.handleApplicationEvent + windowEvents -> handleWindowEvent + webviewRequests -> assets.ServeWebViewRequest + windowMessageBuffer -> handleWindowMessage (custom vs wails: prefix) + windowKeyEvents -> handleWindowKeyEvent + windowDragAndDropBuffer -> handleDragAndDropMessage (debug logs) + menuItemClicked -> Menu.handleMenuItemClicked + mark running=true; flush pendingRun queue by invoking runnable.Run() asynchronously + if GOOS == darwin -> set application menu immediately + if Icon provided -> impl.setIcon + return a.impl.run() (enters platform main loop) +``` + +```d2 +direction: down +root: "App.Run dispatch hub" +root -> applicationEvents: "goroutine" +applicationEvents -> EventManager: "Event.handleApplicationEvent" +root -> windowEvents: "goroutine" +windowEvents -> Windows: "handleWindowEvent" +root -> webviewRequests +webviewRequests -> AssetServer: "ServeWebViewRequest" +root -> windowMessages +windowMessages -> MessageHandlers: "HandleMessage / RawMessageHandler" +root -> windowKeyEvents +windowKeyEvents -> KeyBinding: "HandleKeyEvent" +root -> dragDropBuffer +dragDropBuffer -> Windows: "HandleDragAndDrop" +root -> menuItemClicked +menuItemClicked -> MenuManager +``` + +### Shutdown Path +- `shutdownServices()` iterates bound services in reverse start order, invoking `ServiceShutdown` when implemented and cancelling the app context. +- `OnShutdown` hooks run synchronously on the main thread; `PostShutdown` runs last (useful on macOS where `Run` may block indefinitely). +- `cleanup()` (triggered via `impl.destroy()` and `App.Quit`) sets `performingShutdown`, cancels the context, runs queued shutdown tasks, releases the single-instance manager, and closes windows/system trays. + +--- + +## Message Bridge & Asset Server +### Message Processor (`messageprocessor.go` et al.) +- Handles `/wails/runtime` POST requests from the frontend. +- Dispatch keyed by `object` query parameter: + - `callRequest` (0) → service bindings. + - `clipboardRequest`, `applicationRequest`, `eventsRequest`, `contextMenuRequest`, `dialogRequest`, `windowRequest`, `screensRequest`, `systemRequest`, `browserRequest`, `cancelCallRequest`. +- Uses HTTP headers `x-wails-window-id` / `name` to resolve the target `Window` (`getTargetWindow`). +- Maintains `runningCalls` map to support cancellation by call ID. + +```pseudo +func processCallMethod(method, rw, req, window, params): + args := params.Args() + callID := args.String("call-id") + if method == CallBinding: + options := params.ToStruct(CallOptions) + ctx, cancel := context.WithCancel(request.Context) + register cancel in runningCalls[callID] + respond 200 OK immediately + go func(): + boundMethod := lookup by name or ID (honour aliases) + if not found -> CallError(kind=ReferenceError) + if window != nil -> ctx = context.WithValue(ctx, WindowKey, window) + result, err := boundMethod.Call(ctx, options.Args) + on CallError -> window.CallError(callID, json, knownError=true) + marshal result -> jsonResult + window.CallResponse(callID, jsonResult) + cleanup runningCalls entry and cancel context + ``` +``` + +- Errors bubble to the frontend via `CallError` JSON with `ReferenceError`, `TypeError`, or `RuntimeError` kinds. +- Cancellation (`cancelCallRequest`) removes the call ID from `runningCalls` and cancels the context. + +### Asset Server (`internal/assetserver`) +- `AssetServer.ServeHTTP` wraps responses with MIME sniffing, logs duration, window metadata, and status codes. +- `serveHTTP` intercepts root/index requests to optionally serve a localized index fallback (`defaultIndexHTML` uses `accept-language`). +- `/wails/*` special routes and user-defined services share the same middleware chain, allowing injection of auth, logging, or routing. +- Dev vs production logic uses environment variable `FRONTEND_DEVSERVER_URL`; when present, requests proxy to the external dev server instead of the embedded assets (`asset_fileserver.go`). +- `AttachServiceHandler` mounts service-provided HTTP handlers under custom routes (`ServiceOptions.Route`). +- WebView-specific request/response types are implemented in `internal/assetserver/webview`, providing platform-native bridges to feed asset bytes directly into the webview without round-tripping through TCP when possible. + +### Runtime JS Exposure +- `/wails/runtime.js` serves the concatenated runtime string produced by `internal/runtime`, ensuring the frontend has access to `window.wails` helpers. +- `/wails/flags` serializes `Options.Flags` extended by the platform implementation (`windowsApp.GetFlags` injects resize handles, etc.), allowing frontend startup logic to adjust to platform constraints. +- `/wails/capabilities` exposes a JSON describing features like native drag support (populated by `internal/capabilities`). + +--- + +## Windowing & UI Layer (`pkg/application`) +### Window Manager (`window_manager.go`) +- Maintains the authoritative map of `Window` instances keyed by numeric ID. +- Defers actual `Run` execution if the app has not started (`runOrDeferToAppRun`). +- Provides lookup by name/ID, iteration (`GetAll`), and lifecycle hooks (`OnCreate`). +- Works in tandem with `App.pendingRun` to ensure windows created before `App.Run` are executed after the platform loop is ready. + +### Webview Windows (`webview_window.go` + `webview_window_.go`) +- `WebviewWindow` wraps a platform-specific `webviewWindowImpl` with synchronized maps for event listeners, key bindings, menus, and asynchronous cancellers. +- Supports full window control API: sizing, positioning, zoom, devtools, menu bar toggles, context menu injection, border size queries, drag/resize operations. +- `HandleMessage`, `HandleKeyEvent`, and drag-and-drop handlers are invoked by the central channels in `App.Run`. +- On runtime readiness, windows emit `events.Common.WindowRuntimeReady` to allow frontends to hydrate state once the JS bridge is loaded. + +### Event System (`events.go`, `context_*`) +- `ApplicationEvent` and `WindowEvent` objects carry strongly-typed contexts (`ApplicationEventContext`, `WindowEventContext`) to provide structured data (files dropped, URLs, screen info, etc.). +- Channels `applicationEvents`, `windowEvents`, and `menuItemClicked` are buffered to avoid blocking the OS event loop. +- `EventProcessor` manages custom user events, offering `On`, `Once`, `Emit`, and hook registration for pre-dispatch inspection. +- Platform-specific files (`events_common_windows.go`, `events_common_darwin.go`, etc.) translate native callbacks into the common channel structure. + +### Managers & Subsystems +| Manager | File | Responsibility | +| --- | --- | --- | +| `ContextMenuManager` | `context_menu_manager.go` | Creates native context menus, wires handlers. | +| `DialogManager` | `dialog_manager.go` + OS-specific files | Wraps native file/message dialogs. | +| `ClipboardManager` | `clipboard_manager.go` | Provides cross-platform clipboard access, proxies to OS-specific implementations. | +| `ScreenManager` | `screenmanager.go` | Exposes multi-monitor info, resolution, scaling. | +| `SystemTrayManager` | `system_tray_manager.go` | Manages tray icons, menu interactions. | +| `BrowserManager` | `browser_manager.go` | Handles window navigation and devtools. | +| `KeyBindingManager` | `key_binding_manager.go` | Registers accelerators and callbacks. | +| `EnvironmentManager` | `environment_manager.go` | Tracks env state (dark mode, accent color) exposed to the frontend. | + +--- + +## Services & Binding Engine +### Service Definition (`service.go`, `bindings.go`) +- Services are user-provided structs implementing optional interfaces: + - `ServiceStartup(ctx context.Context, opts ServiceOptions) error` + - `ServiceShutdown() error` + - `ServeHTTP` (when exposing HTTP routes) + - Methods to be bound must be exported, live on pointer receivers of named types, and cannot be generic. + +```pseudo +func startupService(service): + bindings.Add(service) // reflect, hash method signatures, register alias map + if service.Route != "": + if instance implements http.Handler -> attach to asset server + else -> attach fallback handler returning 503 + if instance implements ServiceStartup -> call with app context +``` + +- `Bindings.Add` enumerates exported methods via reflection, hashes fully qualified names using `internal/hash`, and stores them in `boundMethods` (by name) and `boundByID` (by numeric ID). Hash collisions are guarded with explicit error messages instructing developers to rename methods. +- `CallOptions` submitted from the frontend include `MethodID`, `MethodName`, and JSON-encoded args; the binding engine supports both to allow smaller payloads in production (IDs) while keeping dev ergonomics (names). + +### Error Marshalling & Aliases +- Custom error marshaling can be provided per service (`ServiceOptions.MarshalError`) or globally (`Options.MarshalError`), allowing user-defined JSON payloads. +- `BindAliases` maps alternative IDs to primary method IDs, helpful when generated bindings are versioned and need to stay stable across refactors. + +### Context Propagation +- During calls, the bridge injects `context.Context` with the window (`context.WithValue(ctx, WindowKey, window)`) so services can inspect which window invoked them (e.g., to push events back via `window.Emit`). +- Cancellation is propagated when the frontend aborts a promise or the app shuts down (`App.cancel()`). + +--- + +## Configuration Options & Their Effects +### `application.Options` Highlights (`application_options.go`) +| Field | Effect on Runtime | +| --- | --- | +| `Assets.Handler` | Base HTTP handler serving static assets. Overrides default embedded bundle, but `/wails/*` middleware still executes. | +| `Assets.Middleware` | Injects custom middleware before internal routes; can short-circuit requests to implement routing or authentication. | +| `Assets.DisableLogging` | Swaps the asset server logger to a discard handler to avoid noisy logs. | +| `Flags` | Merged with platform flags and exposed to the frontend at `/wails/flags`. Changing affects frontend boot configuration. | +| `Services` | List of services auto-registered during `Run`. Order matters for startup/shutdown. | +| `BindAliases` | Remaps method IDs used by the runtime. Critical when regenerating bindings without breaking existing frontend code. | +| `KeyBindings` | Global accelerator map executed per window. Processed during `New`, stored per window at runtime. | +| `OnShutdown` / `PostShutdown` | Lifecycle hooks executed during teardown. `OnShutdown` runs before services shut down; `PostShutdown` runs after platform loop returns (if ever). | +| `ShouldQuit` | Gatekeeper invoked when the user attempts to quit (e.g., Cmd+Q). Returning `false` keeps the app alive. | +| `RawMessageHandler` | Receives messages that do not start with the `wails:` prefix, enabling custom bridge protocols aside from service calls. | +| `WarningHandler` / `ErrorHandler` | Overrides default slog warnings/errors for system-level diagnostics. | +| `FileAssociations` | Used during packaging and when launch arguments are parsed on Windows/macOS to emit `ApplicationOpenedWithFile`. | +| `SingleInstance` | Triggers single-instance manager setup; options include encryption key, exit code, and `OnSecondInstanceLaunch` callback. | + +### Single Instance Workflow (`single_instance.go`) +```pseudo +func newSingleInstanceManager(app, opts): + if opts == nil -> return nil + start goroutine that reads secondInstanceBuffer and dispatches OnSecondInstanceLaunch + lock := newPlatformLock(opts.UniqueID) // OS-specific + if lock.acquire fails -> already running + return manager + +func manager.notifyFirstInstance(): + data := {Args, WorkingDir, AdditionalData} + payload := json.Marshal(data) or encrypt(AES-256-GCM) + lock.notify(payload) +``` +- On startup, if another instance is detected, the CLI prints a warning, invokes `notifyFirstInstance`, and exits with the configured code. +- Platform locks live in `single_instance_.go`, using named pipes, mutexes, or DBus depending on OS. + +--- + +## Platform Implementations +| Platform | Entry Files | Notes | +| --- | --- | --- | +| Windows | `application_windows.go`, `webview_window_windows*.go`, `pkg/w32` | Integrates with Win32 APIs, WebView2 via `go-webview2`. Handles taskbar recreation, dark mode, accent color, custom title bars, drag/resize, `WndProcInterceptor`. | +| macOS | `application_darwin.go`, `webview_window_darwin*.go`, `pkg/mac` | Objective-C bridges (via cgo) for NSApplication, implements activation policy, app/URL events, and main thread run loops. | +| Linux | `application_linux.go`, `webview_window_linux*.go`, `pkg/application/linux_*` | GTK/WebKit2 integration, theme detection, DBus single-instance hooks. Pure Go fallback provided (`linux_purego`). | +| Runtime JS | `runtime_.go` | Defines `window._wails.invoke` binding for each platform (e.g., `webkit.messageHandlers.external.postMessage` on Linux/macOS, `chrome.webview.postMessage` on Windows). | + +Key responsibilities of each platform struct (e.g., `windowsApp`): +- Manage native window/class registration, system tray IDs, focus management. +- Emit platform-specific events into `applicationEvents` when OS notifications occur (power events, taskbar resets, theme changes). +- Provide platform-specific implementations of `setApplicationMenu`, `dispatchOnMainThread`, `GetFlags`, etc. + +--- + +## Packaging & Distribution Pipeline +- **Linux NFPM**: `internal/packager/packager.go` wraps `nfpm` to parse YAML recipes and build `.deb`, `.rpm`, `.apk`, `.ipk`, or Arch packages. CLI `wails3 tool package` selects the format and output path. +- **Windows**: `msix.go`, `generate_syso.go`, and build assets templates generate installers, embedding certificate details and executable paths from `BuildAssetsOptions`. +- **macOS**: `dmg` command scaffolds DMG packaging scripts. +- **AppImage**: `GenerateAppImage` compiles a portable AppImage by invoking the bundled `linuxdeploy` scripts (`appimage_testfiles` contains test fixtures). +- **Build Info**: `tool_buildinfo.go` prints deduced module versions using the captured `BuildSettings` map. + +During release automation (see `v3/tasks/release`), these commands are orchestrated by Taskfile recipes to build cross-platform artifacts. + +--- + +## Diagnostics, Logging & Error Handling +- Logging defaults differ: debug builds use structured logging (`DefaultLogger`), production builds discard framework logs unless a logger is provided. +- `fatalHandler` sets a package-level panic handler that exits the process on unrecoverable errors (panic + formatted message). +- `App.handleError`/`handleWarning` route errors to user-provided handlers or `Logger.Error/Warn`. +- Signal handling (SIGINT/SIGTERM) ensures the watcher and the app cleanly shut down, releasing OS resources and closing windows. +- `internal/term` and `term2` provide colored console output; used heavily in CLI warnings/errors to standardize UX (`term.Warningf`, `term.Hyperlink`). + +--- + +## Developer Workflow Checklist +1. **Familiarize with Taskfile**: Run `task -l` or `wails3 task --list` to see available workflows (`install`, `precommit`, example builds). +2. **Local CLI Install**: `task v3:install` compiles the CLI (`go install` inside `cmd/wails3`). +3. **Run Dev Mode**: `wails3 dev` (or `task dev`) spawns the watcher, sets `FRONTEND_DEVSERVER_URL`, and tails the Go backend. +4. **Build Release**: `wails3 build` → alias for `task build` (see `Taskfile.yaml` for pipeline details, including `go build`, asset bundling, templating, packaging). +5. **Packaging Tests**: Use `wails3 tool package --type deb --config ...` or `GenerateBuildAssets` to refresh installer scaffolding. +6. **Pre-commit routine**: `task v3:precommit` runs `go test ./...` and repository formatters before opening PRs. + +--- + +## Lifecycles at a Glance +### Application Startup Timeline +1. CLI entry (user application) calls `wails.Run()` (inside user code) → constructs `application.Options`. +2. `application.New` merges defaults, sets up asset server, bindings, single-instance manager, event processor. +3. User code configures windows/services (often via `app.Window.New()` or services via `application.NewService()`). +4. `App.Run`: + - Validates no concurrent runs. + - Instantiates platform app (calls Objective-C/Win32/GTK setup). + - Starts services, mounts HTTP routes. + - Spins up channel goroutines and flushes deferred runnables. + - Enters platform main loop (`impl.run()`), bridging native events into Go. +5. Frontend loads `runtime.js` → initializes `window.wails`, fetches `/wails/flags` & `/wails/capabilities`, and begins invoking bound methods via JSON payloads. + +### Shutdown Timeline +1. `App.Quit` (called from user code, signal handler, or platform event) sets `performingShutdown`. +2. Cancels `context.Context` to abort long-running service calls. +3. Executes `OnShutdown` tasks in registration order. +4. Invokes `shutdownServices` in reverse startup order; each service can release resources. +5. Releases single-instance lock, destroys windows/system trays, and calls platform `destroy()`. +6. Executes `PostShutdown` hook (if provided) then exits the process. + +--- + +## Key Channels & Buffers +| Channel | Producer | Consumer | Purpose | +| --- | --- | --- | --- | +| `applicationEvents` (`events.go`) | Platform apps (`application_.go`) and system signals | `App.Event.handleApplicationEvent` | Broadcast application-level events (startup, theme changes, power events). | +| `windowEvents` | Platform window callbacks | `App.handleWindowEvent` | Window lifecycle events (move, resize, focus). | +| `webviewRequests` | Asset server (`webview/request.go`) | `App.handleWebViewRequest` | Direct webview resource streaming. | +| `windowMessageBuffer` | Platform message pump | `App.handleWindowMessage` | Messages from WebView (prefixed `wails:`) -> service invocations or raw handlers. | +| `windowDragAndDropBuffer` | Drag/drop handlers | `App.handleDragAndDropMessage` | File drop notifications with DOM metadata. | +| `windowKeyEvents` | Accelerator handlers | `App.handleWindowKeyEvent` | Dispatch registered key bindings to the appropriate window. | +| `menuItemClicked` | Native menu callbacks | `Menu.handleMenuItemClicked` | Execute Go handlers for menu events. | + +All buffers default to size 5 to absorb bursty events without blocking UI threads. + +--- + +## Environment Variables & Configuration Hooks +| Variable | Used By | Effect | +| --- | --- | --- | +| `FRONTEND_DEVSERVER_URL` | Asset server (`GetDevServerURL`) | Redirects asset requests to external dev server instead of embedded bundle. | +| `WAILS_VITE_PORT` | `commands.Dev` | Controls Vite dev server port advertised to frontend tasks. | +| `NO_COLOR` | Task runner | Disables colored CLI output. | +| `WAILSENV` | User applications (common pattern) | Choose between dev/prod logic when bootstrapping `Options`. | + +--- + +## Debugging Tips +- **Inspect Call Flow**: Enable verbose logging (`Options.LogLevel = slog.LevelDebug`) to trace binding calls and HTTP requests. +- **Capture Runtime JS**: Hit `http://127.0.0.1:/wails/runtime.js` while running in dev to verify injected flags and capabilities. +- **Watch Service Registration**: Look for `Registering bound method` debug lines (`bindings.go`) to confirm service methods were detected. +- **Single Instance Issues**: Check platform lock files (e.g., `%AppData%\Wails\Locks` on Windows, `$XDG_RUNTIME_DIR` on Linux) and ensure the encryption key is consistent. +- **Asset Server Paths**: Use `assetserver.ServeFile` error output (logged via slog) to diagnose missing assets or MIME type issues. + +--- + +## Extending the Codebase +1. **Adding a CLI Command**: Implement function in `internal/commands`, define flags under `internal/flags`, register in `cmd/wails3/main.go`. Document the Taskfile hook if it wraps tasks. +2. **New Service Template**: Update `internal/service/template`, ensure `gosod` placeholders map to new options, and adjust `flags.ServiceInit` accordingly. +3. **Runtime Feature Flags**: Modify `internal/runtime/runtime_.go` to expose additional data via `/wails/flags`; update frontend expectations. +4. **Custom Middleware**: Provide `Options.Assets.Middleware` to inject auth/logging; remember middleware runs before internal routes, so call `next` for default behavior. +5. **Platform-Specific Fixes**: Locate corresponding `application_.go` and `webview_window_*.go` files. Keep cross-platform interfaces (`platformApp`, `webviewWindowImpl`) stable. + +--- + +## Reference: Critical Files by Responsibility +| Responsibility | File(s) | Notes | +| --- | --- | --- | +| CLI bootstrap | `v3/cmd/wails3/main.go` | Command registration, browser helpers. | +| Build info capture | `v3/cmd/wails3/main.go:init` | Populates `commands.BuildSettings`. | +| Task aliasing | `v3/internal/commands/task_wrapper.go` | Rewrites `os.Args` and invokes `RunTask`. | +| Asset server core | `v3/internal/assetserver/assetserver.go` | Middleware, logging, fallback handling. | +| Application singleton | `v3/pkg/application/application.go:56` | Global `App` creation. | +| Service binding | `v3/pkg/application/bindings.go` | Reflection, aliasing, error marshaling. | +| Message bridge | `v3/pkg/application/messageprocessor*.go` | Runtime call routing. | +| Event channels | `v3/pkg/application/events.go` | Buffered channels + processors. | +| Window API | `v3/pkg/application/webview_window.go` | Platform interface and user-facing API. | +| Single-instance control | `v3/pkg/application/single_instance*.go` | Locking and IPC. | +| Platform adapters | `v3/pkg/application/application_.go` | Native message loops, start/stop. | +| Packaging | `v3/internal/packager/packager.go`, `internal/commands/tool_package.go` | Linux packaging wrappers. | + +--- + +Armed with these maps, pseudocode, and lifecycle notes, you can confidently trace any bug from CLI invocation through runtime dispatch, into platform-specific glue, and back. Use the pseudocode as a mental model, verify concrete behavior by jumping into the referenced files, and lean on the diagrams to understand how data flows between layers. \ No newline at end of file diff --git a/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md b/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md new file mode 100644 index 000000000..dbc8d9776 --- /dev/null +++ b/docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md @@ -0,0 +1,206 @@ +--- +slug: blog/wails-v2-beta-for-windows +title: Wails v2 Beta for Windows +authors: [leaanthony] +tags: [wails, v2] +date: 2021-09-27 +--- + +![wails screenshot](../../../assets/blog-images/wails.webp) + +When I first announced Wails on Reddit, just over 2 years ago from a train in +Sydney, I did not expect it to get much attention. A few days later, a prolific +tech vlogger released a tutorial video, gave it a positive review and from that +point on, interest in the project has skyrocketed. + +It was clear that people were excited about adding web frontends to their Go +projects, and almost immediately pushed the project beyond the proof of concept +that I had created. At the time, Wails used the +[webview](https://github.com/webview/webview) project to handle the frontend, +and the only option for Windows was the IE11 renderer. Many bug reports were +rooted in this limitation: poor JavaScript/CSS support and no dev tools to debug +it. This was a frustrating development experience but there wasn't much that +could have been done to rectify it. + +For a long time, I'd firmly believed that Microsoft would eventually have to +sort out their browser situation. The world was moving on, frontend development +was booming and IE wasn't cutting it. When Microsoft announced the move to using +Chromium as the basis for their new browser direction, I knew it was only a +matter of time until Wails could use it, and move the Windows developer +experience to the next level. + +Today, I am pleased to announce: **Wails v2 Beta for Windows**! There's a huge +amount to unpack in this release, so grab a drink, take a seat and we'll +begin... + +### No CGO Dependency! + +No, I'm not joking: _No_ _CGO_ _dependency_ 🤯! The thing about Windows is that, +unlike MacOS and Linux, it doesn't come with a default compiler. In addition, +CGO requires a mingw compiler and there's a ton of different installation +options. Removing the CGO requirement has massively simplified setup, as well as +making debugging an awful lot easier. Whilst I have put a fair bit of effort in +getting this working, the majority of the credit should go to +[John Chadwick](https://github.com/jchv) for not only starting a couple of +projects to make this possible, but also being open to someone taking those +projects and building on them. Credit also to +[Tad Vizbaras](https://github.com/tadvi) whose +[winc](https://github.com/tadvi/winc) project started me down this path. + +### WebView2 Chromium Renderer + +![devtools screenshot](../../../assets/blog-images/devtools.png) + +Finally, Windows developers get a first class rendering engine for their +applications! Gone are the days of contorting your frontend code to work on +Windows. On top of that, you get a first-class developer tools experience! + +The WebView2 component does, however, have a requirement to have the +`WebView2Loader.dll` sitting alongside the binary. This makes distribution just +that little bit more painful than we gophers are used to. All solutions and +libraries (that I know of) that use WebView2 have this dependency. + +However, I'm really excited to announce that Wails applications _have no such +requirement_! Thanks to the wizardry of +[John Chadwick](https://github.com/jchv), we are able to bundle this dll inside +the binary and get Windows to load it as if it were present on disk. + +Gophers rejoice! The single binary dream lives on! + +### New Features + +![wails-menus screenshot](../../../assets/blog-images/wails-menus.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +There is now the option to generate IDE configuration along with your project. +This means that if you open your project in a supported IDE, it will already be +configured for building and debugging your application. Currently VSCode is +supported but we hope to support other IDEs such as Goland soon. + +![vscode screenshot](../../../assets/blog-images/vscode.webp) + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `` tag with a local src path. Want to use a cool font? Copy +it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +![browser screenshot](../../../assets/blog-images/browser.webp) + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger and auto reload of + the application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote screenshot](../../../assets/blog-images/remote.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### In Conclusion + +Wails v2 represents a new foundation for the project. The aim of this release is +to get feedback on the new approach, and to iron out any bugs before a full +release. Your input would be most welcome. Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +There were many twists and turns, pivots and u-turns to get to this point. This +was due partly to early technical decisions that needed changing, and partly +because some core problems we had spent time building workarounds for were fixed +upstream: Go’s embed feature is a good example. Fortunately, everything came +together at the right time, and today we have the very best solution that we can +have. I believe the wait has been worth it - this would not have been possible +even 2 months ago. + +I also need to give a huge thank you :pray: to the following people because +without them, this release just wouldn't exist: + +- [Misite Bao](https://github.com/misitebao) - An absolute workhorse on the + Chinese translations and an incredible bug finder. +- [John Chadwick](https://github.com/jchv) - His amazing work on + [go-webview2](https://github.com/jchv/go-webview2) and + [go-winloader](https://github.com/jchv/go-winloader) have made the Windows + version we have today possible. +- [Tad Vizbaras](https://github.com/tadvi) - Experimenting with his + [winc](https://github.com/tadvi/winc) project was the first step down the path + to a pure Go Wails. +- [Mat Ryer](https://github.com/matryer) - His support, encouragement and + feedback has really helped drive the project forward. + +And finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors), including +[JetBrains](https://www.jetbrains.com?from=Wails), whose support drives the +project in many ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: MacOS and Linux users need not feel left out - porting to this new +foundation is actively under way and most of the hard work has already been +done. Hang in there! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md b/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md new file mode 100644 index 000000000..31d53e1bc --- /dev/null +++ b/docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md @@ -0,0 +1,166 @@ +--- +slug: blog/wails-v2-beta-for-mac +title: Wails v2 Beta for MacOS +authors: [leaanthony] +tags: [wails, v2] +date: 2021-11-08 +--- + +![wails-mac screenshot](../../../assets/blog-images/wails-mac.webp) + +Today marks the first beta release of Wails v2 for Mac! It's taken quite a while +to get to this point and I'm hoping that today's release will give you something +that's reasonably useful. There have been a number of twists and turns to get to +this point and I'm hoping, with your help, to iron out the crinkles and get the +Mac port polished for the final v2 release. + +You mean this isn't ready for production? For your use case, it may well be +ready, but there are still a number of known issues so keep your eye on +[this project board](https://github.com/wailsapp/wails/projects/7) and if you +would like to contribute, you'd be very welcome! + +So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the +Windows Beta :wink: + +### New Features + +![wails-menus-mac screenshot](../../../assets/blog-images/wails-menus-mac.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +### Mac Specific Options + +In addition to the normal application options, Wails v2 for Mac also brings some +Mac extras: + +- Make your window all funky and translucent, like all the pretty swift apps! +- Highly customisable titlebar +- We support the NSAppearance options for the application +- Simple config to auto-create an "About" menu + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `` tag with a local src path. Want to use a cool font? Copy +it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger and auto reload of + the application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote-mac screenshot](../../../assets/blog-images/remote-mac.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### Native M1 Support + +Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the +Wails project now supports M1 native builds: + +![build-darwin-arm screenshot](../../../assets/blog-images/build-darwin-arm.webp) + +You can also specify `darwin/amd64` as a target too: + +![build-darwin-amd screenshot](../../../assets/blog-images/build-darwin-amd.webp) + +Oh, I almost forgot.... you can also do `darwin/universal`.... :wink: + +![build-darwin-universal screenshot](../../../assets/blog-images/build-darwin-universal.webp) + +### Cross Compilation to Windows + +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. + +![build-cross-windows screenshot](../../../assets/blog-images/build-cross-windows.webp) +bu + +### WKWebView Renderer + +V1 relied on a (now deprecated) WebView component. V2 uses the most recent +WKWebKit component so expect the latest and greatest from Apple. + +### In Conclusion + +As I'd said in the Windows release notes, Wails v2 represents a new foundation +for the project. The aim of this release is to get feedback on the new approach, +and to iron out any bugs before a full release. Your input would be most +welcome! Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +And finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors), including +[JetBrains](https://www.jetbrains.com?from=Wails), whose support drive the +project in many ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: Linux users, you're next! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md b/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md new file mode 100644 index 000000000..047236258 --- /dev/null +++ b/docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md @@ -0,0 +1,129 @@ +--- +slug: blog/wails-v2-beta-for-linux +title: Wails v2 Beta for Linux +authors: [leaanthony] +tags: [wails, v2] +date: 2022-02-22 +--- + +![wails-linux screenshot](../../../assets/blog-images/wails-linux.webp) + +I'm pleased to finally announce that Wails v2 is now in beta for Linux! It is +somewhat ironic that the very first experiments with v2 was on Linux and yet it +has ended up as the last release. That being said, the v2 we have today is very +different from those first experiments. So without further ado, let's go over +the new features: + +### New Features + +![wails-menus-linux screenshot](../../../assets/blog-images/wails-menus-linux.webp) + +There were a lot of requests for native menu support. Wails has finally got you +covered. Application menus are now available and include support for most native +menu features. This includes standard menu items, checkboxes, radio groups, +submenus and separators. + +There were a huge number of requests in v1 for the ability to have greater +control of the window itself. I'm happy to announce that there's new runtime +APIs specifically for this. It's feature-rich and supports multi-monitor +configurations. There is also an improved dialogs API: Now, you can have modern, +native dialogs with rich configuration to cater for all your dialog needs. + +### No requirement to bundle assets + +A huge pain-point of v1 was the need to condense your entire application down to +single JS & CSS files. I'm happy to announce that for v2, there is no +requirement to bundle assets, in any way, shape or form. Want to load a local +image? Use an `<../../../assets/blog-images>` tag with a local src path. Want to +use a cool font? Copy it in and add the path to it in your CSS. + +> Wow, that sounds like a webserver... + +Yes, it works just like a webserver, except it isn't. + +> So how do I include my assets? + +You just pass a single `embed.FS` that contains all your assets into your +application configuration. They don't even need to be in the top directory - +Wails will just work it out for you. + +### New Development Experience + +Now that assets don't need to be bundled, it's enabled a whole new development +experience. The new `wails dev` command will build and run your application, but +instead of using the assets in the `embed.FS`, it loads them directly from disk. + +It also provides the additional features: + +- Hot reload - Any changes to frontend assets will trigger an auto reload of the + application frontend +- Auto rebuild - Any changes to your Go code will rebuild and relaunch your + application + +In addition to this, a webserver will start on port 34115. This will serve your +application to any browser that connects to it. All connected web browsers will +respond to system events like hot reload on asset change. + +In Go, we are used to dealing with structs in our applications. It's often +useful to send structs to our frontend and use them as state in our application. +In v1, this was a very manual process and a bit of a burden on the developer. +I'm happy to announce that in v2, any application run in dev mode will +automatically generate TypeScript models for all structs that are input or +output parameters to bound methods. This enables seamless interchange of data +models between the two worlds. + +In addition to this, another JS module is dynamically generated wrapping all +your bound methods. This provides JSDoc for your methods, providing code +completion and hinting in your IDE. It's really cool when you get data models +auto-imported when hitting tab in an auto-generated module wrapping your Go +code! + +### Remote Templates + +![remote-linux screenshot](../../../assets/blog-images/remote-linux.webp) + +Getting an application up and running quickly was always a key goal for the +Wails project. When we launched, we tried to cover a lot of the modern +frameworks at the time: react, vue and angular. The world of frontend +development is very opinionated, fast moving and hard to keep on top of! As a +result, we found our base templates getting out of date pretty quickly and this +caused a maintenance headache. It also meant that we didn't have cool modern +templates for the latest and greatest tech stacks. + +With v2, I wanted to empower the community by giving you the ability to create +and host templates yourselves, rather than rely on the Wails project. So now you +can create projects using community supported templates! I hope this will +inspire developers to create a vibrant ecosystem of project templates. I'm +really quite excited about what our developer community can create! + +### Cross Compilation to Windows + +Because Wails v2 for Windows is pure Go, you can target Windows builds without +docker. + +![build-cross-windows screenshot](../../../assets/blog-images/linux-build-cross-windows.webp) + +### In Conclusion + +As I'd said in the Windows release notes, Wails v2 represents a new foundation +for the project. The aim of this release is to get feedback on the new approach, +and to iron out any bugs before a full release. Your input would be most +welcome! Please direct any feedback to the +[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board. + +Linux is **hard** to support. We expect there to be a number of quirks with the +beta. Please help us to help you by filing detailed bug reports! + +Finally, I'd like to give a special thank you to all the +[project sponsors](/credits#sponsors) whose support drives the project in many +ways behind the scenes. + +I look forward to seeing what people build with Wails in this next exciting +phase of the project! + +Lea. + +PS: The v2 release isn't far off now! + +PPS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md b/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md new file mode 100644 index 000000000..c72d7f566 --- /dev/null +++ b/docs/src/content/docs/blog/2022-09-22-v2-release-notes.md @@ -0,0 +1,198 @@ +--- +slug: blog/wails-v2-released +title: Wails v2 Released +authors: [leaanthony] +tags: [wails, v2] +date: 2022-09-22 +--- + +![montage screenshot](../../../assets/blog-images/montage.png) + +# It's here! + +Today marks the release of [Wails](https://wails.io) v2. It's been about 18 +months since the first v2 alpha and about a year from the first beta release. +I'm truly grateful to everyone involved in the evolution of the project. + +Part of the reason it took that long was due to wanting to get to some +definition of completeness before officially calling it v2. The truth is, +there's never a perfect time to tag a release - there's always outstanding +issues or "just one more" feature to squeeze in. What tagging an imperfect major +release does do, however, is to provide a bit of stability for users of the +project, as well as a bit of a reset for the developers. + +This release is more than I'd ever expected it to be. I hope it gives you as +much pleasure as it has given us to develop it. + +# What _is_ Wails? + +If you are unfamiliar with Wails, it is a project that enables Go programmers to +provide rich frontends for their Go programs using familiar web technologies. +It's a lightweight, Go alternative to Electron. Much more information can be +found on the [official site](https://wails.io/docs/introduction). + +# What's new? + +The v2 release is a huge leap forward for the project, addressing many of the +pain points of v1. If you have not read any of the blog posts on the Beta +releases for [macOS](/blog/wails-v2-beta-for-mac), +[Windows](/blog/wails-v2-beta-for-windows) or +[Linux](/blog/wails-v2-beta-for-linux), then I encourage you to do so as it +covers all the major changes in more detail. In summary: + +- Webview2 component for Windows that supports modern web standards and + debugging capabilities. +- [Dark / Light theme](https://wails.io/docs/reference/options#theme) + + [custom theming](https://wails.io/docs/reference/options#customtheme) on Windows. +- Windows now has no CGO requirements. +- Out-of-the-box support for Svelte, Vue, React, Preact, Lit & Vanilla project + templates. +- [Vite](https://vitejs.dev/) integration providing a hot-reload development + environment for your application. +- Native application + [menus](https://wails.io/docs/guides/application-development#application-menu) and + [dialogs](https://wails.io/docs/reference/runtime/dialog). +- Native window translucency effects for + [Windows](https://wails.io/docs/reference/options#windowistranslucent) and + [macOS](https://wails.io/docs/reference/options#windowistranslucent-1). Support for Mica & + Acrylic backdrops. +- Easily generate an [NSIS installer](https://wails.io/docs/guides/windows-installer) for + Windows deployments. +- A rich [runtime library](https://wails.io/docs/reference/runtime/intro) providing utility + methods for window manipulation, eventing, dialogs, menus and logging. +- Support for [obfuscating](https://wails.io/docs/guides/obfuscated) your application using + [garble](https://github.com/burrowers/garble). +- Support for compressing your application using [UPX](https://upx.github.io/). +- Automatic TypeScript generation of Go structs. More info + [here](https://wails.io/docs/howdoesitwork#calling-bound-go-methods). +- No extra libraries or DLLs are required to be shipped with your application. + For any platform. +- No requirement to bundle frontend assets. Just develop your application like + any other web application. + +# Credit & Thanks + +Getting to v2 has been a huge effort. There have been ~2.2K commits by 89 +contributors between the initial alpha and the release today, and many, many +more that have provided translations, testing, feedback and help on the +discussion forums as well as the issue tracker. I'm so unbelievably grateful to +each one of you. I'd also like to give an extra special thank you to all the +project sponsors who have provided guidance, advice and feedback. Everything you +do is hugely appreciated. + +There are a few people I'd like to give special mention to: + +Firstly, a **huge** thank you to [@stffabi](https://github.com/stffabi) who has +provided so many contributions which we all benefit from, as well as providing a +lot of support on many issues. He has provided some key features such as the +external dev server support which transformed our dev mode offering by allowing +us to hook into [Vite](https://vitejs.dev/)'s superpowers. It's fair to say that +Wails v2 would be a far less exciting release without his +[incredible contributions](https://github.com/wailsapp/wails/commits?author=stffabi&since=2020-01-04). +Thank you so much @stffabi! + +I'd also like to give a huge shout-out to +[@misitebao](https://github.com/misitebao) who has tirelessly been maintaining +the website, as well as providing Chinese translations, managing Crowdin and +helping new translators get up to speed. This is a hugely important task, and +I'm extremely grateful for all the time and effort put into this! You rock! + +Last, but not least, a huge thank you to Mat Ryer who has provided advice and +support during the development of v2. Writing xBar together using an early Alpha +of v2 was helpful in shaping the direction of v2, as well as give me an +understanding of some design flaws in the early releases. I'm happy to announce +that as of today, we will start to port xBar to Wails v2, and it will become the +flagship application for the project. Cheers Mat! + +# Lessons Learnt + +There are a number of lessons learnt in getting to v2 that will shape +development moving forward. + +## Smaller, Quicker, Focused Releases + +In the course of developing v2, there were many features and bug fixes that were +developed on an ad-hoc basis. This led to longer release cycles and were harder +to debug. Moving forward, we are going to create releases more often that will +include a reduced number of features. A release will involve updates to +documentation as well as thorough testing. Hopefully, these smaller, quicker, +focussed releases will lead to fewer regressions and better quality +documentation. + +## Encourage Engagement + +When starting this project, I wanted to immediately help everyone who had a +problem. Issues were "personal" and I wanted them resolved as quickly as +possible. This is unsustainable and ultimately works against the longevity of +the project. Moving forward, I will be giving more space for people to get +involved in answering questions and triaging issues. It would be good to get +some tooling to help with this so if you have any suggestions, please join in +the discussion [here](https://github.com/wailsapp/wails/discussions/1855). + +## Learning to say No + +The more people that engage with an Open Source project, the more requests there +will be for additional features that may or may not be useful to the majority of +people. These features will take an initial amount of time to develop and debug, +and incur an ongoing maintenance cost from that point on. I myself am the most +guilty of this, often wanting to "boil the sea" rather than provide the minimum +viable feature. Moving forward, we will need to say "No" a bit more to adding +core features and focus our energies on a way to empower developers to provide +that functionality themselves. We are looking seriously into plugins for this +scenario. This will allow anyone to extend the project as they see fit, as well +as providing an easy way to contribute towards the project. + +# Looking to the Future + +There are so many core features we are looking at to add to Wails in the next +major development cycle already. The +[roadmap](https://github.com/wailsapp/wails/discussions/1484) is full of +interesting ideas, and I'm keen to start work on them. One of the big asks has +been for multiple window support. It's a tricky one and to do it right, and we +may need to look at providing an alternative API, as the current one was not +designed with this in mind. Based on some preliminary ideas and feedback, I +think you'll like where we're looking to go with it. + +I'm personally very excited at the prospect of getting Wails apps running on +mobile. We already have a demo project showing that it is possible to run a +Wails app on Android, so I'm really keen to explore where we can go with this! + +A final point I'd like to raise is that of feature parity. It has long been a +core principle that we wouldn't add anything to the project without there being +full cross-platform support for it. Whilst this has proven to be (mainly) +achievable so far, it has really held the project back in releasing new +features. Moving forward, we will be adopting a slightly different approach: any +new feature that cannot be immediately released for all platforms will be +released under an experimental configuration or API. This allows early adopters +on certain platforms to try the feature and provide feedback that will feed into +the final design of the feature. This, of course, means that there are no +guarantees of API stability until it is fully supported by all the platforms it +can be supported on, but at least it will unblock development. + +# Final Words + +I'm really proud of what we've been able to achieve with the V2 release. It's +amazing to see what people have already been able to build using the beta +releases so far. Quality applications like [Varly](https://varly.app/), +[Surge](https://getsurge.io/) and [October](https://october.utf9k.net/). I +encourage you to check them out. + +This release was achieved through the hard work of many contributors. Whilst it +is free to download and use, it has not come about through zero cost. Make no +mistakes, this project has come at considerable cost. It has not only been my +time and the time of each and every contributor, but also the cost of absence +from friends and families of each of those people too. That's why I'm extremely +grateful for every second that has been dedicated to making this project happen. +The more contributors we have, the more this effort can be spread out and the +more we can achieve together. I'd like to encourage you all to pick one thing +that you can contribute, whether it is confirming someone's bug, suggesting a +fix, making a documentation change or helping out someone who needs it. All of +these small things have such a huge impact! It would be so awesome if you too +were part of the story in getting to v3. + +Enjoy! + +‐ Lea + +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! diff --git a/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md b/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md new file mode 100644 index 000000000..a7d0d3b94 --- /dev/null +++ b/docs/src/content/docs/blog/2023-01-17-v3-roadmap.md @@ -0,0 +1,256 @@ +--- +slug: blog/the-road-to-wails-v3 +title: The Road to Wails v3 +authors: [leaanthony] +tags: [wails, v3] +date: 2023-01-17 +--- + +![multiwindow screenshot](../../../assets/blog-images/multiwindow.webp) + +# Introduction + +Wails is a project that simplifies the ability to write cross-platform desktop +applications using Go. It uses native webview components for the frontend (not +embedded browsers), bringing the power of the world's most popular UI system to +Go, whilst remaining lightweight. + +Version 2 was released on the 22nd of September 2022 and brought with it a lot +of enhancements including: + +- Live development, leveraging the popular Vite project +- Rich functionality for managing windows and creating menus +- Microsoft's WebView2 component +- Generation of Typescript models that mirror your Go structs +- Creating of NSIS Installer +- Obfuscated builds + +Right now, Wails v2 provides powerful tooling for creating rich, cross-platform +desktop applications. + +This blog post aims to look at where the project is at right now and what we can +improve on moving forward. + +# Where are we now? + +It's been incredible to see the popularity of Wails rising since the v2 release. +I'm constantly amazed by the creativity of the community and the wonderful +things that are being built with it. With more popularity, comes more eyes on +the project. And with that, more feature requests and bug reports. + +Over time, I've been able to identify some of the most pressing issues facing +the project. I've also been able to identify some of the things that are holding +the project back. + +## Current issues + +I've identified the following areas that I feel are holding the project back: + +- The API +- Bindings generation +- The Build System + +### The API + +The API to build a Wails application currently consists of 2 parts: + +- The Application API +- The Runtime API + +The Application API famously has only 1 function: `Run()` which takes a heap of +options which govern how the application will work. Whilst this is very simple +to use, it is also very limiting. It is a "declarative" approach which hides a +lot of the underlying complexity. For instance, there is no handle to the main +window, so you can't interact with it directly. For that, you need to use the +Runtime API. This is a problem when you start to want to do more complex things +like create multiple windows. + +The Runtime API provides a lot of utility functions for the developer. This +includes: + +- Window management +- Dialogs +- Menus +- Events +- Logs + +There are a number of things I am not happy with the Runtime API. The first is +that it requires a "context" to be passed around. This is both frustrating and +confusing for new developers who pass in a context and then get a runtime error. + +The biggest issue with the Runtime API is that it was designed for applications +that only use a single window. Over time, the demand for multiple windows has +grown and the API is not well suited to this. + +### Thoughts on the v3 API + +Wouldn't it be great if we could do something like this? + +```go +func main() { + app := wails.NewApplication(options.App{}) + myWindow := app.NewWindow(options.Window{}) + myWindow.SetTitle("My Window") + myWindow.On(events.Window.Close, func() { + app.Quit() + }) + app.Run() +} +``` + +This programmatic approach is far more intuitive and allows the developer to +interact with the application elements directly. All current runtime methods for +windows would simply be methods on the window object. For the other runtime +methods, we could move them to the application object like so: + +```go +app := wails.NewApplication(options.App{}) +app.NewInfoDialog(options.InfoDialog{}) +app.Log.Info("Hello World") +``` + +This is a much more powerful API which will allow for more complex applications +to be built. It also allows for the creation of multiple windows, +[the most up-voted feature on GitHub](https://github.com/wailsapp/wails/issues/1480): + +```go +func main() { + app := wails.NewApplication(options.App{}) + myWindow := app.NewWindow(options.Window{}) + myWindow.SetTitle("My Window") + myWindow.On(events.Window.Close, func() { + app.Quit() + }) + myWindow2 := app.NewWindow(options.Window{}) + myWindow2.SetTitle("My Window 2") + myWindow2.On(events.Window.Close, func() { + app.Quit() + }) + app.Run() +} +``` + +### Bindings generation + +One of the key features of Wails is generating bindings for your Go methods so +they may be called from Javascript. The current method for doing this is a bit +of a hack. It involves building the application with a special flag and then +running the resultant binary which uses reflection to determine what has been +bound. This leads to a bit of a chicken and egg situation: You can't build the +application without the bindings and you can't generate the bindings without +building the application. There are many ways around this but the best one would +be not to use this approach at all. + +There were a number of attempts at writing a static analyser for Wails projects +but they didn't get very far. In more recent times, it has become slightly +easier to do this with more material available on the subject. + +Compared to reflection, the AST approach is much faster however it is +significantly more complicated. To start with, we may need to impose certain +constraints on how to specify bindings in the code. The goal is to support the +most common use cases and then expand it later on. + +### The Build System + +Like the declarative approach to the API, the build system was created to hide +the complexities of building a desktop application. When you run `wails build`, +it does a lot of things behind the scenes: + +- Builds the backend binary for bindings and generates the bindings +- Installs the frontend dependencies +- Builds the frontend assets +- Determines if the application icon is present and if so, embeds it +- Builds the final binary +- If the build is for `darwin/universal` it builds 2 binaries, one for + `darwin/amd64` and one for `darwin/arm64` and then creates a fat binary using + `lipo` +- If compression is required, it compresses the binary with UPX +- Determines if this binary is to be packaged and if so: + - Ensures the icon and application manifest are compiled into the binary + (Windows) + - Builds out the application bundle, generates the icon bundle and copies it, + the binary and Info.plist to the application bundle (Mac) +- If an NSIS installer is required, it builds it + +This entire process, whilst very powerful, is also very opaque. It is very +difficult to customise it and it is very difficult to debug. + +To address this in v3, I would like to move to a build system that exists +outside of Wails. After using [Task](https://taskfile.dev/) for a while, I am a +big fan of it. It is a great tool for configuring build systems and should be +reasonably familiar to anyone who has used Makefiles. + +The build system would be configured using a `Taskfile.yml` file which would be +generated by default with any of the supported templates. This would have all of +the steps required to do all the current tasks, such as building or packaging +the application, allowing for easy customisation. + +There will be no external requirement for this tooling as it would form part of +the Wails CLI. This means that you can still use `wails build` and it will do +all the things it does today. However, if you want to customise the build +process, you can do so by editing the `Taskfile.yml` file. It also means you can +easily understand the build steps and use your own build system if you wish. + +The missing piece in the build puzzle is the atomic operations in the build +process, such as icon generation, compression and packaging. To require a bunch +of external tooling would not be a great experience for the developer. To +address this, the Wails CLI will provide all these capabilities as part of the +CLI. This means that the builds still work as expected, with no extra external +tooling, however you can replace any step of the build with any tool you like. + +This will be a much more transparent build system which will allow for easier +customisation and address a lot of the issues that have been raised around it. + +## The Payoff + +These positive changes will be a huge benefit to the project: + +- The new API will be much more intuitive and will allow for more complex + applications to be built. +- Using static analysis for bindings generation will be much faster and reduce a + lot of the complexity around the current process. +- Using an established, external build system will make the build process + completely transparent, allowing for powerful customisation. + +Benefits to the project maintainers are: + +- The new API will be much easier to maintain and adapt to new features and + platforms. +- The new build system will be much easier to maintain and extend. I hope this + will lead to a new ecosystem of community driven build pipelines. +- Better separation of concerns within the project. This will make it easier to + add new features and platforms. + +## The Plan + +A lot of the experimentation for this has already been done and it's looking +good. There is no current timeline for this work but I'm hoping by the end of Q1 +2023, there will be an alpha release for Mac to allow the community to test, +experiment with and provide feedback. + +## Summary + +- The v2 API is declarative, hides a lot from the developer and not suitable for + features such as multiple windows. A new API will be created which will be + simpler, intuitive and more powerful. +- The build system is opaque and difficult to customise so we will move to an + external build system which will open it all up. +- The bindings generation is slow and complex so we will move to static analysis + which will remove a lot of the complexity the current method has. + +There has been a lot of work put into the guts of v2 and it's solid. It's now +time to address the layer on top of it and make it a much better experience for +the developer. + +I hope you are as excited about this as I am. I'm looking forward to hearing +your thoughts and feedback. + +Regards, + +‐ Lea + +PS: If you or your company find Wails useful, please consider +[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks! + +PPS: Yes, that's a genuine screenshot of a multi-window application built with +Wails. It's not a mockup. It's real. It's awesome. It's coming soon. diff --git a/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md b/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md new file mode 100644 index 000000000..0cba0baa7 --- /dev/null +++ b/docs/src/content/docs/blog/2024-12-03-alpha10-and-new-release-strategy.md @@ -0,0 +1,52 @@ +--- +title: Alpha 10 Released - A New Chapter for Wails +description: Announcing Wails v3 Alpha 10 and our new daily release strategy +date: 2024-12-03 +author: The Wails Team +--- + +# Alpha 10 Released - A New Chapter for Wails + +We're thrilled to announce the release of **Wails v3 Alpha 10** - and it's big! While our release cadence may have seemed slow recently, there's been an incredible amount of work happening behind the scenes. Today marks not just another release, but a shift in how we approach the development of Wails. + +## Why the Wait? + +Like many development teams, we fell into the trap of trying to achieve perfection before each release. We wanted to squash every bug, polish every feature, and ensure everything was just right. But here's the thing - software is never truly bug-free, and waiting for that mythical state only delays getting improvements into your hands. + +## Our New Release Strategy + +Starting today, we're adopting a **daily release strategy**. Any new changes merged during the day will be released at the end of that day. This approach will: + +- Get bug fixes and features to you faster +- Provide more frequent feedback loops +- Speed up our journey to Beta +- Make the development process more transparent + +This means you might see more frequent, smaller releases rather than occasional large ones. We believe this will benefit everyone in the Wails community. + +## How You Can Help + +We've been overwhelmed by the number of people wanting to contribute to Wails! To make it easier for contributors to get involved, we're implementing a new system: + +- Firstly, we are opening up bug reporting for alpha releases. The frequent releases allow us to do this. +- Issues marked with **"Ready for Work"** are open for community contributions +- These issues have clear requirements and are ready to be tackled +- Most importantly, **we need people testing PRs** - this is one of the most valuable contributions you can make + +Testing doesn't require deep knowledge of the codebase, but it provides immense value by ensuring changes work across different environments and use cases. + +## What's Next? + +With our new daily release strategy, expect to see rapid progress toward Beta. We're committed to maintaining momentum and getting Wails v3 to a stable release as quickly as possible without sacrificing quality. + +And who knows? There might be a few surprises along the way... 😉 + +## Thank You + +To everyone who has contributed code, tested releases, reported bugs, or simply used Wails - thank you. Your support and feedback drive this project forward. + +Here's to more frequent releases, faster iteration, and an exciting journey ahead! + +--- + +*Want to get involved? Check out our [GitHub repository](https://github.com/wailsapp/wails) for issues marked "ready for work", or join our community to help test the latest changes.* \ No newline at end of file diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx new file mode 100644 index 000000000..a71e11bd2 --- /dev/null +++ b/docs/src/content/docs/changelog.mdx @@ -0,0 +1,802 @@ +--- +title: Changelog +--- + + +Legend: +-  - macOS +- ⊞ - Windows +- 🐧 - Linux + +/*-- +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +*/ + +/* + ** PLEASE DO NOT UPDATE THIS FILE ** + Updates should be added to `v3/UNRELEASED_CHANGELOG.md` + Thank you! +*/ +## [Unreleased] + +## v3.0.0-alpha.31 - 2025-09-27 + +## Fixed +- Windows: Flicker of window at start and hidden windows being shown incorrectly in [PR](https://github.com/wailsapp/wails/pull/4600) by @leaanthony. +- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier) + +## v3.0.0-alpha.30 - 2025-09-26 + +## Fixed +- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier) + +## v3.0.0-alpha.29 - 2025-09-25 + +## Added +- macOS: Shows native window controls in the menu bar in [#4588](https://github.com/wailsapp/wails/pull/4588) by @nidib +- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451) + +## Changed +- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas + +## Fixed +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 +- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni +- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42 +- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly + +## v3.0.0-alpha.29 - 2025-09-25 + +## Added +- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451) + +## Changed +- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas + +## Fixed +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 +- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni +- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42 +- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly + +## v3.0.0-alpha.27 - 2025-09-07 + +## Fixed +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 + +## v3.0.0-alpha.26 - 2025-08-24 + +## Added +- Add native Liquid Glass effect support for macOS with NSGlassEffectView (macOS 15.0+) and NSVisualEffectView fallback, including comprehensive material customization options by @leaanthony in [#4534](https://github.com/wailsapp/wails/pull/4534) + +## v3.0.0-alpha.25 - 2025-08-16 + +## Changed +- When running `wails3 update build-assets` with the `-config` parameter, values set via the `-product*` parameters are + no longer ignored, and override the config value. + +## v3.0.0-alpha.24 - 2025-08-13 + +## Added +- Browser URL Sanitisation by @leaanthony in [#4500](https://github.dev/wailsapp/wails/pull/4500). Based on [#4484](https://github.com/wailsapp/wails/pull/4484) by @APShenkin. + +## v3.0.0-alpha.23 - 2025-08-11 + +## Fixed +- Fix SetBackgroundColour on Windows by @PPTGamer in [PR](https://github.com/wailsapp/wails/pull/4492) + +## v3.0.0-alpha.22 - 2025-08-10 + +## Added +- Add Content Protection on Windows/Mac by [@leaanthony](https://github.com/leaanthony) based on the original work of [@Taiterbase](https://github.com/Taiterbase) in this [PR](https://github.com/wailsapp/wails/pull/4241) +- Add support for passing CLI variables to Task commands through `wails3 build` and `wails3 package` aliases (#4422) by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4488) + +## Changed +- `window.NativeWindowHandle()` -> `window.NativeWindow()` by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471) +- Refactor internal window handling by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471) ++ Fix extra-broad Linux package dependencies, fix outdated RPM dependencies. + +## v3.0.0-alpha.21 - 2025-08-07 + +## Fixed +- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476) +- Fix Linux .desktop file appicon variable in Linux taskfile [PR #4477](https://github.com/wailsapp/wails/pull/4477) + +## v3.0.0-alpha.20 - 2025-08-06 + +## Fixed +- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476) + +## v3.0.0-alpha.19 - 2025-08-05 + +## Added +- Support for dropzones with event sourcing dropped element data [@atterpac](https://github.com/atterpac) in [#4318](https://github.com/wailsapp/wails/pull/4318) +- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467) +- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286) +- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463) + +## Fixed +- Fix Windows nil pointer dereference bug reported in [#4456](https://github.com/wailsapp/wails/issues/4456) by @leaanthony in [#4460](https://github.com/wailsapp/wails/pull/4460) +- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857) +- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation. +- Fix Vite server not being cleaned up when build fails (#4403) +- Fixed panic when closing or cancelling a `SaveFileDialog` on windows. Fixed in [PR](https://github.com/wailsapp/wails/pull/4284) by @hkhere +- Fixed HTML level drag and drop on Windows by [@mbaklor](https://github.com/mbaklor) in [#4259](https://github.com/wailsapp/wails/pull/4259) + +## v3.0.0-alpha.18 - 2025-08-03 + +## Added +- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467) +- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286) +- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463) + +## Fixed +- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857) +- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation. +- Fix Vite server not being cleaned up when build fails (#4403) + +## v3.0.0-alpha.17 - 2025-07-31 + +## Fixed +- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450) + +## v3.0.0-alpha.16 - 2025-07-25 + +## Added +- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427) + +## v3.0.0-alpha.15 - 2025-07-25 + +## Added +- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427) + +## v3.0.0-alpha.14 - 2025-07-25 + +## Added +- Windows dark theme menus + menubar. By @leaanthony in [a29b4f0861b1d0a700e9eb213c6f1076ec40efd5](https://github.com/wailsapp/wails/commit/a29b4f0861b1d0a700e9eb213c6f1076ec40efd5) +- Rename built-in services for clearer JS/TS bindings by @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4405) + +## v3.0.0-alpha.12 - 2025-07-15 + +### Added +- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913) +- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137) + +### Fixed +- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390) + + +## v3.0.0-alpha.11 - 2025-07-12 + +## Added +- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345) +- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404) + +## v3.0.0-alpha.10 - 2025-07-06 + +### Breaking Changes +- **Manager API Refactoring**: Reorganized application API from flat structure to organized managers for better code organization and discoverability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359) + - `app.NewWebviewWindow()` → `app.Window.New()` + - `app.CurrentWindow()` → `app.Window.Current()` + - `app.GetAllWindows()` → `app.Window.GetAll()` + - `app.WindowByName()` → `app.Window.GetByName()` + - `app.EmitEvent()` → `app.Event.Emit()` + - `app.OnApplicationEvent()` → `app.Event.OnApplicationEvent()` + - `app.OnWindowEvent()` → `app.Event.OnWindowEvent()` + - `app.SetApplicationMenu()` → `app.Menu.SetApplicationMenu()` + - `app.OpenFileDialog()` → `app.Dialog.OpenFile()` + - `app.SaveFileDialog()` → `app.Dialog.SaveFile()` + - `app.MessageDialog()` → `app.Dialog.Message()` + - `app.InfoDialog()` → `app.Dialog.Info()` + - `app.WarningDialog()` → `app.Dialog.Warning()` + - `app.ErrorDialog()` → `app.Dialog.Error()` + - `app.QuestionDialog()` → `app.Dialog.Question()` + - `app.NewSystemTray()` → `app.SystemTray.New()` + - `app.GetSystemTray()` → `app.SystemTray.Get()` + - `app.ShowContextMenu()` → `app.ContextMenu.Show()` + - `app.RegisterKeybinding()` → `app.KeyBinding.Add()` + - `app.UnregisterKeybinding()` → `app.KeyBinding.Remove()` + - `app.GetPrimaryScreen()` → `app.Screen.GetPrimary()` + - `app.GetAllScreens()` → `app.Screen.GetAll()` + - `app.BrowserOpenURL()` → `app.Browser.OpenURL()` + - `app.Environment()` → `app.Env.Info()` + - `app.ClipboardGetText()` → `app.Clipboard.Text()` + - `app.ClipboardSetText()` → `app.Clipboard.SetText()` +- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony) +- Moved `Path` and `Paths` methods to `application` package by [@leaanthony](https://github.com/leaanthony) +- The application menu is now macOS only by [@leaanthony](https://github.com/leaanthony) + +### Added + +- **Organized Testing Infrastructure**: Moved Docker test files to dedicated `test/docker/` directory with optimized images and enhanced build reliability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359) +- **Improved Resource Management Patterns**: Added proper event handler cleanup and context-aware goroutine management in examples by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359) +- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) +- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony) +- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony) +- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony) +- New Menu guide by [@leaanthony](https://github.com/leaanthony) +- Better panic handling by [@leaanthony](https://github.com/leaanthony) +- New Menu guide by [@leaanthony](https://github.com/leaanthony) +- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Improved menu control by [@FalcoG](https://github.com/FalcoG) and [@leaanthony](https://github.com/leaanthony) in [#4031](https://github.com/wailsapp/wails/pull/4031) +- More documentation by [@leaanthony](https://github.com/leaanthony) +- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony) +- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony) +- Systray `SetTooltip` support by [@leaanthony](https://github.com/leaanthony). Original idea by [@lujihong](https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304) +- Report package path in binding generator warnings about unsupported types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for generic aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for `omitzero` JSON flag by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068) +- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065) +- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony) +- Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `Load` method on `kvstore` service to reload data from file after config changes by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `Clear` method on `kvstore` service to delete all keys by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add type `Level` in `log` service to provide JS-side log-level constants by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add `Log` method on `log` service to specify log-level dynamically by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `sqlite` service provides an in-memory DB by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in this [PR](https://github.com/wailsapp/wails/pull/3537) +- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134) +- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony) +- Add Notification support by [@popaprozac](https://github.com/popaprozac) in [#4098](https://github.com/wailsapp/wails/pull/4098) +-  Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177) +- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony) +- Add badging support for macOS and Windows by [@popaprozac](https://github.com/popaprozac) in [#](https://github.com/wailsapp/wails/pull/4234) + +### Fixed + +- Fixed nil pointer dereference in processURLRequest for Mac by [@etesam913](https://github.com/etesam913) in [#4366](https://github.com/wailsapp/wails/pull/4366) +- Fixed a linux bug preventing filtered dialogs by [@bh90210](https://github.com/bh90210) in [#4287](https://github.com/wailsapp/wails/pull/4287) +- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062) +- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) +- Window ID skip issue by [@leaanthony](https://github.com/leaanthony) +- Fix nil menu issue when calling RegisterContextMenu by [@leaanthony](https://github.com/leaanthony) +- Fixed dependency cycles in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- Fixed use-before-define errors in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- Pass build flags to binding generator by [@fbbdev](https://github.com/fbbdev) in [#4023](https://github.com/wailsapp/wails/pull/4023) +- Change paths in windows Taskfile to forward slashes to ensure it works on non-Windows platforms by [@leaanthony](https://github.com/leaanthony) +- Mac + Mac JS events now fixed by [@leaanthony](https://github.com/leaanthony) +- Fixed event deadlock for macOS by [@leaanthony](https://github.com/leaanthony) +- Fixed a `Parameter incorrect` error in Window initialisation on Windows when HTML provided but no JS by [@leaanthony](https://github.com/leaanthony) +- Fixed size of response prefix used for content type sniffing in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049) +- Fixed handling of non-404 responses on root index path in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049) +- Fixed undefined behaviour in binding generator when testing properties of generic types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for models when underlying type has not the same properties as named wrapper by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for map key types and preprocessing by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed binding generator output for structs that implement marshaler interfaces by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed detection of type cycles involving generic types in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed invalid references to unexported models in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Moved injected code to the end of service files by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed handling of errors from file close operations in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056) +- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony) +- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +-  Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +-  Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony) +- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116) +- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does +- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125) +- Fixed `fullscreen`, `unfullscreen`, `unminimise` and `unmaximise` events not being emitted by [@oSethoum](https://github.com/osethoum) in [#4130](https://github.com/wailsapp/wails/pull/4130) +- Fixed NSIS Error because of incorrect prefix on default version in config by [@robin-samuel](https://github.com/robin-samuel) in [#4126](https://github.com/wailsapp/wails/pull/4126) +- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188) +- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony). +- Fixed input issue with macOS by [@leaanthony](https://github.com/leaanthony). +- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219). +- Fixed transparency issue for frameless windows by [@leaanthony](https://github.com/leaanthony) based on work by @kron. +- Fixed focus calls when window is disabled or minimised by [@leaanthony](https://github.com/leaanthony) based on work by @kron. +- Fixed system trays not showing after taskbar restarts by [@leaanthony](https://github.com/leaanthony) based on work by @kron. +- Fixed fallbackResponseWriter not implementing Flush() in [#4245](https://github.com/wailsapp/wails/pull/4245) +- Fixed fallbackResponseWriter not implementing Flush() by [@superDingda] in [#4236](https://github.com/wailsapp/wails/issues/4236) +- Fixed macOS window close with pending async Go-bound function call crashes by [@joshhardy](https://github.com/joshhardy) in [#4354](https://github.com/wailsapp/wails/pull/4354) +- Fixed Windows Efficiency mode startup race condition by [@leaanthony](https://github.com/leaanthony) +- Fixed Windows icon handle cleanup by [@leaanthony](https://github.com/leaanthony). +- Fixed `OpenFileManager` on Windows by [@PPTGamer](https://github.com/PPTGamer) in [#4375](https://github.com/wailsapp/wails/pull/4375). + +### Changed + +- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony) +- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony) +- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001) +- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony) +- `application.NewService` does not accept options as an optional parameter anymore (use `application.NewServiceWithOptions` instead) by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024) +- Removed `nanoid` dependency by [@leaanthony](https://github.com/leaanthony) +- Updated Window example for mica/acrylic/tabbed window styles by [@leaanthony](https://github.com/leaanthony) +- In JS/TS bindings, `internal.js/ts` model files have been removed; all models can now be found in `models.js/ts` by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- In JS/TS bindings, named types are never rendered as aliases for other named types; the old behaviour is now restricted to aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- In JS/TS bindings, in class mode, struct fields whose type is a type parameter are marked optional and never initialised automatically by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045) +- Remove ESLint from templates by by [@IanVS](https://github.com/IanVS) in [#4059](https://github.com/wailsapp/wails/pull/4059) +- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037) +- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075) +- Go 1.24 support by [@leaanthony](https://github.com/leaanthony) +- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066) +- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony) +- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100) +- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067) +- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133) +- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac](https://github.com/popaprozac) in [#4171](https://github.com/wailsapp/wails/pull/4171) + +## v3.0.0-alpha.9 - 2025-01-13 + +### Added + +- `app.OpenFileManager(path string, selectFile bool)` to open the system file manager to the path `path` with optional highlighting via `selectFile` by [@Krzysztofz01](https://github.com/Krzysztofz01) [@rcalixte](https://github.com/rcalixte) +- New `-git` flag for `wails3 init` command by [@leaanthony](https://github.com/leaanthony) +- New `wails3 generate webview2bootstrapper` command by [@leaanthony](https://github.com/leaanthony) +- Added `init()` method in runtime to allow manual initialisation of the runtime by [@leaanthony](https://github.com/leaanthony) +- Added `WindowDidMoveDebounceMS` option to Window's WindowOptions by [@leaanthony](https://github.com/leaanthony) +- Added Single Instance feature by [@leaanthony](https://github.com/leaanthony). Based on the [v2 PR](https://github.com/wailsapp/wails/pull/2951) by @APshenkin. +- `wails3 generate template` command by [@leaanthony](https://github.com/leaanthony) +- `wails3 releasenotes` command by [@leaanthony](https://github.com/leaanthony) +- `wails3 update cli` command by [@leaanthony](https://github.com/leaanthony) +- `-clean` option for `wails3 generate bindings` command by [@leaanthony](https://github.com/leaanthony) +- Allow for aarch64 (arm64) AppImage Linux builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981) + +### Fixed + +- Fixed min/max width options for linux by @atterpac in [#3979](https://github.com/wailsapp/wails/pull/3979) +- Typescript templates types definitions via npm version bump by @atterpac in [#3966](https://github.com/wailsapp/wails/pull/3966) +- Fix Sveltekit template CSS referance by @atterpac in [#3945](https://github.com/wailsapp/wails/pull/3945) +- Ensure key callbacks in window run() are called on the main thread by [@leaanthony](https://github.com/leaanthony) +- Fix dialog directory chooser examples by [@leaanthony](https://github.com/leaanthony) +- Created new Chinese error page when index.html is missing by [@leaanthony](https://github.com/leaanthony) +-  Ensure `windowDidBecomeKey` callback is running on main thread by [@leaanthony](https://github.com/leaanthony) +-  Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony) +-  Improved window destroying logic by [@leaanthony](https://github.com/leaanthony) +-  Fix window position logic when attached to system trays by [@leaanthony](https://github.com/leaanthony) +-  Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony) +- Fix event handling by [@leaanthony](https://github.com/leaanthony) +- Fixed window shutdown logic by [@leaanthony](https://github.com/leaanthony) +- Common taskfile now defaults to generating Typescript bindings for Typescript templates by [@leaanthony](https://github.com/leaanthony) +- Fix Close application on WM_CLOSE message when no windows are open/systray only by [@mmalcek](https://github.com/mmalcek) in [#3990](https://github.com/wailsapp/wails/pull/3990) +- Fixed garble build by @5aaee9 in [#3192](https://github.com/wailsapp/wails/pull/3192) +- Fixed windows nsis builds by [@leaanthony](https://github.com/leaanthony) + +### Changed + +- Moved build assets to platform specific directories by [@leaanthony](https://github.com/leaanthony) +- Moved and renamed Taskfiles to platform specific directories by [@leaanthony](https://github.com/leaanthony) +- Created a much better experience when `index.html` is missing by [@leaanthony](https://github.com/leaanthony) +- [Windows] Improved performance of minimise and restore by [@leaanthony](https://github.com/leaanthony). Based on original [PR](https://github.com/wailsapp/wails/pull/3955) by [562589540](https://github.com/562589540) +- Removed `ShouldClose` option (Register a hook for events.Common.WindowClosing instead) by [@leaanthony](https://github.com/leaanthony) +- [Windows] Reduced flicker when opening a window by [@leaanthony](https://github.com/leaanthony) +- Removed `Window.Destroy` as this was intended to be an internal function by [@leaanthony](https://github.com/leaanthony) +- Renamed `WindowClose` events to `WindowClosing` by [@leaanthony](https://github.com/leaanthony) +- Frontend builds now use vite environment "development" or "production" depending on build type by [@leaanthony](https://github.com/leaanthony) +- Update to go-webview2 v1.19 by [@leaanthony](https://github.com/leaanthony) + +## v3.0.0-alpha.8.3 - 2024-12-07 + +### Changed + +- Ensure fork of taskfile is used by @leaanthony + +## v3.0.0-alpha.8.2 - 2024-12-07 + +### Changed + +- Update fork of Taskfile to fix version issues when installing using + `go install` by @leaanthony + +## v3.0.0-alpha.8.1 - 2024-12-07 + +### Changed + +- Using fork of Taskfile to fix version issues when installing using + `go install` by @leaanthony + +## v3.0.0-alpha.8 - 2024-12-06 + +### Added + +- Added hyperlink for sponsor by @ansxuman in [#3958](https://github.com/wailsapp/wails/pull/3958) +- Support of linux packaging of deb,rpm, and arch linux packager builds by + @atterpac in [#3909](https://github.com/wailsapp/wails/3909) +- Added Support for darwin universal builds and packages by + [ansxuman](https://github.com/ansxuman) in + [#3902](https://github.com/wailsapp/wails/pull/3902) +- Events documentation to the website by + [atterpac](https://github.com/atterpac) in + [#3867](https://github.com/wailsapp/wails/pull/3867) +- Templates for sveltekit and sveltekit-ts that are set for non-SSR development + by [atterpac](https://github.com/atterpac) in + [#3829](https://github.com/wailsapp/wails/pull/3829) +- Update build assets using new `wails3 update build-assets` command by + [leaanthony](https://github.com/leaanthony) +- Example to test the HTML Drag and Drop API by + [FerroO2000](https://github.com/FerroO2000) in + [#3856](https://github.com/wailsapp/wails/pull/3856) +- File Association support by [leaanthony](https://github.com/leaanthony) in + [#3873](https://github.com/wailsapp/wails/pull/3873) +- New `wails3 generate runtime` command by + [leaanthony](https://github.com/leaanthony) +- New `InitialPosition` option to specify if the window should be centered or + positioned at the given X/Y location by + [leaanthony](https://github.com/leaanthony) in + [#3885](https://github.com/wailsapp/wails/pull/3885) +- Add `Path` & `Paths` methods to `application` package by + [ansxuman](https://github.com/ansxuman) and + [leaanthony](https://github.com/leaanthony) in + [#3823](https://github.com/wailsapp/wails/pull/3823) +- Added `GeneralAutofillEnabled` and `PasswordAutosaveEnabled` Windows options + by [leaanthony](https://github.com/leaanthony) in + [#3766](https://github.com/wailsapp/wails/pull/3766) +- Added the ability to retrieve the window calling a service method by + [leaanthony](https://github.com/leaanthony) in + [#3888](https://github.com/wailsapp/wails/pull/3888) +- Added `EnabledFeatures` and `DisabledFeatures` options for Webview2 by + [leaanthony](https://github.com/leaanthony). +- + +### Changed + +- `service.OnStartup` now shutdowns the application on error and runs + `service.OnShutdown`for any prior services that started by @atterpac in + [#3920](https://github.com/wailsapp/wails/pull/3920) +- Refactored systray click messaging to better align with user interactions by + @atterpac in [#3907](https://github.com/wailsapp/wails/pull/3907) +- Asset embed to include `all:frontend/dist` to support frameworks that generate + subfolders by @atterpac in + [#3887](https://github.com/wailsapp/wails/pull/3887) +- Taskfile refactor by [leaanthony](https://github.com/leaanthony) in + [#3748](https://github.com/wailsapp/wails/pull/3748) +- Upgrade to `go-webview2` v1.0.16 by + [leaanthony](https://github.com/leaanthony) +- Fixed `Screen` type to include `ID` not `Id` by + [etesam913](https://github.com/etesam913) in + [#3778](https://github.com/wailsapp/wails/pull/3778) +- Update `go.mod.tmpl` wails version to support `application.ServiceOptions` by + [northes](https://github.com/northes) in + [#3836](https://github.com/wailsapp/wails/pull/3836) +- Fixed service name determination by [windom](https://github.com/windom/) in + [#3827](https://github.com/wailsapp/wails/pull/3827) +- mkdocs serve now uses docker by [leaanthony](https://github.com/leaanthony) +- Consolidated dev config into `config.yml` by + [leaanthony](https://github.com/leaanthony) +- Systray dialog now defaults to the application icon if available (Windows) by + [@leaanthony](https://github.com/leaanthony) +- Better reporting of GPU + Memory for macOS by + [@leaanthony](https://github.com/leaanthony) +- Removed `WebviewGpuIsDisabled` and `EnableFraudulentWebsiteWarnings` + (superseded by `EnabledFeatures` and `DisabledFeatures` options) by + [leaanthony](https://github.com/leaanthony) + +### Fixed + +- Fixed deadlock in Linux dialog for multiple selections caused by unclosed + channel variable by @michael-freling in + [#3925](https://github.com/wailsapp/wails/pull/3925) +- Fixed cross-platform cleanup for .syso files during Windows build by + [ansxuman](https://github.com/ansxuman) in + [#3924](https://github.com/wailsapp/wails/pull/3924) +- Fixed amd64 appimage compile by @atterpac in + [#3898](https://github.com/wailsapp/wails/pull/3898) +- Fixed build assets update by @ansxuman in + [#3901](https://github.com/wailsapp/wails/pull/3901) +- Fixed Linux systray `OnClick` and `OnRightClick` implementation by @atterpac + in [#3886](https://github.com/wailsapp/wails/pull/3886) +- Fixed `AlwaysOnTop` not working on Mac by + [leaanthony](https://github.com/leaanthony) in + [#3841](https://github.com/wailsapp/wails/pull/3841) +-  Fixed `application.NewEditMenu` including a duplicate + `PasteAndMatchStyle` role in the edit menu on Darwin by + [johnmccabe](https://github.com/johnmccabe) in + [#3839](https://github.com/wailsapp/wails/pull/3839) +- 🐧 Fixed aarch64 compilation + [#3840](https://github.com/wailsapp/wails/issues/3840) in + [#3854](https://github.com/wailsapp/wails/pull/3854) by + [kodflow](https://github.com/kodflow) +- ⊞ Fixed radio group menu items by + [@leaanthony](https://github.com/leaanthony) +- Fix error on building runnable .app on MacOS when 'name' and 'outputfilename' + are different. by @nickisworking in + [#3789](https://github.com/wailsapp/wails/pull/3789) + +## v3.0.0-alpha.7 - 2024-09-18 + +### Added + +- ⊞ New DIP system for Enhanced High DPI Monitor Support by + [mmghv](https://github.com/mmghv) in + [#3665](https://github.com/wailsapp/wails/pull/3665) +- ⊞ Window class name option by [windom](https://github.com/windom/) in + [#3682](https://github.com/wailsapp/wails/pull/3682) +- Services have been expanded to provide plugin functionality. By + [atterpac](https://github.com/atterpac) and + [leaanthony](https://github.com/leaanthony) in + [#3570](https://github.com/wailsapp/wails/pull/3570) + +### Changed + +- Events API change: `On`/`Emit` -> user events, `OnApplicationEvent` -> + Application Events `OnWindowEvent` -> Window Events, by + [leaanthony](https://github.com/leaanthony) +- Fix for Events API on Linux by [TheGB0077](https://github.com/TheGB0077) in + [#3734](https://github.com/wailsapp/wails/pull/3734) +- [CI] improvements to actions & enable to run actions also in forks and + branches prefixed with `v3/` or `v3-` by + [stendler](https://github.com/stendler) in + [#3747](https://github.com/wailsapp/wails/pull/3747) + +### Fixed + +- Fixed bug with usage of customEventProcessor in drag-n-drop example by + [etesam913](https://github.com/etesam913) in + [#3742](https://github.com/wailsapp/wails/pull/3742) +- 🐧 Fixed linux compile error introduced by IgnoreMouseEvents addition by + [atterpac](https://github.com/atterpac) in + [#3721](https://github.com/wailsapp/wails/pull/3721) +- ⊞ Fixed syso icon file generation bug by + [atterpac](https://github.com/atterpac) in + [#3675](https://github.com/wailsapp/wails/pull/3675) +- 🐧 Fix to run natively in wayland incorporated from + [#1811](https://github.com/wailsapp/wails/pull/1811) in + [#3614](https://github.com/wailsapp/wails/pull/3614) by + [@stendler](https://github.com/stendler) +- Do not bind internal service methods in + [#3720](https://github.com/wailsapp/wails/pull/3720) by + [leaanthony](https://github.com/leaanthony) +- ⊞ Fixed system tray startup panic in + [#3693](https://github.com/wailsapp/wails/issues/3693) by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) +- Do not bind internal service methods in + [#3720](https://github.com/wailsapp/wails/pull/3720) by + [leaanthony](https://github.com/leaanthony) +- ⊞ Fixed system tray startup panic in + [#3693](https://github.com/wailsapp/wails/issues/3693) by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) +- Major menu item refactor and event handling. Mainly improves macOS for now. By + [leaanthony](https://github.com/leaanthony) +- Fix tests after plugins and event refactor in + [#3746](https://github.com/wailsapp/wails/pull/3746) by + [@stendler](https://github.com/stendler) +- ⊞ Fixed `Failed to unregister class Chrome_WidgetWin_0` warning. By + [leaanthony](https://github.com/leaanthony) + +## v3.0.0-alpha.6 - 2024-07-30 + +### Fixed + +- Module issues + +## v3.0.0-alpha.5 - 2024-07-30 + +### Added + +- 🐧 WindowDidMove / WindowDidResize events in + [#3580](https://github.com/wailsapp/wails/pull/3580) +- ⊞ WindowDidResize event in + [#3580](https://github.com/wailsapp/wails/pull/3580) +-  add Event ApplicationShouldHandleReopen to be able to handle dock + icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991) +-  add getPrimaryScreen/getScreens to impl by @tmclane in + [#2618](https://github.com/wailsapp/wails/pull/2618) +-  add option for showing the toolbar in fullscreen mode on macOS by + [@fbbdev](https://github.com/fbbdev) in + [#3282](https://github.com/wailsapp/wails/pull/3282) +- 🐧 add onKeyPress logic to convert linux keypress into an accelerator + @[Atterpac](https://github.com/Atterpac) + in[#3022](https://github.com/wailsapp/wails/pull/3022]) +- 🐧 add task `run:linux` by + [@marcus-crane](https://github.com/marcus-crane) in + [#3146](https://github.com/wailsapp/wails/pull/3146) +- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in + [PR](https://github.com/wailsapp/wails/pull/3147) +- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in + [PR](https://github.com/wailsapp/wails/pull/3189) +- Restore `ToggleMaximise` method in `Window` interface by + [@fbbdev](https://github.com/fbbdev) in + [#3281](https://github.com/wailsapp/wails/pull/3281) +- Added more information to `Environment()`. By @leaanthony in + [aba82cc](https://github.com/wailsapp/wails/commit/aba82cc52787c97fb99afa58b8b63a0004b7ff6c) + based on [PR](https://github.com/wailsapp/wails/pull/2044) by @Mai-Lapyst +- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Support multiple space-separated trigger events in the WML system by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Add ESM exports from the bundled JS runtime script by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Add binding generator flag for using the bundled JS runtime script instead of + the npm package by [@fbbdev](https://github.com/fbbdev) in + [#3334](https://github.com/wailsapp/wails/pull/3334) +- Implement `setIcon` on linux by [@abichinger](https://github.com/abichinger) + in [#3354](https://github.com/wailsapp/wails/pull/3354) +- Add flag `-port` to dev command and support environment variable + `WAILS_VITE_PORT` by [@abichinger](https://github.com/abichinger) in + [#3429](https://github.com/wailsapp/wails/pull/3429) +- Add tests for bound method calls by + [@abichinger](https://github.com/abichinger) in + [#3431](https://github.com/wailsapp/wails/pull/3431) +- ⊞ add `SetIgnoreMouseEvents` for already created window by + [@bruxaodev](https://github.com/bruxaodev) in + [#3667](https://github.com/wailsapp/wails/pull/3667) +-  Add ability to set a window's stacking level (order) by + [@OlegGulevskyy](https://github.com/OlegGulevskyy) in + [#3674](https://github.com/wailsapp/wails/pull/3674) + +### Fixed + +- Fixed resize event messaging by [atterpac](https://github.com/atterpac) in + [#3606](https://github.com/wailsapp/wails/pull/3606) +- 🐧Fixed theme handling error on NixOS by + [tmclane](https://github.com/tmclane) in + [#3515](https://github.com/wailsapp/wails/pull/3515) +- Fixed cross volume project install for windows by + [atterpac](https://github.com/atterac) in + [#3512](https://github.com/wailsapp/wails/pull/3512) +- Fixed react template css to show footer by + [atterpac](https://github.com/atterpac) in + [#3477](https://github.com/wailsapp/wails/pull/3477) +- Fixed zombie processes when working in devmode by updating to latest refresh + by [Atterpac](https://github.com/atterpac) in + [#3320](https://github.com/wailsapp/wails/pull/3320). +- Fixed appimage webkit file sourcing by [Atterpac](https://github.com/atterpac) + in [#3306](https://github.com/wailsapp/wails/pull/3306). +- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in + [#2972](https://github.com/wailsapp/wails/pull/2972). +- Fixed application frozen when quit (Darwin) by @5aaee9 in + [#2982](https://github.com/wailsapp/wails/pull/2982) +- Fixed background colours of examples on Windows by + [mmghv](https://github.com/mmghv) in + [#2750](https://github.com/wailsapp/wails/pull/2750). +- Fixed default context menus by [mmghv](https://github.com/mmghv) in + [#2753](https://github.com/wailsapp/wails/pull/2753). +- Fixed hex values for arrow keys on Darwin by + [jaybeecave](https://github.com/jaybeecave) in + [#3052](https://github.com/wailsapp/wails/pull/3052). +- Set drag-n-drop for windows to working. Added by + [@pylotlight](https://github.com/pylotlight) in + [PR](https://github.com/wailsapp/wails/pull/3039) +- Fixed bug for linux in doctor in the event user doesn't have proper drivers + installed. Added by [@pylotlight](https://github.com/pylotlight) in + [PR](https://github.com/wailsapp/wails/pull/3032) +- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in + [PR](https://github.com/wailsapp/wails/pull/3145) +- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with + spaces - @leaanthony. +- Fix MacOS systray click handling when no attached window by + [thomas-senechal](https://github.com/thomas-senechal) in PR + [#3207](https://github.com/wailsapp/wails/pull/3207) +- Fix failing Windows build due to unknown option by + [thomas-senechal](https://github.com/thomas-senechal) in PR + [#3208](https://github.com/wailsapp/wails/pull/3208) +- Fix crash on windows left clicking the systray icon when not having an + attached window [tw1nk](https://github.com/tw1nk) in PR + [#3271](https://github.com/wailsapp/wails/pull/3271) +- Fix wrong baseURL when open window twice by @5aaee9 in PR + [#3273](https://github.com/wailsapp/wails/pull/3273) +- Fix ordering of if branches in `WebviewWindow.Restore` method by + [@fbbdev](https://github.com/fbbdev) in + [#3279](https://github.com/wailsapp/wails/pull/3279) +- Correctly compute `startURL` across multiple `GetStartURL` invocations when + `FRONTEND_DEVSERVER_URL` is present. + [#3299](https://github.com/wailsapp/wails/pull/3299) +- Fix the JS type of the `Screen` struct to match its Go counterpart by + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Fix the `WML.Reload` method to ensure proper cleanup of registered event + listeners by [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- Fix custom context menu closing immediately on linux by + [@abichinger](https://github.com/abichinger) in + [#3330](https://github.com/wailsapp/wails/pull/3330) +- Fix the output path and extension of model files produced by the binding + generator by [@fbbdev](https://github.com/fbbdev) in + [#3334](https://github.com/wailsapp/wails/pull/3334) +- Fix the import paths of model files in JS code produced by the binding + generator by [@fbbdev](https://github.com/fbbdev) in + [#3334](https://github.com/wailsapp/wails/pull/3334) +- Fix drag-n-drop on some linux distros by + [@abichinger](https://github.com/abichinger) in + [#3346](https://github.com/wailsapp/wails/pull/3346) +- Fix missing task for macOS when using `wails3 task dev` by + [@hfoxy](https://github.com/hfoxy) in + [#3417](https://github.com/wailsapp/wails/pull/3417) +- Fix registering events causing a nil map assignment by + [@hfoxy](https://github.com/hfoxy) in + [#3426](https://github.com/wailsapp/wails/pull/3426) +- Fix unmarshaling of bound method parameters by + [@fbbdev](https://github.com/fbbdev) in + [#3431](https://github.com/wailsapp/wails/pull/3431) +- Fix handling of multiple return values from bound methods by + [@fbbdev](https://github.com/fbbdev) in + [#3431](https://github.com/wailsapp/wails/pull/3431) +- Fix doctor detection of npm that is not installed with system package manager + by [@pekim](https://github.com/pekim) in + [#3458](https://github.com/wailsapp/wails/pull/3458) +- Fix missing MicrosoftEdgeWebview2Setup.exe. Thanks to + [@robin-samuel](https://github.com/robin-samuel). +- Fix random crash on linux due to window ID handling by @leaanthony. Based on + PR [#3466](https://github.com/wailsapp/wails/pull/3622) by + [@5aaee9](https://github.com/5aaee9). +- Fix systemTray.setIcon crashing on Linux by + [@windom](https://github.com/windom/) in + [#3636](https://github.com/wailsapp/wails/pull/3636). +- Fix Ensure Window Frame is Applied on First Call in `setFrameless` Function on + Windows by [@bruxaodev](https://github.com/bruxaodev/) in + [#3691](https://github.com/wailsapp/wails/pull/3691). + +### Changed + +- Renamed `AbsolutePosition()` to `Position()` by + [mmghv](https://github.com/mmghv) in + [#3611](https://github.com/wailsapp/wails/pull/3611) +- Update linux webkit dependency to webkit2gtk-4.1 over webkitgtk2-4.0 to + support Ubuntu 24.04 LTS by [atterpac](https://github.com/atterpac) in + [#3461](https://github.com/wailsapp/wails/pull/3461) +- The bundled JS runtime script is now an ESM module: script tags importing it + must have the `type="module"` attribute. By + [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- The `@wailsio/runtime` package does not publish its API on the `window.wails` + object, and does not start the WML system. This has been done to improve + encapsulation. The WML system can be started manually if desired by calling + the new `WML.Enable` method. The bundled JS runtime script still performs both + operations automatically. By [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- The Window API module `@wailsio/runtime/src/window` now exposes the containing + window object as a default export. It is not possible anymore to import + individual methods through ESM named or namespace import syntax. +- The JS window API has been updated to match the current Go `WebviewWindow` + API. Some methods have changed name or prototype, specifically: `Screen` + becomes `GetScreen`; `GetZoomLevel`/`SetZoomLevel` become `GetZoom`/`SetZoom`; + `GetZoom`, `Width` and `Height` now return values directly instead of wrapping + them within objects. By [@fbbdev](https://github.com/fbbdev) in + [#3295](https://github.com/wailsapp/wails/pull/3295) +- The binding generator now uses calls by ID by default. The `-id` CLI option + has been removed. Use the `-names` CLI option to switch back to calls by name. + By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- New binding code layout: output files were previously organised in folders + named after their containing package; now full Go import paths are used, + including the module path. By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- The struct field `application.Options.Bind` has been renamed to + `application.Options.Services`. By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- New syntax for binding services: service instances must now be wrapped in a + call to `application.NewService`. By [@fbbdev](https://github.com/fbbdev) in + [#3468](https://github.com/wailsapp/wails/pull/3468) +- Disable spinner on Non-Terminal or CI Environment by + [@DeltaLaboratory](https://github.com/DeltaLaboratory) in + [#3574](https://github.com/wailsapp/wails/pull/3574) diff --git a/docs/src/content/docs/community/links.md b/docs/src/content/docs/community/links.md new file mode 100644 index 000000000..9507991dd --- /dev/null +++ b/docs/src/content/docs/community/links.md @@ -0,0 +1,27 @@ +--- +title: Links +--- + +This page serves as a list for community related links. + +:::tip[How to Submit a Link] + +You can click the `Edit page` at the bottom of this page to submit a PR. + +::: + +## Awesome Wails + +The [definitive list](https://github.com/wailsapp/awesome-wails) of links +related to Wails. + +## Support Channels + +- [Wails Discord Server](https://discord.gg/bdj28QNHmT) +- [Github Issues](https://github.com/wailsapp/wails/issues) + +## Social Media + +- [Twitter](https://x.com/wailsapp) +- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) - + Group number: 1067173054 diff --git a/docs/src/content/docs/community/showcase/_template.md b/docs/src/content/docs/community/showcase/_template.md new file mode 100644 index 000000000..f98a2ef06 --- /dev/null +++ b/docs/src/content/docs/community/showcase/_template.md @@ -0,0 +1,25 @@ +--- +title: My Project +draft: true +--- + + + +![My Project Screenshot](../../../../assets/showcase-images/your-project.webp) + + + +Your project description goes here. Explain what it does, what makes it special, and why you built it with Wails. + + + +[Visit Project Website](https://your-project.com) | [View on GitHub](https://github.com/yourusername/your-project) diff --git a/docs/src/content/docs/community/showcase/bulletinboard.md b/docs/src/content/docs/community/showcase/bulletinboard.md new file mode 100644 index 000000000..58d24b97f --- /dev/null +++ b/docs/src/content/docs/community/showcase/bulletinboard.md @@ -0,0 +1,16 @@ +--- +title: BulletinBoard +--- + +![BulletinBoard](../../../../assets/showcase-images/bboard.webp) + +The [BulletinBoard](https://github.com/raguay/BulletinBoard) application is a +versital message board for static messages or dialogs to get information from +the user for a script. It has a TUI for creating new dialogs that can latter be +used to get information from the user. It's design is to stay running on your +system and show the information as needed and then hide away. I have a process +for watching a file on my system and sending the contents to BulletinBoard when +changed. It works great with my workflows. T here is also an +[Alfred workflow](https://github.com/raguay/MyAlfred/blob/master/Alfred%205/EmailIt.alfredworkflow) +for sending information to the program. The workflow is also for working with +[EmailIt](https://github.com/raguay/EmailIt). diff --git a/docs/src/content/docs/community/showcase/cfntracker.md b/docs/src/content/docs/community/showcase/cfntracker.md new file mode 100644 index 000000000..dc9c030f3 --- /dev/null +++ b/docs/src/content/docs/community/showcase/cfntracker.md @@ -0,0 +1,36 @@ +--- +title: CFN Tracker +--- + +![CFN Tracker](../../../../assets/showcase-images/cfntracker.webp) + +[CFN Tracker](https://github.com/williamsjokvist/cfn-tracker) - Track any Street +Fighter 6 or V CFN profile's live matches. Check +[the website](https://cfn.williamsjokvist.se/) to get started. + +## Features + +- Real-time match tracking +- Storing match logs and statistics +- Support for displaying live stats to OBS via Browser Source +- Support for both SF6 and SFV +- Ability for users to create their own OBS Browser themes with CSS + +### Major tech used alongside Wails + +- [Task](https://github.com/go-task/task) - wrapping the Wails CLI to make + common commands easy to use +- [React](https://github.com/facebook/react) - chosen for its rich ecosystem + (radix, framer-motion) +- [Bun](https://github.com/oven-sh/bun) - used for its fast dependency + resolution and build-time +- [Rod](https://github.com/go-rod/rod) - headless browser automation for + authentication and polling changes +- [SQLite](https://github.com/mattn/go-sqlite3) - used for storing matches, + sessions and profiles +- [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) - + a http stream to send tracking updates to OBS browser sources +- [i18next](https://github.com/i18next/) - with backend connector to serve + localization objects from the Go layer +- [xstate](https://github.com/statelyai/xstate) - state machines for auth + process and tracking diff --git a/docs/src/content/docs/community/showcase/clave.md b/docs/src/content/docs/community/showcase/clave.md new file mode 100644 index 000000000..9c405e616 --- /dev/null +++ b/docs/src/content/docs/community/showcase/clave.md @@ -0,0 +1,18 @@ +--- +title: Clave +--- + +![Clave](../../../../assets/showcase-images/clave.png) + +Key Features + +- 🎨 Simple, intuitive design for hassle-free experience +- ✅ Add accounts easily via manual entry or QR code image import. +- 🔒 End-to-end encryption with PIN or Touch ID(macOS only). +- 💻 Available for macOS, Windows, and Linux +- ⚡ Quick access from system tray for convenience +- 📂 Easily backup and restore your profiles + +Try Clave Today! + +💻 [https://clave.rocks](https://clave.rocks) diff --git a/docs/src/content/docs/community/showcase/emailit.md b/docs/src/content/docs/community/showcase/emailit.md new file mode 100644 index 000000000..6afc1da5e --- /dev/null +++ b/docs/src/content/docs/community/showcase/emailit.md @@ -0,0 +1,14 @@ +--- +title: EmailIt +--- + +![EmailIt](../../../../assets/showcase-images/emailit.webp) + +[EmailIt](https://github.com/raguay/EmailIt/) is a Wails 2 program that is a +markdown based email sender only with nine notepads, scripts to manipulate the +text, and templates. It also has a scripts terminal to run scripts in EmailIt on +files in your system. The scripts and templates can be used from the commandline +itself or with the Alfred, Keyboard Maestro, Dropzone, or PopClip extensions. It +also supports scripts and themes downloaded form GitHub. Documentation is not +complete, but the programs works. It’s built using Wails2 and Svelte, and the +download is a universal macOS application. diff --git a/docs/src/content/docs/community/showcase/encrypteasy.md b/docs/src/content/docs/community/showcase/encrypteasy.md new file mode 100644 index 000000000..3a098fac9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/encrypteasy.md @@ -0,0 +1,16 @@ +--- +title: EncryptEasy +--- + +![EncryptEasy](../../../../assets/showcase-images/encrypteasy.webp) + +**[EncryptEasy](https://www.encrypteasy.app) is a simple and easy to use PGP +encryption tool, managing all your and your contacts keys. Encryption should be +simple. Developed with Wails.** + +Encrypting messages using PGP is the industry standard. Everyone has a private +and a public key. Your private key, well, needs to be kept private so only you +can read messages. Your public key is distributed to anyone who wants to send +you secret, encrypted messages. Managing keys, encrypting messages and +decrypting messages should be a smooth experience. EncryptEasy is all about +making it easy. diff --git a/docs/src/content/docs/community/showcase/espstudio.md b/docs/src/content/docs/community/showcase/espstudio.md new file mode 100644 index 000000000..0ac90a827 --- /dev/null +++ b/docs/src/content/docs/community/showcase/espstudio.md @@ -0,0 +1,9 @@ +--- +title: ESP Studio +--- + +![ESP Studio](../../../../assets/showcase-images/esp-studio.png) + +[ESP Studio](https://github.com/torabian/esp-studio) - Cross platform, Desktop, +Cloud, and Embedded software for controlling ESP/Arduino devices, and building +complex IOT workflows and control systems diff --git a/docs/src/content/docs/community/showcase/filehound.md b/docs/src/content/docs/community/showcase/filehound.md new file mode 100644 index 000000000..627007f97 --- /dev/null +++ b/docs/src/content/docs/community/showcase/filehound.md @@ -0,0 +1,29 @@ +--- +title: FileHound Export Utility +--- + +![FileHound Export Utility](../../../../assets/showcase-images/filehound.webp) + +[FileHound Export Utility](https://www.filehound.co.uk/) FileHound is a cloud +document management platform made for secure file retention, business process +automation and SmartCapture capabilities. + +The FileHound Export Utility allows FileHound Administrators the ability to run +a secure document and data extraction tasks for alternative back-up and recovery +purposes. This application will download all documents and/or meta data saved in +FileHound based on the filters you choose. The metadata will be exported in both +JSON and XML formats. + +Backend built with: + +- Go 1.15 +- Wails 1.11.0 +- go-sqlite3 1.14.6 +- go-linq 3.2 + +Frontend with: + +- Vue 2.6.11 +- Vuex 3.4.0 +- TypeScript +- Tailwind 1.9.6 diff --git a/docs/src/content/docs/community/showcase/hiposter.md b/docs/src/content/docs/community/showcase/hiposter.md new file mode 100644 index 000000000..08acfa832 --- /dev/null +++ b/docs/src/content/docs/community/showcase/hiposter.md @@ -0,0 +1,8 @@ +--- +title: hiposter +--- + +![hiposter](../../../../assets/showcase-images/hiposter.webp) + +[hiposter](https://github.com/obity/hiposter) is a simple and efficient http API +testing client tool. Based on Wails, Go and sveltejs. diff --git a/docs/src/content/docs/community/showcase/index.mdx b/docs/src/content/docs/community/showcase/index.mdx new file mode 100644 index 000000000..e1da3f75d --- /dev/null +++ b/docs/src/content/docs/community/showcase/index.mdx @@ -0,0 +1,189 @@ +--- +title: Overview +sidebar: + order: 1 +--- + +import { ShowcaseImage } from "starlight-showcases"; +import { Steps } from "@astrojs/starlight/components"; + +:::tip[See how to add your project] + +Check out the +[How to add my project in showcase](#how-to-add-my-project-in-showcase) section. + +::: + + + +## How to add my project in showcase + + + +1. Make a fork of the repository. +2. Add the image(s) under `docs/src/assets/showcase-images` folder. +3. Make a copy of the `_template.md` file under + `docs/src/content/docs/community/showcase` folder. +4. Rename the copied file to the name of your project. (Name should not start + with `_`)) +5. Update the title, image, link and content of the file. +6. Add it on the above list in + `docs/src/content/docs/community/showcase/index.mdx`. +7. Submit a PR. + + diff --git a/docs/src/content/docs/community/showcase/mchat.md b/docs/src/content/docs/community/showcase/mchat.md new file mode 100644 index 000000000..444aed343 --- /dev/null +++ b/docs/src/content/docs/community/showcase/mchat.md @@ -0,0 +1,8 @@ +--- +title: Mchat +--- + +![Mchat](../../../../assets/showcase-images/mchat.png) + +[Official page](https://marcio199226.github.io/mchat-site/public/) Fully +anonymous end2end encrypted chat. diff --git a/docs/src/content/docs/community/showcase/minecraftupdater.md b/docs/src/content/docs/community/showcase/minecraftupdater.md new file mode 100644 index 000000000..57cd80e30 --- /dev/null +++ b/docs/src/content/docs/community/showcase/minecraftupdater.md @@ -0,0 +1,10 @@ +--- +title: Minecraft Updater +--- + +![Minecraft Updater](../../../../assets/showcase-images/minecraft-mod-updater.webp) + +[Minecraft Updater](https://github.com/Gurkengewuerz/MinecraftModUpdater) is a +utility tool to update and synchronize Minecraft mods for your userbase. It’s +built using Wails2 and React with [antd](https://ant.design/) as frontend +framework. diff --git a/docs/src/content/docs/community/showcase/minesweeper-xp.md b/docs/src/content/docs/community/showcase/minesweeper-xp.md new file mode 100644 index 000000000..9ca30ed55 --- /dev/null +++ b/docs/src/content/docs/community/showcase/minesweeper-xp.md @@ -0,0 +1,8 @@ +--- +title: Minesweeper XP +--- + +![Minesweeper XP](../../../../assets/showcase-images/minesweeper-xp.webp) + +[Minesweeper-XP](https://git.new/Minesweeper-XP) allows you to experience the +classic Minesweeper XP (+ 98 and 3.1) on macOS, Windows, and Linux! diff --git a/docs/src/content/docs/community/showcase/modalfilemanager.md b/docs/src/content/docs/community/showcase/modalfilemanager.md new file mode 100644 index 000000000..7a7748d4d --- /dev/null +++ b/docs/src/content/docs/community/showcase/modalfilemanager.md @@ -0,0 +1,21 @@ +--- +title: Modal File Manager +--- + +![Modal File Manager](../../../../assets/showcase-images/modalfilemanager.webp) + +[Modal File Manager](https://github.com/raguay/ModalFileManager) is a dual pane +file manager using web technologies. My original design was based on NW.js and +can be found [here](https://github.com/raguay/ModalFileManager-NWjs). This +version uses the same Svelte based frontend code (but it has be greatly modified +since the departure from NW.js), but the backend is a +[Wails 2](https://wails.io/) implementation. By using this implementation, I no +longer use command line `rm`, `cp`, etc. commands, but a git install has to be +on the system to download themes and extensions. It is fully coded using Go and +runs much faster than the previous versions. + +This file manager is designed around the same principle as Vim: a state +controlled keyboard actions. The number of states isn't fixed, but very +programmable. Therefore, an infinite number of keyboard configurations can be +created and used. This is the main difference from other file managers. There +are themes and extensions available to download from GitHub. diff --git a/docs/src/content/docs/community/showcase/mollywallet.md b/docs/src/content/docs/community/showcase/mollywallet.md new file mode 100644 index 000000000..930aebd75 --- /dev/null +++ b/docs/src/content/docs/community/showcase/mollywallet.md @@ -0,0 +1,9 @@ +--- +title: Molley Wallet +--- + +![Molly Wallet](../../../../assets/showcase-images/mollywallet.webp) + +[Molly Wallet](https://github.com/grvlle/constellation_wallet/) the official +$DAG wallet of the Constellation Network. It'll let users interact with the +Hypergraph Network in various ways, not limited to producing $DAG transactions. diff --git a/docs/src/content/docs/community/showcase/october.md b/docs/src/content/docs/community/showcase/october.md new file mode 100644 index 000000000..60a29e91b --- /dev/null +++ b/docs/src/content/docs/community/showcase/october.md @@ -0,0 +1,16 @@ +--- +title: October +--- + +![October](../../../../assets/showcase-images/october.webp) + +[October](https://october.utf9k.net) is a small Wails application that makes it +really easy to extract highlights from +[Kobo eReaders](https://en.wikipedia.org/wiki/Kobo_eReader) and then forward +them to [Readwise](https://readwise.io). + +It has a relatively small scope with all platform versions weighing in under +10MB, and that's without enabling [UPX compression](https://upx.github.io/)! + +In contrast, the author's previous attempts with Electron quickly bloated to +several hundred megabytes. diff --git a/docs/src/content/docs/community/showcase/optimus.md b/docs/src/content/docs/community/showcase/optimus.md new file mode 100644 index 000000000..6128258b2 --- /dev/null +++ b/docs/src/content/docs/community/showcase/optimus.md @@ -0,0 +1,9 @@ +--- +title: Optimus +--- + +![Optimus](../../../../assets/showcase-images/optimus.webp) + +[Optimus](https://github.com/splode/optimus) is a desktop image optimization +application. It supports conversion and compression between WebP, JPEG, and PNG +image formats. diff --git a/docs/src/content/docs/community/showcase/portfall.md b/docs/src/content/docs/community/showcase/portfall.md new file mode 100644 index 000000000..c1ffc76f9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/portfall.md @@ -0,0 +1,8 @@ +--- +title: Portfall +--- + +![Portfall](../../../../assets/showcase-images/portfall.webp) + +[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s +port-forwarding portal for easy access to all your cluster UIs diff --git a/docs/src/content/docs/community/showcase/resizem.md b/docs/src/content/docs/community/showcase/resizem.md new file mode 100644 index 000000000..3e8761dbc --- /dev/null +++ b/docs/src/content/docs/community/showcase/resizem.md @@ -0,0 +1,9 @@ +--- +title: Resizem +--- + +![Resizem Screenshot](../../../../assets/showcase-images/resizem.webp) + +[Resizem](https://github.com/barats/resizem) - is an app designed for bulk image +process. It is particularly useful for users who need to resize, convert, and +manage large numbers of image files at once. diff --git a/docs/src/content/docs/community/showcase/restic-browser.md b/docs/src/content/docs/community/showcase/restic-browser.md new file mode 100644 index 000000000..5a68cb02e --- /dev/null +++ b/docs/src/content/docs/community/showcase/restic-browser.md @@ -0,0 +1,9 @@ +--- +title: Restic Browser +--- + +![Restic Browser](../../../../assets/showcase-images/restic-browser-2.png) + +[Restic-Browser](https://github.com/emuell/restic-browser) - A simple, +cross-platform [restic](https://github.com/restic/restic) backup GUI for +browsing and restoring restic repositories. diff --git a/docs/src/content/docs/community/showcase/riftshare.md b/docs/src/content/docs/community/showcase/riftshare.md new file mode 100644 index 000000000..b6ed8f464 --- /dev/null +++ b/docs/src/content/docs/community/showcase/riftshare.md @@ -0,0 +1,23 @@ +--- +title: RiftShare +--- + +![RiftShare](../../../../assets/showcase-images/riftshare-main.webp) + +Easy, Secure, and Free file sharing for everyone. Learn more at +[Riftshare.app](https://riftshare.app) + +## Features + +- Easy secure file sharing between computers both in the local network and + through the internet +- Supports sending files or directories securely through the + [magic wormhole protocol](https://magic-wormhole.readthedocs.io/en/latest/) +- Compatible with all other apps using magic wormhole (magic-wormhole or + wormhole-william CLI, wormhole-gui, etc.) +- Automatic zipping of multiple selected files to send at once +- Full animations, progress bar, and cancellation support for sending and + receiving +- Native OS File Selection +- Open files in one click once received +- Auto Update - don't worry about having the latest release! diff --git a/docs/src/content/docs/community/showcase/scriptbar.md b/docs/src/content/docs/community/showcase/scriptbar.md new file mode 100644 index 000000000..1e03ab528 --- /dev/null +++ b/docs/src/content/docs/community/showcase/scriptbar.md @@ -0,0 +1,14 @@ +--- +title: ScriptBar +--- + +![ScriptBar](../../../../assets/showcase-images/scriptbar.webp) + +[ScriptBar](https://GitHub.com/raguay/ScriptBarApp) is a program to show the +output of scripts or [Node-Red](https://nodered.org) server. It runs scripts +defined in EmailIt program and shows the output. Scripts from xBar or TextBar +can be used, but currently on the TextBar scripts work well. It also displays +the output of scripts on your system. ScriptBar doesn't put them in the menubar, +but has them all in a convient window for easy viewing. You can have multiple +tabs to have many different things show. You can also keep the links to your +most visited web sites. diff --git a/docs/src/content/docs/community/showcase/snippetexpander.md b/docs/src/content/docs/community/showcase/snippetexpander.md new file mode 100644 index 000000000..0b5a88801 --- /dev/null +++ b/docs/src/content/docs/community/showcase/snippetexpander.md @@ -0,0 +1,34 @@ +--- +title: Snippet Expander +--- + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-select-snippet.png) + +Screenshot of Snippet Expander's Select Snippet window + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-add-snippet.png) + +Screenshot of Snippet Expander's Add Snippet screen + +![Snippet Expander Screenshot](../../../../assets/showcase-images/snippetexpandergui-search-and-paste.png) + +Screenshot of Snippet Expander's Search & Paste window + +[Snippet Expander](https://snippetexpander.org) is "Your little expandable text +snippets helper", for Linux. + +Snippet Expander comprises of a GUI application built with Wails for managing +snippets and settings, with a Search & Paste window mode for quickly selecting +and pasting a snippet. + +The Wails based GUI, go-lang CLI and vala-lang auto expander daemon all +communicate with a go-lang daemon via D-Bus. The daemon does the majority of the +work, managing the database of snippets and common settings, and providing +services for expanding and pasting snippets etc. + +Check out the +[source code](https://git.sr.ht/~ianmjones/snippetexpander/tree/trunk/item/cmd/snippetexpandergui/app.go#L38) +to see how the Wails app sends messages from the UI to the backend that are then +sent to the daemon, and subscribes to a D-Bus event to monitor changes to +snippets via another instance of the app or CLI and show them instantly in the +UI via a Wails event. diff --git a/docs/src/content/docs/community/showcase/surge.md b/docs/src/content/docs/community/showcase/surge.md new file mode 100644 index 000000000..3ab4444dd --- /dev/null +++ b/docs/src/content/docs/community/showcase/surge.md @@ -0,0 +1,9 @@ +--- +title: Surge +--- + +![Surge Screenshot](../../../../assets/showcase-images/surge.png) + +[Surge](https://getsurge.io/) is a p2p filesharing app designed to utilize +blockchain technologies to enable 100% anonymous file transfers. Surge is +end-to-end encrypted, decentralized and open source. diff --git a/docs/src/content/docs/community/showcase/tinyrdm.md b/docs/src/content/docs/community/showcase/tinyrdm.md new file mode 100644 index 000000000..f9551e046 --- /dev/null +++ b/docs/src/content/docs/community/showcase/tinyrdm.md @@ -0,0 +1,12 @@ +--- +title: Tiny RDM +--- + +![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm1.webp) +![Tiny RDM Screenshot](../../../../assets/showcase-images/tiny-rdm2.webp) + +The [Tiny RDM](https://redis.tinycraft.cc/) application is an open-source, +modern lightweight Redis GUI. It has a beautful UI, intuitive Redis database +management, and compatible with Windows, Mac, and Linux. It provides visual +key-value data operations, supports various data decoding and viewing options, +built-in console for executing commands, slow log queries and more. diff --git a/docs/src/content/docs/community/showcase/wailsterm.md b/docs/src/content/docs/community/showcase/wailsterm.md new file mode 100644 index 000000000..0521aac70 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wailsterm.md @@ -0,0 +1,8 @@ +--- +title: WailsTerm +--- + +![WailsTerm Screenshot](../../../../assets/showcase-images/wailsterm.webp) + +[WailsTerm](https://github.com/rlshukhov/wailsterm) is a simple translucent +terminal app powered by Wails and Xterm.js. diff --git a/docs/src/content/docs/community/showcase/wally.md b/docs/src/content/docs/community/showcase/wally.md new file mode 100644 index 000000000..3f4ddbfa2 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wally.md @@ -0,0 +1,10 @@ +--- +title: Wally +--- + +![Wally Screenshot](../../../../assets/showcase-images/wally.webp) + +[Wally](https://ergodox-ez.com/pages/wally) is the official firmware flasher for +[Ergodox](https://ergodox-ez.com/) keyboards. It looks great and is a fantastic +example of what you can achieve with Wails: the ability to combine the power of +Go and the rich graphical tools of the web development world. diff --git a/docs/src/content/docs/community/showcase/warmine.md b/docs/src/content/docs/community/showcase/warmine.md new file mode 100644 index 000000000..19de97f2a --- /dev/null +++ b/docs/src/content/docs/community/showcase/warmine.md @@ -0,0 +1,17 @@ +--- +title: Minecraft launcher for WarMine +--- + +![WarMine Screenshot](../../../../assets/showcase-images/warmine1.png) +![WarMine Screenshot](../../../../assets/showcase-images/warmine2.png) + +[Minecraft launcher for WarMine](https://warmine.ru/) is a Wails application, +that allows you to easily join modded game servers and manage your game +accounts. + +The Launcher downloads the game files, checks their integrity and launches the +game with a wide range of customization options for the launch arguments from +the backend. + +Frontend is written in Svelte, whole launcher fits in 9MB and supports Windows +7-11. diff --git a/docs/src/content/docs/community/showcase/wombat.md b/docs/src/content/docs/community/showcase/wombat.md new file mode 100644 index 000000000..d61bdd6c9 --- /dev/null +++ b/docs/src/content/docs/community/showcase/wombat.md @@ -0,0 +1,7 @@ +--- +title: Wombat +--- + +![Wombat Screenshot](../../../../assets/showcase-images/wombat.webp) + +[Wombat](https://github.com/rogchap/wombat) is a cross platform gRPC client. diff --git a/docs/src/content/docs/community/showcase/ytd.md b/docs/src/content/docs/community/showcase/ytd.md new file mode 100644 index 000000000..bbcd6979f --- /dev/null +++ b/docs/src/content/docs/community/showcase/ytd.md @@ -0,0 +1,10 @@ +--- +title: Ytd +--- + +![Ytd Screenshot](../../../../assets/showcase-images/ytd.webp) + +[Ytd](https://github.com/marcio199226/ytd/tree/v2-wails) is an app for +downloading tracks from youtube, creating offline playlists and share them with +your friends, your friends will be able to playback your playlists or download +them for offline listening, has an built-in player. diff --git a/docs/src/content/docs/community/templates.md b/docs/src/content/docs/community/templates.md new file mode 100644 index 000000000..855b70c23 --- /dev/null +++ b/docs/src/content/docs/community/templates.md @@ -0,0 +1,132 @@ +--- +title: Templates +--- + +:::caution + +This page might be outdated for Wails v3. + +::: + + + +This page serves as a list for community supported templates. To build your own +template, please see the [Templates](https://wails.io/docs/guides/templates) +guide. + +:::tip[How to Submit a Template] + +You can click `Edit this page` at the bottom to include your templates. + +::: + +To use these templates, run +`wails init -n "Your Project Name" -t [the link below[@version]]` + +If there is no version suffix, the main branch code template is used by default. +If there is a version suffix, the code template corresponding to the tag of this +version is used. + +Example: +`wails init -n "Your Project Name" -t https://github.com/misitebao/wails-template-vue` + +:::danger[Attention] + +**The Wails project does not maintain, is not responsible nor liable for 3rd +party templates!** + +If you are unsure about a template, inspect `package.json` and `wails.json` for +what scripts are run and what packages are installed. + +::: + +## Vue + +- [wails-template-vue](https://github.com/misitebao/wails-template-vue) - Wails + template based on Vue ecology (Integrated TypeScript, Dark theme, + Internationalization, Single page routing, TailwindCSS) +- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) - + A template using JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, + Prettier) +- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) - + A template using TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint, + Prettier, Composition API with <script setup>) +- [wails-template-naive](https://github.com/tk103331/wails-template-naive) - + Wails template based on Naive UI (A Vue 3 Component Library) +- [wails-template-nuxt](https://github.com/gornius/wails-template-nuxt) - Wails + template using clean Nuxt3 and TypeScript with auto-imports for wails js + runtime +- [Wails-Tool-Template](https://github.com/xisuo67/Wails-Tool-Template) - Wails + template using Vue+TypeScript+Vite+Element-plus(仿网易云) + +## Angular + +- [wails-template-angular](https://github.com/mateothegreat/wails-template-angular) - + Angular 15+ action packed & ready to roll to production. +- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) - + Angular with TypeScript, Sass, Hot-Reload, Code-Splitting and i18n + +## React + +- [wails-react-template](https://github.com/AlienRecall/wails-react-template) - + A template using reactjs +- [wails-react-template](https://github.com/flin7/wails-react-template) - A + minimal template for React that supports live development +- [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - A + template using Next.js and TypeScript +- [wails-template-nextjs-app-router](https://github.com/thisisvk-in/wails-template-nextjs-app-router) - + A template using Next.js and TypeScript with App router +- [wails-template-nextjs-app-router-src](https://github.com/edai-git/wails-template-nextjs-app-router) - + A template using Next.js and TypeScript with App router src + example +- [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) - + A template for React + TypeScript + Vite + TailwindCSS +- [wails-vite-react-ts-tailwind-shadcnui-template](https://github.com/Mahcks/wails-vite-react-tailwind-shadcnui-ts) - + A template with Vite, React, TypeScript, TailwindCSS, and shadcn/ui + +## Svelte + +- [wails-svelte-template](https://github.com/raitonoberu/wails-svelte-template) - + A template using Svelte +- [wails-vite-svelte-template](https://github.com/BillBuilt/wails-vite-svelte-template) - + A template using Svelte and Vite +- [wails-vite-svelte-tailwind-template](https://github.com/BillBuilt/wails-vite-svelte-tailwind-template) - + A template using Svelte and Vite with TailwindCSS v3 +- [wails-svelte-tailwind-vite-template](https://github.com/PylotLight/wails-vite-svelte-tailwind-template/tree/master) - + An updated template using Svelte v4.2.0 and Vite with TailwindCSS v3.3.3 +- [wails-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) - + A template using SvelteKit +- [wails-template-shadcn-svelte](https://github.com/xijaja/wails-template-shadcn-svelte) - + A template using Sveltekit and Shadcn-Svelte + +## Solid + +- [wails-template-vite-solid-ts](https://github.com/xijaja/wails-template-solid-ts) - + A template using Solid + Ts + Vite +- [wails-template-vite-solid-js](https://github.com/xijaja/wails-template-solid-js) - + A template using Solid + Js + Vite + +## Elm + +- [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) - + Develop your GUI app with functional programming and a **snappy** hot-reload + setup :tada: :rocket: +- [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) - + Combine the powers :muscle: of Elm + Tailwind CSS + Wails! Hot reloading + supported. + +## HTMX + +- [wails-htmx-templ-chi-tailwind](https://github.com/PylotLight/wails-hmtx-templ-template) - + Use a unique combination of pure htmx for interactivity plus templ for + creating components and forms + +## Pure JavaScript (Vanilla) + +- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A + template with nothing but just basic JavaScript, HTML, and CSS + +## Lit (web components) + +- [wails-lit-shoelace-esbuild-template](https://github.com/Braincompiler/wails-lit-shoelace-esbuild-template) - + Wails template providing frontend with lit, Shoelace component library + + pre-configured prettier and typescript. diff --git a/docs/src/content/docs/concepts/architecture.mdx b/docs/src/content/docs/concepts/architecture.mdx new file mode 100644 index 000000000..4f72c97ee --- /dev/null +++ b/docs/src/content/docs/concepts/architecture.mdx @@ -0,0 +1,675 @@ +--- +title: How Wails Works +description: Understanding the Wails architecture and how it achieves native performance +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails is a framework for building desktop applications using **Go for the backend** and **web technologies for the frontend**. But unlike Electron, Wails doesn't bundle a browser—it uses the **operating system's native WebView**. + +```d2 +direction: right + +User: "User" { + shape: person + style.fill: "#3B82F6" +} + +Application: "Your Wails Application" { + Frontend: "Frontend\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } + + Runtime: "Wails Runtime" { + Bridge: "Message Bridge" { + shape: diamond + style.fill: "#10B981" + } + + Bindings: "Type-Safe Bindings" { + shape: rectangle + style.fill: "#10B981" + } + } + + Backend: "Go Backend" { + Services: "Your Services" { + shape: rectangle + style.fill: "#00ADD8" + } + + NativeAPIs: "OS APIs" { + shape: rectangle + style.fill: "#00ADD8" + } + } +} + +OS: "Operating System" { + WebView: "Native WebView\n(WebKit/WebView2/WebKitGTK)" { + shape: rectangle + style.fill: "#6B7280" + } + + SystemAPIs: "System APIs\n(Windows/macOS/Linux)" { + shape: rectangle + style.fill: "#6B7280" + } +} + +User -> Application.Frontend: "Interacts with UI" +Application.Frontend <-> Application.Runtime.Bridge: "JSON messages" +Application.Runtime.Bridge <-> Application.Backend.Services: "Direct function calls" +Application.Runtime.Bindings -> Application.Frontend: "TypeScript definitions" +Application.Frontend -> OS.WebView: "Renders in" +Application.Backend.NativeAPIs -> OS.SystemAPIs: "Native calls" +``` + +**Key differences from Electron:** + +| Aspect | Wails | Electron | +|--------|-------|----------| +| **Browser** | OS-provided WebView | Bundled Chromium (~100MB) | +| **Backend** | Go (compiled) | Node.js (interpreted) | +| **Communication** | In-memory bridge | IPC (inter-process) | +| **Bundle Size** | ~15MB | ~150MB | +| **Memory** | ~10MB | ~100MB+ | +| **Startup** | <0.5s | 2-3s | + +## Core Components + +### 1. Native WebView + +Wails uses the operating system's built-in web rendering engine: + + + + **WebView2** (Microsoft Edge WebView2) + - Based on Chromium (same as Edge browser) + - Pre-installed on Windows 10/11 + - Automatic updates via Windows Update + - Full modern web standards support + + + + **WebKit** (Safari's rendering engine) + - Built into macOS + - Same engine as Safari browser + - Excellent performance and battery life + - Full modern web standards support + + + + **WebKitGTK** (GTK port of WebKit) + - Installed via package manager + - Same engine as GNOME Web (Epiphany) + - Good standards support + - Lightweight and performant + + + +**Why this matters:** +- **No bundled browser** → Smaller app size +- **OS-native** → Better integration and performance +- **Auto-updates** → Security patches from OS updates +- **Familiar rendering** → Same as system browser + +### 2. The Wails Bridge + +The bridge is the heart of Wails—it enables **direct communication** between Go and JavaScript. + +```d2 +direction: down + +Frontend: "Frontend (JavaScript)" { + shape: rectangle + style.fill: "#8B5CF6" +} + +Bridge: "Wails Bridge" { + Encoder: "JSON Encoder" { + shape: rectangle + } + + Router: "Method Router" { + shape: diamond + style.fill: "#10B981" + } + + Decoder: "JSON Decoder" { + shape: rectangle + } +} + +Backend: "Backend (Go)" { + Services: "Registered Services" { + shape: rectangle + style.fill: "#00ADD8" + } +} + +Frontend -> Bridge.Encoder: "1. Call Go method\nGreet('Alice')" +Bridge.Encoder -> Bridge.Router: "2. Encode to JSON\n{method: 'Greet', args: ['Alice']}" +Bridge.Router -> Backend.Services: "3. Route to service\nGreetService.Greet('Alice')" +Backend.Services -> Bridge.Decoder: "4. Return result\n'Hello, Alice!'" +Bridge.Decoder -> Frontend: "5. Decode to JS\nPromise resolves" +``` + +**How it works:** + +1. **Frontend calls a Go method** (via auto-generated binding) +2. **Bridge encodes the call** to JSON (method name + arguments) +3. **Router finds the Go method** in registered services +4. **Go method executes** and returns a value +5. **Bridge decodes the result** and sends back to frontend +6. **Promise resolves** in JavaScript with the result + +**Performance characteristics:** +- **In-memory**: No network overhead, no HTTP +- **Zero-copy** where possible (for large data) +- **Async by default**: Non-blocking on both sides +- **Type-safe**: TypeScript definitions auto-generated + +### 3. Service System + +Services are the recommended way to expose Go functionality to the frontend. + +```go +// Define a service (just a regular Go struct) +type GreetService struct { + prefix string +} + +// Methods with exported names are automatically available +func (g *GreetService) Greet(name string) string { + return g.prefix + name + "!" +} + +func (g *GreetService) GetTime() time.Time { + return time.Now() +} + +// Register the service +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{prefix: "Hello, "}), + }, +}) +``` + +**Service discovery:** +- Wails **scans your struct** at startup +- **Exported methods** become callable from frontend +- **Type information** is extracted for TypeScript bindings +- **Error handling** is automatic (Go errors → JS exceptions) + +**Generated TypeScript binding:** +```typescript +// Auto-generated in frontend/bindings/GreetService.ts +export function Greet(name: string): Promise +export function GetTime(): Promise +``` + +**Why services?** +- **Type-safe**: Full TypeScript support +- **Auto-discovery**: No manual registration of methods +- **Organised**: Group related functionality +- **Testable**: Services are just Go structs + +[Learn more about services →](/features/bindings/services) + +### 4. Event System + +Events enable **pub/sub communication** between components. + +```d2 +direction: right + +GoService: "Go Service" { + shape: rectangle + style.fill: "#00ADD8" +} + +EventBus: "Event Bus" { + shape: cylinder + style.fill: "#10B981" +} + +Frontend1: "Window 1" { + shape: rectangle + style.fill: "#8B5CF6" +} + +Frontend2: "Window 2" { + shape: rectangle + style.fill: "#8B5CF6" +} + +GoService -> EventBus: "Emit('data-updated', data)" +EventBus -> Frontend1: "Notify subscribers" +EventBus -> Frontend2: "Notify subscribers" +Frontend1 -> EventBus: "On('data-updated', handler)" +Frontend2 -> EventBus: "On('data-updated', handler)" +``` + +**Use cases:** +- **Window communication**: One window notifies others +- **Background tasks**: Go service notifies UI of progress +- **State synchronisation**: Keep multiple windows in sync +- **Loose coupling**: Components don't need direct references + +**Example:** +```go +// Go: Emit an event +app.Event.Emit("user-logged-in", user) +``` + +```javascript +// JavaScript: Listen for event +import { Events } from '@wailsio/runtime' + +Events.On('user-logged-in', (event) => { + console.log('User logged in:', event.data) +}) +``` + +[Learn more about events →](/features/events/system) + +## Application Lifecycle + +Understanding the lifecycle helps you know when to initialise resources and clean up. + +```d2 +direction: down + +Start: "Application Start" { + shape: oval + style.fill: "#10B981" +} + +Init: "Initialisation" { + Create: "Create Application" { + shape: rectangle + } + + Register: "Register Services" { + shape: rectangle + } + + Setup: "Setup Windows/Menus" { + shape: rectangle + } +} + +Run: "Event Loop" { + Events: "Process Events" { + shape: rectangle + } + + Messages: "Handle Messages" { + shape: rectangle + } + + Render: "Update UI" { + shape: rectangle + } +} + +Shutdown: "Shutdown" { + Cleanup: "Cleanup Resources" { + shape: rectangle + } + + Save: "Save State" { + shape: rectangle + } +} + +End: "Application End" { + shape: oval + style.fill: "#EF4444" +} + +Start -> Init.Create +Init.Create -> Init.Register +Init.Register -> Init.Setup +Init.Setup -> Run.Events +Run.Events -> Run.Messages +Run.Messages -> Run.Render +Run.Render -> Run.Events: "Loop" +Run.Events -> Shutdown.Cleanup: "Quit signal" +Shutdown.Cleanup -> Shutdown.Save +Shutdown.Save -> End +``` + +**Application lifecycle events:** + +```go +// React to application lifecycle using events +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + // Application has fully started - safe to show notifications, check for updates, etc. +}) + +// Shutdown and quit control via application options +app := application.New(application.Options{ + Name: "My App", + OnShutdown: func() { + // Cleanup before shutdown + }, + ShouldQuit: func() bool { + // Return false to prevent quit + return true + }, +}) +``` + +**Service initialisation** (for resource setup, not lifecycle hooks): + +```go +// Services initialise their own resources during startup +type AppService struct{ db *sql.DB } + +func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + s.db, err = sql.Open("sqlite3", "app.db") + return err +} + +func (s *AppService) ServiceShutdown() error { + return s.db.Close() +} +``` + +[Learn more about lifecycle →](/concepts/lifecycle) + +## Build Process + +Understanding how Wails builds your application: + +```d2 +direction: down + +Source: "Source Code" { + Go: "Go Code\n(main.go, services)" { + shape: rectangle + style.fill: "#00ADD8" + } + + Frontend: "Frontend Code\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } +} + +Build: "Build Process" { + AnalyseGo: "Analyse Go Code" { + shape: rectangle + } + + GenerateBindings: "Generate Bindings" { + shape: rectangle + } + + BuildFrontend: "Build Frontend" { + shape: rectangle + } + + CompileGo: "Compile Go" { + shape: rectangle + } + + Embed: "Embed Assets" { + shape: rectangle + } +} + +Output: "Output" { + Binary: "Native Binary\n(myapp.exe/.app)" { + shape: rectangle + style.fill: "#10B981" + } +} + +Source.Go -> Build.AnalyseGo +Build.AnalyseGo -> Build.GenerateBindings: "Extract types" +Build.GenerateBindings -> Source.Frontend: "TypeScript bindings" +Source.Frontend -> Build.BuildFrontend: "Compile (Vite/webpack)" +Build.BuildFrontend -> Build.Embed: "Bundled assets" +Source.Go -> Build.CompileGo +Build.CompileGo -> Build.Embed +Build.Embed -> Output.Binary +``` + +**Build steps:** + +1. **Analyse Go code** + - Scan services for exported methods + - Extract parameter and return types + - Generate method signatures + +2. **Generate TypeScript bindings** + - Create `.ts` files for each service + - Include full type definitions + - Add JSDoc comments + +3. **Build frontend** + - Run your bundler (Vite, webpack, etc.) + - Minify and optimise + - Output to `frontend/dist/` + +4. **Compile Go** + - Compile with optimisations (`-ldflags="-s -w"`) + - Include build metadata + - Platform-specific compilation + +5. **Embed assets** + - Embed frontend files into Go binary + - Compress assets + - Create single executable + +**Result:** A single native executable with everything embedded. + +[Learn more about building →](/guides/build/building) + +## Development vs Production + +Wails behaves differently in development and production: + + + + **Characteristics:** + - **Hot reload**: Frontend changes reload instantly + - **Source maps**: Debug with original source + - **DevTools**: Browser DevTools available + - **Logging**: Verbose logging enabled + - **External frontend**: Served from dev server (Vite) + + **How it works:** + ```d2 + direction: right + + WailsApp: "Wails App" { + shape: rectangle + style.fill: "#00ADD8" + } + + DevServer: "Vite Dev Server\n(localhost:5173)" { + shape: rectangle + style.fill: "#8B5CF6" + } + + WebView: "WebView" { + shape: rectangle + style.fill: "#6B7280" + } + + WailsApp -> DevServer: "Proxy requests" + DevServer -> WebView: "Serve with HMR" + WebView -> WailsApp: "Call Go methods" + ``` + + **Benefits:** + - Instant feedback on changes + - Full debugging capabilities + - Faster iteration + + + + **Characteristics:** + - **Embedded assets**: Frontend built into binary + - **Optimised**: Minified, compressed + - **No DevTools**: Disabled by default + - **Minimal logging**: Errors only + - **Single file**: Everything in one executable + + **How it works:** + ```d2 + direction: right + + Binary: "Single Binary\n(myapp.exe)" { + GoCode: "Compiled Go" { + shape: rectangle + style.fill: "#00ADD8" + } + + Assets: "Embedded Assets\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } + } + + WebView: "WebView" { + shape: rectangle + style.fill: "#6B7280" + } + + Binary.Assets -> WebView: "Serve from memory" + WebView -> Binary.GoCode: "Call Go methods" + ``` + + **Benefits:** + - Single file distribution + - Smaller size (minified) + - Better performance + - No external dependencies + + + +## Memory Model + +Understanding memory usage helps you build efficient applications. + +{/* VISUAL PLACEHOLDER: Memory Diagram +Description: Memory layout diagram showing: +1. Go Heap (services, application state) +2. WebView Memory (DOM, JavaScript heap) +3. Shared Memory (bridge communication) +4. Arrows showing data flow between regions +5. Annotations for zero-copy optimisations +Style: Technical diagram with memory regions as boxes, clear labels, size indicators +*/} + +**Memory regions:** + +1. **Go Heap** + - Your services and application state + - Managed by Go garbage collector + - Typically 5-10MB for simple apps + +2. **WebView Memory** + - DOM, JavaScript heap, CSS + - Managed by WebView's engine + - Typically 10-20MB for simple apps + +3. **Bridge Memory** + - Message buffers for communication + - Minimal overhead (\<1MB) + - Zero-copy for large data where possible + +**Optimisation tips:** +- **Avoid large data transfers**: Pass IDs, fetch details on demand +- **Use events for updates**: Don't poll from frontend +- **Stream large files**: Don't load entirely into memory +- **Clean up listeners**: Remove event listeners when done + +[Learn more about performance →](/guides/advanced/performance) + +## Security Model + +Wails provides a secure-by-default architecture: + +```d2 +direction: down + +Frontend: "Frontend (Untrusted)" { + shape: rectangle + style.fill: "#EF4444" +} + +Bridge: "Wails Bridge (Validation)" { + shape: diamond + style.fill: "#F59E0B" +} + +Backend: "Backend (Trusted)" { + shape: rectangle + style.fill: "#10B981" +} + +Frontend -> Bridge: "Call method" +Bridge -> Bridge: "Validate:\n- Method exists?\n- Types correct?\n- Access allowed?" +Bridge -> Backend: "Execute if valid" +Backend -> Bridge: "Return result" +Bridge -> Frontend: "Send response" +``` + +**Security features:** + +1. **Method whitelisting** + - Only exported methods are callable + - Private methods are inaccessible + - Explicit service registration required + +2. **Type validation** + - Arguments checked against Go types + - Invalid types rejected + - Prevents injection attacks + +3. **No eval()** + - Frontend can't execute arbitrary Go code + - Only predefined methods callable + - No dynamic code execution + +4. **Context isolation** + - Each window has its own context + - Services can check caller context + - Permissions per window possible + +**Best practices:** +- **Validate user input** in Go (don't trust frontend) +- **Use context** for authentication/authorisation +- **Sanitise file paths** before file operations +- **Rate limit** expensive operations + +[Learn more about security →](/guides/advanced/security) + +## Next Steps + +**Application Lifecycle** - Understand startup, shutdown, and lifecycle hooks +[Learn More →](/concepts/lifecycle) + +**Go-Frontend Bridge** - Deep dive into how the bridge works +[Learn More →](/concepts/bridge) + +**Build System** - Understand how Wails builds your application +[Learn More →](/concepts/build-system) + +**Start Building** - Apply what you've learned in a tutorial +[Tutorials →](/tutorials/03-notes-vanilla) + +--- + +**Questions about architecture?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [API reference](/reference/overview). diff --git a/docs/src/content/docs/concepts/bridge.mdx b/docs/src/content/docs/concepts/bridge.mdx new file mode 100644 index 000000000..0773dc374 --- /dev/null +++ b/docs/src/content/docs/concepts/bridge.mdx @@ -0,0 +1,701 @@ +--- +title: Go-Frontend Bridge +description: Deep dive into how Wails enables direct communication between Go and JavaScript +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Direct Go-JavaScript Communication + +Wails provides a **direct, in-memory bridge** between Go and JavaScript, enabling seamless communication without HTTP overhead, process boundaries, or serialisation bottlenecks. + +## The Big Picture + +```d2 +direction: right + +Frontend: "Frontend (JavaScript)" { + UI: "React/Vue/Vanilla" { + shape: rectangle + style.fill: "#8B5CF6" + } + + Bindings: "Auto-Generated Bindings" { + shape: rectangle + style.fill: "#A78BFA" + } +} + +Bridge: "Wails Bridge" { + Encoder: "JSON Encoder" { + shape: rectangle + style.fill: "#10B981" + } + + Router: "Method Router" { + shape: diamond + style.fill: "#10B981" + } + + Decoder: "JSON Decoder" { + shape: rectangle + style.fill: "#10B981" + } + + TypeGen: "Type Generator" { + shape: rectangle + style.fill: "#10B981" + } +} + +Backend: "Backend (Go)" { + Services: "Your Services" { + shape: rectangle + style.fill: "#00ADD8" + } + + Registry: "Service Registry" { + shape: rectangle + style.fill: "#00ADD8" + } +} + +Frontend.UI -> Frontend.Bindings: "import { Method }" +Frontend.Bindings -> Bridge.Encoder: "Call Method('arg')" +Bridge.Encoder -> Bridge.Router: "Encode to JSON" +Bridge.Router -> Backend.Registry: "Find service" +Backend.Registry -> Backend.Services: "Invoke method" +Backend.Services -> Bridge.Decoder: "Return result" +Bridge.Decoder -> Frontend.Bindings: "Decode to JS" +Frontend.Bindings -> Frontend.UI: "Promise resolves" +Bridge.TypeGen -> Frontend.Bindings: "Generate types" +``` + +**Key insight:** No HTTP, no IPC, no process boundaries. Just **direct function calls** with **type safety**. + +## How It Works: Step by Step + +### 1. Service Registration (Startup) + +When your application starts, Wails scans your services: + +```go +type GreetService struct { + prefix string +} + +func (g *GreetService) Greet(name string) string { + return g.prefix + name + "!" +} + +func (g *GreetService) Add(a, b int) int { + return a + b +} + +// Register service +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{prefix: "Hello, "}), + }, +}) +``` + +**What Wails does:** +1. **Scans the struct** for exported methods +2. **Extracts type information** (parameters, return types) +3. **Builds a registry** mapping method names to functions +4. **Generates TypeScript bindings** with full type definitions + +### 2. Binding Generation (Build Time) + +Wails generates TypeScript bindings automatically: + +```typescript +// Auto-generated: frontend/bindings/GreetService.ts +export function Greet(name: string): Promise +export function Add(a: number, b: number): Promise +``` + +**Type mapping:** + +| Go Type | TypeScript Type | +|---------|-----------------| +| `string` | `string` | +| `int`, `int32`, `int64` | `number` | +| `float32`, `float64` | `number` | +| `bool` | `boolean` | +| `[]T` | `T[]` | +| `map[string]T` | `Record` | +| `struct` | `interface` | +| `time.Time` | `Date` | +| `error` | Exception (thrown) | + +### 3. Frontend Call (Runtime) + +Developer calls the Go method from JavaScript: + +```javascript +import { Greet, Add } from './bindings/GreetService' + +// Call Go from JavaScript +const greeting = await Greet("World") +console.log(greeting) // "Hello, World!" + +const sum = await Add(5, 3) +console.log(sum) // 8 +``` + +**What happens:** +1. **Binding function called** - `Greet("World")` +2. **Message created** - `{ service: "GreetService", method: "Greet", args: ["World"] }` +3. **Sent to bridge** - Via WebView's JavaScript bridge +4. **Promise returned** - Awaits response + +### 4. Bridge Processing (Runtime) + +The bridge receives the message and processes it: + +```d2 +direction: down + +Receive: "Receive Message" { + shape: rectangle + style.fill: "#10B981" +} + +Parse: "Parse JSON" { + shape: rectangle +} + +Validate: "Validate" { + Check: "Service exists?" { + shape: diamond + } + + CheckMethod: "Method exists?" { + shape: diamond + } + + CheckTypes: "Types correct?" { + shape: diamond + } +} + +Invoke: "Invoke Go Method" { + shape: rectangle + style.fill: "#00ADD8" +} + +Encode: "Encode Result" { + shape: rectangle +} + +Send: "Send Response" { + shape: rectangle + style.fill: "#10B981" +} + +Error: "Send Error" { + shape: rectangle + style.fill: "#EF4444" +} + +Receive -> Parse +Parse -> Validate.Check +Validate.Check -> Validate.CheckMethod: "Yes" +Validate.Check -> Error: "No" +Validate.CheckMethod -> Validate.CheckTypes: "Yes" +Validate.CheckMethod -> Error: "No" +Validate.CheckTypes -> Invoke: "Yes" +Validate.CheckTypes -> Error: "No" +Invoke -> Encode: "Success" +Invoke -> Error: "Error" +Encode -> Send +``` + +**Security:** Only registered services and exported methods are callable. + +### 5. Go Execution (Runtime) + +The Go method executes: + +```go +func (g *GreetService) Greet(name string) string { + // This runs in Go + return g.prefix + name + "!" +} +``` + +**Execution context:** +- Runs in a **goroutine** (non-blocking) +- Has access to **all Go features** (file system, network, databases) +- Can call **other Go code** freely +- Returns result or error + +### 6. Response (Runtime) + +Result is sent back to JavaScript: + +```javascript +// Promise resolves with result +const greeting = await Greet("World") +// greeting = "Hello, World!" +``` + +**Error handling:** + +```go +func (g *GreetService) Divide(a, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("division by zero") + } + return a / b, nil +} +``` + +```javascript +try { + const result = await Divide(10, 0) +} catch (error) { + console.error("Go error:", error) // "division by zero" +} +``` + +## Performance Characteristics + +### Speed + +**Typical call overhead:** <1ms + +``` +Frontend Call → Bridge → Go Execution → Bridge → Frontend Response + ↓ ↓ ↓ ↓ ↓ + <0.1ms <0.1ms [varies] <0.1ms <0.1ms +``` + +**Compared to alternatives:** +- **HTTP/REST:** 5-50ms (network stack, serialisation) +- **IPC:** 1-10ms (process boundaries, marshalling) +- **Wails Bridge:** <1ms (in-memory, direct call) + +### Memory + +**Per-call overhead:** ~1KB (message buffer) + +**Zero-copy optimisation:** Large data (>1MB) uses shared memory where possible. + +### Concurrency + +**Calls are concurrent:** +- Each call runs in its own goroutine +- Multiple calls can execute simultaneously +- No blocking between calls + +```javascript +// These run concurrently +const [result1, result2, result3] = await Promise.all([ + SlowOperation1(), + SlowOperation2(), + SlowOperation3(), +]) +``` + +## Type System + +### Supported Types + +#### Primitives + +```go +// Go +func Example( + s string, + i int, + f float64, + b bool, +) (string, int, float64, bool) { + return s, i, f, b +} +``` + +```typescript +// TypeScript (auto-generated) +function Example( + s: string, + i: number, + f: number, + b: boolean, +): Promise<[string, number, number, boolean]> +``` + +#### Slices and Arrays + +```go +// Go +func Sum(numbers []int) int { + total := 0 + for _, n := range numbers { + total += n + } + return total +} +``` + +```typescript +// TypeScript +function Sum(numbers: number[]): Promise + +// Usage +const total = await Sum([1, 2, 3, 4, 5]) // 15 +``` + +#### Maps + +```go +// Go +func GetConfig() map[string]interface{} { + return map[string]interface{}{ + "theme": "dark", + "fontSize": 14, + "enabled": true, + } +} +``` + +```typescript +// TypeScript +function GetConfig(): Promise> + +// Usage +const config = await GetConfig() +console.log(config.theme) // "dark" +``` + +#### Structs + +```go +// Go +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` +} + +func GetUser(id int) (*User, error) { + return &User{ + ID: id, + Name: "Alice", + Email: "alice@example.com", + }, nil +} +``` + +```typescript +// TypeScript (auto-generated) +interface User { + id: number + name: string + email: string +} + +function GetUser(id: number): Promise + +// Usage +const user = await GetUser(1) +console.log(user.name) // "Alice" +``` + +**JSON tags:** Use `json:` tags to control field names in TypeScript. + +#### Time + +```go +// Go +func GetTimestamp() time.Time { + return time.Now() +} +``` + +```typescript +// TypeScript +function GetTimestamp(): Promise + +// Usage +const timestamp = await GetTimestamp() +console.log(timestamp.toISOString()) +``` + +#### Errors + +```go +// Go +func Validate(input string) error { + if input == "" { + return errors.New("input cannot be empty") + } + return nil +} +``` + +```typescript +// TypeScript +function Validate(input: string): Promise + +// Usage +try { + await Validate("") +} catch (error) { + console.error(error) // "input cannot be empty" +} +``` + +### Unsupported Types + +These types **cannot** be passed across the bridge: + +- **Channels** (`chan T`) +- **Functions** (`func()`) +- **Interfaces** (except `interface{}` / `any`) +- **Pointers** (except to structs) +- **Unexported fields** (lowercase) + +**Workaround:** Use IDs or handles: + +```go +// ❌ Can't pass file handle +func OpenFile(path string) (*os.File, error) { + return os.Open(path) +} + +// ✅ Return file ID instead +var files = make(map[string]*os.File) + +func OpenFile(path string) (string, error) { + file, err := os.Open(path) + if err != nil { + return "", err + } + id := generateID() + files[id] = file + return id, nil +} + +func ReadFile(id string) ([]byte, error) { + file := files[id] + return io.ReadAll(file) +} + +func CloseFile(id string) error { + file := files[id] + delete(files, id) + return file.Close() +} +``` + +## Advanced Patterns + +### Context Passing + +Services can access the call context: + +```go +type UserService struct{} + +func (s *UserService) GetCurrentUser(ctx context.Context) (*User, error) { + // Access window that made the call + window := application.ContextWindow(ctx) + + // Access application + app := application.ContextApplication(ctx) + + // Your logic + return getCurrentUser(), nil +} +``` + +**Context provides:** +- Window that made the call +- Application instance +- Request metadata + +### Streaming Data + +For large data, use events instead of return values: + +```go +func ProcessLargeFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + lineNum := 0 + + for scanner.Scan() { + lineNum++ + // Emit progress events + app.Event.Emit("file-progress", lineNum, scanner.Text()) + } + + return scanner.Err() +} +``` + +```javascript +import { Events } from '@wailsio/runtime' +import { ProcessLargeFile } from './bindings/FileService' + +// Listen for progress +// event.data is an array of the variadic args: [lineNum, text] +Events.On('file-progress', (event) => { + const [line, text] = event.data + console.log(`Line ${line}: ${text}`) +}) + +// Start processing +await ProcessLargeFile('/path/to/large/file.txt') +``` + +### Cancellation + +Use context for cancellable operations: + +```go +func LongRunningTask(ctx context.Context) error { + for i := 0; i < 1000; i++ { + // Check if cancelled + select { + case <-ctx.Done(): + return ctx.Err() + default: + // Continue work + time.Sleep(100 * time.Millisecond) + } + } + return nil +} +``` + +**Note:** Context cancellation on frontend disconnect is automatic. + +### Batch Operations + +Reduce bridge overhead by batching: + +```go +// ❌ Inefficient: N bridge calls +for _, item := range items { + await ProcessItem(item) +} + +// ✅ Efficient: 1 bridge call +await ProcessItems(items) +``` + +```go +func ProcessItems(items []Item) ([]Result, error) { + results := make([]Result, len(items)) + for i, item := range items { + results[i] = processItem(item) + } + return results, nil +} +``` + +## Debugging the Bridge + +### Enable Debug Logging + +```go +import "log/slog" + +app := application.New(application.Options{ + Name: "My App", + Logger: application.DefaultLogger(slog.LevelDebug), + LogLevel: slog.LevelDebug, +}) +``` + +**Output shows:** +- Method calls +- Parameters +- Return values +- Errors +- Timing information + +### Inspect Generated Bindings + +Check `frontend/bindings/` to see generated TypeScript: + +```typescript +// frontend/bindings/MyService.ts +export function MyMethod(arg: string): Promise { + return window.wails.Call('MyService.MyMethod', arg) +} +``` + +### Test Services Directly + +Test Go services without the frontend: + +```go +func TestGreetService(t *testing.T) { + service := &GreetService{prefix: "Hello, "} + result := service.Greet("Test") + if result != "Hello, Test!" { + t.Errorf("Expected 'Hello, Test!', got '%s'", result) + } +} +``` + +## Performance Tips + +### ✅ Do + +- **Batch operations** - Reduce bridge calls +- **Use events for streaming** - Don't return large arrays +- **Keep methods fast** - <100ms ideal +- **Use goroutines** - For long operations +- **Cache on Go side** - Avoid repeated calculations + +### ❌ Don't + +- **Don't make excessive calls** - Batch when possible +- **Don't return huge data** - Use pagination or streaming +- **Don't block** - Use goroutines for long operations +- **Don't pass complex types** - Keep it simple +- **Don't ignore errors** - Always handle them + +## Security + +The bridge is secure by default: + +1. **Whitelist only** - Only registered services callable +2. **Type validation** - Arguments checked against Go types +3. **No eval()** - Frontend can't execute arbitrary Go code +4. **No reflection abuse** - Only exported methods accessible + +**Best practices:** +- **Validate input** in Go (don't trust frontend) +- **Use context** for authentication/authorisation +- **Rate limit** expensive operations +- **Sanitise** file paths and user input + +## Next Steps + +**Build System** - Learn how Wails builds and bundles your application +[Learn More →](/concepts/build-system) + +**Services** - Deep dive into the service system +[Learn More →](/features/bindings/services) + +**Events** - Use events for pub/sub communication +[Learn More →](/features/events/system) + +--- + +**Questions about the bridge?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding). diff --git a/docs/src/content/docs/concepts/build-system.mdx b/docs/src/content/docs/concepts/build-system.mdx new file mode 100644 index 000000000..78a5fae31 --- /dev/null +++ b/docs/src/content/docs/concepts/build-system.mdx @@ -0,0 +1,694 @@ +--- +title: Build System +description: Understanding how Wails builds and packages your application +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Unified Build System + +Wails provides a **unified build system** that compiles Go code, bundles frontend assets, embeds everything into a single executable, and handles platform-specific builds—all with one command. + +```bash +wails3 build +``` + +**Output:** Native executable with everything embedded. + +## Build Process Overview + +```d2 +direction: down + +Source: "Source Code" { + Go: "Go Code\n(main.go, services)" { + shape: rectangle + style.fill: "#00ADD8" + } + + Frontend: "Frontend Code\n(HTML/CSS/JS)" { + shape: rectangle + style.fill: "#8B5CF6" + } +} + +Analysis: "Analysis Phase" { + ScanGo: "Scan Go Services" { + shape: rectangle + } + + ExtractTypes: "Extract Types" { + shape: rectangle + } +} + +Generation: "Generation Phase" { + GenBindings: "Generate TypeScript Bindings" { + shape: rectangle + style.fill: "#10B981" + } + + BuildFrontend: "Build Frontend\n(Vite/webpack)" { + shape: rectangle + style.fill: "#8B5CF6" + } +} + +Compilation: "Compilation Phase" { + CompileGo: "Compile Go\n(with optimisations)" { + shape: rectangle + style.fill: "#00ADD8" + } + + EmbedAssets: "Embed Frontend Assets" { + shape: rectangle + style.fill: "#10B981" + } +} + +Output: "Output" { + Binary: "Native Binary\n(myapp.exe/.app)" { + shape: rectangle + style.fill: "#10B981" + } +} + +Source.Go -> Analysis.ScanGo +Analysis.ScanGo -> Analysis.ExtractTypes +Analysis.ExtractTypes -> Generation.GenBindings +Generation.GenBindings -> Source.Frontend: "TypeScript types" +Source.Frontend -> Generation.BuildFrontend +Generation.BuildFrontend -> Compilation.EmbedAssets: "Bundled assets" +Source.Go -> Compilation.CompileGo +Compilation.CompileGo -> Compilation.EmbedAssets +Compilation.EmbedAssets -> Output.Binary +``` + +## Build Phases + +### 1. Analysis Phase + +Wails scans your Go code to understand your services: + +```go +type GreetService struct { + prefix string +} + +func (g *GreetService) Greet(name string) string { + return g.prefix + name + "!" +} +``` + +**What Wails extracts:** +- Service name: `GreetService` +- Method name: `Greet` +- Parameter types: `string` +- Return types: `string` + +**Used for:** Generating TypeScript bindings + +### 2. Generation Phase + +#### TypeScript Bindings + +Wails generates type-safe bindings: + +```typescript +// Auto-generated: frontend/bindings/GreetService.ts +export function Greet(name: string): Promise { + return window.wails.Call('GreetService.Greet', name) +} +``` + +**Benefits:** +- Full type safety +- IDE autocomplete +- Compile-time errors +- JSDoc comments + +#### Frontend Build + +Your frontend bundler runs (Vite, webpack, etc.): + +```bash +# Vite example +vite build --outDir dist +``` + +**What happens:** +- JavaScript/TypeScript compiled +- CSS processed and minified +- Assets optimised +- Source maps generated (dev only) +- Output to `frontend/dist/` + +### 3. Compilation Phase + +#### Go Compilation + +Go code is compiled with optimisations: + +```bash +go build -ldflags="-s -w" -o myapp.exe +``` + +**Flags:** +- `-s`: Strip symbol table +- `-w`: Strip DWARF debugging info +- Result: Smaller binary (~30% reduction) + +**Platform-specific:** +- Windows: `.exe` with icon embedded +- macOS: `.app` bundle structure +- Linux: ELF binary + +#### Asset Embedding + +Frontend assets are embedded into the Go binary: + +```go +//go:embed frontend/dist +var assets embed.FS +``` + +**Result:** Single executable with everything inside. + +### 4. Output + +**Single native binary:** +- Windows: `myapp.exe` (~15MB) +- macOS: `myapp.app` (~15MB) +- Linux: `myapp` (~15MB) + +**No dependencies** (except system WebView). + +## Development vs Production + + + + **Optimised for speed:** + + ```bash + wails3 dev + ``` + + **What happens:** + 1. Starts frontend dev server (Vite on port 9245) + 2. Compiles Go without optimisations + 3. Launches app pointing to dev server + 4. Enables hot reload + 5. Includes source maps + + **Characteristics:** + - **Fast rebuilds** (<1s for frontend changes) + - **No asset embedding** (served from dev server) + - **Debug symbols** included + - **Source maps** enabled + - **Verbose logging** + + **File size:** Larger (~50MB with debug symbols) + + + + **Optimised for size and performance:** + + ```bash + wails3 build + ``` + + **What happens:** + 1. Builds frontend for production (minified) + 2. Compiles Go with optimisations + 3. Strips debug symbols + 4. Embeds assets + 5. Creates single binary + + **Characteristics:** + - **Optimised code** (minified, tree-shaken) + - **Assets embedded** (no external files) + - **Debug symbols stripped** + - **No source maps** + - **Minimal logging** + + **File size:** Smaller (~15MB) + + + +## Build Commands + +### Basic Build + +```bash +wails3 build +``` + +**Output:** Binary in `bin/` directory. + +`wails3 build` is a wrapper for `wails3 task build`. It runs the `build` task defined in your project's `Taskfile.yml`, which dispatches to the appropriate platform-specific Taskfile. + +### Build with Tags + +```bash +# Pass additional Go build tags +wails3 build -tags production +``` + +### Build with CLI Variables + +You can pass CLI variables to customize the build: + +```bash +wails3 build PRODUCTION=true +``` + +These variables are forwarded to the underlying task and can be accessed in your `Taskfile.yml` using Go template syntax. + +### Platform-Specific Tasks + +The default project structure includes platform-specific Taskfiles. The main `Taskfile.yml` dispatches to the correct one based on `{{OS}}`: + +```bash +# Run platform-specific tasks directly +wails3 task darwin:build +wails3 task windows:build +wails3 task linux:build +``` + +## Build Configuration + +### Taskfile.yml + +Wails uses [Taskfile](https://taskfile.dev/) for build configuration. The main `Taskfile.yml` in the project root includes platform-specific Taskfiles: + +```yaml +# Taskfile.yml +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "myproject" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} +``` + +**Run tasks:** + +```bash +wails3 task build +wails3 task package +wails3 task run +``` + +## Asset Embedding + +### How It Works + +Wails uses Go's `embed` package: + +```go +package main + +import ( + "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "My App", + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + app.Window.New() + app.Run() +} +``` + +**At build time:** +1. Frontend built to `frontend/dist/` +2. `//go:embed` directive includes files +3. Files compiled into binary +4. Binary contains everything + +**At runtime:** +1. App starts +2. Assets served from memory +3. No disk I/O for assets +4. Fast loading + +### Custom Assets + +Embed additional files: + +```go +//go:embed frontend/dist +var frontendAssets embed.FS + +//go:embed data/*.json +var dataAssets embed.FS + +//go:embed templates/*.html +var templateAssets embed.FS +``` + +## Build Optimisations + +### Frontend Optimisations + +**Vite (default):** + +```javascript +// vite.config.js +export default { + build: { + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, // Remove console.log + drop_debugger: true, + }, + }, + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], // Separate vendor bundle + }, + }, + }, + }, +} +``` + +**Results:** +- JavaScript minified (~70% reduction) +- CSS minified (~60% reduction) +- Images optimised +- Tree-shaking applied + +### Go Optimisations + +**Compiler flags:** + +```bash +-ldflags="-s -w" +``` + +- `-s`: Strip symbol table (~10% reduction) +- `-w`: Strip DWARF debug info (~20% reduction) + +**Additional optimisations:** + +```bash +-ldflags="-s -w -X main.version=1.0.0" +``` + +- `-X`: Set variable values at build time +- Useful for version numbers, build dates + +### Binary Compression + +**UPX (optional):** + +```bash +# After building +upx --best bin/myapp.exe +``` + +**Results:** +- ~50% size reduction +- Slightly slower startup (~100ms) +- Not recommended for macOS (code signing issues) + +## Platform-Specific Builds + +### Windows + +**Output:** `myapp.exe` + +**Includes:** +- Application icon +- Version information +- Manifest (UAC settings) + +**Icon:** + +Icon generation is handled by the platform-specific Taskfile. The default Windows Taskfile generates an `.ico` file from `build/appicon.png` using the `wails3 generate icons` command. + +**Manifest:** + +```xml + + + + + + + + + + + + +``` + +### macOS + +**Output:** `myapp.app` (application bundle) + +**Structure:** + +``` +myapp.app/ +├── Contents/ +│ ├── Info.plist # App metadata +│ ├── MacOS/ +│ │ └── myapp # Binary +│ ├── Resources/ +│ │ └── icon.icns # Icon +│ └── _CodeSignature/ # Code signature (if signed) +``` + +**Info.plist:** + +```xml + + + + + CFBundleName + My App + CFBundleIdentifier + com.example.myapp + CFBundleVersion + 1.0.0 + + +``` + +**Universal Binary:** + +The macOS Taskfile supports building universal binaries. You can also use the `wails3 tool lipo` command to combine architecture-specific binaries: + +```bash +wails3 tool lipo -i bin/myapp-amd64 -i bin/myapp-arm64 -output bin/myapp-universal +``` + +### Linux + +**Output:** `myapp` (ELF binary) + +**Dependencies:** +- GTK3 +- WebKitGTK + +**Desktop file:** + +```ini +# myapp.desktop +[Desktop Entry] +Name=My App +Exec=/usr/bin/myapp +Icon=myapp +Type=Application +Categories=Utility; +``` + +**Installation:** + +```bash +# Copy binary +sudo cp myapp /usr/bin/ + +# Copy desktop file +sudo cp myapp.desktop /usr/share/applications/ + +# Copy icon +sudo cp icon.png /usr/share/icons/hicolor/256x256/apps/myapp.png +``` + +## Build Performance + +### Typical Build Times + +| Phase | Time | Notes | +|-------|------|-------| +| Analysis | <1s | Go code scanning | +| Binding Generation | <1s | TypeScript generation | +| Frontend Build | 5-30s | Depends on project size | +| Go Compilation | 2-10s | Depends on code size | +| Asset Embedding | <1s | Embedding frontend | +| **Total** | **10-45s** | First build | +| **Incremental** | **5-15s** | Subsequent builds | + +### Speeding Up Builds + +**1. Use build cache:** + +```bash +# Go build cache is automatic +# Frontend cache (Vite) +npm run build # Uses cache by default +``` + +**2. Use faster tools:** + +```bash +# Use esbuild instead of webpack +# (Vite uses esbuild by default) +``` + +## Troubleshooting + +### Build Fails + +**Symptom:** `wails3 build` exits with error + +**Common causes:** + +1. **Go compilation error** + ```bash + # Check Go code compiles + go build + ``` + +2. **Frontend build error** + ```bash + # Check frontend builds + cd frontend + npm run build + ``` + +3. **Missing dependencies** + ```bash + # Install dependencies + npm install + go mod download + ``` + +### Binary Too Large + +**Symptom:** Binary is >50MB + +**Solutions:** + +1. **Strip debug symbols** (should be automatic) + The default Taskfiles include `-ldflags="-s -w"` in the build task. + +2. **Check embedded assets** + ```bash + # Remove unnecessary files from frontend/dist/ + # Check for large images, videos, etc. + ``` + +3. **Use UPX compression** + ```bash + upx --best bin/myapp.exe + ``` + +### Slow Builds + +**Symptom:** Builds take >1 minute + +**Solutions:** + +1. **Use build cache** + - Go cache is automatic + - Frontend cache (Vite) is automatic + +2. **Optimise frontend build** + ```javascript + // vite.config.js + export default { + build: { + minify: 'esbuild', // Faster than terser + }, + } + ``` + +## Best Practices + +### ✅ Do + +- **Use `wails3 dev` during development** - Fast iteration +- **Use `wails3 build` for releases** - Optimised output +- **Version your builds** - Use `-ldflags` in your Taskfile to embed version +- **Test builds on target platforms** - Cross-compilation isn't perfect +- **Keep frontend builds fast** - Optimise bundler config +- **Use build cache** - Speeds up subsequent builds + +### ❌ Don't + +- **Don't commit `build/` directory** - Add to `.gitignore` +- **Don't skip testing builds** - Always test before release +- **Don't embed unnecessary assets** - Keep binaries small +- **Don't use debug builds for production** - Use optimised builds +- **Don't forget code signing** - Required for distribution + +## Next Steps + +**Building Applications** - Detailed guide to building and packaging +[Learn More →](/guides/building) + +**Cross-Platform Builds** - Build for all platforms from one machine +[Learn More →](/guides/cross-platform) + +**Creating Installers** - Create installers for end users +[Learn More →](/guides/installers) + +--- + +**Questions about building?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [build examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/build). diff --git a/docs/src/content/docs/concepts/lifecycle.mdx b/docs/src/content/docs/concepts/lifecycle.mdx new file mode 100644 index 000000000..9d722c3fc --- /dev/null +++ b/docs/src/content/docs/concepts/lifecycle.mdx @@ -0,0 +1,750 @@ +--- +title: Application Lifecycle +description: Understanding the Wails application lifecycle from startup to shutdown +sidebar: + order: 2 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Understanding Application Lifecycle + +Desktop applications have a lifecycle from startup to shutdown. Wails provides hooks at each stage to **initialise resources**, **clean up properly**, **handle errors gracefully**, and **manage multiple windows** effectively. + +## The Lifecycle Stages + +```d2 +direction: down + +Start: "Application Start" { + shape: oval + style.fill: "#10B981" +} + +PreInit: "Pre-Initialisation" { + Parse: "Parse Options" { + shape: rectangle + } + Register: "Register Services" { + shape: rectangle + } + Validate: "Validate Config" { + shape: rectangle + } +} + +ServiceInit: "Service Initialisation" { + shape: rectangle + style.fill: "#3B82F6" +} + +CreateWindows: "Create Windows" { + shape: rectangle +} + +AppStarted: "ApplicationStarted Event" { + shape: rectangle + style.fill: "#3B82F6" +} + +EventLoop: "Event Loop" { + Process: "Process Events" { + shape: rectangle + } + Handle: "Handle Messages" { + shape: rectangle + } + Update: "Update UI" { + shape: rectangle + } +} + +QuitSignal: "Quit Signal" { + shape: diamond + style.fill: "#F59E0B" +} + +ShouldQuit: "ShouldQuit Check" { + shape: rectangle + style.fill: "#3B82F6" +} + +OnShutdown: "Shutdown Hooks" { + shape: rectangle + style.fill: "#3B82F6" +} + +Cleanup: "Cleanup" { + Close: "Close Windows" { + shape: rectangle + } + Release: "Release Resources" { + shape: rectangle + } +} + +End: "Application End" { + shape: oval + style.fill: "#EF4444" +} + +Start -> PreInit.Parse +PreInit.Parse -> PreInit.Register +PreInit.Register -> PreInit.Validate +PreInit.Validate -> ServiceInit +ServiceInit -> CreateWindows +CreateWindows -> AppStarted +AppStarted -> EventLoop.Process +EventLoop.Process -> EventLoop.Handle +EventLoop.Handle -> EventLoop.Update +EventLoop.Update -> EventLoop.Process: "Loop" +EventLoop.Process -> QuitSignal: "User quits" +QuitSignal -> ShouldQuit: "Can cancel?" +ShouldQuit -> EventLoop.Process: "Cancelled" +ShouldQuit -> OnShutdown: "Confirmed" +OnShutdown -> Cleanup.Close +Cleanup.Close -> Cleanup.Release +Cleanup.Release -> End +``` + +### 1. Pre-Initialisation + +Before your code runs, Wails: +1. Parses `application.Options` +2. Registers services +3. Validates configuration +4. Sets up the runtime + +**You don't control this phase** - it happens automatically. + +### 2. Service Initialisation + +Services that implement `ServiceStartup` initialise their own resources during `app.Run()`. This is for **service-specific setup** (database connections, config loading), not for reacting to application lifecycle events: + +```go +type AppService struct { + db *sql.DB + config *Config +} + +func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Initialise database + var err error + s.db, err = sql.Open("sqlite3", "app.db") + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + + // Load configuration + s.config, err = loadConfig() + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + + return nil +} + +app := application.New(application.Options{ + Name: "My App", + Services: []application.Service{ + application.NewService(&AppService{}), + }, +}) +``` + +**When it runs:** During `app.Run()`, before the event loop starts + +**Use it for:** +- Database connections +- Configuration loading +- Resource initialisation + +**Context:** The `context.Context` is valid as long as the application is running and is cancelled right before shutdown. + +### 3. Application Lifecycle Events + +To react to application lifecycle stages, use **events** — not services: + +```go +// React to application start +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) { + // Application is fully running - safe to show notifications, check for updates, etc. + log.Println("Application started") +}) + +// React to theme changes +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + log.Println("Theme changed") +}) +``` + +**Available common application events:** +- `events.Common.ApplicationStarted` — Application has fully started +- `events.Common.ThemeChanged` — System theme changed (light/dark) + +**Platform-specific events** are also available (e.g. `events.Mac.ApplicationDidFinishLaunching`, `events.Windows.ApplicationStarted`). + +**Key distinction:** +- **`ServiceStartup`** = initialise your service's resources (DB, config) +- **Application events** = react to lifecycle stages (app started, theme changed) + +### 4. Window Creation + +After services start up, you create windows: + +```go +window := app.Window.New() +``` + +**What happens:** +1. Window is created (but not shown) +2. WebView is initialised +3. Frontend assets are loaded +4. Window is shown (unless `Hidden: true`) + +### 5. Event Loop + +The application enters the event loop: + +```go +err := app.Run() // Blocks here until quit +``` + +**What happens in the loop:** +- OS events processed (mouse, keyboard, window events) +- Go-to-JS messages handled +- JS-to-Go calls executed +- UI updates rendered + +**This is where your application spends most of its time.** + +### 6. Quit Signal + +User triggers quit via: +- Closing last window (default behaviour) +- Cmd+Q / Alt+F4 / File → Quit +- Your code calling `app.Quit()` + +### 7. ShouldQuit / Window Close Prevention + +**Application-level quit prevention with `ShouldQuit`:** + +```go +app := application.New(application.Options{ + ShouldQuit: func() bool { + // Return false to cancel quit + // Return true to allow quit + if hasUnsavedChanges() { + return false + } + return true + }, +}) +``` + +**Window-level close prevention with `RegisterHook`:** + +```go +window := app.Window.New() +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the window from closing + } +}) +``` + +**Use cases:** +- Confirm quit with unsaved changes +- Prevent accidental closure +- Save state before quitting + +### 8. Shutdown Hooks + +There are multiple hooks for shutdown: + +**`OnShutdown` (application option)** - runs first during shutdown: + +```go +app := application.New(application.Options{ + OnShutdown: func() { + // Save application state + saveState() + }, +}) +``` + +**`app.OnShutdown()` (runtime registration)** - add shutdown tasks dynamically: + +```go +app.OnShutdown(func() { + // Additional cleanup + cleanup() +}) +``` + +**`ServiceShutdown` (service interface)** - services shut down in reverse registration order: + +```go +func (s *MyService) ServiceShutdown() error { + // Close database, release resources + return s.db.Close() +} +``` + +**`PostShutdown` (application option)** - runs after everything else, just before process exit: + +```go +app := application.New(application.Options{ + PostShutdown: func() { + log.Println("Application has finished shutting down") + }, +}) +``` + +**Shutdown order:** +1. `OnShutdown` callbacks (in order added) +2. `ServiceShutdown` (reverse registration order) +3. Windows closed, system trays destroyed +4. `PostShutdown` callback + +**Important:** Keep shutdown fast. OS may force-kill if too slow. + +### 9. Cleanup & Exit + +Wails automatically: +1. Closes all windows +2. Releases WebView resources +3. Exits the process + +## Lifecycle Reference + +**Application events** (react to lifecycle stages): + +| Event | When | Use For | +|-------|------|---------| +| `events.Common.ApplicationStarted` | App fully running | Post-start tasks (notifications, update checks) | +| `events.Common.ThemeChanged` | System theme changed | Theme adaptation | + +**Service interfaces** (resource management): + +| Interface | When | Can Cancel? | Use For | +|-----------|------|-------------|---------| +| `ServiceStartup` | During `app.Run()`, before event loop | Yes (return error) | Resource initialisation | +| `ServiceShutdown` | During shutdown, reverse order | No | Resource cleanup | + +**Application options** (shutdown/quit control): + +| Option | When | Can Cancel? | Use For | +|--------|------|-------------|---------| +| `ShouldQuit` | App quit requested | Yes (return false) | Confirm quit | +| `OnShutdown` | After quit confirmed | No | Cleanup | +| `PostShutdown` | After shutdown complete | No | Logging, testing | + +**Window hooks** (per-window lifecycle): + +| Hook | When | Can Cancel? | Use For | +|------|------|-------------|---------| +| `events.Common.WindowClosing` | Window closing | Yes (`e.Cancel()`) | Prevent window close | + +## Common Patterns + +### Pattern 1: Database Lifecycle + +```go +type DatabaseService struct { + db *sql.DB +} + +func (d *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + d.db, err = sql.Open("sqlite3", "app.db") + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + + // Run migrations + if err := runMigrations(d.db); err != nil { + return fmt.Errorf("migrations failed: %w", err) + } + + return nil +} + +func (d *DatabaseService) ServiceShutdown() error { + if d.db != nil { + return d.db.Close() + } + return nil +} + +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&DatabaseService{}), + }, +}) +``` + +### Pattern 2: Configuration Management + +```go +type Config struct { + Theme string + Language string + WindowPos Point +} + +type ConfigService struct { + config *Config +} + +func (c *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + var err error + c.config, err = loadConfig() // Load from disk + if err != nil { + return fmt.Errorf("failed to load config: %w", err) + } + return nil +} + +func (c *ConfigService) ServiceShutdown() error { + return saveConfig(c.config) // Save to disk +} + +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&ConfigService{}), + }, +}) +``` + +### Pattern 3: Confirm Quit with Unsaved Changes + +Use `RegisterHook` with `events.Common.WindowClosing` to prevent window close, or `ShouldQuit` on the application options: + +```go +// Application-level: prevent quit +app := application.New(application.Options{ + ShouldQuit: func() bool { + if hasUnsavedChanges { + // Return false to prevent quit + return false + } + return true + }, +}) + +// Window-level: prevent individual window close +window := app.Window.New() +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges { + e.Cancel() // Prevent window from closing + } +}) +``` + +### Pattern 4: Background Tasks + +```go +type SyncService struct{} + +func (s *SyncService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Start background task + go func() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + performBackgroundSync() + case <-ctx.Done(): + // Context cancelled, quit + return + } + } + }() + return nil +} + +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&SyncService{}), + }, +}) +``` + +**Important:** Use `ctx.Done()` to know when to stop background tasks. The context is cancelled right before shutdown. + +## Window Lifecycle + +Each window has its own lifecycle: + +```d2 +direction: down + +Create: "Create Window" { + shape: oval + style.fill: "#10B981" +} + +Load: "Load Frontend" { + shape: rectangle +} + +Show: "Show Window" { + shape: rectangle +} + +Active: "Window Active" { + Events: "Handle Events" { + shape: rectangle + } +} + +CloseRequest: "Close Request" { + shape: diamond + style.fill: "#F59E0B" +} + +CloseHook: "WindowClosing Hook" { + shape: rectangle + style.fill: "#3B82F6" +} + +Destroy: "Destroy Window" { + shape: rectangle +} + +End: "Window Closed" { + shape: oval + style.fill: "#EF4444" +} + +Create -> Load +Load -> Show +Show -> Active.Events +Active.Events -> Active.Events: "Loop" +Active.Events -> CloseRequest: "User closes" +CloseRequest -> CloseHook +CloseHook -> Active.Events: "Cancelled" +CloseHook -> Destroy: "Confirmed" +Destroy -> End +``` + +**Key points:** +- Each window is independent +- Closing last window quits app (by default) +- Windows can prevent their own closure + +## Multi-Window Lifecycle + +With multiple windows: + +```go +// Create main window +mainWindow := app.Window.New() + +// Create secondary window +secondaryWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Settings", + Width: 400, + Height: 600, +}) + +// Closing secondary window doesn't quit app +// Closing main window quits app (closes all windows) +``` + +**Default behaviour:** +- Closing any window closes just that window +- Closing the **last** window quits the application + +**Custom behaviour:** + +```go +// Prevent app quit when last window closes +app := application.New(application.Options{ + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, +}) + +// Now app stays running even with no windows +// Useful for menu bar / system tray apps +``` + +## Error Handling During Lifecycle + +### Startup Errors + +Return an error from `ServiceStartup` to abort application startup. The error is propagated from `app.Run()`: + +```go +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if err := initialise(); err != nil { + return fmt.Errorf("initialisation failed: %w", err) + } + return nil +} + +// In main: +err := app.Run() +if err != nil { + log.Fatal("Failed to start:", err) +} +``` + +### Shutdown Errors + +Errors returned from `ServiceShutdown` are logged but do not prevent shutdown: + +```go +func (s *MyService) ServiceShutdown() error { + if err := saveState(); err != nil { + // Error will be logged by Wails + return fmt.Errorf("failed to save state: %w", err) + } + return nil +} +``` + +**Important:** Shutdown hooks run during quit - don't show dialogs or try to cancel. + +## Platform Differences + +### macOS + +- **Application menu** persists even with no windows +- **Cmd+Q** always quits (can't be prevented) +- **Dock icon** remains unless hidden + +### Windows + +- **No application menu** without a window +- **Alt+F4** closes window (can be prevented) +- **System tray** can keep app running + +### Linux + +- **Behaviour varies** by desktop environment +- **Generally similar to Windows** + +## Debugging Lifecycle Issues + +### Problem: Resources Not Cleaned Up + +**Symptom:** Database connections left open, files not closed + +**Solution:** Implement `ServiceShutdown` on your services: + +```go +func (s *MyService) ServiceShutdown() error { + log.Println("Cleaning up...") + // Your cleanup code + return s.db.Close() +} +``` + +### Problem: Application Won't Quit + +**Symptom:** App hangs when trying to quit + +**Causes:** +1. `ShouldQuit` returning `false` +2. Window close hook cancelling close events +3. Shutdown tasks taking too long +4. Background goroutines not stopping + +**Solution:** + +```go +// 1. Check ShouldQuit logic +app := application.New(application.Options{ + ShouldQuit: func() bool { + log.Println("ShouldQuit called") + return true // Allow quit + }, +}) + +// 2. Keep shutdown fast +app.OnShutdown(func() { + log.Println("Shutdown started") + // Fast cleanup only + log.Println("Shutdown finished") +}) + +// 3. Stop background tasks using context +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + go func() { + for { + select { + case <-ctx.Done(): + log.Println("Background task stopped") + return + default: + // Work + } + } + }() + return nil +} +``` + +### Problem: Initialisation Fails Silently + +**Symptom:** App starts but doesn't work correctly + +**Solution:** Return errors from `ServiceStartup` to abort startup: + +```go +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + if err := initialise(); err != nil { + return fmt.Errorf("initialisation failed: %w", err) + } + return nil +} + +// In main - app.Run() returns the error: +if err := app.Run(); err != nil { + log.Fatal("Failed to start:", err) +} +``` + +## Best Practices + +### ✅ Do + +- **Use `ServiceStartup` for resource init** - Database, config, connections +- **Use application events for lifecycle** - `events.Common.ApplicationStarted` for post-start tasks +- **Clean up in `ServiceShutdown`** - Close connections, save state +- **Keep shutdown fast** - <1 second +- **Use context for cancellation** - Stop background tasks +- **Handle errors gracefully** - Return errors from ServiceStartup +- **Test quit scenarios** - Unsaved changes, background tasks + +### ❌ Don't + +- **Don't use `ServiceStartup` for lifecycle hooks** - Use application events instead +- **Don't block ServiceStartup** - Keep it fast (<2 seconds) +- **Don't show dialogs during shutdown** - App is quitting +- **Don't ignore errors** - Log or return them +- **Don't leak resources** - Always clean up in ServiceShutdown +- **Don't forget background tasks** - Stop them using ctx.Done() + +## Next Steps + +**Go-Frontend Bridge** - Understand how Go and JavaScript communicate +[Learn More →](/concepts/bridge) + +**Build System** - Learn how Wails builds your application +[Learn More →](/concepts/build-system) + +**Events System** - Use events for communication between components +[Learn More →](/features/events/system) + +**Window Management** - Create and manage multiple windows +[Learn More →](/features/windows/basics) + +--- + +**Questions about lifecycle?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/concepts/manager-api.mdx b/docs/src/content/docs/concepts/manager-api.mdx new file mode 100644 index 000000000..e67111841 --- /dev/null +++ b/docs/src/content/docs/concepts/manager-api.mdx @@ -0,0 +1,266 @@ +--- +title: Manager API +description: Organized API structure with focused manager interfaces +sidebar: + order: 2 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +The Wails v3 Manager API provides an organized and discoverable way to access application functionality through focused manager structs. This new API structure groups related methods together while maintaining full backward compatibility with the traditional App API. + +## Overview + +The Manager API organizes application functionality into eleven focused areas: + +- **`app.Window`** - Window creation, management, and callbacks +- **`app.ContextMenu`** - Context menu registration and management +- **`app.KeyBinding`** - Global key binding management +- **`app.Browser`** - Browser integration (opening URLs and files) +- **`app.Env`** - Environment information and system state +- **`app.Dialog`** - File and message dialog operations +- **`app.Event`** - Custom event handling and application events +- **`app.Menu`** - Application menu management +- **`app.Screen`** - Screen management and coordinate transformations +- **`app.Clipboard`** - Clipboard text operations +- **`app.SystemTray`** - System tray icon creation and management + +## Benefits + +- **Better discoverability** - IDE autocomplete shows organized API surface +- **Improved code organization** - Related methods grouped together +- **Enhanced maintainability** - Separation of concerns across managers +- **Future extensibility** - Easier to add new features to specific areas + +## Usage + +The Manager API provides organized access to all application functionality: + +```go +// Events and custom event handling +app.Event.Emit("custom", data) +app.Event.On("custom", func(e *CustomEvent) { ... }) + +// Window management +window, _ := app.Window.GetByName("main") +app.Window.OnCreate(func(window Window) { ... }) + +// Browser integration +app.Browser.OpenURL("https://wails.io") + +// Menu management +menu := app.Menu.New() +app.Menu.Set(menu) + +// System tray +systray := app.SystemTray.New() +``` + +## Manager Reference + +### Window Manager + +Manages window creation, retrieval, and lifecycle callbacks. + +```go +// Create windows +window := app.Window.New() +window := app.Window.NewWithOptions(options) +current := app.Window.Current() + +// Find windows +window, exists := app.Window.GetByName("main") +windows := app.Window.GetAll() + +// Window callbacks +app.Window.OnCreate(func(window Window) { + // Handle window creation +}) +``` + +### Event Manager + +Handles custom events and application event listening. + +```go +// Custom events +app.Event.Emit("userAction", data) +cancelFunc := app.Event.On("userAction", func(e *CustomEvent) { + // Handle event +}) +app.Event.Off("userAction") +app.Event.Reset() // Remove all listeners + +// Application events +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *ApplicationEvent) { + // Handle system theme change +}) +``` + +### Browser Manager + +Provides browser integration for opening URLs and files. + +```go +// Open URLs and files in default browser +err := app.Browser.OpenURL("https://wails.io") +err := app.Browser.OpenFile("/path/to/document.pdf") +``` + +### Environment Manager + +Access to system environment information. + +```go +// Get environment info +env := app.Env.Info() +fmt.Printf("OS: %s, Arch: %s\n", env.OS, env.Arch) + +// Check system theme +if app.Env.IsDarkMode() { + // Dark mode is active +} + +// Open file manager +err := app.Env.OpenFileManager("/path/to/folder", false) +``` + +### Dialog Manager + +Organized access to file and message dialogs. + +```go +// File dialogs +result, err := app.Dialog.OpenFile(). + AddFilter("Text Files", "*.txt"). + PromptForSingleSelection() + +result, err = app.Dialog.SaveFile(). + SetFilename("document.txt"). + PromptForSingleSelection() + +// Message dialogs +app.Dialog.Info(). + SetTitle("Information"). + SetMessage("Operation completed successfully"). + Show() + +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("An error occurred"). + Show() +``` + +### Menu Manager + +Application menu creation and management. + +```go +// Create and set application menu +menu := app.Menu.New() +fileMenu := menu.AddSubmenu("File") +fileMenu.Add("New").OnClick(func(ctx *Context) { + // Handle menu click +}) + +app.Menu.Set(menu) + +// Show about dialog +app.Menu.ShowAbout() +``` + +### Key Binding Manager + +Dynamic management of global key bindings. + +```go +// Add key bindings +app.KeyBinding.Add("ctrl+n", func(window application.Window) { + // Handle Ctrl+N +}) + +app.KeyBinding.Add("ctrl+q", func(window application.Window) { + app.Quit() +}) + +// Remove key bindings +app.KeyBinding.Remove("ctrl+n") + +// Get all bindings +bindings := app.KeyBinding.GetAll() +``` + +### Context Menu Manager + +Advanced context menu management (for library authors). + +```go +// Create and register context menu +menu := app.ContextMenu.New() +app.ContextMenu.Add("myMenu", menu) + +// Retrieve context menu +menu, exists := app.ContextMenu.Get("myMenu") + +// Remove context menu +app.ContextMenu.Remove("myMenu") +``` + +### Screen Manager + +Screen management and coordinate transformations for multi-monitor setups. + +```go +// Get screen information +screens := app.Screen.GetAll() +primary := app.Screen.GetPrimary() + +// Coordinate transformations +physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint) +logicalPoint := app.Screen.PhysicalToDipPoint(physicalPoint) + +// Screen detection +screen := app.Screen.ScreenNearestDipPoint(point) +screen = app.Screen.ScreenNearestDipRect(rect) +``` + +### Clipboard Manager + +Clipboard operations for reading and writing text. + +```go +// Set text to clipboard +success := app.Clipboard.SetText("Hello World") +if !success { + // Handle error +} + +// Get text from clipboard +text, ok := app.Clipboard.Text() +if !ok { + // Handle error +} else { + // Use the text +} +``` + +### SystemTray Manager + +System tray icon creation and management. + +```go +// Create system tray +systray := app.SystemTray.New() +systray.SetLabel("My App") +systray.SetIcon(iconBytes) + +// Add menu to system tray +menu := app.Menu.New() +menu.Add("Open").OnClick(func(ctx *Context) { + // Handle click +}) +systray.SetMenu(menu) + +// Destroy system tray when done +systray.Destroy() +``` \ No newline at end of file diff --git a/docs/src/content/docs/contributing.mdx b/docs/src/content/docs/contributing.mdx new file mode 100644 index 000000000..841c30282 --- /dev/null +++ b/docs/src/content/docs/contributing.mdx @@ -0,0 +1,275 @@ +--- +title: Contributing +description: Contribute to Wails +sidebar: + order: 100 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Welcome Contributors! + +We welcome contributions to Wails! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated. + +## Ways to Contribute + +### 1. Report Issues + +Found a bug? [Open an issue](https://github.com/wailsapp/wails/issues/new) with: +- Clear description +- Steps to reproduce +- Expected vs actual behaviour +- System information +- Code samples + +### 2. Improve Documentation + +Documentation improvements are always welcome: +- Fix typos and errors +- Add examples +- Clarify explanations +- Translate content + +### 3. Submit Code + +Contribute code through pull requests: +- Bug fixes +- New features +- Performance improvements +- Tests + +## Getting Started + +### Fork and Clone + +```bash +# Fork the repository on GitHub +# Then clone your fork +git clone https://github.com/YOUR_USERNAME/wails.git +cd wails + +# Add upstream remote +git remote add upstream https://github.com/wailsapp/wails.git +``` + +### Build from Source + +```bash +# Install dependencies +go mod download + +# Build Wails CLI +cd v3/cmd/wails3 +go build + +# Test your build +./wails3 version +``` + +### Run Tests + +```bash +# Run all tests +go test ./... + +# Run specific package tests +go test ./v3/pkg/application + +# Run with coverage +go test -cover ./... +``` + +## Making Changes + +### Create a Branch + +```bash +# Update main +git checkout main +git pull upstream main + +# Create feature branch +git checkout -b feature/my-feature +``` + +### Make Your Changes + +1. **Write code** following Go conventions +2. **Add tests** for new functionality +3. **Update documentation** if needed +4. **Run tests** to ensure nothing breaks +5. **Commit changes** with clear messages + +### Commit Guidelines + +```bash +# Good commit messages +git commit -m "fix: resolve window focus issue on macOS" +git commit -m "feat: add support for custom window chrome" +git commit -m "docs: improve bindings documentation" + +# Use conventional commits: +# - feat: New feature +# - fix: Bug fix +# - docs: Documentation +# - test: Tests +# - refactor: Code refactoring +# - chore: Maintenance +``` + +### Submit Pull Request + +```bash +# Push to your fork +git push origin feature/my-feature + +# Open pull request on GitHub +# Provide clear description +# Reference related issues +``` + +## Pull Request Guidelines + +### Good PR Description + +```markdown +## Description +Brief description of changes + +## Changes +- Added feature X +- Fixed bug Y +- Updated documentation + +## Testing +- Tested on macOS 14 +- Tested on Windows 11 +- All tests passing + +## Related Issues +Fixes #123 +``` + +### PR Checklist + +- [ ] Code follows Go conventions +- [ ] Tests added/updated +- [ ] Documentation updated +- [ ] All tests passing +- [ ] No breaking changes (or documented) +- [ ] Commit messages clear + +## Code Guidelines + +### Go Code Style + +```go +// ✅ Good: Clear, documented, tested +// ProcessData processes the input data and returns the result. +// It returns an error if the data is invalid. +func ProcessData(data string) (string, error) { + if data == "" { + return "", errors.New("data cannot be empty") + } + + result := process(data) + return result, nil +} + +// ❌ Bad: No docs, no error handling +func ProcessData(data string) string { + return process(data) +} +``` + +### Testing + +```go +func TestProcessData(t *testing.T) { + tests := []struct { + name string + input string + want string + wantErr bool + }{ + {"valid input", "test", "processed", false}, + {"empty input", "", "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ProcessData(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ProcessData() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ProcessData() = %v, want %v", got, tt.want) + } + }) + } +} +``` + +## Documentation + +### Writing Docs + +Documentation uses Starlight (Astro): + +```bash +cd docs +npm install +npm run dev +``` + +### Documentation Style + +- Use International English spelling +- Start with the problem +- Provide working examples +- Include troubleshooting +- Cross-reference related content + +## Community + +### Get Help + +- **Discord:** [Join our community](https://discord.gg/JDdSxwjhGf) +- **GitHub Discussions:** Ask questions +- **GitHub Issues:** Report bugs + +### Code of Conduct + +Be respectful, inclusive, and professional. We're all here to build great software together. + +## Recognition + +Contributors are recognised in: +- Release notes +- Contributors list +- GitHub insights + +Thank you for contributing to Wails! 🎉 + +## Next Steps + + + + Visit the Wails repository. + + [View on GitHub →](https://github.com/wailsapp/wails) + + + + Join the community. + + [Join Discord →](https://discord.gg/JDdSxwjhGf) + + + + Read the docs. + + [Browse Docs →](/quick-start/why-wails) + + diff --git a/docs/src/content/docs/contributing/_category_.json b/docs/src/content/docs/contributing/_category_.json new file mode 100644 index 000000000..5f76b677d --- /dev/null +++ b/docs/src/content/docs/contributing/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Technical Documentation", + "link": { + "type": "generated-index", + "description": "Deep dive into Wails v3 internals for developers who want to understand or contribute to the codebase." + }, + "order": 50, + "collapsed": false +} diff --git a/docs/src/content/docs/contributing/architecture.mdx b/docs/src/content/docs/contributing/architecture.mdx new file mode 100644 index 000000000..5f0083714 --- /dev/null +++ b/docs/src/content/docs/contributing/architecture.mdx @@ -0,0 +1,170 @@ +--- +title: Wails v3 Architecture +description: Deep-dive diagrams and explanations of every moving part inside Wails v3 +sidebar: + order: 1 +--- + + +Wails v3 is a **full-stack desktop framework** consisting of a Go runtime, +a JavaScript bridge, a task-driven tool-chain and a collection of templates that +let you ship native applications powered by modern web tech. + +This page presents the *big picture* in four diagrams: + +1. **Overall Architecture** – how every subsystem connects +2. **Runtime Flow** – what happens when JS calls Go and vice-versa +3. **Development vs Production** – two modes of the asset server +4. **Platform Implementations** – where OS-specific code lives + +--- + +## 1 · Overall Architecture + +**Wails v3 – High-Level Stack** + +```d2 +direction: right +Developer: "wails3 CLI" +Build: { + label: "Build-time Tool-chain" + GEN: "Binding Generator +(static analysis)" + TMP: "Template Engine" + ASSETDEV: "Asset Server (dev)" + PKG: "Cross-compilation & Packaging" +} +Runtime: { + label: "Native Runtime" + RT: "Desktop Runtime +(window, dialogs, tray, …)" + BRIDGE: "Message Bridge +(JSON channel)" +} +App: { + label: "Your Application" + BACKEND: "Go Backend" + FRONTEND: "Web Frontend +(React/Vue/…)" +} +Developer -> TMP: "init" +Developer -> GEN: "generate" +Developer -> ASSETDEV: "dev" +Developer -> PKG: "build / package" +GEN -> BACKEND: "Go & TS stubs" +GEN -> FRONTEND: "bindings.json" +ASSETDEV <-> FRONTEND: "HTTP" +BACKEND <-> BRIDGE: "calls / events" +FRONTEND <-> BRIDGE: "invoke / emit" +BRIDGE <-> RT: "native API" +RT -> ASSETDEV: "serve assets" +``` + +--- + +## 2 · Runtime Call Flow + +**Runtime – JavaScript ⇄ Go Calling Path** + +```d2 +direction: right +JS: "JavaScript +(frontend)" +Bridge: "Bridge +(WebView callback)" +MP: "Message Processor +(Go)" +GoNode: "Bound Go Function" +JS -> Bridge: "invoke(\"Greet\",\"Alice\")" +Bridge -> MP: "JSON call {t:c,id:42,…}" +MP -> GoNode: "call Greet(\"Alice\")" +GoNode -> MP: "\"Hello Alice\"" +MP -> Bridge: "JSON {t:r,id:42,result:\"Hello Alice\"}" +Bridge -> JS: "Promise.resolve(\"Hello Alice\")" +``` + +Key points: + +* **No HTTP / IPC** – the bridge uses the native WebView’s in-memory channel +* **Method IDs** – deterministic FNV-hash enables O(1) lookup in Go +* **Promises** – errors propagate as rejections with stack & code + +--- + +## 3 · Development vs Production Asset Flow + +**Dev ↔ Prod Asset Server** + +```d2 +direction: right +Dev: { + label: "`wails3 dev`" + VITE: "Framework Dev Server +(port 5173)" + ASDEV: "Asset Server (dev) +(proxy + disk)" + FRONTENDDEV: "Browser" +} +Prod: { + label: "`wails3 build`" + EMBED: "Embedded FS +(go:embed)" + ASPROD: "Asset Server (prod) +(read-only)" + FRONTENDPROD: "WebView Window" +} +VITE <-> ASDEV: "proxy / HMR" +ASDEV <-> FRONTENDDEV: "HTTP" +EMBED -> ASPROD: "serve assets" +ASPROD <-> FRONTENDPROD: "in-memory" +``` + +* In **dev** the server proxies unknown paths to the framework’s live-reload + server and serves static assets from disk. +* In **prod** the same API is backed by `go:embed`, producing a zero-dependency + binary. + +--- + +## 4 · Platform-Specific Runtime Split + +**Per-OS Runtime Files** + +```d2 +direction: right +Window: "runtime::Window +Shared interface (pkg/application)" +WindowDarwin: "Window_darwin +//go:build darwin +Objective-C (cgo) +NSWindow* ptr" +WindowLinux: "Window_linux +//go:build linux +Pure Go GTK calls +GtkWindow* ptr" +WindowWindows: "Window_windows +//go:build windows +Win32 API via syscall +HWND ptr" +Window -> WindowDarwin +Window -> WindowLinux +Window -> WindowWindows +``` + +Every feature follows this pattern: + +1. **Common interface** in `pkg/application` +2. **Message processor** entry in `pkg/application/messageprocessor_*.go` +3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags + +Missing functionality on an OS should return `ErrCapability` and register +availability via `internal/capabilities`. + +--- + +## Summary + +These diagrams outline **where the code lives**, **how data moves**, and +**which layers own which responsibilities**. +Keep them handy while exploring the detailed pages that follow – they are your +map to the Wails v3 source tree. diff --git a/docs/src/content/docs/contributing/architecture/bindings.mdx b/docs/src/content/docs/contributing/architecture/bindings.mdx new file mode 100644 index 000000000..db8272f87 --- /dev/null +++ b/docs/src/content/docs/contributing/architecture/bindings.mdx @@ -0,0 +1,156 @@ +--- +title: Binding System +description: How the binding system collects, processes, and generates JavaScript/TypeScript code +sidebar: + order: 1 +--- + +import { FileTree } from "@astrojs/starlight/components"; + +This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation. + +## Architecture Overview + +The Wails binding system consists of three main components: + +1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations +2. **Configuration**: Manages settings and options for the binding generation process +3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information + + +- internal/generator/ + - collect/ # Package analysis and information extraction + - config/ # Configuration structures and interfaces + - render/ # Code generation for JS/TS + + +## Collection Process + +The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package. + +### Key Components + +- **Collector**: Manages package information and caches collected data +- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives +- **Service**: Collects information about service types and their methods +- **Model**: Collects detailed information about model types, including fields, values, and type parameters +- **Directive**: Parses and interprets `//wails:` directives in Go source code + +### Collection Flow + +1. The collector scans the Go packages specified in the project +2. It identifies service types (structs with methods that will be exposed to the frontend) +3. For each service, it collects information about its methods +4. It identifies model types (structs used as parameters or return values in service methods) +5. For each model, it collects information about its fields and type parameters +6. It processes any `//wails:` directives found in the code + +## Rendering Process + +The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package. + +### Key Components + +- **Renderer**: Orchestrates the rendering of service, model, and index files +- **Module**: Represents a single generated JavaScript/TypeScript module +- **Templates**: Text templates used for code generation + +### Rendering Flow + +1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods +2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct +3. The renderer generates index files that re-export all services and models +4. The renderer applies any custom code injections specified by `//wails:inject` directives + +## Type Mapping + +One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping: + +| Go Type | JavaScript Type | TypeScript Type | +|---------|----------------|----------------| +| `bool` | `boolean` | `boolean` | +| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` | +| `string` | `string` | `string` | +| `[]byte` | `Uint8Array` | `Uint8Array` | +| `[]T` | `Array` | `T[]` | +| `map[K]V` | `Object` | `Record` | +| `struct` | `Object` | Custom class | +| `interface{}` | `any` | `any` | +| `*T` | `T \| null` | `T \| null` | +| `func` | Not supported | Not supported | +| `chan` | Not supported | Not supported | + +## Directives System + +The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code. + +### Available Directives + +- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings +- `//wails:include`: Includes additional files with the generated bindings +- `//wails:internal`: Marks a type or method as module-private — it is still generated in JS/TS bindings but not re-exported from the package index file. Useful with `//wails:inject` for wrapping methods with custom JS +- `//wails:ignore`: Completely excludes a method from binding generation — no JS/TS code is generated for it at all +- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID + +### Directive Processing + +1. During the collection phase, the collector identifies and parses directives in the Go code +2. The directives are stored with the corresponding declarations (services, methods, models, etc.) +3. During the rendering phase, the renderer applies the directives to customize the generated code + +## Advanced Features + +### Conditional Code Generation + +The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives: + +``` + + + +
+

Application Report

+

Generated: {{.Timestamp}}

+
+
+ + {{range .Items}} +

{{.}}

+ {{end}} +
+ +` + + // Write report to file + file, err := os.Create(reportPath) + if err != nil { + return err + } + defer file.Close() + + t, err := template.New("report").Parse(tmpl) + if err != nil { + return err + } + + err = t.Execute(file, data) + if err != nil { + return err + } + + // Open in browser + return app.Browser.OpenFile(reportPath) +} +``` + +### Development Tools + +Open development resources during development: + +```go +func setupDevelopmentMenu(app *application.App) { + if !app.Env.Info().Debug { + return // Only show in debug mode + } + + menu := app.Menu.New() + devMenu := menu.AddSubmenu("Development") + + devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) { + // This would open browser devtools if available + window := app.Window.Current() + if window != nil { + window.OpenDevTools() + } + }) + + devMenu.Add("View Source").OnClick(func(ctx *application.Context) { + // Open source code repository + app.Browser.OpenURL("https://github.com/youruser/yourapp") + }) + + devMenu.Add("API Documentation").OnClick(func(ctx *application.Context) { + // Open local API docs + app.Browser.OpenURL("http://localhost:8080/docs") + }) +} +``` + +## Error Handling + +### Graceful Error Handling + +Always handle potential errors when opening URLs or files: + +```go +func openURLWithFallback(app *application.App, url string, fallbackMessage string) { + err := app.Browser.OpenURL(url) + if err != nil { + app.Logger.Error("Failed to open URL", "url", url, "error", err) + + // Show fallback dialog with URL + dialog := app.Dialog.Info() + dialog.SetTitle("Unable to Open Link") + dialog.SetMessage(fmt.Sprintf("%s\n\nURL: %s", fallbackMessage, url)) + dialog.Show() + } +} + +// Usage +openURLWithFallback(app, + "https://docs.example.com", + "Please open the following URL manually in your browser:") +``` + +### User Feedback + +Provide feedback when operations succeed or fail: + +```go +func openURLWithFeedback(app *application.App, url string) { + err := app.Browser.OpenURL(url) + if err != nil { + // Show error dialog + app.Dialog.Error(). + SetTitle("Browser Error"). + SetMessage(fmt.Sprintf("Could not open URL: %s", err.Error())). + Show() + } else { + // Optionally show success notification + app.Logger.Info("URL opened successfully", "url", url) + } +} +``` + +## Platform Considerations + + + + + On macOS: + + - Uses the `open` command to launch the default browser + - Respects user's default browser setting in System Preferences + - May prompt for permission if the application is sandboxed + - Handles `file://` URLs correctly for local files + + + + + + On Windows: + + - Uses Windows Shell API to open URLs + - Respects default browser setting in Windows Settings + - Handles Windows path formats correctly + - May show security warnings for untrusted URLs + + + + + + On Linux: + + - Attempts to use `xdg-open` first, falls back to other methods + - Behavior varies by desktop environment + - Respects `BROWSER` environment variable if set + - May require additional packages in minimal installations + + + + +## Best Practices + +1. **Always Handle Errors**: Browser operations can fail for various reasons: + ```go + if err := app.Browser.OpenURL(url); err != nil { + app.Logger.Error("Failed to open browser", "error", err) + // Provide fallback or user notification + } + ``` + +2. **Validate URLs**: Ensure URLs are well-formed before opening: + ```go + func isValidHTTPURL(str string) bool { + u, err := url.Parse(str) + return err == nil && (u.Scheme == "http" || u.Scheme == "https") + } + ``` + +3. **User Confirmation**: For external links, consider asking user permission: + ```go + // Show confirmation dialog before opening external links + confirmAndOpen(app, "https://external-site.com") + ``` + +4. **Secure File Paths**: When opening files, ensure paths are safe: + ```go + func openSafeFile(app *application.App, filename string) error { + // Ensure file exists and is readable + if _, err := os.Stat(filename); err != nil { + return err + } + return app.Browser.OpenFile(filename) + } + ``` + +## Complete Example + +Here's a complete example showing various browser integration patterns: + +```go +package main + +import ( + "fmt" + "os" + "path/filepath" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Browser Integration Demo", + }) + + // Setup menu with browser actions + setupMenu(app) + + // Create main window + window := app.Window.New() + window.SetTitle("Browser Integration") + + err := app.Run() + if err != nil { + panic(err) + } +} + +func setupMenu(app *application.App) { + menu := app.Menu.New() + + // File menu + fileMenu := menu.AddSubmenu("File") + fileMenu.Add("Generate Report").OnClick(func(ctx *application.Context) { + generateHTMLReport(app) + }) + + // Help menu + helpMenu := menu.AddSubmenu("Help") + helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) { + openWithConfirmation(app, "https://docs.example.com") + }) + helpMenu.Add("Support").OnClick(func(ctx *application.Context) { + openWithConfirmation(app, "https://support.example.com") + }) + + app.Menu.Set(menu) +} + +func openWithConfirmation(app *application.App, url string) { + dialog := app.Dialog.Question() + dialog.SetTitle("Open External Link") + dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url)) + + dialog.AddButton("Open").OnClick(func() { + if err := app.Browser.OpenURL(url); err != nil { + showError(app, "Failed to open URL", err) + } + }) + + dialog.AddButton("Cancel") + dialog.Show() +} + +func generateHTMLReport(app *application.App) { + // Create temporary HTML file + tmpDir := os.TempDir() + reportPath := filepath.Join(tmpDir, "demo_report.html") + + html := ` + + + + Demo Report + + + +
+

Application Report

+

This is a sample report generated by the application.

+
+
+

Report Details

+

This report was generated to demonstrate browser integration.

+
+ +` + + err := os.WriteFile(reportPath, []byte(html), 0644) + if err != nil { + showError(app, "Failed to create report", err) + return + } + + // Open in browser + err = app.Browser.OpenFile(reportPath) + if err != nil { + showError(app, "Failed to open report", err) + } +} + +func showError(app *application.App, message string, err error) { + app.Dialog.Error(). + SetTitle("Error"). + SetMessage(fmt.Sprintf("%s: %v", message, err)). + Show() +} +``` + +:::tip[Pro Tip] +Consider providing fallback mechanisms for when browser operations fail, such as copying URLs to clipboard or showing them in a dialog for manual opening. +::: + +:::danger[Warning] +Always validate URLs and file paths before opening them to prevent security issues. Be cautious about opening user-provided URLs without validation. +::: \ No newline at end of file diff --git a/docs/src/content/docs/features/clipboard/basics.mdx b/docs/src/content/docs/features/clipboard/basics.mdx new file mode 100644 index 000000000..b2eaf8b2f --- /dev/null +++ b/docs/src/content/docs/features/clipboard/basics.mdx @@ -0,0 +1,493 @@ +--- +title: Clipboard Operations +description: Copy and paste text with the system clipboard +sidebar: + order: 1 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Clipboard Operations + +Wails provides a **unified clipboard API** that works across all platforms. Copy and paste text with simple, consistent methods on Windows, macOS, and Linux. + +## Quick Start + +```go +// Copy text to clipboard +app.Clipboard.SetText("Hello, World!") + +// Get text from clipboard +text, ok := app.Clipboard.Text() +if ok { + fmt.Println("Clipboard:", text) +} +``` + +**That's it!** Cross-platform clipboard access. + +## Copying Text + +### Basic Copy + +```go +success := app.Clipboard.SetText("Text to copy") +if !success { + app.Logger.Error("Failed to copy to clipboard") +} +``` + +**Returns:** `bool` - `true` if successful, `false` otherwise + +### Copy from Service + +```go +type ClipboardService struct { + app *application.App +} + +func (c *ClipboardService) CopyToClipboard(text string) bool { + return c.app.Clipboard.SetText(text) +} +``` + +**Call from JavaScript:** + +```javascript +import { CopyToClipboard } from './bindings/myapp/clipboardservice' + +await CopyToClipboard("Text to copy") +``` + +### Copy with Feedback + +```go +func copyWithFeedback(text string) { + if app.Clipboard.SetText(text) { + app.Dialog.Info(). + SetTitle("Copied"). + SetMessage("Text copied to clipboard!"). + Show() + } else { + app.Dialog.Error(). + SetTitle("Copy Failed"). + SetMessage("Failed to copy to clipboard."). + Show() + } +} +``` + +## Pasting Text + +### Basic Paste + +```go +text, ok := app.Clipboard.Text() +if !ok { + app.Logger.Error("Failed to read clipboard") + return +} + +fmt.Println("Clipboard text:", text) +``` + +**Returns:** `(string, bool)` - Text and success flag + +### Paste from Service + +```go +func (c *ClipboardService) PasteFromClipboard() string { + text, ok := c.app.Clipboard.Text() + if !ok { + return "" + } + return text +} +``` + +**Call from JavaScript:** + +```javascript +import { PasteFromClipboard } from './bindings/myapp/clipboardservice' + +const text = await PasteFromClipboard() +console.log("Pasted:", text) +``` + +### Paste with Validation + +```go +func pasteText() (string, error) { + text, ok := app.Clipboard.Text() + if !ok { + return "", errors.New("clipboard empty or unavailable") + } + + // Validate + if len(text) == 0 { + return "", errors.New("clipboard is empty") + } + + if len(text) > 10000 { + return "", errors.New("clipboard text too large") + } + + return text, nil +} +``` + +## Complete Examples + +### Copy Button + +**Go:** + +```go +type TextService struct { + app *application.App +} + +func (t *TextService) CopyText(text string) error { + if !t.app.Clipboard.SetText(text) { + return errors.New("failed to copy") + } + return nil +} +``` + +**JavaScript:** + +```javascript +import { CopyText } from './bindings/myapp/textservice' + +async function copyToClipboard(text) { + try { + await CopyText(text) + showNotification("Copied to clipboard!") + } catch (error) { + showError("Failed to copy: " + error) + } +} + +// Usage +document.getElementById('copy-btn').addEventListener('click', () => { + const text = document.getElementById('text').value + copyToClipboard(text) +}) +``` + +### Paste and Process + +**Go:** + +```go +type DataService struct { + app *application.App +} + +func (d *DataService) PasteAndProcess() (string, error) { + // Get clipboard text + text, ok := d.app.Clipboard.Text() + if !ok { + return "", errors.New("clipboard unavailable") + } + + // Process text + processed := strings.TrimSpace(text) + processed = strings.ToUpper(processed) + + return processed, nil +} +``` + +**JavaScript:** + +```javascript +import { PasteAndProcess } from './bindings/myapp/dataservice' + +async function pasteAndProcess() { + try { + const result = await PasteAndProcess() + document.getElementById('output').value = result + } catch (error) { + showError("Failed to paste: " + error) + } +} +``` + +### Copy Multiple Formats + +```go +type CopyService struct { + app *application.App +} + +func (c *CopyService) CopyAsPlainText(text string) bool { + return c.app.Clipboard.SetText(text) +} + +func (c *CopyService) CopyAsJSON(data interface{}) bool { + jsonBytes, err := json.MarshalIndent(data, "", " ") + if err != nil { + return false + } + return c.app.Clipboard.SetText(string(jsonBytes)) +} + +func (c *CopyService) CopyAsCSV(rows [][]string) bool { + var buf bytes.Buffer + writer := csv.NewWriter(&buf) + + for _, row := range rows { + if err := writer.Write(row); err != nil { + return false + } + } + + writer.Flush() + return c.app.Clipboard.SetText(buf.String()) +} +``` + +### Clipboard Monitor + +```go +type ClipboardMonitor struct { + app *application.App + lastText string + ticker *time.Ticker + stopChan chan bool +} + +func NewClipboardMonitor(app *application.App) *ClipboardMonitor { + return &ClipboardMonitor{ + app: app, + stopChan: make(chan bool), + } +} + +func (cm *ClipboardMonitor) Start() { + cm.ticker = time.NewTicker(1 * time.Second) + + go func() { + for { + select { + case <-cm.ticker.C: + cm.checkClipboard() + case <-cm.stopChan: + return + } + } + }() +} + +func (cm *ClipboardMonitor) Stop() { + if cm.ticker != nil { + cm.ticker.Stop() + } + cm.stopChan <- true +} + +func (cm *ClipboardMonitor) checkClipboard() { + text, ok := cm.app.Clipboard.Text() + if !ok { + return + } + + if text != cm.lastText { + cm.lastText = text + cm.app.Event.Emit("clipboard-changed", text) + } +} +``` + +### Copy with History + +```go +type ClipboardHistory struct { + app *application.App + history []string + maxSize int +} + +func NewClipboardHistory(app *application.App) *ClipboardHistory { + return &ClipboardHistory{ + app: app, + history: make([]string, 0), + maxSize: 10, + } +} + +func (ch *ClipboardHistory) Copy(text string) bool { + if !ch.app.Clipboard.SetText(text) { + return false + } + + // Add to history + ch.history = append([]string{text}, ch.history...) + + // Limit size + if len(ch.history) > ch.maxSize { + ch.history = ch.history[:ch.maxSize] + } + + return true +} + +func (ch *ClipboardHistory) GetHistory() []string { + return ch.history +} + +func (ch *ClipboardHistory) RestoreFromHistory(index int) bool { + if index < 0 || index >= len(ch.history) { + return false + } + + return ch.app.Clipboard.SetText(ch.history[index]) +} +``` + +## Frontend Integration + +### Using Browser Clipboard API + +For simple text, you can use the browser's clipboard API: + +```javascript +// Copy +async function copyText(text) { + try { + await navigator.clipboard.writeText(text) + console.log("Copied!") + } catch (error) { + console.error("Copy failed:", error) + } +} + +// Paste +async function pasteText() { + try { + const text = await navigator.clipboard.readText() + return text + } catch (error) { + console.error("Paste failed:", error) + return "" + } +} +``` + +**Note:** Browser clipboard API requires HTTPS or localhost, and user permission. + +### Using Wails Clipboard + +For system-wide clipboard access: + +```javascript +import { CopyToClipboard, PasteFromClipboard } from './bindings/myapp/clipboardservice' + +// Copy +async function copy(text) { + const success = await CopyToClipboard(text) + if (success) { + console.log("Copied!") + } +} + +// Paste +async function paste() { + const text = await PasteFromClipboard() + return text +} +``` + +## Best Practices + +### ✅ Do + +- **Check return values** - Handle failures gracefully +- **Provide feedback** - Let users know copy succeeded +- **Validate pasted text** - Check format and size +- **Use appropriate method** - Browser API vs Wails API +- **Handle empty clipboard** - Check before using +- **Trim whitespace** - Clean pasted text + +### ❌ Don't + +- **Don't ignore failures** - Always check success +- **Don't copy sensitive data** - Clipboard is shared +- **Don't assume format** - Validate pasted data +- **Don't poll too frequently** - If monitoring clipboard +- **Don't copy large data** - Use files instead +- **Don't forget security** - Sanitise pasted content + +## Platform Differences + +### macOS + +- Uses NSPasteboard +- Supports rich text (future) +- System-wide clipboard +- Clipboard history (system feature) + +### Windows + +- Uses Windows Clipboard API +- Supports multiple formats (future) +- System-wide clipboard +- Clipboard history (Windows 10+) + +### Linux + +- Uses X11/Wayland clipboard +- Primary and clipboard selections +- Varies by desktop environment +- May require clipboard manager + +## Limitations + +### Current Version + +- **Text only** - Images not yet supported +- **No format detection** - Plain text only +- **No clipboard events** - Must poll for changes +- **No clipboard history** - Implement yourself + +### Future Features + +- Image support +- Rich text support +- Multiple formats +- Clipboard change events +- Clipboard history API + +## Next Steps + + + + Call Go functions from JavaScript. + + [Learn More →](/features/bindings/methods) + + + + Use events for clipboard notifications. + + [Learn More →](/features/events/system) + + + + Show copy/paste feedback. + + [Learn More →](/features/dialogs/message) + + + + Organise clipboard code. + + [Learn More →](/features/bindings/services) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [clipboard examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/dialogs/custom.mdx b/docs/src/content/docs/features/dialogs/custom.mdx new file mode 100644 index 000000000..a630ee1e3 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/custom.mdx @@ -0,0 +1,626 @@ +--- +title: Custom dialogs +sidebar: + order: 4 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Custom dialogs + +Create **custom dialog windows** using regular Wails windows with dialog-like behaviour. Build custom forms, complex input validation, branded appearance, and rich content (images, videos) whilst maintaining familiar dialog patterns. + +## Quick Start + +```go +// Create custom dialog window +dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom dialog", + Width: 400, + Height: 300, + AlwaysOnTop: true, + Frameless: true, + Hidden: true, +}) + +// Load custom UI +dialog.SetURL("http://wails.localhost/dialog.html") + +// Show as modal +dialog.Show() +dialog.Focus() +``` + +**That's it!** Custom UI with dialog behaviour. + +## Creating Custom dialogs + +### Basic Custom dialog + +```go +type Customdialog struct { + window *application.WebviewWindow + result chan string +} + +func NewCustomdialog(app *application.App) *Customdialog { + dialog := &Customdialog{ + result: make(chan string, 1), + } + + dialog.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom dialog", + Width: 400, + Height: 300, + AlwaysOnTop: true, + Resizable: false, + Hidden: true, + }) + + return dialog +} + +func (d *Customdialog) Show() string { + d.window.Show() + d.window.Focus() + + // Wait for result + return <-d.result +} + +func (d *Customdialog) Close(result string) { + d.result <- result + d.window.Close() +} +``` + +### Modal dialog + +```go +func ShowModaldialog(parent *application.WebviewWindow, title string) string { + // Create dialog + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, + Width: 400, + Height: 200, + Parent: parent, + AlwaysOnTop: true, + Resizable: false, + }) + + // Disable parent + parent.SetEnabled(false) + + // Re-enable parent on close + dialog.OnClose(func() bool { + parent.SetEnabled(true) + parent.Focus() + return true + }) + + dialog.Show() + + return waitForResult(dialog) +} +``` + +### Form dialog + +```go +type Formdialog struct { + window *application.WebviewWindow + data map[string]interface{} + done chan bool +} + +func NewFormdialog(app *application.App) *Formdialog { + fd := &Formdialog{ + data: make(map[string]interface{}), + done: make(chan bool, 1), + } + + fd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Enter Information", + Width: 500, + Height: 400, + Frameless: true, + Hidden: true, + }) + + return fd +} + +func (fd *Formdialog) Show() (map[string]interface{}, bool) { + fd.window.Show() + fd.window.Focus() + + ok := <-fd.done + return fd.data, ok +} + +func (fd *Formdialog) Submit(data map[string]interface{}) { + fd.data = data + fd.done <- true + fd.window.Close() +} + +func (fd *Formdialog) Cancel() { + fd.done <- false + fd.window.Close() +} +``` + +## dialog Patterns + +### Confirmation dialog + +```go +func ShowConfirmdialog(message string) bool { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Confirm", + Width: 400, + Height: 150, + AlwaysOnTop: true, + Frameless: true, + }) + + // Pass message to dialog + dialog.OnReady(func() { + dialog.EmitEvent("set-message", message) + }) + + result := make(chan bool, 1) + + // Handle responses + app.Event.On("confirm-yes", func(e *application.CustomEvent) { + result <- true + dialog.Close() + }) + + app.Event.On("confirm-no", func(e *application.CustomEvent) { + result <- false + dialog.Close() + }) + + dialog.Show() + return <-result +} +``` + +**Frontend (HTML/JS):** + +```html +
+

+
+ + +
+
+ + +``` + +### Input dialog + +```go +func ShowInputdialog(prompt string, defaultValue string) (string, bool) { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Input", + Width: 400, + Height: 150, + Frameless: true, + }) + + result := make(chan struct { + value string + ok bool + }, 1) + + dialog.OnReady(func() { + dialog.EmitEvent("set-prompt", map[string]string{ + "prompt": prompt, + "default": defaultValue, + }) + }) + + app.Event.On("input-submit", func(e *application.CustomEvent) { + result <- struct { + value string + ok bool + }{e.Data.(string), true} + dialog.Close() + }) + + app.Event.On("input-cancel", func(e *application.CustomEvent) { + result <- struct { + value string + ok bool + }{"", false} + dialog.Close() + }) + + dialog.Show() + r := <-result + return r.value, r.ok +} +``` + +### Progress dialog + +```go +type Progressdialog struct { + window *application.WebviewWindow +} + +func NewProgressdialog(title string) *Progressdialog { + pd := &Progressdialog{} + + pd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, + Width: 400, + Height: 150, + Frameless: true, + }) + + return pd +} + +func (pd *Progressdialog) Show() { + pd.window.Show() +} + +func (pd *Progressdialog) UpdateProgress(current, total int, message string) { + pd.window.EmitEvent("progress-update", map[string]interface{}{ + "current": current, + "total": total, + "message": message, + }) +} + +func (pd *Progressdialog) Close() { + pd.window.Close() +} +``` + +**Usage:** + +```go +func processFiles(files []string) { + progress := NewProgressdialog("Processing Files") + progress.Show() + + for i, file := range files { + progress.UpdateProgress(i+1, len(files), + fmt.Sprintf("Processing %s...", filepath.Base(file))) + + processFile(file) + } + + progress.Close() +} +``` + +## Complete Examples + +### Login dialog + +**Go:** + +```go +type Logindialog struct { + window *application.WebviewWindow + result chan struct { + username string + password string + ok bool + } +} + +func NewLogindialog(app *application.App) *Logindialog { + ld := &Logindialog{ + result: make(chan struct { + username string + password string + ok bool + }, 1), + } + + ld.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Login", + Width: 400, + Height: 250, + Frameless: true, + }) + + return ld +} + +func (ld *Logindialog) Show() (string, string, bool) { + ld.window.Show() + ld.window.Focus() + + r := <-ld.result + return r.username, r.password, r.ok +} + +func (ld *Logindialog) Submit(username, password string) { + ld.result <- struct { + username string + password string + ok bool + }{username, password, true} + ld.window.Close() +} + +func (ld *Logindialog) Cancel() { + ld.result <- struct { + username string + password string + ok bool + }{"", "", false} + ld.window.Close() +} +``` + +**Frontend:** + +```html + + + +``` + +### Settings dialog + +**Go:** + +```go +type Settingsdialog struct { + window *application.WebviewWindow + settings map[string]interface{} + done chan bool +} + +func NewSettingsdialog(app *application.App, current map[string]interface{}) *Settingsdialog { + sd := &Settingsdialog{ + settings: current, + done: make(chan bool, 1), + } + + sd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Settings", + Width: 600, + Height: 500, + }) + + sd.window.OnReady(func() { + sd.window.EmitEvent("load-settings", current) + }) + + return sd +} + +func (sd *Settingsdialog) Show() (map[string]interface{}, bool) { + sd.window.Show() + + ok := <-sd.done + return sd.settings, ok +} + +func (sd *Settingsdialog) Save(settings map[string]interface{}) { + sd.settings = settings + sd.done <- true + sd.window.Close() +} + +func (sd *Settingsdialog) Cancel() { + sd.done <- false + sd.window.Close() +} +``` + +### Wizard dialog + +```go +type Wizarddialog struct { + window *application.WebviewWindow + currentStep int + data map[string]interface{} + done chan bool +} + +func NewWizarddialog(app *application.App) *Wizarddialog { + wd := &Wizarddialog{ + currentStep: 0, + data: make(map[string]interface{}), + done: make(chan bool, 1), + } + + wd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Setup Wizard", + Width: 600, + Height: 400, + Resizable: false, + }) + + return wd +} + +func (wd *Wizarddialog) Show() (map[string]interface{}, bool) { + wd.window.Show() + + ok := <-wd.done + return wd.data, ok +} + +func (wd *Wizarddialog) NextStep(stepData map[string]interface{}) { + // Merge step data + for k, v := range stepData { + wd.data[k] = v + } + + wd.currentStep++ + wd.window.EmitEvent("next-step", wd.currentStep) +} + +func (wd *Wizarddialog) PreviousStep() { + if wd.currentStep > 0 { + wd.currentStep-- + wd.window.EmitEvent("previous-step", wd.currentStep) + } +} + +func (wd *Wizarddialog) Finish(finalData map[string]interface{}) { + for k, v := range finalData { + wd.data[k] = v + } + + wd.done <- true + wd.window.Close() +} + +func (wd *Wizarddialog) Cancel() { + wd.done <- false + wd.window.Close() +} +``` + +## Best Practices + +### Do + +- **Use appropriate window options** - AlwaysOnTop, Frameless, etc. +- **Handle cancellation** - Always provide a way to cancel +- **Validate input** - Check data before accepting +- **Provide feedback** - Loading states, errors +- **Use events for communication** - Clean separation +- **Clean up resources** - Close windows, remove listeners + +### Don't + +- **Don't block the main thread** - Use channels for results +- **Don't forget to close** - Memory leaks +- **Don't skip validation** - Always validate input +- **Don't ignore errors** - Handle all error cases +- **Don't make it too complex** - Keep dialogs simple +- **Don't forget accessibility** - Keyboard navigation + +## Styling Custom dialogs + +### Modern dialog Style + +```css +.dialog { + display: flex; + flex-direction: column; + height: 100vh; + background: white; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; +} + +.dialog-header { + --wails-draggable: drag; + padding: 16px; + background: #f5f5f5; + border-bottom: 1px solid #e0e0e0; +} + +.dialog-content { + flex: 1; + padding: 24px; + overflow: auto; +} + +.dialog-footer { + padding: 16px; + background: #f5f5f5; + border-top: 1px solid #e0e0e0; + display: flex; + justify-content: flex-end; + gap: 8px; +} + +button { + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +button.primary { + background: #007aff; + color: white; +} + +button.secondary { + background: #e0e0e0; + color: #333; +} +``` + +## Next Steps + + + + Standard info, warning, error dialogs. + + [Learn More →](/features/dialogs/message) + + + + Open, save, folder selection. + + [Learn More →](/features/dialogs/file) + + + + Learn about window management. + + [Learn More →](/features/windows/basics) + + + + Use events for dialog communication. + + [Learn More →](/features/events/system) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [custom dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/dialogs/file.mdx b/docs/src/content/docs/features/dialogs/file.mdx new file mode 100644 index 000000000..c7daf3f30 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/file.mdx @@ -0,0 +1,591 @@ +--- +title: File dialogs +description: Open, save, and folder selection dialogs +sidebar: + order: 3 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## File dialogs + +Wails provides **native file dialogs** with platform-appropriate appearance for opening files, saving files, and selecting folders. Simple API with file type filtering, multiple selection support, and default locations. +## Open File dialog + +Select files to open: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err != nil { + return +} + +openFile(path) +``` + +**Use cases:** +- Open documents +- Import files +- Load images +- Select configuration files + +### Single File Selection + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Open Document"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err != nil { + // User cancelled or error occurred + return +} + +// Use selected file +data, _ := os.ReadFile(path) +``` + +### Multiple File Selection + +```go +paths, err := app.Dialog.OpenFile(). + SetTitle("Select Images"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + PromptForMultipleSelection() + +if err != nil { + return +} + +// Process all selected files +for _, path := range paths { + processFile(path) +} +``` + +### With Default Directory + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Open File"). + SetDirectory("/Users/me/Documents"). + PromptForSingleSelection() +``` + +## Save File dialog + +Choose where to save: + +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Save Document"). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err != nil { + return +} + +saveFile(path, data) +``` + +**Use cases:** +- Save documents +- Export data +- Create new files +- Save as... + +### With Default Filename + +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Export Data"). + SetFilename("export.csv"). + AddFilter("CSV Files", "*.csv"). + PromptForSingleSelection() +``` + +### With Default Directory + +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Save File"). + SetDirectory("/Users/me/Documents"). + SetFilename("untitled.txt"). + PromptForSingleSelection() +``` + +### Overwrite Confirmation + +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Save File"). + SetFilename("document.txt"). + PromptForSingleSelection() + +if err != nil { + return +} + +// Check if file exists +if _, err := os.Stat(path); err == nil { + dialog := app.Dialog.Question(). + SetTitle("Confirm Overwrite"). + SetMessage("File already exists. Overwrite?") + + overwriteBtn := dialog.AddButton("Overwrite") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsDefault() + cancelBtn.SetAsCancel() + + overwriteBtn.OnClick(func() { + saveFile(path, data) + }) + + dialog.Show() + return +} + +saveFile(path, data) +``` + +## Select Folder dialog + +Choose a directory: + +```go +path, err := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + SetTitle("Select Output Folder"). + PromptForSingleSelection() + +if err != nil { + return +} + +exportToFolder(path) +``` + +**Use cases:** +- Choose output directory +- Select workspace +- Pick backup location +- Choose installation directory + +### With Default Directory + +```go +path, err := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + SetTitle("Select Folder"). + SetDirectory("/Users/me/Documents"). + PromptForSingleSelection() +``` + +## File Filters + +### Basic Filters + +```go +path, _ := app.Dialog.OpenFile(). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() +``` + +### Multiple Extensions + +```go +path, _ := app.Dialog.OpenFile(). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"). + AddFilter("Documents", "*.txt;*.doc;*.docx;*.pdf"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() +``` + +### Platform-Specific Patterns + +**Windows:** +```go +// Semicolon-separated +// Pattern: "*.png;*.jpg;*.gif" +``` + +**macOS/Linux:** +```go +// Space or comma-separated (both work) +// Pattern: "*.png *.jpg *.gif" +// or +// Pattern: "*.png,*.jpg,*.gif" +``` + +**Cross-platform (recommended):** +```go +// Use semicolons (works on all platforms) +// Pattern: "*.png;*.jpg;*.gif" +``` + +## Complete Examples + +### Open Image File + +```go +func openImage() (image.Image, error) { + path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"). + PromptForSingleSelection() + + if err != nil { + return nil, err + } + + // Open and decode image + file, err := os.Open(path) + if err != nil { + app.Dialog.Error(). + SetTitle("Open Failed"). + SetMessage(err.Error()). + Show() + return nil, err + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + app.Dialog.Error(). + SetTitle("Invalid Image"). + SetMessage("Could not decode image file."). + Show() + return nil, err + } + + return img, nil +} +``` + +### Save Document with Validation + +```go +func saveDocument(content string) error { + path, err := app.Dialog.SaveFile(). + SetTitle("Save Document"). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("Markdown Files", "*.md"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + + if err != nil { + return err + } + + // Validate extension + ext := filepath.Ext(path) + if ext != ".txt" && ext != ".md" { + dialog := app.Dialog.Question(). + SetTitle("Confirm Extension"). + SetMessage(fmt.Sprintf("Save as %s file?", ext)) + + saveBtn := dialog.AddButton("Save") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsCancel() + + confirmed := make(chan bool, 1) + saveBtn.OnClick(func() { + confirmed <- true + }) + cancelBtn.OnClick(func() { + confirmed <- false + }) + + dialog.Show() + + if !<-confirmed { + return errors.New("cancelled") + } + } + + // Save file + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(err.Error()). + Show() + return err + } + + app.Dialog.Info(). + SetTitle("Saved"). + SetMessage("Document saved successfully!"). + Show() + + return nil +} +``` + +### Batch File Processing + +```go +func processMultipleFiles() { + paths, err := app.Dialog.OpenFile(). + SetTitle("Select Files to Process"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + + if err != nil || len(paths) == 0 { + return + } + + // Confirm processing + dialog := app.Dialog.Question(). + SetTitle("Confirm Processing"). + SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths))) + + processBtn := dialog.AddButton("Process") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsCancel() + + processBtn.OnClick(func() { + // Process files + var errs []error + for i, path := range paths { + if err := processFile(path); err != nil { + errs = append(errs, err) + } + + // Update progress + app.Event.Emit("progress", map[string]interface{}{ + "current": i + 1, + "total": len(paths), + }) + } + + // Show results + if len(errs) > 0 { + app.Dialog.Warning(). + SetTitle("Processing Complete"). + SetMessage(fmt.Sprintf("Processed %d files with %d errors.", + len(paths), len(errs))). + Show() + } else { + app.Dialog.Info(). + SetTitle("Success"). + SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))). + Show() + } + }) + + dialog.Show() +} +``` + +### Export with Folder Selection + +```go +func exportData(data []byte) { + // Select output folder + folder, err := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + SetTitle("Select Export Folder"). + SetDirectory(getDefaultExportFolder()). + PromptForSingleSelection() + + if err != nil { + return + } + + // Generate filename + filename := fmt.Sprintf("export_%s.csv", + time.Now().Format("2006-01-02_15-04-05")) + path := filepath.Join(folder, filename) + + // Save file + if err := os.WriteFile(path, data, 0644); err != nil { + app.Dialog.Error(). + SetTitle("Export Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Show success with option to open folder + dialog := app.Dialog.Question(). + SetTitle("Export Complete"). + SetMessage(fmt.Sprintf("Exported to %s", filename)) + + openFolderBtn := dialog.AddButton("Open Folder") + okBtn := dialog.AddButton("OK") + okBtn.SetAsDefault() + + openFolderBtn.OnClick(func() { + openFolder(folder) + }) + + dialog.Show() +} +``` + +### Import with Validation + +```go +func importConfiguration() error { + path, err := app.Dialog.OpenFile(). + SetTitle("Import Configuration"). + AddFilter("JSON Files", "*.json"). + AddFilter("YAML Files", "*.yaml;*.yml"). + PromptForSingleSelection() + + if err != nil { + return err + } + + // Read file + data, err := os.ReadFile(path) + if err != nil { + app.Dialog.Error(). + SetTitle("Read Failed"). + SetMessage(err.Error()). + Show() + return err + } + + // Validate configuration + config, err := parseConfig(data) + if err != nil { + app.Dialog.Error(). + SetTitle("Invalid Configuration"). + SetMessage("File is not a valid configuration."). + Show() + return err + } + + // Confirm import + dialog := app.Dialog.Question(). + SetTitle("Confirm Import"). + SetMessage("Import this configuration?") + + importBtn := dialog.AddButton("Import") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsCancel() + + importResult := make(chan bool, 1) + importBtn.OnClick(func() { + importResult <- true + }) + cancelBtn.OnClick(func() { + importResult <- false + }) + + dialog.Show() + + if !<-importResult { + return errors.New("cancelled") + } + + // Apply configuration + if err := applyConfig(config); err != nil { + app.Dialog.Error(). + SetTitle("Import Failed"). + SetMessage(err.Error()). + Show() + return err + } + + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Configuration imported successfully!"). + Show() + + return nil +} +``` + +## Best Practices + +### Do + +- **Provide file filters** - Help users find files +- **Set appropriate titles** - Clear context +- **Use default directories** - Start in logical location +- **Validate selections** - Check file types +- **Handle cancellation** - User might cancel +- **Show confirmation** - For destructive actions +- **Provide feedback** - Success/error messages + +### Don't + +- **Don't skip validation** - Check file types +- **Don't ignore errors** - Handle cancellation +- **Don't use generic filters** - Be specific +- **Don't forget "All Files"** - Always include as option +- **Don't hardcode paths** - Use user's home directory +- **Don't assume file exists** - Check before opening + +## Platform Differences + +### macOS + +- Native NSOpenPanel/NSSavePanel +- Sheet-style when attached to window +- Follows system theme +- Supports Quick Look preview +- Tags and favourites integration + +### Windows + +- Native File Open/Save dialogs +- Follows system theme +- Recent files integration +- Network location support + +### Linux + +- GTK file chooser +- Varies by desktop environment +- Follows desktop theme +- Recent files support + +## Next Steps + + + + Info, warning, and error dialogs. + + [Learn More →](/features/dialogs/message) + + + + Create custom dialog windows. + + [Learn More →](/features/dialogs/custom) + + + + Call Go functions from JavaScript. + + [Learn More →](/features/bindings/methods) + + + + Use events for progress updates. + + [Learn More →](/features/events/system) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [file dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/dialogs/message.mdx b/docs/src/content/docs/features/dialogs/message.mdx new file mode 100644 index 000000000..5bec6d9eb --- /dev/null +++ b/docs/src/content/docs/features/dialogs/message.mdx @@ -0,0 +1,518 @@ +--- +title: Message dialogs +description: Display information, warnings, errors, and questions +sidebar: + order: 2 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Message dialogs + +Wails provides **native message dialogs** with platform-appropriate appearance: info, warning, error, and question dialogs with customisable titles, messages, and buttons. Simple API, native behaviour, accessible by default. +## Information dialog + +Display informational messages: + +```go +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() +``` + +**Use cases:** +- Success confirmations +- Completion notices +- Informational messages +- Status updates + +**Example - Save confirmation:** + +```go +func saveFile(path string, data []byte) error { + if err := os.WriteFile(path, data, 0644); err != nil { + return err + } + + app.Dialog.Info(). + SetTitle("File Saved"). + SetMessage(fmt.Sprintf("Saved to %s", filepath.Base(path))). + Show() + + return nil +} +``` + +## Warning dialog + +Show warnings: + +```go +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +**Use cases:** +- Non-critical warnings +- Deprecation notices +- Caution messages +- Potential issues + +**Example - Disk space warning:** + +```go +func checkDiskSpace() { + available := getDiskSpace() + + if available < 100*1024*1024 { // Less than 100MB + app.Dialog.Warning(). + SetTitle("Low Disk Space"). + SetMessage(fmt.Sprintf("Only %d MB available.", available/(1024*1024))). + Show() + } +} +``` + +## Error dialog + +Display errors: + +```go +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Failed to connect to server."). + Show() +``` + +**Use cases:** +- Error messages +- Failure notifications +- Exception handling +- Critical issues + +**Example - Network error:** + +```go +func fetchData(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + app.Dialog.Error(). + SetTitle("Network Error"). + SetMessage(fmt.Sprintf("Failed to connect: %v", err)). + Show() + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} +``` + +## Question dialog + +Ask users questions using button callbacks: + +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Save changes before closing?") + +saveBtn := dialog.AddButton("Save") +dontSaveBtn := dialog.AddButton("Don't Save") +cancelBtn := dialog.AddButton("Cancel") +saveBtn.SetAsDefault() +cancelBtn.SetAsCancel() + +saveBtn.OnClick(func() { + saveChanges() +}) + +dontSaveBtn.OnClick(func() { + // Continue without saving +}) + +cancelBtn.OnClick(func() { + // Don't close +}) + +dialog.Show() +``` + +**Use cases:** +- Confirm actions +- Yes/No questions +- Multiple choice +- User decisions + +**Example - Unsaved changes:** + +```go +func closeDocument() { + if !hasUnsavedChanges() { + closeWindow() + return + } + + dialog := app.Dialog.Question(). + SetTitle("Unsaved Changes"). + SetMessage("Do you want to save your changes?") + + saveBtn := dialog.AddButton("Save") + dontSaveBtn := dialog.AddButton("Don't Save") + cancelBtn := dialog.AddButton("Cancel") + saveBtn.SetAsDefault() + cancelBtn.SetAsCancel() + + saveBtn.OnClick(func() { + saveDocument() + closeWindow() + }) + + dontSaveBtn.OnClick(func() { + closeWindow() + }) + + // Cancel button does nothing - dialog just closes + + dialog.Show() +} +``` + +## dialog Options + +### Title and Message + +```go +dialog := app.Dialog.Info(). + SetTitle("Operation Complete"). + SetMessage("All files have been processed successfully.") +``` + +**Best practices:** +- **Title:** Short, descriptive (2-5 words) +- **Message:** Clear, specific, actionable +- **Avoid jargon:** Use plain language + +### Buttons + +**Single button (Info/Warning/Error):** + +```go +// Default "OK" button +app.Dialog.Info(). + SetMessage("Done!"). + Show() +``` + +**Multiple buttons (Question) with callbacks:** + +```go +dialog := app.Dialog.Question(). + SetMessage("Choose an action") + +opt1Btn := dialog.AddButton("Option 1") +opt2Btn := dialog.AddButton("Option 2") +opt3Btn := dialog.AddButton("Option 3") + +opt1Btn.OnClick(func() { + // handle option 1 +}) + +opt2Btn.OnClick(func() { + // handle option 2 +}) + +opt3Btn.OnClick(func() { + // handle option 3 +}) + +dialog.Show() +``` + +**Default button:** + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +deleteBtn := dialog.AddButton("Delete") +cancelBtn := dialog.AddButton("Cancel") +cancelBtn.SetAsDefault() // Safe option +cancelBtn.SetAsCancel() + +deleteBtn.OnClick(func() { + // handle delete +}) + +dialog.Show() +``` + +**Best practices:** +- **1-3 buttons:** Don't overwhelm users +- **Clear labels:** "Save" not "OK" +- **Safe default:** Non-destructive action +- **Order matters:** Most likely action first (except Cancel) + +### Window Attachment + +Attach to specific window: + +```go +dialog := app.Dialog.Question(). + SetMessage("Window-specific question"). + AttachToWindow(window) + +dialog.Show() +``` + +**Benefits:** +- dialog appears on correct window +- Parent window disabled whilst shown +- Better multi-window UX + +## Complete Examples + +### Confirm Destructive Action + +```go +func deleteFiles(paths []string) { + // Confirm deletion + message := fmt.Sprintf("Delete %d file(s)?", len(paths)) + if len(paths) == 1 { + message = fmt.Sprintf("Delete %s?", filepath.Base(paths[0])) + } + + dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage(message) + + deleteBtn := dialog.AddButton("Delete") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsDefault() + cancelBtn.SetAsCancel() + + deleteBtn.OnClick(func() { + // Perform deletion + var errs []error + for _, path := range paths { + if err := os.Remove(path); err != nil { + errs = append(errs, err) + } + } + + // Show result + if len(errs) > 0 { + app.Dialog.Error(). + SetTitle("Delete Failed"). + SetMessage(fmt.Sprintf("Failed to delete %d file(s)", len(errs))). + Show() + } else { + app.Dialog.Info(). + SetTitle("Delete Complete"). + SetMessage(fmt.Sprintf("Deleted %d file(s)", len(paths))). + Show() + } + }) + + dialog.Show() +} +``` + +### Error Handling with Retry + +```go +func connectToServer(url string, attempts int) { + if err := tryConnect(url); err == nil { + return + } + + if attempts >= 3 { + app.Dialog.Error(). + SetTitle("Connection Failed"). + SetMessage("Could not connect after 3 attempts."). + Show() + return + } + + dialog := app.Dialog.Question(). + SetTitle("Connection Failed"). + SetMessage("Failed to connect. Retry?") + + retryBtn := dialog.AddButton("Retry") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsCancel() + + retryBtn.OnClick(func() { + connectToServer(url, attempts+1) + }) + + dialog.Show() +} +``` + +### Multi-Step Workflow + +```go +func exportAndEmail() { + // Step 1: Confirm export + dialog := app.Dialog.Question(). + SetTitle("Export and Email"). + SetMessage("Export data and send via email?") + + continueBtn := dialog.AddButton("Continue") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsCancel() + + continueBtn.OnClick(func() { + // Step 2: Export data + data, err := exportData() + if err != nil { + app.Dialog.Error(). + SetTitle("Export Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Step 3: Send email + if err := sendEmail(data); err != nil { + app.Dialog.Error(). + SetTitle("Email Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Step 4: Success + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Data exported and emailed successfully!"). + Show() + }) + + dialog.Show() +} +``` + +### Validation with Feedback + +```go +func validateAndSave(data string) { + // Validate + if err := validate(data); err != nil { + app.Dialog.Warning(). + SetTitle("Validation Warning"). + SetMessage(err.Error()). + Show() + + dialog := app.Dialog.Question(). + SetMessage("Save anyway?") + + saveBtn := dialog.AddButton("Save") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsDefault() + cancelBtn.SetAsCancel() + + saveBtn.OnClick(func() { + doSave(data) + }) + + dialog.Show() + return + } + + doSave(data) +} + +func doSave(data string) { + if err := save(data); err != nil { + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(err.Error()). + Show() + return + } + + app.Dialog.Info(). + SetTitle("Saved"). + SetMessage("Data saved successfully!"). + Show() +} +``` + +## Best Practices + +### Do + +- **Be specific** - "File saved to Documents" not "Success" +- **Use appropriate type** - Error for errors, Warning for warnings +- **Provide context** - Include relevant details +- **Use clear button labels** - "Delete" not "OK" +- **Set safe defaults** - Non-destructive action +- **Handle cancellation** - User might close dialog + +### Don't + +- **Don't overuse** - Interrupts workflow +- **Don't use for frequent updates** - Use notifications instead +- **Don't use generic messages** - "Error" tells nothing +- **Don't block unnecessarily** - Consider async alternatives +- **Don't use technical jargon** - Plain language + +## Platform Differences + +### macOS + +- Sheet-style when attached to window +- Standard keyboard shortcuts (⌘. for Cancel) +- Follows system theme automatically +- Accessibility built-in + +### Windows + +- Modal dialogs +- TaskDialog appearance +- Esc for Cancel +- Follows system theme + +### Linux + +- GTK dialogs +- Varies by desktop environment +- Follows desktop theme +- Standard keyboard navigation + +## Next Steps + + + + Open, save, and folder selection. + + [Learn More →](/features/dialogs/file) + + + + Create custom dialog windows. + + [Learn More →](/features/dialogs/custom) + + + + Non-intrusive notifications. + + [Learn More →](/features/notifications) + + + + Use events for non-blocking communication. + + [Learn More →](/features/events/system) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/dialogs/overview.mdx b/docs/src/content/docs/features/dialogs/overview.mdx new file mode 100644 index 000000000..99ff8b2f2 --- /dev/null +++ b/docs/src/content/docs/features/dialogs/overview.mdx @@ -0,0 +1,494 @@ +--- +title: dialogs Overview +description: Display native system dialogs in your application +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Native dialogs + +Wails provides **native system dialogs** that work across all platforms: message dialogs (info, warning, error, question), file dialogs (open, save, folder), and custom dialog windows with platform-native appearance and behaviour. + +## Quick Start + +```go +// Information dialog +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() + +// Question dialog with button callbacks +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Delete this file?") + +deleteBtn := dialog.AddButton("Delete") +cancelBtn := dialog.AddButton("Cancel") +cancelBtn.SetAsDefault() +cancelBtn.SetAsCancel() + +deleteBtn.OnClick(func() { + deleteFile() +}) + +dialog.Show() + +// File open dialog +path, _ := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg"). + PromptForSingleSelection() +``` + +**That's it!** Native dialogs with minimal code. + +## dialog Types + +### Information dialog + +Display simple messages: + +```go +app.Dialog.Info(). + SetTitle("Welcome"). + SetMessage("Welcome to our application!"). + Show() +``` + +**Use cases:** +- Success messages +- Informational notices +- Completion confirmations + +### Warning dialog + +Show warnings: + +```go +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +**Use cases:** +- Non-critical warnings +- Deprecation notices +- Caution messages + +### Error dialog + +Display errors: + +```go +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Failed to save file: " + err.Error()). + Show() +``` + +**Use cases:** +- Error messages +- Failure notifications +- Exception handling + +### Question dialog + +Ask users questions: + +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage("Are you sure you want to delete this file?") + +deleteBtn := dialog.AddButton("Delete") +cancelBtn := dialog.AddButton("Cancel") +cancelBtn.SetAsDefault() +cancelBtn.SetAsCancel() + +deleteBtn.OnClick(func() { + deleteFile() +}) + +dialog.Show() +``` + +**Use cases:** +- Confirm actions +- Yes/No questions +- Multiple choice + +## File dialogs + +### Open File dialog + +Select files to open: + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err == nil { + openFile(path) +} +``` + +**Multiple selection:** + +```go +paths, err := app.Dialog.OpenFile(). + SetTitle("Select Images"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + +for _, path := range paths { + processFile(path) +} +``` + +### Save File dialog + +Choose where to save: + +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Save Document"). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() + +if err == nil { + saveFile(path) +} +``` + +### Select Folder dialog + +Choose a directory: + +```go +path, err := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + SetTitle("Select Output Folder"). + PromptForSingleSelection() + +if err == nil { + exportToFolder(path) +} +``` + +## dialog Options + +### Title and Message + +```go +dialog := app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Operation completed successfully!") +``` + +### Buttons + +**Single button (Info/Warning/Error):** + +```go +// Default "OK" button +app.Dialog.Info(). + SetMessage("Done!"). + Show() +``` + +**Multiple buttons (Question) with callbacks:** + +```go +dialog := app.Dialog.Question(). + SetMessage("Choose action") + +opt1Btn := dialog.AddButton("Option 1") +opt2Btn := dialog.AddButton("Option 2") +opt3Btn := dialog.AddButton("Option 3") + +opt1Btn.OnClick(func() { + // handle option 1 +}) + +opt2Btn.OnClick(func() { + // handle option 2 +}) + +opt3Btn.OnClick(func() { + // handle option 3 +}) + +dialog.Show() +``` + +**Default button:** + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +deleteBtn := dialog.AddButton("Delete") +cancelBtn := dialog.AddButton("Cancel") +cancelBtn.SetAsDefault() // Safe option highlighted by default +cancelBtn.SetAsCancel() + +deleteBtn.OnClick(func() { + // handle delete +}) + +dialog.Show() +``` + +### Window Attachment + +Attach dialog to specific window: + +```go +dialog := app.Dialog.Info(). + SetMessage("Window-specific message"). + AttachToWindow(window) + +dialog.Show() +``` + +**Behaviour:** +- dialog appears centred on parent window +- Parent window disabled whilst dialog shown +- dialog moves with parent window (macOS) + +## Platform Behaviour + + + + **macOS dialogs:** + + - Native NSAlert appearance + - Follow system theme (light/dark) + - Support keyboard navigation + - Standard shortcuts (⌘. for Cancel) + - Accessibility features built-in + - Sheet-style when attached to window + + **Example:** + ```go + // Appears as sheet on macOS + app.Dialog.Question(). + SetMessage("Save changes?"). + AttachToWindow(window). + Show() + ``` + + + + **Windows dialogs:** + + - Native TaskDialog appearance + - Follow system theme + - Support keyboard navigation + - Standard shortcuts (Esc for Cancel) + - Accessibility features built-in + - Modal to parent window + + **Example:** + ```go + // Modal dialog on Windows + app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Operation failed"). + Show() + ``` + + + + **Linux dialogs:** + + - GTK dialog appearance + - Follow desktop theme + - Support keyboard navigation + - Desktop environment integration + - Varies by DE (GNOME, KDE, etc.) + + **Example:** + ```go + // GTK dialog on Linux + app.Dialog.Info(). + SetMessage("Update complete"). + Show() + ``` + + + +## Common Patterns + +### Confirm Before Destructive Action + +```go +func deleteFile(path string) { + dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage(fmt.Sprintf("Delete %s?", filepath.Base(path))) + + deleteBtn := dialog.AddButton("Delete") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsDefault() + cancelBtn.SetAsCancel() + + deleteBtn.OnClick(func() { + os.Remove(path) + }) + + dialog.Show() +} +``` + +### Error Handling with dialog + +```go +func saveDocument(path string, data []byte) { + if err := os.WriteFile(path, data, 0644); err != nil { + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(fmt.Sprintf("Could not save file: %v", err)). + Show() + return + } + + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() +} +``` + +### File Selection with Validation + +```go +func selectImageFile() (string, error) { + path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + PromptForSingleSelection() + + if err != nil { + return "", err + } + + // Validate file + if !isValidImage(path) { + app.Dialog.Error(). + SetTitle("Invalid File"). + SetMessage("Selected file is not a valid image."). + Show() + return "", errors.New("invalid image") + } + + return path, nil +} +``` + +### Multi-Step dialog Flow + +```go +func exportData() { + // Step 1: Confirm export + dialog := app.Dialog.Question(). + SetTitle("Export Data"). + SetMessage("Export all data to CSV?") + + exportBtn := dialog.AddButton("Export") + cancelBtn := dialog.AddButton("Cancel") + cancelBtn.SetAsCancel() + + exportBtn.OnClick(func() { + // Step 2: Select destination + path, err := app.Dialog.SaveFile(). + SetTitle("Save Export"). + SetFilename("export.csv"). + AddFilter("CSV Files", "*.csv"). + PromptForSingleSelection() + + if err != nil { + return + } + + // Step 3: Perform export + if err := performExport(path); err != nil { + app.Dialog.Error(). + SetTitle("Export Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Step 4: Success + app.Dialog.Info(). + SetTitle("Export Complete"). + SetMessage("Data exported successfully!"). + Show() + }) + + dialog.Show() +} +``` + +## Best Practices + +### Do + +- **Use native dialogs** - Better UX than custom +- **Provide clear messages** - Be specific +- **Set appropriate titles** - Context matters +- **Use default buttons wisely** - Safe option as default +- **Handle cancellation** - User might cancel +- **Validate file selections** - Check file types + +### Don't + +- **Don't overuse dialogs** - Interrupts workflow +- **Don't use for frequent messages** - Use notifications +- **Don't forget error handling** - User might cancel +- **Don't block unnecessarily** - Consider alternatives +- **Don't use generic messages** - Be specific +- **Don't ignore platform differences** - Test on all platforms + +## Next Steps + + + + Info, warning, and error dialogs. + + [Learn More →](/features/dialogs/message) + + + + Open, save, and folder selection. + + [Learn More →](/features/dialogs/file) + + + + Create custom dialog windows. + + [Learn More →](/features/dialogs/custom) + + + + Learn about window management. + + [Learn More →](/features/windows/basics) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs). diff --git a/docs/src/content/docs/features/environment/info.mdx b/docs/src/content/docs/features/environment/info.mdx new file mode 100644 index 000000000..e6ed82a83 --- /dev/null +++ b/docs/src/content/docs/features/environment/info.mdx @@ -0,0 +1,630 @@ +--- +title: Environment +sidebar: + order: 59 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails provides comprehensive environment information through the EnvironmentManager API. This allows your application to detect system properties, theme preferences, and integrate with the operating system's file manager. + +## Accessing the Environment Manager + +The environment manager is accessed through the `Env` property on your application instance: + +```go +app := application.New(application.Options{ + Name: "Environment Demo", +}) + +// Access the environment manager +env := app.Env +``` + +## System Information + +### Get Environment Information + +Retrieve comprehensive information about the runtime environment: + +```go +envInfo := app.Env.Info() + +app.Logger.Info("Environment information", + "os", envInfo.OS, // "windows", "darwin", "linux" + "arch", envInfo.Arch, // "amd64", "arm64", etc. + "debug", envInfo.Debug, // Debug mode flag +) + +// Operating system details +if envInfo.OSInfo != nil { + app.Logger.Info("OS details", + "name", envInfo.OSInfo.Name, + "version", envInfo.OSInfo.Version, + ) +} + +// Platform-specific information +for key, value := range envInfo.PlatformInfo { + app.Logger.Info("Platform info", "key", key, "value", value) +} +``` + +### Environment Structure + +The environment information includes several important fields: + +```go +type EnvironmentInfo struct { + OS string // Operating system: "windows", "darwin", "linux" + Arch string // Architecture: "amd64", "arm64", "386", etc. + Debug bool // Whether running in debug mode + OSInfo *operatingsystem.OS // Detailed OS information + PlatformInfo map[string]any // Platform-specific details +} +``` + +## Theme Detection + +### Dark Mode Detection + +Detect whether the system is using dark mode: + +```go +if app.Env.IsDarkMode() { + app.Logger.Info("System is in dark mode") + // Apply dark theme to your application + applyDarkTheme() +} else { + app.Logger.Info("System is in light mode") + // Apply light theme to your application + applyLightTheme() +} +``` + +### Theme Change Monitoring + +Listen for theme changes to update your application dynamically: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Listen for theme changes +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + if app.Env.IsDarkMode() { + app.Logger.Info("Switched to dark mode") + updateApplicationTheme("dark") + } else { + app.Logger.Info("Switched to light mode") + updateApplicationTheme("light") + } +}) + +func updateApplicationTheme(theme string) { + // Update your application's theme + // This could emit an event to the frontend + app.Event.Emit("theme:changed", theme) +} +``` + +### Accent Colour + +Get the system accent colour: + +```go +accentColor := app.Env.GetAccentColor() +app.Logger.Info("System accent colour", "color", accentColor) +// Returns a CSS-compatible colour string, e.g. "rgb(0,122,255)" +``` + +## File Manager Integration + +### Open File Manager + +Open the system's file manager at a specific location: + +```go +// Open file manager at a directory +err := app.Env.OpenFileManager("/Users/username/Documents", false) +if err != nil { + app.Logger.Error("Failed to open file manager", "error", err) +} + +// Open file manager and select a specific file +err = app.Env.OpenFileManager("/Users/username/Documents/report.pdf", true) +if err != nil { + app.Logger.Error("Failed to open file manager with selection", "error", err) +} +``` + +### Common Use Cases + +Show files or folders in the file manager from your application: + +```go +func showInFileManager(app *application.App, path string) { + err := app.Env.OpenFileManager(path, true) + if err != nil { + // Fallback: try opening just the directory + dir := filepath.Dir(path) + err = app.Env.OpenFileManager(dir, false) + if err != nil { + app.Logger.Error("Could not open file manager", "path", path, "error", err) + + // Show error to user + app.Dialog.Error(). + SetTitle("File Manager Error"). + SetMessage("Could not open file manager"). + Show() + } + } +} + +// Usage examples +func setupFileMenu(app *application.App) { + menu := app.Menu.New() + fileMenu := menu.AddSubmenu("File") + + fileMenu.Add("Show Downloads Folder").OnClick(func(ctx *application.Context) { + homeDir, _ := os.UserHomeDir() + downloadsDir := filepath.Join(homeDir, "Downloads") + showInFileManager(app, downloadsDir) + }) + + fileMenu.Add("Show Application Data").OnClick(func(ctx *application.Context) { + configDir, _ := os.UserConfigDir() + appDir := filepath.Join(configDir, "MyApp") + showInFileManager(app, appDir) + }) +} +``` + +## Platform-Specific Behavior + +### Adaptive Application Behavior + +Use environment information to adapt your application's behavior: + +```go +func configureForPlatform(app *application.App) { + envInfo := app.Env.Info() + + switch envInfo.OS { + case "darwin": + configureMacOS(app) + case "windows": + configureWindows(app) + case "linux": + configureLinux(app) + } + + // Adapt to architecture + if envInfo.Arch == "arm64" { + app.Logger.Info("Running on ARM architecture") + // Potentially optimize for ARM + } +} + +func configureMacOS(app *application.App) { + app.Logger.Info("Configuring for macOS") + + // macOS-specific configuration + menu := app.Menu.New() + menu.AddRole(application.AppMenu) // Add standard macOS app menu + + // Handle dark mode + if app.Env.IsDarkMode() { + setMacOSDarkTheme() + } +} + +func configureWindows(app *application.App) { + app.Logger.Info("Configuring for Windows") + + // Windows-specific configuration + // Set up Windows-style menus, key bindings, etc. +} + +func configureLinux(app *application.App) { + app.Logger.Info("Configuring for Linux") + + // Linux-specific configuration + // Adapt to different desktop environments +} +``` + +## Debug Mode Handling + +### Development vs Production + +Use debug mode information to enable development features: + +```go +func setupApplicationMode(app *application.App) { + envInfo := app.Env.Info() + + if envInfo.Debug { + app.Logger.Info("Running in debug mode") + setupDevelopmentFeatures(app) + } else { + app.Logger.Info("Running in production mode") + setupProductionFeatures(app) + } +} + +func setupDevelopmentFeatures(app *application.App) { + // Enable development-only features + menu := app.Menu.New() + + // Add development menu + devMenu := menu.AddSubmenu("Development") + devMenu.Add("Reload Application").OnClick(func(ctx *application.Context) { + // Reload the application + window := app.Window.Current() + if window != nil { + window.Reload() + } + }) + + devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) { + window := app.Window.Current() + if window != nil { + window.OpenDevTools() + } + }) + + devMenu.Add("Show Environment").OnClick(func(ctx *application.Context) { + showEnvironmentDialog(app) + }) +} + +func setupProductionFeatures(app *application.App) { + // Production-only features + // Disable debug logging, enable analytics, etc. +} +``` + +## Environment Information Dialog + +### Display System Information + +Create a dialog showing environment information: + +```go +func showEnvironmentDialog(app *application.App) { + envInfo := app.Env.Info() + + details := fmt.Sprintf(`Environment Information: + +Operating System: %s +Architecture: %s +Debug Mode: %t + +Dark Mode: %t + +Platform Information:`, + envInfo.OS, + envInfo.Arch, + envInfo.Debug, + app.Env.IsDarkMode()) + + // Add platform-specific details + for key, value := range envInfo.PlatformInfo { + details += fmt.Sprintf("\n%s: %v", key, value) + } + + if envInfo.OSInfo != nil { + details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s", + envInfo.OSInfo.Name, + envInfo.OSInfo.Version) + } + + dialog := app.Dialog.Info() + dialog.SetTitle("Environment Information") + dialog.SetMessage(details) + dialog.Show() +} +``` + +## Platform Considerations + + + + + On macOS: + + - Dark mode detection uses system appearance settings + - File manager operations use Finder + - Platform info includes macOS version details + - Architecture may be "arm64" on Apple Silicon Macs + + ```go + if envInfo.OS == "darwin" { + // macOS-specific handling + if envInfo.Arch == "arm64" { + app.Logger.Info("Running on Apple Silicon") + } + } + ``` + + + + + + On Windows: + + - Dark mode detection uses Windows theme settings + - File manager operations use Windows Explorer + - Platform info includes Windows version details + - May include additional Windows-specific information + + ```go + if envInfo.OS == "windows" { + // Windows-specific handling + for key, value := range envInfo.PlatformInfo { + if key == "windows_version" { + app.Logger.Info("Windows version", "version", value) + } + } + } + ``` + + + + + + On Linux: + + - Dark mode detection varies by desktop environment + - File manager operations use system default file manager + - Platform info includes distribution details + - Behavior may vary between different Linux distributions + + ```go + if envInfo.OS == "linux" { + // Linux-specific handling + if distro, ok := envInfo.PlatformInfo["distribution"]; ok { + app.Logger.Info("Linux distribution", "distro", distro) + } + } + ``` + + + + +## Best Practices + +1. **Cache Environment Information**: Environment info rarely changes during runtime: + ```go + type App struct { + envInfo *application.EnvironmentInfo + } + + func (a *App) getEnvInfo() application.EnvironmentInfo { + if a.envInfo == nil { + info := a.app.Env.Info() + a.envInfo = &info + } + return *a.envInfo + } + ``` + +2. **Handle Theme Changes**: Listen for system theme changes: + ```go + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + updateTheme(app.Env.IsDarkMode()) + }) + ``` + +3. **Graceful File Manager Failures**: Always handle file manager errors: + ```go + func openFileManagerSafely(app *application.App, path string) { + err := app.Env.OpenFileManager(path, false) + if err != nil { + // Provide fallback or user notification + app.Logger.Warn("Could not open file manager", "path", path) + } + } + ``` + +4. **Platform-Specific Features**: Use environment info to enable platform features: + ```go + envInfo := app.Env.Info() + if envInfo.OS == "darwin" { + // Enable macOS-specific features + } + ``` + +## Complete Example + +Here's a complete example demonstrating environment management: + +```go +package main + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "Environment Demo", + }) + + // Setup application based on environment + setupForEnvironment(app) + + // Monitor theme changes + monitorThemeChanges(app) + + // Create menu with environment features + setupEnvironmentMenu(app) + + // Create main window + window := app.Window.New() + window.SetTitle("Environment Demo") + + err := app.Run() + if err != nil { + panic(err) + } +} + +func setupForEnvironment(app *application.App) { + envInfo := app.Env.Info() + + app.Logger.Info("Application environment", + "os", envInfo.OS, + "arch", envInfo.Arch, + "debug", envInfo.Debug, + "darkMode", app.Env.IsDarkMode(), + ) + + // Configure for platform + switch envInfo.OS { + case "darwin": + app.Logger.Info("Configuring for macOS") + // macOS-specific setup + case "windows": + app.Logger.Info("Configuring for Windows") + // Windows-specific setup + case "linux": + app.Logger.Info("Configuring for Linux") + // Linux-specific setup + } + + // Apply initial theme + if app.Env.IsDarkMode() { + applyDarkTheme(app) + } else { + applyLightTheme(app) + } +} + +func monitorThemeChanges(app *application.App) { + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + if app.Env.IsDarkMode() { + app.Logger.Info("System switched to dark mode") + applyDarkTheme(app) + } else { + app.Logger.Info("System switched to light mode") + applyLightTheme(app) + } + }) +} + +func setupEnvironmentMenu(app *application.App) { + menu := app.Menu.New() + + // Add platform-specific app menu + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // Tools menu + toolsMenu := menu.AddSubmenu("Tools") + + toolsMenu.Add("Show Environment Info").OnClick(func(ctx *application.Context) { + showEnvironmentInfo(app) + }) + + toolsMenu.Add("Open Downloads Folder").OnClick(func(ctx *application.Context) { + openDownloadsFolder(app) + }) + + toolsMenu.Add("Toggle Theme").OnClick(func(ctx *application.Context) { + // This would typically be handled by the system + // but shown here for demonstration + toggleTheme(app) + }) + + app.Menu.Set(menu) +} + +func showEnvironmentInfo(app *application.App) { + envInfo := app.Env.Info() + + message := fmt.Sprintf(`Environment Information: + +Operating System: %s +Architecture: %s +Debug Mode: %t +Dark Mode: %t + +Platform Details:`, + envInfo.OS, + envInfo.Arch, + envInfo.Debug, + app.Env.IsDarkMode()) + + for key, value := range envInfo.PlatformInfo { + message += fmt.Sprintf("\n%s: %v", key, value) + } + + if envInfo.OSInfo != nil { + message += fmt.Sprintf("\n\nOS Information:\nName: %s\nVersion: %s", + envInfo.OSInfo.Name, + envInfo.OSInfo.Version) + } + + dialog := app.Dialog.Info() + dialog.SetTitle("Environment Information") + dialog.SetMessage(message) + dialog.Show() +} + +func openDownloadsFolder(app *application.App) { + homeDir, err := os.UserHomeDir() + if err != nil { + app.Logger.Error("Could not get home directory", "error", err) + return + } + + downloadsDir := filepath.Join(homeDir, "Downloads") + err = app.Env.OpenFileManager(downloadsDir, false) + if err != nil { + app.Logger.Error("Could not open Downloads folder", "error", err) + + app.Dialog.Error(). + SetTitle("File Manager Error"). + SetMessage("Could not open Downloads folder"). + Show() + } +} + +func applyDarkTheme(app *application.App) { + app.Logger.Info("Applying dark theme") + // Emit theme change to frontend + app.Event.Emit("theme:apply", "dark") +} + +func applyLightTheme(app *application.App) { + app.Logger.Info("Applying light theme") + // Emit theme change to frontend + app.Event.Emit("theme:apply", "light") +} + +func toggleTheme(app *application.App) { + // This is just for demonstration + // Real theme changes should come from the system + currentlyDark := app.Env.IsDarkMode() + if currentlyDark { + applyLightTheme(app) + } else { + applyDarkTheme(app) + } +} +``` + +:::tip[Pro Tip] +Use environment information to provide platform-appropriate user experiences. For example, use Command key shortcuts on macOS and Control key shortcuts on Windows/Linux. +::: + +:::danger[Warning] +Environment information is generally stable during application runtime, but theme preferences can change. Always listen for theme change events to keep your UI synchronized. +::: \ No newline at end of file diff --git a/docs/src/content/docs/features/events/system.mdx b/docs/src/content/docs/features/events/system.mdx new file mode 100644 index 000000000..767780cac --- /dev/null +++ b/docs/src/content/docs/features/events/system.mdx @@ -0,0 +1,585 @@ +--- +title: Event System +description: Communicate between components with the event system +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Event System + +Wails provides a **unified event system** for pub/sub communication. Emit events from anywhere, listen from anywhere—Go to JavaScript, JavaScript to Go, window to window—enabling decoupled architecture with typed events and lifecycle hooks. + +## Quick Start + +**Go (emit):** + +```go +app.Event.Emit("user-logged-in", map[string]interface{}{ + "userId": 123, + "name": "Alice", +}) +``` + +**JavaScript (listen):** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On("user-logged-in", (event) => { + console.log(`User ${event.data.name} logged in`) +}) +``` + +**That's it!** Cross-language pub/sub. + +## Event Types + +### Custom Events + +Your application-specific events: + +```go +// Emit from Go +app.Event.Emit("order-created", order) +app.Event.Emit("payment-processed", payment) +app.Event.Emit("notification", message) +``` + +```javascript +// Listen in JavaScript +Events.On("order-created", handleOrder) +Events.On("payment-processed", handlePayment) +Events.On("notification", showNotification) +``` + +### System Events + +Built-in OS and application events: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Theme changes +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + if e.Context().IsDarkMode() { + app.Logger.Info("Dark mode enabled") + } +}) + +// Application lifecycle +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application started") +}) +``` + +### Window Events + +Window-specific events: + +```go +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + app.Logger.Info("Window closing") +}) +``` + +## Emitting Events + +### From Go + +**Basic emit:** + +```go +app.Event.Emit("event-name", data) +``` + +**With different data types:** + +```go +// String +app.Event.Emit("message", "Hello") + +// Number +app.Event.Emit("count", 42) + +// Struct +app.Event.Emit("user", User{ID: 1, Name: "Alice"}) + +// Map +app.Event.Emit("config", map[string]interface{}{ + "theme": "dark", + "fontSize": 14, +}) + +// Array +app.Event.Emit("items", []string{"a", "b", "c"}) +``` + +**To specific window:** + +```go +window.EmitEvent("window-specific-event", data) +``` + +### From JavaScript + +```javascript +import { Events } from '@wailsio/runtime' + +// Emit to Go +Events.Emit("button-clicked", { buttonId: "submit" }) + +// Emit to all windows +Events.Emit("broadcast-message", "Hello everyone") +``` + +## Listening to Events + +### In Go + +**Application events:** + +```go +app.Event.On("custom-event", func(e *application.CustomEvent) { + data := e.Data + // Handle event +}) +``` + +**With type assertion:** + +```go +app.Event.On("user-updated", func(e *application.CustomEvent) { + user := e.Data.(User) + app.Logger.Info("User updated", "name", user.Name) +}) +``` + +**Multiple handlers:** + +```go +// All handlers will be called +app.Event.On("order-created", logOrder) +app.Event.On("order-created", sendEmail) +app.Event.On("order-created", updateInventory) +``` + +### In JavaScript + +**Basic listener:** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On("event-name", (event) => { + console.log("Event received:", event.data) +}) +``` + +**With cleanup:** + +```javascript +const unsubscribe = Events.On("event-name", handleEvent) + +// Later, stop listening +unsubscribe() +``` + +**Multiple handlers:** + +```javascript +Events.On("data-updated", updateUI) +Events.On("data-updated", saveToCache) +Events.On("data-updated", logChange) +``` + +## System Events + +### Application Events + +**Common events (cross-platform):** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Application started +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("App started") +}) + +// Theme changed +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + isDark := e.Context().IsDarkMode() + app.Event.Emit("theme-changed", isDark) +}) + +// File opened +app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) { + filePath := e.Context().Filename() + openFile(filePath) +}) +``` + +**Platform-specific events:** + + + + ```go + // Application became active + app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) { + app.Logger.Info("App became active") + }) + + // Application will terminate + app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) { + cleanup() + }) + ``` + + + + ```go + // Power status changed + app.Event.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) { + app.Logger.Info("Power status changed") + }) + + // System suspending + app.Event.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) { + saveState() + }) + ``` + + + + ```go + // Application startup + app.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) { + app.Logger.Info("App starting") + }) + + // Theme changed + app.Event.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) { + updateTheme() + }) + ``` + + + +### Window Events + +**Common window events:** + +```go +// Window focus +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +// Window lost focus +window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window lost focus") +}) + +// Window closing - use a hook to cancel if needed +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent close + } +}) + +// Window closing - use a listener for cleanup (cannot cancel) +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + cleanup() +}) +``` + +## Event Hooks + +Hooks run **before** standard listeners and can **cancel** events: + +```go +// Hook - runs first, can cancel +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + result := showConfirmdialog("Unsaved changes. Close anyway?") + if result != "yes" { + e.Cancel() // Prevent window close + } + } +}) + +// Standard listener - runs after hooks +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + app.Logger.Info("Window closing") +}) +``` + +**Key differences:** + +| Feature | Hooks | Standard Listeners | +|---------|-------|-------------------| +| Execution order | First, in registration order | After hooks, no guaranteed order | +| Blocking | Synchronous, blocks next hook | Asynchronous, non-blocking | +| Can cancel | Yes | No (already propagated) | +| Use case | Control flow, validation | Logging, side effects | + +## Event Patterns + +### Pub/Sub Pattern + +```go +// Publisher (service) +type OrderService struct { + app *application.App +} + +func (o *OrderService) CreateOrder(items []Item) (*Order, error) { + order := &Order{Items: items} + + if err := o.saveOrder(order); err != nil { + return nil, err + } + + // Publish event + o.app.Event.Emit("order-created", order) + + return order, nil +} + +// Subscribers +app.Event.On("order-created", func(e *application.CustomEvent) { + order := e.Data.(*Order) + sendConfirmationEmail(order) +}) + +app.Event.On("order-created", func(e *application.CustomEvent) { + order := e.Data.(*Order) + updateInventory(order) +}) + +app.Event.On("order-created", func(e *application.CustomEvent) { + order := e.Data.(*Order) + logOrder(order) +}) +``` + +### Request/Response Pattern + +```javascript +// Frontend requests data +Events.Emit("get-user-data", { userId: 123 }) +``` + +```go +// Backend responds +app.Event.On("get-user-data", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + userId := int(data["userId"].(float64)) + + user := getUserFromDB(userId) + + // Send response + app.Event.Emit("user-data-response", user) +}) +``` + +```javascript +// Frontend receives response +Events.On("user-data-response", (event) => { + displayUser(event.data) +}) +``` + +**Note:** For request/response, **bindings are better**. Use events for notifications. + +### Broadcast Pattern + +```go +// Broadcast to all windows +app.Event.Emit("global-notification", "System update available") +``` + +```javascript +// Each window handles it +Events.On("global-notification", (event) => { + showNotification(event.data) +}) +``` + +### Event Aggregation + +```go +type EventAggregator struct { + events []Event + mu sync.Mutex +} + +func (ea *EventAggregator) Add(event Event) { + ea.mu.Lock() + defer ea.mu.Unlock() + + ea.events = append(ea.events, event) + + // Emit batch every 100 events + if len(ea.events) >= 100 { + app.Event.Emit("event-batch", ea.events) + ea.events = nil + } +} +``` + +## Complete Example + +**Go:** + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type NotificationService struct { + app *application.App +} + +func (n *NotificationService) Notify(message string) { + // Emit to all windows + n.app.Event.Emit("notification", map[string]interface{}{ + "message": message, + "timestamp": time.Now(), + }) +} + +func main() { + app := application.New(application.Options{ + Name: "Event Demo", + }) + + notifService := &NotificationService{app: app} + + // System events + app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + isDark := e.Context().IsDarkMode() + app.Event.Emit("theme-changed", isDark) + }) + + // Custom events from frontend + app.Event.On("user-action", func(e *application.CustomEvent) { + data := e.Data.(map[string]interface{}) + action := data["action"].(string) + + app.Logger.Info("User action", "action", action) + + // Respond + notifService.Notify("Action completed: " + action) + }) + + // Window events + window := app.Window.New() + + window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Event.Emit("window-focused", window.Name()) + }) + + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + // Confirm before close + app.Event.Emit("confirm-close", nil) + e.Cancel() // Wait for confirmation + }) + + app.Run() +} +``` + +**JavaScript:** + +```javascript +import { Events } from '@wailsio/runtime' + +// Listen for notifications +Events.On("notification", (event) => { + showNotification(event.data.message) +}) + +// Listen for theme changes +Events.On("theme-changed", (event) => { + document.body.classList.toggle('dark', event.data) +}) + +// Listen for window focus +Events.On("window-focused", (event) => { + console.log(`Window ${event.data} focused`) +}) + +// Handle close confirmation +Events.On("confirm-close", () => { + if (confirm("Close window?")) { + Events.Emit("close-confirmed", true) + } +}) + +// Emit user actions +document.getElementById('button').addEventListener('click', () => { + Events.Emit("user-action", { action: "button-clicked" }) +}) +``` + +## Best Practices + +### ✅ Do + +- **Use events for notifications** - One-way communication +- **Use bindings for requests** - Two-way communication +- **Keep event names consistent** - Use kebab-case +- **Document event data** - What fields are included? +- **Unsubscribe when done** - Prevent memory leaks +- **Use hooks for validation** - Control event flow + +### ❌ Don't + +- **Don't use events for RPC** - Use bindings instead +- **Don't emit too frequently** - Batch if needed +- **Don't block in handlers** - Keep them fast +- **Don't forget to unsubscribe** - Memory leaks +- **Don't use events for large data** - Use bindings +- **Don't create event loops** - A emits B, B emits A + +## Next Steps + + + + Create your own event types. + + [Learn More →](/features/events/custom) + + + + Common event patterns and best practices. + + [Learn More →](/features/events/patterns) + + + + Use bindings for request/response. + + [Learn More →](/features/bindings/methods) + + + + Handle window lifecycle events. + + [Learn More →](/features/windows/events) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events). diff --git a/docs/src/content/docs/features/keyboard/shortcuts.mdx b/docs/src/content/docs/features/keyboard/shortcuts.mdx new file mode 100644 index 000000000..2897a9f9b --- /dev/null +++ b/docs/src/content/docs/features/keyboard/shortcuts.mdx @@ -0,0 +1,434 @@ +--- +title: Keyboard Shortcuts +description: Register global keyboard shortcuts for quick access to functionality +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails provides a powerful key binding system that allows you to register global keyboard shortcuts that work across all windows in your application. This enables users to quickly access functionality without navigating through menus. + +## Accessing the Key Binding Manager + +The key binding manager is accessed through the `KeyBindings` property on your application instance: + +```go +app := application.New(application.Options{ + Name: "Keyboard Shortcuts Demo", +}) + +// Access the key binding manager +keyBindings := app.KeyBinding +``` + +## Adding Key Bindings + +### Basic Key Binding + +Register a simple keyboard shortcut: + +```go +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + // Handle save action + app.Logger.Info("Save shortcut triggered") + // Perform save operation... +}) +``` + +### Multiple Key Bindings + +Register multiple shortcuts for common operations: + +```go +// File operations +app.KeyBinding.Add("Ctrl+N", func(window application.Window) { + // New file + window.EmitEvent("file:new", nil) +}) + +app.KeyBinding.Add("Ctrl+O", func(window application.Window) { + // Open file + dialog := app.Dialog.OpenFile() + if file, err := dialog.PromptForSingleSelection(); err == nil { + window.EmitEvent("file:open", file) + } +}) + +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + // Save file + window.EmitEvent("file:save", nil) +}) + +// Edit operations +app.KeyBinding.Add("Ctrl+Z", func(window application.Window) { + // Undo + window.EmitEvent("edit:undo", nil) +}) + +app.KeyBinding.Add("Ctrl+Y", func(window application.Window) { + // Redo (Windows/Linux) + window.EmitEvent("edit:redo", nil) +}) + +app.KeyBinding.Add("Cmd+Shift+Z", func(window application.Window) { + // Redo (macOS) + window.EmitEvent("edit:redo", nil) +}) +``` + +## Key Binding Accelerators + +### Accelerator Format + +Key bindings use a standard accelerator format with modifiers and keys: + +```go +// Modifier keys +"Ctrl+S" // Control + S +"Cmd+S" // Command + S (macOS) +"Alt+F4" // Alt + F4 +"Shift+Ctrl+Z" // Shift + Control + Z + +// Function keys +"F1" // F1 key +"Ctrl+F5" // Control + F5 + +// Special keys +"Escape" // Escape key +"Enter" // Enter key +"Space" // Spacebar +"Tab" // Tab key +"Backspace" // Backspace key +"Delete" // Delete key + +// Arrow keys +"Up" // Up arrow +"Down" // Down arrow +"Left" // Left arrow +"Right" // Right arrow +``` + +### Platform-Specific Accelerators + +Handle platform differences for common shortcuts: + +```go +import "runtime" + +// Cross-platform save shortcut +if runtime.GOOS == "darwin" { + app.KeyBinding.Add("Cmd+S", saveHandler) +} else { + app.KeyBinding.Add("Ctrl+S", saveHandler) +} + +// Or register both +app.KeyBinding.Add("Ctrl+S", saveHandler) +app.KeyBinding.Add("Cmd+S", saveHandler) +``` + +## Managing Key Bindings + +### Removing Key Bindings + +Remove key bindings when they're no longer needed: + +```go +// Remove a specific key binding +app.KeyBinding.Remove("Ctrl+S") + +// Example: Temporary key binding for a modal +app.KeyBinding.Add("Escape", func(window application.Window) { + // Close modal + window.EmitEvent("modal:close", nil) + // Remove this temporary binding + app.KeyBinding.Remove("Escape") +}) +``` + +### Getting All Key Bindings + +Retrieve all registered key bindings: + +```go +allBindings := app.KeyBinding.GetAll() +for _, binding := range allBindings { + app.Logger.Info("Key binding", "accelerator", binding.Accelerator) +} +``` + +## Advanced Usage + +### Context-Aware Key Bindings + +Make key bindings context-aware by checking application state: + +```go +app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + // Check current application state + if isEditMode() { + // Save document + saveDocument() + } else if isInSettings() { + // Save settings + saveSettings() + } else { + app.Logger.Info("Save not available in current context") + } +}) +``` + +### Window-Specific Actions + +Key bindings receive the active window, allowing window-specific behavior: + +```go +app.KeyBinding.Add("F11", func(window application.Window) { + // Toggle fullscreen for the active window + window.ToggleFullscreen() +}) + +app.KeyBinding.Add("Ctrl+W", func(window application.Window) { + // Close the active window + window.Close() +}) +``` + +### Dynamic Key Binding Management + +Dynamically add and remove key bindings based on application state: + +```go +func enableEditMode() { + // Add edit-specific key bindings + app.KeyBinding.Add("Ctrl+B", func(window application.Window) { + window.EmitEvent("format:bold", nil) + }) + + app.KeyBinding.Add("Ctrl+I", func(window application.Window) { + window.EmitEvent("format:italic", nil) + }) + + app.KeyBinding.Add("Ctrl+U", func(window application.Window) { + window.EmitEvent("format:underline", nil) + }) +} + +func disableEditMode() { + // Remove edit-specific key bindings + app.KeyBinding.Remove("Ctrl+B") + app.KeyBinding.Remove("Ctrl+I") + app.KeyBinding.Remove("Ctrl+U") +} +``` + +## Platform Considerations + + + + + On macOS: + + - Use `Cmd` instead of `Ctrl` for standard shortcuts + - `Cmd+Q` is typically reserved for quitting the application + - `Cmd+H` hides the application + - `Cmd+M` minimizes windows + - Consider standard macOS keyboard shortcuts + + Common macOS patterns: + ```go + app.KeyBinding.Add("Cmd+N", newFileHandler) // New + app.KeyBinding.Add("Cmd+O", openFileHandler) // Open + app.KeyBinding.Add("Cmd+S", saveFileHandler) // Save + app.KeyBinding.Add("Cmd+Z", undoHandler) // Undo + app.KeyBinding.Add("Cmd+Shift+Z", redoHandler) // Redo + app.KeyBinding.Add("Cmd+C", copyHandler) // Copy + app.KeyBinding.Add("Cmd+V", pasteHandler) // Paste + ``` + + + + + + On Windows: + + - Use `Ctrl` for standard shortcuts + - `Alt+F4` closes applications + - `F1` typically opens help + - Consider Windows keyboard conventions + + Common Windows patterns: + ```go + app.KeyBinding.Add("Ctrl+N", newFileHandler) // New + app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open + app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save + app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo + app.KeyBinding.Add("Ctrl+Y", redoHandler) // Redo + app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy + app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste + app.KeyBinding.Add("F1", helpHandler) // Help + ``` + + + + + + On Linux: + + - Generally follows Windows conventions with `Ctrl` + - May vary by desktop environment + - Consider GNOME/KDE standard shortcuts + - Some desktop environments reserve certain shortcuts + + Common Linux patterns: + ```go + app.KeyBinding.Add("Ctrl+N", newFileHandler) // New + app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open + app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save + app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo + app.KeyBinding.Add("Ctrl+Shift+Z", redoHandler) // Redo + app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy + app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste + ``` + + + + +## Best Practices + +1. **Use Standard Shortcuts**: Follow platform conventions for common operations: + ```go + // Cross-platform save + if runtime.GOOS == "darwin" { + app.KeyBinding.Add("Cmd+S", saveHandler) + } else { + app.KeyBinding.Add("Ctrl+S", saveHandler) + } + ``` + +2. **Provide Visual Feedback**: Let users know when shortcuts are triggered: + ```go + app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + saveDocument() + // Show brief notification + window.EmitEvent("notification:show", "Document saved") + }) + ``` + +3. **Handle Conflicts**: Be careful not to override important system shortcuts: + ```go + // Avoid overriding system shortcuts like: + // Ctrl+Alt+Del (Windows) + // Cmd+Space (macOS Spotlight) + // Alt+Tab (Window switching) + ``` + +4. **Document Shortcuts**: Provide help or documentation for available shortcuts: + ```go + app.KeyBinding.Add("F1", func(window application.Window) { + // Show help dialog with available shortcuts + showKeyboardShortcutsHelp() + }) + ``` + +5. **Clean Up**: Remove temporary key bindings when they're no longer needed: + ```go + func enterEditMode() { + app.KeyBinding.Add("Escape", exitEditModeHandler) + } + + func exitEditModeHandler(window application.Window) { + exitEditMode() + app.KeyBinding.Remove("Escape") // Clean up temporary binding + } + ``` + +## Complete Example + +Here's a complete example of a text editor with keyboard shortcuts: + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "Text Editor with Shortcuts", + }) + + // File operations + if runtime.GOOS == "darwin" { + app.KeyBinding.Add("Cmd+N", func(window application.Window) { + window.EmitEvent("file:new", nil) + }) + app.KeyBinding.Add("Cmd+O", func(window application.Window) { + openFile(app, window) + }) + app.KeyBinding.Add("Cmd+S", func(window application.Window) { + window.EmitEvent("file:save", nil) + }) + } else { + app.KeyBinding.Add("Ctrl+N", func(window application.Window) { + window.EmitEvent("file:new", nil) + }) + app.KeyBinding.Add("Ctrl+O", func(window application.Window) { + openFile(app, window) + }) + app.KeyBinding.Add("Ctrl+S", func(window application.Window) { + window.EmitEvent("file:save", nil) + }) + } + + // View operations + app.KeyBinding.Add("F11", func(window application.Window) { + window.ToggleFullscreen() + }) + + app.KeyBinding.Add("F1", func(window application.Window) { + showKeyboardShortcuts(window) + }) + + // Create main window + window := app.Window.New() + window.SetTitle("Text Editor") + + err := app.Run() + if err != nil { + panic(err) + } +} + +func openFile(app *application.App, window application.Window) { + dialog := app.Dialog.OpenFile() + dialog.AddFilter("Text Files", "*.txt;*.md") + + if file, err := dialog.PromptForSingleSelection(); err == nil { + window.EmitEvent("file:open", file) + } +} + +func showKeyboardShortcuts(window application.Window) { + shortcuts := ` +Keyboard Shortcuts: +- Ctrl/Cmd+N: New file +- Ctrl/Cmd+O: Open file +- Ctrl/Cmd+S: Save file +- F11: Toggle fullscreen +- F1: Show this help +` + window.EmitEvent("help:show", shortcuts) +} +``` + +:::tip[Pro Tip] +Test your key bindings on all target platforms to ensure they work correctly and don't conflict with system shortcuts. +::: + +:::danger[Warning] +Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications. +::: \ No newline at end of file diff --git a/docs/src/content/docs/features/menus/application.mdx b/docs/src/content/docs/features/menus/application.mdx new file mode 100644 index 000000000..5f1e0d417 --- /dev/null +++ b/docs/src/content/docs/features/menus/application.mdx @@ -0,0 +1,644 @@ +--- +title: Application Menus +description: Create native menu bars for your desktop application +sidebar: + order: 1 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +## The Problem + +Professional desktop applications need menu bars—File, Edit, View, Help. But menus work differently on each platform: +- **macOS**: Global menu bar at top of screen +- **Windows**: Menu bar in window title bar +- **Linux**: Varies by desktop environment + +Building platform-appropriate menus manually is tedious and error-prone. + +## The Wails Solution + +Wails provides a **unified API** that creates platform-native menus automatically. Write once, get native behaviour on all platforms. + +{/* VISUAL PLACEHOLDER: Menu Bar Comparison +Description: Three screenshots side-by-side showing the same Wails menu on: +1. macOS - Global menu bar at top of screen with app name +2. Windows - Menu bar in window title bar +3. Linux (GNOME) - Menu bar in window +All showing identical menu structure: File, Edit, View, Tools, Help +Style: Clean screenshots with subtle borders, labels indicating platform +*/} + +## Quick Start + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My App", + }) + + // Create menu + menu := application.NewMenu() + + // Add standard menus (platform-appropriate) + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) // macOS only + } + menu.AddRole(application.FileMenu) + menu.AddRole(application.EditMenu) + menu.AddRole(application.WindowMenu) + menu.AddRole(application.HelpMenu) + + // Set the menu + app.Menu.Set(menu) + + // Create window and run + app.Window.New() + app.Run() +} +``` + +**That's it!** You now have platform-native menus with standard items. + +## Creating Menus + +### Basic Menu Creation + +```go +// Create a new menu +menu := application.NewMenu() + +// Add a top-level menu +fileMenu := menu.AddSubmenu("File") + +// Add menu items +fileMenu.Add("New").OnClick(func(ctx *application.Context) { + // Handle New +}) + +fileMenu.Add("Open").OnClick(func(ctx *application.Context) { + // Handle Open +}) + +fileMenu.AddSeparator() + +fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) +``` + +### Setting the Menu + +Platform-specific API: + + + + **Global menu bar** (one per application): + + ```go + app.Menu.Set(menu) + ``` + + The menu appears at the top of the screen and persists even when all windows are closed. + + + + **Per-window menu bar**: + + ```go + window.SetMenu(menu) + ``` + + Each window can have its own menu. The menu appears in the window's title bar. + + + + **Per-window menu bar** (usually): + + ```go + window.SetMenu(menu) + ``` + + Behaviour varies by desktop environment. Some (like Unity) support global menus. + + + +**Cross-platform helper:** + +```go +func setMenuForPlatform(app *application.App, window application.Window, menu *application.Menu) { + if runtime.GOOS == "darwin" { + app.Menu.Set(menu) + } else { + window.SetMenu(menu) + } +} +``` + +## Menu Roles + +Wails provides **predefined menu roles** that create platform-appropriate menu structures automatically. + +### Available Roles + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | **macOS only** | +| `FileMenu` | File menu with Close Window (macOS) or Quit (Windows/Linux) | All platforms | +| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste, etc.) | All platforms | +| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms | +| `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms | +| `ServicesMenu` | macOS Services submenu | **macOS only** | +| `SpeechMenu` | Speech menu with Start/Stop Speaking | **macOS only** | +| `HelpMenu` | Help and information | All platforms | + +### Using Roles + +```go +menu := application.NewMenu() + +// macOS: Add application menu +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) +} + +// All platforms: Add standard menus +menu.AddRole(application.FileMenu) +menu.AddRole(application.EditMenu) +menu.AddRole(application.WindowMenu) +menu.AddRole(application.HelpMenu) +``` + +**What you get:** + + + + **AppMenu** (with app name): + - About [App Name] + - --- + - Services + - --- + - Hide [App Name] (⌘H) + - Hide Others (⌥⌘H) + - Unhide + - --- + - Quit [App Name] (⌘Q) + + **FileMenu**: + - Close Window (⌘W) + + **EditMenu**: + - Undo (⌘Z) + - Redo (⇧⌘Z) + - --- + - Cut (⌘X) + - Copy (⌘C) + - Paste (⌘V) + - Paste and Match Style + - Delete + - Select All (⌘A) + - --- + - Speech + + **WindowMenu**: + - Minimise (⌘M) + - Zoom + - --- + - Bring All to Front + + **HelpMenu**: + - Learn More + + + + **FileMenu**: + - Quit + + **EditMenu**: + - Undo (Ctrl+Z) + - Redo (Ctrl+Y) + - --- + - Cut (Ctrl+X) + - Copy (Ctrl+C) + - Paste (Ctrl+V) + - Delete + - --- + - Select All (Ctrl+A) + + **WindowMenu**: + - Minimise + - Zoom + - Close Window + + **HelpMenu**: + - Learn More + + + + Similar to Windows, but keyboard shortcuts may vary by desktop environment. + + + +### Customising Role Menus + +Add items to role menus by finding the submenu after adding the role: + +```go +menu.AddRole(application.FileMenu) + +// Find the File submenu and add custom items +fileMenu := menu.FindByLabel("File").GetSubmenu() +fileMenu.Add("Import...").OnClick(handleImport) +fileMenu.Add("Export...").OnClick(handleExport) +``` + +Note: `AddRole()` returns the receiver `*Menu` (for chaining), not the role submenu. Use `FindByLabel()` to access the submenu. + +## Custom Menus + +Create your own menus for application-specific features: + +```go +// Add a custom top-level menu +toolsMenu := menu.AddSubmenu("Tools") + +// Add items +toolsMenu.Add("Settings").OnClick(func(ctx *application.Context) { + showSettingsWindow() +}) + +toolsMenu.AddSeparator() + +// Add checkbox +toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) { + isDark := ctx.ClickedMenuItem().Checked() + setTheme(isDark) +}) + +// Add radio group +toolsMenu.AddRadio("Small", true).OnClick(handleFontSize) +toolsMenu.AddRadio("Medium", false).OnClick(handleFontSize) +toolsMenu.AddRadio("Large", false).OnClick(handleFontSize) + +// Add submenu +advancedMenu := toolsMenu.AddSubmenu("Advanced") +advancedMenu.Add("Configure...").OnClick(showAdvancedSettings) +``` + +**For more menu item types**, see [Menu Reference](/features/menus/reference). + +## Dynamic Menus + +Update menus based on application state: + +### Enable/Disable Items + +```go +var saveMenuItem *application.MenuItem + +func createMenu() { + menu := application.NewMenu() + fileMenu := menu.AddSubmenu("File") + + saveMenuItem = fileMenu.Add("Save") + saveMenuItem.SetEnabled(false) // Initially disabled + saveMenuItem.OnClick(handleSave) + + app.Menu.Set(menu) +} + +func onDocumentChanged() { + saveMenuItem.SetEnabled(hasUnsavedChanges()) + menu.Update() // Important! +} +``` + +:::caution[Always Call menu.Update()] +After changing menu state (enable/disable, label, checked), **always call `menu.Update()`**. This is especially critical on Windows where menus are reconstructed. + +See [Menu Reference](/features/menus/reference#enabled-state) for details. +::: + +### Change Labels + +```go +updateMenuItem := menu.Add("Check for Updates") + +updateMenuItem.OnClick(func(ctx *application.Context) { + updateMenuItem.SetLabel("Checking...") + menu.Update() + + checkForUpdates() + + updateMenuItem.SetLabel("Check for Updates") + menu.Update() +}) +``` + +### Rebuild Menus + +For major changes, rebuild the entire menu: + +```go +func rebuildFileMenu() { + menu := application.NewMenu() + fileMenu := menu.AddSubmenu("File") + + fileMenu.Add("New").OnClick(handleNew) + fileMenu.Add("Open").OnClick(handleOpen) + + // Add recent files dynamically + if hasRecentFiles() { + recentMenu := fileMenu.AddSubmenu("Open Recent") + for _, file := range getRecentFiles() { + filePath := file // Capture for closure + recentMenu.Add(filepath.Base(file)).OnClick(func(ctx *application.Context) { + openFile(filePath) + }) + } + recentMenu.AddSeparator() + recentMenu.Add("Clear Recent").OnClick(clearRecentFiles) + } + + fileMenu.AddSeparator() + fileMenu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + app.Menu.Set(menu) +} +``` + +## Window Control from Menus + +Menu items can control windows: + +```go +viewMenu := menu.AddSubmenu("View") + +// Toggle fullscreen +viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) { + window, _ := app.Window.GetByName("main") + window.ToggleFullscreen() +}) + +// Zoom controls +viewMenu.Add("Zoom In").SetAccelerator("CmdOrCtrl++").OnClick(func(ctx *application.Context) { + // Increase zoom +}) + +viewMenu.Add("Zoom Out").SetAccelerator("CmdOrCtrl+-").OnClick(func(ctx *application.Context) { + // Decrease zoom +}) + +viewMenu.Add("Reset Zoom").SetAccelerator("CmdOrCtrl+0").OnClick(func(ctx *application.Context) { + // Reset zoom +}) +``` + +**Get the active window:** + +```go +menuItem.OnClick(func(ctx *application.Context) { + window := app.Window.Current() + // Use window +}) +``` + +## Platform-Specific Considerations + +### macOS + +**Menu bar behaviour:** +- Appears at **top of screen** (global) +- Persists when all windows closed +- First menu is **always the application menu** +- Use `menu.AddRole(application.AppMenu)` for standard items + +**Standard locations:** +- **About**: Application menu +- **Quit**: Application menu (⌘Q) +- **Help**: Help menu + +**Example:** + +```go +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) // Adds About, Services, Hide/Show, Quit + + // Don't add Quit to File menu on macOS + // Don't add About to Help menu on macOS +} +``` + +### Windows + +**Menu bar behaviour:** +- Appears in **window title bar** +- Each window has its own menu +- No application menu + +**Standard locations:** +- **Exit**: File menu (Alt+F4) +- **Settings**: Tools or Edit menu +- **About**: Help menu + +**Example:** + +```go +if runtime.GOOS == "windows" { + menu.AddRole(application.FileMenu) + // Exit is added automatically + + menu.AddRole(application.HelpMenu) + // About is added automatically +} +``` + +### Linux + +**Menu bar behaviour:** +- Usually per-window (like Windows) +- Some DEs support global menus (Unity, GNOME with extension) +- Appearance varies by desktop environment + +**Best practice:** Follow Windows conventions, test on target DEs. + +## Complete Example + +Here's a production-ready menu structure: + +```go +package main + +import ( + "runtime" + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + // Create and set menu + createMenu(app) + + // Create main window + app.Window.New() + + app.Run() +} + +func createMenu(app *application.App) { + menu := application.NewMenu() + + // Platform-specific application menu (macOS only) + if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) + } + + // File menu + menu.AddRole(application.FileMenu) + fileMenu := menu.FindByLabel("File").GetSubmenu() + fileMenu.Add("Import...").SetAccelerator("CmdOrCtrl+I").OnClick(handleImport) + fileMenu.Add("Export...").SetAccelerator("CmdOrCtrl+E").OnClick(handleExport) + + // Edit menu + menu.AddRole(application.EditMenu) + + // View menu + viewMenu := menu.AddSubmenu("View") + viewMenu.Add("Toggle Fullscreen").SetAccelerator("F11").OnClick(toggleFullscreen) + viewMenu.AddSeparator() + viewMenu.AddCheckbox("Show Sidebar", true).OnClick(toggleSidebar) + viewMenu.AddCheckbox("Show Toolbar", true).OnClick(toggleToolbar) + + // Tools menu + toolsMenu := menu.AddSubmenu("Tools") + + // Settings location varies by platform convention + if runtime.GOOS != "darwin" { + toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings) + } + + toolsMenu.AddSeparator() + toolsMenu.AddCheckbox("Dark Mode", false).OnClick(toggleDarkMode) + + // Window menu + menu.AddRole(application.WindowMenu) + + // Help menu + menu.AddRole(application.HelpMenu) + helpMenu := menu.FindByLabel("Help").GetSubmenu() + helpMenu.Add("Documentation").OnClick(openDocumentation) + + // About location varies by platform + if runtime.GOOS == "darwin" { + // On macOS, About is in Application menu (added by AppMenu role) + } else { + helpMenu.AddSeparator() + helpMenu.Add("About").OnClick(showAbout) + } + + // Set the menu + app.Menu.Set(menu) +} + +func handleImport(ctx *application.Context) { + // Implementation +} + +func handleExport(ctx *application.Context) { + // Implementation +} + +func toggleFullscreen(ctx *application.Context) { + // Use a reference to the window captured during menu setup +} + +func toggleSidebar(ctx *application.Context) { + // Implementation +} + +func toggleToolbar(ctx *application.Context) { + // Implementation +} + +func showSettings(ctx *application.Context) { + // Implementation +} + +func toggleDarkMode(ctx *application.Context) { + isDark := ctx.ClickedMenuItem().Checked() + // Apply theme +} + +func openDocumentation(ctx *application.Context) { + // Open browser +} + +func showAbout(ctx *application.Context) { + // Show about dialog +} +``` + +## Best Practices + +### ✅ Do + +- **Use menu roles** for standard menus (File, Edit, etc.) +- **Follow platform conventions** for menu structure +- **Add keyboard shortcuts** to common actions +- **Call menu.Update()** after changing menu state +- **Test on all platforms** - behaviour varies +- **Keep menus shallow** - 2-3 levels maximum +- **Use clear labels** - "Save Project" not "Save" + +### ❌ Don't + +- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl` +- **Don't put Quit in File menu on macOS** - It's in Application menu +- **Don't put About in Help menu on macOS** - It's in Application menu +- **Don't forget menu.Update()** - Menus won't work properly +- **Don't nest too deeply** - Users get lost +- **Don't use jargon** - Keep labels user-friendly + +## Next Steps + + + + Complete reference for menu item types and properties. + + [Learn More →](/features/menus/reference) + + + + Create right-click context menus. + + [Learn More →](/features/menus/context) + + + + Add system tray/menu bar integration. + + [Learn More →](/features/menus/systray) + + + + Common menu patterns and best practices. + + [Learn More →](/guides/patterns/menus) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu). diff --git a/docs/src/content/docs/features/menus/context.mdx b/docs/src/content/docs/features/menus/context.mdx new file mode 100644 index 000000000..fd00eee29 --- /dev/null +++ b/docs/src/content/docs/features/menus/context.mdx @@ -0,0 +1,719 @@ +--- +title: Context Menus +description: Create right-click context menus for your application +sidebar: + order: 2 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +## The Problem + +Users expect right-click menus with context-specific actions. Different elements need different menus: +- **Text**: Cut, Copy, Paste +- **Images**: Save, Copy, Open +- **Custom elements**: Application-specific actions + +Building context menus manually means handling mouse events, positioning, and platform differences. + +## The Wails Solution + +Wails provides **declarative context menus** using CSS properties. Associate menus with HTML elements, pass data, and handle clicks—all with native platform behaviour. + +## Quick Start + +**Go code:** + +```go +// Create and register context menu (auto-registered by name) +contextMenu := application.NewContextMenu("editor-menu") +contextMenu.Add("Cut").OnClick(handleCut) +contextMenu.Add("Copy").OnClick(handleCopy) +contextMenu.Add("Paste").OnClick(handlePaste) +contextMenu.Update() +``` + +**HTML:** + +```html + +``` + +**That's it!** Right-clicking the textarea shows your custom menu. + +## Creating Context Menus + +### Basic Context Menu + +```go +// Create menu (auto-registered by the name passed to NewContextMenu) +contextMenu := application.NewContextMenu("text-menu") + +// Add items +contextMenu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(func(ctx *application.Context) { + // Handle cut +}) + +contextMenu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(func(ctx *application.Context) { + // Handle copy +}) + +contextMenu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(func(ctx *application.Context) { + // Handle paste +}) + +contextMenu.Update() +``` + +**Menu name:** Must be unique. Used to associate menu with HTML elements. + +### With Submenus + +```go +contextMenu := application.NewContextMenu("image-menu") + +// Add regular items +contextMenu.Add("Open").OnClick(handleOpen) +contextMenu.Add("Delete").OnClick(handleDelete) + +contextMenu.AddSeparator() + +// Add submenu +exportMenu := contextMenu.AddSubmenu("Export As") +exportMenu.Add("PNG").OnClick(exportPNG) +exportMenu.Add("JPEG").OnClick(exportJPEG) +exportMenu.Add("SVG").OnClick(exportSVG) + +contextMenu.Update() +``` + +### With Checkboxes and Radio Groups + +```go +contextMenu := application.NewContextMenu("view-menu") + +// Checkbox +contextMenu.AddCheckbox("Show Grid", true).OnClick(func(ctx *application.Context) { + showGrid := ctx.ClickedMenuItem().Checked() + // Toggle grid +}) + +contextMenu.AddSeparator() + +// Radio group +contextMenu.AddRadio("Small", false).OnClick(handleSize) +contextMenu.AddRadio("Medium", true).OnClick(handleSize) +contextMenu.AddRadio("Large", false).OnClick(handleSize) + +contextMenu.Update() +``` + +**For all menu item types**, see [Menu Reference](/features/menus/reference). + +## Associating with HTML Elements + +Use CSS custom properties to attach context menus: + +### Basic Association + +```html +
+ Right-click me! +
+``` + +**CSS property:** `--custom-contextmenu: ` + +### With Context Data + +Pass data from HTML to Go: + +```html +
+ Right-click this file +
+``` + +**Go handler:** + +```go +contextMenu := application.NewContextMenu("file-menu") +contextMenu.Add("Open").OnClick(func(ctx *application.Context) { + fileID := ctx.ContextMenuData() // "file-123" + openFile(fileID) +}) +contextMenu.Update() +``` + +**CSS properties:** +- `--custom-contextmenu: ` - Which menu to show +- `--custom-contextmenu-data: ` - Data to pass to handlers + +### Dynamic Data + +Generate data dynamically in JavaScript: + +```html +
+ File.txt +
+ + +``` + +### Multiple Elements, Same Menu + +```html +
+ Document.pdf +
+ +
+ Image.png +
+ +
+ Video.mp4 +
+``` + +**One menu, different data for each element.** + +## Context Data + +### Accessing Context Data + +```go +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + data := ctx.ContextMenuData() // Get data from HTML + + // Use the data + processItem(data) +}) +``` + +**Data type:** Always `string`. Parse as needed. + +### Passing Complex Data + +Use JSON for complex data: + +```html +
+ Image.png +
+``` + +**Go handler:** + +```go +import "encoding/json" + +type ItemData struct { + ID int `json:"id"` + Type string `json:"type"` +} + +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + dataStr := ctx.ContextMenuData() + + var data ItemData + if err := json.Unmarshal([]byte(dataStr), &data); err != nil { + log.Printf("Invalid data: %v", err) + return + } + + processItem(data.ID, data.Type) +}) +``` + +:::caution[Security] +**Always validate context data** from the frontend. Users can manipulate CSS properties, so treat data as untrusted input. +::: + +### Validation Example + +```go +contextMenu.Add("Delete").OnClick(func(ctx *application.Context) { + fileID := ctx.ContextMenuData() + + // Validate + if !isValidFileID(fileID) { + log.Printf("Invalid file ID: %s", fileID) + return + } + + // Check permissions + if !canDeleteFile(fileID) { + showError("Permission denied") + return + } + + // Safe to proceed + deleteFile(fileID) +}) +``` + +## Default Context Menu + +The WebView provides a built-in context menu for standard operations (copy, paste, inspect). Control it with `--default-contextmenu`: + +### Hide Default Menu + +```html +
+ No default menu here +
+``` + +**Use case:** Custom UI elements where default menu doesn't make sense. + +### Show Default Menu + +```html +
+ Default menu always shown +
+``` + +**Use case:** Text areas, input fields, editable content. + +### Auto (Smart) Mode + +```html +
+ Smart context menu +
+``` + +**Default behaviour.** Shows default menu when: +- Text is selected +- In text input fields +- In editable content (`contenteditable`) + +Hides default menu otherwise. + +### Combining Custom and Default + +```html + + +``` + +**Behaviour:** +1. Custom menu shows first +2. If custom menu is empty or not found, default menu shows +3. Both can coexist (platform-dependent) + +## Dynamic Context Menus + +Update menus based on application state: + +### Enable/Disable Items + +```go +var cutMenuItem *application.MenuItem +var copyMenuItem *application.MenuItem + +func createContextMenu() { + contextMenu := application.NewContextMenu("editor-menu") + + cutMenuItem = contextMenu.Add("Cut") + cutMenuItem.SetEnabled(false) // Initially disabled + cutMenuItem.OnClick(handleCut) + + copyMenuItem = contextMenu.Add("Copy") + copyMenuItem.SetEnabled(false) + copyMenuItem.OnClick(handleCopy) + + contextMenu.Update() +} + +func onSelectionChanged(hasSelection bool) { + cutMenuItem.SetEnabled(hasSelection) + copyMenuItem.SetEnabled(hasSelection) + contextMenu.Update() // Important! +} +``` + +:::caution[Always Call Update()] +After changing menu state, **call `contextMenu.Update()`**. This is critical on Windows. + +See [Menu Reference](/features/menus/reference#enabled-state) for details. +::: + +### Change Labels + +```go +playMenuItem := contextMenu.Add("Play") + +playMenuItem.OnClick(func(ctx *application.Context) { + if isPlaying { + playMenuItem.SetLabel("Pause") + } else { + playMenuItem.SetLabel("Play") + } + contextMenu.Update() +}) +``` + +### Rebuild Menus + +For major changes, rebuild the entire menu: + +```go +func rebuildContextMenu(fileType string) { + contextMenu := application.NewContextMenu("file-menu") + + // Common items + contextMenu.Add("Open").OnClick(handleOpen) + contextMenu.Add("Delete").OnClick(handleDelete) + + contextMenu.AddSeparator() + + // Type-specific items + switch fileType { + case "image": + contextMenu.Add("Edit Image").OnClick(editImage) + contextMenu.Add("Set as Wallpaper").OnClick(setWallpaper) + case "video": + contextMenu.Add("Play").OnClick(playVideo) + contextMenu.Add("Extract Audio").OnClick(extractAudio) + case "document": + contextMenu.Add("Print").OnClick(printDocument) + contextMenu.Add("Export PDF").OnClick(exportPDF) + } + + contextMenu.Update() +} +``` + +## Platform Behaviour + +Context menus are **platform-native**: + + + + **Native macOS context menus:** + - System animations and transitions + - Right-click = Control+Click (automatic) + - Adapts to system appearance (light/dark) + - Standard text operations in default menu + - Native scrolling for long menus + + **macOS conventions:** + - Use sentence case for menu items + - Use ellipsis (...) for items that open dialogs + - Common shortcuts: ⌘C (Copy), ⌘V (Paste) + + + + **Native Windows context menus:** + - Windows native style + - Follows Windows theme + - Standard Windows operations in default menu + - Touch and pen input support + + **Windows conventions:** + - Use title case for menu items + - Use ellipsis (...) for items that open dialogs + - Common shortcuts: Ctrl+C (Copy), Ctrl+V (Paste) + + + + **Desktop environment integration:** + - Adapts to desktop theme (GTK, Qt, etc.) + - Right-click behaviour follows system settings + - Default menu content varies by environment + - Positioning follows DE conventions + + **Linux considerations:** + - Test on target desktop environments + - GTK and Qt have different behaviours + - Some DEs customise context menus + + + +## Complete Example + +**Go code:** + +```go +package main + +import ( + "encoding/json" + "log" + "github.com/wailsapp/wails/v3/pkg/application" +) + +type FileData struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` +} + +func main() { + app := application.New(application.Options{ + Name: "Context Menu Demo", + }) + + // Create context menus (auto-registered by name) + createFileMenu() + createImageMenu() + createTextMenu() + + app.Window.New() + app.Run() +} + +func createFileMenu() { + menu := application.NewContextMenu("file-menu") + + menu.Add("Open").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + openFile(data.ID) + }) + + menu.Add("Rename").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + renameFile(data.ID) + }) + + menu.AddSeparator() + + menu.Add("Delete").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + deleteFile(data.ID) + }) + + menu.Update() +} + +func createImageMenu() { + menu := application.NewContextMenu("image-menu") + + menu.Add("View").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + viewImage(data.ID) + }) + + menu.Add("Edit").OnClick(func(ctx *application.Context) { + data := parseFileData(ctx.ContextMenuData()) + editImage(data.ID) + }) + + menu.AddSeparator() + + exportMenu := menu.AddSubmenu("Export As") + exportMenu.Add("PNG").OnClick(exportPNG) + exportMenu.Add("JPEG").OnClick(exportJPEG) + exportMenu.Add("WebP").OnClick(exportWebP) + + menu.Update() +} + +func createTextMenu() { + menu := application.NewContextMenu("text-menu") + + menu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(handleCut) + menu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(handleCopy) + menu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(handlePaste) + + menu.Update() +} + +func parseFileData(dataStr string) FileData { + var data FileData + if err := json.Unmarshal([]byte(dataStr), &data); err != nil { + log.Printf("Invalid file data: %v", err) + } + return data +} + +// Handler implementations... +func openFile(id string) { /* ... */ } +func renameFile(id string) { /* ... */ } +func deleteFile(id string) { /* ... */ } +func viewImage(id string) { /* ... */ } +func editImage(id string) { /* ... */ } +func exportPNG(ctx *application.Context) { /* ... */ } +func exportJPEG(ctx *application.Context) { /* ... */ } +func exportWebP(ctx *application.Context) { /* ... */ } +func handleCut(ctx *application.Context) { /* ... */ } +func handleCopy(ctx *application.Context) { /* ... */ } +func handlePaste(ctx *application.Context) { /* ... */ } +``` + +**HTML:** + +```html + + + + + + +

Files

+ + +
+ 📄 Report.pdf +
+ + +
+ 🖼️ Photo.jpg +
+ +

Text Editor

+ + + + +

No Context Menu

+ + +
+ Right-click here - no menu appears +
+ + +``` + +## Best Practices + +### ✅ Do + +- **Keep menus focused** - Only relevant actions for the element +- **Validate context data** - Treat as untrusted input +- **Use clear labels** - "Delete File" not "Delete" +- **Call menu.Update()** - After changing menu state +- **Test on all platforms** - Behaviour varies +- **Provide keyboard shortcuts** - For common actions +- **Group related items** - Use separators + +### ❌ Don't + +- **Don't trust context data** - Always validate +- **Don't make menus too long** - 7-10 items maximum +- **Don't forget menu.Update()** - Menus won't work properly +- **Don't nest too deeply** - 2 levels maximum +- **Don't use jargon** - Keep labels user-friendly +- **Don't block handlers** - Keep them fast + +## Troubleshooting + +### Context Menu Not Appearing + +**Possible causes:** +1. Menu ID mismatch +2. CSS property typo +3. Runtime not initialised + +**Solution:** + +```go +// Check menu is created and registered with the correct name +contextMenu := application.NewContextMenu("my-menu") +// ... add items ... +contextMenu.Update() +``` + +```html + +
+``` + +### Context Data Not Received + +**Possible causes:** +1. CSS property not set +2. Data contains special characters + +**Solution:** + +```html + +
+``` + +Or use JavaScript: + +```javascript +element.style.setProperty('--custom-contextmenu-data', JSON.stringify(data)) +``` + +### Menu Items Not Responding + +**Cause:** Forgot to call `menu.Update()` after enabling + +**Solution:** + +```go +menuItem.SetEnabled(true) +contextMenu.Update() // Add this! +``` + +## Next Steps + + + + Complete reference for menu item types and properties. + + [Learn More →](/features/menus/reference) + + + + Create application menu bars. + + [Learn More →](/features/menus/application) + + + + Add system tray/menu bar integration. + + [Learn More →](/features/menus/systray) + + + + Common menu patterns and best practices. + + [Learn More →](/guides/patterns/menus) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [context menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/contextmenus). diff --git a/docs/src/content/docs/features/menus/reference.mdx b/docs/src/content/docs/features/menus/reference.mdx new file mode 100644 index 000000000..b92495aa3 --- /dev/null +++ b/docs/src/content/docs/features/menus/reference.mdx @@ -0,0 +1,569 @@ +--- +title: Menu Reference +description: Complete reference for menu item types, properties, and methods +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Menu Reference + +Complete reference for menu item types, properties, and dynamic behaviour. Build professional, responsive menus with checkboxes, radio groups, separators, and dynamic updates. + +## Menu Item Types + +### Regular Menu Items + +The most common type—displays text and triggers an action: + +```go +menuItem := menu.Add("Click Me") +menuItem.OnClick(func(ctx *application.Context) { + fmt.Println("Menu item clicked!") +}) +``` + +**Use for:** Commands, actions, opening windows + +### Checkboxes + +Toggle-able menu items with checked/unchecked state: + +```go +checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked +checkbox.OnClick(func(ctx *application.Context) { + isChecked := ctx.ClickedMenuItem().Checked() + fmt.Printf("Feature is now: %v\n", isChecked) +}) +``` + +**Use for:** Boolean settings, feature toggles, view options + +**Important:** The checked state toggles automatically when clicked. + +### Radio Groups + +Mutually exclusive options—only one can be selected: + +```go +menu.AddRadio("Small", true) // true = initially selected +menu.AddRadio("Medium", false) +menu.AddRadio("Large", false) +``` + +**Use for:** Mutually exclusive choices (size, theme, mode) + +**How grouping works:** +- Adjacent radio items form a group automatically +- Selecting one deselects others in the group +- Separate groups with a separator or regular item + +**Example with multiple groups:** + +```go +// Group 1: Size +menu.AddRadio("Small", true) +menu.AddRadio("Medium", false) +menu.AddRadio("Large", false) + +menu.AddSeparator() + +// Group 2: Theme +menu.AddRadio("Light", true) +menu.AddRadio("Dark", false) +``` + +### Submenus + +Nested menu structures for organisation: + +```go +submenu := menu.AddSubmenu("More Options") +submenu.Add("Submenu Item 1").OnClick(func(ctx *application.Context) { + // Handle click +}) +submenu.Add("Submenu Item 2") +``` + +**Use for:** Grouping related items, reducing clutter + +**Nesting limit:** Most platforms support 2-3 levels. Avoid deeper nesting. + +### Separators + +Visual dividers between menu items: + +```go +menu.Add("Item 1") +menu.AddSeparator() +menu.Add("Item 2") +``` + +**Use for:** Grouping related items visually + +**Best practice:** Don't start or end menus with separators. + +## Menu Item Properties + +### Label + +The text displayed for the menu item: + +```go +menuItem := menu.Add("Initial Label") +menuItem.SetLabel("New Label") + +// Get current label +label := menuItem.Label() +``` + +**Dynamic labels:** + +```go +updateMenuItem := menu.Add("Check for Updates") +updateMenuItem.OnClick(func(ctx *application.Context) { + updateMenuItem.SetLabel("Checking...") + menu.Update() // Important on Windows! + + // Perform update check + checkForUpdates() + + updateMenuItem.SetLabel("Check for Updates") + menu.Update() +}) +``` + +### Enabled State + +Control whether the menu item can be interacted with: + +```go +menuItem := menu.Add("Save") +menuItem.SetEnabled(false) // Greyed out, can't click + +// Enable it later +menuItem.SetEnabled(true) +menu.Update() // Important: Call this after changing enabled state! + +// Check current state +isEnabled := menuItem.Enabled() +``` + +:::caution[Windows Menu Behaviour] +On Windows, menus need to be reconstructed when their state changes. **Always call `menu.Update()` after enabling/disabling menu items**, especially if the item was created whilst disabled. + +**Why:** Windows menus are rebuilt from scratch when updated. If you don't call `Update()`, click handlers won't fire properly. +::: + +**Example: Dynamic enable/disable** + +```go +var hasSelection bool + +cutMenuItem := menu.Add("Cut") +cutMenuItem.SetEnabled(false) // Initially disabled + +copyMenuItem := menu.Add("Copy") +copyMenuItem.SetEnabled(false) + +// When selection changes +func onSelectionChanged(selected bool) { + hasSelection = selected + cutMenuItem.SetEnabled(hasSelection) + copyMenuItem.SetEnabled(hasSelection) + menu.Update() // Critical on Windows! +} +``` + +**Common pattern: Enable on condition** + +```go +saveMenuItem := menu.Add("Save") + +func updateSaveMenuItem() { + canSave := hasUnsavedChanges() && !isSaving() + saveMenuItem.SetEnabled(canSave) + menu.Update() +} + +// Call whenever state changes +onDocumentChanged(func() { + updateSaveMenuItem() +}) +``` + +### Checked State + +For checkbox and radio items, control or query their checked state: + +```go +checkbox := menu.AddCheckbox("Feature", false) +checkbox.SetChecked(true) +menu.Update() + +// Query state +isChecked := checkbox.Checked() +``` + +**Auto-toggle:** Checkboxes toggle automatically when clicked. You don't need to call `SetChecked()` in the click handler. + +**Manual control:** + +```go +checkbox := menu.AddCheckbox("Auto-save", false) + +// Sync with external state +func syncAutoSave(enabled bool) { + checkbox.SetChecked(enabled) + menu.Update() +} +``` + +### Accelerators (Keyboard Shortcuts) + +Add keyboard shortcuts to menu items: + +```go +saveMenuItem := menu.Add("Save") +saveMenuItem.SetAccelerator("CmdOrCtrl+S") + +quitMenuItem := menu.Add("Quit") +quitMenuItem.SetAccelerator("CmdOrCtrl+Q") +``` + +**Accelerator format:** `Modifier+Key` joined by `+`. Modifiers are case-insensitive. + +**Modifiers:** +- `CmdOrCtrl` (aliases: `Cmd`, `Command`) - Cmd on macOS, Ctrl on Windows/Linux +- `Ctrl` - Control key on all platforms +- `Alt` (aliases: `Option`, `OptionOrAlt`) - Alt on Windows/Linux, Option on macOS +- `Shift` - Shift key on all platforms +- `Super` - Cmd on macOS, Windows key on Windows/Linux + +**Keys:** +- `A-Z`, `0-9` - Letter and number keys +- `F1-F35` - Function keys +- `Plus` - The `+` character (since `+` is the separator) +- Named keys: `Backspace`, `Tab`, `Return`, `Enter`, `Escape`, `Left`, `Right`, `Up`, `Down`, `Space`, `Delete`, `Home`, `End`, `Page Up`, `Page Down`, `NumLock` +- Any single printable character + +**Examples:** + +```go +"CmdOrCtrl+S" // Save +"CmdOrCtrl+Shift+S" // Save As +"CmdOrCtrl+W" // Close Window +"CmdOrCtrl+Q" // Quit +"F5" // Refresh +"CmdOrCtrl+," // Preferences (macOS convention) +"Alt+F4" // Close (Windows convention) +``` + +**Platform-specific accelerators:** + +```go +if runtime.GOOS == "darwin" { + prefsMenuItem.SetAccelerator("Cmd+,") +} else { + prefsMenuItem.SetAccelerator("Ctrl+P") +} +``` + +### Tooltip + +Add hover text to menu items (platform support varies): + +```go +menuItem := menu.Add("Advanced Options") +menuItem.SetTooltip("Configure advanced settings") +``` + +**Platform support:** +- **Windows:** ✅ Supported +- **macOS:** ❌ Not supported (tooltips not standard for menus) +- **Linux:** ⚠️ Varies by desktop environment + +### Hidden State + +Hide menu items without removing them: + +```go +debugMenuItem := menu.Add("Debug Mode") +debugMenuItem.SetHidden(true) // Hidden + +// Show in debug builds +if isDebugBuild { + debugMenuItem.SetHidden(false) + menu.Update() +} +``` + +**Use for:** Debug options, feature flags, conditional features + +## Event Handling + +### OnClick Handler + +Execute code when menu item is clicked: + +```go +menuItem := menu.Add("Click Me") +menuItem.OnClick(func(ctx *application.Context) { + // Handle click + fmt.Println("Clicked!") +}) +``` + +**Context methods:** +- `ctx.ClickedMenuItem() *MenuItem` - Returns the menu item that was clicked +- `ctx.IsChecked() bool` - Returns the checked state of the clicked item (for checkbox/radio items) +- `ctx.ContextMenuData() string` - Returns the context data string from the HTML element (context menus only) + +**Example: Access menu item in handler** + +```go +checkbox := menu.AddCheckbox("Feature", false) +checkbox.OnClick(func(ctx *application.Context) { + item := ctx.ClickedMenuItem() + isChecked := ctx.IsChecked() + fmt.Printf("Feature is now: %v\n", isChecked) +}) +``` + +### Multiple Handlers + +You can set multiple handlers (last one wins): + +```go +menuItem := menu.Add("Action") +menuItem.OnClick(func(ctx *application.Context) { + fmt.Println("First handler") +}) + +// This replaces the first handler +menuItem.OnClick(func(ctx *application.Context) { + fmt.Println("Second handler - this one runs") +}) +``` + +**Best practice:** Set handler once, use conditional logic inside if needed. + +## Dynamic Menus + +### Updating Menu Items + +**The golden rule:** Always call `menu.Update()` after changing menu state. + +```go +// ✅ Correct +menuItem.SetEnabled(true) +menu.Update() + +// ❌ Wrong (especially on Windows) +menuItem.SetEnabled(true) +// Forgot to call Update() - click handlers may not work! +``` + +**Why this matters:** +- **Windows:** Menus are reconstructed when updated +- **macOS/Linux:** Less critical but still recommended +- **Click handlers:** Won't fire properly without Update() + +### Rebuilding Menus + +For major changes, rebuild the entire menu: + +```go +func rebuildFileMenu() { + menu := app.Menu.New() + + menu.Add("New").OnClick(handleNew) + menu.Add("Open").OnClick(handleOpen) + + if hasRecentFiles() { + recentMenu := menu.AddSubmenu("Open Recent") + for _, file := range getRecentFiles() { + recentMenu.Add(file).OnClick(func(ctx *application.Context) { + openFile(file) + }) + } + } + + menu.AddSeparator() + menu.Add("Quit").OnClick(handleQuit) + + // Set the new menu + window.SetMenu(menu) +} +``` + +**When to rebuild:** +- Recent files list changes +- Plugin menus change +- Major state transitions + +**When to update:** +- Enable/disable items +- Change labels +- Toggle checkboxes + +### Context-Sensitive Menus + +Adjust menus based on application state: + +```go +func updateEditMenu() { + cutMenuItem.SetEnabled(hasSelection()) + copyMenuItem.SetEnabled(hasSelection()) + pasteMenuItem.SetEnabled(hasClipboardContent()) + undoMenuItem.SetEnabled(canUndo()) + redoMenuItem.SetEnabled(canRedo()) + menu.Update() +} + +// Call whenever state changes +onSelectionChanged(updateEditMenu) +onClipboardChanged(updateEditMenu) +onUndoStackChanged(updateEditMenu) +``` + +## Platform Differences + +### Menu Bar Location + +| Platform | Location | Notes | +|----------|----------|-------| +| **macOS** | Top of screen | Global menu bar | +| **Windows** | Top of window | Per-window menu | +| **Linux** | Top of window | Per-window (usually) | + +### Standard Menus + +**macOS:** +- Has "Application" menu (with app name) +- `AppMenu` role adds: About, Services, Hide/Show, Quit +- "Quit" in Application menu + +**Windows/Linux:** +- No Application menu +- "Settings" typically in Edit or Tools menu +- "Exit" in File menu + +**Example: Platform-appropriate structure** + +```go +menu := app.Menu.New() + +// macOS gets Application menu +if runtime.GOOS == "darwin" { + menu.AddRole(application.AppMenu) +} + +// File menu +fileMenu := menu.AddSubmenu("File") +fileMenu.Add("New") +fileMenu.Add("Open") + +// Settings location varies by platform convention +if runtime.GOOS != "darwin" { + // On Windows/Linux, add to Edit or Tools menu + editMenu := menu.AddSubmenu("Edit") + editMenu.Add("Settings") +} +``` + +### Accelerator Conventions + +**macOS:** +- `Cmd+` for most shortcuts +- `Cmd+,` for Preferences +- `Cmd+Q` for Quit + +**Windows:** +- `Ctrl+` for most shortcuts +- `Ctrl+P` or `Ctrl+,` for Preferences +- `Alt+F4` for Exit (or `Ctrl+Q`) + +**Linux:** +- Generally follows Windows conventions +- Desktop environment may override + +## Best Practices + +### ✅ Do + +- **Call menu.Update()** after changing menu state (especially on Windows) +- **Use radio groups** for mutually exclusive options +- **Use checkboxes** for toggleable features +- **Add accelerators** to common actions +- **Group related items** with separators +- **Test on all platforms** - behaviour varies + +### ❌ Don't + +- **Don't forget menu.Update()** - Click handlers won't work properly +- **Don't nest too deeply** - 2-3 levels maximum +- **Don't start/end with separators** - Looks unprofessional +- **Don't use tooltips on macOS** - Not supported +- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl` + +## Troubleshooting + +### Menu Items Not Responding + +**Symptom:** Click handlers don't fire + +**Cause:** Forgot to call `menu.Update()` after enabling item + +**Solution:** + +```go +menuItem.SetEnabled(true) +menu.Update() // Add this! +``` + +### Menu Items Greyed Out + +**Symptom:** Can't click menu items + +**Cause:** Items are disabled + +**Solution:** + +```go +menuItem.SetEnabled(true) +menu.Update() +``` + +### Accelerators Not Working + +**Symptom:** Keyboard shortcuts don't trigger menu items + +**Causes:** +1. Accelerator format incorrect +2. Conflict with system shortcuts +3. Window doesn't have focus + +**Solution:** + +```go +// Check format +menuItem.SetAccelerator("CmdOrCtrl+S") // ✅ Correct +menuItem.SetAccelerator("Ctrl+S") // ❌ Wrong (macOS uses Cmd) + +// Avoid conflicts +// ❌ Cmd+H (Hide Window on macOS - system shortcut) +// ✅ Cmd+Shift+H (Custom shortcut) +``` + +## Next Steps + +- [Application Menus](/features/menus/application) - Create application menu bars +- [Context Menus](/features/menus/context) - Right-click context menus +- [System Tray Menus](/features/menus/systray) - System tray/menu bar menus +- [Menu Patterns](/guides/patterns/menus) - Common menu patterns and best practices + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu). diff --git a/docs/src/content/docs/features/menus/systray.mdx b/docs/src/content/docs/features/menus/systray.mdx new file mode 100644 index 000000000..e6f3f15b6 --- /dev/null +++ b/docs/src/content/docs/features/menus/systray.mdx @@ -0,0 +1,709 @@ +--- +title: System Tray Menus +description: Add system tray (notification area) integration to your application +sidebar: + order: 3 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## System Tray Menus + +Wails provides **unified system tray APIs** that work across all platforms. Create tray icons with menus, attach windows, and handle clicks with native platform behaviour for background applications, services, and quick-access utilities. +{/* VISUAL PLACEHOLDER: System Tray Comparison +Description: Three screenshots showing the same Wails system tray icon on: +1. Windows - Notification area (bottom-right) +2. macOS - Menu bar (top-right) with label +3. Linux (GNOME) - Top bar +All showing the same icon and menu structure +Style: Clean screenshots with arrows pointing to tray icon, menu expanded +*/} + +## Quick Start + +```go +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/icon.png +var icon []byte + +func main() { + app := application.New(application.Options{ + Name: "Tray App", + }) + + // Create system tray + systray := app.SystemTray.New() + systray.SetIcon(icon) + systray.SetLabel("My App") + + // Add menu + menu := application.NewMenu() + menu.Add("Show").OnClick(func(ctx *application.Context) { + // Show main window + }) + menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + systray.SetMenu(menu) + + // Create hidden window + window := app.Window.New() + window.Hide() + + app.Run() +} +``` + +**Result:** System tray icon with menu on all platforms. + +## Creating a System Tray + +### Basic System Tray + +```go +// Create system tray +systray := app.SystemTray.New() + +// Set icon +systray.SetIcon(iconBytes) + +// Set label (macOS) / tooltip (Windows) +systray.SetLabel("My Application") +``` + +### With Icon + +Icons should be embedded: + +```go +import _ "embed" + +//go:embed assets/icon.png +var icon []byte + +//go:embed assets/icon-dark.png +var iconDark []byte + +func main() { + app := application.New(application.Options{ + Name: "My App", + }) + + systray := app.SystemTray.New() + systray.SetIcon(icon) + systray.SetDarkModeIcon(iconDark) // macOS dark mode + + app.Run() +} +``` + +**Icon requirements:** + +| Platform | Size | Format | Notes | +|----------|------|--------|-------| +| **Windows** | 16x16 or 32x32 | PNG, ICO | Notification area | +| **macOS** | 18x18 to 22x22 | PNG | Menu bar, template recommended | +| **Linux** | 22x22 to 48x48 | PNG, SVG | Varies by DE | + +### Template Icons (macOS) + +Template icons adapt to light/dark mode automatically: + +```go +systray.SetTemplateIcon(iconBytes) +``` + +**Template icon guidelines:** +- Use black and clear (transparent) colours only +- Black becomes white in dark mode +- Name file with `Template` suffix: `iconTemplate.png` +- [Design guide](https://bjango.com/articles/designingmenubarextras/) + +## Adding Menus + +System tray menus work like application menus: + +```go +menu := application.NewMenu() + +// Add items +menu.Add("Open").OnClick(func(ctx *application.Context) { + showMainWindow() +}) + +menu.AddSeparator() + +menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) { + enabled := ctx.ClickedMenuItem().Checked() + setStartAtLogin(enabled) +}) + +menu.AddSeparator() + +menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) + +// Set menu +systray.SetMenu(menu) +``` + +**For all menu item types**, see [Menu Reference](/features/menus/reference). + +## Attaching Windows + +Attach a window to the tray icon for automatic show/hide: + +```go +// Create window +window := app.Window.New() + +// Attach to tray +systray.AttachWindow(window) + +// Configure behaviour +systray.WindowOffset(10) // Pixels from tray icon +systray.WindowDebounce(200 * time.Millisecond) // Click debounce +``` + +**Behaviour:** +- Window starts hidden +- **Left-click tray icon** → Toggle window visibility +- **Right-click tray icon** → Show menu (if set) +- Window positioned near tray icon + +**Example: Popup window** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Quick Access", + Width: 300, + Height: 400, + Frameless: true, // No title bar + AlwaysOnTop: true, // Stay on top +}) + +systray.AttachWindow(window) +systray.WindowOffset(5) +``` + +## Click Handlers + +Handle tray icon clicks: + +```go +systray := app.SystemTray.New() + +// Left click +systray.OnClick(func() { + fmt.Println("Tray icon clicked") +}) + +// Right click +systray.OnRightClick(func() { + fmt.Println("Tray icon right-clicked") +}) + +// Double click +systray.OnDoubleClick(func() { + fmt.Println("Tray icon double-clicked") +}) + +// Mouse enter/leave +systray.OnMouseEnter(func() { + fmt.Println("Mouse entered tray icon") +}) + +systray.OnMouseLeave(func() { + fmt.Println("Mouse left tray icon") +}) +``` + +**Platform support:** + +| Event | Windows | macOS | Linux | +|-------|---------|-------|-------| +| OnClick | ✅ | ✅ | ✅ | +| OnRightClick | ✅ | ✅ | ✅ | +| OnDoubleClick | ✅ | ✅ | ⚠️ Varies | +| OnRightDoubleClick | ✅ | ✅ | ⚠️ Varies | +| OnMouseEnter | ✅ | ✅ | ⚠️ Varies | +| OnMouseLeave | ✅ | ✅ | ⚠️ Varies | + +## Dynamic Updates + +Update tray icon and menu dynamically: + +### Change Icon + +```go +var isActive bool + +func updateTrayIcon() { + if isActive { + systray.SetIcon(activeIcon) + systray.SetLabel("Active") + } else { + systray.SetIcon(inactiveIcon) + systray.SetLabel("Inactive") + } +} +``` + +### Update Menu + +```go +var isPaused bool + +pauseMenuItem := menu.Add("Pause") + +pauseMenuItem.OnClick(func(ctx *application.Context) { + isPaused = !isPaused + + if isPaused { + pauseMenuItem.SetLabel("Resume") + } else { + pauseMenuItem.SetLabel("Pause") + } + + menu.Update() // Important! +}) +``` + +:::caution[Always Call Update()] +After changing menu state, **call `menu.Update()`**. See [Menu Reference](/features/menus/reference#enabled-state). +::: + +### Rebuild Menu + +For major changes, rebuild the entire menu: + +```go +func rebuildTrayMenu(status string) { + menu := application.NewMenu() + + // Status-specific items + switch status { + case "syncing": + menu.Add("Syncing...").SetEnabled(false) + menu.Add("Pause Sync").OnClick(pauseSync) + case "synced": + menu.Add("Up to date ✓").SetEnabled(false) + menu.Add("Sync Now").OnClick(startSync) + case "error": + menu.Add("Sync Error").SetEnabled(false) + menu.Add("Retry").OnClick(retrySync) + } + + menu.AddSeparator() + menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() + }) + + systray.SetMenu(menu) +} +``` + +## Platform-Specific Features + + + + **Menu bar integration:** + + ```go + // Set label (appears next to icon) + systray.SetLabel("My App") + + // Use template icon (adapts to dark mode) + systray.SetTemplateIcon(iconBytes) + + // Set icon position + systray.SetIconPosition(application.NSImageLeading) + ``` + + **Icon positions:** + - `NSImageLeading` - Icon leading edge of label (default) + - `NSImageTrailing` - Icon trailing edge of label + - `NSImageLeft` - Icon left of label + - `NSImageRight` - Icon right of label + - `NSImageOnly` - Icon only, no label + - `NSImageNone` - Label only, no icon + - `NSImageAbove` - Icon above label + - `NSImageBelow` - Icon below label + - `NSImageOverlaps` - Icon overlaps label + + **Best practices:** + - Use template icons (black + transparent) + - Keep labels short (3-5 characters) + - 18x18 to 22x22 pixels for Retina displays + - Test in both light and dark modes + + + + **Notification area integration:** + + ```go + // Set tooltip (appears on hover) + systray.SetTooltip("My Application") + + // Or use SetLabel (same as tooltip on Windows) + systray.SetLabel("My Application") + + // Show/Hide functionality (fully functional) + systray.Show() // Show tray icon + systray.Hide() // Hide tray icon + ``` + + **Icon requirements:** + - 16x16 or 32x32 pixels + - PNG or ICO format + - Transparent background + + **Tooltip limits:** + - Maximum 127 UTF-16 characters + - Longer tooltips will be truncated + - Keep concise for best experience + + **Platform features:** + - Tray icon survives Windows Explorer restarts + - Show() and Hide() methods fully functional + - Proper lifecycle management + + **Best practices:** + - Use 32x32 for high-DPI displays + - Keep tooltips under 127 characters + - Test on different Windows versions + - Consider notification area overflow + - Use Show/Hide for conditional tray visibility + + + + **System tray integration:** + + Uses StatusNotifierItem specification (most modern DEs). + + ```go + systray.SetIcon(iconBytes) + systray.SetLabel("My App") + ``` + + **Desktop environment support:** + - **GNOME**: Top bar (with extension) + - **KDE Plasma**: System tray + - **XFCE**: Notification area + - **Others**: Varies + + **Best practices:** + - Use 22x22 or 24x24 pixels + - SVG icons scale better + - Test on target desktop environments + - Provide fallback for unsupported DEs + + + +## Complete Example + +Here's a production-ready system tray application: + +```go +package main + +import ( + _ "embed" + "fmt" + "time" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed assets/icon.png +var icon []byte + +//go:embed assets/icon-active.png +var iconActive []byte + +type TrayApp struct { + app *application.App + systray *application.SystemTray + window *application.WebviewWindow + menu *application.Menu + isActive bool +} + +func main() { + app := application.New(application.Options{ + Name: "Tray Application", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + trayApp := &TrayApp{app: app} + trayApp.setup() + + app.Run() +} + +func (t *TrayApp) setup() { + // Create system tray + t.systray = t.app.SystemTray.New() + t.systray.SetIcon(icon) + t.systray.SetLabel("Inactive") + + // Create menu + t.createMenu() + + // Create window (hidden by default) + t.window = t.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Tray Application", + Width: 400, + Height: 600, + Hidden: true, + }) + + // Attach window to tray + t.systray.AttachWindow(t.window) + t.systray.WindowOffset(10) + + // Handle tray clicks + t.systray.OnRightClick(func() { + t.systray.OpenMenu() + }) + + // Start background task + go t.backgroundTask() +} + +func (t *TrayApp) createMenu() { + t.menu = application.NewMenu() + + // Status item (disabled) + statusItem := t.menu.Add("Status: Inactive") + statusItem.SetEnabled(false) + + t.menu.AddSeparator() + + // Toggle active + t.menu.Add("Start").OnClick(func(ctx *application.Context) { + t.toggleActive() + }) + + // Show window + t.menu.Add("Show Window").OnClick(func(ctx *application.Context) { + t.window.Show() + t.window.Focus() + }) + + t.menu.AddSeparator() + + // Settings + t.menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) { + enabled := ctx.ClickedMenuItem().Checked() + t.setStartAtLogin(enabled) + }) + + t.menu.AddSeparator() + + // Quit + t.menu.Add("Quit").OnClick(func(ctx *application.Context) { + t.app.Quit() + }) + + t.systray.SetMenu(t.menu) +} + +func (t *TrayApp) toggleActive() { + t.isActive = !t.isActive + t.updateTray() +} + +func (t *TrayApp) updateTray() { + if t.isActive { + t.systray.SetIcon(iconActive) + t.systray.SetLabel("Active") + } else { + t.systray.SetIcon(icon) + t.systray.SetLabel("Inactive") + } + + // Rebuild menu with new status + t.createMenu() +} + +func (t *TrayApp) backgroundTask() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for range ticker.C { + if t.isActive { + fmt.Println("Background task running...") + // Do work + } + } +} + +func (t *TrayApp) setStartAtLogin(enabled bool) { + // Implementation varies by platform + fmt.Printf("Start at login: %v\n", enabled) +} +``` + +## Visibility Control + +Show/hide the tray icon dynamically: + +```go +// Hide tray icon +systray.Hide() + +// Show tray icon +systray.Show() +``` + +**Platform Support:** + +| Platform | Hide() | Show() | Notes | +|----------|--------|--------|-------| +| **Windows** | ✅ | ✅ | Fully functional - icon appears/disappears from notification area | +| **macOS** | ✅ | ✅ | Menu bar item shows/hides | +| **Linux** | ✅ | ✅ | Varies by desktop environment | + +**Use cases:** +- Temporarily hide tray icon based on user preference +- Headless mode with tray icon appearing only when needed +- Toggle visibility based on application state + +**Example - Conditional Tray Visibility:** + +```go +func (t *TrayApp) setTrayVisibility(visible bool) { + if visible { + t.systray.Show() + } else { + t.systray.Hide() + } +} + +// Show tray only when updates are available +func (t *TrayApp) checkForUpdates() { + if hasUpdates { + t.systray.Show() + t.systray.SetLabel("Update Available") + } else { + t.systray.Hide() + } +} +``` + +## Cleanup + +Destroy the tray icon when done: + +```go +// In OnShutdown +app := application.New(application.Options{ + OnShutdown: func() { + if systray != nil { + systray.Destroy() + } + }, +}) +``` + +**Important:** Always destroy system tray on shutdown to release resources. + +## Best Practices + +### ✅ Do + +- **Use template icons on macOS** - Adapts to dark mode +- **Keep labels short** - 3-5 characters maximum +- **Provide tooltips on Windows** - Helps users identify your app +- **Test on all platforms** - Behaviour varies +- **Handle clicks appropriately** - Left-click for main action, right-click for menu +- **Update icon for status** - Visual feedback is important +- **Destroy on shutdown** - Release resources + +### ❌ Don't + +- **Don't use large icons** - Follow platform guidelines +- **Don't use long labels** - Gets truncated +- **Don't forget dark mode** - Test on macOS dark mode +- **Don't block click handlers** - Keep them fast +- **Don't forget menu.Update()** - After changing menu state +- **Don't assume tray support** - Some Linux DEs don't support it + +## Troubleshooting + +### Tray Icon Not Appearing + +**Possible causes:** +1. Icon format not supported +2. Icon size too large/small +3. System tray not supported (Linux) + +**Solution:** +- Verify icon format and size match platform requirements (see icon table above) +- On Linux, ensure your desktop environment supports StatusNotifierItem or has an AppIndicator extension installed +- Test with a known-good PNG icon to rule out format issues + +### Icon Looks Wrong on macOS + +**Cause:** Not using template icon + +**Solution:** + +```go +// Use template icon +systray.SetTemplateIcon(iconBytes) + +// Or design icon as template (black + transparent) +``` + +### Menu Not Updating + +**Cause:** Forgot to call `menu.Update()` + +**Solution:** + +```go +menuItem.SetLabel("New Label") +menu.Update() // Add this! +``` + +## Next Steps + + + + Complete reference for menu item types and properties. + + [Learn More →](/features/menus/reference) + + + + Create application menu bars. + + [Learn More →](/features/menus/application) + + + + Create right-click context menus. + + [Learn More →](/features/menus/context) + + + + Build a complete system tray application. + + [Learn More →](/tutorials/system-tray) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [system tray examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/systray-basic). diff --git a/docs/src/content/docs/features/notifications/overview.mdx b/docs/src/content/docs/features/notifications/overview.mdx new file mode 100644 index 000000000..78b467250 --- /dev/null +++ b/docs/src/content/docs/features/notifications/overview.mdx @@ -0,0 +1,307 @@ +--- +title: Notifications +description: Display native system notifications with action buttons and text input +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Introduction + +Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields. + +## Basic Usage + +### Creating the Service + +First, initialize the notifications service: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/notifications" + +// Create a new notification service +notifier := notifications.New() + +//Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(notifier), + }, +}) +``` + +## Notification Authorization + +Notifications on macOS require user authorization. Request and check authorization: + +```go +authorized, err := notifier.CheckNotificationAuthorization() +if err != nil { + // Handle authorization error +} +if authorized { + // Send notifications +} else { + // Request authorization + authorized, err = notifier.RequestNotificationAuthorization() +} +``` +On Windows and Linux this always returns `true`. + +## Notification Types + +### Basic Notifications + +Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users: + +```go +notifier.SendNotification(notifications.NotificationOptions{ + ID: "unique-id", + Title: "New Calendar Invite", + Subtitle: "From: Jane Doe", // Optional + Body: "Tap to view the event", +}) + +``` + +### Interactive Notifications +Send a notification with action buttons and text inputs. These notifications require a notification category to be registered first: + +```go +// Define a unique category id +categoryID := "unique-category-id" + +// Define a category with actions +category := notifications.NotificationCategory{ + ID: categoryID, + Actions: []notifications.NotificationAction{ + { + ID: "OPEN", + Title: "Open", + }, + { + ID: "ARCHIVE", + Title: "Archive", + Destructive: true, /* macOS-specific */ + }, + }, + HasReplyField: true, + ReplyPlaceholder: "message...", + ReplyButtonTitle: "Reply", +} + +// Register the category +notifier.RegisterNotificationCategory(category) + +// Send an interactive notification with the actions registered in the provided category +notifier.SendNotificationWithActions(notifications.NotificationOptions{ + ID: "unique-id", + Title: "New Message", + Subtitle: "From: Jane Doe", + Body: "Are you able to make it?", + CategoryID: categoryID, +}) +``` + +## Notification Responses + +Process user interactions with notifications: + +```go +notifier.OnNotificationResponse(func(result notifications.NotificationResult) { + response := result.Response + fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier) + + if response.ActionIdentifier == "TEXT_REPLY" { + fmt.Printf("User replied: %s\n", response.UserText) + } + + if data, ok := response.UserInfo["sender"].(string); ok { + fmt.Printf("Original sender: %s\n", data) + } + + // Emit an event to the frontend + app.Event.Emit("notification", result.Response) +}) +``` + +## Notification Customisation + +### Custom Metadata + +Basic and interactive notifications can include custom data: + +```go +notifier.SendNotification(notifications.NotificationOptions{ + ID: "unique-id", + Title: "New Calendar Invite", + Subtitle: "From: Jane Doe", // Optional + Body: "Tap to view the event", + Data: map[string]interface{}{ + "sender": "jane.doe@example.com", + "timestamp": "2025-03-10T15:30:00Z", + } +}) + +``` + +## Platform Considerations + + + + + On macOS, notifications: + + - Require user authorization + - Require the app to be notarized for distribution + - Use system-standard notification appearances + - Support `subtitle` + - Support user text input + - Support the `Destructive` action option + - Automatically handle dark/light mode + + + + + + On Windows, notifications: + + - Use Windows system toast styles + - Adapt to Windows theme settings + - Support user text input + - Support high DPI displays + - Do not support `subtitle` + + + + + + On Linux, notification behaviour depends on the desktop environment: + + - Use native notifications when available + - Follow desktop environment theme + - Position according to desktop environment rules + - Support `subtitle` + - Do not support user text input + + + + +## Best Practices + +1. Check and request for authorization: + - macOS requires user authorization + +2. Provide clear and concise notifications: + - Use descriptive titles, subtitles, text, and action titles + +3. Handle dialog responses appropriately: + - Check for errors in notification responses + - Provide feedback for user actions + +4. Consider platform conventions: + - Follow platform-specific notification patterns + - Respect system settings + +## Examples + +Explore this example: + +- [Notifications](/examples/notifications) + +## API Reference + +### Service Management +| Method | Description | +|--------------------------------------------|-------------------------------------------------------| +| `New()` | Creates a new notifications service | + +### Notification Authorization +| Method | Description | +|----------------------------------------------|------------------------------------------------------------| +| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) | +| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) | + +### Sending Notifications +| Method | Returns | Description | +|------------------------------------------------------------|---------|------------------------------------------------| +| `SendNotification(options NotificationOptions)` | `error` | Sends a basic notification | +| `SendNotificationWithActions(options NotificationOptions)` | `error` | Sends an interactive notification with actions | + +### Notification Categories +| Method | Returns | Description | +|---------------------------------------------------------------|---------|------------------------------------------- | +| `RegisterNotificationCategory(category NotificationCategory)` | `error` | Registers a reusable notification category | +| `RemoveNotificationCategory(categoryID string)` | `error` | Removes a previously registered category | + +### Managing Notifications +| Method | Returns | Description | +|-------------------------------------------------|---------|---------------------------------------------------------------------| +| `RemoveAllPendingNotifications()` | `error` | Removes all pending notifications (macOS and Linux only) | +| `RemovePendingNotification(identifier string)` | `error` | Removes a specific pending notification (macOS and Linux only) | +| `RemoveAllDeliveredNotifications()` | `error` | Removes all delivered notifications (macOS and Linux only) | +| `RemoveDeliveredNotification(identifier string)`| `error` | Removes a specific delivered notification (macOS and Linux only) | +| `RemoveNotification(identifier string)` | `error` | Removes a notification (Linux-specific) | + +### Event Handling +| Method | Description | +|--------------------------------------------------------------------|-------------------------------------------------| +| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses | + +### Structs and Types + +#### NotificationOptions +```go +type NotificationOptions struct { + ID string // Unique identifier for the notification + Title string // Main notification title + Subtitle string // Subtitle text (macOS and Linux only) + Body string // Main notification content + CategoryID string // Category identifier for interactive notifications + Data map[string]interface{} // Custom data to associate with the notification +} +``` + +#### NotificationCategory +```go +type NotificationCategory struct { + ID string // Unique identifier for the category + Actions []NotificationAction // Button actions for the notification + HasReplyField bool // Whether to include a text input field + ReplyPlaceholder string // Placeholder text for the input field + ReplyButtonTitle string // Text for the reply button +} +``` + +#### NotificationAction +```go +type NotificationAction struct { + ID string // Unique identifier for the action + Title string // Button text + Destructive bool // Whether the action is destructive (macOS-specific) +} +``` + +#### NotificationResponse +```go +type NotificationResponse struct { + ID string // Notification identifier + ActionIdentifier string // Action that was triggered + CategoryID string // Category of the notification + Title string // Title of the notification + Subtitle string // Subtitle of the notification + Body string // Body text of the notification + UserText string // Text entered by the user + UserInfo map[string]interface{} // Custom data from the notification +} +``` + +#### NotificationResult +```go +type NotificationResult struct { + Response NotificationResponse // Response data + Error error // Any error that occurred +} +``` \ No newline at end of file diff --git a/docs/src/content/docs/features/platform/dock.mdx b/docs/src/content/docs/features/platform/dock.mdx new file mode 100644 index 000000000..2638f7bf2 --- /dev/null +++ b/docs/src/content/docs/features/platform/dock.mdx @@ -0,0 +1,235 @@ +--- +title: Dock & Taskbar +description: Manage dock icon visibility and display badges on macOS and Windows +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Introduction + +Wails provides a cross-platform Dock service for desktop applications. This service allows you to: + +- Hide and show the application icon in the macOS Dock +- Display badges on your application tile or dock/taskbar icon (macOS and Windows) + +## Basic Usage + +### Creating the Service + +First, initialize the dock service: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/dock" + +// Create a new Dock service +dockService := dock.New() + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(dockService), + }, +}) +``` + +### Creating the Service with Custom Badge Options (Windows Only) + +On Windows, you can customize the badge appearance with various options: + +```go +import "github.com/wailsapp/wails/v3/pkg/application" +import "github.com/wailsapp/wails/v3/pkg/services/dock" +import "image/color" + +// Create a dock service with custom badge options +options := dock.BadgeOptions{ + TextColour: color.RGBA{255, 255, 255, 255}, // White text + BackgroundColour: color.RGBA{0, 0, 255, 255}, // Blue background + FontName: "consolab.ttf", // Bold Consolas font + FontSize: 20, // Font size for single character + SmallFontSize: 14, // Font size for multiple characters +} + +dockService := dock.NewWithOptions(options) + +// Register the service with the application +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(dockService), + }, +}) +``` + +## Dock Operations + +### Hiding the dock app icon + +Hide the app icon from the macOS Dock: + +```go +// Hide the app icon +dockService.HideAppIcon() +``` + +### Showing the dock app icon + +Show the app icon in the macOS Dock: + +```go +// Show the app icon +dockService.ShowAppIcon() +``` + +## Badge Operations + +### Setting a Badge + +Set a badge on the application tile/dock icon: + +```go +// Set a default badge +dockService.SetBadge("") + +// Set a numeric badge +dockService.SetBadge("3") + +// Set a text badge +dockService.SetBadge("New") +``` + +### Setting a Custom Badge (Windows Only) + +Set a badge with one-off options applied: + +```go +options := dock.BadgeOptions{ + BackgroundColour: color.RGBA{0, 255, 255, 255}, + FontName: "arialb.ttf", // System font + FontSize: 16, + SmallFontSize: 10, + TextColour: color.RGBA{0, 0, 0, 255}, +} + +// Set a default badge +dockService.SetCustomBadge("", options) + +// Set a numeric badge +dockService.SetCustomBadge("3", options) + +// Set a text badge +dockService.SetCustomBadge("New", options) +``` + +### Removing a Badge + +Remove the badge from the application icon: + +```go +dockService.RemoveBadge() +``` + +## Platform Considerations + + + + + On macOS: + + - The dock icon can be **hidden** and **shown** + - Badges are displayed directly on the dock icon + - Badge options are **not customizable** (any options passed to `NewWithOptions`/`SetCustomBadge` are ignored) + - The standard macOS dock badge styling is used and automatically adapts to appearance + - Label overflow is handled by the system + - Providing an empty label displays a default badge of "●" + + + + + + On Windows: + + - Hiding/showing the taskbar icon is not currently supported by this service + - Badges are displayed as an overlay icon in the taskbar + - Badges support text values + - Badge appearance can be customized via `BadgeOptions` + - The application must have a window for badges to display + - A smaller font size is automatically used for multi-character labels + - Label overflow is not handled + - Customization options: + - **TextColour**: Text color (default: white) + - **BackgroundColour**: Badge background color (default: red) + - **FontName**: Font file name (default: "segoeuib.ttf") + - **FontSize**: Font size for single character (default: 18) + - **SmallFontSize**: Font size for multiple characters (default: 14) + + + + + + On Linux: + + - Dock icon visibility and badge functionality are not available + + + + +## Best Practices + +1. **When hiding the dock icon (macOS):** + - Ensure users can still access your app (e.g., via [system tray](https://v3alpha.wails.io/learn/systray/)) + - Include a "Quit" option in your alternative UI + - The app won't appear in Command+Tab switcher + - Open windows remain visible and functional + - Closing all windows may not quit the app (macOS behavior varies) + - Users lose the standard way to quit via Dock right-click + +2. **Use badges sparingly:** + - Too many badge updates can distract users + - Reserve badges for important notifications + +3. **Keep badge text short:** + - Numeric badges are most effective + - On macOS, text badges should be brief + +4. **For Windows badge customization:** + - Ensure high contrast between text and background colors + - Test with different text lengths as font size decreases with length + - Use common system fonts to ensure availability + + +## API Reference + +### Service Management +| Method | Description | +|--------------------------------------------|-------------------------------------------------------| +| `New()` | Creates a new dock service | +| `NewWithOptions(options BadgeOptions)` | Creates a new dock service with custom badge options (Windows only; options are ignored on macOS and Linux) | + +### Dock Operations +| Method | Description | +|--------------------------------|-------------------------------------------------------------| +| `HideAppIcon()` | Hides the app icon from the macOS Dock (macOS only) | +| `ShowAppIcon()` | Shows the app icon in the macOS Dock (macOS only) | + +### Badge Operations +| Method | Description | +|---------------------------------------------------|------------------------------------------------------------| +| `SetBadge(label string) error` | Sets a badge with the specified label | +| `SetCustomBadge(label string, options BadgeOptions) error` | Sets a badge with the specified label and custom styling options (Windows only) | +| `RemoveBadge() error` | Removes the badge from the application icon | + +### Structs and Types + +```go +// Options for customizing badge appearance (Windows only) +type BadgeOptions struct { + TextColour color.RGBA // Color of the badge text + BackgroundColour color.RGBA // Color of the badge background + FontName string // Font file name (e.g., "segoeuib.ttf") + FontSize int // Font size for single character + SmallFontSize int // Font size for multiple characters +} +``` diff --git a/docs/src/content/docs/features/screens/info.mdx b/docs/src/content/docs/features/screens/info.mdx new file mode 100644 index 000000000..71f3e7d6d --- /dev/null +++ b/docs/src/content/docs/features/screens/info.mdx @@ -0,0 +1,463 @@ +--- +title: Screen Information +description: Get information about displays and monitors +sidebar: + order: 1 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Screen Information + +Wails provides a **unified screen API** that works across all platforms. Get screen information, detect multiple monitors, query screen properties (size, position, DPI), identify the primary display, and handle DPI scaling with consistent code. + +## Quick Start + +```go +// Get all screens +screens := app.Screen.GetAll() + +for _, screen := range screens { + fmt.Printf("Screen: %s (%dx%d)\n", + screen.Name, screen.Size.Width, screen.Size.Height) +} + +// Get primary screen +primary := app.Screen.GetPrimary() +fmt.Printf("Primary: %s\n", primary.Name) +``` + +**That's it!** Cross-platform screen information. + +## Getting Screen Information + +### All Screens + +```go +screens := app.Screen.GetAll() + +for _, screen := range screens { + fmt.Printf("ID: %s\n", screen.ID) + fmt.Printf("Name: %s\n", screen.Name) + fmt.Printf("Size: %dx%d\n", screen.Size.Width, screen.Size.Height) + fmt.Printf("Position: %d,%d\n", screen.X, screen.Y) + fmt.Printf("Scale: %.2f\n", screen.ScaleFactor) + fmt.Printf("Primary: %v\n", screen.IsPrimary) + fmt.Println("---") +} +``` + +### Primary Screen + +```go +primary := app.Screen.GetPrimary() + +fmt.Printf("Primary screen: %s\n", primary.Name) +fmt.Printf("Resolution: %dx%d\n", primary.Size.Width, primary.Size.Height) +fmt.Printf("Scale factor: %.2f\n", primary.ScaleFactor) +``` + +## Screen Properties + +### Screen Structure + +```go +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + ScaleFactor float32 // The scale factor of the display (DPI/96) + X int // The x-coordinate of the top-left corner + Y int // The y-coordinate of the top-left corner + Size Size // The size of the display + Bounds Rect // The bounds of the display + PhysicalBounds Rect // The physical bounds (before scaling) + WorkArea Rect // The work area of the display + PhysicalWorkArea Rect // The physical work area (before scaling) + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display +} + +type Rect struct { + X int + Y int + Width int + Height int +} + +type Size struct { + Width int + Height int +} +``` + +### Physical vs Logical Pixels + +```go +screen := app.Screen.GetPrimary() + +// Logical pixels (DIP - what you use) +logicalWidth := screen.Bounds.Width +logicalHeight := screen.Bounds.Height + +// Physical pixels (actual display) +physicalWidth := screen.PhysicalBounds.Width +physicalHeight := screen.PhysicalBounds.Height + +fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight) +fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight) +fmt.Printf("Scale: %.2f\n", screen.ScaleFactor) +``` + +**Common scale factors:** +- `1.0` - Standard DPI (96 DPI) +- `1.25` - 125% scaling (120 DPI) +- `1.5` - 150% scaling (144 DPI) +- `2.0` - 200% scaling (192 DPI) - Retina +- `3.0` - 300% scaling (288 DPI) - 4K/5K + +## Window Positioning + +### Centre on Screen + +```go +func centreOnScreen(window *application.WebviewWindow, screen *Screen) { + windowWidth, windowHeight := window.Size() + + x := screen.X + (screen.Size.Width-windowWidth)/2 + y := screen.Y + (screen.Size.Height-windowHeight)/2 + + window.SetPosition(x, y) +} +``` + +### Position on Specific Screen + +```go +func moveToScreen(window *application.WebviewWindow, screenIndex int) { + screens := app.Screen.GetAll() + + if screenIndex < 0 || screenIndex >= len(screens) { + return + } + + screen := screens[screenIndex] + + // Centre on target screen + centreOnScreen(window, screen) +} +``` + +### Position Relative to Screen + +```go +// Top-left corner +func positionTopLeft(window *application.WebviewWindow, screen *Screen) { + window.SetPosition(screen.X+10, screen.Y+10) +} + +// Top-right corner +func positionTopRight(window *application.WebviewWindow, screen *Screen) { + windowWidth, _ := window.Size() + window.SetPosition(screen.X+screen.Size.Width-windowWidth-10, screen.Y+10) +} + +// Bottom-right corner +func positionBottomRight(window *application.WebviewWindow, screen *Screen) { + windowWidth, windowHeight := window.Size() + window.SetPosition( + screen.X+screen.Size.Width-windowWidth-10, + screen.Y+screen.Size.Height-windowHeight-10, + ) +} +``` + +## Multi-Monitor Support + +### Detect Multiple Monitors + +```go +func hasMultipleMonitors() bool { + return len(app.Screen.GetAll()) > 1 +} + +func getMonitorCount() int { + return len(app.Screen.GetAll()) +} +``` + +### List All Monitors + +```go +func listMonitors() { + screens := app.Screen.GetAll() + + fmt.Printf("Found %d monitor(s):\n", len(screens)) + + for i, screen := range screens { + primary := "" + if screen.IsPrimary { + primary = " (Primary)" + } + + fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary) + fmt.Printf(" Resolution: %dx%d\n", screen.Size.Width, screen.Size.Height) + fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y) + fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor) + } +} +``` + +### Choose Monitor + +```go +func chooseMonitor() (*Screen, error) { + screens := app.Screen.GetAll() + + if len(screens) == 1 { + return screens[0], nil + } + + // Show dialog to choose + var options []string + for i, screen := range screens { + primary := "" + if screen.IsPrimary { + primary = " (Primary)" + } + options = append(options, + fmt.Sprintf("%d. %s%s - %dx%d", + i+1, screen.Name, primary, screen.Size.Width, screen.Size.Height)) + } + + // Use dialog to select + // (Implementation depends on your dialog system) + + return screens[0], nil +} +``` + +## Complete Examples + +### Multi-Monitor Window Manager + +```go +type MultiMonitorManager struct { + app *application.App + windows map[int]*application.WebviewWindow +} + +func NewMultiMonitorManager(app *application.App) *MultiMonitorManager { + return &MultiMonitorManager{ + app: app, + windows: make(map[int]*application.WebviewWindow), + } +} + +func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error { + screens := m.app.Screen.GetAll() + + if screenIndex < 0 || screenIndex >= len(screens) { + return errors.New("invalid screen index") + } + + screen := screens[screenIndex] + + // Create window + window := m.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: fmt.Sprintf("Window on %s", screen.Name), + Width: 800, + Height: 600, + }) + + // Centre on screen + x := screen.X + (screen.Size.Width-800)/2 + y := screen.Y + (screen.Size.Height-600)/2 + window.SetPosition(x, y) + + window.Show() + + m.windows[screenIndex] = window + return nil +} + +func (m *MultiMonitorManager) CreateWindowOnEachScreen() { + screens := m.app.Screen.GetAll() + + for i := range screens { + m.CreateWindowOnScreen(i) + } +} +``` + +### Screen Change Detection + +```go +type ScreenMonitor struct { + app *application.App + lastScreens []*Screen + changeHandler func([]*Screen) +} + +func NewScreenMonitor(app *application.App) *ScreenMonitor { + return &ScreenMonitor{ + app: app, + lastScreens: app.Screen.GetAll(), + } +} + +func (sm *ScreenMonitor) OnScreenChange(handler func([]*Screen)) { + sm.changeHandler = handler +} + +func (sm *ScreenMonitor) Start() { + ticker := time.NewTicker(2 * time.Second) + + go func() { + for range ticker.C { + sm.checkScreens() + } + }() +} + +func (sm *ScreenMonitor) checkScreens() { + current := sm.app.Screen.GetAll() + + if len(current) != len(sm.lastScreens) { + sm.lastScreens = current + if sm.changeHandler != nil { + sm.changeHandler(current) + } + } +} +``` + +### DPI-Aware Window Sizing + +```go +func createDPIAwareWindow(screen *Screen) *application.WebviewWindow { + // Base size at 1.0 scale + baseWidth := 800 + baseHeight := 600 + + // Adjust for DPI + width := int(float32(baseWidth) * screen.ScaleFactor) + height := int(float32(baseHeight) * screen.ScaleFactor) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "DPI-Aware Window", + Width: width, + Height: height, + }) + + // Centre on screen + x := screen.X + (screen.Size.Width-width)/2 + y := screen.Y + (screen.Size.Height-height)/2 + window.SetPosition(x, y) + + return window +} +``` + +### Screen Layout Visualiser + +```go +func visualiseScreenLayout() string { + screens := app.Screen.GetAll() + + var layout strings.Builder + layout.WriteString("Screen Layout:\n\n") + + for i, screen := range screens { + primary := "" + if screen.IsPrimary { + primary = " [PRIMARY]" + } + + layout.WriteString(fmt.Sprintf("Screen %d: %s%s\n", i+1, screen.Name, primary)) + layout.WriteString(fmt.Sprintf(" Position: (%d, %d)\n", screen.X, screen.Y)) + layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Size.Width, screen.Size.Height)) + layout.WriteString(fmt.Sprintf(" Scale: %.2fx\n", screen.ScaleFactor)) + layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n", + screen.PhysicalBounds.Width, + screen.PhysicalBounds.Height)) + layout.WriteString("\n") + } + + return layout.String() +} +``` + +## Best Practices + +### ✅ Do + +- **Check screen count** - Handle single and multiple monitors +- **Use logical pixels** - Wails handles DPI automatically +- **Centre windows** - Better UX than fixed positions +- **Validate positions** - Ensure windows are visible +- **Handle screen changes** - Monitors can be added/removed +- **Test on different DPI** - 100%, 125%, 150%, 200% + +### ❌ Don't + +- **Don't hardcode positions** - Use screen dimensions +- **Don't assume primary screen** - User might have multiple +- **Don't ignore scale factor** - Important for DPI awareness +- **Don't position off-screen** - Validate coordinates +- **Don't forget screen changes** - Laptops dock/undock +- **Don't use physical pixels** - Use logical pixels + +## Platform Differences + +### macOS + +- Retina displays (2x scale factor) +- Multiple displays common +- Coordinate system: (0,0) at bottom-left +- Spaces (virtual desktops) affect positioning + +### Windows + +- Various DPI scaling (100%, 125%, 150%, 200%) +- Multiple displays common +- Coordinate system: (0,0) at top-left +- Per-monitor DPI awareness + +### Linux + +- Varies by desktop environment +- X11 vs Wayland differences +- DPI scaling support varies +- Multiple displays supported + +## Next Steps + + + + Learn about window management. + + [Learn More →](/features/windows/basics) + + + + Configure window appearance. + + [Learn More →](/features/windows/options) + + + + Multi-window patterns. + + [Learn More →](/features/windows/multiple) + + + + Call Go functions from JavaScript. + + [Learn More →](/features/bindings/methods) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [screen examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx new file mode 100644 index 000000000..b30a66bb8 --- /dev/null +++ b/docs/src/content/docs/features/windows/basics.mdx @@ -0,0 +1,570 @@ +--- +title: Window Basics +description: Creating and managing application windows in Wails +sidebar: + order: 1 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Window Management + +Wails provides a **unified window management API** that works across all platforms. Create windows, control their behaviour, and manage multiple windows with full control over creation, appearance, behaviour, and lifecycle. + +## Quick Start + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +func main() { + app := application.New(application.Options{ + Name: "My App", + }) + + // Create a window + window := app.Window.New() + + // Configure it + window.SetTitle("Hello Wails") + window.SetSize(800, 600) + window.Center() + + // Show it + window.Show() + + app.Run() +} +``` + +**That's it!** You have a cross-platform window. + +## Creating Windows + +### Basic Window + +The simplest way to create a window: + +```go +window := app.Window.New() +``` + +**What you get:** +- Default size (800x600) +- Default title (application name) +- WebView ready for your frontend +- Platform-native appearance + +### Window with Options + +Create a window with custom configuration: + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My Application", + Width: 1200, + Height: 800, + X: 100, // Position from left + Y: 100, // Position from top + AlwaysOnTop: false, + Frameless: false, + Hidden: false, + MinWidth: 400, + MinHeight: 300, + MaxWidth: 1920, + MaxHeight: 1080, +}) +``` + +**Common options:** + +| Option | Type | Description | +|--------|------|-------------| +| `Title` | `string` | Window title | +| `Width` | `int` | Window width in pixels | +| `Height` | `int` | Window height in pixels | +| `X` | `int` | X position (from left) | +| `Y` | `int` | Y position (from top) | +| `AlwaysOnTop` | `bool` | Keep window above others | +| `Frameless` | `bool` | Remove title bar and borders | +| `Hidden` | `bool` | Start hidden | +| `MinWidth` | `int` | Minimum width | +| `MinHeight` | `int` | Minimum height | +| `MaxWidth` | `int` | Maximum width | +| `MaxHeight` | `int` | Maximum height | + +**See [Window Options](/features/windows/options) for complete list.** + +### Named Windows + +Give windows names for easy retrieval: + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main-window", + Title: "Main Application", +}) + +// Later, find it by name +mainWindow, ok := app.Window.GetByName("main-window") +if ok { + mainWindow.Show() +} +``` + +**Use cases:** +- Multiple windows (main, settings, about) +- Finding windows from different parts of your code +- Window communication + +## Controlling Windows + +### Show and Hide + +```go +// Show window +window.Show() + +// Hide window +window.Hide() + +// Check if visible +if window.IsVisible() { + fmt.Println("Window is visible") +} +``` + +**Use cases:** +- Splash screens (show, then hide) +- Settings windows (hide when not needed) +- Popup windows (show on demand) + +### Position and Size + +```go +// Set size +window.SetSize(1024, 768) + +// Set position +window.SetPosition(100, 100) + +// Centre on screen +window.Center() + +// Get current size +width, height := window.Size() + +// Get current position +x, y := window.Position() +``` + +**Coordinate system:** +- (0, 0) is top-left of primary screen +- Positive X goes right +- Positive Y goes down + +### Window State + +```go +// Minimise +window.Minimise() + +// Maximise +window.Maximise() + +// Fullscreen +window.Fullscreen() + +// Restore to normal +window.Restore() + +// Check state +if window.IsMinimised() { + fmt.Println("Window is minimised") +} + +if window.IsMaximised() { + fmt.Println("Window is maximised") +} + +if window.IsFullscreen() { + fmt.Println("Window is fullscreen") +} +``` + +**State transitions:** + +``` +Normal ←→ Minimised +Normal ←→ Maximised +Normal ←→ Fullscreen +``` + +### Title and Appearance + +```go +// Set title +window.SetTitle("My Application - Document.txt") + +// Set background colour +window.SetBackgroundColour(application.NewRGBA(0, 0, 0, 255)) + +// Set always on top +window.SetAlwaysOnTop(true) + +// Set resizable +window.SetResizable(false) +``` + +### Closing Windows + +```go +// Close window (triggers WindowClosing event, can be cancelled via RegisterHook) +window.Close() +``` + +## Finding Windows + +### By Name + +```go +window, ok := app.Window.GetByName("settings") +if ok { + window.Show() +} +``` + +### Current Window + +Get the currently focused window: + +```go +current := app.Window.Current() +if current != nil { + current.SetTitle("Active Window") +} +``` + +### All Windows + +Get all windows: + +```go +windows := app.Window.GetAll() +fmt.Printf("Total windows: %d\n", len(windows)) + +for _, w := range windows { + fmt.Printf("Window: %s\n", w.Name()) +} +``` + +## Window Lifecycle + +### Creation + +```go +app.Window.OnCreate(func(window application.Window) { + fmt.Printf("Window created: %s\n", window.Name()) + + // Configure new windows + window.SetMinSize(400, 300) +}) +``` + +### Preventing Close + +Use `RegisterHook` to intercept the window close event and optionally cancel it: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the window from closing + } +}) +``` + +### Cleanup on Close + +Use `OnWindowEvent` to run cleanup logic when the window closes: + +```go +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + fmt.Println("Window closing, cleaning up...") + // Cleanup resources +}) +``` + +## Multiple Windows + +### Creating Multiple Windows + +```go +// Main window +mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Main Application", + Width: 1200, + Height: 800, +}) + +// Settings window +settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + Hidden: true, // Start hidden +}) + +// Show settings when needed +settingsWindow.Show() +``` + +### Window Communication + +Windows can communicate via events: + +```go +// In main window +app.Event.Emit("data-updated", map[string]interface{}{ + "value": 42, +}) + +// In settings window +app.Event.On("data-updated", func(event *application.CustomEvent) { + data := event.Data.(map[string]interface{}) + value := data["value"].(int) + fmt.Printf("Received: %d\n", value) +}) +``` + +**See [Events](/features/events/system) for more.** + +### Multiple Window Communication + +Windows can communicate via the event system. See [Multiple Windows](/features/windows/multiple) for patterns. + +## Platform-Specific Features + + + + **Windows-specific features:** + + ```go + // Flash taskbar button + window.Flash(true) // Start flashing + window.Flash(false) // Stop flashing + + // Trigger Windows 11 Snap Assist (Win+Z) + window.SnapAssist() + ``` + + **Snap Assist:** + Shows Windows 11 snap layout options for the window. + + **Taskbar flashing:** + Useful for notifications when window is minimised. + + + + **macOS-specific features:** + + ```go + // Transparent title bar + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + }, + Backdrop: application.MacBackdropTranslucent, + }, + }) + ``` + + **Backdrop types:** + - `MacBackdropNormal` - Standard window + - `MacBackdropTransparent` - Fully transparent + - `MacBackdropTranslucent` - Translucent background + - `MacBackdropLiquidGlass` - Liquid Glass effect (macOS 15.0+) + + **Native fullscreen:** + macOS fullscreen creates a new Space (virtual desktop). + + + + **Linux-specific features:** + + ```go + // Set window icon + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Linux: application.LinuxWindow{ + Icon: iconBytes, + }, + }) + ``` + + **Desktop environment notes:** + - GNOME: Full support + - KDE Plasma: Full support + - XFCE: Partial support + - Others: Varies + + **Window managers:** + - Tiling WMs may ignore size/position + - Some WMs don't support always-on-top + + + +## Common Patterns + +### Splash Screen + +```go +// Create splash screen +splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Loading...", + Width: 400, + Height: 300, + Frameless: true, + AlwaysOnTop: true, +}) + +// Show splash +splash.Show() + +// Initialise application +time.Sleep(2 * time.Second) + +// Hide splash, show main window +splash.Close() +mainWindow.Show() +``` + +### Settings Window + +```go +var settingsWindow *application.WebviewWindow + +func showSettings() { + if settingsWindow == nil { + settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + }) + } + + settingsWindow.Show() + settingsWindow.Focus() +} +``` + +### Confirm Before Close + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the window from closing + } +}) +``` + +## Best Practices + +### ✅ Do + +- **Name important windows** - Easier to find later +- **Set minimum size** - Prevent unusable layouts +- **Centre windows** - Better UX than random position +- **Handle close events** - Prevent data loss +- **Test on all platforms** - Behaviour varies +- **Use appropriate sizes** - Consider different screen sizes + +### ❌ Don't + +- **Don't create too many windows** - Confusing for users +- **Don't forget to close windows** - Memory leaks +- **Don't hardcode positions** - Different screen sizes +- **Don't ignore platform differences** - Test thoroughly +- **Don't block the UI thread** - Use goroutines for long operations + +## Troubleshooting + +### Window Not Showing + +**Possible causes:** +1. Window created as hidden +2. Window off-screen +3. Window behind other windows + +**Solution:** + +```go +window.Show() +window.Center() +window.Focus() +``` + +### Window Wrong Size + +**Cause:** DPI scaling on Windows/Linux + +**Solution:** + +```go +// Wails handles DPI automatically +// Just use logical pixels +window.SetSize(800, 600) +``` + +### Window Closes Immediately + +**Cause:** Application exits when last window closes + +**Solution:** + +```go +app := application.New(application.Options{ + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, +}) +``` + +## Next Steps + + + + Complete reference for all window options. + + [Learn More →](/features/windows/options) + + + + Patterns for multi-window applications. + + [Learn More →](/features/windows/multiple) + + + + Create custom window chrome. + + [Learn More →](/features/windows/frameless) + + + + Handle window lifecycle events. + + [Learn More →](/features/windows/events) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [window examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/windows/events.mdx b/docs/src/content/docs/features/windows/events.mdx new file mode 100644 index 000000000..3f474165f --- /dev/null +++ b/docs/src/content/docs/features/windows/events.mdx @@ -0,0 +1,516 @@ +--- +title: Window Events +description: Handle window lifecycle and state change events +sidebar: + order: 5 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Window Events + +Wails provides **comprehensive event handling** for window lifecycle and state changes: creation, focus/blur, resize/move, minimise/maximise, and close events. Register callbacks, handle events, and coordinate between windows with simple, consistent APIs. + +## Event Registration Methods + +Wails provides two methods for handling window events: + +- **`OnWindowEvent()`** - Listen to window events (cannot prevent them) +- **`RegisterHook()`** - Hook into window events (can prevent them by calling `event.Cancel()`) + +Both methods return a cleanup function that can be called to remove the listener/hook. + +### OnWindowEvent + +Registers a callback for window events. The callback receives a `*WindowEvent` argument. + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Listen for window focus +cancel := window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + fmt.Println("Window gained focus") +}) + +// Later, remove the listener +cancel() +``` + +### RegisterHook + +Registers a hook that runs **before** listeners and can prevent the event by calling `event.Cancel()`. + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Prevent window from closing +cancel := window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the close + } +}) +``` + +## Lifecycle Events + +### OnCreate + +Called when a window is created. Register via the window manager: + +```go +app.Window.OnCreate(func(window application.Window) { + fmt.Printf("Window created: %s\n", window.Name()) + + // Configure all new windows + window.SetMinSize(400, 300) +}) +``` + +**Use cases:** +- Configure all windows consistently +- Register event handlers +- Track window creation +- Initialise window-specific resources + +### WindowClosing + +Fired when the user attempts to close a window. Use `RegisterHook` to cancel the close. + +```go +// Prevent close with a hook +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the window from closing + } +}) + +// React to close with a listener (cannot cancel) +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + fmt.Println("Window is closing, cleaning up...") + cleanup() +}) +``` + +**Important:** +- Hooks run before listeners and can call `e.Cancel()` to prevent the close +- Listeners cannot cancel the event + +## Focus Events + +### WindowFocus / WindowLostFocus + +```go +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + fmt.Println("Window gained focus") + updateTitleBar(true) +}) + +window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) { + fmt.Println("Window lost focus") + updateTitleBar(false) +}) +``` + +**Use cases:** +- Update UI appearance +- Refresh data on focus +- Pause operations on blur +- Coordinate with other windows + +**Example: Focus-aware UI:** + +```go +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + window.EmitEvent("update-theme", "active") +}) + +window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) { + window.EmitEvent("update-theme", "inactive") +}) +``` + +## State Change Events + +### WindowMinimise / WindowUnMinimise + +```go +window.OnWindowEvent(events.Common.WindowMinimise, func(e *application.WindowEvent) { + fmt.Println("Window minimised") + pauseRendering() +}) + +window.OnWindowEvent(events.Common.WindowUnMinimise, func(e *application.WindowEvent) { + fmt.Println("Window restored from minimised") + resumeRendering() +}) +``` + +### WindowMaximise / WindowUnMaximise + +```go +window.OnWindowEvent(events.Common.WindowMaximise, func(e *application.WindowEvent) { + fmt.Println("Window maximised") + window.EmitEvent("layout-mode", "maximised") +}) + +window.OnWindowEvent(events.Common.WindowUnMaximise, func(e *application.WindowEvent) { + fmt.Println("Window restored from maximised") + window.EmitEvent("layout-mode", "normal") +}) +``` + +### WindowFullscreen / WindowUnFullscreen + +```go +window.OnWindowEvent(events.Common.WindowFullscreen, func(e *application.WindowEvent) { + fmt.Println("Window entered fullscreen") + window.EmitEvent("chrome-visibility", false) +}) + +window.OnWindowEvent(events.Common.WindowUnFullscreen, func(e *application.WindowEvent) { + fmt.Println("Window exited fullscreen") + window.EmitEvent("chrome-visibility", true) +}) +``` + +## Position and Size Events + +### WindowDidMove + +```go +window.OnWindowEvent(events.Common.WindowDidMove, func(e *application.WindowEvent) { + x, y := window.Position() + fmt.Printf("Window moved to: %d, %d\n", x, y) + saveWindowPosition(x, y) +}) +``` + +### WindowDidResize + +```go +window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) { + width, height := window.Size() + fmt.Printf("Window resized to: %dx%d\n", width, height) + saveWindowSize(width, height) +}) +``` + +**Note:** The move and resize event callbacks receive a `*WindowEvent` argument. To get the new position or size, call `window.Position()` or `window.Size()` inside the callback. + +## Common Window Events Reference + +| Event | Description | +|-------|-------------| +| `events.Common.WindowClosing` | Window is about to close | +| `events.Common.WindowDidMove` | Window moved | +| `events.Common.WindowDidResize` | Window resized | +| `events.Common.WindowDPIChanged` | DPI scaling changed | +| `events.Common.WindowFilesDropped` | Files were dropped onto the window | +| `events.Common.WindowFocus` | Window gained focus | +| `events.Common.WindowFullscreen` | Window entered fullscreen | +| `events.Common.WindowHide` | Window was hidden | +| `events.Common.WindowLostFocus` | Window lost focus | +| `events.Common.WindowMaximise` | Window maximised | +| `events.Common.WindowMinimise` | Window minimised | +| `events.Common.WindowToggleFrameless` | Frameless mode toggled | +| `events.Common.WindowRestore` | Window restored from min/max | +| `events.Common.WindowRuntimeReady` | Wails runtime loaded in window | +| `events.Common.WindowShow` | Window became visible | +| `events.Common.WindowUnFullscreen` | Window exited fullscreen | +| `events.Common.WindowUnMaximise` | Window restored from maximised | +| `events.Common.WindowUnMinimise` | Window restored from minimised | +| `events.Common.WindowZoom` | Window zoom changed | +| `events.Common.WindowZoomIn` | Zoom level increased | +| `events.Common.WindowZoomOut` | Zoom level decreased | +| `events.Common.WindowZoomReset` | Zoom reset to default | + +## Complete Example + +Here's a production-ready window with full event handling: + +```go +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type WindowState struct { + X int `json:"x"` + Y int `json:"y"` + Width int `json:"width"` + Height int `json:"height"` + Maximised bool `json:"maximised"` + Fullscreen bool `json:"fullscreen"` +} + +type ManagedWindow struct { + app *application.App + window *application.WebviewWindow + state WindowState + dirty bool +} + +func main() { + app := application.New(application.Options{ + Name: "Event Demo", + }) + + mw := &ManagedWindow{app: app} + mw.CreateWindow() + mw.LoadState() + mw.SetupEventHandlers() + + app.Run() +} + +func (mw *ManagedWindow) CreateWindow() { + mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Event Demo", + Width: 800, + Height: 600, + }) +} + +func (mw *ManagedWindow) SetupEventHandlers() { + // Focus events + mw.window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + fmt.Println("Window focused") + mw.window.EmitEvent("focus-state", true) + }) + + mw.window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) { + fmt.Println("Window blurred") + mw.window.EmitEvent("focus-state", false) + }) + + // State change events + mw.window.OnWindowEvent(events.Common.WindowMinimise, func(e *application.WindowEvent) { + fmt.Println("Window minimised") + mw.SaveState() + }) + + mw.window.OnWindowEvent(events.Common.WindowMaximise, func(e *application.WindowEvent) { + fmt.Println("Window maximised") + mw.state.Maximised = true + mw.dirty = true + }) + + mw.window.OnWindowEvent(events.Common.WindowUnMaximise, func(e *application.WindowEvent) { + fmt.Println("Window restored from maximised") + mw.state.Maximised = false + mw.dirty = true + }) + + mw.window.OnWindowEvent(events.Common.WindowFullscreen, func(e *application.WindowEvent) { + fmt.Println("Window fullscreen") + mw.state.Fullscreen = true + mw.dirty = true + }) + + mw.window.OnWindowEvent(events.Common.WindowUnFullscreen, func(e *application.WindowEvent) { + fmt.Println("Window exited fullscreen") + mw.state.Fullscreen = false + mw.dirty = true + }) + + // Position and size events + mw.window.OnWindowEvent(events.Common.WindowDidMove, func(e *application.WindowEvent) { + mw.state.X, mw.state.Y = mw.window.Position() + mw.dirty = true + }) + + mw.window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) { + mw.state.Width, mw.state.Height = mw.window.Size() + mw.dirty = true + }) + + // Lifecycle: save state on close + mw.window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + if mw.dirty { + mw.SaveState() + } + fmt.Println("Window closing") + }) +} + +func (mw *ManagedWindow) LoadState() { + data, err := os.ReadFile("window-state.json") + if err != nil { + return + } + + if err := json.Unmarshal(data, &mw.state); err != nil { + return + } + + // Restore window state + mw.window.SetPosition(mw.state.X, mw.state.Y) + mw.window.SetSize(mw.state.Width, mw.state.Height) + + if mw.state.Maximised { + mw.window.Maximise() + } + + if mw.state.Fullscreen { + mw.window.Fullscreen() + } +} + +func (mw *ManagedWindow) SaveState() { + data, err := json.Marshal(mw.state) + if err != nil { + return + } + + os.WriteFile("window-state.json", data, 0644) + mw.dirty = false + + fmt.Println("Window state saved") +} +``` + +## Event Coordination + +### Cross-Window Events + +Coordinate between multiple windows: + +```go +// In main window +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + // Notify all windows via the application event system + app.Event.Emit("main-window-focused", nil) +}) + +// In other windows +app.Event.On("main-window-focused", func(event *application.CustomEvent) { + updateRelativeToMain() +}) +``` + +### Event Chains + +Chain events together: + +```go +window.OnWindowEvent(events.Common.WindowMaximise, func(e *application.WindowEvent) { + // Save state + saveWindowState() + + // Update layout + window.EmitEvent("layout-changed", "maximised") + + // Notify other windows + app.Event.Emit("window-maximised", window.Name()) +}) +``` + +### Debounced Events + +Debounce frequent events: + +```go +var resizeTimer *time.Timer + +window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) { + if resizeTimer != nil { + resizeTimer.Stop() + } + + resizeTimer = time.AfterFunc(500*time.Millisecond, func() { + width, height := window.Size() + saveWindowSize(width, height) + }) +}) +``` + +## Best Practices + +### Do + +- **Save state on close** - Restore window position/size +- **Cleanup on close** - Release resources via `OnWindowEvent(events.Common.WindowClosing, ...)` +- **Debounce frequent events** - Resize, move +- **Handle focus changes** - Update UI appropriately +- **Coordinate windows** - Use events for communication +- **Test all events** - Ensure handlers work correctly + +### Don't + +- **Don't block event handlers** - Keep them fast +- **Don't forget cleanup** - Memory leaks +- **Don't ignore errors** - Log or handle them +- **Don't save on every event** - Debounce first +- **Don't create circular events** - Infinite loops +- **Don't forget platform differences** - Test thoroughly + +## Troubleshooting + +### Close Not Being Prevented + +**Cause:** Using `OnWindowEvent` instead of `RegisterHook` + +**Solution:** + +```go +// OnWindowEvent cannot cancel events +// Use RegisterHook instead: +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + e.Cancel() // This prevents the close +}) +``` + +### Events Not Firing + +**Cause:** Handler registered after event occurred + +**Solution:** + +```go +// Register handlers immediately after creation +window := app.Window.New() +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + // handle close +}) +``` + +### Memory Leaks + +**Cause:** Not calling the cleanup function returned by event registration + +**Solution:** + +```go +// Store and call the cancel function when done +cancel := window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + // handle focus +}) + +// Later, remove the listener +cancel() +``` + +## Next Steps + +**Window Basics** - Learn the fundamentals of window management +[Learn More ->](/features/windows/basics) + +**Multiple Windows** - Patterns for multi-window applications +[Learn More ->](/features/windows/multiple) + +**Events System** - Deep dive into the event system +[Learn More ->](/features/events/system) + +**Application Lifecycle** - Understand the application lifecycle +[Learn More ->](/concepts/lifecycle) + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/features/windows/frameless.mdx b/docs/src/content/docs/features/windows/frameless.mdx new file mode 100644 index 000000000..fb90e7dcd --- /dev/null +++ b/docs/src/content/docs/features/windows/frameless.mdx @@ -0,0 +1,870 @@ +--- +title: Frameless Windows +description: Create custom window chrome with frameless windows +sidebar: + order: 4 +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + +## Frameless Windows + +Wails provides **frameless window support** with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls. + +## Quick Start + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Frameless App", + Width: 800, + Height: 600, + Frameless: true, +}) +``` + +**CSS for draggable title bar:** + +```css +.titlebar { + --wails-draggable: drag; + height: 40px; + background: #333; +} + +.titlebar button { + --wails-draggable: no-drag; +} +``` + +**HTML:** + +```html +
+ My Application + +
+``` + +**That's it!** You have a custom title bar. + +## Creating Frameless Windows + +### Basic Frameless Window + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + Width: 800, + Height: 600, +}) +``` + +**What you get:** +- No title bar +- No window borders +- No system buttons +- Transparent background (optional) + +**What you need to implement:** +- Draggable area +- Close/minimise/maximise buttons +- Resize handles (if resizable) + +### With Transparent Background + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +**Use cases:** +- Rounded corners +- Custom shapes +- Overlay windows +- Splash screens + +## Drag Regions + +### CSS-Based Dragging + +Use the `--wails-draggable` CSS property: + +```css +/* Draggable area */ +.titlebar { + --wails-draggable: drag; +} + +/* Non-draggable elements within draggable area */ +.titlebar button { + --wails-draggable: no-drag; +} +``` + +**Values:** +- `drag` - Area is draggable +- `no-drag` - Area is not draggable (even if parent is) + +### Complete Title Bar Example + +```html +
+
My Application
+
+ + + +
+
+``` + +```css +.titlebar { + --wails-draggable: drag; + display: flex; + justify-content: space-between; + align-items: center; + height: 40px; + background: #2c2c2c; + color: white; + padding: 0 16px; +} + +.title { + font-size: 14px; + user-select: none; +} + +.controls { + display: flex; + gap: 8px; +} + +.controls button { + --wails-draggable: no-drag; + width: 32px; + height: 32px; + border: none; + background: transparent; + color: white; + font-size: 16px; + cursor: pointer; + border-radius: 4px; +} + +.controls button:hover { + background: rgba(255, 255, 255, 0.1); +} + +.controls .close:hover { + background: #e81123; +} +``` + +**JavaScript for buttons:** + +```javascript +import { WindowMinimise, WindowMaximise, WindowClose } from '@wailsio/runtime' + +document.querySelector('.minimize').addEventListener('click', WindowMinimise) +document.querySelector('.maximize').addEventListener('click', WindowMaximise) +document.querySelector('.close').addEventListener('click', WindowClose) +``` + +## System Buttons + +### Implementing Close/Minimise/Maximise + +**Go side:** + +```go +type WindowControls struct { + window *application.WebviewWindow +} + +func (wc *WindowControls) Minimise() { + wc.window.Minimise() +} + +func (wc *WindowControls) Maximise() { + if wc.window.IsMaximised() { + wc.window.UnMaximise() + } else { + wc.window.Maximise() + } +} + +func (wc *WindowControls) Close() { + wc.window.Close() +} +``` + +**JavaScript side:** + +```javascript +import { Minimise, Maximise, Close } from './bindings/WindowControls' + +document.querySelector('.minimize').addEventListener('click', Minimise) +document.querySelector('.maximize').addEventListener('click', Maximise) +document.querySelector('.close').addEventListener('click', Close) +``` + +**Or use runtime methods:** + +```javascript +import { + WindowMinimise, + WindowMaximise, + WindowClose +} from '@wailsio/runtime' + +document.querySelector('.minimize').addEventListener('click', WindowMinimise) +document.querySelector('.maximize').addEventListener('click', WindowMaximise) +document.querySelector('.close').addEventListener('click', WindowClose) +``` + +### Toggle Maximise State + +Track maximise state for button icon: + +```javascript +import { WindowIsMaximised, WindowMaximise, WindowUnMaximise } from '@wailsio/runtime' + +async function toggleMaximise() { + const isMaximised = await WindowIsMaximised() + + if (isMaximised) { + await WindowUnMaximise() + } else { + await WindowMaximise() + } + + updateMaximiseButton() +} + +async function updateMaximiseButton() { + const isMaximised = await WindowIsMaximised() + const button = document.querySelector('.maximize') + button.textContent = isMaximised ? '❐' : '□' +} +``` + +## Resize Handles + +### CSS-Based Resize + +Wails provides automatic resize handles for frameless windows: + +```css +/* Enable resize on all edges */ +body { + --wails-resize: all; +} + +/* Or specific edges */ +.resize-top { + --wails-resize: top; +} + +.resize-bottom { + --wails-resize: bottom; +} + +.resize-left { + --wails-resize: left; +} + +.resize-right { + --wails-resize: right; +} + +/* Corners */ +.resize-top-left { + --wails-resize: top-left; +} + +.resize-top-right { + --wails-resize: top-right; +} + +.resize-bottom-left { + --wails-resize: bottom-left; +} + +.resize-bottom-right { + --wails-resize: bottom-right; +} +``` + +**Values:** +- `all` - Resize from all edges +- `top`, `bottom`, `left`, `right` - Specific edges +- `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners +- `none` - No resize + +### Resize Handle Example + +```html +
+
...
+
...
+
+
+``` + +```css +.resize-handle { + position: absolute; + width: 16px; + height: 16px; +} + +.resize-bottom-right { + --wails-resize: bottom-right; + bottom: 0; + right: 0; + cursor: nwse-resize; +} +``` + +## Platform-Specific Behaviour + + + + **Windows frameless windows:** + + ```go + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + Windows: application.WindowsWindow{ + DisableFramelessWindowDecorations: false, + }, + }) + ``` + + **Features:** + - Automatic drop shadow + - Snap layouts support (Windows 11) + - Aero Snap support + - DPI scaling + + **Disable decorations:** + ```go + Windows: application.WindowsWindow{ + DisableFramelessWindowDecorations: true, + }, + ``` + + **Snap Assist:** + ```go + // Trigger Windows 11 Snap Assist + window.SnapAssist() + ``` + + **Custom title bar height:** + Windows automatically detects drag regions from CSS. + + + + **macOS frameless windows:** + + ```go + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + }, + InvisibleTitleBarHeight: 40, + }, + }) + ``` + + **Features:** + - Native fullscreen support + - Traffic light buttons (optional) + - Vibrancy effects + - Transparent title bar + + **Hide traffic lights:** + ```go + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + }, + ``` + + **Invisible title bar:** + Allows dragging whilst hiding the title bar: + ```go + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 40, + }, + ``` + + + + **Linux frameless windows:** + + ```go + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + }) + ``` + + **Features:** + - Basic frameless support + - CSS drag regions + - Varies by desktop environment + + **Desktop environment notes:** + - **GNOME:** Good support + - **KDE Plasma:** Good support + - **XFCE:** Basic support + - **Tiling WMs:** Limited support + + **Compositor required:** + Transparency requires a compositor (most modern DEs have one). + + + +## Common Patterns + +### Pattern 1: Modern Title Bar + +```html +
+
+ App Icon +
+
My Application
+
+ + + +
+
+``` + +```css +.modern-titlebar { + --wails-draggable: drag; + display: flex; + align-items: center; + height: 40px; + background: linear-gradient(to bottom, #3a3a3a, #2c2c2c); + border-bottom: 1px solid #1a1a1a; + padding: 0 16px; +} + +.app-icon { + --wails-draggable: no-drag; + width: 24px; + height: 24px; + margin-right: 12px; +} + +.title { + flex: 1; + font-size: 13px; + color: #e0e0e0; + user-select: none; +} + +.controls { + display: flex; + gap: 1px; +} + +.controls button { + --wails-draggable: no-drag; + width: 46px; + height: 32px; + border: none; + background: transparent; + color: #e0e0e0; + font-size: 14px; + cursor: pointer; + transition: background 0.2s; +} + +.controls button:hover { + background: rgba(255, 255, 255, 0.1); +} + +.controls .close:hover { + background: #e81123; + color: white; +} +``` + +### Pattern 2: Splash Screen + +```go +splash := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Loading...", + Width: 400, + Height: 300, + Frameless: true, + AlwaysOnTop: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +```css +body { + background: transparent; + display: flex; + justify-content: center; + align-items: center; +} + +.splash { + background: white; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + padding: 40px; + text-align: center; +} +``` + +### Pattern 3: Rounded Window + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +```css +body { + background: transparent; + margin: 8px; +} + +.window { + background: white; + border-radius: 16px; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15); + overflow: hidden; + height: calc(100vh - 16px); +} + +.titlebar { + --wails-draggable: drag; + background: #f5f5f5; + border-bottom: 1px solid #e0e0e0; +} +``` + +### Pattern 4: Overlay Window + +```go +overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Frameless: true, + AlwaysOnTop: true, + BackgroundType: application.BackgroundTypeTransparent, +}) +``` + +```css +body { + background: transparent; +} + +.overlay { + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + border-radius: 8px; + padding: 20px; +} +``` + +## Complete Example + +Here's a production-ready frameless window: + +**Go:** + +```go +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "Frameless App", + }) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Frameless Application", + Width: 1000, + Height: 700, + MinWidth: 800, + MinHeight: 600, + Frameless: true, + + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + }, + InvisibleTitleBarHeight: 40, + }, + + Windows: application.WindowsWindow{ + DisableFramelessWindowDecorations: false, + }, + }) + + window.Center() + window.Show() + + app.Run() +} +``` + +**HTML:** + +```html + + + + + + +
+
+
Frameless Application
+
+ + + +
+
+
+

Hello from Frameless Window!

+
+
+ + + +``` + +**CSS:** + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: #f5f5f5; +} + +.window { + height: 100vh; + display: flex; + flex-direction: column; +} + +.titlebar { + --wails-draggable: drag; + display: flex; + justify-content: space-between; + align-items: center; + height: 40px; + background: #ffffff; + border-bottom: 1px solid #e0e0e0; + padding: 0 16px; +} + +.title { + font-size: 13px; + font-weight: 500; + color: #333; + user-select: none; +} + +.controls { + display: flex; + gap: 8px; +} + +.controls button { + --wails-draggable: no-drag; + width: 32px; + height: 32px; + border: none; + background: transparent; + color: #666; + font-size: 16px; + cursor: pointer; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.controls button:hover { + background: #f0f0f0; + color: #333; +} + +.controls .close:hover { + background: #e81123; + color: white; +} + +.content { + flex: 1; + padding: 40px; + overflow: auto; +} +``` + +**JavaScript:** + +```javascript +import { + WindowMinimise, + WindowMaximise, + WindowUnMaximise, + WindowIsMaximised, + WindowClose +} from '@wailsio/runtime' + +// Minimise button +document.querySelector('.minimize').addEventListener('click', () => { + WindowMinimise() +}) + +// Maximise/restore button +const maximiseBtn = document.querySelector('.maximize') +maximiseBtn.addEventListener('click', async () => { + const isMaximised = await WindowIsMaximised() + + if (isMaximised) { + await WindowUnMaximise() + } else { + await WindowMaximise() + } + + updateMaximiseButton() +}) + +// Close button +document.querySelector('.close').addEventListener('click', () => { + WindowClose() +}) + +// Update maximise button icon +async function updateMaximiseButton() { + const isMaximised = await WindowIsMaximised() + maximiseBtn.textContent = isMaximised ? '❐' : '□' + maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise' +} + +// Initial state +updateMaximiseButton() +``` + +## Best Practices + +### ✅ Do + +- **Provide draggable area** - Users need to move the window +- **Implement system buttons** - Close, minimise, maximise +- **Set minimum size** - Prevent unusable layouts +- **Test on all platforms** - Behaviour varies +- **Use CSS for drag regions** - Flexible and maintainable +- **Provide visual feedback** - Hover states on buttons + +### ❌ Don't + +- **Don't forget resize handles** - If window is resizable +- **Don't make entire window draggable** - Prevents interaction +- **Don't forget no-drag on buttons** - They won't work +- **Don't use tiny drag areas** - Hard to grab +- **Don't forget platform differences** - Test thoroughly + +## Troubleshooting + +### Window Won't Drag + +**Cause:** Missing `--wails-draggable: drag` + +**Solution:** + +```css +.titlebar { + --wails-draggable: drag; +} +``` + +### Buttons Don't Work + +**Cause:** Buttons are in draggable area + +**Solution:** + +```css +.titlebar button { + --wails-draggable: no-drag; +} +``` + +### Can't Resize Window + +**Cause:** Missing resize handles + +**Solution:** + +```css +body { + --wails-resize: all; +} +``` + +## Next Steps + + + + Learn the fundamentals of window management. + + [Learn More →](/features/windows/basics) + + + + Complete reference for window options. + + [Learn More →](/features/windows/options) + + + + Handle window lifecycle events. + + [Learn More →](/features/windows/events) + + + + Patterns for multi-window applications. + + [Learn More →](/features/windows/multiple) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless). diff --git a/docs/src/content/docs/features/windows/multiple.mdx b/docs/src/content/docs/features/windows/multiple.mdx new file mode 100644 index 000000000..23099c5aa --- /dev/null +++ b/docs/src/content/docs/features/windows/multiple.mdx @@ -0,0 +1,783 @@ +--- +title: Multiple Windows +description: Patterns and best practices for multi-window applications +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Multi-Window Applications + +Wails v3 provides **native multi-window support** for creating settings windows, document windows, tool palettes, and inspector windows. Track windows, enable communication between them, and manage their lifecycle with simple, consistent APIs. + +### Main + Settings Window + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +type App struct { + app *application.App + mainWindow *application.WebviewWindow + settingsWindow *application.WebviewWindow +} + +func main() { + app := &App{} + + app.app = application.New(application.Options{ + Name: "Multi-Window App", + }) + + // Create main window + app.mainWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Main Application", + Width: 1200, + Height: 800, + }) + + // Create settings window (hidden initially) + app.settingsWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + Hidden: true, + }) + + app.app.Run() +} + +// Show settings from main window +func (a *App) ShowSettings() { + if a.settingsWindow != nil { + a.settingsWindow.Show() + a.settingsWindow.Focus() + } +} +``` + +**Key points:** +- Main window always visible +- Settings window created but hidden +- Show settings on demand +- Reuse same window (don't create multiple) + +## Window Tracking + +### Get All Windows + +```go +windows := app.Window.GetAll() +fmt.Printf("Total windows: %d\n", len(windows)) + +for _, window := range windows { + fmt.Printf("- %s\n", window.Name()) +} +``` + +### Find Specific Window + +```go +// By name +settings, ok := app.Window.GetByName("settings") +if ok { + settings.Show() +} + +// Current (focused) window +current := app.Window.Current() +``` + +### Window Registry Pattern + +Track windows in your application: + +```go +type WindowManager struct { + windows map[string]*application.WebviewWindow + mu sync.RWMutex +} + +func (wm *WindowManager) Register(name string, window *application.WebviewWindow) { + wm.mu.Lock() + defer wm.mu.Unlock() + wm.windows[name] = window +} + +func (wm *WindowManager) Get(name string) *application.WebviewWindow { + wm.mu.RLock() + defer wm.mu.RUnlock() + return wm.windows[name] +} + +func (wm *WindowManager) Remove(name string) { + wm.mu.Lock() + defer wm.mu.Unlock() + delete(wm.windows, name) +} +``` + +## Window Communication + +### Using Events + +Windows communicate via the event system: + +```go +// In main window - emit event +app.Event.Emit("settings-changed", map[string]interface{}{ + "theme": "dark", + "fontSize": 14, +}) + +// In settings window - listen for event +app.Event.On("settings-changed", func(event *application.CustomEvent) { + data := event.Data.(map[string]interface{}) + theme := data["theme"].(string) + fontSize := data["fontSize"].(int) + + // Update UI + updateSettings(theme, fontSize) +}) +``` + +### Shared State Pattern + +Use a shared state manager: + +```go +type AppState struct { + theme string + fontSize int + mu sync.RWMutex +} + +var state = &AppState{ + theme: "light", + fontSize: 12, +} + +func (s *AppState) SetTheme(theme string) { + s.mu.Lock() + s.theme = theme + s.mu.Unlock() + + // Notify all windows + app.Event.Emit("theme-changed", theme) +} + +func (s *AppState) GetTheme() string { + s.mu.RLock() + defer s.mu.RUnlock() + return s.theme +} +``` + +### Window-to-Window Messages + +Send messages between specific windows: + +```go +// Get target window +targetWindow, ok := app.Window.GetByName("preview") +if ok { + // Emit event to specific window + targetWindow.EmitEvent("update-preview", previewData) +} +``` + +## Common Patterns + +### Pattern 1: Singleton Windows + +Ensure only one instance of a window exists: + +```go +var settingsWindow *application.WebviewWindow + +func ShowSettings(app *application.App) { + // Create if doesn't exist + if settingsWindow == nil { + settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + }) + + // Cleanup on close + settingsWindow.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + settingsWindow = nil + }) + } + + // Show and focus + settingsWindow.Show() + settingsWindow.Focus() +} +``` + +### Pattern 2: Document Windows + +Multiple instances of the same window type: + +```go +type DocumentWindow struct { + window *application.WebviewWindow + filePath string + modified bool +} + +var documents = make(map[string]*DocumentWindow) + +func OpenDocument(app *application.App, filePath string) { + // Check if already open + if doc, exists := documents[filePath]; exists { + doc.window.Show() + doc.window.Focus() + return + } + + // Create new document window + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: filepath.Base(filePath), + Width: 800, + Height: 600, + }) + + doc := &DocumentWindow{ + window: window, + filePath: filePath, + modified: false, + } + + documents[filePath] = doc + + // Cleanup on close + window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + delete(documents, filePath) + }) + + // Load document + loadDocument(window, filePath) +} +``` + +### Pattern 3: Tool Palettes + +Floating windows that stay on top: + +```go +func CreateToolPalette(app *application.App) *application.WebviewWindow { + palette := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "tools", + Title: "Tools", + Width: 200, + Height: 400, + AlwaysOnTop: true, + DisableResize: true, + }) + + return palette +} +``` + +### Pattern 4: Modal dialogs + +Child windows that block parent: + +```go +func ShowModaldialog(parent *application.WebviewWindow, title string) { + dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: title, + Width: 400, + Height: 200, + AlwaysOnTop: true, + DisableResize: true, + }) + + // Disable parent (platform-specific) + parent.SetEnabled(false) + + // Re-enable parent on close + dialog.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + parent.SetEnabled(true) + parent.Focus() + }) +} +``` + +### Pattern 5: Inspector/Preview Windows + +Linked windows that update together: + +```go +type EditorApp struct { + editor *application.WebviewWindow + preview *application.WebviewWindow +} + +func (e *EditorApp) UpdatePreview(content string) { + if e.preview != nil && e.preview.IsVisible() { + e.preview.EmitEvent("content-changed", content) + } +} + +func (e *EditorApp) TogglePreview() { + if e.preview == nil { + e.preview = app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "preview", + Title: "Preview", + Width: 600, + Height: 800, + }) + + e.preview.OnWindowEvent(events.Common.WindowClosing, func(e2 *application.WindowEvent) { + e.preview = nil + }) + } + + if e.preview.IsVisible() { + e.preview.Hide() + } else { + e.preview.Show() + } +} +``` + +## Modal-like Behaviour + +Create modal-like behaviour by disabling the parent window: + +```go +func ShowModal(parent *application.WebviewWindow) { + modal := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Modal dialog", + Width: 400, + Height: 200, + AlwaysOnTop: true, + }) + + // Disable parent interaction + parent.SetEnabled(false) + + // Re-enable on close + modal.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + parent.SetEnabled(true) + parent.Focus() + }) +} +``` + +**Note:** True modal behaviour (blocking) varies by platform. + +## Window Lifecycle Management + +### Creation Callbacks + +Be notified when windows are created: + +```go +app.Window.OnCreate(func(window application.Window) { + fmt.Printf("Window created: %s\n", window.Name()) + + // Configure all new windows + window.SetMinSize(400, 300) +}) +``` + +### Window Close Callbacks + +Cleanup when windows are closed: + +```go +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + fmt.Printf("Window %s closing\n", window.Name()) + + // Cleanup resources + cleanup(window.Name()) + + // Remove from tracking + removeFromRegistry(window.Name()) +}) +``` + +### Application Quit Behaviour + +Control when application quits: + +```go +app := application.New(application.Options{ + Mac: application.MacOptions{ + // Don't quit when last window closes + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, +}) +``` + +**Use cases:** +- System tray applications +- Background services +- Menu bar applications (macOS) + +## Memory Management + +### Preventing Leaks + +Always clean up window references: + +```go +var windows = make(map[string]*application.WebviewWindow) + +func CreateWindow(name string) { + window := app.Window.New() + windows[name] = window + + // IMPORTANT: Clean up on close + window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + delete(windows, name) + }) +} +``` + +### Closing Windows + +```go +// Close - triggers WindowClosing event, can be cancelled via RegisterHook +window.Close() +``` + +**Note:** `Close()` emits a `WindowClosing` event. Use `RegisterHook` to intercept and optionally cancel the close. + +### Resource Cleanup + +```go +type ManagedWindow struct { + window *application.WebviewWindow + resources []io.Closer +} + +func (mw *ManagedWindow) CleanupAndClose() { + // Close all resources + for _, resource := range mw.resources { + resource.Close() + } + + // Close window + mw.window.Close() +} +``` + +## Advanced Patterns + +### Window Pool + +Reuse windows instead of creating new ones: + +```go +type WindowPool struct { + available []*application.WebviewWindow + inUse map[string]*application.WebviewWindow + mu sync.Mutex +} + +func (wp *WindowPool) Acquire() *application.WebviewWindow { + wp.mu.Lock() + defer wp.mu.Unlock() + + // Reuse available window + if len(wp.available) > 0 { + window := wp.available[0] + wp.available = wp.available[1:] + wp.inUse[window.Name()] = window + return window + } + + // Create new window + window := app.Window.New() + wp.inUse[window.Name()] = window + return window +} + +func (wp *WindowPool) Release(window *application.WebviewWindow) { + wp.mu.Lock() + defer wp.mu.Unlock() + + delete(wp.inUse, window.Name()) + window.Hide() + wp.available = append(wp.available, window) +} +``` + +### Window Groups + +Manage related windows together: + +```go +type WindowGroup struct { + name string + windows []*application.WebviewWindow +} + +func (wg *WindowGroup) Add(window *application.WebviewWindow) { + wg.windows = append(wg.windows, window) +} + +func (wg *WindowGroup) ShowAll() { + for _, window := range wg.windows { + window.Show() + } +} + +func (wg *WindowGroup) HideAll() { + for _, window := range wg.windows { + window.Hide() + } +} + +func (wg *WindowGroup) CloseAll() { + for _, window := range wg.windows { + window.Close() + } +} +``` + +### Workspace Management + +Save and restore window layouts: + +```go +type WindowLayout struct { + Windows []WindowState `json:"windows"` +} + +type WindowState struct { + Name string `json:"name"` + X int `json:"x"` + Y int `json:"y"` + Width int `json:"width"` + Height int `json:"height"` +} + +func SaveLayout() *WindowLayout { + layout := &WindowLayout{} + + for _, window := range app.Window.GetAll() { + x, y := window.Position() + width, height := window.Size() + + layout.Windows = append(layout.Windows, WindowState{ + Name: window.Name(), + X: x, + Y: y, + Width: width, + Height: height, + }) + } + + return layout +} + +func RestoreLayout(layout *WindowLayout) { + for _, state := range layout.Windows { + window, ok := app.Window.GetByName(state.Name) + if ok { + window.SetPosition(state.X, state.Y) + window.SetSize(state.Width, state.Height) + } + } +} +``` + +## Complete Example + +Here's a production-ready multi-window application: + +```go +package main + +import ( + "encoding/json" + "os" + "sync" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type MultiWindowApp struct { + app *application.App + windows map[string]*application.WebviewWindow + mu sync.RWMutex +} + +func main() { + mwa := &MultiWindowApp{ + windows: make(map[string]*application.WebviewWindow), + } + + mwa.app = application.New(application.Options{ + Name: "Multi-Window Application", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + + // Create main window + mwa.CreateMainWindow() + + // Load saved layout + mwa.LoadLayout() + + mwa.app.Run() +} + +func (mwa *MultiWindowApp) CreateMainWindow() { + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main", + Title: "Main Application", + Width: 1200, + Height: 800, + }) + + mwa.RegisterWindow("main", window) +} + +func (mwa *MultiWindowApp) ShowSettings() { + if window := mwa.GetWindow("settings"); window != nil { + window.Show() + window.Focus() + return + } + + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Title: "Settings", + Width: 600, + Height: 400, + }) + + mwa.RegisterWindow("settings", window) +} + +func (mwa *MultiWindowApp) OpenDocument(path string) { + name := "doc-" + path + + if window := mwa.GetWindow(name); window != nil { + window.Show() + window.Focus() + return + } + + window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: name, + Title: path, + Width: 800, + Height: 600, + }) + + mwa.RegisterWindow(name, window) +} + +func (mwa *MultiWindowApp) RegisterWindow(name string, window *application.WebviewWindow) { + mwa.mu.Lock() + mwa.windows[name] = window + mwa.mu.Unlock() + + window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + mwa.UnregisterWindow(name) + }) +} + +func (mwa *MultiWindowApp) UnregisterWindow(name string) { + mwa.mu.Lock() + delete(mwa.windows, name) + mwa.mu.Unlock() +} + +func (mwa *MultiWindowApp) GetWindow(name string) *application.WebviewWindow { + mwa.mu.RLock() + defer mwa.mu.RUnlock() + return mwa.windows[name] +} + +func (mwa *MultiWindowApp) SaveLayout() { + layout := make(map[string]WindowState) + + mwa.mu.RLock() + for name, window := range mwa.windows { + x, y := window.Position() + width, height := window.Size() + + layout[name] = WindowState{ + X: x, + Y: y, + Width: width, + Height: height, + } + } + mwa.mu.RUnlock() + + data, _ := json.Marshal(layout) + os.WriteFile("layout.json", data, 0644) +} + +func (mwa *MultiWindowApp) LoadLayout() { + data, err := os.ReadFile("layout.json") + if err != nil { + return + } + + var layout map[string]WindowState + if err := json.Unmarshal(data, &layout); err != nil { + return + } + + for name, state := range layout { + if window := mwa.GetWindow(name); window != nil { + window.SetPosition(state.X, state.Y) + window.SetSize(state.Width, state.Height) + } + } +} + +type WindowState struct { + X int `json:"x"` + Y int `json:"y"` + Width int `json:"width"` + Height int `json:"height"` +} +``` + +## Best Practices + +### ✅ Do + +- **Track windows** - Keep references for easy access +- **Clean up on close** - Use `OnWindowEvent` with `WindowClosing` to prevent memory leaks +- **Use events for communication** - Decoupled architecture +- **Reuse windows** - Don't create duplicates +- **Save/restore layouts** - Better UX +- **Handle window close** - Confirm before closing with unsaved data + +### ❌ Don't + +- **Don't create unlimited windows** - Memory and performance issues +- **Don't forget to clean up** - Memory leaks +- **Don't use global variables carelessly** - Thread-safety issues +- **Don't block window creation** - Create asynchronously if needed +- **Don't ignore platform differences** - Test on all platforms + +## Next Steps + +- [Window Basics](/features/windows/basics) - Learn the fundamentals of window management +- [Window Events](/features/windows/events) - Handle window lifecycle events +- [Events System](/features/events/system) - Deep dive into the event system +- [Frameless Windows](/features/windows/frameless) - Create custom window chrome + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [multi-window example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/multi-window). diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx new file mode 100644 index 000000000..22c3eedd7 --- /dev/null +++ b/docs/src/content/docs/features/windows/options.mdx @@ -0,0 +1,884 @@ +--- +title: Window Options +description: Complete reference for WebviewWindowOptions +sidebar: + order: 2 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Window Configuration Options + +Wails provides comprehensive window configuration with dozens of options for size, position, appearance, and behaviour. This reference covers all available options across Windows, macOS, and Linux as the **complete reference** for `WebviewWindowOptions`. Every option, every platform, with examples and constraints. + +## WebviewWindowOptions Structure + +```go +type WebviewWindowOptions struct { + // Identity + Name string + Title string + + // Size and Position + Width int + Height int + X int + Y int + MinWidth int + MinHeight int + MaxWidth int + MaxHeight int + InitialPosition WindowStartPosition + + // Initial State + Hidden bool + Frameless bool + DisableResize bool + AlwaysOnTop bool + StartState WindowState + + // Appearance + BackgroundColour RGBA + BackgroundType BackgroundType + + // Content + URL string + HTML string + JS string + CSS string + + // Zoom + Zoom float64 + ZoomControlEnabled bool + + // Features + EnableFileDrop bool + OpenInspectorOnStartup bool + DevToolsEnabled bool + DefaultContextMenuDisabled bool + IgnoreMouseEvents bool + + // Security + ContentProtectionEnabled bool + + // Toolbar button states + MinimiseButtonState ButtonState + MaximiseButtonState ButtonState + CloseButtonState ButtonState + + // Key Bindings + KeyBindings map[string]func(window Window) + + // Focus/Escape behaviour + HideOnFocusLost bool + HideOnEscape bool + + // Menu + UseApplicationMenu bool + + // Platform-Specific + Mac MacWindow + Windows WindowsWindow + Linux LinuxWindow +} +``` + +## Core Options + +### Name + +**Type:** `string` +**Default:** Auto-generated (`window-{id}`) +**Platform:** All + +```go +Name: "main-window" +``` + +**Purpose:** Unique identifier for finding windows later. + +**Best practices:** +- Use descriptive names: `"main"`, `"settings"`, `"about"` +- Use kebab-case: `"file-browser"`, `"color-picker"` +- Keep it short and memorable + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings-window", +}) + +// Later... +settings, ok := app.Window.GetByName("settings-window") +``` + +### Title + +**Type:** `string` +**Default:** Application name +**Platform:** All + +```go +Title: "My Application" +``` + +**Purpose:** Text shown in title bar and taskbar. + +**Dynamic updates:** + +```go +window.SetTitle("My Application - Document.txt") +``` + +### Width / Height + +**Type:** `int` (pixels) +**Default:** 800 x 600 +**Platform:** All +**Constraints:** Must be positive + +```go +Width: 1200, +Height: 800, +``` + +**Purpose:** Initial window size in logical pixels. + +**Notes:** +- Wails handles DPI scaling automatically +- Use logical pixels, not physical pixels +- Consider minimum screen resolution (1024x768) + +**Example sizes:** + +| Use Case | Width | Height | +|----------|-------|--------| +| Small utility | 400 | 300 | +| Standard app | 1024 | 768 | +| Large app | 1440 | 900 | +| Full HD | 1920 | 1080 | + +### X / Y + +**Type:** `int` (pixels) +**Default:** Centred on screen +**Platform:** All + +```go +X: 100, // 100px from left edge +Y: 100, // 100px from top edge +``` + +**Purpose:** Initial window position. + +**Coordinate system:** +- (0, 0) is top-left of primary screen +- Positive X goes right +- Positive Y goes down + +**Best practice:** Use `Center()` instead: + +```go +window := app.Window.New() +window.Center() +``` + +### MinWidth / MinHeight + +**Type:** `int` (pixels) +**Default:** 0 (no minimum) +**Platform:** All + +```go +MinWidth: 400, +MinHeight: 300, +``` + +**Purpose:** Prevent window from being too small. + +**Use cases:** +- Prevent broken layouts +- Ensure usability +- Maintain aspect ratio + +**Example:** + +```go +// Prevent window smaller than 400x300 +MinWidth: 400, +MinHeight: 300, +``` + +### MaxWidth / MaxHeight + +**Type:** `int` (pixels) +**Default:** 0 (no maximum) +**Platform:** All + +```go +MaxWidth: 1920, +MaxHeight: 1080, +``` + +**Purpose:** Prevent window from being too large. + +**Use cases:** +- Fixed-size applications +- Prevent excessive resource usage +- Maintain design constraints + +## State Options + +### Hidden + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +Hidden: true, +``` + +**Purpose:** Create window without showing it. + +**Use cases:** +- Background windows +- Windows shown on demand +- Splash screens (create, load, then show) +- Prevent white flash while loading content + +**Platform improvements:** +- **Windows:** Fixed white window flash - window stays invisible until `Show()` is called +- **macOS:** Full support +- **Linux:** Full support + +**Recommended pattern for smooth loading:** + +```go +// Create hidden window +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "main-window", + Hidden: true, + BackgroundColour: application.NewRGBA(30, 30, 30, 255), // Match your theme +}) + +// Load content while hidden +// ... content loads ... + +// Show when ready (no flash!) +window.Show() +``` + +**Example:** + +```go +settings := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Name: "settings", + Hidden: true, +}) + +// Show when needed +settings.Show() +``` + +### Frameless + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +Frameless: true, +``` + +**Purpose:** Remove title bar and window borders. + +**Use cases:** +- Custom window chrome +- Splash screens +- Kiosk applications +- Custom-designed windows + +**Important:** You'll need to implement: +- Window dragging +- Close/minimise/maximise buttons +- Resize handles (if resizable) + +**See [Frameless Windows](/features/windows/frameless) for details.** + +### DisableResize + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +DisableResize: true, +``` + +**Purpose:** Prevent window resizing when set to `true`. + +**Use cases:** +- Fixed-size applications +- Splash screens +- Dialogs + +**Note:** Users can still maximise/fullscreen unless you prevent those too. + +**Runtime control:** + +```go +window.SetResizable(false) // Disable resizing +window.SetResizable(true) // Enable resizing +``` + +### AlwaysOnTop + +**Type:** `bool` +**Default:** `false` +**Platform:** All + +```go +AlwaysOnTop: true, +``` + +**Purpose:** Keep window above all other windows. + +**Use cases:** +- Floating toolbars +- Notifications +- Picture-in-picture +- Timers + +**Platform notes:** +- **macOS:** Full support +- **Windows:** Full support +- **Linux:** Depends on window manager + +### StartState + +**Type:** `WindowState` enum +**Default:** `WindowStateNormal` +**Platform:** All + +```go +StartState: application.WindowStateMaximised, +``` + +**Purpose:** Set the initial window state. + +**Values:** +- `WindowStateNormal` - Normal window +- `WindowStateMinimised` - Minimised +- `WindowStateMaximised` - Maximised +- `WindowStateFullscreen` - Fullscreen + +**Platform behaviour for fullscreen:** +- **macOS:** Creates new Space (virtual desktop) +- **Windows:** Covers taskbar +- **Linux:** Varies by desktop environment + +**Toggle at runtime:** + +```go +window.Fullscreen() +window.UnFullscreen() +window.Maximise() +window.UnMaximise() +window.Minimise() +window.UnMinimise() +``` + +## Appearance Options + +### BackgroundColour + +**Type:** `RGBA` struct +**Default:** White (255, 255, 255, 255) +**Platform:** All + +```go +BackgroundColour: application.NewRGBA(0, 0, 0, 255), +``` + +**Purpose:** Window background colour before content loads. + +**Use cases:** +- Match your app's theme +- Prevent white flash on dark themes +- Smooth loading experience + +**Example:** + +```go +// Dark theme +BackgroundColour: application.NewRGBA(30, 30, 30, 255), + +// Light theme +BackgroundColour: application.NewRGB(255, 255, 255), +``` + +**Helper method:** + +```go +window.SetBackgroundColour(application.NewRGBA(30, 30, 30, 255)) +``` + +### BackgroundType + +**Type:** `BackgroundType` enum +**Default:** `BackgroundTypeSolid` +**Platform:** macOS, Windows (partial) + +```go +BackgroundType: application.BackgroundTypeTranslucent, +``` + +**Values:** +- `BackgroundTypeSolid` - Solid colour +- `BackgroundTypeTransparent` - Fully transparent +- `BackgroundTypeTranslucent` - Semi-transparent blur + +**Platform support:** +- **macOS:** All types supported +- **Windows:** Transparent and Translucent (Windows 11+) +- **Linux:** Solid only + +**Example (macOS):** + +```go +BackgroundType: application.BackgroundTypeTranslucent, +Mac: application.MacWindow{ + Backdrop: application.MacBackdropTranslucent, +}, +``` + +## Content Options + +### URL + +**Type:** `string` +**Default:** Empty (loads from Assets) +**Platform:** All + +```go +URL: "https://example.com", +``` + +**Purpose:** Load external URL instead of embedded assets. + +**Use cases:** +- Development (load from dev server) +- Web-based applications +- Hybrid applications + +**Example:** + +```go +// Development +URL: "http://localhost:5173", + +// Production +Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), +}, +``` + +### HTML + +**Type:** `string` +**Default:** Empty +**Platform:** All + +```go +HTML: "

Hello World

", +``` + +**Purpose:** Load HTML string directly. + +**Use cases:** +- Simple windows +- Generated content +- Testing + +**Example:** + +```go +HTML: ` + + +Simple Window +

Hello from Wails!

+ +`, +``` + +## Security Options + +### ContentProtectionEnabled + +**Type:** `bool` +**Default:** `false` +**Platform:** Windows (10+), macOS + +```go +ContentProtectionEnabled: true, +``` + +**Purpose:** Prevent screen capture of window contents. + +**Platform support:** +- **Windows:** Windows 10 build 19041+ (full), older versions (partial) +- **macOS:** Full support +- **Linux:** Not supported + +**Use cases:** +- Banking applications +- Password managers +- Medical records +- Confidential documents + +**Important notes:** +1. Doesn't prevent physical photography +2. Some tools may bypass protection +3. Part of comprehensive security, not sole protection +4. DevTools windows not protected automatically + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Secure Window", + ContentProtectionEnabled: true, +}) + +// Toggle at runtime +window.SetContentProtection(true) +``` + +## Lifecycle Handling + +Window lifecycle events are handled via `OnWindowEvent` and `RegisterHook` methods after window creation, not through options fields. + +### Preventing Close + +Use `RegisterHook` to intercept close events and optionally cancel them: + +```go +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() // Prevent the window from closing + } +}) +``` + +### Cleanup on Close + +Use `OnWindowEvent` for cleanup when the window closes: + +```go +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + // Close database connection + if db != nil { + db.Close() + } + + // Remove from window list + removeWindow(window.Name()) +}) +``` + +**See [Window Events](/features/windows/events) for details.** + +## Platform-Specific Options + +### Mac Options + +```go +Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, + }, + Backdrop: application.MacBackdropTranslucent, + InvisibleTitleBarHeight: 50, +}, +``` + +**TitleBar** (`MacTitleBar`) +- `AppearsTransparent` - Makes title bar transparent +- `Hide` - Hides the entire title bar +- `HideTitle` - Hides just the title text +- `FullSizeContent` - Extends content to full window size +- `UseToolbar` - Uses a toolbar instead of title bar +- `HideToolbarSeparator` - Hides the toolbar separator +- `ToolbarStyle` - Style of toolbar (`MacToolbarStyleAutomatic`, `MacToolbarStyleExpanded`, etc.) + +**Predefined title bar configurations:** +- `MacTitleBarDefault` - Standard title bar +- `MacTitleBarHidden` - Hidden title bar with traffic lights +- `MacTitleBarHiddenInset` - Hidden with inset traffic lights +- `MacTitleBarHiddenInsetUnified` - Hidden with unified toolbar style + +**DisableShadow** (`bool`) +- Disable the window shadow + +**Backdrop** (`MacBackdrop`) +- `MacBackdropNormal` - Standard opaque background +- `MacBackdropTransparent` - Fully transparent +- `MacBackdropTranslucent` - Blurred translucent +- `MacBackdropLiquidGlass` - Apple's Liquid Glass effect (macOS 15.0+, falls back to translucent) + +**InvisibleTitleBarHeight** (`int`) +- Height of invisible title bar (for dragging) + +**Appearance** (`MacAppearanceType`) +- `DefaultAppearance` - System default +- `NSAppearanceNameAqua` - Light appearance +- `NSAppearanceNameDarkAqua` - Dark appearance +- `NSAppearanceNameVibrantLight` - Light vibrant appearance +- `NSAppearanceNameAccessibilityHighContrastAqua` - High-contrast light +- `NSAppearanceNameAccessibilityHighContrastDarkAqua` - High-contrast dark +- `NSAppearanceNameAccessibilityHighContrastVibrantLight` - High-contrast vibrant light +- `NSAppearanceNameAccessibilityHighContrastVibrantDark` - High-contrast vibrant dark + +**EnableFraudulentWebsiteWarnings** (`bool`) +- Enable warnings for fraudulent websites + +**WebviewPreferences** (`MacWebviewPreferences`) +- `TabFocusesLinks` - Enable tabbing to links +- `TextInteractionEnabled` - Enable text interaction +- `FullscreenEnabled` - Enable fullscreen +- `AllowsBackForwardNavigationGestures` - Enable horizontal swipe gestures for back/forward navigation + +**WindowLevel** (`MacWindowLevel`) +- Controls the window level ordering. Values: `MacWindowLevelNormal`, `MacWindowLevelFloating`, `MacWindowLevelTornOffMenu`, `MacWindowLevelModalPanel`, `MacWindowLevelMainMenu`, `MacWindowLevelStatus`, `MacWindowLevelPopUpMenu`, `MacWindowLevelScreenSaver` + +**CollectionBehavior** (`MacWindowCollectionBehavior`) +- Controls how the window behaves across macOS Spaces and fullscreen. Values can be combined with bitwise OR. Includes: `MacWindowCollectionBehaviorCanJoinAllSpaces`, `MacWindowCollectionBehaviorMoveToActiveSpace`, `MacWindowCollectionBehaviorFullScreenPrimary`, `MacWindowCollectionBehaviorFullScreenAuxiliary`, etc. + +**EventMapping** (`map[events.WindowEventType]events.WindowEventType`) +- Maps platform-specific events to common event types + +**LiquidGlass** (`MacLiquidGlass`) +- Configuration for the Liquid Glass effect: `Style`, `Material`, `CornerRadius`, `TintColor`, `GroupID`, `GroupSpacing` + +**ShowToolbarWhenFullscreen** (in `MacTitleBar`) +- Keep the toolbar visible when the window is in fullscreen mode + +**Example:** + +```go +Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + Backdrop: application.MacBackdropTranslucent, + InvisibleTitleBarHeight: 50, +}, +``` + +### Windows Options + +```go +Windows: application.WindowsWindow{ + DisableIcon: false, + BackdropType: application.Mica, + CustomTheme: nil, + DisableFramelessWindowDecorations: false, +}, +``` + +**DisableIcon** (`bool`) +- Remove icon from title bar +- Cleaner appearance + +**BackdropType** (`BackdropType`) +- `Auto` - System default +- `None` - No backdrop +- `Mica` - Mica material (Windows 11 22621+) +- `Acrylic` - Acrylic material (Windows 11 22621+) +- `Tabbed` - Tabbed material (Windows 11 22621+) + +**Theme** (`Theme`) +- `SystemDefault` - Follow system theme +- `Dark` - Dark mode +- `Light` - Light mode + +**CustomTheme** (`ThemeSettings`) +- Custom colour theme for dark/light mode +- Override system colours + +**DisableFramelessWindowDecorations** (`bool`) +- Disable default frameless decorations (shadow and rounded corners) +- For custom window chrome + +**WindowMask** (`[]byte`) +- Set the window shape using a PNG with an alpha channel + +**WindowMaskDraggable** (`bool`) +- Make the window draggable by clicking on the window mask + +**ResizeDebounceMS** (`uint16`) +- Amount of time in milliseconds to debounce redraws of webview2 when resizing + +**WindowDidMoveDebounceMS** (`uint16`) +- Amount of time in milliseconds to debounce the WindowDidMove event when moving + +**EventMapping** (`map[events.WindowEventType]events.WindowEventType`) +- Maps platform-specific events to common event types + +**HiddenOnTaskbar** (`bool`) +- Hide the window from the taskbar + +**EnableSwipeGestures** (`bool`) +- Enable swipe gestures for the window + +**Menu** (`*Menu`) +- The menu to use for the window + +**Permissions** (`map[CoreWebView2PermissionKind]CoreWebView2PermissionState`) +- WebView2 permissions map. If empty, default permissions will be granted. + +**ExStyle** (`int`) +- Extended window style + +**GeneralAutofillEnabled** (`bool`) +- Enable general autofill + +**PasswordAutosaveEnabled** (`bool`) +- Enable autosaving passwords + +**Example:** + +```go +Windows: application.WindowsWindow{ + BackdropType: application.Mica, + DisableIcon: true, + Theme: application.Dark, +}, +``` + +### Linux Options + +```go +Linux: application.LinuxWindow{ + Icon: []byte{/* PNG data */}, + WindowIsTranslucent: false, +}, +``` + +**Icon** (`[]byte`) +- Window icon (PNG format) +- Shown in title bar and taskbar + +**WindowIsTranslucent** (`bool`) +- Enable window translucency +- Requires compositor support + +**WebviewGpuPolicy** (`WebviewGpuPolicy`) +- `WebviewGpuPolicyAlways` - Hardware acceleration always enabled +- `WebviewGpuPolicyOnDemand` - Enabled/disabled as requested by web contents +- `WebviewGpuPolicyNever` - Hardware acceleration always disabled + +**WindowDidMoveDebounceMS** (`uint16`) +- Debounce time in milliseconds for the WindowDidMove event + +**Menu** (`*Menu`) +- The window's menu + +**MenuStyle** (`LinuxMenuStyle`) - GTK4 only, ignored on GTK3 +- `LinuxMenuStyleMenuBar` - Traditional menu bar below the title bar (default) +- `LinuxMenuStylePrimaryMenu` - Primary menu button in the header bar (GNOME style) + +**Example:** + +```go +//go:embed icon.png +var icon []byte + +Linux: application.LinuxWindow{ + Icon: icon, +}, +``` + +## Complete Example + +Here's a production-ready window configuration: + +```go +package main + +import ( + _ "embed" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +//go:embed icon.png +var icon []byte + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + // Identity + Name: "main-window", + Title: "My Application", + + // Size and Position + Width: 1200, + Height: 800, + MinWidth: 800, + MinHeight: 600, + + // Initial State + StartState: application.WindowStateNormal, + + // Appearance + BackgroundColour: application.NewRGB(255, 255, 255), + + // Platform-Specific + Mac: application.MacWindow{ + TitleBar: application.MacTitleBar{ + AppearsTransparent: true, + }, + Backdrop: application.MacBackdropTranslucent, + }, + + Windows: application.WindowsWindow{ + BackdropType: application.Mica, + DisableIcon: false, + }, + + Linux: application.LinuxWindow{ + Icon: icon, + }, + }) + + // Handle close events after creation + window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + if hasUnsavedChanges() { + e.Cancel() + } + }) + + window.Center() + window.Show() + + app.Run() +} +``` + +## Next Steps + +- [Window Basics](/features/windows/basics) - Creating and controlling windows +- [Multiple Windows](/features/windows/multiple) - Multi-window patterns +- [Frameless Windows](/features/windows/frameless) - Custom window chrome +- [Window Events](/features/windows/events) - Lifecycle events + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/feedback.mdx b/docs/src/content/docs/feedback.mdx new file mode 100644 index 000000000..afe4a8bfb --- /dev/null +++ b/docs/src/content/docs/feedback.mdx @@ -0,0 +1,86 @@ +--- +title: v3 Alpha Feedback +sidebar: + order: 40 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +We welcome (and encourage) your feedback! Please search for existing tickets or +posts before creating new ones. Here are the different ways to provide feedback: + + + + + If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord. + + - The post should clearly state what the bug is and have a simple reproducible example. If the docs are unclear what *should* happen, please include that in the post. + - The post should be given the `Bug` tag. + - Please include the output of `wails doctor` in your post. + - If the bug is behavior that does not align with current documentation, e.g. a window does not resize properly, please do the following: + - Update an existing example in the `v3/example` directory or create a new example in the `v3/examples` folder that clearly shows the issue. + - Open a [PR](https://github.com/wailsapp/wails/pulls) with the title `[v3 alpha test] `. + - Please include a link to the PR in your post. + + :::caution + + _Remember_, unexpected behavior isn't necessarily a bug - it might just not do + what you expect it to do. Use `Suggestions` for this. + + ::: + + + + + + If you have a fix for a bug or an update for documentation, please do the following: + + - Open a pull request on the [Wails repository](https://github.com/wailsapp/wails). The title of the PR should start with `[v3 alpha]`. + - Create a post in the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel. + - The post should be given the `PR` tag. + - Please include a link to the PR in your post. + + + + + + If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord: + + - The post should be given the `Suggestion` tag. + + Please feel free to reach out to us on [Discord](https://discord.gg/bdj28QNHmT) if you have any questions. + + + + + + - Posts can be "upvoted" by using the :thumbsup: emoji. Please apply to any posts that are a priority for you. + - Please *don't* just add comments like "+1" or "me too". + - Please feel free to comment if there is more to add to the post, such as "this bug also affect ARM builds" or "Another option would be to ....." + + + + + +There is a list of known issues & work in progress can be found +[here](https://github.com/orgs/wailsapp/projects/6). + +## Things we are looking for feedback on + +- The API + - Is it easy to use? + - Does it do what you expect? + - Is it missing anything? + - Is there anything that should be removed? + - Is it consistent between Go and JS? +- The build system + - Is it easy to use? + - Can we improve it? +- The examples + - Are they clear? + - Do they cover the basics? +- Features + - What features are missing? + - What features are not needed? +- Documentation + - What could be clearer? diff --git a/docs/src/content/docs/getting-started/installation.mdx b/docs/src/content/docs/getting-started/installation.mdx new file mode 100644 index 000000000..0049a9735 --- /dev/null +++ b/docs/src/content/docs/getting-started/installation.mdx @@ -0,0 +1,114 @@ +--- +title: Installation +sidebar: + order: 10 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Supported Platforms + +- Windows 10/11 AMD64/ARM64 +- macOS 10.15+ AMD64 (Can deploy to macOS 10.13+) +- macOS 11.0+ ARM64 +- Ubuntu 24.04 AMD64/ARM64 (other Linux may work too!) + +## Dependencies + +Wails has a number of common dependencies that are required before installation: + + + + + Download Go from the [Go Downloads Page](https://go.dev/dl/). + + Ensure that you follow the official [Go installation instructions](https://go.dev/doc/install). You will also need to ensure that your `PATH` environment variable also includes the path to your `~/go/bin` directory. Restart your terminal and do the following checks: + + - Check Go is installed correctly: `go version` + - Check `~/go/bin` is in your PATH variable + - Mac / Linux: `echo $PATH | grep go/bin` + - Windows: `$env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' }` + + + + + + Although Wails doesn't require npm to be installed, it is needed by most of the bundled templates. + + Download the latest node installer from the [Node Downloads Page](https://nodejs.org/en/download/). It is best to use the latest release as that is what we generally test against. + + Run `npm --version` to verify. + + :::note + If you prefer a different package manager to npm, feel free to use it. You will need to update the project Taskfiles to use it. + ::: + + + + + +## Platform Specific Dependencies + +You will also need to install platform specific dependencies: + + + + + Wails requires that the xcode command line tools are installed. This can be + done by running: + + ```sh + xcode-select --install + ``` + + + + + Wails requires that the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) is installed. Almost all Windows installations will already have this installed. You can check using the `wails3 doctor` command. + + + + + Linux requires the standard `gcc` build tools plus `gtk3` and `webkit2gtk`. Run wails3 doctor after installation to be shown how to install the dependencies. If your distro/package manager is not supported, please let us know on discord. + + + + + +## Installation + +To install the Wails CLI using Go Modules, run the following commands: + +```shell +go install -v github.com/wailsapp/wails/v3/cmd/wails3@latest +``` + +If you would like to install the latest development version, run the following +commands: + +```shell +git clone https://github.com/wailsapp/wails.git +cd wails +git checkout v3-alpha +cd v3/cmd/wails3 +go install +``` + +When using the development version, all generated projects will use Go's +[replace](https://go.dev/ref/mod#go-mod-file-replace) directive to ensure +projects use the development version of Wails. + +## System Check + +Running `wails3 doctor` will check if you have the correct dependencies +installed. If not, it will advise on what is missing and help on how to rectify +any problems. + +## The `wails3` command appears to be missing? + +If your system is reporting that the `wails3` command is missing, check the +following: + +- Make sure you have followed the above `Go installation guide` correctly and + that the `go/bin` directory is in the `PATH` environment variable. +- Close/Reopen current terminals to pick up the new `PATH` variable. diff --git a/docs/src/content/docs/getting-started/your-first-app.mdx b/docs/src/content/docs/getting-started/your-first-app.mdx new file mode 100644 index 000000000..143e66256 --- /dev/null +++ b/docs/src/content/docs/getting-started/your-first-app.mdx @@ -0,0 +1,272 @@ +--- +title: Your First Application +sidebar: + order: 20 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; +import { Badge } from '@astrojs/starlight/components'; +import { Steps } from "@astrojs/starlight/components"; +import { FileTree } from '@astrojs/starlight/components'; + +import wails_init from '../../../assets/wails_init.mp4'; +import wails_build from '../../../assets/wails_build.mp4'; +import wails_dev from '../../../assets/wails_dev.mp4'; + + +This guide shows you how to create your first Wails v3 application, covering project setup, building, and development workflow. + +
+
+ + + +1. ## Creating a New Project + + Open your terminal and run the following command to create a new Wails + project: + + ```bash + wails3 init -n myfirstapp + ``` + + This command creates a new directory called `myfirstapp` with all the + necessary files. + + + +2. ## Exploring the Project Structure + + Navigate to the `myfirstapp` directory. You'll find several files and + folders: + + + - build/ Contains files used by the build process + - appicon.png The application icon + - config.yml Build configuration + - Taskfile.yml Build tasks + - darwin/ macOS specific build files + - Info.dev.plist Development configuration + - Info.plist Production configuration + - Taskfile.yml macOS build tasks + - icons.icns macOS application icon + - linux/ Linux specific build files + - Taskfile.yml Linux build tasks + - appimage/ AppImage packaging + - build.sh AppImage build script + - nfpm/ NFPM packaging + - nfpm.yaml Package configuration + - scripts/ Build scripts + - windows/ Windows specific build files + - Taskfile.yml Windows build tasks + - icon.ico Windows application icon + - info.json Application metadata + - wails.exe.manifest Windows manifest file + - nsis/ NSIS installer files + - project.nsi NSIS project file + - wails_tools.nsh NSIS helper scripts + - frontend/ Frontend application files + - index.html Main HTML file + - main.js Main JavaScript file + - package.json NPM package configuration + - public/ Static assets + - Inter Font License.txt Font license + - .gitignore Git ignore file + - README.md Project documentation + - Taskfile.yml Project tasks + - go.mod Go module file + - go.sum Go module checksums + - greetservice.go Greeting service + - main.go Main application code + + + Take a moment to explore these files and familiarize yourself with the + structure. + + :::note + Although Wails v3 uses [Task](https://taskfile.dev/) as its + default build system, there is nothing stopping you from using `make` or any other + alternative build system. + ::: + +3. ## Building Your Application + + To build your application, execute: + + ```bash + wails3 build + ``` + + This command compiles a debug version of your application and saves it in a + new `bin` directory. + + :::note + `wails3 build` is shorthand for `wails3 task build` and will run the `build` task in `Taskfile.yml`. + ::: + + + + + Once built, you can run this like you would any normal application: + + + + + + + ```sh + ./bin/myfirstapp + ``` + + + + + + ```sh + bin\myfirstapp.exe + ``` + + + + + + ```sh + ./bin/myfirstapp + ``` + + + + + + You'll see a simple UI, the starting point for your application. As it is + the debug version, you'll also see logs in the console window. This is + useful for debugging purposes. + +4. ## Dev Mode + + We can also run the application in development mode. This mode allows you to + make changes to your frontend code and see the changes reflected in the + running application without having to rebuild the entire application. + + 1. Open a new terminal window. + 2. Run `wails3 dev`. The application will compile and run in debug mode. + 3. Open `frontend/index.html` in your editor of choice. + 4. Edit the code and change `Please enter your name below` to + `Please enter your name below!!!`. + 5. Save the file. + + This change will reflect in your application immediately. + + Any changes to backend code will trigger a rebuild: + + 1. Open `greetservice.go`. + 2. Change the line that has `return "Hello " + name + "!"` to + `return "Hello there " + name + "!"`. + 3. Save the file. + + The application will update within a matter of seconds. + + + +5. ## Packaging Your Application + + Once your application is ready for distribution, you can create + platform-specific packages: + + + + + + To create a `.app` bundle: + + ```bash + wails3 package + ``` + + This will create a production build and package it into a `.app` bundle in the `bin` directory. + + + + + To create an NSIS installer: + + ```bash + wails3 package + ``` + + This will create a production build and package it into an NSIS installer in the `bin` directory. + + + + + Wails supports multiple package formats for Linux distribution: + + ```bash + # Create all package types (AppImage, deb, rpm, and Arch Linux) + wails3 package + + # Or create specific package types + wails3 task linux:create:appimage # AppImage format + wails3 task linux:create:deb # Debian package + wails3 task linux:create:rpm # Red Hat package + wails3 task linux:create:aur # Arch Linux package + ``` + + + + + + For more detailed information about packaging options and configuration, + check out our [Packaging Guide](/guides/packaging). + +6. ## Setting up Version Control and Module Name + + Your project is created with the placeholder module name `changeme`. It's recommended to update this to match your repository URL: + + 1. Create a new repository on GitHub (or your preferred Git host) + 2. Initialize git in your project directory: + ```bash + git init + git add . + git commit -m "Initial commit" + ``` + 3. Set your remote repository (replace with your repository URL): + ```bash + git remote add origin https://github.com/username/myfirstapp.git + ``` + 4. Update your module name in `go.mod` to match your repository URL: + ```bash + go mod edit -module github.com/username/myfirstapp + ``` + 5. Push your code: + ```bash + git push -u origin main + ``` + + This ensures your Go module name aligns with Go's module naming conventions and makes it easier to share your code. + + :::tip[Pro Tip] + You can automate all of the initialisation steps by using the `-git` flag when creating your project: + ```bash + wails3 init -n myfirstapp -git github.com/username/myfirstapp + ``` + This supports various Git URL formats: + - HTTPS: `https://github.com/username/project` + - SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project` + - Git protocol: `git://github.com/username/project` + - Filesystem: `file:///path/to/project.git` + ::: + + + +## Congratulations! + +You've just created, developed and packaged your first Wails application. +This is just the beginning of what you can achieve with Wails v3. + +## Next Steps + +If you are new to Wails, we recommend reading through our Tutorials next which will be a practical guide through +the various features of Wails. The first tutorial is [Creating a Service](/tutorials/01-creating-a-service). + +If you are a more advanced user, check out our [Guides](/guides) for more detailed information on how to use Wails. diff --git a/docs/src/content/docs/guides/_category_.json b/docs/src/content/docs/guides/_category_.json new file mode 100644 index 000000000..edc4bf6e3 --- /dev/null +++ b/docs/src/content/docs/guides/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Guides", + "position": 3, + "link": { + "type": "generated-index", + "description": "Comprehensive guides for developing Wails applications" + } +} diff --git a/docs/src/content/docs/guides/architecture.mdx b/docs/src/content/docs/guides/architecture.mdx new file mode 100644 index 000000000..2b08708fc --- /dev/null +++ b/docs/src/content/docs/guides/architecture.mdx @@ -0,0 +1,199 @@ +--- +title: Architecture Patterns +description: Design patterns for Wails applications +sidebar: + order: 7 +--- + +## Overview + +Proven patterns for organising your Wails application. + +## Service Layer Pattern + +### Structure + +``` +app/ +├── main.go +├── services/ +│ ├── user_service.go +│ ├── data_service.go +│ └── file_service.go +└── models/ + └── user.go +``` + +### Implementation + +```go +// Service interface +type UserService interface { + Create(email, password string) (*User, error) + GetByID(id int) (*User, error) + Update(user *User) error + Delete(id int) error +} + +// Implementation +type userService struct { + app *application.App + db *sql.DB +} + +func NewUserService(app *application.App, db *sql.DB) UserService { + return &userService{app: app, db: db} +} +``` + +## Repository Pattern + +### Structure + +```go +// Repository interface +type UserRepository interface { + Create(user *User) error + FindByID(id int) (*User, error) + Update(user *User) error + Delete(id int) error +} + +// Service uses repository +type UserService struct { + repo UserRepository +} + +func (s *UserService) Create(email, password string) (*User, error) { + user := &User{Email: email} + return user, s.repo.Create(user) +} +``` + +## Event-Driven Architecture + +### Event Bus + +```go +type EventBus struct { + app *application.App + listeners map[string][]func(interface{}) + mu sync.RWMutex +} + +func (eb *EventBus) Subscribe(event string, handler func(interface{})) { + eb.mu.Lock() + defer eb.mu.Unlock() + eb.listeners[event] = append(eb.listeners[event], handler) +} + +func (eb *EventBus) Publish(event string, data interface{}) { + eb.mu.RLock() + handlers := eb.listeners[event] + eb.mu.RUnlock() + + for _, handler := range handlers { + go handler(data) + } +} +``` + +### Usage + +```go +// Subscribe +eventBus.Subscribe("user.created", func(data interface{}) { + user := data.(*User) + sendWelcomeEmail(user) +}) + +// Publish +eventBus.Publish("user.created", user) +``` + +## Dependency Injection + +### Manual DI + +```go +type App struct { + userService *UserService + fileService *FileService + db *sql.DB +} + +func NewApp() *App { + db := openDatabase() + + return &App{ + db: db, + userService: NewUserService(db), + fileService: NewFileService(db), + } +} +``` + +### Using Wire + +```go +// wire.go +//go:build wireinject + +func InitializeApp() (*App, error) { + wire.Build( + openDatabase, + NewUserService, + NewFileService, + NewApp, + ) + return nil, nil +} +``` + +## State Management + +### Centralised State + +```go +type AppState struct { + currentUser *User + settings *Settings + mu sync.RWMutex +} + +func (s *AppState) SetUser(user *User) { + s.mu.Lock() + defer s.mu.Unlock() + s.currentUser = user +} + +func (s *AppState) GetUser() *User { + s.mu.RLock() + defer s.mu.RUnlock() + return s.currentUser +} +``` + +## Best Practices + +### ✅ Do + +- Separate concerns +- Use interfaces +- Inject dependencies +- Handle errors properly +- Keep services focused +- Document architecture + +### ❌ Don't + +- Don't create god objects +- Don't tightly couple components +- Don't skip error handling +- Don't ignore concurrency +- Don't over-engineer + +## Next Steps + +- [Security](/guides/security) - Security best practices +- [Best Practices](/features/bindings/best-practices) - Bindings best practices diff --git a/docs/src/content/docs/guides/auto-updates.mdx b/docs/src/content/docs/guides/auto-updates.mdx new file mode 100644 index 000000000..7cb36adf7 --- /dev/null +++ b/docs/src/content/docs/guides/auto-updates.mdx @@ -0,0 +1,172 @@ +--- +title: Auto-Updates +description: Implement automatic application updates +sidebar: + order: 4 +--- + +## Overview + +Keep your application up-to-date with automatic updates. + +## Update Strategies + +### 1. Check on Startup + +```go +func (a *App) checkForUpdates() { + latest, err := getLatestVersion() + if err != nil { + return + } + + if isNewer(latest, currentVersion) { + a.promptUpdate(latest) + } +} +``` + +### 2. Periodic Checks + +```go +func (a *App) startUpdateChecker() { + ticker := time.NewTicker(24 * time.Hour) + go func() { + for range ticker.C { + a.checkForUpdates() + } + }() +} +``` + +### 3. Manual Check + +```go +func (a *App) CheckForUpdates() { + dialog := a.app.Dialog.Question(). + SetMessage("Check for updates?") + + dialog.AddButton("Check").OnClick(func() { + a.checkForUpdates() + }) + dialog.AddButton("Cancel") + + dialog.Show() +} +``` + +## Implementation + +### Version Checking + +```go +type UpdateInfo struct { + Version string `json:"version"` + DownloadURL string `json:"download_url"` + ReleaseNotes string `json:"release_notes"` +} + +func getLatestVersion() (*UpdateInfo, error) { + resp, err := http.Get("https://api.example.com/latest") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var info UpdateInfo + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + return nil, err + } + + return &info, nil +} +``` + +### Download and Install + +```go +func (a *App) downloadUpdate(url string) error { + // Download update + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Save to temp file + tmpFile, err := os.CreateTemp("", "update-*.exe") + if err != nil { + return err + } + defer tmpFile.Close() + + // Copy data + if _, err := io.Copy(tmpFile, resp.Body); err != nil { + return err + } + + // Launch installer and quit + return a.installUpdate(tmpFile.Name()) +} +``` + +## Third-Party Solutions + +### Squirrel + +```go +import "github.com/Squirrel/go-squirrel" + +func setupUpdater() { + updater := squirrel.NewUpdater(squirrel.Options{ + URL: "https://updates.example.com", + }) + + updater.CheckForUpdates() +} +``` + +### Self-Hosted + +Host update manifests on your own server: + +```json +{ + "version": "1.0.1", + "platforms": { + "windows": { + "url": "https://example.com/myapp-1.0.1-windows.exe", + "sha256": "..." + }, + "darwin": { + "url": "https://example.com/myapp-1.0.1-macos.dmg", + "sha256": "..." + } + }, + "release_notes": "Bug fixes and improvements" +} +``` + +## Best Practices + +### ✅ Do + +- Verify update signatures +- Show release notes +- Allow users to skip versions +- Test update process thoroughly +- Provide rollback mechanism +- Use HTTPS for downloads + +### ❌ Don't + +- Don't force immediate updates +- Don't skip signature verification +- Don't interrupt user work +- Don't forget error handling +- Don't lose user data + +## Next Steps + +- [Creating Installers](/guides/installers) - Package your application +- [Testing](/guides/testing) - Test your application diff --git a/docs/src/content/docs/guides/build/customization.mdx b/docs/src/content/docs/guides/build/customization.mdx new file mode 100644 index 000000000..041738ddf --- /dev/null +++ b/docs/src/content/docs/guides/build/customization.mdx @@ -0,0 +1,282 @@ +--- +title: Build Customization +description: Customize your build process using Task and Taskfile.yml +sidebar: + order: 1 +--- + +import { FileTree } from "@astrojs/starlight/components"; + +## Overview + +The Wails build system is a flexible and powerful tool designed to streamline +the build process for your Wails applications. It leverages +[Task](https://taskfile.dev), a task runner that allows you to define and run +tasks easily. While the v3 build system is the default, Wails encourages a +"bring your own tooling" approach, allowing developers to customize their build +process as needed. + +Learn more about how to use Task in the +[official documentation](https://taskfile.dev/usage/). + +## Task: The Heart of the Build System + +[Task](https://taskfile.dev) is a modern alternative to Make, written in Go. It +uses a YAML file to define tasks and their dependencies. In the Wails build +system, [Task](https://taskfile.dev) plays a central role in orchestrating the +build process. + +The main `Taskfile.yml` is located in the project root, while platform-specific +tasks are defined in `build//Taskfile.yml` files. A common `Taskfile.yml` +file in the `build` directory contains common tasks that are shared across +platforms. + + + +- Project Root + - Taskfile.yml + - build + - windows/Taskfile.yml + - darwin/Taskfile.yml + - linux/Taskfile.yml + - Taskfile.yml + + + +## Taskfile.yml + +The `Taskfile.yml` file in the project root is the main entry point for the build system. It defines +the tasks and their dependencies. Here's the default `Taskfile.yml` file: + +```yaml +version: '3' + +includes: + common: ./build/Taskfile.yml + windows: ./build/windows/Taskfile.yml + darwin: ./build/darwin/Taskfile.yml + linux: ./build/linux/Taskfile.yml + +vars: + APP_NAME: "myproject" + BIN_DIR: "bin" + VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' + +tasks: + build: + summary: Builds the application + cmds: + - task: "{{OS}}:build" + + package: + summary: Packages a production build of the application + cmds: + - task: "{{OS}}:package" + + run: + summary: Runs the application + cmds: + - task: "{{OS}}:run" + + dev: + summary: Runs the application in development mode + cmds: + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} + + +``` + +## Platform-Specific Taskfiles + +Each platform has its own Taskfile, located in the platform directories beneath the `build` directory. These +files define the core tasks for that platform. Each taskfile includes common tasks from the `build/Taskfile.yml` file. + +### Windows + +Location: `build/windows/Taskfile.yml` + +The Windows-specific Taskfile includes tasks for building, packaging, and +running the application on Windows. Key features include: + +- Building with optional production flags +- Generating `.ico` icon file +- Generating Windows `.syso` file +- Creating an NSIS installer for packaging + +### Linux + +Location: `build/linux/Taskfile.yml` + +The Linux-specific Taskfile includes tasks for building, packaging, and running +the application on Linux. Key features include: + +- Building with optional production flags +- Creating an AppImage, deb, rpm, and Arch Linux packages +- Generating `.desktop` file for Linux applications + +### macOS + +Location: `build/darwin/Taskfile.yml` + +The macOS-specific Taskfile includes tasks for building, packaging, and running +the application on macOS. Key features include: + +- Building binaries for amd64, arm64 and universal (both) architectures +- Generating `.icns` icon file +- Creating an `.app` bundle for distributing +- Ad-hoc signing `.app` bundles +- Setting macOS-specific build flags and environment variables + +## Task Execution and Command Aliases + +The `wails3 task` command is an embedded version of [Taskfile](https://taskfile.dev), which executes +the tasks defined in your `Taskfile.yml`. + +The `wails3 build` and `wails3 package` commands are aliases for +`wails3 task build` and `wails3 task package` respectively. When you run these +commands, Wails internally translates them to the appropriate task execution: + +- `wails3 build` → `wails3 task build` +- `wails3 package` → `wails3 task package` + +### Passing Parameters to Tasks + +You can pass CLI variables to tasks using the `KEY=VALUE` format. These variables are forwarded through the alias commands: + +```bash +# These are equivalent: +wails3 build PLATFORM=linux CONFIG=production +wails3 task build PLATFORM=linux CONFIG=production + +# Package with custom version: +wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg +``` + +In your `Taskfile.yml`, you can access these variables using Go template syntax: + +```yaml +tasks: + build: + cmds: + - echo "Building for {{.PLATFORM | default "darwin"}}" + - go build -tags {{.CONFIG | default "debug"}} -o myapp +``` + +## Common Build Process + +Across all platforms, the build process typically includes the following steps: + +1. Tidying Go modules +2. Building the frontend +3. Generating icons +4. Compiling the Go code with platform-specific flags +5. Packaging the application (platform-specific) + +## Customising the Build Process + +While the v3 build system provides a solid default configuration, you can easily +customise it to fit your project's needs. By modifying the `Taskfile.yml` and +platform-specific Taskfiles, you can: + +- Add new tasks +- Modify existing tasks +- Change the order of task execution +- Integrate with other tools and scripts + +This flexibility allows you to tailor the build process to your specific +requirements while still benefiting from the structure provided by the Wails +build system. + +:::tip[Learning Taskfile] +We highly recommend reading the [Taskfile](https://taskfile.dev) documentation to +understand how to use Taskfile effectively. +You can find out which version of Taskfile is embedded in the Wails CLI by running `wails3 task --version`. +::: + +## Development Mode + +The Wails build system includes a powerful development mode that enhances the +developer experience by providing live reloading and hot module replacement. +This mode is activated using the `wails3 dev` command. + +### How It Works + +When you run `wails3 dev`, the following process occurs: + +1. The command checks for an available port, defaulting to 9245 if not + specified. +2. It sets up the environment variables for the frontend dev server (Vite). +3. It starts the file watcher using the [refresh](https://github.com/atterpac/refresh) library. + +The [refresh](https://github.com/atterpac/refresh) library is responsible for +monitoring file changes and triggering rebuilds. It uses the configuration defined under the `dev_mode` key in the `./build/config.yaml` file. +It may be configured to ignore certain directories and files, to determine which files to watch and what actions to take when changes are detected. +The default configuration works pretty well, but feel free to customise it to your needs. + +### Configuration + +Here's an example of its structure: + +```yaml +dev_mode: + root_path: . + log_level: warn + debounce: 1000 + ignore: + dir: + - .git + - node_modules + - frontend + - bin + file: + - .DS_Store + - .gitignore + - .gitkeep + watched_extension: + - "*.go" + git_ignore: true + executes: + - cmd: wails3 task common:install:frontend:deps + type: once + - cmd: wails3 task common:dev:frontend + type: background + - cmd: go mod tidy + type: blocking + - cmd: wails3 task build + type: blocking + - cmd: wails3 task run + type: primary +``` + +This configuration file allows you to: + +- Set the root path for file watching +- Configure logging level +- Set a debounce time for file change events +- Ignore specific directories, files, or file extensions +- Define commands to execute on file changes + +### Customising Development Mode + +You can customise the development mode experience by modifying these values in the `config.yaml` file. + +Some ways to customise include: + +1. Changing the watched directories or files +2. Adjusting the debounce time to control how quickly the system responds to + changes +3. Adding or modifying the execute commands to fit your project's needs + +### Using a browser for development + +Whilst Wails v2 fully supported the use of a browser for development, it caused a lot +of confusion. Applications that would work in the browser would not necessarily +work in the desktop application, as not all browser APIs are available in webviews. + +For UI-focused development work, you still have the flexibility to use a browser +in v3, by accessing the Vite URL at `http://localhost:9245` in dev mode. This +gives you access to powerful browser dev tools while working on styling and +layout. Be aware that Go bindings *will not work* in this mode. +When you're ready to test functionality like bindings and events, simply +switch to the desktop view to ensure everything works perfectly in the +production environment. diff --git a/docs/src/content/docs/guides/build/windows.mdx b/docs/src/content/docs/guides/build/windows.mdx new file mode 100644 index 000000000..225d08b28 --- /dev/null +++ b/docs/src/content/docs/guides/build/windows.mdx @@ -0,0 +1,409 @@ +--- +title: Windows Packaging +description: Package your Wails application for Windows distribution +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +Learn how to package your Wails application for Windows distribution using various installer formats. + +## Overview + +Wails provides several packaging options for Windows: +- **NSIS** - Nullsoft Scriptable Install System (recommended) +- **MSI** - Windows Installer Package +- **Portable** - Standalone executable (no installation) +- **MSIX** - Modern Windows app package + +## NSIS Installer + +NSIS creates professional installers with customization options and automatic system integration. + +### Quick Start + +Build an NSIS installer: + +```bash +wails3 build +wails3 task nsis +``` + +This generates `build/bin/---installer.exe`. + +### Features + +**Automatic Integration:** +- ✅ Start Menu shortcuts +- ✅ Desktop shortcuts (optional) +- ✅ File associations +- ✅ **Custom URL protocol registration** (NEW) +- ✅ Uninstaller creation +- ✅ Registry entries + +**Custom Protocols:** + +Wails automatically registers custom URL protocols defined in your application: + +```go +app := application.New(application.Options{ + Name: "My Application", + Protocols: []application.Protocol{ + { + Scheme: "myapp", + Description: "My Application Protocol", + }, + }, +}) +``` + +The NSIS installer: +1. Registers all protocols during installation +2. Associates them with your application executable +3. Removes them during uninstallation + +**No additional configuration needed!** + +### Customization + +Customize the installer via `wails.json`: + +```json +{ + "name": "MyApp", + "outputfilename": "myapp.exe", + "nsis": { + "companyName": "My Company", + "productName": "My Application", + "productDescription": "An amazing desktop application", + "productVersion": "1.0.0", + "installerIcon": "build/appicon.ico", + "license": "LICENSE.txt", + "allowInstallDirCustomization": true, + "installDirectory": "$PROGRAMFILES64\\MyApp", + "createDesktopShortcut": true, + "runAfterInstall": true, + "adminPrivileges": false + } +} +``` + +### NSIS Options + +| Option | Type | Description | Default | +|--------|------|-------------|---------| +| `companyName` | string | Company name shown in installer | Project name | +| `productName` | string | Product name | App name | +| `productDescription` | string | Description shown in installer | App description | +| `productVersion` | string | Version number (e.g., "1.0.0") | "1.0.0" | +| `installerIcon` | string | Path to .ico file for installer | App icon | +| `license` | string | Path to license file (txt) | None | +| `allowInstallDirCustomization` | boolean | Let users choose install location | true | +| `installDirectory` | string | Default installation directory | `$PROGRAMFILES64\` | +| `createDesktopShortcut` | boolean | Create desktop shortcut | true | +| `runAfterInstall` | boolean | Run app after installation | false | +| `adminPrivileges` | boolean | Require admin rights to install | false | + +### Advanced NSIS + +#### Custom NSIS Script + +For advanced customization, provide your own NSIS script: + +```bash +# Create custom template +cp build/nsis/installer.nsi build/nsis/installer.custom.nsi + +# Edit build/nsis/installer.custom.nsi +# Add custom sections, pages, or logic + +# Build with custom script +wails3 task nsis --script build/nsis/installer.custom.nsi +``` + +#### Available Macros + +Wails provides NSIS macros for common tasks: + +**wails.associateCustomProtocols** +- Registers all custom URL protocols defined in `application.Options.Protocols` +- Called automatically during installation +- Creates registry entries under `HKEY_CURRENT_USER\SOFTWARE\Classes\` + +**wails.unassociateCustomProtocols** +- Removes custom URL protocol registrations +- Called automatically during uninstallation +- Cleans up all protocol-related registry entries + +**Example usage in custom NSIS script:** + +```nsis +Section "Install" + # ... your installation code ... + + # Register custom protocols + !insertmacro wails.associateCustomProtocols + + # ... more installation code ... +SectionEnd + +Section "Uninstall" + # Remove custom protocols + !insertmacro wails.unassociateCustomProtocols + + # ... your uninstallation code ... +SectionEnd +``` + +## MSI Installer + +Windows Installer Package format with Windows logo certification support. + +### Build MSI + +```bash +wails3 build +wails3 task msi +``` + +Generates `build/bin/--.msi`. + +### Customization + +Configure via `wails.json`: + +```json +{ + "msi": { + "productCode": "{GUID}", + "upgradeCode": "{GUID}", + "manufacturer": "My Company", + "installScope": "perMachine", + "shortcuts": { + "desktop": true, + "startMenu": true + } + } +} +``` + +### MSI vs NSIS + +| Feature | NSIS | MSI | +|---------|------|-----| +| Customization | ✅ High | ⚠️ Limited | +| File Size | ✅ Smaller | ⚠️ Larger | +| Corporate Deployment | ⚠️ Less common | ✅ Preferred | +| Custom UI | ✅ Full control | ⚠️ Restricted | +| Windows Logo | ❌ No | ✅ Yes | +| Protocol Registration | ✅ Automatic | ⚠️ Manual | + +**Use NSIS when:** +- You want maximum customization +- You need custom branding and UI +- You want automatic protocol registration +- File size matters + +**Use MSI when:** +- You need Windows logo certification +- You're deploying in enterprise environments +- You need Group Policy support +- You want Windows Update integration + +## Portable Executable + +Single executable with no installation required. + +### Build Portable + +```bash +wails3 build +``` + +Output: `build/bin/.exe` + +### Characteristics + +- No installation required +- No registry changes +- No administrator privileges needed +- Can run from USB drives +- No automatic updates +- No Start Menu integration + +### Use Cases + +- Trial versions +- USB stick applications +- Corporate environments with restricted installations +- Quick testing and demos + +## MSIX Packages + +Modern Windows app package format for Microsoft Store and sideloading. + +See [MSIX Packaging Guide](/guides/build/msix) for detailed information. + +## Code Signing + +Sign your executables and installers to: +- Avoid Windows SmartScreen warnings +- Establish publisher identity +- Enable automatic updates +- Meet corporate security requirements + +See [Code Signing Guide](/guides/build/signing) for details. + +## Icon Requirements + +### Application Icon + +**Format:** `.ico` file with multiple resolutions + +**Recommended sizes:** +- 16x16, 32x32, 48x48, 64x64, 128x128, 256x256 + +**Create from PNG:** + +```bash +# Using ImageMagick +magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 appicon.ico + +# Using online tools +# https://icoconvert.com/ +``` + +### Installer Icon + +Same `.ico` file can be used for both application and installer. + +Configure in `wails.json`: + +```json +{ + "nsis": { + "installerIcon": "build/appicon.ico" + }, + "windows": { + "applicationIcon": "build/appicon.ico" + } +} +``` + +## Building for Different Architectures + +The target architecture is controlled by the `GOARCH` environment variable and the platform-specific Taskfile. The default Windows Taskfile builds for the current architecture. + +### AMD64 (64-bit) + +```bash +GOARCH=amd64 wails3 build +``` + +### ARM64 + +```bash +GOARCH=arm64 wails3 build +``` + +## Distribution Checklist + +Before distributing your Windows application: + +- [ ] Test installer on clean Windows installation +- [ ] Verify application icon displays correctly +- [ ] Test uninstaller completely removes application +- [ ] Verify Start Menu shortcuts work +- [ ] Test custom protocol handlers (if used) +- [ ] Check SmartScreen behavior (sign your app if possible) +- [ ] Test on Windows 10 and Windows 11 +- [ ] Verify file associations work (if used) +- [ ] Test with antivirus software +- [ ] Include license and documentation + +## Troubleshooting + +### NSIS Build Fails + +**Error:** `makensis: command not found` + +**Solution:** +```bash +# Install NSIS +winget install NSIS.NSIS + +# Or download from https://nsis.sourceforge.io/ +``` + +### Custom Protocols Not Working + +**Check registration:** +```powershell +# Check registry +Get-ItemProperty -Path "HKCU:\SOFTWARE\Classes\myapp" + +# Test protocol +Start-Process "myapp://test" +``` + +**Fix:** +1. Reinstall with NSIS installer +2. Run installer as administrator if needed +3. Verify `Protocols` configuration in Go code + +### SmartScreen Warning + +**Cause:** Unsigned executable + +**Solutions:** +1. **Code sign your application** (recommended) +2. Build reputation (downloads over time) +3. Submit to Microsoft for analysis +4. Use Extended Validation (EV) certificate for immediate trust + +### Installer Won't Run + +**Possible causes:** +- Antivirus blocking +- Missing dependencies +- Corrupted download +- User permissions + +**Solutions:** +1. Temporarily disable antivirus for testing +2. Run as administrator +3. Re-download installer +4. Check Windows Event Viewer for errors + +## Best Practices + +### ✅ Do + +- **Code sign your releases** - Avoids SmartScreen warnings +- **Test on clean Windows installations** - Don't rely on dev environment +- **Provide both installer and portable versions** - Give users choice +- **Include comprehensive uninstaller** - Remove all traces +- **Use semantic versioning** - Clear version numbering +- **Test protocol handlers thoroughly** - Validate all URL inputs +- **Provide clear installation instructions** - Help users succeed + +### ❌ Don't + +- **Don't skip code signing** - Users will see scary warnings +- **Don't require admin for normal apps** - Only if truly necessary +- **Don't install to non-standard locations** - Use `$PROGRAMFILES64` +- **Don't leave orphaned registry entries** - Clean up properly +- **Don't forget to test uninstaller** - Broken uninstallers frustrate users +- **Don't hardcode paths** - Use Windows environment variables + +## Next Steps + +- [Code Signing](/guides/build/signing) - Sign your executables and installers +- [MSIX Packaging](/guides/build/msix) - Modern Windows app packages +- [Custom Protocols](/guides/distribution/custom-protocols) - Deep linking and URL schemes +- [Auto-Updates](/guides/distribution/auto-updates) - Keep your app current + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [Windows packaging examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/guides/building.mdx b/docs/src/content/docs/guides/building.mdx new file mode 100644 index 000000000..d711724b4 --- /dev/null +++ b/docs/src/content/docs/guides/building.mdx @@ -0,0 +1,179 @@ +--- +title: Building Applications +description: Build and package your Wails application +sidebar: + order: 1 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +## Overview + +Wails uses [Task](https://taskfile.dev) as its build system. The `wails3 build`, `wails3 package`, and `wails3 dev` commands are wrappers around tasks defined in your project's `Taskfile.yml`. You can customize the build process by editing the Taskfile. + +## Development Build + +### Quick Start + +```bash +wails3 dev +``` + +**Features:** +- Hot reload for frontend changes +- Automatic Go rebuild on changes +- Debug mode enabled +- Fast iteration + +### Dev Options + +```bash +# Custom config file +wails3 dev -config ./build/config.yml + +# Custom vite dev server port +wails3 dev -port 3000 + +# Enable HTTPS for the dev server +wails3 dev -s +``` + +The dev server port defaults to `9245`. You can also set the port via the `WAILS_VITE_PORT` environment variable. + + :::note + `wails3 dev` is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file. + ::: + +## Production Build + +### Basic Build + +```bash +wails3 build +``` + +**Output:** Binary in `bin/` directory. + +### Build with Tags + +```bash +# Pass additional Go build tags +wails3 build -tags production,debug +``` + +### Build with CLI Variables + +You can pass CLI variables to customize the build via the underlying Taskfile: + +```bash +wails3 build PRODUCTION=true +``` + +These variables can be accessed in your `Taskfile.yml` using Go template syntax. + + :::note + `wails3 build` is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file. + ::: + +## Platform-Specific Builds + +Platform-specific build tasks are defined in the Taskfiles under `build//Taskfile.yml`. The main `Taskfile.yml` dispatches to the correct platform automatically based on the current OS. + + + + The Windows Taskfile handles: + - Building the binary with appropriate flags + - Generating `.ico` icon file + - Generating Windows `.syso` file + - Creating NSIS installers for packaging + + ```bash + # Build for Windows (runs automatically on Windows) + wails3 build + ``` + + + + The macOS Taskfile handles: + - Building binaries for amd64, arm64, and universal architectures + - Generating `.icns` icon file + - Creating `.app` bundles + - Ad-hoc code signing + + ```bash + # Build for macOS (runs automatically on macOS) + wails3 build + ``` + + + + The Linux Taskfile handles: + - Building the binary with appropriate flags + - Generating `.desktop` files + - Creating AppImage, deb, rpm, and Arch Linux packages + + ```bash + # Build for Linux (runs automatically on Linux) + wails3 build + ``` + + + +## Packaging + +```bash +wails3 package +``` + +This creates platform-specific packages for distribution. Like `build`, it is a wrapper around `wails3 task package`. + +| Platform | Package Types | +|----------|-------------------------------------------| +| Windows | `.exe`, NSIS installer | +| macOS | `.app` bundle | +| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` | + +## Optimization + +### Binary Size + +```bash +# UPX compression (external tool) +upx --best --lzma bin/myapp +``` + +The default Taskfiles already include `-ldflags "-s -w"` to strip debug symbols. + +### Build Tags + +```bash +# Pass build tags to the Go compiler +wails3 build -tags production +``` + +## Troubleshooting + +### Build Fails + +**Problem:** Build errors + +**Solutions:** +- Check `go.mod` is up to date +- Run `go mod tidy` +- Verify frontend builds: `cd frontend && npm run build` +- Run `wails3 doctor` to check your environment + +### Large Binary Size + +**Problem:** Binary is too large + +**Solutions:** +- Ensure Taskfile includes `-ldflags "-s -w"` to strip symbols (default Taskfiles do this) +- Remove unused dependencies +- Use UPX compression +- Check embedded assets size + +## Next Steps + +- [Build Customization](/guides/build/customization) - Customize the build process with Taskfiles +- [Build System](/concepts/build-system) - Understand how the build system works diff --git a/docs/src/content/docs/guides/cli.mdx b/docs/src/content/docs/guides/cli.mdx new file mode 100644 index 000000000..5335fc7ab --- /dev/null +++ b/docs/src/content/docs/guides/cli.mdx @@ -0,0 +1,696 @@ +--- +title: CLI Reference +description: Complete reference for the Wails CLI commands +sidebar: + order: 1 +--- + +The Wails CLI provides a comprehensive set of commands to help you develop, build, and maintain your Wails applications. + +## Core Commands + +Core commands are the primary commands used for project creation, development, and building. + +All CLI commands are of the following format: `wails3 `. + +### `init` +Initializes a new Wails project. During this initialization, the `go mod tidy` command is run to bring the project packages up to date. This can be bypassed by using the `-skipgomodtidy` flag with the `init` command. + +```bash +wails3 init [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------------------|----------------------|--------------------------| +| `-p` | Package name | `main` | +| `-t` | Template name or URL | `vanilla` | +| `-n` | Project name | | +| `-d` | Project directory | `.` | +| `-q` | Suppress output | `false` | +| `-l` | List templates | `false` | +| `-git` | Git repository URL | | +| `-productname` | Product name | `My Product` | +| `-productdescription` | Product description | `My Product Description` | +| `-productversion` | Product version | `0.1.0` | +| `-productcompany` | Company name | `My Company` | +| `-productcopyright` | Copyright notice | ` now, My Company` | +| `-productcomments` | File comments | `This is a comment` | +| `-productidentifier` | Product identifier | | +| `-s` | Skip warning for remote templates | `false` | +| `-skipgomodtidy` | Skip go mod tidy | `false` | +| `-mod` | Go module path | | + +The `-mod` flag sets the Go module path for the project. If omitted and `-git` is provided, the module path will be computed from the Git URL. + +The `-git` flag accepts various Git URL formats: +- HTTPS: `https://github.com/username/project` +- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project` +- Git protocol: `git://github.com/username/project` +- Filesystem: `file:///path/to/project.git` + +When provided, this flag will: +1. Initialize a git repository in the project directory +2. Set the specified URL as the remote origin +3. Update the module name in `go.mod` to match the repository URL +4. Add all files + +### `dev` +Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected +in the running application without having to rebuild the entire application. Changes to your Go code will also be detected and the application +will automatically rebuild and relaunch. + +```bash +wails3 dev [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------|----------------------|----------------------| +| `-config` | Config file path | `./build/config.yml` | +| `-port` | Vite dev server port | `9245` | +| `-s` | Enable HTTPS | `false` | + + + :::note + This is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file. + ::: + + +### `build` +Builds a debug version of your application. It defaults to building for the current platform and architecture. + +```bash +wails3 build [flags] [CLI variables...] +``` + +#### Flags +| Flag | Description | Default | +|-----------|----------------------------------------------------------|---------| +| `-tags` | Additional build tags to pass to the Go compiler (comma-separated) | | + +You can pass CLI variables to customize the build: +```bash +wails3 build PLATFORM=linux CONFIG=production +``` + +You can also pass additional Go build tags: +```bash +wails3 build -tags production,debug +``` + + :::note + This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables passed to `build` are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file. + ::: + +### `package` + +Creates platform-specific packages for distribution. + +```bash +wails3 package [CLI variables...] +``` + +You can pass CLI variables to customize the packaging: +```bash +wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg +``` + +#### Package Types + +The following package types are available for each platform: + +| Platform | Package Type | +|----------|-------------------------------------------| +| Windows | `.exe` | +| macOS | `.app`, | +| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` | + + :::note + This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. Any CLI variables passed to `package` are forwarded to the underlying task. You can customise the packaging process by editing the `Taskfile.yml` file. + ::: + + +### `task` +Runs tasks defined in your project's Taskfile.yml. This is an embedded version of [Taskfile](https://taskfile.dev) that allows you to define and run custom build, test, and deployment tasks. + +```bash +wails3 task [taskname] [CLI variables...] [flags] +``` + +#### CLI Variables +You can pass variables to tasks in the format `KEY=VALUE`: +```bash +wails3 task build PLATFORM=linux CONFIG=production +wails3 task deploy ENV=staging VERSION=1.2.3 +``` + +These variables can be accessed in your Taskfile.yml using Go template syntax: +```yaml +tasks: + build: + cmds: + - echo "Building for {{.PLATFORM | default "darwin"}}" + - echo "Config: {{.CONFIG | default "debug"}}" +``` + +#### Flags +| Flag | Description | Default | +|--------------|-------------------------------------------|----------| +| `-h` | Shows Task usage | `false` | +| `-i` | Creates a new Taskfile.yml | `false` | +| `-list` | Lists tasks with descriptions | `false` | +| `-list-all` | Lists all tasks (with or without descriptions) | `false` | +| `-json` | Formats task list as JSON | `false` | +| `-status` | Exits with non-zero if task is not up-to-date | `false` | +| `-f` | Forces execution even when task is up-to-date | `false` | +| `-w` | Enables watch mode for the given task | `false` | +| `-v` | Enables verbose mode | `false` | +| `-version` | Prints Task version | `false` | +| `-s` | Disables echoing | `false` | +| `-p` | Executes tasks in parallel | `false` | +| `-dry` | Compiles and prints tasks without executing | `false` | +| `-summary` | Shows summary about a task | `false` | +| `-x` | Pass-through the exit code of the task | `false` | +| `-dir` | Sets directory of execution | | +| `-taskfile` | Choose which Taskfile to run | | +| `-output` | Sets output style: [interleaved|group|prefixed] | | +| `-c` | Colored output (enabled by default) | `true` | +| `-C` | Limit number of tasks to run concurrently | | +| `-interval` | Interval to watch for changes (in seconds) | | + +#### Examples +```bash +# Run the default task +wails3 task + +# Run a specific task +wails3 task test + +# Run a task with variables +wails3 task build PLATFORM=windows ARCH=amd64 + +# List all available tasks +wails3 task --list + +# Run multiple tasks in parallel +wails3 task -p task1 task2 task3 + +# Watch for changes and re-run task +wails3 task -w dev +``` + +### `doctor` +Performs a system check and displays a status report. + +```bash +wails3 doctor +``` + +## Generate Commands + +Generate commands help create various project assets like bindings, icons, and build files. All generate commands use the base command: `wails3 generate `. + +### `generate bindings` +Generates bindings and models for your Go code. + +```bash +wails3 generate bindings [flags] [patterns...] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------|---------------------| +| `-f` | Additional Go build flags | | +| `-d` | Output directory | `frontend/bindings` | +| `-models` | Models filename | `models` | +| `-index` | Index filename | `index` | +| `-ts` | Generate TypeScript | `false` | +| `-i` | Use TS interfaces | `false` | +| `-b` | Use bundled runtime | `false` | +| `-names` | Use names instead of IDs | `false` | +| `-noevents` | Skip custom event types | `false` | +| `-noindex` | Skip index files | `false` | +| `-dry` | Dry run | `false` | +| `-silent` | Silent mode | `false` | +| `-v` | Debug output | `false` | +| `-clean` | Clean output directory | `true` | + +### `generate build-assets` +Generates build assets for your application. + +```bash +wails3 generate build-assets [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|---------------------|--------------------| +| `-name` | Project name | | +| `-dir` | Output directory | `build` | +| `-silent` | Suppress output | `false` | +| `-company` | Company name | | +| `-productname` | Product name | | +| `-description` | Product description | | +| `-version` | Product version | | +| `-identifier` | Product identifier | `com.wails.[name]` | +| `-copyright` | Copyright notice | | +| `-comments` | File comments | | + +### `generate icons` +Generates application icons. + +```bash +wails3 generate icons [flags] +``` + +#### Flags +| Flag | Description | Default | +|--------------------|------------------------------|-----------------------| +| `-input` | Input PNG file | Required | +| `-windowsfilename` | Windows output filename | | +| `-macfilename` | macOS output filename | | +| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` | +| `-example` | Generate example icon | `false` | + +### `generate syso` +Generates Windows .syso file. + +```bash +wails3 generate syso [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------|----------------------------| +| `-manifest` | Path to manifest file | Required | +| `-icon` | Path to icon file | Required | +| `-info` | Path to version info file | | +| `-arch` | Target architecture | Current GOARCH | +| `-out` | Output filename | `rsrc_windows_[arch].syso` | + +### `generate .desktop` +Generates a Linux .desktop file. + +```bash +wails3 generate .desktop [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------------|---------------------------|------------------| +| `-name` | Application name | Required | +| `-exec` | Executable path | Required | +| `-icon` | Icon path | | +| `-categories` | Application categories | `Utility` | +| `-comment` | Application comment | | +| `-terminal` | Run in terminal | `false` | +| `-keywords` | Search keywords | | +| `-version` | Application version | | +| `-genericname` | Generic name | | +| `-startupnotify` | Show startup notification | `false` | +| `-mimetype` | Supported MIME types | | +| `-output` | Output filename | `[name].desktop` | + +### `generate runtime` +Generates the pre-built version of the runtime. + +```bash +wails3 generate runtime +``` + +### `generate constants` +Generates JavaScript constants from Go code. + +```bash +wails3 generate constants [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|--------------------------------|----------------| +| `-f` | The Go constants filename | `constants.go` | +| `-o` | The output JavaScript file | `constants.js` | + +### `generate appimage` +Generates a Linux AppImage. + +```bash +wails3 generate appimage [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|-----------------------|----------------| +| `-binary` | Path to binary | Required | +| `-icon` | Path to icon file | Required | +| `-desktop` | Path to .desktop file | Required | +| `-builddir` | Build directory | Temp directory | +| `-output` | Output directory | `.` | + +Base command: `wails3 service` + +## Service Commands + +Service commands help manage Wails services. All service commands use the base command: `wails3 service `. + +### `service init` +Initializes a new service. + +```bash +wails3 service init [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|---------------------|-------------------| +| `-n` | Service name | `example_service` | +| `-d` | Service description | `Example service` | +| `-p` | Package name | | +| `-o` | Output directory | `.` | +| `-q` | Suppress output | `false` | +| `-a` | Author name | | +| `-v` | Version | | +| `-w` | Website URL | | +| `-r` | Repository URL | | +| `-l` | License | | + +Base command: `wails3 tool` + +## Tool Commands + +Tool commands provide utilities for development and debugging. All tool commands use the base command: `wails3 tool `. + +### `tool checkport` +Checks if a port is open. Useful for testing if vite is running. + +```bash +wails3 tool checkport [flags] +``` + +#### Flags +| Flag | Description | Default | +|---------|---------------|-------------| +| `-port` | Port to check | `9245` | +| `-host` | Host to check | `localhost` | + +### `tool watcher` +Watches files and runs a command when they change. + +```bash +wails3 tool watcher [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------|---------------------|----------------------| +| `-config` | Config file path | `./build/config.yml` | +| `-ignore` | Patterns to ignore | | +| `-include` | Patterns to include | | + +### `tool cp` +Copies files. + +```bash +wails3 tool cp +``` + +### `tool buildinfo` +Shows build information about the application. + +```bash +wails3 tool buildinfo +``` + +### `tool version` +Bumps a semantic version based on the provided flags. + +```bash +wails3 tool version [flags] +``` + +#### Flags +| Flag | Description | Default | +|---------------|--------------------------------------------------|---------| +| `-v` | Current version to bump | | +| `-major` | Bump major version | `false` | +| `-minor` | Bump minor version | `false` | +| `-patch` | Bump patch version | `false` | +| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` | + +The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components. + +Example usage: +```bash +wails3 tool version -v 1.2.3 -major # Output: 2.0.0 +wails3 tool version -v v1.2.3 -minor # Output: v1.3.0 +wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha +wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6 +``` + +### `tool package` +Generates Linux packages (deb, rpm, archlinux). + +```bash +wails3 tool package [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|------------------------------------------------|---------| +| `-format` | Package format (deb, rpm, archlinux, dmg) | `deb` | +| `-name` | Executable name | `myapp` | +| `-config` | Config file path | | +| `-out` | Output directory | `.` | +| `-background` | Path to optional background image for DMG | | +| `-create-dmg` | Create a DMG file (macOS only) | `false` | + +### `tool lipo` +Creates a macOS universal binary from multiple architecture-specific binaries. + +```bash +wails3 tool lipo [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------|-------------------------------------------------|---------| +| `-output` (`-o`) | Output path for the universal binary | | +| `-input` (`-i`) | Input binaries to combine (specify multiple times) | | + +### `tool capabilities` +Checks system build capabilities (GTK4/GTK3 availability on Linux). + +```bash +wails3 tool capabilities +``` + +### `tool sign` +Signs a binary or package directly. + +```bash +wails3 tool sign [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------------|------------------------------------------------------|---------| +| `-input` | Path to the file to sign | | +| `-output` | Output path (defaults to in-place signing) | | +| `-verbose` | Enable verbose output | `false` | +| `-certificate` | Path to PKCS#12 (.pfx/.p12) certificate file | | +| `-password` | Certificate password | | +| `-thumbprint` | Certificate thumbprint in Windows certificate store | | +| `-timestamp` | Timestamp server URL | | +| `-identity` | macOS signing identity | | +| `-entitlements` | Path to entitlements plist file | | +| `-hardened-runtime` | Enable hardened runtime | `false` | +| `-notarize` | Submit for Apple notarization after signing | `false` | +| `-keychain-profile` | Keychain profile for notarization credentials | | +| `-pgp-key` | Path to PGP private key file | | +| `-pgp-password` | PGP key password | | +| `-role` | DEB signing role (origin, maint, archive, builder) | | + +Base command: `wails3 update` + +## Update Commands + +Update commands help manage and update project assets. All update commands use the base command: `wails3 update `. + +### `update cli` +Updates the Wails CLI to a new version. + +```bash +wails3 update cli [flags] +``` + +#### Flags +| Flag | Description | Default | +|-------------|---------------------------------|---------| +| `-pre` | Update to latest pre-release | `false` | +| `-version` | Update to specific version | | +| `-nocolour` | Disable colored output | `false` | + +The update cli command allows you to update your Wails CLI installation. By default, it updates to the latest stable release. +You can use the `-pre` flag to update to the latest pre-release version, or specify a particular version using the `-version` flag. + +After updating, remember to update your project's go.mod file to use the same version: +```bash +require github.com/wailsapp/wails/v3 v3.x.x +``` + +### `update build-assets` +Updates the build assets using the given config file. + +```bash +wails3 update build-assets [flags] +``` + +#### Flags +| Flag | Description | Default | +|----------------|---------------------|---------| +| `-config` | Config file path | | +| `-dir` | Output directory | `build` | +| `-silent` | Suppress output | `false` | +| `-company` | Company name | | +| `-productname` | Product name | | +| `-description` | Product description | | +| `-version` | Product version | | +| `-identifier` | Product identifier | | +| `-copyright` | Copyright notice | | +| `-comments` | File comments | | + +Base command: `wails3` + +## Utility Commands + +Utility commands provide helpful shortcuts for common tasks. Use these commands directly with the base command: `wails3 `. + +### `docs` +Opens the Wails documentation in your default browser. + +```bash +wails3 docs +``` + +### `releasenotes` +Shows the release notes for the current or specified version. + +```bash +wails3 releasenotes [flags] +``` + +#### Flags +| Flag | Description | Default | +|------|-----------------------------------|---------| +| `-v` | Version to show release notes for | | +| `-n` | Disable colour output | `false` | + +### `version` +Prints the current version of Wails. + +```bash +wails3 version +``` + +### `sponsor` +Opens the Wails sponsorship page in your default browser. + +```bash +wails3 sponsor +``` + +### `doctor-ng` +System status report using the new TUI interface. + +```bash +wails3 doctor-ng +``` + +## Setup Commands + +Setup commands provide project configuration wizards. All setup commands use the base command: `wails3 setup `. + +### `setup signing` +Configures code signing for your project. + +```bash +wails3 setup signing [flags] +``` + +#### Flags +| Flag | Description | Default | +|--------------|--------------------------------------------------------------|---------| +| `-platform` | Platform(s) to configure (darwin, windows, linux). Auto-detects if not specified. | | + +### `setup entitlements` +Configures macOS entitlements for your project. + +```bash +wails3 setup entitlements [flags] +``` + +#### Flags +| Flag | Description | Default | +|------------|--------------------------------------|-------------------------------------| +| `-output` | Output path for entitlements.plist | `build/darwin/entitlements.plist` | + +## Sign Command + +### `sign` +Signs binaries and packages for the current or specified platform. This is a wrapper that calls platform-specific signing tasks. + +```bash +wails3 sign [flags] [args...] +``` + +## Generate Commands (continued) + +### `generate template` +Generates a new Wails template from an existing project. + +```bash +wails3 generate template [flags] +``` + +#### Flags +| Flag | Description | Default | +|-----------------|--------------------------|----------| +| `-name` | Template name | | +| `-shortname` | Short name | | +| `-author` | Template author | | +| `-description` | Template description | | +| `-helpurl` | Help URL | | +| `-version` | Template version | `v0.0.1` | +| `-dir` | Output directory | `.` | +| `-frontend` | Frontend directory to migrate | | + +### `generate webview2bootstrapper` +Generates the WebView2 bootstrapper executable for Windows. + +```bash +wails3 generate webview2bootstrapper [flags] +``` + +#### Flags +| Flag | Description | Default | +|--------|---------------------------------|---------| +| `-dir` | Directory to write the file to | | + +## iOS Commands + +iOS commands provide tooling for iOS development. All iOS commands use the base command: `wails3 ios `. + +### `ios overlay:gen` +Generates a Go overlay for the iOS bridge shim. + +```bash +wails3 ios overlay:gen +``` + +### `ios xcode:gen` +Generates an Xcode project in the output directory. + +```bash +wails3 ios xcode:gen +``` diff --git a/docs/src/content/docs/guides/cross-platform.mdx b/docs/src/content/docs/guides/cross-platform.mdx new file mode 100644 index 000000000..3b51da037 --- /dev/null +++ b/docs/src/content/docs/guides/cross-platform.mdx @@ -0,0 +1,113 @@ +--- +title: Cross-Platform Building +description: Build for multiple platforms from a single machine +sidebar: + order: 2 +--- + +## Overview + +Wails supports building for Windows, macOS, and Linux. The build process is driven by platform-specific Taskfiles that handle the details of compilation, icon generation, and packaging for each platform. + +## Supported Platforms + +Wails supports the following target platforms: +- `windows/amd64`, `windows/arm64` +- `darwin/amd64`, `darwin/arm64` (universal binaries via `wails3 tool lipo`) +- `linux/amd64`, `linux/arm64` + +## How It Works + +When you run `wails3 build`, the main `Taskfile.yml` dispatches to the platform-specific Taskfile based on the current OS using `{{OS}}`: + +```yaml +tasks: + build: + cmds: + - task: "{{OS}}:build" +``` + +Each platform Taskfile (in `build//Taskfile.yml`) handles the compilation with the appropriate flags and environment variables. + +## Cross-Compilation + +To cross-compile, set the `GOOS` and `GOARCH` environment variables. Note that CGo cross-compilation may require additional tooling (e.g., a cross-compiler like `mingw-w64` for Windows builds from Linux/macOS). + +### Building for Windows from macOS/Linux + +```bash +# Install cross-compiler (one-time) +# macOS: brew install mingw-w64 +# Linux: apt-get install mingw-w64 + +# Cross-compile +GOOS=windows GOARCH=amd64 go build -o myapp.exe +``` + +### Building for Linux from macOS/Windows + +```bash +GOOS=linux GOARCH=amd64 go build -o myapp +``` + +### macOS Builds + +macOS builds are best done on macOS due to code signing and notarization requirements. The macOS Taskfile supports building for different architectures: + +```bash +# Build for the current architecture +wails3 build + +# Create a universal binary using tool lipo +wails3 tool lipo -i bin/myapp-amd64 -i bin/myapp-arm64 -output bin/myapp-universal +``` + +## CI/CD Integration + +### GitHub Actions + +The recommended approach for cross-platform builds is to use CI/CD with platform-specific runners: + +```yaml +name: Build + +on: [push] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install Wails + run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest + + - name: Build + run: wails3 build + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: app-${{ matrix.os }} + path: bin/ +``` + +## Best Practices + +- **Build on target platform** - Use CI/CD runners matching target OS for reliable builds +- **Test on target platforms** - Cross-compiled binaries should be tested on their target OS +- **Use the Taskfile system** - Customize builds per platform in `build//Taskfile.yml` +- **Version your builds** - Use `-ldflags` in your Taskfile to embed version info + +## Next Steps + +- [Build Customization](/guides/build/customization) - Customize the build process +- [Building](/guides/building) - Basic building guide diff --git a/docs/src/content/docs/guides/custom-protocol-association.mdx b/docs/src/content/docs/guides/custom-protocol-association.mdx new file mode 100644 index 000000000..c6de13591 --- /dev/null +++ b/docs/src/content/docs/guides/custom-protocol-association.mdx @@ -0,0 +1,167 @@ +--- +title: Custom Protocol Schemes (Deep Linking) +description: Guide to implementing custom URL schemes for deep linking in Wails applications across macOS, Windows, and Linux. +--- + +import { Aside } from '@astrojs/starlight/components'; + +# Custom Protocol Schemes (Deep Linking) + +Custom protocol schemes (also known as custom URL schemes or deep linking) allow your Wails application to be launched or brought to the foreground by clicking a URL with a scheme you define (e.g., `myapp://some/data`). This is useful for various purposes, such as: + +- OAuth authentication flows. +- Inter-application communication. +- Launching your app with a specific context or to perform a particular action. + +Wails provides a unified way to handle these custom URL invocations across macOS, Windows, and Linux through the `events.Common.ApplicationLaunchedWithUrl` event. + +## Defining Your Protocols + +First, you need to define the custom protocol schemes your application will use. This is done in your `wails.json` project configuration file. Wails reads this file during the build process (`wails build`) to configure the necessary platform-specific assets like `Info.plist` for macOS, NSIS installer scripts for Windows, and `.desktop` files for Linux. + +**Example: `wails.json`** + +```json title="wails.json" +{ + "name": "My App", + "description": "An amazing Wails app!", + "info": { + "companyName": "My Company", + "productName": "My Product", + // ... other info fields ... + "protocols": [ + { + "scheme": "myapp", + "description": "My Application Custom Protocol" + }, + { + "scheme": "anotherprotocol", + "description": "Another protocol for specific actions" + } + ] + } + // ... other wails.json fields ... +} +``` + +This `info.protocols` array is what Wails uses to generate the necessary entries in platform-specific files. For example, in template files, you might access this via a path like `{{.Info.Protocols}}`. + + + +## Handling the Event in Your Application + +When your application is launched or activated via a custom URL, Wails emits an `events.Common.ApplicationLaunchedWithUrl` event. You can listen for this event and retrieve the URL that triggered the launch. + +```go title="main.go" +import ( + "log" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "My App", // Ensure this matches relevant info from wails.json if needed + Description: "An amazing Wails app!", + // ... other runtime options ... + }) + + app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) { + launchedURL := e.Context().URL() // Retrieve the URL from the event context + log.Printf("Application launched with URL: %s", launchedURL) + + // TODO: Process the URL (e.g., navigate, perform action, etc.) + // Example: app.Event.Emit("frontend:ShowURL", launchedURL) + }) + + // ... rest of your main function ... + err := app.Run() + if err != nil { + log.Fatal(err) + } +} +``` + + + +## Platform-Specific Setup and Behavior + +While Wails aims for a unified event, the underlying mechanism for custom protocol registration and URL delivery varies by operating system. + +### macOS + +- **Setup:** Wails automatically configures your application's `Info.plist` file during the build process. It adds `CFBundleURLTypes` entries based on the `info.protocols` defined in your `wails.json` file. + ```xml title="Info.plist (excerpt generated by Wails)" + CFBundleURLTypes + + + CFBundleURLName + My Application Custom Protocol + CFBundleURLSchemes + + myapp + + + + + ``` +- **How it Works:** When a URL like `myapp://` is opened, macOS uses LaunchServices to find the application registered for that scheme and sends it an Apple Event (`kAEGetURL`). Wails intercepts this event and translates it into the common `events.Common.ApplicationLaunchedWithUrl` Wails event, providing the URL via `e.Context().URL()`. + +### Windows + +- **Setup:** Custom protocol schemes on Windows are registered in the Windows Registry. Wails facilitates this through its NSIS installer template. + - When you build your application with the `-nsis` flag, Wails uses the `v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl` file. + - This template contains macros like `CUSTOM_PROTOCOL_ASSOCIATE` and `wails.associateCustomProtocols` which use the `info.protocols` from your `wails.json` (passed as `{{.Info.Protocols}}` to the template) to create the necessary registry entries during installation. + ```nsis title="wails_tools.nsh.tmpl (excerpt)" + !macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + {{end}} + !macroend + ``` +- **How it Works:** The installer registers your application executable to be called with the URL as a command-line argument (`%1`). For example, `your_app.exe "myapp://some/data"`. + - The Wails runtime for Windows (`v3/pkg/application/application_windows.go`) has been updated to check `os.Args` upon startup. If it detects an argument that looks like a URL (e.g., `os.Args[1]` contains `"://"`), it now emits the `events.Common.ApplicationLaunchedWithUrl` event with this URL. + + + +### Linux + +- **Setup:** On Linux, custom protocol handling is typically managed via `.desktop` files and the MIME type system. + - Wails uses a `.desktop` file template (e.g., `v3/internal/commands/updatable_build_assets/linux/desktop.tmpl`) which is populated during the build using information from `wails.json`. + ```desktop title="desktop.tmpl (excerpt)" + [Desktop Entry] + Name={{.ProductName}} + Exec=/usr/local/bin/{{.BinaryName}} %u + MimeType={{range $index, $protocol := .Info.Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}} + ``` + The `Exec` line uses `%u` which gets replaced by the URL. The `MimeType` line registers your application as a handler for `x-scheme-handler/your-scheme` for each protocol defined in `wails.json` (via `{{.Info.Protocols}}`). + - When packaging for Linux (e.g., using `nfpm`), this `.desktop` file is installed to `/usr/share/applications/`. + - A `postinstall.sh` script (e.g., `v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh`) is used to update the system's application and MIME databases: + ```sh title="postinstall.sh (excerpt)" + #!/bin/sh + update-desktop-database -q /usr/share/applications + update-mime-database -n /usr/share/mime + ``` +- **How it Works:** When a URL like `myapp://` is opened, the desktop environment uses the MIME database to find the associated `.desktop` file and executes the command specified in its `Exec` line, substituting `%u` with the URL. Your application receives this URL as a command-line argument. + - The Wails runtime for Linux (`v3/pkg/application/application_linux.go`) checks `os.Args` on startup. If it detects an argument that looks like a URL, it emits the `events.Common.ApplicationLaunchedWithUrl` event. + +## Testing Your Custom Protocols + +- **macOS:** Open Terminal and type `open "your-scheme://your/data"`. +- **Linux:** Open a terminal and type `xdg-open "your-scheme://your/data"` (requires `xdg-utils` to be installed and the app to be properly packaged and registered). +- **Windows:** After installation via NSIS: + - You can try running `start your-scheme://your/data` from Command Prompt or PowerShell. + - Create a simple HTML file with a link `Test Link` and open it in a browser. + + + +By following this guide, you can effectively use custom protocol schemes to enhance your Wails application's interactivity and integration with other applications or web services. diff --git a/docs/src/content/docs/guides/custom-templates.mdx b/docs/src/content/docs/guides/custom-templates.mdx new file mode 100644 index 000000000..3da561b35 --- /dev/null +++ b/docs/src/content/docs/guides/custom-templates.mdx @@ -0,0 +1,293 @@ +--- +title: Creating Custom Templates +description: Learn how to create and customise your own Wails v3 templates +sidebar: + order: 50 +--- + +This guide will walk you through the process of creating a custom template for Wails v3. + +## Why would I make a custom template? + +Wails comes with a number of pre-configured templates that allow you to get your application up and running quickly. But if you need a more customised setup, you can create your own template to suit your needs. This can then be shared with the Wails community for others to use. + +### 1. Generating a Template + +To create a custom template, you can use the `wails generate template` command: + +```bash +wails3 generate template -name mytemplate +``` + +This will create a new directory called "mytemplate" in your current directory. + +The `wails3 generate template` command supports the following options: + +| Option | Description | Default | +|----------------|---------------------------------------------------|-------------------| +| `-name` | The name of your template (required) | - | +| `-frontend` | Path to an existing frontend directory to include | - | +| `-author` | The author of the template | - | +| `-description` | A description of the template | - | +| `-helpurl` | URL for template documentation | - | +| `-dir` | Directory to generate the template in | Current directory | +| `-version` | Template version | v0.0.1 | + +For example, to create a template with all options: + +```bash +wails3 generate template \ + -name "My Custom Template" \ + -frontend ./my-existing-frontend \ + -author "Your Name" \ + -description "A template with my preferred setup" \ + -helpurl "https://github.com/yourusername/template-docs" \ + -dir ./templates \ + -version "v1.0.0" +``` + + :::tip + Using the `-frontend` option will copy an existing web frontend project into the template. + ::: + +### 2. Configure Template Metadata + +If you didn't specify the template configuration when generating the template, you can update the `template.json` file in the template directory: + +```json5 +{ + "name": "Your Template Name", // Display name of your template + "shortname": "template-shortname", // Used when referencing your template + "author": "Your Name", // Template author + "description": "Template description", // Template description + "helpurl": "https://your-docs.com", // Documentation URL + "version": "v0.0.1", // Template version + "schema": 3 // Must be kept as 3 for Wails v3 +} +``` +:::caution +The `schema` field must remain set to `3` for compatibility with Wails v3. +::: + +### 3. Set Up Build Tasks + +In the `build` directory is `Taskfile.yml` where you can define your template's build process. +This file uses [Task](https://taskfile.dev) for build automation. The key steps are: + +```yaml +tasks: + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + + +``` + +### 4. Frontend Setup + +If you did not use `-frontend` when generating the template, you need to add frontend files to your template. + +There are a number of ways to set up your frontend: starting from scratch or using an existing framework. + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + + + + If you want to start from scratch, you can create your frontend project just like you would for any web application. + The `frontend` directory in your template is just a regular directory where you can set up your preferred + development environment. You might want to use build tools like Vite, webpack, or even just plain HTML, CSS, and + JavaScript - it's entirely up to you! + + For example, if you're using Vite, you could navigate to the `frontend` directory and run: + + ```bash + npm create vite@latest . + ``` + + Then follow the prompts to set up your project exactly how you want it. The key thing to remember is that this is just a regular frontend project - you can use any tools, frameworks, or libraries you're familiar with. + + + For this example, we'll use [Vite](https://vitejs.dev/) to set up a React frontend project: + + ```bash + npm create vite@latest frontend -- --template react + cd frontend + npm install + ``` + + + + + +Now you have the frontend files in place, update `common/Taskfile.yml` with the appropriate commands: + ```yaml + tasks: + install:frontend:deps: + summary: Install frontend dependencies + dir: frontend + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build:frontend: + summary: Build the frontend project + dir: frontend + sources: + - "**/*" + generates: + - dist/* + deps: + - task: install:frontend:deps + - task: generate:bindings + cmds: + - npm run build -q + ``` + + :::note + For this example, the default Tasks do not need updating as they use the standard `npm install` and `npm run build` commands. + ::: + +### 5. Configure the Go Application + +The default files in the template directory are sufficient to get users started. However, you may want to provide some additional functionality to demonstrate your template's capabilities. The best way to do this is to rename `main.go.tmpl` to `main.go` and edit it like any other Go file. Once finished, ensure you rename it back to `main.go.tmpl` before committing your changes. If you do not care about having a templated `main.go` file (the default template injests the project name into the `Name` field of the application), you can skip this step. + +#### Template Variables + +Wails uses Go's templating engine to process files with the `.tmpl` extension. During template generation, several variables are available for use in your template files: + +| Variable | Description | Example | +|----------------------|----------------------------------|-----------------------------------| +| `Name` | The name of the project | `"MyApp"` | +| `BinaryName` | The name of the generated binary | `"myapp"` | +| `ProductName` | The product name | `"My Application"` | +| `ProductDescription` | Description of the product | `"An awesome application"` | +| `ProductVersion` | Version of the product | `"1.0.0"` | +| `ProductCompany` | Company name | `"My Company Ltd"` | +| `ProductCopyright` | Copyright information | `"Copyright 2024 My Company Ltd"` | +| `ProductComments` | Additional product comments | `"Built with Wails"` | +| `ProductIdentifier` | Unique product identifier | `"com.mycompany.myapp"` | +| `Typescript` | Whether TypeScript is being used | `true` or `false` | +| `WailsVersion` | The version of Wails being used | `"3.0.0"` | + +You can use these variables in your template files using Go's template syntax: + +```go +// main.go.tmpl +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "{{.ProductName}}", + Description: "{{.ProductDescription}}", + }) + // ... +} +``` + + :::tip + Templating can be applied to any file in your template, even html files, so long as the filename has `.tmpl` in its name. + ::: + +### 6. Testing Your Template + +To test your template: + +1. Generate a project using your template: `wails3 init -n testproject -t path/to/your/template` +2. Run `wails3 build` to generate the production build and make sure the binary in `bin` runs correctly +3. Run `wails3 dev` to start the development server. +4. Test that changes to the frontend code are reflected in the application. +5. Test that changes to the Go code rebuild and relaunch the application + +### 7. Sharing Your Template + +Once your template is ready, you can share it with the community by hosting it on GitHub. Here's how: + +1. Create a new GitHub repository for your template +2. Push your template code to the repository +3. Tag your releases using semantic versioning (e.g., v1.0.0) + +Users can then use your template directly from GitHub using the HTTPS URL: + +```bash +wails3 init -n myapp -t https://github.com/yourusername/your-template +``` + +You can also specify a particular version using the URL format: + +```bash +# Use a specific version tag +wails3 init -n myapp -t https://github.com/yourusername/your-template/releases/tag/v1.0.0 + +# Use a specific branch +wails3 init -n myapp -t https://github.com/yourusername/your-template/tree/main +``` + +To test your template before sharing: + +1. Push your changes to GitHub +2. Create a new test project using the HTTPS URL: + ```bash + wails3 init -n testapp -t https://github.com/yourusername/your-template + ``` +3. Verify that all files are correctly generated +4. Test the build and development workflow as described in the testing section + + :::note + Make sure your repository is public if you want others to use your template. + ::: + +For more information, visit the [Wails documentation](https://wails.io) + + +## Best Practices + +Let's talk about some key practices that will help make your template more useful and maintainable. Here are the main areas to focus on: + +1. **Make Your Template Easy to Understand** + - Write a clear, helpful README.md that gets users started quickly + - Add comments in your config files to explain the "why" behind your choices + - Show examples of common customisations - users love to see real-world use cases! + +2. **Keep Dependencies Happy** + - Stay on top of your frontend package updates + - Lock down specific versions in package.json to avoid surprises + - Let users know upfront what they'll need to have installed + +3. **Love Your Template** + - Keep it fresh with regular updates + - Give it a thorough test drive before sharing + - Share it with the Wails community - we'd love to see what you create! diff --git a/docs/src/content/docs/guides/customising-windows.mdx b/docs/src/content/docs/guides/customising-windows.mdx new file mode 100644 index 000000000..27f3999b5 --- /dev/null +++ b/docs/src/content/docs/guides/customising-windows.mdx @@ -0,0 +1,129 @@ +--- +title: Customising Windows in Wails +sidebar: + order: 10 +--- + +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
+ +Wails provides an API to control the appearance and functionality of the +controls of a window. This functionality is available on Windows and macOS, but +not on Linux. + +## Setting the Window Button States + +The button states are defined by the `ButtonState` enum: + +```go +type ButtonState int + +const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 +) +``` + +- `ButtonEnabled`: The button is enabled and visible. +- `ButtonDisabled`: The button is visible but disabled (grayed out). +- `ButtonHidden`: The button is hidden from the titlebar. + +The button states can be set during window creation or at runtime. + +### Setting Button States During Window Creation + +When creating a new window, you can set the initial state of the buttons using +the `WebviewWindowOptions` struct: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + MinimiseButtonState: application.ButtonHidden, + MaximiseButtonState: application.ButtonDisabled, + CloseButtonState: application.ButtonEnabled, + }) + + app.Run() +} +``` + +In the example above, the minimise button is hidden, the maximise button is +inactive (grayed out), and the close button is active. + +### Setting Button States at Runtime + +You can also change the button states at runtime using the following methods on +the `Window` interface: + +```go +window.SetMinimiseButtonState(application.ButtonHidden) +window.SetMaximiseButtonState(application.ButtonEnabled) +window.SetCloseButtonState(application.ButtonDisabled) +``` + +### Platform Differences + +The button state functionality behaves slightly differently on Windows and +macOS: + +| | Windows | Mac | +|-----------------------|------------------------|------------------------| +| Disable Min/Max/Close | Disables Min/Max/Close | Disables Min/Max/Close | +| Hide Min | Disables Min | Hides Min button | +| Hide Max | Disables Max | Hides Max button | +| Hide Close | Hides all controls | Hides Close | + +Note: On Windows, it is not possible to hide the Min/Max buttons individually. +However, disabling both will hide both of the controls and only show the close +button. + +### Controlling Window Style (Windows) + +To control the style of the titlebar on Windows, you can use the `ExStyle` field +in the `WebviewWindowOptions` struct: + +Example: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/w32" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Windows: application.WindowsWindow{ + ExStyle: w32.WS_EX_TOOLWINDOW | w32.WS_EX_NOREDIRECTIONBITMAP | w32.WS_EX_TOPMOST, + }, + }) + + app.Run() +} +``` + +Other options that affect the Extended Style of a window will be overridden by +this setting: + +- HiddenOnTaskbar +- AlwaysOnTop +- IgnoreMouseEvents +- BackgroundType diff --git a/docs/src/content/docs/guides/distribution/custom-protocols.mdx b/docs/src/content/docs/guides/distribution/custom-protocols.mdx new file mode 100644 index 000000000..344c4aafc --- /dev/null +++ b/docs/src/content/docs/guides/distribution/custom-protocols.mdx @@ -0,0 +1,591 @@ +--- +title: Custom URL Protocols +description: Register custom URL schemes to launch your application from links +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +Custom URL protocols (also called URL schemes) allow your application to be launched when users click links with your custom protocol, such as `myapp://action` or `myapp://open/document`. + +## Overview + +Custom protocols enable: +- **Deep linking**: Launch your app with specific data +- **Browser integration**: Handle links from web pages +- **Email links**: Open your app from email clients +- **Inter-app communication**: Launch from other applications + +**Example**: `myapp://open/document?id=123` launches your app and opens document 123. + +## Configuration + +Define custom protocols in your application options: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + Description: "My awesome application", + }) + + // Register handler for protocol events + app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(event *application.ApplicationEvent) { + url := event.Context().ClickedURL() + handleCustomURL(url) + }) + + app.Run() +} + +func handleCustomURL(url string) { + // Parse and handle the custom URL + // Example: myapp://open/document?id=123 + println("Received URL:", url) +} +``` + +:::note +Custom protocol schemes are registered at the OS level through your application's build assets +(Info.plist on macOS, NSIS installer on Windows, .desktop files on Linux), +not through `application.Options`. See the [Platform Registration](#platform-registration) section below for details. +::: + +## Protocol Handler + +Listen for protocol events to handle incoming URLs: + +```go +app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(event *application.ApplicationEvent) { + url := event.Context().ClickedURL() + + // Parse the URL + parsedURL, err := parseCustomURL(url) + if err != nil { + app.Logger.Error("Failed to parse URL:", err) + return + } + + // Handle different actions + switch parsedURL.Action { + case "open": + openDocument(parsedURL.DocumentID) + case "settings": + showSettings() + case "user": + showUser Profile(parsedURL.UserID) + default: + app.Logger.Warn("Unknown action:", parsedURL.Action) + } +}) +``` + +## URL Structure + +Design clear, hierarchical URL structures: + +``` +myapp://action/resource?param=value + +Examples: +myapp://open/document?id=123 +myapp://settings/theme?mode=dark +myapp://user/profile?username=john +``` + +**Best practices:** +- Use lowercase scheme names +- Keep schemes short and memorable +- Use hierarchical paths for resources +- Include query parameters for optional data +- URL-encode special characters + +## Platform Registration + +Custom protocols are registered differently on each platform. + + + + +### Windows NSIS Installer + +**Wails v3 automatically registers custom protocols** when using NSIS installers. + +#### Automatic Registration + +When you build your application with `wails3 build`, the NSIS installer: +1. Automatically registers all protocols defined in your build assets configuration +2. Associates protocols with your application executable +3. Sets up proper registry entries +4. Removes protocol associations during uninstall + +**No additional configuration required!** + +#### How It Works + +The NSIS template includes built-in macros: +- `wails.associateCustomProtocols` - Registers protocols during installation +- `wails.unassociateCustomProtocols` - Removes protocols during uninstall + +These macros are automatically called based on your build assets protocol configuration. + +#### Manual Registry (Advanced) + +If you need manual registration (outside NSIS): + +```batch +@echo off +REM Register custom protocol +REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /ve /d "URL:My Application Protocol" /f +REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp" /v "URL Protocol" /t REG_SZ /d "" /f +REG ADD "HKEY_CURRENT_USER\SOFTWARE\Classes\myapp\shell\open\command" /ve /d "\"%1\"" /f +``` + +#### Testing + +Test your protocol registration: + +```powershell +# Open protocol URL from PowerShell +Start-Process "myapp://test/action" + +# Or from command prompt +start myapp://test/action +``` + + + + + +### Info.plist Configuration + +On macOS, protocols are registered via your `Info.plist` file. + +#### Automatic Configuration + +Wails automatically generates the `Info.plist` with your protocols when you build with `wails3 build`. + +The protocols from your build assets configuration are added to: + +```xml +CFBundleURLTypes + + + CFBundleURLName + My Application Protocol + CFBundleURLSchemes + + myapp + + CFBundleTypeRole + Editor + + +``` + +#### Testing + +```bash +# Open protocol URL from terminal +open "myapp://test/action" + +# Check registered handlers +/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -dump | grep myapp +``` + + + + + +### Desktop Entry + +On Linux, protocols are registered via `.desktop` files. + +#### Automatic Configuration + +Wails generates a desktop entry file with protocol handlers when you build with `wails3 build`. + +**Fixed in v3**: Linux desktop template now properly includes protocol handling. + +The generated desktop file includes: + +```ini +[Desktop Entry] +Type=Application +Name=My Application +Exec=/usr/bin/myapp %u +MimeType=x-scheme-handler/myapp; +``` + +#### Manual Registration + +If needed, manually install the desktop file: + +```bash +# Copy desktop file +cp myapp.desktop ~/.local/share/applications/ + +# Update desktop database +update-desktop-database ~/.local/share/applications/ + +# Register protocol handler +xdg-mime default myapp.desktop x-scheme-handler/myapp +``` + +#### Testing + +```bash +# Open protocol URL +xdg-open "myapp://test/action" + +# Check registered handler +xdg-mime query default x-scheme-handler/myapp +``` + + + + +## Complete Example + +Here's a complete example handling multiple protocol actions: + +```go +package main + +import ( + "fmt" + "net/url" + "strings" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type App struct { + app *application.App + window *application.WebviewWindow +} + +func main() { + app := application.New(application.Options{ + Name: "DeepLink Demo", + Description: "Custom protocol demonstration", + }) + + myApp := &App{app: app} + myApp.setup() + + app.Run() +} + +func (a *App) setup() { + // Create window + a.window = a.app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "DeepLink Demo", + Width: 800, + Height: 600, + URL: "http://wails.localhost/", + }) + + // Handle custom protocol URLs + a.app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(event *application.ApplicationEvent) { + customURL := event.Context().ClickedURL() + a.handleDeepLink(customURL) + }) +} + +func (a *App) handleDeepLink(rawURL string) { + // Parse URL + parsedURL, err := url.Parse(rawURL) + if err != nil { + a.app.Logger.Error("Failed to parse URL:", err) + return + } + + // Bring window to front + a.window.Show() + a.window.Focus() + + // Extract path and query + path := strings.Trim(parsedURL.Path, "/") + query := parsedURL.Query() + + // Handle different actions + parts := strings.Split(path, "/") + if len(parts) == 0 { + return + } + + action := parts[0] + + switch action { + case "open": + if len(parts) >= 2 { + resource := parts[1] + id := query.Get("id") + a.openResource(resource, id) + } + + case "settings": + section := "" + if len(parts) >= 2 { + section = parts[1] + } + a.openSettings(section) + + case "user": + if len(parts) >= 2 { + username := parts[1] + a.openUserProfile(username) + } + + default: + a.app.Logger.Warn("Unknown action:", action) + } +} + +func (a *App) openResource(resourceType, id string) { + fmt.Printf("Opening %s with ID: %s\n", resourceType, id) + // Emit event to frontend + a.app.Event.Emit("navigate", map[string]string{ + "type": resourceType, + "id": id, + }) +} + +func (a *App) openSettings(section string) { + fmt.Printf("Opening settings section: %s\n", section) + a.app.Event.Emit("navigate", map[string]string{ + "page": "settings", + "section": section, + }) +} + +func (a *App) openUserProfile(username string) { + fmt.Printf("Opening user profile: %s\n", username) + a.app.Event.Emit("navigate", map[string]string{ + "page": "user", + "user": username, + }) +} +``` + +## Frontend Integration + +Handle navigation events in your frontend: + +```javascript +import { Events } from '@wailsio/runtime' + +// Listen for navigation events from protocol handler +Events.On('navigate', (event) => { + const { type, id, page, section, user } = event.data + + if (type === 'document') { + // Open document with ID + router.push(`/document/${id}`) + } else if (page === 'settings') { + // Open settings + router.push(`/settings/${section}`) + } else if (page === 'user') { + // Open user profile + router.push(`/user/${user}`) + } +}) +``` + +## Security Considerations + +### Validate All Input + +Always validate and sanitize URLs from external sources: + +```go +func (a *App) handleDeepLink(rawURL string) { + // Parse URL + parsedURL, err := url.Parse(rawURL) + if err != nil { + a.app.Logger.Error("Invalid URL:", err) + return + } + + // Validate scheme + if parsedURL.Scheme != "myapp" { + a.app.Logger.Warn("Invalid scheme:", parsedURL.Scheme) + return + } + + // Validate path + path := strings.Trim(parsedURL.Path, "/") + if !isValidPath(path) { + a.app.Logger.Warn("Invalid path:", path) + return + } + + // Sanitize parameters + params := sanitizeQueryParams(parsedURL.Query()) + + // Process validated URL + a.processDeepLink(path, params) +} + +func isValidPath(path string) bool { + // Only allow alphanumeric and forward slashes + validPath := regexp.MustCompile(`^[a-zA-Z0-9/]+$`) + return validPath.MatchString(path) +} + +func sanitizeQueryParams(query url.Values) map[string]string { + sanitized := make(map[string]string) + for key, values := range query { + if len(values) > 0 { + // Take first value and sanitize + sanitized[key] = sanitizeString(values[0]) + } + } + return sanitized +} +``` + +### Prevent Injection Attacks + +Never execute URLs directly as code or SQL: + +```go +// ❌ DON'T: Execute URL content +func badHandler(url string) { + exec.Command("sh", "-c", url).Run() // DANGEROUS! +} + +// ✅ DO: Parse and validate +func goodHandler(url string) { + parsed, _ := url.Parse(url) + action := parsed.Query().Get("action") + + // Whitelist allowed actions + allowed := map[string]bool{ + "open": true, + "settings": true, + "help": true, + } + + if allowed[action] { + handleAction(action) + } +} +``` + +## Testing + +### Manual Testing + +Test protocol handlers during development: + +**Windows:** +```powershell +Start-Process "myapp://test/action?id=123" +``` + +**macOS:** +```bash +open "myapp://test/action?id=123" +``` + +**Linux:** +```bash +xdg-open "myapp://test/action?id=123" +``` + +### HTML Testing + +Create a test HTML page: + +```html + + + + Protocol Test + + +

Custom Protocol Test Links

+ + + + +``` + +## Troubleshooting + +### Protocol Not Registered + +**Windows:** +- Check registry: `HKEY_CURRENT_USER\SOFTWARE\Classes\` +- Reinstall with NSIS installer +- Verify installer ran with proper permissions + +**macOS:** +- Rebuild application with `wails3 build` +- Check `Info.plist` in app bundle: `MyApp.app/Contents/Info.plist` +- Reset Launch Services: `/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill` + +**Linux:** +- Check desktop file: `~/.local/share/applications/myapp.desktop` +- Update database: `update-desktop-database ~/.local/share/applications/` +- Verify handler: `xdg-mime query default x-scheme-handler/myapp` + +### Application Not Launching + +**Check logs:** +```go +import "log/slog" + +app := application.New(application.Options{ + LogLevel: slog.LevelDebug, + // ... +}) +``` + +**Common issues:** +- Application not installed in expected location +- Executable path in registration doesn't match actual location +- Permissions issues + +## Best Practices + +### ✅ Do + +- **Use descriptive scheme names** - `mycompany-myapp` instead of `mca` +- **Validate all input** - Never trust URLs from external sources +- **Handle errors gracefully** - Log invalid URLs, don't crash +- **Provide user feedback** - Show what action was triggered +- **Test on all platforms** - Protocol handling varies +- **Document your URL structure** - Help users and integrators + +### ❌ Don't + +- **Don't use common scheme names** - Avoid `http`, `file`, `app`, etc. +- **Don't execute URLs as code** - Huge security risk +- **Don't expose sensitive operations** - Require confirmation for destructive actions +- **Don't assume protocols work everywhere** - Have fallback mechanisms +- **Don't forget URL encoding** - Handle special characters properly + +## Next Steps + +- [Windows Packaging](/guides/build/windows) - Learn about NSIS installer options +- [File Associations](/guides/distribution/file-associations) - Open files with your app +- [Single Instance](/guides/distribution/single-instance) - Prevent multiple app instances + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/guides/e2e-testing.mdx b/docs/src/content/docs/guides/e2e-testing.mdx new file mode 100644 index 000000000..f461afa2e --- /dev/null +++ b/docs/src/content/docs/guides/e2e-testing.mdx @@ -0,0 +1,173 @@ +--- +title: End-to-End Testing +description: Test complete user workflows +sidebar: + order: 6 +--- + +## Overview + +End-to-end testing validates complete user workflows in your application. + +## Using Playwright + +### Setup + +```bash +# Install Playwright +npm install -D @playwright/test + +# Initialize +npx playwright install +``` + +### Configuration + +```javascript +// playwright.config.js +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + use: { + baseURL: 'http://localhost:34115', // Wails dev server + }, +}) +``` + +### Writing Tests + +```javascript +// e2e/app.spec.js +import { test, expect } from '@playwright/test' + +test('create note', async ({ page }) => { + await page.goto('/') + + // Click new note button + await page.click('#new-note-btn') + + // Fill in title + await page.fill('#note-title', 'Test Note') + + // Fill in content + await page.fill('#note-content', 'Test content') + + // Verify note appears in list + await expect(page.locator('.note-item')).toContainText('Test Note') +}) + +test('delete note', async ({ page }) => { + await page.goto('/') + + // Create a note first + await page.click('#new-note-btn') + await page.fill('#note-title', 'To Delete') + + // Delete it + await page.click('#delete-btn') + + // Confirm dialog + page.on('dialog', dialog => dialog.accept()) + + // Verify it's gone + await expect(page.locator('.note-item')).not.toContainText('To Delete') +}) +``` + +## Testing dialogs + +```javascript +test('file save dialog', async ({ page }) => { + await page.goto('/') + + // Intercept file dialog + page.on('filechooser', async (fileChooser) => { + await fileChooser.setFiles('/path/to/test/file.json') + }) + + // Trigger save + await page.click('#save-btn') + + // Verify success message + await expect(page.locator('.success-message')).toBeVisible() +}) +``` + +## Testing Window Behaviour + +```javascript +test('window state', async ({ page }) => { + await page.goto('/') + + // Test window title + await expect(page).toHaveTitle('My App') + + // Test window size + const size = await page.viewportSize() + expect(size.width).toBe(800) + expect(size.height).toBe(600) +}) +``` + +## Best Practices + +### ✅ Do + +- Test critical user flows +- Use data-testid attributes +- Clean up test data +- Run tests in CI/CD +- Test error scenarios +- Keep tests independent + +### ❌ Don't + +- Don't test implementation details +- Don't write brittle selectors +- Don't skip cleanup +- Don't ignore flaky tests +- Don't test everything + +## Running E2E Tests + +```bash +# Run all tests +npx playwright test + +# Run in headed mode +npx playwright test --headed + +# Run specific test +npx playwright test e2e/app.spec.js + +# Debug mode +npx playwright test --debug +``` + +## CI/CD Integration + +```yaml +# .github/workflows/e2e.yml +name: E2E Tests + +on: [push] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - name: Install dependencies + run: npm ci + - name: Install Playwright + run: npx playwright install --with-deps + - name: Run tests + run: npx playwright test +``` + +## Next Steps + +- [Testing](/guides/testing) - Learn unit testing +- [Building](/guides/building) - Build your application diff --git a/docs/src/content/docs/guides/events-reference.mdx b/docs/src/content/docs/guides/events-reference.mdx new file mode 100644 index 000000000..d0081d9db --- /dev/null +++ b/docs/src/content/docs/guides/events-reference.mdx @@ -0,0 +1,617 @@ +--- +title: Events Guide +description: A practical guide to using events in Wails v3 for application communication and lifecycle management +--- + +**NOTE: This guide is a work in progress** + +# Events Guide + +Events are the heartbeat of communication in Wails applications. They allow different parts of your application to talk to each other without being tightly coupled. This guide will walk you through everything you need to know about using events effectively in your Wails application. + +## Understanding Wails Events + +Think of events as messages that get broadcast throughout your application. Any part of your application can listen for these messages and react accordingly. This is particularly useful for: + +- **Responding to window changes**: Know when your window is minimized, maximized, or moved +- **Handling system events**: React to theme changes or power events +- **Custom application logic**: Create your own events for features like data updates or user actions +- **Cross-component communication**: Let different parts of your app communicate without direct dependencies + +## Event Naming Convention + +All Wails events follow a namespace pattern to clearly indicate their origin: + +- `common:` - Cross-platform events that work on Windows, macOS, and Linux +- `windows:` - Windows-specific events +- `mac:` - macOS-specific events +- `linux:` - Linux-specific events + +For example: +- `common:WindowFocus` - Window gained focus (works everywhere) +- `windows:APMSuspend` - System is suspending (Windows only) +- `mac:ApplicationDidBecomeActive` - App became active (macOS only) + +## Getting Started with Events + +### Listening to Events (Frontend) + +The most common use case is listening for events in your frontend code: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Listen for when the window gains focus +Events.On('common:WindowFocus', () => { + console.log('Window is now focused!'); + // Maybe refresh some data or resume animations +}); + +// Listen for theme changes +Events.On('common:ThemeChanged', (event) => { + console.log('Theme changed:', event.data); + // Update your app's theme accordingly +}); + +// Listen for custom events from your Go backend +Events.On('my-app:data-updated', (event) => { + console.log('Data updated:', event.data); + // Update your UI with the new data +}); +``` + +### Emitting Events (Backend) + +From your Go code, you can emit events that your frontend can listen to: + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "time" +) + +func (s *Service) UpdateData() { + // Do some data processing... + + // Notify the frontend + app := application.Get() + app.Event.Emit("my-app:data-updated", + map[string]interface{}{ + "timestamp": time.Now(), + "count": 42, + }, + ) +} +``` + +### Emitting Events (Frontend) + +While not as commonly used, you can also emit events from your frontend that your Go code can listen to: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Event without data +Events.Emit('myapp:close-window') + +// Event with data +Events.Emit('myapp:disconnect-requested', 'id-123') +``` + +If you are using TypeScript in your frontend and [registering typed events](#typed-events-with-typecheck) in your Go code, you will get event name autocomplete/checking and data type checking. + +### Removing Event Listeners + +Always clean up your event listeners when they're no longer needed: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Store the handler reference +const focusHandler = () => { + console.log('Window focused'); +}; + +// Add the listener +Events.On('common:WindowFocus', focusHandler); + +// Later, remove it when no longer needed +Events.Off('common:WindowFocus', focusHandler); + +// Or remove all listeners for an event +Events.Off('common:WindowFocus'); +``` + +## Common Use Cases + +### 1. Pause/Resume on Window Focus + +Many applications need to pause certain activities when the window loses focus: + +```javascript +import { Events } from '@wailsio/runtime'; + +let animationRunning = true; + +Events.On('common:WindowLostFocus', () => { + animationRunning = false; + pauseBackgroundTasks(); +}); + +Events.On('common:WindowFocus', () => { + animationRunning = true; + resumeBackgroundTasks(); +}); +``` + +### 2. Responding to Theme Changes + +Handle theme changes in Go and forward to the frontend: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + isDark := e.Context().IsDarkMode() + app.Event.Emit("theme-changed", isDark) +}) +``` + +Then listen in JavaScript: + +```javascript +import { Events } from '@wailsio/runtime'; + +Events.On('theme-changed', (event) => { + if (event.data) { + document.body.classList.add('dark-theme'); + document.body.classList.remove('light-theme'); + } else { + document.body.classList.add('light-theme'); + document.body.classList.remove('dark-theme'); + } +}); +``` + +You can also listen for the system event directly in JavaScript, but note that the theme data is not included: + +```javascript +Events.On('common:ThemeChanged', () => { + // Theme changed, but isDark is not available + // Use CSS media queries or query the System API instead +}); +``` + +### 3. Handling File Drops + +Handle file drops in Go using the `WindowEventContext`: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.OnWindowEvent(events.Common.WindowFilesDropped, func(e *application.WindowEvent) { + files := e.Context().DroppedFiles() + for _, file := range files { + fmt.Println("File dropped:", file) + } + + // Optionally get drop target details + details := e.Context().DropTargetDetails() + if details != nil { + fmt.Println("Dropped on element:", details.ID) + } + + // Notify the frontend + window.EmitEvent("files-dropped", files) +}) +``` + +Then listen in JavaScript: + +```javascript +import { Events } from '@wailsio/runtime'; + +Events.On('files-dropped', (event) => { + // EmitEvent is variadic, so event.data is a []any wrapping the args. + // The files array is the first element. + const files = event.data[0]; + files.forEach(file => { + console.log('File dropped:', file); + handleFileUpload(file); + }); +}); +``` + +:::note +The `common:WindowFilesDropped` system event is dispatched to the JS frontend but does not include the file paths in its data. Use a Go event handler to access the dropped files via `event.Context().DroppedFiles()` and forward them to the frontend with a custom event. +::: + +### 4. Window Lifecycle Management + +Respond to window state changes: + +```javascript +import { Events } from '@wailsio/runtime'; + +Events.On('common:WindowClosing', () => { + // Save user data before closing + saveApplicationState(); +}); + +Events.On('common:WindowMaximise', () => { + // Adjust UI for maximized view + adjustLayoutForMaximized(); +}); + +Events.On('common:WindowRestore', () => { + // Return UI to normal state + adjustLayoutForNormal(); +}); +``` + +### 5. Platform-Specific Features + +Handle platform-specific events when needed: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Windows-specific power management +Events.On('windows:APMSuspend', () => { + console.log('System is going to sleep'); + saveState(); +}); + +Events.On('windows:APMResumeSuspend', () => { + console.log('System woke up'); + refreshData(); +}); + +// macOS-specific app lifecycle +Events.On('mac:ApplicationWillTerminate', () => { + console.log('App is about to quit'); + performCleanup(); +}); +``` + +## Creating Custom Events + +You can create your own events for application-specific needs. + +### Backend (Go) + +```go +// Emit a custom event when data changes + +func (s *Service) ProcessUserData(userData UserData) error { + // Process the data... + + app := application.Get() + // Notify all listeners + app.Event.Emit("user:data-processed", + map[string]interface{}{ + "userId": userData.ID, + "status": "completed", + "timestamp": time.Now(), + }, + ) + + return nil +} + +// Emit periodic updates +func (s *Service) StartMonitoring() { + app := application.Get() + ticker := time.NewTicker(5 * time.Second) + go func() { + for range ticker.C { + stats := s.collectStats() + app.Event.Emit("monitor:stats-updated", stats) + } + }() +} +``` + +### Frontend (JavaScript) + +```javascript +import { Events } from '@wailsio/runtime'; + +// Listen for your custom events +Events.On('user:data-processed', (event) => { + const { userId, status, timestamp } = event.data; + + showNotification(`User ${userId} processing ${status}`); + updateUIWithNewData(); +}); + +Events.On('monitor:stats-updated', (event) => { + updateDashboard(event.data); +}); +``` + +## Typed Events with Type Safety + +Wails v3 supports typed events with full TypeScript type safety through event registration and automatic binding generation. + +### Registering Custom Events + +Call `application.RegisterEvent` at init time to register custom event names with their data types: + +```go +package main + +import "github.com/wailsapp/wails/v3/pkg/application" + +type UserLoginData struct { + UserID string + Username string + LoginTime string +} + +type MonitorStats struct { + CPUUsage float64 + MemoryUsage float64 +} + +func init() { + // Register events with their data types + application.RegisterEvent[UserLoginData]("user:login") + application.RegisterEvent[MonitorStats]("monitor:stats") + + // Register events without data (void events) + application.RegisterEvent[application.Void]("app:ready") +} +``` + +:::caution +`RegisterEvent` is meant to be called at init time and will panic if: +- The event name conflicts with a known system event name +- The same event name is registered more than once +::: + +### Benefits of Event Registration + +Once registered, data arguments passed to `Event.Emit` are type-checked against the specified type. On mismatch: +- An error is emitted and logged (or passed to the registered error handler) +- The offending event will not be propagated +- This ensures the data field of registered events is always assignable to the declared type + +### Strict Mode + +Use the `strictevents` build tag to enable warnings for unregistered events in development: + +```bash +go build -tags strictevents +``` + +With strict mode enabled, the runtime emits at most one warning per unregistered event name to avoid spamming logs. + +### TypeScript Binding Generation + +The binding generator outputs TypeScript definitions and glue code for transparent typed event support in the frontend. + +#### 1. Set up the Vite Plugin + +In your `vite.config.ts`: + +```typescript +import { defineConfig } from 'vite' +import wails from '@wailsio/runtime/plugins/vite' + +export default defineConfig({ + plugins: [wails()], +}) +``` + +#### 2. Generate Bindings + +Run the binding generator: + +```bash +wails3 generate bindings +``` + +This creates TypeScript files in your frontend directory with typed event creators and data interfaces. + +#### 3. Use Typed Events in Frontend + +```typescript +import { Events } from '@wailsio/runtime' +import { UserLogin, MonitorStats } from './bindings/events' + +// Type-safe event emission with autocomplete +Events.Emit(UserLogin({ + UserID: "123", + Username: "john_doe", + LoginTime: new Date().toISOString() +})) + +// Type-safe event listening +Events.On(UserLogin, (event) => { + // event.data is typed as UserLoginData + console.log(`User ${event.data.Username} logged in`) +}) + +Events.On(MonitorStats, (event) => { + // event.data is typed as MonitorStats + updateDashboard({ + cpu: event.data.CPUUsage, + memory: event.data.MemoryUsage + }) +}) +``` + +The typed events provide: +- **Autocomplete** for event names +- **Type checking** for event data +- **Compile-time errors** for mismatched data types +- **IntelliSense** documentation + +## Event Reference + +### Common Events (Cross-platform) + +These events work on all platforms: + +| Event | Description | When to Use | +|-------|-------------|-------------| +| `common:ApplicationOpenedWithFile` | App launched with a file | Open file passed at launch | +| `common:ApplicationStarted` | Application has fully started | Initialize your app, load saved state | +| `common:ApplicationLaunchedWithUrl` | App launched via URL scheme | Handle custom URL schemes | +| `common:ThemeChanged` | System theme changed | Update app appearance | +| `common:WindowClosing` | Window is about to close | Save data, cleanup resources | +| `common:WindowDidMove` | Window was moved | Update position-dependent features | +| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts | +| `common:WindowDPIChanged` | DPI scaling changed | Adjust rendering for new DPI | +| `common:WindowFilesDropped` | Files dropped on window | Handle file imports | +| `common:WindowFocus` | Window gained focus | Resume activities, refresh data | +| `common:WindowFullscreen` | Window entered fullscreen | Adjust layout for fullscreen | +| `common:WindowHide` | Window was hidden | Pause rendering | +| `common:WindowLostFocus` | Window lost focus | Pause activities, save state | +| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen | +| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage | +| `common:WindowToggleFrameless` | Frameless mode toggled | Update window chrome | +| `common:WindowRestore` | Window restored from min/max | Return to normal layout | +| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls | +| `common:WindowShow` | Window became visible | Resume rendering | +| `common:WindowUnFullscreen` | Window exited fullscreen | Restore normal layout | +| `common:WindowUnMaximise` | Window restored from maximized | Restore normal layout | +| `common:WindowUnMinimise` | Window restored from minimized | Resume rendering | +| `common:WindowZoom` | Window zoom changed | Adjust content scaling | +| `common:WindowZoomIn` | Zoom level increased | Update zoom indicator | +| `common:WindowZoomOut` | Zoom level decreased | Update zoom indicator | +| `common:WindowZoomReset` | Zoom reset to default | Update zoom indicator | + +### Platform-Specific Events + +#### Windows Events + +Key events for Windows applications: + +| Event | Description | Use Case | +|-------|-------------|----------| +| `windows:SystemThemeChanged` | Windows theme changed | Update app colors | +| `windows:APMSuspend` | System suspending | Save state, pause operations | +| `windows:APMResumeSuspend` | System resumed | Restore state, refresh data | +| `windows:APMPowerStatusChange` | Power status changed | Adjust performance settings | + +#### macOS Events + +Important macOS application events: + +| Event | Description | Use Case | +|-------|-------------|----------| +| `mac:ApplicationDidBecomeActive` | App became active | Resume operations | +| `mac:ApplicationDidResignActive` | App became inactive | Pause operations | +| `mac:ApplicationWillTerminate` | App will quit | Final cleanup | +| `mac:WindowDidEnterFullScreen` | Entered fullscreen | Adjust UI for fullscreen | +| `mac:WindowDidExitFullScreen` | Exited fullscreen | Restore normal UI | + +#### Linux Events + +Core Linux window events: + +| Event | Description | Use Case | +|-------|-------------|----------| +| `linux:SystemThemeChanged` | Desktop theme changed | Update app theme | +| `linux:WindowFocusIn` | Window gained focus | Resume activities | +| `linux:WindowFocusOut` | Window lost focus | Pause activities | + +## Best Practices + +### 1. Use Event Namespaces + +When creating custom events, use namespaces to avoid conflicts: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Good - namespaced events +Events.Emit('myapp:user:login'); +Events.Emit('myapp:data:updated'); +Events.Emit('myapp:network:connected'); + +// Avoid - generic names that might conflict +Events.Emit('login'); +Events.Emit('update'); +``` + +### 2. Clean Up Listeners + +Always remove event listeners when components unmount: + +```javascript +import { Events } from '@wailsio/runtime'; + +// React example +useEffect(() => { + const handler = (event) => { + // Handle event + }; + + Events.On('common:WindowDidResize', handler); + + // Cleanup + return () => { + Events.Off('common:WindowDidResize', handler); + }; +}, []); +``` + +### 3. Handle Platform Differences + +Check platform availability when using platform-specific events: + +```javascript +import { Platform, Events } from '@wailsio/runtime'; + +if (Platform.isWindows) { + Events.On('windows:APMSuspend', handleSuspend); +} else if (Platform.isMac) { + Events.On('mac:ApplicationWillTerminate', handleTerminate); +} +``` + +### 4. Don't Overuse Events + +While events are powerful, don't use them for everything: + +- ✅ Use events for: System notifications, lifecycle changes, broadcast updates +- ❌ Avoid events for: Direct function returns, single component updates, synchronous operations + +## Debugging Events + +To debug event issues: + +```javascript +import { Events } from '@wailsio/runtime'; + +// Log all events (development only) +if (isDevelopment) { + const originalOn = Events.On; + Events.On = function(eventName, handler) { + console.log(`[Event Registered] ${eventName}`); + return originalOn.call(this, eventName, function(event) { + console.log(`[Event Fired] ${eventName}`, event); + return handler(event); + }); + }; +} +``` + +## Source of Truth + +The complete list of available events can be found in the Wails source code: +- Frontend events: [`v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts`](https://github.com/wailsapp/wails/blob/main/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts) +- Backend events: [`v3/pkg/events/events.go`](https://github.com/wailsapp/wails/blob/main/v3/pkg/events/events.go) + +Always refer to these files for the most up-to-date event names and availability. + +## Summary + +Events in Wails provide a powerful, decoupled way to handle communication in your application. By following the patterns and practices in this guide, you can build responsive, platform-aware applications that react smoothly to system changes and user interactions. + +Remember: start with common events for cross-platform compatibility, add platform-specific events when needed, and always clean up your event listeners to prevent memory leaks. \ No newline at end of file diff --git a/docs/src/content/docs/guides/file-associations.mdx b/docs/src/content/docs/guides/file-associations.mdx new file mode 100644 index 000000000..ba37d9f35 --- /dev/null +++ b/docs/src/content/docs/guides/file-associations.mdx @@ -0,0 +1,178 @@ +--- +title: File Associations +sidebar: + order: 20 +--- + +import { Steps } from "@astrojs/starlight/components"; +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
+ +File associations allow your application to handle specific file types when +users open them. This is particularly useful for text editors, image viewers, or +any application that works with specific file formats. This guide explains how +to implement file associations in your Wails v3 application. + +## Overview + +File association support in Wails v3 is currently available for: + +- Windows (NSIS installer packages) +- macOS (application bundles) + +## Configuration + +File associations are configured in the `config.yml` file located in your +project's `build` directory. + +### Basic Configuration + +To set up file associations: + +1. Open `build/config.yml` +2. Add your file associations under the `fileAssociations` section +3. Run `wails3 update build-assets` to update the build assets +4. Set the `FileAssociations` field in the application options +5. Package your application using `wails3 package` + +Here's an example configuration: + +```yaml +fileAssociations: + - ext: myapp + name: MyApp Document + description: MyApp Document File + iconName: myappFileIcon + role: Editor + - ext: custom + name: Custom Format + description: Custom File Format + iconName: customFileIcon + role: Editor +``` + +### Configuration Properties + +| Property | Description | Platform | +|-------------|------------------------------------------------------------------|----------| +| ext | File extension without the leading period (e.g., `txt`) | All | +| name | Display name for the file type | All | +| description | Description shown in file properties | Windows | +| iconName | Name of the icon file (without extension) in the build folder | All | +| role | Application's role for this file type (e.g., `Editor`, `Viewer`) | macOS | +| mimeType | MIME type for the file (e.g., `image/jpeg`) | macOS | + +## Listening for File Open Events + +To handle file open events in your application, you can listen for the +`events.Common.ApplicationOpenedWithFile` event: + +```go title="main.go" +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +func main() { + app := application.New(application.Options{ + Name: "MyApp", + FileAssociations: []string{".txt", ".md"}, // Specify supported extensions + }) + + // Listen for files being used to open the application + app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(event *application.ApplicationEvent) { + associatedFile := event.Context().Filename() + application.InfoDialog().SetMessage("Application opened with file: " + associatedFile).Show() + }) + + // Create your window and run the app... +} + +``` + +## Step-by-Step Tutorial + +Let's walk through setting up file associations for a simple text editor: + + + +1. ### Create Icons + + - Create icons for your file type (recommended sizes: 16x16, 32x32, 48x48, + 256x256) + - Save the icons in your project's `build` folder + - Name them according to your `iconName` configuration (e.g., + `textFileIcon.png`) + + :::tip + You can use `wails3 generate icons` to generate the required icons for you. + Run `wails3 generate icons --help` for more information. + ::: + + - For macOS add copy statement like `cp build/darwin/documenticon.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources` in the `create:app:bundle:` task. + +2. ### Configure File Associations + + Edit the `build/config.yml` file to add your file associations: + + ```yaml + # build/config.yml + fileAssociations: + - ext: txt + name: Text Document + description: Plain Text Document + iconName: textFileIcon + role: Editor + ``` + +3. ### Update Build Assets + + Run the following command to update the build assets: + + ```bash + wails3 update build-assets + ``` + +4. ### Set File Associations in the Application Options + + In your `main.go` file, set the `FileAssociations` field in the application + options: + + ```go + app := application.New(application.Options{ + Name: "MyApp", + FileAssociations: []string{".txt", ".md"}, // Specify supported extensions + }) + ``` + + :::tip[Why are file extensions required in both the application config and config.yml?] + + On Windows, when a file is opened with a file association, the application is + launched with the filename as the first argument to the application. The + application has no way of knowing if the first argument is a file or a command + line argument, so it uses the `FileAssociations` field in the application + options to determine if the first argument is an associated file or not. + + ::: + +5. ### Package Your Application + + Package your application using the following command: + + ```bash + wails3 package + ``` + + The packaged application will be created in the `bin` directory. You can then + install and test the application. + + ## Additional Notes + + - Icons should be provided in PNG format in the build folder + - Testing file associations requires installing the packaged application + + \ No newline at end of file diff --git a/docs/src/content/docs/guides/gin-routing.mdx b/docs/src/content/docs/guides/gin-routing.mdx new file mode 100644 index 000000000..d4a388a84 --- /dev/null +++ b/docs/src/content/docs/guides/gin-routing.mdx @@ -0,0 +1,263 @@ +--- +title: Using Gin for Routing +description: A comprehensive guide to integrating Gin web framework with Wails v3 applications +--- + +This guide demonstrates how to integrate the [Gin web framework](https://github.com/gin-gonic/gin) with Wails v3. Gin is a high-performance HTTP web framework written in Go that makes it easy to build web applications and APIs. + +## Introduction + +Wails v3 provides a flexible asset system that allows you to use any HTTP handler, including popular web frameworks like Gin. This integration enables you to: + +- Serve web content using Gin's routing and middleware +- Create RESTful APIs accessible from your Wails application +- Use Gin's features while maintaining Wails desktop integration + +## Setting Up Gin with Wails + +To integrate Gin with Wails, you need to create a Gin router and configure it as the asset handler in your Wails application. Here's a step-by-step guide: + +### 1. Install Dependencies + +First, ensure you have the Gin package installed: + +```bash +go get -u github.com/gin-gonic/gin +``` + +### 2. Create a Middleware for Gin + +Create a middleware function that will handle the integration between Wails and Gin: + +```go +// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails +func GinMiddleware(ginEngine *gin.Engine) application.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Let Wails handle the `/wails` route + if strings.HasPrefix(r.URL.Path, "/wails") { + next.ServeHTTP(w, r) + return + } + // Let Gin handle everything else + ginEngine.ServeHTTP(w, r) + }) + } +} +``` + +This middleware passes all HTTP requests to the Gin router. + +### 3. Configure Your Gin Router + +Set up your Gin router with routes, middlewares, and handlers: + +```go +// Create a new Gin router +ginEngine := gin.New() // Using New() instead of Default() to add custom middleware + +// Add middlewares +ginEngine.Use(gin.Recovery()) +ginEngine.Use(LoggingMiddleware()) // Your custom middleware + +// Define routes +ginEngine.GET("/", func(c *gin.Context) { + // Serve your main page +}) + +ginEngine.GET("/api/hello", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Hello from Gin API!", + "time": time.Now().Format(time.RFC3339), + }) +}) +``` + +### 4. Integrate with Wails Application + +Configure your Wails application to use the Gin router as its asset handler: + +```go +// Create a new Wails application +app := application.New(application.Options{ + Name: "Gin Example", + Description: "A demo of using Gin with Wails", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + Assets: application.AssetOptions{ + Handler: ginEngine, + Middleware: GinMiddleware(ginEngine), + }, +}) + +// Create window +app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Wails + Gin Example", + Width: 900, + Height: 700, + URL: "/", // This will load the route handled by Gin +}) +``` + +## Serving Static Content + +Use Go's `embed` package to embed static files into your binary: + +```go +//go:embed static +var staticFiles embed.FS + +ginEngine.StaticFS("/static", http.FS(staticFiles)) +ginEngine.GET("/", func(c *gin.Context) { + file, _ := staticFiles.ReadFile("static/index.html") + c.Data(http.StatusOK, "text/html; charset=utf-8", file) +}) +``` + +For development, serve files directly from disk using `ginEngine.Static("/static", "./static")`. + +## Custom Middleware + +Gin allows you to create custom middleware for various purposes. Here's an example of a logging middleware: + +```go +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + startTime := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(startTime) + + // Log request details + log.Printf("[GIN] %s | %s | %s | %d | %s", + c.Request.Method, + c.Request.URL.Path, + c.ClientIP(), + c.Writer.Status(), + latency, + ) + } +} +``` + +## Handling API Requests + +Gin makes it easy to create RESTful APIs. Here's how to define API endpoints: + +```go +// GET endpoint +ginEngine.GET("/api/users", func(c *gin.Context) { + c.JSON(http.StatusOK, users) +}) + +// POST endpoint with JSON binding +ginEngine.POST("/api/users", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + // Process the new user... + c.JSON(http.StatusCreated, newUser) +}) + +// Path parameters +ginEngine.GET("/api/users/:id", func(c *gin.Context) { + id := c.Param("id") + // Find user by ID... + c.JSON(http.StatusOK, user) +}) + +// Query parameters +ginEngine.GET("/api/search", func(c *gin.Context) { + query := c.DefaultQuery("q", "") + limit := c.DefaultQuery("limit", "10") + // Perform search... + c.JSON(http.StatusOK, results) +}) +``` + +## Using Wails Features + +Your Gin-served web content can interact with Wails features using the `@wailsio/runtime` package. + +### Event Handling + +Register event handlers in Go: + +```go +app.Event.On("my-event", func(event *application.CustomEvent) { + log.Printf("Received event: %v", event.Data) +}) +``` + +Call from JavaScript using the runtime: + +```html + +``` + +## Advanced Configuration + +### Customising Gin's Mode + +Set Gin to release mode for production: + +```go +gin.SetMode(gin.ReleaseMode) // Use gin.DebugMode for development +ginEngine := gin.New() +``` + +### Handling WebSockets + +You can integrate WebSockets with Gin using libraries like Gorilla WebSocket: + +```go +import "github.com/gorilla/websocket" + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true // Allow all connections + }, +} + +// In your route handler: +ginEngine.GET("/ws", func(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Println(err) + return + } + defer conn.Close() + + // Handle WebSocket connection... +}) +``` + +## Best Practices + +- **Use Go's embed Package:** Embed static files into your binary for better distribution. +- **Separate Concerns:** Keep your API logic separate from your UI logic. +- **Error Handling:** Implement proper error handling in both Gin routes and frontend code. +- **Security:** Be mindful of security considerations, especially when handling user input. +- **Performance:** Use Gin's release mode in production for better performance. +- **Testing:** Write tests for your Gin routes using Gin's testing utilities. + +## Conclusion + +Integrating Gin with Wails provides a powerful combination for building desktop applications with web technologies. Gin's performance and feature set complement Wails' desktop integration capabilities, allowing you to create sophisticated applications that use the best of both worlds. + +For more information, refer to the [Gin documentation](https://github.com/gin-gonic/gin) and the [Wails documentation](https://wails.io). diff --git a/docs/src/content/docs/guides/gin-services.mdx b/docs/src/content/docs/guides/gin-services.mdx new file mode 100644 index 000000000..bded210f5 --- /dev/null +++ b/docs/src/content/docs/guides/gin-services.mdx @@ -0,0 +1,558 @@ +--- +title: Using Gin for Services +description: A guide to integrating the Gin web framework with Wails v3 Services +--- + +# Using Gin for Services + +The Gin web framework is a popular choice for building HTTP services in Go. With Wails v3, you can easily integrate Gin-based services into your application, providing a powerful way to handle HTTP requests, implement RESTful APIs, and serve web content. + +This guide will walk you through creating a Gin-based service that can be mounted at a specific route in your Wails application. We'll build a complete example that demonstrates how to: + +1. Create a Gin-based service +2. Implement the Wails Service interface +3. Set up routes and middleware +4. Integrate with the Wails event system +5. Interact with the service from the frontend + +## Prerequisites + +Before you begin, make sure you have: + +- Wails v3 installed +- Basic knowledge of Go and the Gin framework +- Familiarity with HTTP concepts and RESTful APIs + +You'll need to add the Gin framework to your project: + +```bash +go get github.com/gin-gonic/gin +``` + +## Creating a Gin-Based Service + +Let's start by creating a Gin service that implements the Wails Service interface. Our service will manage a collection of users and provide API endpoints for retrieving and creating user records. + +### 1. Define Your Data Models + +First, define the data structures your service will work with: + +```go +package services + +import ( + "context" + "net/http" + "strconv" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// User represents a user in the system +type User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + CreatedAt time.Time `json:"createdAt"` +} + +// EventData represents data sent in events +type EventData struct { + Message string `json:"message"` + Timestamp string `json:"timestamp"` +} +``` + +### 2. Create Your Service Structure + +Next, define the service structure that will hold your Gin router and any state your service needs to maintain: + +```go +// GinService implements a Wails service that uses Gin for HTTP handling +type GinService struct { + ginEngine *gin.Engine + users []User + nextID int + mu sync.RWMutex + app *application.App +} + +// NewGinService creates a new GinService instance +func NewGinService() *GinService { + // Create a new Gin router + ginEngine := gin.New() + + // Add middlewares + ginEngine.Use(gin.Recovery()) + ginEngine.Use(LoggingMiddleware()) + + service := &GinService{ + ginEngine: ginEngine, + users: []User{ + {ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now().Add(-72 * time.Hour)}, + {ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-48 * time.Hour)}, + {ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-24 * time.Hour)}, + }, + nextID: 4, + } + + // Define routes + service.setupRoutes() + + return service +} +``` + +### 3. Implement the Service Interface + +Implement the required methods for the Wails Service interface: + +```go +// ServiceName returns the name of the service +func (s *GinService) ServiceName() string { + return "Gin API Service" +} + +// ServiceStartup is called when the service starts +func (s *GinService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Store the application instance for later use + s.app = application.Get() + + // Register an event handler that can be triggered from the frontend + s.app.Event.On("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // Emit an event back to the frontend + s.app.Event.EmitEvent(&application.CustomEvent{ + Name: "gin-api-response", + Data: map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }, + }) + }) + + return nil +} + +// ServiceShutdown is called when the service shuts down +func (s *GinService) ServiceShutdown() error { + // Clean up resources if needed + return nil +} +``` + +### 3. Implement the http.Handler Interface + +To make your service mountable at a specific route, implement the `http.Handler` interface. This single method, `ServeHTTP`, is the gateway for all HTTP requests to your service. It delegates the request handling to the Gin router, allowing you to use all of Gin's powerful features for routing and middleware. + +```go +// ServeHTTP implements the http.Handler interface +func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // All requests go to the Gin router + s.ginEngine.ServeHTTP(w, r) +} +``` + +### 4. Set Up Your Routes + +Define your API routes in a separate method for better organisation. This approach keeps your code clean and makes it easier to understand the structure of your API. The Gin router provides a fluent API for defining routes, including support for route groups, which help organise related endpoints. + +```go +// setupRoutes configures the API routes +func (s *GinService) setupRoutes() { + // Basic info endpoint + s.ginEngine.GET("/info", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "service": "Gin API Service", + "version": "1.0.0", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Users group + users := s.ginEngine.Group("/users") + { + // Get all users + users.GET("", func(c *gin.Context) { + s.mu.RLock() + defer s.mu.RUnlock() + c.JSON(http.StatusOK, s.users) + }) + + // Get user by ID + users.GET("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.RLock() + defer s.mu.RUnlock() + + for _, user := range s.users { + if user.ID == id { + c.JSON(http.StatusOK, user) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + + // Create a new user + users.POST("", func(c *gin.Context) { + var newUser User + if err := c.ShouldBindJSON(&newUser); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + // Set the ID and creation time + newUser.ID = s.nextID + newUser.CreatedAt = time.Now() + s.nextID++ + + // Add to the users slice + s.users = append(s.users, newUser) + + c.JSON(http.StatusCreated, newUser) + + // Emit an event to notify about the new user + s.app.Event.Emit("user-created", newUser) + }) + + // Delete a user + users.DELETE("/:id", func(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + s.mu.Lock() + defer s.mu.Unlock() + + for i, user := range s.users { + if user.ID == id { + // Remove the user from the slice + s.users = append(s.users[:i], s.users[i+1:]...) + c.JSON(http.StatusOK, gin.H{"message": "User deleted"}) + return + } + } + + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + }) + } +} +``` + +### 5. Create Custom Middleware + +You can create custom Gin middleware to enhance your service. Middleware functions in Gin are executed in the order they are added to the router and can perform tasks such as logging, authentication, and error handling. This example shows a simple logging middleware that records request details. + +```go +// LoggingMiddleware is a Gin middleware that logs request details +func LoggingMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + + // Process request + c.Next() + + // Calculate latency + latency := time.Since(start) + + // Log request details + log.Printf("[GIN] %s %s %d %s", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency) + } +} +``` + +## Registering Your Service + +To use your Gin-based service in a Wails application, you need to register it with the application and specify the route where it should be mounted. This is done when creating the Wails application instance. The route you specify becomes the base path for all endpoints defined in your Gin router. + +```go +app := application.New(application.Options{ + Name: "Gin Service Demo", + Description: "A demo of using Gin in Wails services", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + LogLevel: slog.LevelDebug, + Services: []application.Service{ + application.NewServiceWithOptions(services.NewGinService(), application.ServiceOptions{ + Route: "/api", + }), + }, + Assets: application.AssetOptions{ + Handler: application.BundledAssetFileServer(assets), + }, +}) +``` + +In this example, the Gin service is mounted at the `/api` route. This means that if your Gin router has an endpoint defined as `/info`, it will be accessible at `/api/info` in your application. This approach allows you to organise your API endpoints logically and avoid conflicts with other parts of your application. + +## Integrating with the Wails Event System + +One of the powerful features of using Gin with Wails Services is the ability to seamlessly integrate with the Wails event system. This allows for real-time communication between your backend service and the frontend. + +In your service's `ServiceStartup` method, you can register event handlers to process events from the frontend: + +```go +s.app.Event.On("gin-api-event", func(event *application.CustomEvent) { + // Log the event data + s.app.Logger.Info("Received event from frontend", "data", event.Data) + + // Emit an event back to the frontend + s.app.Event.EmitEvent(&application.CustomEvent{ + Name: "gin-api-response", + Data: map[string]interface{}{ + "message": "Response from Gin API Service", + "time": time.Now().Format(time.RFC3339), + }, + }) +}) +``` + +You can also emit events to the frontend from your Gin routes or other parts of your service: + +```go +// After creating a new user +s.app.Event.Emit("user-created", newUser) +``` + +## Frontend Integration + +To interact with your Gin service from the frontend, you'll need to import the Wails runtime, make HTTP requests to your API endpoints, and use the Wails event system for real-time communication. + +For production use, it's recommended to use the `@wailsio/runtime` package instead of directly importing `/wails/runtime.js`. This ensures type safety, better IDE support, version management through npm, and compatibility with modern JavaScript tooling. + +Install the package: +```bash +npm install @wailsio/runtime +``` + +Then use it in your code: +```javascript +import * as wails from '@wailsio/runtime'; + +// Event emission +wails.Events.Emit('gin-api-event', eventData); +``` + +Here's an example of how to set up frontend integration: + +```html + + + + + + Gin Service Example + + + +

Gin Service Example

+ +
+

API Endpoints

+

Try the Gin API endpoints mounted at /api:

+ + + + + + + +
+
Results will appear here...
+
+
+ +
+

Event Communication

+

Trigger an event to communicate with the Gin service:

+ + + +
+
Event responses will appear here...
+
+
+ + + + + + +``` + +## Closing Thoughts + +Integrating the Gin web framework with Wails v3 Services provides a powerful and flexible approach to building modular, maintainable web applications. By leveraging Gin's routing and middleware capabilities alongside the Wails event system, you can create rich, interactive applications with clean separation of concerns. + +The complete example code for this guide can be found in the Wails repository under `v3/examples/gin-service`. diff --git a/docs/src/content/docs/guides/installers.mdx b/docs/src/content/docs/guides/installers.mdx new file mode 100644 index 000000000..170b26865 --- /dev/null +++ b/docs/src/content/docs/guides/installers.mdx @@ -0,0 +1,160 @@ +--- +title: Creating Installers +description: Package your application for distribution +sidebar: + order: 3 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +## Overview + +Create professional installers for your Wails application on all platforms. + +## Platform Installers + + + + ### NSIS Installer + + ```bash + # Install NSIS + # Download from: https://nsis.sourceforge.io/ + + # Create installer script (installer.nsi) + makensis installer.nsi + ``` + + **installer.nsi:** + ```nsis + !define APPNAME "MyApp" + !define VERSION "1.0.0" + + Name "${APPNAME}" + OutFile "MyApp-Setup.exe" + InstallDir "$PROGRAMFILES\${APPNAME}" + + Section "Install" + SetOutPath "$INSTDIR" + File "build\bin\myapp.exe" + CreateShortcut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\myapp.exe" + SectionEnd + ``` + + ### WiX Toolset + + Alternative for MSI installers. + + + + ### DMG Creation + + ```bash + # Create DMG + hdiutil create -volname "MyApp" -srcfolder build/bin/MyApp.app -ov -format UDZO MyApp.dmg + ``` + + ### Code Signing + + ```bash + # Sign application + codesign --deep --force --verify --verbose --sign "Developer ID" MyApp.app + + # Notarize + xcrun notarytool submit MyApp.dmg --apple-id "email" --password "app-password" + ``` + + ### App Store + + Use Xcode for App Store distribution. + + + + ### DEB Package + + ```bash + # Create package structure + mkdir -p myapp_1.0.0/DEBIAN + mkdir -p myapp_1.0.0/usr/bin + + # Copy binary + cp build/bin/myapp myapp_1.0.0/usr/bin/ + + # Create control file + cat > myapp_1.0.0/DEBIAN/control << EOF + Package: myapp + Version: 1.0.0 + Architecture: amd64 + Maintainer: Your Name + Description: My Application + EOF + + # Build package + dpkg-deb --build myapp_1.0.0 + ``` + + ### RPM Package + + Use `rpmbuild` for RPM-based distributions. + + ### AppImage + + ```bash + # Use appimagetool + appimagetool myapp.AppDir + ``` + + + +## Automated Packaging + +### Using GoReleaser + +```yaml +# .goreleaser.yml +project_name: myapp + +builds: + - binary: myapp + goos: + - windows + - darwin + - linux + goarch: + - amd64 + - arm64 + +archives: + - format: zip + name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + +nfpms: + - formats: + - deb + - rpm + vendor: Your Company + homepage: https://example.com + description: My Application +``` + +## Best Practices + +### ✅ Do + +- Code sign on all platforms +- Include version information +- Create uninstallers +- Test installation process +- Provide clear documentation + +### ❌ Don't + +- Don't skip code signing +- Don't forget file associations +- Don't hardcode paths +- Don't skip testing + +## Next Steps + +- [Auto-Updates](/guides/auto-updates) - Implement automatic updates +- [Cross-Platform Building](/guides/cross-platform) - Build for multiple platforms diff --git a/docs/src/content/docs/guides/menus.mdx b/docs/src/content/docs/guides/menus.mdx new file mode 100644 index 000000000..01097fe9e --- /dev/null +++ b/docs/src/content/docs/guides/menus.mdx @@ -0,0 +1,557 @@ +--- +title: Menus +description: A guide to creating and customising menus in Wails v3 +--- + +Wails v3 provides a powerful menu system that allows you to create both application menus and context menus. This guide will walk you through the various features and capabilities of the menu system. + +### Creating a Menu + +To create a new menu, use the `New()` method from the Menus manager: + +```go +menu := app.Menu.New() +``` + +### Adding Menu Items + +Wails supports several types of menu items, each serving a specific purpose: + +#### Regular Menu Items +Regular menu items are the basic building blocks of menus. They display text and can trigger actions when clicked: + +```go +menuItem := menu.Add("Click Me") +``` + +#### Checkboxes +Checkbox menu items provide a toggleable state, useful for enabling/disabling features or settings: + +```go +checkbox := menu.AddCheckbox("My checkbox", true) // true = initially checked +``` + +#### Radio Groups +Radio groups allow users to select one option from a set of mutually exclusive choices. They are automatically created when radio items are placed next to each other: + +```go +menu.AddRadio("Option 1", true) // true = initially selected +menu.AddRadio("Option 2", false) +menu.AddRadio("Option 3", false) +``` + +#### Separators +Separators are horizontal lines that help organise menu items into logical groups: + +```go +menu.AddSeparator() +``` + +#### Submenus +Submenus are nested menus that appear when hovering over or clicking a menu item. They're useful for organizing complex menu structures: + +```go +submenu := menu.AddSubmenu("File") +submenu.Add("Open") +submenu.Add("Save") +``` + +#### Combining menus +A menu can be added into another menu by appending or prepending it. +```go +menu := app.Menu.New() +menu.Add("First Menu") + +secondaryMenu := app.Menu.New() +secondaryMenu.Add("Second Menu") + +// insert 'secondaryMenu' after 'menu' +menu.Append(secondaryMenu) + +// insert 'secondaryMenu' before 'menu' +menu.Prepend(secondaryMenu) + +// update the menu +menu.Update() +``` + +:::note +By default, `prepend` and `append` will share state with the original menu. If you want to create a new menu with its own state, +you can call `.Clone()` on the menu. + +E.g: `menu.Append(secondaryMenu.Clone())` +::: + +#### Clearing a menu +In some cases it'll be better to construct a whole new menu if you are working with a variable amount of menu items. + +This will clear all items on an existing menu and allows you to add items again. + +```go +menu := app.Menu.New() +menu.Add("Waiting for update...") + +// after certain logic, the menu has to be updated +menu.Clear() +menu.Add("Update complete!") +menu.Update() +``` + +:::note +Clearing a menu simply clears the menu items at the top level. Whilst Submenus won't be visible, they will still occupy memory +so be sure to manage your menus carefully. +::: + +#### Destroying a menu + +If you want to clear and release a menu, use the `Destroy()` method: + +```go +menu := app.Menu.New() +menu.Add("Waiting for update...") + +// after certain logic, the menu has to be destroyed +menu.Destroy() +``` + + +### Menu Item Properties + +Menu items have several properties that can be configured: + +| Property | Method | Description | +|-------------|--------------------------|-----------------------------------------------------| +| Label | `SetLabel(string)` | Sets the display text | +| Enabled | `SetEnabled(bool)` | Enables/disables the item | +| Checked | `SetChecked(bool)` | Sets the checked state (for checkboxes/radio items) | +| Tooltip | `SetTooltip(string)` | Sets the tooltip text | +| Hidden | `SetHidden(bool)` | Shows/hides the item | +| Accelerator | `SetAccelerator(string)` | Sets the keyboard shortcut | + +### Menu Item States + +Menu items can be in different states that control their visibility and interactivity: + +#### Visibility + +Menu items can be shown or hidden dynamically using the `SetHidden()` method: + +```go +menuItem := menu.Add("Dynamic Item") + +// Hide the menu item +menuItem.SetHidden(true) + +// Show the menu item +menuItem.SetHidden(false) + +// Check current visibility +isHidden := menuItem.Hidden() +``` + +Hidden menu items are completely removed from the menu until shown again. This is useful for contextual menu items that should only appear in certain application states. + +#### Enabled State + +Menu items can be enabled or disabled using the `SetEnabled()` method: + +```go +menuItem := menu.Add("Save") + +// Disable the menu item +menuItem.SetEnabled(false) // Item appears grayed out and cannot be clicked + +// Enable the menu item +menuItem.SetEnabled(true) // Item becomes clickable again + +// Check current enabled state +isEnabled := menuItem.Enabled() +``` + +Disabled menu items remain visible but appear grayed out and cannot be clicked. This is commonly used to indicate that an action is currently unavailable, such as: +- Disabling "Save" when there are no changes to save +- Disabling "Copy" when nothing is selected +- Disabling "Undo" when there's no action to undo + +#### Dynamic State Management + +You can combine these states with event handlers to create dynamic menus: + +```go +saveMenuItem := menu.Add("Save") + +// Initially disable the Save menu item +saveMenuItem.SetEnabled(false) + +// Enable Save only when there are unsaved changes +documentChanged := func() { + saveMenuItem.SetEnabled(true) + menu.Update() // Remember to update the menu after changing states +} + +// Disable Save after saving +documentSaved := func() { + saveMenuItem.SetEnabled(false) + menu.Update() +} +``` + +### Event Handling + +Menu items can respond to click events using the `OnClick` method: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Handle the click event + println("Menu item clicked!") +}) +``` + +The context provides information about the clicked menu item: + +```go +menuItem.OnClick(func(ctx *application.Context) { + // Get the clicked menu item + clickedItem := ctx.ClickedMenuItem() + // Get its current state + isChecked := clickedItem.Checked() +}) +``` + +### Role-Based Menu Items + +Wails provides a set of predefined menu roles that automatically create menu items with standard functionality. Here are the supported menu roles: + +#### Complete Menu Structures +These roles create entire menu structures with common functionality: + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | macOS only | +| `FileMenu` | File menu with Close Window (macOS) or Quit (Windows/Linux) | All platforms | +| `EditMenu` | Standard Edit menu with Undo, Redo, Cut, Copy, Paste, etc. | All platforms | +| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms | +| `WindowMenu` | Window controls (Minimise, Zoom, etc.) | All platforms | +| `SpeechMenu` | Speech menu with Start/Stop Speaking | macOS only | +| `HelpMenu` | Help menu with "Learn More" link to Wails website | All platforms | + +#### Individual Menu Items +These roles can be used to add individual menu items: + +| Role | Description | Platform Notes | +|------|-------------|----------------| +| `About` | Show application About dialog | All platforms | +| `Hide` | Hide application | macOS only | +| `HideOthers` | Hide other applications | macOS only | +| `UnHide` | Show hidden application | macOS only | +| `CloseWindow` | Close current window | All platforms | +| `Minimise` | Minimise window | All platforms | +| `Zoom` | Zoom window | macOS only | +| `Front` | Bring window to front | macOS only | +| `Quit` | Quit application | All platforms | +| `Undo` | Undo last action | All platforms | +| `Redo` | Redo last action | All platforms | +| `Cut` | Cut selection | All platforms | +| `Copy` | Copy selection | All platforms | +| `Paste` | Paste from clipboard | All platforms | +| `PasteAndMatchStyle` | Paste and match style | macOS only | +| `SelectAll` | Select all | All platforms | +| `Delete` | Delete selection | All platforms | +| `Reload` | Reload current page | All platforms | +| `ForceReload` | Force reload current page | All platforms | +| `ToggleFullscreen` | Toggle fullscreen mode | All platforms | +| `ResetZoom` | Reset zoom level | All platforms | +| `ZoomIn` | Increase zoom | All platforms | +| `ZoomOut` | Decrease zoom | All platforms | +| `OpenDevTools` | Open developer tools | All platforms | +| `FullScreen` | Enter full screen | All platforms | +| `NewFile` | New file | All platforms | +| `Open` | Open file | All platforms | +| `Save` | Save file | All platforms | +| `SaveAs` | Save file as | All platforms | +| `Revert` | Revert file | All platforms | +| `Print` | Print | All platforms | +| `PageLayout` | Page layout | All platforms | +| `Find` | Find | All platforms | +| `FindAndReplace` | Find and replace | All platforms | +| `FindNext` | Find next | All platforms | +| `FindPrevious` | Find previous | All platforms | +| `ShowAll` | Show all windows | macOS only | +| `BringAllToFront` | Bring all windows to front | macOS only | +| `StartSpeaking` | Start speaking | macOS only | +| `StopSpeaking` | Stop speaking | macOS only | +| `Help` | Help | All platforms | + +Here's an example showing how to use both complete menus and individual roles: + +```go +menu := app.Menu.New() + +// Add complete menu structures +menu.AddRole(application.AppMenu) // macOS only +menu.AddRole(application.EditMenu) // Common edit operations +menu.AddRole(application.ViewMenu) // View controls +menu.AddRole(application.WindowMenu) // Window controls + +// Add individual role-based items to a custom menu +fileMenu := menu.AddSubmenu("File") +fileMenu.AddRole(application.CloseWindow) +fileMenu.AddSeparator() +fileMenu.AddRole(application.Quit) +``` + +## Application Menus + +Application menus are the menus that appear at the top of your application window (Windows/Linux) or at the top of the screen (macOS). + + +### Application Menu Behaviour + +When you set an application menu using `app.Menu.Set()`, it becomes the main menu on macOS. +Menus are set on a per-window basis for Windows/Linux. + +```go +app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom Menu Window", + Windows: application.WindowsWindow{ + Menu: customMenu, // Override application menu for this window + }, +}) +``` + +Here's a complete example showing these different menu behaviours: + +```go +func main() { + app := application.New(application.Options{}) + + // Create application menu + appMenu := app.Menu.New() + fileMenu := appMenu.AddSubmenu("File") + fileMenu.Add("New").OnClick(func(ctx *application.Context) { + // This will be available in all windows unless overridden + window := app.Window.Current() + window.SetTitle("New Window") + }) + + // Set as application menu - this is for macOS + app.Menu.Set(appMenu) + + // Window with custom menu on Windows + customMenu := app.Menu.New() + customMenu.Add("Custom Action") + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "Custom Menu", + Windows: application.WindowsWindow{ + Menu: customMenu, + }, + }) + + app.Run() +} +``` + +## Context Menus + +Context menus are popup menus that appear when right-clicking elements in your application. They provide quick access to relevant actions for the clicked element. + +### Default Context Menu + +The default context menu is the webview's built-in context menu that provides system-level operations such as: +- Copy, Cut, and Paste for text manipulation +- Text selection controls +- Spell checking options + +#### Controlling the Default Context Menu + +You can control when the default context menu appears using the `--default-contextmenu` CSS property: + +```html + +
+ + +
+ + +
+
Custom context menu only
+
+ + +
+ +

Select this text to see the default menu

+ +
+``` + +:::note +This feature will only work as expected after the runtime [has been initialised](../../learn/runtime#initialisation). +::: + +#### Nested Context Menu Behavior + +When using the `--default-contextmenu` property on nested elements, the following rules apply: + +1. Child elements inherit their parent's context menu setting unless explicitly overridden +2. The most specific (closest) setting takes precedence +3. The `auto` value can be used to reset to default behaviour + +Example of nested context menu behaviour: + +```html + +
+ +

No context menu here

+ + +
+

Context menu shown here

+ + + Also has context menu + + +
+

Shows menu only when text is selected

+
+
+
+``` + +### Custom Context Menus + +Custom context menus allow you to provide application-specific actions that are relevant to the element being clicked. They're particularly useful for: +- File operations in a document manager +- Image manipulation tools +- Custom actions in a data grid +- Component-specific operations + +#### Creating a Custom Context Menu + +When creating a custom context menu, you provide a unique identifier (name) that links the menu to HTML elements: + +```go +// Create a context menu with identifier "imageMenu" +contextMenu := application.NewContextMenu("imageMenu") + +// Add items and call Update() to apply changes +contextMenu.Add("Cut") +contextMenu.Add("Copy") +contextMenu.Add("Paste") +contextMenu.Update() +``` + +The name parameter ("imageMenu" in this example) serves as a unique identifier that will be used to: +1. Link HTML elements to this specific context menu +2. Identify which menu should be shown when right-clicking +3. Allow menu updates and cleanup + +#### Context Data + +When handling context menu events, you can access both the clicked menu item and its associated context data: + +```go +contextMenu.Add("Process").OnClick(func(ctx *application.Context) { + // Get the clicked menu item + menuItem := ctx.ClickedMenuItem() + + // Get the context data as a string + contextData := ctx.ContextMenuData() + + // Check if the menu item is checked (for checkbox/radio items) + isChecked := ctx.IsChecked() + + // Use the data + if contextData != "" { + processItem(contextData) + } +}) +``` + +The context data is passed from the HTML element's `--custom-contextmenu-data` property and is available in the click handler through `ctx.ContextMenuData()`. This is particularly useful when: + +- Working with lists or grids where each item needs unique identification +- Handling operations on specific components or elements +- Passing state or metadata from the frontend to the backend + +#### Context Menu Management + +After making changes to a context menu, call the `Update()` method to apply the changes: + +```go +contextMenu.Update() +``` + +When you no longer need a context menu, you can destroy it: + +```go +contextMenu.Destroy() +``` +:::danger[Warning] +After calling `Destroy()`, using the context menu reference again will result in a panic. +::: + +### Real-World Example: Image Gallery + +Here's a complete example of implementing a custom context menu for an image gallery: + +```go +// Backend: Create the context menu (auto-registered by name) +imageMenu := application.NewContextMenu("imageMenu") + +// Add relevant operations +imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) { + // Get the image ID from context data + if imageID := ctx.ContextMenuData(); imageID != "" { + openFullSizeImage(imageID) + } +}) + +imageMenu.Add("Download").OnClick(func(ctx *application.Context) { + if imageID := ctx.ContextMenuData(); imageID != "" { + downloadImage(imageID) + } +}) + +imageMenu.Add("Share").OnClick(func(ctx *application.Context) { + if imageID := ctx.ContextMenuData(); imageID != "" { + showShareDialog(imageID) + } +}) + +imageMenu.Update() +``` + +```html + + +``` + +In this example: +1. The context menu is created with the identifier "imageMenu" +2. Each image container is linked to the menu using `--custom-contextmenu: imageMenu` +3. Each container provides its image ID as context data using `--custom-contextmenu-data` +4. The backend receives the image ID in click handlers and can perform specific operations +5. The same menu is reused for all images, but the context data tells us which image to operate on + +This pattern is particularly powerful for: +- Data grids where rows need specific operations +- File managers where files need context-specific actions +- Design tools where different elements need different operations +- Any component where the same operations apply to multiple instances diff --git a/docs/src/content/docs/guides/msix-packaging.mdx b/docs/src/content/docs/guides/msix-packaging.mdx new file mode 100644 index 000000000..8b9ac57a1 --- /dev/null +++ b/docs/src/content/docs/guides/msix-packaging.mdx @@ -0,0 +1,138 @@ +--- +title: MSIX Packaging (Windows) +description: Guide for creating MSIX packages with Wails v3 +--- + +# MSIX Packaging (Windows) + +Wails v3 can generate modern **MSIX** installers for Windows applications, providing a cleaner, safer and Store-ready alternative to traditional **NSIS** or plain `.exe` bundles. + +This guide walks through: + +* Prerequisites & tool installation +* Building your app as an MSIX package +* Signing the package +* Command-line reference +* Troubleshooting + +--- + +## 1. Prerequisites + +| Requirement | Notes | +|-------------|-------| +| **Windows 10 1809+ / Windows 11** | MSIX is only supported on Windows. | +| **Windows SDK** (for `MakeAppx.exe` & `signtool.exe`) | Install from the [Windows SDK download page](https://developer.microsoft.com/windows/downloads/windows-sdk/). | +| **Microsoft MSIX Packaging Tool** (optional) | Available from the Microsoft Store – provides a GUI & CLI. | +| **Code-signing certificate** (recommended) | A `.pfx` file generated by your CA or `New-SelfSignedCertificate`. | + +> **Tip:** Wails ships a Task that opens the download pages for you: + +```bash +# installs MakeAppx / signtool (via Windows SDK) and the MSIX Packaging Tool +wails3 task windows:install:msix:tools +``` + +--- + +## 2. Building an MSIX package + +### 2.1 Quick CLI + +```bash +# Production build + MSIX +wails3 tool msix \ + --name MyApp \ # executable name + --executable build/bin/MyApp.exe \ + --arch x64 \ # x64, x86 or arm64 + --out build/bin/MyApp-x64.msix +``` + +The command will: + +1. Create a temporary layout (`AppxManifest.xml`, `Assets/`). +2. Call **MakeAppx.exe** (default) or **MsixPackagingTool.exe** if `--use-msix-tool` is passed. +3. Optionally sign the package (see §3). + +### 2.2 Using the generated Taskfile + +When you ran `wails init`, Wails created `build/windows/Taskfile.yml`. +Packaging with MSIX is a one-liner: + +```bash +# default=nsis, override FORMAT +wails3 task windows:package FORMAT=msix +``` + +Output goes to `build/bin/MyApp-.msix`. + +--- + +## 3. Signing the package + +Windows will refuse unsigned MSIX packages unless you enable developer-mode, so signing is strongly recommended. + +```bash +wails3 tool msix \ + --cert build/cert/CodeSign.pfx \ + --cert-password "pfx-password" \ + --publisher "CN=MyCompany" \ + --out build/bin/MyApp.msix +``` + +* If you pass `--cert`, Wails automatically runs `signtool sign …`. +* `--publisher` sets the `Publisher` field inside `AppxManifest.xml`. + It **must** match the subject of your certificate. + +--- + +## 4. Command-line reference + +| Flag | Default | Description | +|------|---------|-------------| +| `--config` | `wails.json` | Project config with **Info** & `fileAssociations`. | +| `--name` | — | Executable name inside the package (no spaces). | +| `--executable` | — | Path to the built `.exe`. | +| `--arch` | `x64` | `x64`, `x86`, or `arm64`. | +| `--out` | `.msix` | Output path / filename. | +| `--publisher` | `CN=` | Publisher string in the manifest. | +| `--cert` | ― | Path to `.pfx` certificate for signing. | +| `--cert-password` | ― | Password for the `.pfx`. | +| `--use-msix-tool` | `false` | Use **MsixPackagingTool.exe** instead of **MakeAppx.exe**. | +| `--use-makeappx` | `true` | Force MakeAppx even if the MSIX Tool is installed. | + +--- + +## 5. File associations + +Wails automatically injects file associations declared in `wails.json` into the package manifest: + +```json +"fileAssociations": [ + { "ext": "wails", "name": "Wails Project", "description": "Wails file", "role": "Editor" } +] +``` + +After installation, Windows will offer your app as a handler for these extensions. + +--- + +## 6. Troubleshooting + +| Problem | Solution | +|---------|----------| +| `MakeAppx.exe not found` | Install the Windows SDK and restart the terminal. | +| `signtool.exe not found` | Same as above – both live in the SDK’s *bin* folder. | +| *Package cannot be installed because publisher mismatch* | The certificate subject (CN) must match `--publisher`. | +| *The certificate is not trusted* | Import the certificate into **Trusted Root Certification Authorities** or use a publicly trusted code-signing cert. | +| Need GUI | Install **MSIX Packaging Tool** from the store and run `MsixPackagingTool.exe`. The template generated by Wails is fully compatible. | + +--- + +## 7. Next steps + +* [Windows Installer (NSIS) guide](./windows-installer.mdx) – legacy format. +* [Cross-platform update mechanism](../updates.mdx) – coming soon. +* Join the community on Discord to share feedback! + +Happy packaging! diff --git a/docs/src/content/docs/guides/panic-handling.mdx b/docs/src/content/docs/guides/panic-handling.mdx new file mode 100644 index 000000000..f252f0dc3 --- /dev/null +++ b/docs/src/content/docs/guides/panic-handling.mdx @@ -0,0 +1,116 @@ +--- +title: Handling Panics +description: How to handle panics in your Wails application +--- + +In Go applications, panics can occur during runtime when something unexpected happens. This guide explains how to handle panics both in general Go code and specifically in your Wails application. + +## Understanding Panics in Go + +Before diving into Wails-specific panic handling, it's essential to understand how panics work in Go: + +1. Panics are for unrecoverable errors that shouldn't happen during normal operation +2. When a panic occurs in a goroutine, only that goroutine is affected +3. Panics can be recovered using `defer` and `recover()` + +Here's a basic example of panic handling in Go: + +```go +func doSomething() { + // Deferred functions run even when a panic occurs + defer func() { + if r := recover(); r != nil { + fmt.Printf("Recovered from panic: %v\n", r) + } + }() + + // Your code that might panic + panic("something went wrong") +} +``` + +For more detailed information about panic and recover in Go, see the [Go Blog: Defer, Panic, and Recover](https://go.dev/blog/defer-panic-and-recover). + +## Panic Handling in Wails + +Wails automatically handles panics that occur in your Service methods when they are called from the frontend. This means you don't need to add panic recovery to these methods - Wails will catch the panic and process it through your configured panic handler. + +The panic handler is specifically designed to catch: +- Panics in bound service methods called from the frontend +- Internal panics from the Wails runtime + +For other scenarios, such as background goroutines or standalone Go code, you should handle panics yourself using Go's standard panic recovery mechanisms. + +## The PanicDetails Struct + +When a panic occurs, Wails captures important information about the panic in a `PanicDetails` struct: + +```go +type PanicDetails struct { + StackTrace string // The stack trace of where the panic occurred. Potentially trimmed to provide more context + Error error // The error that caused the panic + Time time.Time // The time when the panic occurred + FullStackTrace string // The complete stack trace including runtime frames +} +``` + +This structure provides comprehensive information about the panic: +- `StackTrace`: A formatted string showing the call stack that led to the panic +- `Error`: The actual error or panic message +- `Time`: The exact time when the panic occurred +- `FullStackTrace`: The complete stack trace including runtime frames + +:::note[Panics in Service Code] + +When panics are caught in your Service code after being called from the frontend, the stack trace is trimmed to focus on exactly where in your code the panic occurred. +If you want to see the full stack trace, you can use the `FullStackTrace` field. + +::: + +## Default Panic Handler + +If you don't specify a custom panic handler, Wails will use its default handler which outputs error information in a formatted log message and then quits. +For example: + +``` +************************ FATAL ****************************** +* There has been a catastrophic failure in your application * +********************* Error Details ************************* +panic error: oh no! something went wrong deep in my service! :( +main.(*WindowService).call2 + at E:/wails/v3/examples/panic-handling/main.go:23 +main.(*WindowService).call1 + at E:/wails/v3/examples/panic-handling/main.go:19 +main.(*WindowService).GeneratePanic + at E:/wails/v3/examples/panic-handling/main.go:15 +************************************************************* +``` + +## Custom Panic Handler + +You can implement your own panic handler by setting the `PanicHandler` option when creating your application. Here's an example: + +```go +app := application.New(application.Options{ + Name: "My App", + PanicHandler: func(panicDetails *application.PanicDetails) { + fmt.Printf("*** Custom Panic Handler ***\n") + fmt.Printf("Time: %s\n", panicDetails.Time) + fmt.Printf("Error: %s\n", panicDetails.Error) + fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace) + fmt.Printf("Full Stacktrace: %s\n", panicDetails.FullStackTrace) + + // You could also: + // - Log to a file + // - Send to a crash reporting service + // - Show a user-friendly error dialog + // - Attempt to recover or restart the application + }, +}) +``` + +For a complete working example of panic handling in a Wails application, see the panic-handling example in `v3/examples/panic-handling`. + +## Final Notes + +Remember that the Wails panic handler is specifically for managing panics in bound methods and internal runtime errors. For other parts of your application, you should use Go's standard error handling patterns and panic recovery mechanisms where appropriate. As with all Go applications, it's better to prevent panics through proper error handling where possible. diff --git a/docs/src/content/docs/guides/performance.mdx b/docs/src/content/docs/guides/performance.mdx new file mode 100644 index 000000000..c6dd65d77 --- /dev/null +++ b/docs/src/content/docs/guides/performance.mdx @@ -0,0 +1,339 @@ +--- +title: Performance Optimisation +description: Optimise your Wails application for maximum performance +sidebar: + order: 9 +--- + +## Overview + +Optimise your Wails application for speed, memory efficiency, and responsiveness. + +## Frontend Optimisation + +### Bundle Size + +```javascript +// vite.config.js +export default { + build: { + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + }, + }, + }, + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + }, + }, + }, +} +``` + +### Code Splitting + +```javascript +// Lazy load components +const Settings = lazy(() => import('./Settings')) + +function App() { + return ( + }> + + + ) +} +``` + +### Asset Optimisation + +```javascript +// Optimise images +import { defineConfig } from 'vite' +import imagemin from 'vite-plugin-imagemin' + +export default defineConfig({ + plugins: [ + imagemin({ + gifsicle: { optimizationLevel: 3 }, + optipng: { optimizationLevel: 7 }, + svgo: { plugins: [{ removeViewBox: false }] }, + }), + ], +}) +``` + +## Backend Optimisation + +### Efficient Bindings + +```go +// ❌ Bad: Return everything +func (s *Service) GetAllData() []Data { + return s.db.FindAll() // Could be huge +} + +// ✅ Good: Paginate +func (s *Service) GetData(page, size int) (*PagedData, error) { + return s.db.FindPaged(page, size) +} +``` + +### Caching + +```go +type CachedService struct { + cache *lru.Cache + ttl time.Duration +} + +func (s *CachedService) GetData(key string) (interface{}, error) { + // Check cache + if val, ok := s.cache.Get(key); ok { + return val, nil + } + + // Fetch and cache + data, err := s.fetchData(key) + if err != nil { + return nil, err + } + + s.cache.Add(key, data) + return data, nil +} +``` + +### Goroutines for Long Operations + +```go +func (s *Service) ProcessLargeFile(path string) error { + // Process in background + go func() { + result, err := s.process(path) + if err != nil { + s.app.Event.Emit("process-error", err.Error()) + return + } + s.app.Event.Emit("process-complete", result) + }() + + return nil +} +``` + +## Memory Optimisation + +### Avoid Memory Leaks + +```go +// ❌ Bad: Goroutine leak +func (s *Service) StartPolling() { + ticker := time.NewTicker(1 * time.Second) + go func() { + for range ticker.C { + s.poll() + } + }() + // ticker never stopped! +} + +// ✅ Good: Proper cleanup +func (s *Service) StartPolling() { + ticker := time.NewTicker(1 * time.Second) + s.stopChan = make(chan bool) + + go func() { + for { + select { + case <-ticker.C: + s.poll() + case <-s.stopChan: + ticker.Stop() + return + } + } + }() +} + +func (s *Service) StopPolling() { + close(s.stopChan) +} +``` + +### Pool Resources + +```go +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func processData(data []byte) []byte { + buf := bufferPool.Get().(*bytes.Buffer) + defer bufferPool.Put(buf) + + buf.Reset() + buf.Write(data) + // Process... + return buf.Bytes() +} +``` + +## Event Optimisation + +### Debounce Events + +```javascript +// Debounce frequent events +let debounceTimer +function handleInput(value) { + clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => { + UpdateData(value) + }, 300) +} +``` + +### Batch Updates + +```go +type BatchProcessor struct { + items []Item + mu sync.Mutex + timer *time.Timer +} + +func (b *BatchProcessor) Add(item Item) { + b.mu.Lock() + defer b.mu.Unlock() + + b.items = append(b.items, item) + + if b.timer == nil { + b.timer = time.AfterFunc(100*time.Millisecond, b.flush) + } +} + +func (b *BatchProcessor) flush() { + b.mu.Lock() + items := b.items + b.items = nil + b.timer = nil + b.mu.Unlock() + + // Process batch + processBatch(items) +} +``` + +## Build Optimisation + +### Binary Size + +The default Taskfiles already include `-ldflags "-s -w"` to strip debug symbols. You can further reduce binary size by adding `-trimpath` to the `go build` command in your Taskfile: + +```bash +go build -ldflags="-s -w" -trimpath +``` + +### Compilation Speed + +```bash +# Use build cache +go build -buildmode=default + +# Parallel compilation +go build -p 8 +``` + +## Profiling + +### CPU Profiling + +```go +import "runtime/pprof" + +func profileCPU() { + f, _ := os.Create("cpu.prof") + defer f.Close() + + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + // Code to profile +} +``` + +### Memory Profiling + +```go +import "runtime/pprof" + +func profileMemory() { + f, _ := os.Create("mem.prof") + defer f.Close() + + runtime.GC() + pprof.WriteHeapProfile(f) +} +``` + +### Analyse Profiles + +```bash +# View CPU profile +go tool pprof cpu.prof + +# View memory profile +go tool pprof mem.prof + +# Web interface +go tool pprof -http=:8080 cpu.prof +``` + +## Best Practices + +### ✅ Do + +- Profile before optimising +- Cache expensive operations +- Use pagination for large datasets +- Debounce frequent events +- Pool resources +- Clean up goroutines +- Optimise bundle size +- Use lazy loading + +### ❌ Don't + +- Don't optimise prematurely +- Don't ignore memory leaks +- Don't block the main thread +- Don't return huge datasets +- Don't skip profiling +- Don't forget cleanup + +## Performance Checklist + +- [ ] Frontend bundle optimised +- [ ] Images compressed +- [ ] Code splitting implemented +- [ ] Backend methods paginated +- [ ] Caching implemented +- [ ] Goroutines cleaned up +- [ ] Events debounced +- [ ] Binary size optimised +- [ ] Profiling done +- [ ] Memory leaks fixed + +## Next Steps + +- [Architecture](/guides/architecture) - Application architecture patterns +- [Testing](/guides/testing) - Test your application +- [Building](/guides/building) - Build optimised binaries diff --git a/docs/src/content/docs/guides/security.mdx b/docs/src/content/docs/guides/security.mdx new file mode 100644 index 000000000..51bc21b3e --- /dev/null +++ b/docs/src/content/docs/guides/security.mdx @@ -0,0 +1,261 @@ +--- +title: Security Best Practices +description: Secure your Wails application +sidebar: + order: 8 +--- + +## Overview + +Security is critical for desktop applications. Follow these practices to keep your application secure. + +## Input Validation + +### Always Validate + +```go +func (s *UserService) CreateUser(email, password string) (*User, error) { + // Validate email + if !isValidEmail(email) { + return nil, errors.New("invalid email") + } + + // Validate password strength + if len(password) < 8 { + return nil, errors.New("password too short") + } + + // Sanitise input + email = strings.TrimSpace(email) + email = html.EscapeString(email) + + // Continue... +} +``` + +### Sanitise HTML + +```go +import "html" + +func (s *Service) SaveComment(text string) error { + // Escape HTML + text = html.EscapeString(text) + + // Validate length + if len(text) > 1000 { + return errors.New("comment too long") + } + + return s.db.Save(text) +} +``` + +## Authentication + +### Secure Password Storage + +```go +import "golang.org/x/crypto/bcrypt" + +func hashPassword(password string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hash), err +} + +func verifyPassword(hash, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} +``` + +### Session Management + +```go +type Session struct { + UserID int + Token string + ExpiresAt time.Time +} + +func (a *AuthService) CreateSession(userID int) (*Session, error) { + token := generateSecureToken() + + session := &Session{ + UserID: userID, + Token: token, + ExpiresAt: time.Now().Add(24 * time.Hour), + } + + return session, a.saveSession(session) +} +``` + +## Data Protection + +### Encrypt Sensitive Data + +```go +import "crypto/aes" +import "crypto/cipher" + +func encrypt(data []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + return gcm.Seal(nonce, nonce, data, nil), nil +} +``` + +### Secure Storage + +```go +// Use OS keychain for sensitive data +import "github.com/zalando/go-keyring" + +func saveAPIKey(key string) error { + return keyring.Set("myapp", "api_key", key) +} + +func getAPIKey() (string, error) { + return keyring.Get("myapp", "api_key") +} +``` + +## Network Security + +### Use HTTPS + +```go +func makeAPICall(url string) (*Response, error) { + // Always use HTTPS + if !strings.HasPrefix(url, "https://") { + return nil, errors.New("only HTTPS allowed") + } + + return http.Get(url) +} +``` + +### Verify Certificates + +```go +import "crypto/tls" + +func secureClient() *http.Client { + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, + }, + } +} +``` + +## File Operations + +### Validate Paths + +```go +func readFile(path string) ([]byte, error) { + // Prevent path traversal + if strings.Contains(path, "..") { + return nil, errors.New("invalid path") + } + + // Check file exists in allowed directory + absPath, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + if !strings.HasPrefix(absPath, allowedDir) { + return nil, errors.New("access denied") + } + + return os.ReadFile(absPath) +} +``` + +## Rate Limiting + +```go +type RateLimiter struct { + requests map[string][]time.Time + mu sync.Mutex + limit int + window time.Duration +} + +func (r *RateLimiter) Allow(key string) bool { + r.mu.Lock() + defer r.mu.Unlock() + + now := time.Now() + + // Clean old requests + var recent []time.Time + for _, t := range r.requests[key] { + if now.Sub(t) < r.window { + recent = append(recent, t) + } + } + + if len(recent) >= r.limit { + return false + } + + r.requests[key] = append(recent, now) + return true +} +``` + +## Best Practices + +### ✅ Do + +- Validate all input +- Use HTTPS for network calls +- Encrypt sensitive data +- Use secure password hashing +- Implement rate limiting +- Keep dependencies updated +- Log security events +- Use OS keychains + +### ❌ Don't + +- Don't trust user input +- Don't store passwords in plain text +- Don't hardcode secrets +- Don't skip certificate verification +- Don't expose sensitive data in logs +- Don't use weak encryption +- Don't ignore security updates + +## Security Checklist + +- [ ] All user input validated +- [ ] Passwords hashed with bcrypt +- [ ] Sensitive data encrypted +- [ ] HTTPS used for all network calls +- [ ] Rate limiting implemented +- [ ] File paths validated +- [ ] Dependencies up to date +- [ ] Security logging enabled +- [ ] Error messages don't leak info +- [ ] Code reviewed for vulnerabilities + +## Next Steps + +- [Architecture](/guides/architecture) - Application architecture patterns +- [Best Practices](/features/bindings/best-practices) - Bindings best practices diff --git a/docs/src/content/docs/guides/signing.mdx b/docs/src/content/docs/guides/signing.mdx new file mode 100644 index 000000000..b5de768c9 --- /dev/null +++ b/docs/src/content/docs/guides/signing.mdx @@ -0,0 +1,228 @@ +--- +title: Code Signing +description: Guide for signing your Wails applications on macOS and Windows +sidebar: + order: 4 +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; +import { Card, CardGrid } from '@astrojs/starlight/components'; + +# Code Signing Your Application + +This guide covers how to sign your Wails applications for both macOS and Windows, with a focus on automated signing using GitHub Actions. + + + + Sign your Windows executables with certificates + + + Sign and notarize your macOS applications + + + +## Windows Code Signing + + +1. **Obtain a Code Signing Certificate** + - Get from a trusted provider listed on [Microsoft's documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate) + - Standard code signing certificate is sufficient (EV not required) + - Test signing locally before setting up CI + +2. **Prepare for GitHub Actions** + - Convert your certificate to Base64 + - Store in GitHub Secrets + - Set up signing workflow + +3. **Configure GitHub Actions** + ```yaml + name: Sign Windows Binary + + on: + workflow_dispatch: + release: + types: [created] + + jobs: + sign: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + + - name: Import Certificate + run: | + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value ${{ secrets.WINDOWS_CERTIFICATE }} + certutil -decode certificate\certificate.txt certificate\certificate.pfx + + - name: Sign Binary + run: | + & 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\signtool.exe' sign /f certificate\certificate.pfx /t http://timestamp.sectigo.com /p ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} /v /fd sha256 .\build\bin\app.exe + ``` + + +### Important Windows Parameters + +- **Signing Algorithm**: Usually `sha256` +- **Timestamp Server**: Valid timestamping server URL +- **Certificate Password**: Stored in GitHub Secrets +- **Binary Path**: Path to your compiled executable + +## macOS Code Signing + + +1. **Prerequisites** + - Apple Developer Account + - Developer ID Certificate + - App Store Connect API Key + - [gon](https://github.com/mitchellh/gon) for notarization + +2. **Certificate Setup** + - Generate Developer ID Certificate + - Download and install certificate + - Export certificate for CI + +3. **Configure Notarization** + ```json title="gon-sign.json" + { + "source": ["./build/bin/app"], + "bundle_id": "com.company.app", + "apple_id": { + "username": "dev@company.com", + "password": "@env:AC_PASSWORD" + }, + "sign": { + "application_identity": "Developer ID Application: Company Name" + } + } + ``` + +4. **GitHub Actions Configuration** + ```yaml + name: Sign macOS Binary + + on: + workflow_dispatch: + release: + types: [created] + + jobs: + sign: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + + - name: Import Certificate + env: + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} + run: | + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "" build.keychain + security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain + + - name: Sign and Notarize + env: + AC_USERNAME: ${{ secrets.AC_USERNAME }} + AC_PASSWORD: ${{ secrets.AC_PASSWORD }} + run: | + gon -log-level=info ./build/darwin/gon-sign.json + ``` + + +### Important macOS Parameters + +- **Bundle ID**: Unique identifier for your app +- **Developer ID**: Your Developer ID Application certificate +- **Apple ID**: Developer account credentials +- **ASC API Key**: App Store Connect API credentials + +## Best Practices + +1. **Security** + - Store all credentials in GitHub Secrets + - Use environment variables for sensitive data + - Regularly rotate certificates and credentials + +2. **Workflow** + - Test signing locally first + - Use conditional signing based on platform + - Implement proper error handling + +3. **Verification** + - Verify signatures after signing + - Test notarization process + - Check timestamp validity + +## Troubleshooting + +### Windows Issues +- Certificate not found +- Invalid timestamp server +- Signing tool errors + +### macOS Issues +- Keychain access issues +- Notarization failures +- Certificate validation errors + +## Complete GitHub Actions Workflow + +```yaml +name: Sign Binaries + +on: + workflow_dispatch: + release: + types: [created] + +jobs: + sign: + strategy: + matrix: + platform: [windows-latest, macos-latest] + runs-on: ${{ matrix.platform }} + + steps: + - uses: actions/checkout@v3 + + # Windows Signing + - name: Sign Windows Binary + if: matrix.platform == 'windows-latest' + env: + CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + run: | + New-Item -ItemType directory -Path certificate + Set-Content -Path certificate\certificate.txt -Value $env:CERTIFICATE + certutil -decode certificate\certificate.txt certificate\certificate.pfx + & 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x86\signtool.exe' sign /f certificate\certificate.pfx /t http://timestamp.sectigo.com /p $env:CERTIFICATE_PASSWORD /v /fd sha256 .\build\bin\app.exe + + # macOS Signing + - name: Sign macOS Binary + if: matrix.platform == 'macos-latest' + env: + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} + AC_USERNAME: ${{ secrets.AC_USERNAME }} + AC_PASSWORD: ${{ secrets.AC_PASSWORD }} + run: | + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "" build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p "" build.keychain + security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain + gon -log-level=info ./build/darwin/gon-sign.json +``` + +## Additional Resources + +- [Apple Code Signing Documentation](https://developer.apple.com/support/code-signing/) +- [Microsoft Code Signing Documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/dashboard/get-a-code-signing-certificate) +- [Gon Documentation](https://github.com/mitchellh/gon) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) diff --git a/docs/src/content/docs/guides/single-instance.mdx b/docs/src/content/docs/guides/single-instance.mdx new file mode 100644 index 000000000..25b7fac47 --- /dev/null +++ b/docs/src/content/docs/guides/single-instance.mdx @@ -0,0 +1,119 @@ +--- +title: Single Instance +description: Limiting your app to a single running instance +sidebar: + order: 40 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +Single instance locking is a mechanism that prevents multiple instances of your app from running at the same time. +It is useful for apps that are designed to open files from the command line or from the OS file explorer. + + +## Usage + +To enable single instance functionality in your app, provide a `SingleInstanceOptions` struct when creating your application: + +```go +app := application.New(application.Options{ + // ... other options ... + SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.myapp.unique-id", + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + log.Printf("Second instance launched with args: %v", data.Args) + log.Printf("Working directory: %s", data.WorkingDir) + log.Printf("Additional data: %v", data.AdditionalData) + }, + // Optional: Pass additional data to second instance + AdditionalData: map[string]string{ + "launchtime": time.Now().String(), + }, + }, +}) +``` + +The `SingleInstanceOptions` struct has the following fields: + +- `UniqueID`: A unique identifier for your application. This should be a unique string, typically in reverse domain notation (e.g., "com.company.appname"). +- `EncryptionKey`: Optional 32-byte array for encrypting data passed between instances using AES-256-GCM. If provided as a non-zero array, all communication between instances will be encrypted. +- `OnSecondInstanceLaunch`: A callback function that is called when a second instance of your app is launched. The callback receives a `SecondInstanceData` struct containing: + - `Args`: The command line arguments passed to the second instance + - `WorkingDir`: The working directory of the second instance + - `AdditionalData`: Any additional data passed from the second instance (if provided) +- `AdditionalData`: Optional map of string key-value pairs that will be passed to the first instance when subsequent instances are launched + +:::danger[Warning] +The Single Instance feature implements an optional encryption protocol using AES-256-GCM. Without encryption enabled, +data passed between instances is not secure. When using the single instance feature without encryption, +your app should treat any data passed to it from second instance callback as untrusted. +You should verify that args that you receive are valid and don't contain any malicious data. +::: + +### Secure Communication + +To enable secure communication between instances, provide a 32-byte encryption key. This key must be the same for all instances of your application: + +```go +// Define your encryption key (must be exactly 32 bytes) +var encryptionKey = [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +} + +// Use the key in SingleInstanceOptions +SingleInstance: &application.SingleInstanceOptions{ + UniqueID: "com.myapp.unique-id", + // Enable encryption for instance communication + EncryptionKey: encryptionKey, + // ... other options ... +} +``` + +:::tip[Security Best Practices] +- Use a unique key for your application +- Store the key securely if loading it from configuration +- Do not use the example key shown above - create your own! +::: + +### Window Management + +When handling second instance launches, you'll often want to bring your application window to the front. You can do this using the window's `Focus()` method. If your window is minimized, you may need to restore it first: + +```go + + var mainWindow *application.WebviewWindow + + SingleInstance: &application.SingleInstanceOptions{ + // Other options... + OnSecondInstanceLaunch: func(data application.SecondInstanceData) { + // Focus the window if needed + if mainWindow != nil { + mainWindow.Restore() + mainWindow.Focus() + } + }, + } +``` + +## How it works + + + + + Single instance lock using a named mutex. The mutex name is generated from the unique id that you provide. Data is passed to the first instance via [NSDistributedNotificationCenter](https://developer.apple.com/documentation/foundation/nsdistributednotificationcenter) + + + + + Single instance lock using a named mutex. The mutex name is generated from the unique id that you provide. Data is passed to the first instance via a shared window using [SendMessage](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage) + + + + + Single instance lock using [dbus](https://www.freedesktop.org/wiki/Software/dbus/). The dbus name is generated from the unique id that you provide. Data is passed to the first instance via [dbus](https://www.freedesktop.org/wiki/Software/dbus/) + + + diff --git a/docs/src/content/docs/guides/testing.mdx b/docs/src/content/docs/guides/testing.mdx new file mode 100644 index 000000000..4bd3a07b8 --- /dev/null +++ b/docs/src/content/docs/guides/testing.mdx @@ -0,0 +1,175 @@ +--- +title: Testing +description: Test your Wails application +sidebar: + order: 5 +--- + +## Overview + +Testing ensures your application works correctly and prevents regressions. + +## Unit Testing + +### Testing Services + +```go +func TestUserService_Create(t *testing.T) { + service := &UserService{ + users: make(map[string]*User), + } + + user, err := service.Create("john@example.com", "password123") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if user.Email != "john@example.com" { + t.Errorf("expected email john@example.com, got %s", user.Email) + } +} +``` + +### Testing with Mocks + +```go +type MockDB struct { + users map[string]*User +} + +func (m *MockDB) Create(user *User) error { + m.users[user.ID] = user + return nil +} + +func TestUserService_WithMock(t *testing.T) { + mockDB := &MockDB{users: make(map[string]*User)} + service := &UserService{db: mockDB} + + user, err := service.Create("test@example.com", "pass") + if err != nil { + t.Fatal(err) + } + + if len(mockDB.users) != 1 { + t.Error("expected 1 user in mock") + } +} +``` + +## Integration Testing + +### Testing with Real Dependencies + +```go +func TestIntegration(t *testing.T) { + // Setup test database + db, err := sql.Open("sqlite3", ":memory:") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + // Create schema + _, err = db.Exec(`CREATE TABLE users (...)`) + if err != nil { + t.Fatal(err) + } + + // Test service + service := &UserService{db: db} + user, err := service.Create("test@example.com", "password") + if err != nil { + t.Fatal(err) + } + + // Verify in database + var count int + db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count) + if count != 1 { + t.Errorf("expected 1 user, got %d", count) + } +} +``` + +## Frontend Testing + +### JavaScript Unit Tests + +```javascript +// Using Vitest +import { describe, it, expect } from 'vitest' +import { formatDate } from './utils' + +describe('formatDate', () => { + it('formats date correctly', () => { + const date = new Date('2024-01-01') + expect(formatDate(date)).toBe('2024-01-01') + }) +}) +``` + +### Testing Bindings + +```javascript +import { vi } from 'vitest' +import { GetUser } from './bindings/myapp/userservice' + +// Mock the binding +vi.mock('./bindings/myapp/userservice', () => ({ + GetUser: vi.fn() +})) + +describe('User Component', () => { + it('loads user data', async () => { + GetUser.mockResolvedValue({ name: 'John', email: 'john@example.com' }) + + // Test your component + const user = await GetUser(1) + expect(user.name).toBe('John') + }) +}) +``` + +## Best Practices + +### ✅ Do + +- Write tests before fixing bugs +- Test edge cases +- Use table-driven tests +- Mock external dependencies +- Test error handling +- Keep tests fast + +### ❌ Don't + +- Don't skip error cases +- Don't test implementation details +- Don't write flaky tests +- Don't ignore test failures +- Don't skip integration tests + +## Running Tests + +```bash +# Run Go tests +go test ./... + +# Run with coverage +go test -cover ./... + +# Run specific test +go test -run TestUserService + +# Run frontend tests +cd frontend && npm test + +# Run with watch mode +cd frontend && npm test -- --watch +``` + +## Next Steps + +- [End-to-End Testing](/guides/e2e-testing) - Test complete user flows +- [Best Practices](/features/bindings/best-practices) - Learn best practices diff --git a/docs/src/content/docs/guides/windows-uac.mdx b/docs/src/content/docs/guides/windows-uac.mdx new file mode 100644 index 000000000..5bb380179 --- /dev/null +++ b/docs/src/content/docs/guides/windows-uac.mdx @@ -0,0 +1,151 @@ +--- +title: Windows UAC Configuration +sidebar: + order: 11 +--- + +import {Badge} from '@astrojs/starlight/components'; + +Relevant Platforms: +
+ +Windows User Account Control (UAC) determines the execution privileges of your Wails application. By default, Wails v3 applications include explicit UAC configuration in their Windows manifest, ensuring consistent behavior across different machines. + +## UAC Execution Levels + +Windows applications can request different execution levels through their manifest file. Wails v3 automatically includes UAC configuration with a default execution level that you can customize based on your application's needs. + +### Available Execution Levels + +| Level | Description | Use Case | +|-------|-------------|----------| +| `asInvoker` | Runs with the same privileges as the parent process | Default for most applications | +| `highestAvailable` | Runs with the highest privileges available to the user | Applications that may need elevated access | +| `requireAdministrator` | Always requires administrator privileges | System utilities, installers | + +### Default Configuration + +Wails v3 applications include a default UAC configuration in their Windows manifest: + +```xml + + + + + + + +``` + +This configuration ensures your application: +- Runs with the same privileges as the launching process +- Does not require elevation by default +- Works consistently across different machines +- Does not trigger UAC prompts for normal users + +## Customizing UAC Configuration + +Since Wails v3 encourages users to customize their build assets, you can modify the UAC configuration by editing your Windows manifest template directly. + +### Locating the Manifest Template + +The Windows manifest template is located at: +``` +build/windows/wails.exe.manifest +``` + +### Modifying the Execution Level + +To change the execution level, edit the `level` attribute in the `requestedExecutionLevel` element: + +```xml title="build/windows/wails.exe.manifest" + + + + + + + +``` + +### Examples + +#### Standard Application (Default) +Most applications should use the default `asInvoker` level: + +```xml + +``` + +#### System Utility +Applications that need elevated access when available: + +```xml + +``` + +#### Administrative Tool +Applications that always require administrator privileges: + +```xml + +``` + +## UI Access + +The `uiAccess` attribute controls whether your application can interact with higher-privilege UI elements. In most cases, this should remain `false`. + +Set to `true` only if your application needs to: +- Send input to other applications +- Drive the UI of other applications +- Access UI elements of higher-privilege processes + +:::caution[UI Access Requirements] +Setting `uiAccess="true"` requires your application to be: +- Digitally signed with a certificate from a trusted certificate authority +- Installed in a secure location (Program Files or Windows\System32) +::: + +## Building with Custom UAC Settings + +After modifying your manifest template, build your application normally: + +```bash +wails3 build +``` + +The build process will automatically embed your custom UAC configuration into the executable. + +## Verifying UAC Configuration + +You can verify that your UAC settings are properly embedded using the `go-winres` tool: + +```bash +go-winres extract --in your-app.exe --out extracted-resources/ +``` + +Then examine the extracted manifest file to confirm your UAC configuration is present. + +:::tip[Manifest Persistence] +Unlike some other frameworks, Wails v3's UAC configuration is embedded directly into the executable during compilation, ensuring it persists when the application is copied to other machines. +::: + +## Troubleshooting + +### UAC Prompts Not Appearing +If you set `requireAdministrator` but don't see UAC prompts: +- Verify the manifest is properly embedded in your executable +- Check that you're not running from an already-elevated process +- Ensure the manifest syntax is valid XML + +### Application Not Starting +If your application fails to start after UAC changes: +- Check the manifest syntax for XML errors +- Verify the execution level value is valid +- Try reverting to `asInvoker` to isolate the issue + +### Inconsistent Behavior Across Machines +If UAC behavior differs between machines: +- Ensure the manifest is embedded in the executable (not external) +- Check that the executable wasn't modified after building +- Verify Windows UAC settings are enabled on the target machine \ No newline at end of file diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 000000000..eb1f6693e --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,294 @@ +--- +title: Build Desktop Apps with Go +description: "Native desktop applications using Go and web technologies" +banner: + content: | + Wails v3 is in ALPHA. v2 docs +template: splash +hero: + tagline: Build beautiful, performant desktop applications using Go and modern web technologies. One codebase. Three platforms. No browsers. + image: + dark: ../../assets/wails-logo-dark.svg + light: ../../assets/wails-logo-light.svg + alt: Wails Logo + actions: + - text: Get Started + link: /quick-start/installation + icon: right-arrow + variant: primary + - text: View Tutorial + link: /tutorials/03-notes-vanilla + icon: open-book + variant: secondary +--- + +import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components"; + + + +## Quickstart + + + +```bash +# Install Wails +go install github.com/wailsapp/wails/v3/cmd/wails3@latest + +# Create your application +wails3 init -n myapp -t vanilla + +# Run with hot reload +cd myapp && wails3 dev +``` + + + +```powershell +# Install Wails +go install github.com/wailsapp/wails/v3/cmd/wails3@latest + +# Create your application +wails3 init -n myapp -t vanilla + +# Run with hot reload +cd myapp; wails3 dev +``` + + + +```bash +# Install Wails +go install github.com/wailsapp/wails/v3/cmd/wails3@latest + +# Create your application +wails3 init -n myapp -t vanilla + +# Run with hot reload +cd myapp && wails3 dev +``` + + + +**Your application is now running** with hot reload and type-safe Go-to-JS bindings. + + +## Why Wails? + + + + - ~15MB binaries vs Electron's 150MB + - ~10MB baseline memory vs 100MB+ + - <0.5s startup time vs 2-3s + - Native rendering using OS WebView + - No bundled browser overhead + + + + - One Go codebase for all platforms + - Any web framework - React, Vue, Svelte + - Hot reload during development + - Auto-generated bindings to easily call Go from Javascript + - In-memory IPC. No network ports + + + + - Multiple windows with lifecycles + - Native menus and system tray + - Platform-native file dialogs + - System integration and shortcuts + - Code signing and packaging tools + + + + - Single codebase for Windows, macOS, Linux + - Platform-specific features when needed + - No compromise on user experience + - Deploy to all platforms from one build + - Mobile coming soon... + + + + + + +## Next Steps + +Next: [Build a complete application](/tutorials/03-notes-vanilla), browse [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples), or check the [API reference](/reference/application). Migrating from v2? See the [upgrade guide](/migration/v2-to-v3). + + +:::note[Production Ready] +Wails v3 is in **ALPHA**. The API is *reasonably* stable, and applications are running in production. We're refining documentation and tooling before the final release. +::: + + + Wails is free and open source, built by developers for developers. If Wails helps you build amazing applications, consider supporting its continued development. + + Your sponsorship helps maintain the project, improve documentation, and develop new features that benefit the entire community. + + [Become a Sponsor →](https://github.com/sponsors/leaanthony) + + diff --git a/docs/src/content/docs/migration/v2-to-v3.mdx b/docs/src/content/docs/migration/v2-to-v3.mdx new file mode 100644 index 000000000..fe4a36123 --- /dev/null +++ b/docs/src/content/docs/migration/v2-to-v3.mdx @@ -0,0 +1,728 @@ +--- +title: Migrating from v2 to v3 +description: Complete guide to migrating your Wails v2 application to v3 +sidebar: + order: 1 +--- + +import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components"; + +Wails v3 is a **complete rewrite** with significant improvements in architecture, performance, and developer experience. This guide helps you migrate your v2 application to v3. + +**Key changes:** +- New application structure +- Improved bindings system +- Enhanced window management +- Better event system +- Simplified configuration + +**Migration time:** 1-4 hours for typical applications + +## Breaking Changes + +### Application Initialisation + +In v2, application setup, window configuration, and execution were all combined into a single `wails.Run()` call. This monolithic approach made it difficult to create multiple windows, handle errors at different stages, or test individual components of your application. + +v3 separates these concerns into distinct phases: application creation, window creation, and execution. This separation gives you explicit control over each stage of your application's lifecycle and makes the code more modular and testable. + +**v2:** + +```go +err := wails.Run(&options.App{ + Title: "My App", + Width: 1024, + Height: 768, + Bind: []interface{}{ + &GreetService{}, + }, +}) +``` + +**v3:** + +```go +app := application.New(application.Options{ + Name: "My App", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, +}) + +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 1024, + Height: 768, +}) + +app.Run() +``` + +**Why this is better:** + +- **Multi-window support**: You can create windows dynamically at any point, not just at startup +- **Better error handling**: Each phase can be validated separately with proper error handling +- **Clearer code**: The separation makes it obvious what's happening at each stage +- **More testable**: You can test application setup without running the event loop +- **More flexible**: Windows can be created, destroyed, and recreated throughout the application lifecycle + +### Bindings + +In v2, every bound struct required a context field and a `startup(ctx)` method to receive the runtime context. This created tight coupling between your business logic and the Wails runtime, making code harder to test and understand. + +v3 introduces the service pattern, where your structs are completely standalone and don't need to store runtime context. If a service needs access to the application instance, it explicitly receives it through dependency injection rather than implicit context threading. + +**v2:** + +```go +type App struct { + ctx context.Context +} + +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +func (a *App) Greet(name string) string { + return "Hello " + name +} +``` + +**v3:** + +```go +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return "Hello " + name +} + +// Register as service +app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&GreetService{}), + }, +}) +``` + +**Why this is better:** + +- **No implicit dependencies**: Services are plain Go structs without hidden runtime dependencies +- **Easier testing**: You can test service methods without mocking a Wails context +- **Clearer code**: Dependencies are explicit (passed as constructor arguments) rather than hidden in a context field +- **Better organization**: Services can be grouped by domain rather than all living in a single `App` struct +- **Proper initialization**: Use `ServiceStartup()` method when you need initialization, making it explicit + +### Runtime + +In v2, all runtime operations required passing a context to global functions from the `runtime` package. This created tight coupling to the context object throughout your codebase and made the API feel procedural rather than object-oriented. + +v3 replaces the context-based runtime with direct method calls on application and window objects. Operations are called directly on the objects they affect, making the code more intuitive and object-oriented. + +**v2:** + +```go +import "github.com/wailsapp/wails/v2/pkg/runtime" + +runtime.WindowSetTitle(a.ctx, "New Title") +runtime.EventsEmit(a.ctx, "event-name", data) +``` + +**v3:** + +```go +// Store app reference +type MyService struct { + app *application.App +} + +func (s *MyService) UpdateTitle() { + window := s.app.Window.Current() + window.SetTitle("New Title") +} + +func (s *MyService) EmitEvent() { + s.app.Event.Emit("event-name", data) +} +``` + +**Why this is better:** + +- **Object-oriented design**: Methods are called on the objects they affect (window, app, menu, etc.) +- **Clearer intent**: `window.SetTitle()` is more obvious than `runtime.WindowSetTitle(ctx, ...)` +- **Better IDE support**: Autocomplete works properly when methods are on objects +- **Multi-window clarity**: With multiple windows, you explicitly choose which window to operate on +- **No context threading**: You don't need to pass context through every function + +### Frontend Bindings + +In v2, bindings were organized by Go package and struct name, typically resulting in paths like `wailsjs/go/main/App`. This structure didn't reflect logical grouping and made it hard to find related functionality. + +v3 organizes bindings by service name and application module, creating a clearer logical structure. The bindings are generated into a `bindings` directory organized by your application name and service names, making it easier to understand what functionality is available. + +**v2:** + +```javascript +import { Greet } from '../wailsjs/go/main/App' + +const result = await Greet("World") +``` + +**v3:** + +```javascript +import { Greet } from './bindings/myapp/greetservice' + +const result = await Greet("World") +``` + +**Why this is better:** + +- **Logical organization**: Bindings are grouped by service name rather than Go package structure +- **Clearer imports**: The path reflects the domain logic (greetservice) not the file structure (main/App) +- **Better discoverability**: You can navigate bindings by feature rather than by technical structure +- **Consistent naming**: Service-based organization matches your backend architecture +- **Simpler paths**: No more `../wailsjs/go` prefix - just `./bindings` + +### Events + +In v2, events used variadic `interface{}` parameters and required passing context to every event function. Event handlers received untyped data that needed manual type assertions, making the event system error-prone and hard to debug. + +v3 introduces typed event objects and removes the context requirement. Event handlers receive a proper event object with typed data, making the event system more reliable and easier to use. + +**v2:** + +```go +runtime.EventsOn(ctx, "event-name", func(data ...interface{}) { + // Handle event +}) + +runtime.EventsEmit(ctx, "event-name", data) +``` + +**v3:** + +```go +app.Event.On("event-name", func(e *application.CustomEvent) { + data := e.Data + // Handle event +}) + +app.Event.Emit("event-name", data) +``` + +**Why this is better:** + +- **Type safety**: Events use proper event objects instead of `...interface{}` +- **Better debugging**: Event objects contain metadata like event name, making debugging easier +- **Clearer API**: `app.Event.On()` and `app.Event.Emit()` are more intuitive than runtime functions +- **No context needed**: Events work directly on the app object without threading context +- **Simpler handlers**: Event handlers have a clear signature instead of variadic parameters + +### Windows + +v2 supported only a single window per application. The window was created at startup and all window operations were performed through runtime functions that implicitly targeted that single window. + +v3 introduces native multi-window support as a core feature. Each window is a first-class object with its own methods and lifecycle. You can create, manage, and destroy multiple windows dynamically throughout your application's lifetime. + +**v2:** + +```go +// Single window only +runtime.WindowSetSize(ctx, 800, 600) +``` + +**v3:** + +```go +// Multiple windows supported +window1 := app.Window.New() +window1.SetSize(800, 600) + +window2 := app.Window.New() +window2.SetSize(1024, 768) +``` + +**Why this is better:** + +- **Multi-window applications**: Build apps with multiple independent windows (dashboards, preferences, tools, etc.) +- **Explicit window references**: Each window is an object you can store and manipulate directly +- **Dynamic window creation**: Create and destroy windows at any time during runtime +- **Independent window state**: Each window has its own events, properties, and lifecycle +- **Better architecture**: Window management is object-oriented rather than context-based + +## Migration Steps + +### Step 1: Update Dependencies + +**go.mod:** + +```go +module myapp + +go 1.21 + +require ( + github.com/wailsapp/wails/v3 v3.0.0-alpha.1 +) +``` + +**Update:** + +```bash +go get github.com/wailsapp/wails/v3@latest +go mod tidy +``` + +### Step 2: Update main.go + +**v2:** + +```go +package main + +import ( + "embed" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" + "github.com/wailsapp/wails/v2/pkg/options/windows" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + app := NewApp() + + err := wails.Run(&options.App{ + Title: "My App", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + Bind: []interface{}{ + app, + }, + Windows: &windows.Options{ + WebviewIsTransparent: false, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} +``` + +**v3:** + +```go +package main + +import ( + "embed" + "github.com/wailsapp/wails/v3/pkg/application" +) + +//go:embed frontend/dist +var assets embed.FS + +func main() { + app := application.New(application.Options{ + Name: "My App", + Services: []application.Service{ + application.NewService(&MyService{}), + }, + Assets: application.AssetOptions{ + Handler: application.AssetFileServerFS(assets), + }, + }) + + app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 1024, + Height: 768, + }) + + err := app.Run() + if err != nil { + panic(err) + } +} +``` + +### Step 3: Convert App Struct to Service + +**v2:** + +```go +type App struct { + ctx context.Context +} + +func NewApp() *App { + return &App{} +} + +func (a *App) startup(ctx context.Context) { + a.ctx = ctx + // Initialisation +} + +func (a *App) Greet(name string) string { + return "Hello " + name +} +``` + +**v3:** + +```go +type MyService struct { + app *application.App +} + +func NewMyService(app *application.App) *MyService { + return &MyService{app: app} +} + +func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + // Initialisation + return nil +} + +func (s *MyService) Greet(name string) string { + return "Hello " + name +} + +// Register after app creation +app := application.New(application.Options{}) +app.RegisterService(application.NewService(NewMyService(app))) +``` + +### Step 4: Update Runtime Calls + +**v2:** + +```go +func (a *App) DoSomething() { + runtime.WindowSetTitle(a.ctx, "New Title") + runtime.EventsEmit(a.ctx, "update", data) + runtime.LogInfo(a.ctx, "Message") +} +``` + +**v3:** + +```go +func (s *MyService) DoSomething() { + window := s.app.Window.Current() + window.SetTitle("New Title") + + s.app.Event.Emit("update", data) + + s.app.Logger.Info("Message") +} +``` + +### Step 5: Update Frontend + +**Generate new bindings:** + +```bash +wails3 generate bindings +``` + +**Update imports:** + +```javascript +// v2 +import { Greet } from '../wailsjs/go/main/App' + +// v3 +import { Greet } from './bindings/myapp/myservice' +``` + +**Update event handling:** + +```javascript +// v2 +import { EventsOn, EventsEmit } from '../wailsjs/runtime/runtime' + +EventsOn("update", (data) => { + console.log(data) +}) + +EventsEmit("action", data) + +// v3 +import { Events } from '@wailsio/runtime' + +Events.On("update", (event) => { + console.log(event.data) +}) + +Events.Emit("action", data) +``` + +### Step 6: Update Configuration + +**v2 (wails.json):** + +```json +{ + "name": "myapp", + "outputfilename": "myapp", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto" +} +``` + +**v3 (wails.json):** + +```json +{ + "name": "myapp", + "frontend": { + "dir": "./frontend", + "install": "npm install", + "build": "npm run build", + "dev": "npm run dev", + "devServerUrl": "http://localhost:5173" + } +} +``` + +## Feature Mapping + +### dialogs + +**v2:** + +```go +selection, err := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{ + Title: "Select File", +}) +``` + +**v3:** + +```go +selection, err := app.Dialog.OpenFile(). + SetTitle("Select File"). + PromptForSingleSelection() +``` + +### Menus + +**v2:** + +```go +menu := menu.NewMenu() +menu.Append(menu.Text("File", nil, []*menu.MenuItem{ + menu.Text("Quit", nil, func(_ *menu.CallbackData) { + runtime.Quit(ctx) + }), +})) +``` + +**v3:** + +```go +menu := application.NewMenu() +fileMenu := menu.AddSubmenu("File") +fileMenu.Add("Quit").OnClick(func(_ *application.Context) { + app.Quit() +}) +``` + +### System Tray + +**v2:** + +```go +// Not available in v2 +``` + +**v3:** + +```go +systray := app.SystemTray.New() +systray.SetIcon(iconBytes) +systray.SetLabel("My App") + +menu := application.NewMenu() +menu.Add("Show").OnClick(func(_ *application.Context) { showWindow() }) +menu.Add("Quit").OnClick(func(_ *application.Context) { app.Quit() }) +systray.SetMenu(menu) +``` + +## Common Issues + +### Issue: Bindings not found + +**Problem:** Import errors after migration + +**Solution:** + +```bash +# Regenerate bindings +wails3 generate bindings + +# Check output directory +ls frontend/bindings +``` + +### Issue: Context errors + +**Problem:** `ctx` not available + +**Solution:** + +Store app reference instead: + +```go +type MyService struct { + app *application.App +} + +func NewMyService(app *application.App) *MyService { + return &MyService{app: app} +} +``` + +### Issue: Window methods not working + +**Problem:** `runtime.WindowSetTitle()` doesn't exist + +**Solution:** + +Use window methods directly: + +```go +window := s.app.Window.Current() +window.SetTitle("New Title") +``` + +### Issue: Events not firing + +**Problem:** Events registered but not received + +**Solution:** + +Check event names match exactly: + +```go +// Go +app.Event.Emit("my-event", data) + +// JavaScript +import { Events } from '@wailsio/runtime' +Events.On("my-event", handler) // Must match exactly +``` + +## Testing Migration + +### Checklist + +- [ ] Application starts without errors +- [ ] All bindings work +- [ ] Events are sent and received +- [ ] Windows open and close correctly +- [ ] Menus work (if applicable) +- [ ] dialogs work (if applicable) +- [ ] System tray works (if applicable) +- [ ] Build process works +- [ ] Production build works + +### Test Commands + +```bash +# Development +wails3 dev + +# Build +wails3 build + +# Generate bindings +wails3 generate bindings +``` + +## Benefits of v3 + +### Performance + +- **Faster startup** - Optimised initialisation +- **Lower memory** - Efficient resource usage +- **Better bridge** - <1ms call overhead + +### Features + +- **Multi-window** - Native support +- **System tray** - Built-in +- **Better events** - Typed, simpler API +- **Services** - Better code organisation + +### Developer Experience + +- **Type safety** - Full TypeScript support +- **Better errors** - Clear error messages +- **Hot reload** - Faster development +- **Better docs** - Comprehensive guides + +## Getting Help + +### Resources + +- [Documentation](/quick-start/why-wails) +- [Discord Community](https://discord.gg/JDdSxwjhGf) +- [GitHub Issues](https://github.com/wailsapp/wails/issues) +- [Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) + +### Common Questions + +**Q: Can I run v2 and v3 side by side?** +A: Yes, they use different import paths. + +**Q: Is v3 production-ready?** +A: v3 is in alpha/beta. Test thoroughly before production. + +**Q: Will v2 be maintained?** +A: Yes, v2 will receive critical updates. + +**Q: How long does migration take?** +A: 1-4 hours for typical applications. + +## Next Steps + + + + Get started with Wails v3. + + [Learn More →](/quick-start/installation) + + + + Understand v3 architecture. + + [Learn More →](/concepts/architecture) + + + + Learn the new bindings system. + + [Learn More →](/features/bindings/methods) + + + + See complete v3 examples. + + [View Examples →](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open an issue](https://github.com/wailsapp/wails/issues). diff --git a/docs/src/content/docs/quick-start/first-app.mdx b/docs/src/content/docs/quick-start/first-app.mdx new file mode 100644 index 000000000..204fed2f8 --- /dev/null +++ b/docs/src/content/docs/quick-start/first-app.mdx @@ -0,0 +1,270 @@ +--- +title: Your First App +description: Build a working Wails application in 10 minutes +sidebar: + order: 3 +--- + +import { Tabs, TabItem, Steps } from "@astrojs/starlight/components"; + +We will build a simple greeting application that demonstrates the core Wails concepts: +- Go backend managing logic +- Frontend calling Go functions +- Type-safe bindings +- Hot reload during development + +**Time to complete:** 10 minutes + +:::tip[Performance Tip for Windows 11 Users] +Consider using [Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/) to store your projects. Dev Drives are optimized for developer workloads and can significantly improve build times and disk access speeds by up to 30% compared to regular NTFS drives. +::: + +## Create Your Project + + + +1. **Generate the project** + + ```bash + wails3 init -n myapp + cd myapp + ``` + + This creates a new project with the default Vanilla + Vite template (plain HTML/CSS/JS with Vite bundler). + + :::tip[Other Templates] + Try `-t react`, `-t vue`, or `-t svelte` for your preferred framework. + Run `wails3 init -l` to see all available templates. + ::: + +2. **Understand the project structure** + + ``` + myapp/ + ├── main.go # Application entry point + ├── greetservice.go # Greet service + ├── frontend/ # Your UI code + │ ├── index.html # HTML entry point + │ ├── src/ + │ │ └── main.js # Frontend JavaScript + │ ├── public/ + │ │ └── style.css # Styles + │ ├── package.json # Frontend dependencies + │ └── vite.config.js # Vite bundler config + ├── build/ # Build configuration + └── Taskfile.yml # Build tasks + ``` + +3. **Run the app** + + ```bash + wails3 dev + ``` + + :::note[First Run] + The first run may take longer than expected as it installs frontend dependencies, generates bindings, etc. Subsequent runs are much faster. + ::: + + The app opens showing a greeting interface. Enter your name and click "Greet" - the Go backend processes your input and returns a greeting. + + + +## How It Works + +Let's understand the code that makes this work. + +### The Go Backend + +Open `greetservice.go`: + +```go title="greetservice.go" +package main + +import ( + "fmt" +) + +type GreetService struct{} + +func (g *GreetService) Greet(name string) string { + return fmt.Sprintf("Hello %s, It's show time!", name) +} +``` + +**Key concepts:** + +1. **Service** - A Go struct with exported methods +2. **Exported method** - `Greet` is capitalized, making it available to the frontend +3. **Simple logic** - Takes a name, returns a greeting +4. **Type safety** - Input and output types are defined + +:::tip[Understanding Services and Bindings] +**Services** are self-contained Go modules that expose functionality to your frontend. They're just regular Go structs with exported methods that you register in the `Services` field of your application config. + +**Bindings** are the auto-generated TypeScript/JavaScript SDK that lets your frontend call these services. When you run `wails3 dev` or `wails3 build`, Wails analyzes your registered services and generates type-safe bindings in `frontend/bindings/`. + +Think of services as your backend API, and bindings as the client library that talks to it. +::: + +### Registering the Service + +Open `main.go` and find the service registration: + +```go title="main.go" {4-6} +err := application.New(application.Options{ + Name: "myapp", + Services: []application.Service{ + application.NewService(&GreetService{}), + }, + // ... other options +}) +``` + +This registers your `GreetService` with Wails, making all its exported methods available to the frontend. + +### The Frontend + +Open `frontend/src/main.js`: + +```javascript title="frontend/src/main.js" +import {GreetService} from "../bindings/changeme"; + +window.greet = async () => { + const nameElement = document.getElementById('name'); + const resultElement = document.getElementById('result'); + + const name = nameElement.value; + if (!name) { + return; + } + + try { + const result = await GreetService.Greet(name); + resultElement.innerText = result; + } catch (err) { + console.error(err); + } +}; +``` + +**Key concepts:** + +1. **Auto-generated bindings** - `GreetService` is imported from generated code +2. **Type-safe calls** - Method names and signatures match your Go code +3. **Async by default** - All Go calls return Promises +4. **Error handling** - Errors from Go are caught in try/catch + +:::note[Where are the bindings?] +Generated bindings are in `frontend/bindings/`. They're created automatically when you run `wails3 dev` or `wails3 build`. + +**Never edit these files manually**—they're regenerated on every build. +::: + +## Customize Your App + +Let's add a new feature to understand the workflow. + +### Add a "Greet Many" Feature + + + +1. **Add the method to GreetService** + + Add this to `greetservice.go`: + + ```go title="greetservice.go" + func (g *GreetService) GreetMany(names []string) []string { + greetings := make([]string, len(names)) + for i, name := range names { + greetings[i] = fmt.Sprintf("Hello %s!", name) + } + return greetings + } + ``` + +2. **The app will auto-rebuild** + + Save the file and `wails3 dev` will automatically rebuild your Go code and restart the app. + + :::note[Auto-Rebuild] + Go code changes trigger an automatic rebuild and restart. Frontend changes hot-reload without restart. + ::: + +3. **Use it in the frontend** + + Add this to `frontend/src/main.js`: + + ```javascript title="frontend/src/main.js" + window.greetMany = async () => { + const names = ['Alice', 'Bob', 'Charlie']; + const greetings = await GreetService.GreetMany(names); + console.log(greetings); + }; + ``` + + Open the browser console and call `greetMany()` - you'll see the array of greetings. + + + +## Build for Production + +When you're ready to distribute your app: + +```bash +wails3 build +``` + +**What this does:** +- Compiles Go code with optimizations +- Builds frontend for production (minified) +- Creates a native executable in `build/bin/` + + + + **Output:** `build/bin/myapp.exe` + + Double-click to run. No dependencies needed (WebView2 is part of Windows). + + + + **Output:** `build/bin/myapp.app` + + Drag to Applications folder or double-click to run. + + + + **Output:** `build/bin/myapp` + + Run with `./build/bin/myapp` or create a `.desktop` file for your launcher. + + + +:::tip[Cross-Platform Builds] +Want to build for other platforms? See [Cross-Platform Builds →](/guides/build/cross-platform) +::: + +## What we've learned + +**Project Structure** +- `main.go` for Go backend +- `frontend/` for UI code +- `Taskfile.yml` for build tasks + +**Services** +- Create Go structs with exported methods +- Register with `application.NewService()` +- Methods automatically available in frontend + +**Bindings** +- Auto-generated TypeScript definitions +- Type-safe function calls +- Async by default (Promises) + +**Development Workflow** +- `wails3 dev` for hot reload +- Go changes auto-rebuild and restart +- Frontend changes hot-reload instantly + +--- + +**Questions?** Join [Discord](https://discord.gg/JDdSxwjhGf) and ask the community. diff --git a/docs/src/content/docs/quick-start/installation.mdx b/docs/src/content/docs/quick-start/installation.mdx new file mode 100644 index 000000000..20bdc51ba --- /dev/null +++ b/docs/src/content/docs/quick-start/installation.mdx @@ -0,0 +1,402 @@ +--- +title: Installation +description: Get Wails installed and ready to build applications +sidebar: + order: 2 +--- + +import { Card, CardGrid, Tabs, TabItem, Steps } from "@astrojs/starlight/components"; + +## Quick Install (5 Minutes) + +:::tip[TL;DR - Experienced Developers] +```bash +# Install Go 1.25+, then: +go install github.com/wailsapp/wails/v3/cmd/wails3@latest +wails3 doctor # Verify installation +``` + +If `wails3 doctor` passes, you're done. [Skip to First App →](/quick-start/first-app) +::: + +## Step-by-Step Installation + + + +1. **Install Go (Required)** + + Wails requires Go 1.25 or later. + + + + Download the Windows installer from **[go.dev/dl](https://go.dev/dl/)** and run it. + + **Verify installation:** + ```powershell + go version # Should show 1.25 or later + ``` + + **Check PATH:** + ```powershell + $env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' } + ``` + + If empty, add `C:\Users\YourName\go\bin` to your PATH. + + + + **Option 1: Official Installer** + + Download the macOS installer (.pkg file) from **[go.dev/dl](https://go.dev/dl/)** and run it. + + **Option 2: Homebrew** + ```bash + brew install go + ``` + + **Verify installation:** + ```bash + go version # Should show 1.25 or later + echo $PATH | grep go/bin # Should show ~/go/bin + ``` + + If `~/go/bin` isn't in PATH, add to `~/.zshrc` or `~/.bash_profile`: + ```bash + export PATH=$PATH:~/go/bin + ``` + + + + **Option 1: Official Tarball** + + Download the Linux tarball from **[go.dev/dl](https://go.dev/dl/)**, then: + ```bash + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz + ``` + + **Option 2: Package Manager** + ```bash + # Ubuntu/Debian + sudo apt install golang-go + + # Fedora + sudo dnf install golang + + # Arch + sudo pacman -S go + ``` + + **Add to PATH** (add to `~/.bashrc` or `~/.zshrc`): + ```bash + export PATH=$PATH:/usr/local/go/bin:~/go/bin + source ~/.bashrc # Reload + ``` + + **Verify:** + ```bash + go version + echo $PATH | grep go/bin + ``` + + + +2. **Install Platform Dependencies** + + + + **WebView2 Runtime** (usually pre-installed) + + Windows 10/11 includes WebView2 by default. If missing: + - Download from [Microsoft](https://developer.microsoft.com/microsoft-edge/webview2/) + - Or run `wails3 doctor` later—it will guide you + + **That's it!** No other dependencies needed. + + :::tip[Performance Tip for Windows 11] + Consider using [Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/) to store your projects. Dev Drives are optimized for developer workloads and can significantly improve build times and disk access speeds by up to 30%. + ::: + + + + **Xcode Command Line Tools** (required) + + ```bash + xcode-select --install + ``` + + Click "Install" in the dialog that appears. + + **Verify:** + ```bash + xcode-select -p # Should show /Library/Developer/CommandLineTools + ``` + + **That's it!** macOS includes WebKit by default. + + + + **Build tools and WebKit** + + + + ```bash + sudo apt update + sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev + ``` + + + + ```bash + sudo dnf install gcc pkg-config gtk3-devel webkit2gtk4.0-devel + ``` + + + + ```bash + sudo pacman -S base-devel gtk3 webkit2gtk + ``` + + + + Run `wails3 doctor` after installing Wails—it will show the exact packages needed for your distribution. + + + + + +3. **Install Wails CLI** + + ```bash + go install github.com/wailsapp/wails/v3/cmd/wails3@latest + ``` + + This installs the `wails3` command to `~/go/bin` (or `%USERPROFILE%\go\bin` on Windows). + +4. **Verify Installation** + + ```bash + wails3 doctor + ``` + + **Expected output (or similar):** + ``` + Wails (v3.0.0-dev) Wails Doctor + + # System + + ┌──────────────────────────────────────────────────┐ + | Name | MacOS | + | Version | 26.0 | + | ID | 25A354 | + | Branding | MacOS 26.0 | + | Platform | darwin | + | Architecture | arm64 | + | Apple Silicon | true | + | CPU | Apple M2 Pro | + | CPU 1 | Apple M2 Pro | + | CPU 2 | Apple M2 Pro | + | GPU | 16 cores, Metal Support: Metal 4 | + | Memory | 16 GB | + └──────────────────────────────────────────────────┘ + + # Build Environment + + ┌─────────────┬─────────────────┐ + | Wails CLI | v3.0.0-alpha.40 | + | Go Version | go1.24.6 | + └─────────────┴─────────────────┘ + + # Dependencies + + ┌─────────────────┬─────────────────────────────────────────────────┐ + | npm | 11.6.2 | + | *NSIS | Not Installed. Install with `brew install...`. | + | Xcode cli tools | 2412 | + └─────────────────┴─────────────────────────────────────────────────┘ + + # Checking for issues + + SUCCESS No issues found + + # Diagnosis + + SUCCESS Your system is ready for Wails development! + ``` + + :::note[If `wails3` command not found] + Your `~/go/bin` isn't in PATH. See step 1 above to fix this, then restart your terminal. + ::: + +5. **Install npm (Optional but Recommended)** + + Most Wails templates use npm for frontend tooling. + + + + Download from [nodejs.org](https://nodejs.org/) and run the installer. + + **Verify:** + ```powershell + npm --version + ``` + + + + **Option 1: Official Installer** + Download from [nodejs.org](https://nodejs.org/) + + **Option 2: Homebrew** + ```bash + brew install node + ``` + + **Verify:** + ```bash + npm --version + ``` + + + + **Option 1: NodeSource** + ```bash + curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - + sudo apt-get install -y nodejs # Ubuntu/Debian + ``` + + **Option 2: Package Manager** + ```bash + sudo dnf install nodejs # Fedora + sudo pacman -S nodejs npm # Arch + ``` + + **Verify:** + ```bash + npm --version + ``` + + + + :::tip[Alternative Package Managers] + Prefer `pnpm`, `yarn`, or `bun`? No problem! Just update the `Taskfile.yml` in your project to use your preferred tool. + ::: + + + +## Troubleshooting + +#### `wails3` command not found + +**Cause:** `~/go/bin` (or `%USERPROFILE%\go\bin`) isn't in your PATH. + +**Solution:** + + + + 1. Open "Environment Variables" (search in Start menu) + 2. Under "User variables", find `Path` + 3. Click "Edit" → "New" + 4. Add: `C:\Users\YourName\go\bin` (replace `YourName`) + 5. Click "OK" on all dialogs + 6. **Restart your terminal** + + **Verify:** + ```powershell + $env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' } + ``` + + + + Add to `~/.zshrc` (macOS) or `~/.bashrc` (Linux): + ```bash + export PATH=$PATH:~/go/bin + ``` + + Reload: + ```bash + source ~/.zshrc # or ~/.bashrc + ``` + + **Verify:** + ```bash + echo $PATH | grep go/bin + wails3 version + ``` + + +--- + +#### `wails3 doctor` reports missing dependencies + +**Linux:** The output tells you exactly which packages to install. Example: +``` +❌ webkit2gtk not found + Install with: sudo apt install libwebkit2gtk-4.0-dev +``` + +**Windows:** If WebView2 is missing: +- Download from [Microsoft](https://developer.microsoft.com/microsoft-edge/webview2/) +- Or it will be installed automatically when you run your first app + +**macOS:** If Xcode tools are missing: +```bash +xcode-select --install +``` +--- +#### Go version too old + +Wails v3 requires Go 1.25+. If you have an older version: + + + + Download the latest from [go.dev/dl](https://go.dev/dl/) and reinstall. + + + + Download the latest tarball from [go.dev/dl](https://go.dev/dl/), then: + ```bash + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf go1.25.linux-amd64.tar.gz + ``` + + + +## Development Version (Bleeding Edge) + +Want to use the absolute latest code from the main development branch? This gives you access to new features and fixes before they're released, but comes with the risk of bugs and breaking changes. Only recommended for contributors or those who need to test upcoming features. + +```bash +git clone https://github.com/wailsapp/wails.git +cd wails +git checkout v3 +cd v3/cmd/wails3 +go install +``` + +:::caution[Development Version] +- May have bugs or breaking changes +- Projects created will use `replace` directive to point to local Wails +- Only recommended for contributors or testing new features +::: + +## Next Steps + +**Installation Complete!** Your system is ready for Wails development. + + + Create a working application in 10 minutes. + + [First App Tutorial →](/quick-start/first-app) + + + + See what's available out of the box. + + ```bash + wails3 init -l # List templates + ``` + + +--- + +**Having issues?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open an issue](https://github.com/wailsapp/wails/issues). diff --git a/docs/src/content/docs/quick-start/next-steps.mdx b/docs/src/content/docs/quick-start/next-steps.mdx new file mode 100644 index 000000000..fc17185d4 --- /dev/null +++ b/docs/src/content/docs/quick-start/next-steps.mdx @@ -0,0 +1,34 @@ +--- +title: Next Steps +description: Where to go after building your first app +sidebar: + order: 4 +--- + +You've built your first Wails app and understand the basics. Here's where to go next. + +## Learn More + +Practice by building complete applications: + +- [TODO App](/tutorials/02-todo-vanilla) - CRUD operations, state management, modern UI +- [All Tutorials](/tutorials/overview) - Notes apps, dashboards, system tray apps, and more + +## Features + +Wails provides native desktop capabilities: + +- [Windows](/features/windows/basics) - Multiple windows, frameless windows, positioning +- [Menus](/features/menus/application) - Application menus, context menus, system tray +- [Dialogs](/features/dialogs/file) - File open/save, message dialogs +- [Events](/features/events/system) - Communication between components +- [Bindings](/features/bindings/methods) - Type-safe Go ↔ JavaScript calls +- [Clipboard](/features/clipboard) - Copy/paste operations +- [Drag & Drop](/features/drag-drop) - File drag and drop +- [Keyboard](/features/keyboard) - Global shortcuts + +## Get Help + +- [Discord](https://discord.gg/JDdSxwjhGf) - Ask questions, share projects +- [Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples) - 50+ working examples +- [API Reference](/reference/overview) - Complete documentation diff --git a/docs/src/content/docs/quick-start/why-wails.mdx b/docs/src/content/docs/quick-start/why-wails.mdx new file mode 100644 index 000000000..a99c299d2 --- /dev/null +++ b/docs/src/content/docs/quick-start/why-wails.mdx @@ -0,0 +1,133 @@ +--- +title: Why Wails? +description: Understand why Wails is the right choice for your desktop application +sidebar: + order: 1 +--- + + +Wails combines **Go's performance and simplicity** with **modern web UI flexibility**, enabling you to build beautiful, native desktop applications with the tools you already know. + +## Performance That Users Notice + +**Wails Applications:** +- **~15MB binaries** (vs Electron's 150MB) +- **~10MB baseline memory** (vs Electron's 100MB+) +- **<0.5s startup time** (vs Electron's 2-3s) +- **Native rendering** using the OS-provided WebView + +Users perceive your application as fast, lightweight, and professional. + +## Developer Experience + +**Write Once, Run Everywhere:** +- One Go codebase for Windows, macOS, and Linux +- Use any web framework (React, Vue, Svelte, vanilla JS) +- Hot reload during development +- TypeScript bindings auto-generated from Go code + +Ship faster with less code to maintain. + +## Production-Ready Features + +**Everything you need:** +- Multiple windows with independent lifecycles +- Native menus (application, context, system tray) +- File dialogs with platform-native UI +- System integration (notifications, clipboard, keyboard shortcuts) +- Code signing and packaging for all platforms + +Build professional applications, not prototypes. + +## Faster Development + +- **One codebase, three platforms** - Write once, build for Windows, macOS, and Linux +- **Use existing skills** - Go for backend, HTML/CSS/JS for UI +- **Instant feedback** - Hot reload during development, compile times measured in seconds +- **Small binaries** - 15MB apps mean faster builds, faster downloads, faster iteration + +## When to Choose Wails + +**Wails is Perfect For:** + +- **Business applications** (CRM, inventory, dashboards, admin tools) +- **Developer tools** (database clients, API testers, deployment tools) +- **Productivity apps** (note-taking, task managers, time trackers) +- **Creative tools** (image editors, video processors, design utilities) +- **Internal tools** (company-specific applications, automation tools) + +## Real-World Success Stories + +:::tip[Production Applications] +Wails powers real applications used by thousands of users: +- **Database management tools** with complex UIs +- **Financial dashboards** processing real-time data +- **Video editing tools** with native performance +- **Development utilities** used by engineering teams + +[See the showcase →](/community/showcase) +::: + +## How Wails Works + +Unlike Electron which bundles an entire browser and Node.js runtime, Wails takes a fundamentally different approach: your Go code compiles to a native binary, and your UI runs in the operating system's built-in WebView. This architecture delivers the small binaries, fast startup, and low memory usage that make Wails applications feel native. + +### Architecture + +Wails applications consist of two main parts that communicate seamlessly: a Go backend handling business logic and system operations, and a web-based frontend for your user interface. The OS-provided WebView renders your UI without bundling a browser, while the bindings layer provides type-safe communication between Go and JavaScript. + +
+
+ +```d2 +direction: down + +Application: { +Go: "Go Backend" { + shape: rectangle + style.fill: "#00ADD8" +} +WebView: "Native WebView" { + shape: rectangle + style.fill: "#3B82F6" + + Frontend: "Your UI\n(React/Vue/etc)" { + shape: rectangle + style.fill: "#8B5CF6" + } + +} +Go <-> WebView: "Bindings & Events" +} + + + +``` + +
+
+ +This simple architecture enables JavaScript code to call Go functions directly (through auto-generated bindings), while Go can send events and data back to the frontend. Both layers communicate through an efficient in-memory bridge with sub-millisecond overhead. + +**How Wails achieves performance:** +1. **No runtime bundled** - Uses Go's compiled binary +2. **Native WebView** - OS-provided rendering engine +3. **Direct Go ↔ JS bridge** - In-memory communication, no network overhead +4. **Compiled binary** - Instant startup, no JIT compilation + +## Next Steps + +Now that you understand what Wails provides, let's get you set up: + +1. **Install Wails** - Set up your development environment in 5 minutes + [Installation Guide →](/quick-start/installation) + +2. **Build Your First App** - Create a working application and understand the basics + [First App Tutorial →](/quick-start/first-app) + +3. **Explore Features** - Discover what Wails can do for your application + [Feature Overview →](/quick-start/next-steps) + +--- + +**Still have questions?** Join our [Discord community](https://discord.gg/JDdSxwjhGf) and ask the team directly. diff --git a/docs/src/content/docs/reference/application.mdx b/docs/src/content/docs/reference/application.mdx new file mode 100644 index 000000000..25257a45d --- /dev/null +++ b/docs/src/content/docs/reference/application.mdx @@ -0,0 +1,572 @@ +--- +title: Application API +description: Complete reference for the Application API +sidebar: + order: 1 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Overview + +The `Application` is the core of your Wails app. It manages windows, services, events, and provides access to all platform features through dedicated managers. + +## Creating an Application + +```go +import "github.com/wailsapp/wails/v3/pkg/application" + +app := application.New(application.Options{ + Name: "My App", + Description: "My awesome application", + Services: []application.Service{ + application.NewService(&MyService{}), + }, +}) +``` + +## Application Options + +The `Options` struct configures the application: + +| Field | Type | Description | +|-------|------|-------------| +| `Name` | `string` | Application name (used in default about box). Default: `"My Wails Application"` | +| `Description` | `string` | Application description (used in default about box). Default: `"An application written using Wails"` | +| `Icon` | `[]byte` | Application icon (used in default about box) | +| `Mac` | `MacOptions` | macOS-specific configuration | +| `Windows` | `WindowsOptions` | Windows-specific configuration | +| `Linux` | `LinuxOptions` | Linux-specific configuration | +| `IOS` | `IOSOptions` | iOS-specific configuration | +| `Android` | `AndroidOptions` | Android-specific configuration | +| `Services` | `[]Service` | Services to bind to the frontend | +| `MarshalError` | `func(error) []byte` | Custom error marshalling for service method errors. Return nil to fall back to default | +| `BindAliases` | `map[uint32]uint32` | Alias IDs for bound methods | +| `Logger` | `*slog.Logger` | Custom logger for Wails system messages | +| `LogLevel` | `slog.Level` | Log level for the Wails system logger | +| `Assets` | `AssetOptions` | Application assets configuration | +| `Flags` | `map[string]any` | Key-value pairs available to the frontend | +| `PanicHandler` | `func(*PanicDetails)` | Called when a panic occurs | +| `DisableDefaultSignalHandler` | `bool` | Disables the default signal handler | +| `KeyBindings` | `map[string]func(window Window)` | Global key bindings | +| `OnShutdown` | `func()` | Called when the application is about to terminate. Blocks shutdown until it returns | +| `PostShutdown` | `func()` | Called after shutdown completes, just before process exit. The app instance is no longer usable | +| `ShouldQuit` | `func() bool` | Called when user tries to quit. Return `false` to prevent quit | +| `RawMessageHandler` | `func(window Window, message string, originInfo *OriginInfo)` | Called when the frontend sends a raw (non-wails) message | +| `WarningHandler` | `func(string)` | Custom warning handler | +| `ErrorHandler` | `func(err error)` | Custom error handler | +| `FileAssociations` | `[]string` | File extensions associated with the app (e.g., `[".txt", ".md"]`) | +| `SingleInstance` | `*SingleInstanceOptions` | Single instance configuration | +| `Transport` | `Transport` | Custom IPC transport layer (default: HTTP fetch-based) | +| `Server` | `ServerOptions` | HTTP server configuration for server mode (build with `server` tag) | + +### AssetOptions + +| Field | Type | Description | +|-------|------|-------------| +| `Handler` | `http.Handler` | Serves all content to the WebView | +| `Middleware` | `Middleware` | HTTP middleware for the asset server request chain | +| `DisableLogging` | `bool` | Disables asset server request logging | + +### Platform Options + +#### MacOptions + +| Field | Type | Description | +|-------|------|-------------| +| `ActivationPolicy` | `ActivationPolicy` | Application activation policy. Default: `ActivationPolicyRegular` | +| `ApplicationShouldTerminateAfterLastWindowClosed` | `bool` | Terminate when last window closes | + +#### WindowsOptions + +| Field | Type | Description | +|-------|------|-------------| +| `WndClass` | `string` | Window class name. Default: `"WailsWebviewWindow"` | +| `WndProcInterceptor` | `func(...)` | Intercept main message loop messages | +| `DisableQuitOnLastWindowClosed` | `bool` | Keep app running after last window closes | +| `WebviewUserDataPath` | `string` | WebView2 user data path. Default: `%APPDATA%\[BinaryName.exe]` | +| `WebviewBrowserPath` | `string` | Path to WebView2 executables | +| `EnabledFeatures` | `[]string` | WebView2 browser features to enable | +| `DisabledFeatures` | `[]string` | WebView2 browser features to disable | +| `AdditionalBrowserArgs` | `[]string` | Additional browser arguments (include `--` prefix) | + +#### LinuxOptions + +| Field | Type | Description | +|-------|------|-------------| +| `DisableQuitOnLastWindowClosed` | `bool` | Keep app running after last window closes | +| `ProgramName` | `string` | Program name for GTK window manager | + +## Core Methods + +### Run() + +Starts the application event loop. + +```go +func (a *App) Run() error +``` + +**Example:** + +```go +err := app.Run() +if err != nil { + log.Fatal(err) +} +``` + +**Returns:** Error if startup fails + +### Quit() + +Gracefully shuts down the application. + +```go +func (a *App) Quit() +``` + +**Example:** + +```go +// In a menu handler +menu.Add("Quit").OnClick(func(ctx *application.Context) { + app.Quit() +}) +``` + +### Config() + +Returns the application configuration. + +```go +func (a *App) Config() Options +``` + +**Example:** + +```go +config := app.Config() +fmt.Println("App name:", config.Name) +``` + +### Context() + +Returns the application context that is canceled when the application shuts down. Use this for graceful shutdown of goroutines and long-running operations. + +```go +func (a *App) Context() context.Context +``` + +### OnShutdown() + +Adds a function to be run when the application is shutting down. + +```go +func (a *App) OnShutdown(f func()) +``` + +**Example:** + +```go +app.OnShutdown(func() { + fmt.Println("Application shutting down...") + // Cleanup resources +}) +``` + +### Hide() / Show() + +Hides or shows the application. + +```go +func (a *App) Hide() +func (a *App) Show() +``` + +### SetIcon() + +Sets the application icon. + +```go +func (a *App) SetIcon(icon []byte) +``` + +### GetPID() + +Returns the process ID of the application. + +```go +func (a *App) GetPID() int +``` + +### Capabilities() + +Returns the application's capabilities information. + +```go +func (a *App) Capabilities() capabilities.Capabilities +``` + +## Managers + +The Application provides access to various managers through properties: + +```go +app.Window // Window management +app.Menu // Menu management +app.Dialog // Dialog management +app.Event // Event management +app.Clipboard // Clipboard operations +app.Screen // Screen information +app.SystemTray // System tray +app.Browser // Browser operations +app.Env // Environment information +app.ContextMenu // Context menu management +app.KeyBinding // Key binding management +``` + +### Example Usage + +```go +// Create window +window := app.Window.New() + +// Show dialog +app.Dialog.Info().SetMessage("Hello!").Show() + +// Copy to clipboard +app.Clipboard.SetText("Copied text") + +// Get screens +screens := app.Screen.GetAll() +``` + +## Window Management + +### app.Window.New() + +Creates a new webview window with default options. + +```go +func (wm *WindowManager) New() *WebviewWindow +``` + +**Example:** + +```go +window := app.Window.New() +``` + +### app.Window.NewWithOptions() + +Creates a new webview window with custom options. + +```go +func (wm *WindowManager) NewWithOptions(options WebviewWindowOptions) *WebviewWindow +``` + +**Example:** + +```go +window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My Window", + Width: 800, + Height: 600, + BackgroundColour: application.NewRGB(255, 255, 255), +}) +``` + +### app.Window.GetByName() + +Gets a window by its name. + +```go +func (wm *WindowManager) GetByName(name string) (Window, bool) +``` + +**Example:** + +```go +window, exists := app.Window.GetByName("main") +if exists { + window.Show() +} +``` + +### app.Window.GetAll() + +Returns all application windows. + +```go +func (wm *WindowManager) GetAll() []Window +``` + +**Example:** + +```go +windows := app.Window.GetAll() +for _, window := range windows { + fmt.Println("Window:", window.Name()) +} +``` + +### app.Window.Current() + +Returns the current active window (may be nil). + +```go +func (wm *WindowManager) Current() Window +``` + +## Service Management + +### RegisterService() + +Registers a service with the application. Must be called before `Run()`. + +```go +func (a *App) RegisterService(service Service) +``` + +**Example:** + +```go +type MyService struct{} + +// Register after app creation but before Run() +app.RegisterService(application.NewService(&MyService{})) +``` + +**Note:** Services can also be registered via the `Services` field in `Options` when creating the application. + +## Event Management + +### app.Event.Emit() + +Emits a custom event. + +```go +func (em *EventManager) Emit(name string, data ...any) +``` + +**Example:** + +```go +// Emit event with data +app.Event.Emit("user-logged-in", map[string]any{ + "username": "john", + "timestamp": time.Now(), +}) +``` + +### app.Event.On() + +Listens for custom events. Returns a cleanup function to remove the listener. + +```go +func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() +``` + +**Example:** + +```go +cancel := app.Event.On("user-logged-in", func(e *application.CustomEvent) { + fmt.Println("User logged in:", e.Data) +}) + +// Later, to stop listening: +cancel() +``` + +### app.Event.OnApplicationEvent() + +Listens for application lifecycle events. Returns a cleanup function. + +```go +func (em *EventManager) OnApplicationEvent( + eventType events.ApplicationEventType, + callback func(*ApplicationEvent), +) func() +``` + +**Example:** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Listen for theme change +cancel := app.Event.OnApplicationEvent(events.Mac.ApplicationDidChangeTheme, func(e *application.ApplicationEvent) { + fmt.Println("Theme changed") +}) +``` + +### app.Event.Off() + +Removes all listeners for a custom event. + +```go +func (em *EventManager) Off(name string) +``` + +## Dialog Methods + +### app.Dialog.Info() + +Creates an information dialog. + +```go +func (dm *DialogManager) Info() *MessageDialog +``` + +**Example:** + +```go +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Operation completed!"). + Show() +``` + +### app.Dialog.Question() + +Creates a question dialog. + +```go +func (dm *DialogManager) Question() *MessageDialog +``` + +**Example:** + +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Continue?") + +yesBtn := dialog.AddButton("Yes") +noBtn := dialog.AddButton("No") + +yesBtn.SetAsDefault() +noBtn.SetAsCancel() + +yesBtn.OnClick(func() { + // Continue +}) + +noBtn.OnClick(func() { + // Cancel +}) + +dialog.Show() +``` + +### app.Dialog.Error() + +Creates an error dialog. + +```go +func (dm *DialogManager) Error() *MessageDialog +``` + +### app.Dialog.Warning() + +Creates a warning dialog. + +```go +func (dm *DialogManager) Warning() *MessageDialog +``` + +### app.Dialog.OpenFile() + +Creates a file open dialog. + +```go +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct +``` + +**Example:** + +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select File"). + AddFilter("Images", "*.png;*.jpg"). + PromptForSingleSelection() +``` + +### app.Dialog.SaveFile() + +Creates a file save dialog. + +```go +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct +``` + +**Example:** + +```go +path, err := app.Dialog.SaveFile(). + SetTitle("Save File"). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + PromptForSingleSelection() +``` + +**Note:** To select a folder, use `OpenFile()` with directory selection: + +```go +folder, err := app.Dialog.OpenFile(). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() +``` + +## Logger + +The application provides a structured logger: + +```go +app.Logger.Info("Message", "key", "value") +app.Logger.Error("Error occurred", "error", err) +app.Logger.Debug("Debug info") +app.Logger.Warn("Warning message") +``` + +## Complete Example + +```go +package main + +import ( + "log" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "My Application", + Description: "A demo application", + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: true, + }, + }) + + // Create main window + window := app.Window.NewWithOptions(application.WebviewWindowOptions{ + Title: "My App", + Width: 1024, + Height: 768, + MinWidth: 800, + MinHeight: 600, + BackgroundColour: application.NewRGB(255, 255, 255), + URL: "/", + }) + + window.Center() + + err := app.Run() + if err != nil { + log.Fatal(err) + } +} +``` diff --git a/docs/src/content/docs/reference/cli.mdx b/docs/src/content/docs/reference/cli.mdx new file mode 100644 index 000000000..0b814ef8d --- /dev/null +++ b/docs/src/content/docs/reference/cli.mdx @@ -0,0 +1,28 @@ +--- +title: CLI Reference +description: Complete reference for the Wails CLI commands +sidebar: + order: 1 +--- + +## Overview + +The Wails CLI (`wails3`) provides commands for creating, developing, building, and managing Wails applications. + +## Coming Soon + +This section is under construction. For now, use `wails3 --help` or `wails3 [command] --help` for command documentation. + +**Common commands:** + +```bash +wails3 init # Create a new project +wails3 dev # Run in development mode +wails3 build # Build the project +wails3 generate # Generate bindings or runtime +wails3 doctor # Check development environment +``` + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples). diff --git a/docs/src/content/docs/reference/dialogs.mdx b/docs/src/content/docs/reference/dialogs.mdx new file mode 100644 index 000000000..e82f8103e --- /dev/null +++ b/docs/src/content/docs/reference/dialogs.mdx @@ -0,0 +1,717 @@ +--- +title: Dialogs API +description: Complete reference for native dialog APIs +sidebar: + order: 5 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Overview + +The Dialogs API provides methods to show native file dialogs and message dialogs. All dialogs are accessed through the `app.Dialog` manager. + +**Dialog Types:** +- **File Dialogs** - Open and Save dialogs +- **Message Dialogs** - Info, Error, Warning, and Question dialogs + +All dialogs are **native OS dialogs** that match the platform's look and feel. + +## File Dialogs + +### app.Dialog.OpenFile() + +Creates a file open dialog. + +```go +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct +``` + +**Example:** +```go +dialog := app.Dialog.OpenFile() +``` + +### OpenFileDialogStruct Methods + +#### SetTitle() + +Sets the dialog title. + +```go +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct +``` + +**Example:** +```go +dialog.SetTitle("Select Image") +``` + +#### AddFilter() + +Adds a file type filter. Call multiple times to add multiple filters. + +```go +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct +``` + +**Parameters:** +- `displayName` - Filter label (e.g., "Image Files") +- `pattern` - Semicolon-separated extensions (e.g., `"*.png;*.jpg"`) + +**Example:** +```go +dialog.AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("Documents", "*.pdf;*.docx"). + AddFilter("All Files", "*.*") +``` + +#### SetDirectory() + +Sets the initial directory. + +```go +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct +``` + +**Example:** +```go +homeDir, _ := os.UserHomeDir() +dialog.SetDirectory(homeDir) +``` + +#### CanChooseDirectories() + +Sets whether directories can be selected. + +```go +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct +``` + +#### CanChooseFiles() + +Sets whether files can be selected (default: true). + +```go +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct +``` + +#### CanCreateDirectories() + +Sets whether new directories can be created in the dialog. + +```go +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct +``` + +#### ShowHiddenFiles() + +Sets whether hidden files are shown. + +```go +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct +``` + +#### ResolvesAliases() + +Sets whether aliases (symlinks) are resolved. + +```go +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct +``` + +#### AllowsOtherFileTypes() + +Sets whether file types other than those in filters are allowed. + +```go +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct +``` + +#### HideExtension() + +Sets whether the file extension is hidden. + +```go +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct +``` + +#### TreatsFilePackagesAsDirectories() + +Sets whether file packages are treated as directories (macOS). + +```go +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct +``` + +#### CanSelectHiddenExtension() + +Sets whether hidden extensions can be selected. + +```go +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct +``` + +#### SetMessage() + +Sets the dialog message text. + +```go +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct +``` + +#### SetButtonText() + +Sets the text for the dialog's confirmation button. + +```go +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window (sheet on macOS). + +```go +func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct +``` + +#### PromptForSingleSelection() + +Shows the dialog and returns the selected file. + +```go +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) +``` + +**Returns:** +- `string` - Selected file path (empty if cancelled) +- `error` - Error if failed + +**Example:** +```go +path, err := app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.gif"). + PromptForSingleSelection() + +if err != nil { + // Handle error + return +} + +if path == "" { + // User cancelled + return +} + +// Use the selected file +processFile(path) +``` + +#### PromptForMultipleSelection() + +Shows the dialog and returns multiple selected files. + +```go +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) +``` + +**Returns:** +- `[]string` - Array of selected file paths +- `error` - Error if failed + +**Example:** +```go +paths, err := app.Dialog.OpenFile(). + SetTitle("Select Images"). + AddFilter("Images", "*.png;*.jpg"). + PromptForMultipleSelection() + +if err != nil { + return +} + +for _, path := range paths { + processFile(path) +} +``` + +### Selecting Folders + +There is no separate folder dialog. Use `OpenFile()` with directory selection enabled: + +```go +folder, err := app.Dialog.OpenFile(). + SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() + +if err != nil { + return +} + +// Use the selected folder +outputDir = folder +``` + +### app.Dialog.OpenFileWithOptions() + +Creates a file open dialog with pre-set options. + +```go +func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct +``` + +### app.Dialog.SaveFile() + +Creates a file save dialog. + +```go +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct +``` + +**Example:** +```go +dialog := app.Dialog.SaveFile() +``` + +### app.Dialog.SaveFileWithOptions() + +Creates a file save dialog with pre-set options. + +```go +func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct +``` + +### SaveFileDialogStruct Methods + +#### SetFilename() + +Sets the default filename. + +```go +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct +``` + +**Example:** +```go +dialog.SetFilename("document.pdf") +``` + +#### AddFilter() + +Adds a file type filter (same as OpenFileDialogStruct). + +```go +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct +``` + +#### SetDirectory() + +Sets the initial directory. + +```go +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct +``` + +#### SetMessage() + +Sets the dialog message text. + +```go +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct +``` + +#### SetButtonText() + +Sets the text for the dialog's confirmation button. + +```go +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct +``` + +#### CanCreateDirectories() + +Sets whether new directories can be created in the dialog. + +```go +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct +``` + +#### ShowHiddenFiles() + +Sets whether hidden files are shown. + +```go +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct +``` + +#### CanSelectHiddenExtension() + +Sets whether hidden extensions can be selected. + +```go +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct +``` + +#### HideExtension() + +Sets whether the file extension is hidden. + +```go +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct +``` + +#### TreatsFilePackagesAsDirectories() + +Sets whether file packages are treated as directories (macOS). + +```go +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct +``` + +#### AllowsOtherFileTypes() + +Sets whether file types other than those in filters are allowed. + +```go +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window (sheet on macOS). + +```go +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct +``` + +#### PromptForSingleSelection() + +Shows the dialog and returns the save path. + +```go +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) +``` + +**Example:** +```go +path, err := app.Dialog.SaveFile(). + SetFilename("untitled.pdf"). + AddFilter("PDF Document", "*.pdf"). + PromptForSingleSelection() + +if err != nil { + return +} + +if path == "" { + // User cancelled + return +} + +// Save to the selected path +saveDocument(path) +``` + +:::note +`SaveFileDialogStruct` does not have a `SetTitle()` method. To set the title, use `SaveFileWithOptions()`: +```go +dialog := app.Dialog.SaveFileWithOptions(&application.SaveFileDialogOptions{ + Title: "Save Document", +}) +``` +::: + +## Message Dialogs + +All message dialogs are created through the `app.Dialog` manager and return a `*MessageDialog`. The dialog type (Info, Question, Warning, Error) is set internally. + +### app.Dialog.Info() + +Creates an information dialog. + +```go +func (dm *DialogManager) Info() *MessageDialog +``` + +### app.Dialog.Question() + +Creates a question dialog. + +```go +func (dm *DialogManager) Question() *MessageDialog +``` + +### app.Dialog.Warning() + +Creates a warning dialog. + +```go +func (dm *DialogManager) Warning() *MessageDialog +``` + +### app.Dialog.Error() + +Creates an error dialog. + +```go +func (dm *DialogManager) Error() *MessageDialog +``` + +### MessageDialog Methods + +#### SetTitle() + +Sets the dialog title. + +```go +func (d *MessageDialog) SetTitle(title string) *MessageDialog +``` + +#### SetMessage() + +Sets the dialog message. + +```go +func (d *MessageDialog) SetMessage(message string) *MessageDialog +``` + +#### SetIcon() + +Sets a custom icon for the dialog. + +```go +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog +``` + +#### AddButton() + +Adds a button to the dialog. Returns the created `*Button` for further configuration. + +```go +func (d *MessageDialog) AddButton(s string) *Button +``` + +**Returns:** `*Button` - The created button + +#### AddButtons() + +Sets all buttons for the dialog at once, replacing any existing buttons. + +```go +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog +``` + +#### SetDefaultButton() + +Sets the default button (activated by pressing Enter). + +```go +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog +``` + +**Note:** Takes a `*Button` pointer, not an index. + +#### SetCancelButton() + +Sets the cancel button (activated by pressing Escape). + +```go +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog +``` + +**Note:** Takes a `*Button` pointer, not an index. + +#### AttachToWindow() + +Attaches the dialog to a window (sheet on macOS). + +```go +func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog +``` + +#### Show() + +Shows the dialog. This method does not return a value; use button callbacks to handle user responses. + +```go +func (d *MessageDialog) Show() +``` + +### Button Methods + +Each button returned by `AddButton()` supports: + +```go +func (b *Button) OnClick(callback func()) *Button +func (b *Button) SetAsDefault() *Button +func (b *Button) SetAsCancel() *Button +``` + +### Message Dialog Examples + +**Simple info dialog:** +```go +app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() +``` + +**Error dialog:** +```go +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Failed to save file: " + err.Error()). + Show() +``` + +**Warning dialog:** +```go +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() +``` + +**Question dialog with buttons:** +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Do you want to save changes?") + +saveBtn := dialog.AddButton("Save") +dontSaveBtn := dialog.AddButton("Don't Save") +cancelBtn := dialog.AddButton("Cancel") + +saveBtn.SetAsDefault() +cancelBtn.SetAsCancel() + +saveBtn.OnClick(func() { + saveDocument() +}) + +dontSaveBtn.OnClick(func() { + // Continue without saving +}) + +cancelBtn.OnClick(func() { + // Do nothing - cancel +}) + +dialog.Show() +``` + +## Complete Examples + +### File Selection Example + +```go +type FileService struct{} + +func (s *FileService) OpenImage(app *application.App) (string, error) { + return app.Dialog.OpenFile(). + SetTitle("Select Image"). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + AddFilter("All Files", "*.*"). + PromptForSingleSelection() +} + +func (s *FileService) SaveDocument(app *application.App, defaultName string) (string, error) { + return app.Dialog.SaveFileWithOptions(&application.SaveFileDialogOptions{ + Title: "Save Document", + }). + SetFilename(defaultName). + AddFilter("PDF Document", "*.pdf"). + AddFilter("Text Document", "*.txt"). + PromptForSingleSelection() +} + +func (s *FileService) SelectOutputFolder(app *application.App) (string, error) { + return app.Dialog.OpenFile(). + SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() +} +``` + +### Confirmation Dialog Example + +```go +func confirmDelete(app *application.App) { + dialog := app.Dialog.Question(). + SetTitle("Confirm Delete"). + SetMessage("Are you sure you want to delete this item?") + + deleteBtn := dialog.AddButton("Delete") + cancelBtn := dialog.AddButton("Cancel") + + cancelBtn.SetAsDefault() // Default to Cancel for safety + cancelBtn.SetAsCancel() + + deleteBtn.OnClick(func() { + deleteFromDatabase(id) + }) + + dialog.Show() +} +``` + +### Error Handling with Dialogs + +```go +func saveFile(app *application.App, data []byte) error { + // Select save location + path, err := app.Dialog.SaveFile(). + SetFilename("data.json"). + AddFilter("JSON File", "*.json"). + PromptForSingleSelection() + + if err != nil { + return err + } + + if path == "" { + // User cancelled - not an error + return nil + } + + // Attempt to save + err = os.WriteFile(path, data, 0644) + if err != nil { + app.Dialog.Error(). + SetTitle("Save Failed"). + SetMessage(fmt.Sprintf("Could not save file: %v", err)). + Show() + return err + } + + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("File saved successfully!"). + Show() + + return nil +} +``` + +## Best Practices + +### Do + +- **Use native dialogs** - They match the platform's look and feel +- **Provide clear titles** - Help users understand the purpose +- **Set appropriate filters** - Guide users to correct file types using `AddFilter()` +- **Handle empty results** - User cancellation returns an empty string (not an error) +- **Show confirmation for destructive actions** - Use question dialogs +- **Set sensible defaults** - Default directory, filename, etc. + +### Don't + +- **Don't hardcode paths** - Use `os.UserHomeDir()` or similar +- **Don't overuse dialogs** - They interrupt workflow +- **Don't use ambiguous button labels** - Be specific: "Save" vs "OK" diff --git a/docs/src/content/docs/reference/events.mdx b/docs/src/content/docs/reference/events.mdx new file mode 100644 index 000000000..fdb3485b9 --- /dev/null +++ b/docs/src/content/docs/reference/events.mdx @@ -0,0 +1,628 @@ +--- +title: Events API +description: Complete reference for the Events API +sidebar: + order: 4 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Overview + +The Events API provides methods to emit and listen to events, enabling communication between different parts of your application. Events are managed through the `app.Event` manager. + +**Event Types:** +- **Application Events** - App lifecycle events (startup, theme change) +- **Window Events** - Window state changes (focus, blur, resize) +- **Custom Events** - User-defined events for app-specific communication + +**Communication Patterns:** +- **Go to Frontend** - Emit events from Go, listen in JavaScript +- **Frontend to Go** - Not directly (use service bindings instead) +- **Frontend to Frontend** - Via Go or local runtime events +- **Window to Window** - Target specific windows or broadcast to all + +## Event Methods (Go) + +### app.Event.Emit() + +Emits a custom event to all listeners. + +```go +func (em *EventManager) Emit(name string, data ...any) bool +``` + +**Parameters:** +- `name` - Event name +- `data` - Optional data to send with the event + +**Returns:** `true` if the event was cancelled by a hook + +**Example:** +```go +// Emit simple event +app.Event.Emit("user-logged-in") + +// Emit with data +app.Event.Emit("data-updated", map[string]any{ + "count": 42, + "status": "success", +}) + +// Emit multiple values (received as slice) +app.Event.Emit("progress", 75, "Processing files...") +``` + +### app.Event.On() + +Listens for custom events in Go. Returns a cleanup function. + +```go +func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() +``` + +**Parameters:** +- `name` - Event name to listen for +- `callback` - Function called when event is emitted + +**Returns:** Cleanup function to remove the event listener + +**Example:** +```go +// Listen for events +cleanup := app.Event.On("user-action", func(e *application.CustomEvent) { + data := e.Data.(map[string]any) + action := data["action"].(string) + app.Logger.Info("User action", "action", action) +}) + +// Later, remove listener +cleanup() +``` + +### app.Event.OnMultiple() + +Listens for custom events a specified number of times. + +```go +func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) +``` + +### app.Event.Off() + +Removes all listeners for a custom event. + +```go +func (em *EventManager) Off(name string) +``` + +### app.Event.Reset() + +Removes all custom event listeners. + +```go +func (em *EventManager) Reset() +``` + +### Window-Specific Events + +Emit events from a specific window: + +```go +// Emit from specific window (sender field set to window name) +window.EmitEvent("notification", "Hello from Go!") + +// Emit to all listeners (no specific sender) +app.Event.Emit("global-update", data) +``` + +## Event Methods (Frontend) + +### Events.On() + +Listens for events from Go. + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On(eventName, callback) +``` + +**Parameters:** +- `eventName` - Name of the event to listen for +- `callback` - Function called with a `WailsEvent` object (`{ name, data, sender }`) + +**Returns:** Cleanup function + +**Example:** +```javascript +import { Events } from '@wailsio/runtime' + +// Listen for events +const cleanup = Events.On('data-updated', (event) => { + console.log('Count:', event.data.count) + console.log('Status:', event.data.status) + updateUI(event.data) +}) + +// Later, remove listener +cleanup() +``` + +### Events.Once() + +Listens for a single event occurrence. + +```javascript +import { Events } from '@wailsio/runtime' + +Events.Once(eventName, callback) +``` + +**Example:** +```javascript +import { Events } from '@wailsio/runtime' + +// Listen for first occurrence only +Events.Once('initialization-complete', (event) => { + console.log('App initialized!', event.data) + // This will only fire once +}) +``` + +### Events.Off() + +Removes event listeners for the specified event names. + +```javascript +import { Events } from '@wailsio/runtime' + +Events.Off(...eventNames) +``` + +**Example:** +```javascript +import { Events } from '@wailsio/runtime' + +Events.Off('my-event', 'other-event') +``` + +### Events.OffAll() + +Removes all event listeners. + +```javascript +import { Events } from '@wailsio/runtime' + +Events.OffAll() +``` + +## Application Events + +### app.Event.OnApplicationEvent() + +Listens for application lifecycle events. Returns a cleanup function. + +```go +func (em *EventManager) OnApplicationEvent( + eventType events.ApplicationEventType, + callback func(event *ApplicationEvent), +) func() +``` + +**Example:** +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Handle application startup +cancel := app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application started") +}) + +// Handle theme change +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + app.Logger.Info("Theme changed") +}) +``` + +### app.Event.RegisterApplicationEventHook() + +Registers an application event hook that can cancel the event. + +```go +func (em *EventManager) RegisterApplicationEventHook( + eventType events.ApplicationEventType, + callback func(event *ApplicationEvent), +) func() +``` + +## Window Events + +### window.OnWindowEvent() + +Listens for window-specific events. Returns a cleanup function. + +```go +func (w *WebviewWindow) OnWindowEvent( + eventType events.WindowEventType, + callback func(event *WindowEvent), +) func() +``` + +**Example:** +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// Handle window focus +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +// Handle window resize +window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEvent) { + width, height := window.Size() + app.Logger.Info("Window resized", "width", width, "height", height) +}) +``` + +### window.RegisterHook() + +Registers a hook that can cancel window events. + +```go +func (w *WebviewWindow) RegisterHook( + eventType events.WindowEventType, + callback func(event *WindowEvent), +) func() +``` + +**Example - Prevent window close:** +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { + dialog := app.Dialog.Question(). + SetTitle("Confirm Close"). + SetMessage("Close window?") + + yesBtn := dialog.AddButton("Yes") + noBtn := dialog.AddButton("No") + + noBtn.OnClick(func() { + e.Cancel() // Prevent close + }) + + dialog.Show() +}) +``` + +## Common Patterns + +### Progress Updates + +**Go:** + +```go +func (s *Service) ProcessFiles(files []string) error { + total := len(files) + + for i, file := range files { + processFile(file) + + // Emit progress event + app.Event.Emit("progress", map[string]any{ + "current": i + 1, + "total": total, + "percent": float64(i+1) / float64(total) * 100, + "file": file, + }) + } + + app.Event.Emit("processing-complete") + return nil +} +``` + +**JavaScript:** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On('progress', (event) => { + progressBar.style.width = `${event.data.percent}%` + statusText.textContent = `Processing ${event.data.file}... (${event.data.current}/${event.data.total})` +}) + +Events.Once('processing-complete', () => { + progressBar.style.width = '100%' + statusText.textContent = 'Complete!' +}) +``` + +### Multi-Window Communication + +**Go:** + +```go +// Broadcast to all windows via app-level event +app.Event.Emit("theme-changed", "dark") + +// Send from specific window (sender field identifies origin) +preferencesWindow.EmitEvent("settings-updated", settings) +``` + +**JavaScript:** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On('theme-changed', (event) => { + document.body.className = event.data +}) +``` + +### State Synchronization + +**Go:** + +```go +type StateService struct { + app *application.App + state map[string]any + mu sync.RWMutex +} + +func (s *StateService) UpdateState(key string, value any) { + s.mu.Lock() + s.state[key] = value + s.mu.Unlock() + + // Notify all listeners + s.app.Event.Emit("state-updated", map[string]any{ + "key": key, + "value": value, + }) +} +``` + +**JavaScript:** + +```javascript +import { Events } from '@wailsio/runtime' + +Events.On('state-updated', (event) => { + localState[event.data.key] = event.data.value + updateUI(event.data.key, event.data.value) +}) +``` + +## Built-in Events + +Wails provides built-in system events for application and window lifecycle. These events are emitted automatically by the framework. + +### Common Events vs Platform-Native Events + +Wails provides two types of system events: + +**Common Events** (`events.Common.*`) are cross-platform abstractions that work consistently across macOS, Windows, and Linux. These are the events you should use in your application for maximum portability. + +**Platform-Native Events** (`events.Mac.*`, `events.Windows.*`, `events.Linux.*`) are the underlying OS-specific events. These provide access to platform-specific behaviors and edge cases. + +**How They Work:** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +// RECOMMENDED: Use Common Events for cross-platform code +window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) { + // This works on all platforms +}) + +// Platform-specific events for advanced use cases +window.OnWindowEvent(events.Mac.WindowWillClose, func(e *application.WindowEvent) { + // macOS-specific "will close" event +}) +``` + +**Event Mapping:** + +Platform-native events are automatically mapped to Common Events: + +- macOS: `events.Mac.WindowShouldClose` maps to `events.Common.WindowClosing` +- Windows: `events.Windows.WindowClosing` maps to `events.Common.WindowClosing` +- Linux: `events.Linux.WindowDeleteEvent` maps to `events.Common.WindowClosing` + +### Application Events + +| Event | Description | +|-------|-------------| +| `events.Common.ApplicationOpenedWithFile` | App launched with a file | +| `events.Common.ApplicationStarted` | App initialization complete | +| `events.Common.ApplicationLaunchedWithUrl` | App launched via URL scheme | +| `events.Common.ThemeChanged` | System theme changed | + +**Usage:** + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application ready!") +}) + +app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { + // Update app theme +}) +``` + +### Window Events + +| Event | Description | Cancellable | +|-------|-------------|-------------| +| `WindowClosing` | Window is about to close | Yes | +| `WindowDidMove` | Window moved | No | +| `WindowDidResize` | Window was resized | No | +| `WindowDPIChanged` | DPI scaling changed | No | +| `WindowFilesDropped` | Files dropped onto window | No | +| `WindowFocus` | Window gained focus | No | +| `WindowFullscreen` | Entered fullscreen | No | +| `WindowHide` | Window was hidden | No | +| `WindowLostFocus` | Window lost focus | No | +| `WindowMaximise` | Window maximized | Yes (macOS) | +| `WindowMinimise` | Window minimized | Yes (macOS) | +| `WindowToggleFrameless` | Frameless mode toggled | No | +| `WindowRestore` | Restored from min/max | No | +| `WindowRuntimeReady` | Wails runtime loaded | No | +| `WindowShow` | Window became visible | No | +| `WindowUnFullscreen` | Exited fullscreen | No | +| `WindowUnMaximise` | Exited maximized state | Yes (macOS) | +| `WindowUnMinimise` | Exited minimized state | Yes (macOS) | +| `WindowZoom` | Zoom level changed | No | +| `WindowZoomIn` | Zoom increased | Yes (macOS) | +| `WindowZoomOut` | Zoom decreased | Yes (macOS) | +| `WindowZoomReset` | Zoom reset to 100% | Yes (macOS) | + +All window events are accessed via `events.Common.*`: + +```go +import "github.com/wailsapp/wails/v3/pkg/events" + +window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + app.Logger.Info("Window focused") +}) + +// Wait for runtime ready before emitting events to frontend +window.OnWindowEvent(events.Common.WindowRuntimeReady, func(e *application.WindowEvent) { + app.Logger.Info("Runtime ready, safe to emit events to frontend") + window.EmitEvent("app-initialized", data) +}) +``` + +**Important Notes:** + +- **WindowRuntimeReady** is critical - wait for this event before emitting events to the frontend +- **WindowDidMove** and **WindowDidResize** are debounced to prevent event flooding +- **Cancellable events** can be prevented by calling `event.Cancel()` in a `RegisterHook()` handler + +## Complete Example + +**Go:** + +```go +package main + +import ( + "time" + + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +type EventDemoService struct { + app *application.App +} + +func (s *EventDemoService) StartLongTask() { + go func() { + s.app.Event.Emit("task-started") + + for i := 1; i <= 10; i++ { + time.Sleep(500 * time.Millisecond) + + s.app.Event.Emit("task-progress", map[string]any{ + "step": i, + "total": 10, + "percent": i * 10, + }) + } + + s.app.Event.Emit("task-completed", map[string]any{ + "message": "Task finished successfully!", + }) + }() +} + +func (s *EventDemoService) BroadcastMessage(message string) { + s.app.Event.Emit("broadcast", message) +} + +func main() { + app := application.New(application.Options{ + Name: "Event Demo", + Services: []application.Service{ + application.NewService(&EventDemoService{}), + }, + }) + + // Handle application lifecycle + app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) { + app.Logger.Info("Application started!") + }) + + // Listen for custom events + app.Event.On("user-action", func(e *application.CustomEvent) { + app.Logger.Info("User action received", "data", e.Data) + }) + + // Create window + window := app.Window.New() + + // Handle window events + window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) { + window.EmitEvent("window-state", "focused") + }) + + window.OnWindowEvent(events.Common.WindowLostFocus, func(e *application.WindowEvent) { + window.EmitEvent("window-state", "blurred") + }) + + err := app.Run() + if err != nil { + panic(err) + } +} +``` + +**JavaScript:** + +```javascript +import { Events } from '@wailsio/runtime' +import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService' + +// Task events +Events.On('task-started', () => { + document.getElementById('status').textContent = 'Running...' +}) + +Events.On('task-progress', (event) => { + const progressBar = document.getElementById('progress') + progressBar.style.width = `${event.data.percent}%` +}) + +Events.Once('task-completed', (event) => { + document.getElementById('status').textContent = event.data.message +}) + +// Window state events +Events.On('window-state', (event) => { + document.body.dataset.windowState = event.data +}) + +// Trigger long task +document.getElementById('startTask').addEventListener('click', async () => { + await StartLongTask() +}) +``` + +## Event Naming Conventions + +```go +// Good - descriptive and specific +app.Event.Emit("user:logged-in", user) +app.Event.Emit("data:fetch:complete", results) +app.Event.Emit("ui:theme:changed", theme) + +// Bad - vague and unclear +app.Event.Emit("event1", data) +app.Event.Emit("update", stuff) +``` diff --git a/docs/src/content/docs/reference/frontend-runtime.mdx b/docs/src/content/docs/reference/frontend-runtime.mdx new file mode 100644 index 000000000..ef9d13c9c --- /dev/null +++ b/docs/src/content/docs/reference/frontend-runtime.mdx @@ -0,0 +1,898 @@ +--- +title: Frontend Runtime +description: The Wails JavaScript runtime package for frontend integration +sidebar: + order: 1 +--- + +The Wails frontend runtime is the standard library for Wails applications. It provides a +number of features that may be used in your applications, including: + +- Window management +- Dialogs +- Browser integration +- Clipboard +- Menus +- System information +- Events +- Context Menus +- Screens +- WML (Wails Markup Language) + +The runtime is required for integration between Go and the frontend. There are 2 +ways to integrate the runtime: + +- Using the `@wailsio/runtime` package +- Using a pre-built bundle + +## Using the npm package + +The `@wailsio/runtime` package is a JavaScript package that provides access to +the Wails runtime from the frontend. It is used by all standard templates +and is the recommended way to integrate the runtime into your application. +By using the `@wailsio/runtime` package, you will only include the parts of the runtime that you use. + +The package is available on npm and can be installed using: + +```shell +npm install --save @wailsio/runtime +``` + +## Using a pre-built bundle + +Some projects will not use a Javascript bundler and may prefer to use a +pre-built bundled version of the runtime. This version can be generated locally +using the following command: + +```shell +wails3 generate runtime +``` + +The command will output a `runtime.js` (and `runtime.debug.js`) file in the current +directory. This file is an ES module that can be imported by your application scripts +just like the npm package, but the API is also exported to the global window object, +so for simpler applications you can use it as follows: + +```html + + + + + + + +``` + +:::caution +It is important to include the `type="module"` attribute on the ` + + + ``` + + Run `wails3 dev` to start the dev server. After a few seconds, the application should open. + + Type in some text and click the "Generate QR Code" button. You should see a QR code in the center of the page: + + QR Code + +
+
+ +7. ## Alternative Approach: HTTP Handler + + So far, we have covered the following areas: + - Creating a new Service + - Generating Bindings + - Using the Bindings in our Frontend code + + **Why use an HTTP handler?** + + Method bindings work great for data operations, but there's an alternative approach for serving files, images, or other media. Instead of converting everything to base64 and sending it through bindings, you can make your service act like a mini web server. + + This is useful when: + - You're serving images, videos, or large files + - You want to use standard HTML `` or `