From 70064be7de8ea6ce47abb1a7b97965fd1467816d Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Wed, 21 Jan 2026 18:49:54 +0100 Subject: [PATCH] - Use browserless over gotenberg - Implement functionality to move items between sections or pages - Enhance custom sections to have a `type` property - Update the v4 importer to account for custom sections - Update healthcheck to be a simple curl command - Update dependencies to latest and a lot more changes --- .env.example | 9 +- .github/workflows/docker-build.yml | 59 +- Dockerfile | 6 + README.md | 2 +- compose.dev.yml | 25 +- compose.yml | 9 +- docs/contributing/architecture.mdx | 2 +- docs/contributing/development.mdx | 6 +- docs/getting-started/quickstart.mdx | 17 +- docs/legal/privacy-policy.mdx | 6 +- docs/legal/terms-of-service.mdx | 2 +- docs/self-hosting/docker.mdx | 61 +- docs/self-hosting/examples.mdx | 73 +- package.json | 13 +- pnpm-lock.yaml | 698 ++++++++++++++---- .../animate-ui/components/buttons/button.tsx | 5 +- .../primitives/radix/alert-dialog.tsx | 4 +- .../animate-ui/primitives/radix/dialog.tsx | 4 +- .../resume/shared/get-section-component.tsx | 81 +- src/components/resume/shared/page-custom.tsx | 34 - src/components/resume/templates/azurill.tsx | 2 +- src/components/resume/templates/bronzor.tsx | 2 +- src/components/resume/templates/kakuna.tsx | 2 +- src/components/resume/templates/onyx.tsx | 2 +- src/components/resume/templates/pikachu.tsx | 2 +- src/components/resume/templates/rhyhorn.tsx | 2 +- src/components/ui/input-group.tsx | 2 +- src/components/ui/input.tsx | 2 +- src/dialogs/resume/sections/award.tsx | 49 +- src/dialogs/resume/sections/certification.tsx | 49 +- src/dialogs/resume/sections/custom.tsx | 56 +- src/dialogs/resume/sections/education.tsx | 61 +- src/dialogs/resume/sections/experience.tsx | 53 +- src/dialogs/resume/sections/interest.tsx | 41 +- src/dialogs/resume/sections/language.tsx | 41 +- src/dialogs/resume/sections/profile.tsx | 45 +- src/dialogs/resume/sections/project.tsx | 45 +- src/dialogs/resume/sections/publication.tsx | 49 +- src/dialogs/resume/sections/reference.tsx | 37 +- src/dialogs/resume/sections/skill.tsx | 49 +- src/dialogs/resume/sections/volunteer.tsx | 49 +- src/dialogs/store.ts | 120 ++- .../import/reactive-resume-v4-json.tsx | 65 +- src/integrations/orpc/router/printer.ts | 15 +- src/integrations/orpc/services/printer.ts | 210 +++--- src/routes/api/health.ts | 15 + .../builder/$resumeId/-components/dock.tsx | 13 +- .../-sidebar/left/sections/custom.tsx | 258 ++++--- .../-sidebar/left/shared/section-item.tsx | 204 ++++- .../-sidebar/right/sections/layout/pages.tsx | 4 +- src/schema/resume/data.ts | 41 +- src/styles/globals.css | 1 - src/utils/env.ts | 4 +- src/utils/resume/move-item.ts | 269 +++++++ 54 files changed, 2153 insertions(+), 822 deletions(-) delete mode 100644 src/components/resume/shared/page-custom.tsx create mode 100644 src/utils/resume/move-item.ts diff --git a/.env.example b/.env.example index 56de618f4..1ec281313 100644 --- a/.env.example +++ b/.env.example @@ -10,14 +10,7 @@ PRINTER_APP_URL="http://host.docker.internal:3000" # Note: set this to `http://host.docker.internal:3000` if your `gotenberg` service is running on docker, but Reactive Resume is running outside of Docker. # --- Printer --- -GOTENBERG_ENDPOINT="http://localhost:4000" - -# Gotenberg Authentication (Optional) -# TIP: It's safest to avoid exposing your Gotenberg instance to the public internet; connect via a private Docker network instead. -# However, if Gotenberg needs to be hosted remotely from Reactive Resume, use these credentials to enable basic authentication for security. -# For setup details and security best practices, see: https://gotenberg.dev/docs/configuration#api:~:text=API%5FENABLE%5FBASIC%5FAUTH -# GOTENBERG_USERNAME="" -# GOTENBERG_PASSWORD="" +PRINTER_ENDPOINT="ws://localhost:4000?token=1234567890" # --- Database (PostgreSQL) --- DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index a01510288..58a85931b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -66,8 +66,13 @@ jobs: ghcr.io/${{ env.IMAGE }} docker.io/${{ env.IMAGE }} tags: | - type=sha,format=short,suffix=-${{ matrix.arch }} type=raw,value=v${{ steps.version.outputs.version }}-${{ matrix.arch }} + type=sha,prefix=sha-,suffix=-${{ matrix.arch }} + labels: | + org.opencontainers.image.title=Reactive Resume + org.opencontainers.image.description=A free and open-source resume builder. + org.opencontainers.image.vendor=Amruth Pillai + org.opencontainers.image.version=${{ steps.version.outputs.version }} - name: Build and Push by Digest id: build @@ -110,7 +115,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: sparse-checkout: package.json sparse-checkout-cone-mode: false @@ -142,6 +147,23 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Parse version components + id: semver + run: | + VERSION="${{ steps.version.outputs.version }}" + MAJOR=$(echo "$VERSION" | cut -d. -f1) + MINOR=$(echo "$VERSION" | cut -d. -f2) + + echo "major=$MAJOR" >> "$GITHUB_OUTPUT" + echo "minor=$MINOR" >> "$GITHUB_OUTPUT" + + # Check if stable release (no pre-release identifiers) + if [[ "$VERSION" =~ (alpha|beta|rc|dev|canary) ]]; then + echo "is_stable=false" >> "$GITHUB_OUTPUT" + else + echo "is_stable=true" >> "$GITHUB_OUTPUT" + fi + - name: Extract metadata for Docker id: meta uses: docker/metadata-action@v5 @@ -150,11 +172,19 @@ jobs: ghcr.io/${{ env.IMAGE }} docker.io/${{ env.IMAGE }} tags: | - type=sha,format=short - type=raw,value=latest + type=raw,value=latest,enable=${{ steps.semver.outputs.is_stable }} type=raw,value=v${{ steps.version.outputs.version }} + type=raw,value=v${{ steps.semver.outputs.major }}.${{ steps.semver.outputs.minor }},enable=${{ steps.semver.outputs.is_stable }} + type=raw,value=v${{ steps.semver.outputs.major }},enable=${{ steps.semver.outputs.is_stable }} + type=sha,prefix=sha- + labels: | + org.opencontainers.image.title=Reactive Resume + org.opencontainers.image.description=A free and open-source resume builder. + org.opencontainers.image.vendor=Amruth Pillai + org.opencontainers.image.version=${{ steps.version.outputs.version }} - name: Create manifest list and push + id: manifest working-directory: /tmp/digests run: | set -euo pipefail @@ -162,13 +192,30 @@ jobs: $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf 'ghcr.io/${{ env.IMAGE }}@sha256:%s ' *) \ $(printf 'docker.io/${{ env.IMAGE }}@sha256:%s ' *) + + # Get the digest of the multi-arch manifest + GHCR_DIGEST=$(docker buildx imagetools inspect ghcr.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }} --format '{{json .Manifest.Digest}}' | tr -d '"') + DOCKER_DIGEST=$(docker buildx imagetools inspect docker.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }} --format '{{json .Manifest.Digest}}' | tr -d '"') + echo "ghcr_digest=$GHCR_DIGEST" >> "$GITHUB_OUTPUT" + echo "docker_digest=$DOCKER_DIGEST" >> "$GITHUB_OUTPUT" + + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + + - name: Sign images with Cosign + run: | + # Sign GHCR image + cosign sign --yes ghcr.io/${{ env.IMAGE }}@${{ steps.manifest.outputs.ghcr_digest }} + + # Sign Docker Hub image + cosign sign --yes docker.io/${{ env.IMAGE }}@${{ steps.manifest.outputs.docker_digest }} - name: Inspect image run: | - docker buildx imagetools inspect ghcr.io/${{ env.IMAGE }}:latest + docker buildx imagetools inspect ghcr.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }} + docker buildx imagetools inspect docker.io/${{ env.IMAGE }}:v${{ steps.version.outputs.version }} - name: Redeploy Service - if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: appleboy/ssh-action@v1 with: key: ${{ secrets.SSH_KEY }} diff --git a/Dockerfile b/Dockerfile index 24d851ab3..a10a0c504 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,9 @@ RUN pnpm run build # ---------- Runtime Layer ---------- FROM node:24-slim AS runtime +RUN apt-get update && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* + WORKDIR /app ENV NODE_ENV=production @@ -45,4 +48,7 @@ COPY --from=dependencies /tmp/prod/node_modules ./node_modules EXPOSE 3000/tcp +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:3000/api/health || exit 1 + ENTRYPOINT ["node", "-r", "reflect-metadata", ".output/server/index.mjs"] diff --git a/README.md b/README.md index e409b11b0..dbf3f29e0 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ Comprehensive guides are available at [docs.rxresu.me](https://docs.rxresu.me): Reactive Resume can be self-hosted using Docker. The stack includes: - **PostgreSQL** — Database for storing user data and resumes -- **Gotenberg** — Headless Chrome service for PDF generation +- **Printer** — Headless Chromium service for PDF and screenshot generation - **SeaweedFS** (optional) — S3-compatible storage for file uploads Pull the latest image from Docker Hub or GitHub Container Registry: diff --git a/compose.dev.yml b/compose.dev.yml index 30bc57a70..4e6ac5bd1 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -19,25 +19,20 @@ services: timeout: 10s retries: 3 - gotenberg: - image: gotenberg/gotenberg:8 + browserless: + image: ghcr.io/browserless/chromium:latest restart: unless-stopped + ports: + - "4000:3000" extra_hosts: - "host.docker.internal:host-gateway" environment: - - WEBHOOK_DISABLE=true - - CHROMIUM_AUTO_START=true - - LIBREOFFICE_DISABLE_ROUTES=true - - PROMETHEUS_DISABLE_COLLECT=true - - CHROMIUM_HOST_RESOLVER_RULES=MAP localhost host.docker.internal - ports: - - "4000:3000" - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] - start_period: 10s - interval: 30s - timeout: 10s - retries: 3 + - HEALTH=true + - MAX_CPU_PERCENT=90 + - MAX_MEMORY_PERCENT=90 + - QUEUED=5 + - CONCURRENT=5 + - TOKEN=1234567890 seaweedfs: image: chrislusf/seaweedfs:latest diff --git a/compose.yml b/compose.yml index d22d92eb4..4bf2306fb 100644 --- a/compose.yml +++ b/compose.yml @@ -100,9 +100,12 @@ services: test: [ "CMD", - "node", - "-e", - "fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))", + "curl", + "-f", + "http://localhost:3000/api/health", + "||", + "exit", + "1", ] start_period: 10s interval: 30s diff --git a/docs/contributing/architecture.mdx b/docs/contributing/architecture.mdx index 2e8d86d0a..4bf8efe77 100644 --- a/docs/contributing/architecture.mdx +++ b/docs/contributing/architecture.mdx @@ -49,7 +49,7 @@ flowchart TD Database["PostgreSQL"] Storage["File System OR S3-Compatible Storage"] - Printer["Gotenberg"] + Printer["Printer (Browserless/Chromium)"] Router --> ORPCClient Query --> ORPCClient diff --git a/docs/contributing/development.mdx b/docs/contributing/development.mdx index 20bbb0e5b..c9e09973a 100644 --- a/docs/contributing/development.mdx +++ b/docs/contributing/development.mdx @@ -47,7 +47,7 @@ This guide walks you through setting up Reactive Resume for local development. W This starts the following infrastructure services: - **PostgreSQL** — Database (port 5432) - **SeaweedFS** — S3-compatible storage (port 8333) - - **Gotenberg** — PDF generation service (port 4000) + - **Printer** — PDF and screenshot generation service (port 4000) - **Mailpit** — Email testing server (SMTP on port 1025, UI on port 8025) @@ -88,7 +88,7 @@ This guide walks you through setting up Reactive Resume for local development. W ``` - **PDF Generation Note**: The `PRINTER_APP_URL` variable is required when running Reactive Resume outside of Docker while the Gotenberg PDF service is running inside Docker (which is the case when using `compose.dev.yml`). Gotenberg needs to reach your local app to render resumes for PDF generation. Since Docker containers cannot access `localhost` on your host machine directly, you must set `PRINTER_APP_URL` to `http://host.docker.internal:3000`. This special hostname allows Docker containers to communicate with services running on your host machine. + **PDF Generation Note**: The `PRINTER_APP_URL` variable is required when running Reactive Resume outside of Docker while the printer service is running inside Docker (which is the case when using `compose.dev.yml`). The printer needs to reach your local app to render resumes for PDF generation. Since Docker containers cannot access `localhost` on your host machine directly, you must set `PRINTER_APP_URL` to `http://host.docker.internal:3000`. This special hostname allows Docker containers to communicate with services running on your host machine. @@ -329,7 +329,7 @@ pnpm run typecheck **Why this happens:** - The app running on your host uploads images to SeaweedFS (S3 storage) using `localhost:8333`. When Gotenberg tries to fetch these images to render the PDF, it cannot resolve `localhost` the same way your host machine does — they're on different networks. + The app running on your host uploads images to SeaweedFS (S3 storage) using `localhost:8333`. When the printer tries to fetch these images to render the PDF, it cannot resolve `localhost` the same way your host machine does — they're on different networks. **Solutions:** diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx index 0803f491a..b0afdc35d 100644 --- a/docs/getting-started/quickstart.mdx +++ b/docs/getting-started/quickstart.mdx @@ -112,7 +112,7 @@ Before you begin, ensure you have the following installed: This will start: - **PostgreSQL** — Database for storing user data and resumes - **SeaweedFS** — S3-compatible storage for file uploads - - **Gotenberg** — PDF generation service + - **Printer** — PDF and screenshot generation service (browserless/chromium) - **Reactive Resume** — The main application @@ -133,7 +133,7 @@ Here's what each service in the stack does: |---------|------|-------------| | `postgres` | 5432 | PostgreSQL database for storing all application data | | `seaweedfs` | 8333 | S3-compatible object storage for file uploads | -| `gotenberg` | 4000 | Headless Chrome service for PDF generation | +| `printer` | 4000 | Headless Chromium service for PDF and screenshot generation | | `app` | 3000 | The main Reactive Resume application | ### Health Checks @@ -159,15 +159,14 @@ Here's a complete list of environment variables you can configure: | `DATABASE_URL` | PostgreSQL connection string | `postgresql://user:pass@host:5432/db` | | `AUTH_SECRET` | Secret key for authentication | Generate with `openssl rand -base64 32` | | `APP_URL` | Public URL of your Application | `https://rxresu.me` | -| `GOTENBERG_ENDPOINT` | URL of the Gotenberg PDF service | `http://gotenberg:4000` | +| `PRINTER_ENDPOINT` | URL of the printer service | `http://printer:3000` | ### Optional Variables | Variable | Description | Default | |----------|-------------|---------| -| `PRINTER_APP_URL` | Public URL for Gotenberg to access the Application | — | -| `GOTENBERG_USERNAME` | Gotenberg Basic Auth Username (if enabled) | — | -| `GOTENBERG_PASSWORD` | Gotenberg Basic Auth Password (if enabled) | — | +| `PRINTER_APP_URL` | Public URL for the printer to access the Application | — | +| `PRINTER_TOKEN` | Printer authentication token (if using browserless with token auth) | — | | `GOOGLE_CLIENT_ID` | Google OAuth Client ID | — | | `GOOGLE_CLIENT_SECRET` | Google OAuth Client Secret | — | | `GITHUB_CLIENT_ID` | GitHub OAuth Client ID | — | @@ -198,7 +197,11 @@ Here's a complete list of environment variables you can configure: > **Note:** Some variables are only required for using related features (OAuth, SMTP, S3, etc.) and can be left unset if unused. - **Hybrid Setup Note**: The `PRINTER_APP_URL` variable is required when running Reactive Resume outside of Docker while the Gotenberg PDF service is running inside Docker. In this scenario, Gotenberg needs to reach your local app to render resumes for PDF generation. Since Docker containers cannot access `localhost` on your host machine directly, you must set `PRINTER_APP_URL` to `http://host.docker.internal:3000`. This special hostname allows Docker containers to communicate with services running on your host machine. + **Hybrid Setup Note**: The `PRINTER_APP_URL` variable is required when running Reactive Resume outside of Docker while the printer service is running inside Docker. In this scenario, the printer needs to reach your local app to render resumes for PDF generation. Since Docker containers cannot access `localhost` on your host machine directly, you must set `PRINTER_APP_URL` to `http://host.docker.internal:3000`. This special hostname allows Docker containers to communicate with services running on your host machine. + + + + **Alternative Printer Options**: If you don't want to use browserless, you can use any headless Chrome/Chromium instance with its remote debugging port open. For example, run `chromium --remote-debugging-port=9222` and point `PRINTER_ENDPOINT` to that instance. --- diff --git a/docs/legal/privacy-policy.mdx b/docs/legal/privacy-policy.mdx index d1e3bd128..f9be5836f 100644 --- a/docs/legal/privacy-policy.mdx +++ b/docs/legal/privacy-policy.mdx @@ -120,10 +120,10 @@ The Service does not include built-in behavioral advertising or third-party anal We share information only as needed to provide the Service: -### PDF generation and screenshots (Gotenberg) -When you export to PDF or request a screenshot, the Service sends a request to a configured **Gotenberg** endpoint to render a resume URL. +### PDF generation and screenshots (Printer) +When you export to PDF or request a screenshot, the Service sends a request to a configured printer endpoint (a headless Chromium browser) to render a resume URL. -Depending on your deployment, Gotenberg may be: +Depending on your deployment, the printer may be: - Self-hosted and controlled by the Service Operator, or - Operated by a third party (in which case the third party will process the resume content for rendering) diff --git a/docs/legal/terms-of-service.mdx b/docs/legal/terms-of-service.mdx index 5299eb93e..8520ba01b 100644 --- a/docs/legal/terms-of-service.mdx +++ b/docs/legal/terms-of-service.mdx @@ -84,7 +84,7 @@ You represent that you have the rights necessary to upload and use any files and ## Exports (PDF) and Screenshots -When you request a PDF export or screenshot, the Service may use a rendering service (commonly **Gotenberg**) to load a resume URL and generate the output. +When you request a PDF export or screenshot, the Service uses a printer service (a headless Chromium browser) to load a resume URL and generate the output. Depending on the deployment, this rendering service may be operated by the Service Operator or a third party. You understand that resume content must be processed for rendering in order to provide the export/screenshot functionality. diff --git a/docs/self-hosting/docker.mdx b/docs/self-hosting/docker.mdx index 6261b7387..3ab6d8cf7 100644 --- a/docs/self-hosting/docker.mdx +++ b/docs/self-hosting/docker.mdx @@ -1,6 +1,6 @@ --- title: "Self-Hosting with Docker" -description: "A comprehensive guide to self-host Reactive Resume with Docker (Postgres + Gotenberg), including a detailed environment variable reference and troubleshooting tips." +description: "A comprehensive guide to self-host Reactive Resume with Docker (Postgres + Printer), including a detailed environment variable reference and troubleshooting tips." --- ## Overview @@ -11,8 +11,8 @@ Reactive Resume can be self-hosted using Docker in a matter of minutes, and this Stores accounts, resumes, and application data. - - Generates PDFs by rendering a special print route. + + Generates PDFs and screenshots using a headless Chromium browser. SMTP for verification emails, password reset, etc. If not configured, emails are logged to the server console. @@ -34,7 +34,7 @@ You can pull the latest app image from: Docker Engine + Docker Compose plugin (or Docker Desktop). - 2 vCPU / 2 GB RAM minimum (4 GB recommended if Postgres + Gotenberg run on the same host). + 2 vCPU / 2 GB RAM minimum (4 GB recommended if Postgres + Printer run on the same host). Enough for Postgres + uploads (start with 10-20 GB and scale as needed). @@ -64,11 +64,11 @@ APP_URL="http://localhost:3000" PRINTER_APP_URL="http://host.docker.internal:3000" # --- Printer --- -GOTENBERG_ENDPOINT="http://gotenberg:3000" +PRINTER_ENDPOINT="http://printer:3000" -# Gotenberg Authentication (Optional) -# GOTENBERG_USERNAME="" -# GOTENBERG_PASSWORD="" +# Printer Authentication (Optional) +# If using browserless with a token, set PRINTER_TOKEN +# PRINTER_TOKEN="" # --- Database (PostgreSQL) --- DATABASE_URL="postgresql://postgres:postgres@postgres:5432/postgres" @@ -150,7 +150,7 @@ FLAG_DISABLE_SIGNUP="false" - This setup runs Postgres + Gotenberg + Reactive Resume on a private Docker network. + This setup runs Postgres + Printer + Reactive Resume on a private Docker network. @@ -171,18 +171,21 @@ services: timeout: 5s retries: 10 - gotenberg: - image: gotenberg/gotenberg:edge + printer: + image: ghcr.io/browserless/chromium:latest restart: unless-stopped ports: - "4000:3000" extra_hosts: - "host.docker.internal:host-gateway" environment: - - CHROMIUM_AUTO_START=true - - LIBREOFFICE_DISABLE_ROUTES=true + - TIMEOUT=120000 + - CONCURRENT=10 + - HEALTH=true + # Optional: Set a token for authentication + # - TOKEN=your-secret-token healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + test: ["CMD", "curl", "-f", "http://localhost:3000/"] interval: 10s timeout: 5s retries: 10 @@ -201,10 +204,10 @@ services: depends_on: postgres: condition: service_healthy - gotenberg: + printer: condition: service_healthy healthcheck: - test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\""] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"] interval: 30s timeout: 10s retries: 3 @@ -215,6 +218,10 @@ volumes: + + **Alternative Printer Options**: If you don't want to use browserless, you can also use any headless Chrome/Chromium instance with its remote debugging port open. For example, you could run `chromium --remote-debugging-port=9222` and point `PRINTER_ENDPOINT` to that instance. + + Prefer pulling from Docker Hub? Keep amruthpillai/reactive-resume:latest. Prefer GHCR? Swap it to ghcr.io/amruthpillai/reactive-resume:latest. @@ -254,7 +261,7 @@ docker compose logs -f reactive-resume
  • APP_URL
  • DATABASE_URL
  • -
  • GOTENBERG_ENDPOINT
  • +
  • PRINTER_ENDPOINT
  • AUTH_SECRET
@@ -272,12 +279,16 @@ docker compose logs -f reactive-resume - **`TZ`**: Sets the container timezone (affects logs and server-side timestamps). Recommended: `Etc/UTC`. - **`APP_URL`**: Canonical/public URL for your instance (used for absolute URLs, redirects, and auth flows). If behind a reverse proxy, set this to your public HTTPS URL (for example, `https://resume.example.com`). - - **`PRINTER_APP_URL`** (optional): Overrides the base URL used when rendering the print route for Gotenberg. Defaults to `APP_URL`. Useful when Gotenberg must access the app via a different internal URL (for example, `http://host.docker.internal:3000`). + - **`PRINTER_APP_URL`** (optional): Overrides the base URL used when rendering the print route for the printer. Defaults to `APP_URL`. Useful when the printer must access the app via a different internal URL (for example, `http://host.docker.internal:3000`). - - - **`GOTENBERG_ENDPOINT`**: Base URL where Reactive Resume reaches Gotenberg. In Compose: `http://gotenberg:3000`. - - **`GOTENBERG_USERNAME`** / **`GOTENBERG_PASSWORD`** (optional): Use if Gotenberg is configured with Basic Auth (recommended only if Gotenberg is reachable outside your private network). + + - **`PRINTER_ENDPOINT`**: Base URL where Reactive Resume reaches the printer service. In Compose: `http://printer:3000`. + - **`PRINTER_TOKEN`** (optional): Authentication token for the printer service. Required if your browserless instance is configured with token authentication. + + + **Alternative to browserless**: You can use any headless Chrome/Chromium instance with its remote debugging port open. For example, run `chromium --remote-debugging-port=9222` and set `PRINTER_ENDPOINT` to point to that instance. + @@ -386,7 +397,7 @@ The Docker Compose configuration includes a health check that periodically calls ```yaml healthcheck: - test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\""] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"] interval: 30s timeout: 10s retries: 3 @@ -439,10 +450,10 @@ A healthy response returns HTTP 200. Any other response (or a connection failure - - **Common cause**: Reactive Resume can't reach Gotenberg or Gotenberg can't reach your app. + - **Common cause**: Reactive Resume can't reach the printer or the printer can't reach your app. - **Checks**: - - `GOTENBERG_ENDPOINT` should usually be `http://gotenberg:3000` in Compose. - - If you use `PRINTER_APP_URL="http://host.docker.internal:3000"`, ensure `extra_hosts: host-gateway` is present for Gotenberg. + - `PRINTER_ENDPOINT` should usually be `http://printer:3000` in Compose. + - If you use `PRINTER_APP_URL="http://host.docker.internal:3000"`, ensure `extra_hosts: host-gateway` is present for the printer service. diff --git a/docs/self-hosting/examples.mdx b/docs/self-hosting/examples.mdx index 632feab85..d5efecf2c 100644 --- a/docs/self-hosting/examples.mdx +++ b/docs/self-hosting/examples.mdx @@ -17,7 +17,7 @@ These examples go beyond the basic setup in the [Self-Hosting with Docker](/self ## Docker with Traefik -This example uses [Traefik](https://traefik.io/) as a reverse proxy with automatic SSL certificate management via Let's Encrypt. Only the Reactive Resume app is exposed through Traefik—Postgres and Gotenberg remain on an internal network. +This example uses [Traefik](https://traefik.io/) as a reverse proxy with automatic SSL certificate management via Let's Encrypt. Only the Reactive Resume app is exposed through Traefik—Postgres and the printer remain on an internal network. Traefik automatically discovers services via Docker labels and handles SSL certificates, making it ideal for setups where you want minimal configuration. @@ -75,19 +75,19 @@ services: timeout: 5s retries: 5 - gotenberg: - image: gotenberg/gotenberg:8 + printer: + image: ghcr.io/browserless/chromium:latest restart: unless-stopped - command: - - "gotenberg" - - "--chromium-auto-start" - - "--api-timeout=120s" + environment: + - TIMEOUT=120000 + - CONCURRENT=10 + - HEALTH=true networks: - reactive_resume_network extra_hosts: - "host.docker.internal:host-gateway" healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + test: ["CMD", "curl", "-f", "http://localhost:3000/"] interval: 30s timeout: 10s retries: 3 @@ -99,7 +99,7 @@ services: - APP_URL=https://resume.${DOMAIN} - PRINTER_APP_URL=http://app:3000 - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres - - GOTENBERG_ENDPOINT=http://gotenberg:3000 + - PRINTER_ENDPOINT=http://printer:3000 - AUTH_SECRET=${AUTH_SECRET} # Add other optional env vars as needed (SMTP, S3, OAuth, etc.) volumes: @@ -109,7 +109,7 @@ services: depends_on: postgres: condition: service_healthy - gotenberg: + printer: condition: service_healthy labels: - "traefik.enable=true" @@ -118,7 +118,7 @@ services: - "traefik.http.routers.reactive-resume.tls.certresolver=letsencrypt" - "traefik.http.services.reactive-resume.loadbalancer.server.port=3000" healthcheck: - test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\""] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"] interval: 30s timeout: 10s retries: 3 @@ -183,19 +183,19 @@ services: timeout: 5s retries: 5 - gotenberg: - image: gotenberg/gotenberg:8 + printer: + image: ghcr.io/browserless/chromium:latest restart: unless-stopped - command: - - "gotenberg" - - "--chromium-auto-start" - - "--api-timeout=120s" + environment: + - TIMEOUT=120000 + - CONCURRENT=10 + - HEALTH=true networks: - reactive_resume_network extra_hosts: - "host.docker.internal:host-gateway" healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + test: ["CMD", "curl", "-f", "http://localhost:3000/"] interval: 30s timeout: 10s retries: 3 @@ -207,7 +207,7 @@ services: - APP_URL=https://resume.${DOMAIN} - PRINTER_APP_URL=http://app:3000 - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres - - GOTENBERG_ENDPOINT=http://gotenberg:3000 + - PRINTER_ENDPOINT=http://printer:3000 - AUTH_SECRET=${AUTH_SECRET} # Add other optional env vars as needed (SMTP, S3, OAuth, etc.) volumes: @@ -217,10 +217,10 @@ services: depends_on: postgres: condition: service_healthy - gotenberg: + printer: condition: service_healthy healthcheck: - test: ["CMD-SHELL", "node -e \"fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\""] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"] interval: 30s timeout: 10s retries: 3 @@ -347,19 +347,18 @@ services: max_attempts: 3 window: 120s - gotenberg: - image: gotenberg/gotenberg:8 + printer: + image: ghcr.io/browserless/chromium:latest networks: - reactive_resume_network environment: - - WEBHOOK_DISABLE=true - - CHROMIUM_AUTO_START=true - - API_ENABLE_BASIC_AUTH=true - - PROMETHEUS_DISABLE_COLLECT=true - - GOTENBERG_API_BASIC_AUTH_USERNAME=$GOTENBERG_USERNAME - - GOTENBERG_API_BASIC_AUTH_PASSWORD=$GOTENBERG_PASSWORD + - TIMEOUT=120000 + - CONCURRENT=10 + - HEALTH=true + # Optional: Set a token for authentication + # - TOKEN=$PRINTER_TOKEN healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] + test: ["CMD", "curl", "-f", "http://localhost:3000/"] interval: 30s timeout: 10s retries: 3 @@ -438,9 +437,9 @@ services: - reactive_resume_app_data:/app/data environment: - APP_URL=$APP_URL - - GOTENBERG_ENDPOINT=$GOTENBERG_ENDPOINT - - GOTENBERG_USERNAME=$GOTENBERG_USERNAME - - GOTENBERG_PASSWORD=$GOTENBERG_PASSWORD + - PRINTER_ENDPOINT=$PRINTER_ENDPOINT + # Optional: Set PRINTER_TOKEN if using browserless with token authentication + # - PRINTER_TOKEN=$PRINTER_TOKEN - DATABASE_URL=$DATABASE_URL - AUTH_SECRET=$AUTH_SECRET - GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID @@ -459,13 +458,7 @@ services: - S3_ENDPOINT=$S3_ENDPOINT - S3_BUCKET=$S3_BUCKET healthcheck: - test: - [ - "CMD", - "node", - "-e", - "fetch('http://localhost:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))", - ] + test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"] interval: 30s timeout: 10s retries: 3 diff --git a/package.json b/package.json index 899fea9d8..305acc169 100644 --- a/package.json +++ b/package.json @@ -55,17 +55,17 @@ "@sindresorhus/slugify": "^3.0.0", "@t3-oss/env-core": "^0.13.10", "@tanstack/react-query": "5.90.19", - "@tanstack/react-router": "^1.153.2", - "@tanstack/react-router-ssr-query": "^1.153.2", - "@tanstack/react-start": "^1.154.0", - "@tanstack/zod-adapter": "^1.153.2", + "@tanstack/react-router": "^1.154.2", + "@tanstack/react-router-ssr-query": "^1.154.2", + "@tanstack/react-start": "^1.154.2", + "@tanstack/zod-adapter": "^1.154.2", "@tiptap/extension-highlight": "^3.16.0", "@tiptap/extension-table": "^3.16.0", "@tiptap/extension-text-align": "^3.16.0", "@tiptap/pm": "^3.16.0", "@tiptap/react": "^3.16.0", "@tiptap/starter-kit": "^3.16.0", - "ai": "^6.0.42", + "ai": "^6.0.44", "ai-sdk-ollama": "^3.1.1", "bcrypt": "^6.0.0", "better-auth": "1.5.0-beta.9", @@ -80,9 +80,10 @@ "input-otp": "^1.4.2", "js-cookie": "^3.0.5", "monaco-editor": "^0.55.1", - "motion": "^12.27.5", + "motion": "^12.28.1", "nodemailer": "^7.0.12", "pg": "^8.17.2", + "puppeteer-core": "^24.35.0", "qrcode.react": "^4.2.0", "radix-ui": "^1.4.3", "react": "^19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 301779cb2..a5b9205a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 1.5.0-beta.9(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/passkey': specifier: 1.5.0-beta.9 - version: 1.5.0-beta.9(d8f2433ef8a40ee5e0f236b0c8d5ad31) + version: 1.5.0-beta.9(f4468a6efc0da5cc1ed9d5bd84681aa4) '@dnd-kit/core': specifier: ^6.3.1 version: 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -58,19 +58,19 @@ importers: version: 1.13.4(@opentelemetry/api@1.9.0) '@orpc/json-schema': specifier: ^1.13.4 - version: 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) + version: 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) '@orpc/openapi': specifier: ^1.13.4 - version: 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) + version: 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) '@orpc/server': specifier: ^1.13.4 - version: 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) + version: 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) '@orpc/tanstack-query': specifier: ^1.13.4 version: 1.13.4(@opentelemetry/api@1.9.0)(@orpc/client@1.13.4(@opentelemetry/api@1.9.0))(@tanstack/query-core@5.90.19) '@orpc/zod': specifier: ^1.13.4 - version: 1.13.4(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.4(@opentelemetry/api@1.9.0))(@orpc/server@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)))(crossws@0.4.3(srvx@0.10.1))(zod@4.3.5) + version: 1.13.4(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.4(@opentelemetry/api@1.9.0))(@orpc/server@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0))(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0)(zod@4.3.5) '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -87,17 +87,17 @@ importers: specifier: 5.90.19 version: 5.90.19(react@19.2.3) '@tanstack/react-router': - specifier: ^1.153.2 - version: 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.154.2 + version: 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-router-ssr-query': - specifier: ^1.153.2 - version: 1.153.2(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.153.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^1.154.2 + version: 1.154.2(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.154.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': - specifier: ^1.154.0 - version: 1.154.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + specifier: ^1.154.2 + version: 1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) '@tanstack/zod-adapter': - specifier: ^1.153.2 - version: 1.153.2(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.5) + specifier: ^1.154.2 + version: 1.154.2(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.5) '@tiptap/extension-highlight': specifier: ^3.16.0 version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0)) @@ -117,17 +117,17 @@ importers: specifier: ^3.16.0 version: 3.16.0 ai: - specifier: ^6.0.42 - version: 6.0.42(zod@4.3.5) + specifier: ^6.0.44 + version: 6.0.44(zod@4.3.5) ai-sdk-ollama: specifier: ^3.1.1 - version: 3.1.1(ai@6.0.42(zod@4.3.5))(zod@4.3.5) + version: 3.1.1(ai@6.0.44(zod@4.3.5))(zod@4.3.5) bcrypt: specifier: ^6.0.0 version: 6.0.0 better-auth: specifier: 1.5.0-beta.9 - version: 1.5.0-beta.9(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.154.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.17.2)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 1.5.0-beta.9(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.17.2)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -162,14 +162,17 @@ importers: specifier: ^0.55.1 version: 0.55.1 motion: - specifier: ^12.27.5 - version: 12.27.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: ^12.28.1 + version: 12.28.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) nodemailer: specifier: ^7.0.12 version: 7.0.12 pg: specifier: ^8.17.2 version: 8.17.2 + puppeteer-core: + specifier: ^24.35.0 + version: 24.35.0 qrcode.react: specifier: ^4.2.0 version: 4.2.0(react@19.2.3) @@ -321,8 +324,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@3.0.17': - resolution: {integrity: sha512-mCz50GlBPyBV96Wcll1Mpaz56MVFuHL+bwRWGkIsCJwKAKIfdgUZecFzS3gckpHGaqP5+BYnmyJocIMzUhTQ2A==} + '@ai-sdk/gateway@3.0.18': + resolution: {integrity: sha512-WlJ7UkBDYgv+5q9UpGZfbnPrrW3KVnY96lRLaXs8nXy7Ea3QTWXf4Jw43jFzMSVkH4Zhhf0s9ExmG+CiyEBY5A==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -2577,6 +2580,11 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 + '@puppeteer/browsers@2.11.1': + resolution: {integrity: sha512-YmhAxs7XPuxN0j7LJloHpfD1ylhDuFmmwMvfy/+6nBSrETT2ycL53LrhgPtR+f+GcPSybQVuQ5inWWu5MrWCpA==} + engines: {node: '>=18'} + hasBin: true + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -3793,8 +3801,8 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-ssr-query@1.153.2': - resolution: {integrity: sha512-smpV1dLt+HoR87cTaPlIQQtUnnuHVZWBTaC0wxlU89XDzkT2WbFeLOCn0yXGK4vfr6P0Z2bMg8t5rVvlLYzyDw==} + '@tanstack/react-router-ssr-query@1.154.2': + resolution: {integrity: sha512-KIrIFA3y5VIKC9vyfNyDdneoy+6OtBBj8FPfAB7lpm0l/7+Zz5xWnuEFt0UK3TAnzevyCDUVKclTHhb4apE79A==} engines: {node: '>=12'} peerDependencies: '@tanstack/query-core': '>=5.90.0' @@ -3803,29 +3811,29 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.153.2': - resolution: {integrity: sha512-fAXUBA2gZAId7h2eSHsRcgTeF8pioUz8V5rrQ+IrvA0a6IsxhbTSKLYyqUg4jRDkkcUKtM8StKtvbZCY+0IYWw==} + '@tanstack/react-router@1.154.2': + resolution: {integrity: sha512-PtzpfqB6df2jNR9AVxhYaCwgBmBOKLaZ3uWFuJCH3Art8T3r3HgeSWGC8ZlISKyDM5GWGR+4AEqXxTwU6NOz/g==} engines: {node: '>=12'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.153.2': - resolution: {integrity: sha512-VlHWqywDKbAuGlnDM484T59kvu9AKHQ/2QaTwILfnPagjU1Dx13gyTQJIrCmORC1BKACVbVtZHD8TvkFwejuLQ==} + '@tanstack/react-start-client@1.154.2': + resolution: {integrity: sha512-jpO5hkfpxJoPQqMYBKxjGwbqKPudZKPMT2k9slYR5ylNeQaErHbezuGVssbx4K4VAznZYP7Aa8jsCzfG/w7jVg==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-server@1.153.2': - resolution: {integrity: sha512-ZehQ8d4C0xvEpcrj2QDmmFWylRoTvDYwitSPUlosg37cf8WVrtSx6dgs2v9TLzStrqcabNPc6t5oFcBd1R7Rzw==} + '@tanstack/react-start-server@1.154.2': + resolution: {integrity: sha512-7gSuLYgVAz9z6t0VYKomUQSqyssQoeXFIBd7Bo9lAW//E2QISrLnkGTggMVRmQ7oulwIJhp8A3O/AQIBRxAILQ==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start@1.154.0': - resolution: {integrity: sha512-umfxigl+oqW2oWlIYEk+FI85a9GUKvUIMLN2AH6MrGvuVqPbL5uFOl4qHY78VKTyzdDjzxndtMwoHSodm038aw==} + '@tanstack/react-start@1.154.2': + resolution: {integrity: sha512-OHFMIdFwQwgCM4hRUYMGXd4eh4ueeAfsc6+Hs8bhFTYUb8aRa3uZjKxaDqWolbN7+ChiIL7JL4XiOwnvPN0Iig==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -3838,20 +3846,20 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.153.2': - resolution: {integrity: sha512-WLaR+rSNW7bj9UCJQ3SKpuh6nZBZkpGnf2mpjn/uRB6joIQ3BU7aRdhb7w9Via/MP52iaHh5sd8NY3MaLpF2tQ==} + '@tanstack/router-core@1.154.2': + resolution: {integrity: sha512-M4xkhDVk62Oguw0vKABICYR6JwFoeKwLrxsESnTk1AiSvyMrwuOJviFp3eSW/0F/VhdtOAIv5oB9PPrpUKO6NA==} engines: {node: '>=12'} - '@tanstack/router-generator@1.153.2': - resolution: {integrity: sha512-bEhmCtXq5vv3HukKq5zmTDBNDRqVllYxsHoWtqEvHv5hCb5xwKKfUMGemRoiQ96/wLFuGnA5DYkem2GZWcG3wg==} + '@tanstack/router-generator@1.154.2': + resolution: {integrity: sha512-WAYTmM8MV62t87bDXQel0BEC7fPn9luSfAg72NsmDo+hf3SIECwGEzlatdHnALCsrizupprNWtqUFC1TasTVDQ==} engines: {node: '>=12'} - '@tanstack/router-plugin@1.153.2': - resolution: {integrity: sha512-aMMc70ChM0wBYOToq39kTMKI2A0EKWpumiKTJyAwEglXf0raF48+26Fmv0gr9/5CLvD0g8ljllsskVDyzg8oDw==} + '@tanstack/router-plugin@1.154.2': + resolution: {integrity: sha512-xCXSlD4m/1SPr5P+5o3gsIrmrJ19pDq1SgWsugcf8N+WT7BhrIdSMVwIbZdHoiWv4VSA5BAFBq+9syCMZdEiVw==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.153.2 + '@tanstack/react-router': ^1.154.2 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' vite-plugin-solid: ^2.11.10 webpack: '>=5.92.0' @@ -3867,8 +3875,8 @@ packages: webpack: optional: true - '@tanstack/router-ssr-query-core@1.153.2': - resolution: {integrity: sha512-IVAUQxp+JL1abe3qvkUEWka5sypKKF44J7QPMMrJTEt+6IGjbMuSJNBEeGzk00OQ2GljqajRNf0dWuuYQk3U1g==} + '@tanstack/router-ssr-query-core@1.154.2': + resolution: {integrity: sha512-3pIPtFZNkeUI6jnDp7O8KzhGckM+8KHUEqFTACb9ONVIX4yP1aCyOkUBoRtj5XqdUM1dsv8DfJaxeat8lDzh2Q==} engines: {node: '>=12'} peerDependencies: '@tanstack/query-core': '>=5.90.0' @@ -3878,26 +3886,26 @@ packages: resolution: {integrity: sha512-N24G4LpfyK8dOlnP8BvNdkuxg1xQljkyl6PcrdiPSA301pOjatRT1y8wuCCJZKVVD8gkd0MpCZ0VEjRMGILOtA==} engines: {node: '>=12'} - '@tanstack/start-client-core@1.153.2': - resolution: {integrity: sha512-CsDV9QiviQeBCTxRbBF5R63seVjasXrfWDk87ccLIMLC15+SP5eUQbNLPkIRtYQYYvaAAQuNGJyvsMzDO+u3yQ==} + '@tanstack/start-client-core@1.154.2': + resolution: {integrity: sha512-81VFanLkU/c6qXi6eR2JE0VilLERLlIJfrYwBsRBVfORCh8HwXrrmRmSPa8TceA7n40I3fmxgaLgSytEfI4XNw==} engines: {node: '>=22.12.0'} '@tanstack/start-fn-stubs@1.151.3': resolution: {integrity: sha512-/zWBnfsOwact936Bn0CxigudU1QRZdiNTsK7ME/LMXXA66XsDxkryX5+5FeGwU5ETNPfLAx6pRUet1mtUKnLCg==} engines: {node: '>=22.12.0'} - '@tanstack/start-plugin-core@1.154.0': - resolution: {integrity: sha512-qlywkB43bltkS9poMtAEjxPG9Y4YTP4GSaCOyINYdkrfCEU42xCpUghvANFMpeKEQn+I2YvBtUlGcRpJxKyjDw==} + '@tanstack/start-plugin-core@1.154.2': + resolution: {integrity: sha512-Qlwn9GFT/nFs+5uIxAj9HmixAa5IiX0t3NnhtezLrcAn92kj93iwuWqAucG/BXrPt/pJ4VkLSYTZ33ECWo/ZCQ==} engines: {node: '>=22.12.0'} peerDependencies: vite: '>=7.0.0' - '@tanstack/start-server-core@1.153.2': - resolution: {integrity: sha512-erjRPok9MWXJpFfw2ofUDh2lYi+aVNCrZiJFrkwrVvt45dhWnkOuLDl9bk3SfW/vaSm15pKjT3xDjEItsX2AUQ==} + '@tanstack/start-server-core@1.154.2': + resolution: {integrity: sha512-CKGIIVakf2VcM1SjPXzCCtMAVKWPk6AV34H+D1k8fY8mcjOnm2GbLdS5uqIv42QHhRpcrfok8RbU0h7pvNKtIA==} engines: {node: '>=22.12.0'} - '@tanstack/start-storage-context@1.153.2': - resolution: {integrity: sha512-kmD+OIxJPx6oHtfT578UuLJp2p4gF5EFD2ZiVD0WfCj9BKWpp0i8fqBe+g7x1rpXzHUPNqAz3ILkGT38wV++xQ==} + '@tanstack/start-storage-context@1.154.2': + resolution: {integrity: sha512-XtsUqyMm5/L5v9hf5Vgdih5z1MdYM0/f0vt750Ae7DHEIIYlHyluLFd8vYsEUcbkpUW7ICREYyBfKvvWYoocTw==} engines: {node: '>=22.12.0'} '@tanstack/store@0.8.0': @@ -3907,8 +3915,8 @@ packages: resolution: {integrity: sha512-CI75JrfqSluhdGwLssgVeQBaCphgfkMQpi8MCY3UJX1hoGzXa8kHYJcUuIFMOLs1q7zqHy++EVVtMK03osR5wQ==} engines: {node: '>=12'} - '@tanstack/zod-adapter@1.153.2': - resolution: {integrity: sha512-E3OPtB/QSRo9lN8wWbxjvjMfQI4MOKjTEZOrwZiHXXMzlOMYWqxg99eCPeHP8kcXKJ+LdIkf29sZ/0rC62I4aQ==} + '@tanstack/zod-adapter@1.154.2': + resolution: {integrity: sha512-D73nClfPVEAmesAy3PGjfPLI6VUOVFlVEi0abq/M/fv32u6sD8KN7hHYbssZzbtOIZd/Xd84coGB3A5Qb0LQKg==} engines: {node: '>=12'} peerDependencies: '@tanstack/react-router': '>=1.43.2' @@ -4082,6 +4090,9 @@ packages: '@tiptap/starter-kit@3.16.0': resolution: {integrity: sha512-eWi+77SgKyhSx91Hmn32ER+gPN6FfInGtod4A+XxSG+LqS/sn6kpUEdowYrnqiZzhUXZCSTSJvC+UcMUZHOkxQ==} + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} @@ -4183,6 +4194,9 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typespec/ts-http-runtime@0.3.2': resolution: {integrity: sha512-IlqQ/Gv22xUC1r/WQm4StLkYQmaaTsXAhUVsNE0+xiyf0yRFiH5++q78U3bw6bLKDCTmh0uqKB9eG9+Bt75Dkg==} engines: {node: '>=20.0.0'} @@ -4224,8 +4238,8 @@ packages: peerDependencies: ai: ^6.0.27 - ai@6.0.42: - resolution: {integrity: sha512-o+MVN7HBE4HEnhtN7nBt9WO1iISI6svyWNoOuY6WiXCdHuZfSGN4MUQ3QwjWz1Ue5gtBEcvwX5XFhgAwlPAxxw==} + ai@6.0.44: + resolution: {integrity: sha512-fOyssuNfSB3i3ODuDeLaWambfJY+gE3LRHQf4nmWEPDrQhfiB+fcpVMVi77LbFRBfoG/h5BrmX0heVqz4Hl7ug==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -4288,6 +4302,10 @@ packages: resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} engines: {node: '>=12.0.0'} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} @@ -4311,6 +4329,14 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + b4a@1.7.3: + resolution: {integrity: sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} @@ -4336,6 +4362,44 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.5.2: + resolution: {integrity: sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.6.2: + resolution: {integrity: sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.7.0: + resolution: {integrity: sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==} + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.3.2: + resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4343,6 +4407,10 @@ packages: resolution: {integrity: sha512-KeUZdBuxngy825i8xvzaK1Ncnkx0tBmb3k8DkEuqjKRkmtvNTjey2ZsNeh8Dw4lfKvbCOu9oeNx2TKm2vHqcRw==} hasBin: true + basic-ftp@5.1.0: + resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==} + engines: {node: '>=10.0.0'} + bcrypt@6.0.0: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} @@ -4453,6 +4521,9 @@ packages: resolution: {integrity: sha512-TtJgBB+QyOlWjrbM+8bRgH84VM/xrDjyBFgSgGrfZF4xvt6gbEDtcswm27Tn9F9TWsjQybxT8b8VpCP/oJK4Dw==} engines: {node: '>=20.19.0'} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -4542,6 +4613,11 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + chromium-bidi@12.0.1: + resolution: {integrity: sha512-fGg+6jr0xjQhzpy5N4ErZxQ4wF7KLEvhGZXD6EgvZKDhu7iOhZXnZhcDxPJDcwTcrD48NPzOCo84RP2lv3Z+Cg==} + peerDependencies: + devtools-protocol: '*' + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} @@ -4722,6 +4798,10 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -4811,6 +4891,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -4829,6 +4913,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devtools-protocol@0.0.1534754: + resolution: {integrity: sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==} + diff@8.0.3: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} @@ -5021,6 +5108,9 @@ packages: encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.4: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} @@ -5095,6 +5185,11 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + esm@3.2.25: resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} engines: {node: '>=6'} @@ -5104,6 +5199,10 @@ packages: engines: {node: '>=4'} hasBin: true + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@1.0.1: resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} @@ -5122,6 +5221,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -5158,6 +5260,11 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} @@ -5169,6 +5276,9 @@ packages: resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} engines: {node: '>=6.0.0'} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -5189,6 +5299,9 @@ packages: fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5238,8 +5351,8 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - framer-motion@12.27.5: - resolution: {integrity: sha512-yUFof7Y2Y2qDJxLKeA91qMazuA6QBOoLOZ0No2J5VIQuhJLWMmGwT/5qyCfpa9mNNS3C7lOR6NhlC3mLZjLw4g==} + framer-motion@12.28.1: + resolution: {integrity: sha512-72GkO7DS4FfcSjf26wx0v+rzkW8Fhn4Djh04aDbuEg7NYG8X8MhJZc6/5weG/YeEgIP+fCo8FS2y1HnXH8k8fQ==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -5334,6 +5447,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -5349,6 +5466,10 @@ packages: get-tsconfig@4.13.0: resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -6034,6 +6155,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + lru.min@1.1.3: resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} @@ -6145,6 +6270,9 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + monaco-editor@0.55.1: resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} @@ -6182,14 +6310,14 @@ packages: moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} - motion-dom@12.27.5: - resolution: {integrity: sha512-UwBv2AUOkA7/TCHr67NGjg3aRT7nbsanmmenRoR7T6IJXZp34OZB+pooGnKjMd8CqqCsF/+qwT657EkukjgmiQ==} + motion-dom@12.28.1: + resolution: {integrity: sha512-xqgID69syDvXwFJnUd5bW6ajGUAr/qevRoUe/EqpsXUbVIopyWrAOiwQOhpgVQD+B7Ra60zTdj5gVkmwncebMg==} motion-utils@12.27.2: resolution: {integrity: sha512-B55gcoL85Mcdt2IEStY5EEAsrMSVE2sI14xQ/uAdPL+mfQxhKKFaEag9JmfxedJOR4vZpBGoPeC/Gm13I/4g5Q==} - motion@12.27.5: - resolution: {integrity: sha512-Am4QS7Nd9+yhAOQSefziBmX0hYtc0HaWbXY5+0r/0J8eBBFf5jXzlBew+v+7i+eNmdVpDagVqwjES8fPYtEayA==} + motion@12.28.1: + resolution: {integrity: sha512-qGq5+6r4IMivHbT2EUhCwxz2NgFBuba3sWDrxcHt06+nYqKMevYJiVh/N90nMRof+vIUpiq8C22ZeOXwkWWiZg==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -6248,6 +6376,10 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + nf3@0.3.5: resolution: {integrity: sha512-1VozaVz0lVfGL3c2wZ4c6bmQCm340gDiIYUU3lcg8vVGL/WeuTdrd6OhJiUHZWofc7fFdquhS8Gm+13c3Tumcw==} @@ -6423,6 +6555,14 @@ packages: resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} engines: {node: '>=18'} + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -6485,6 +6625,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -6621,6 +6764,10 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} @@ -6694,11 +6841,21 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pseudolocale@2.2.0: resolution: {integrity: sha512-O+D2eU7fO9wVLqrohvt9V/9fwMadnJQ4jxwiK+LeNEqhMx8JYx4xQHkArDCJFAdPPOp/pQq6z5L37eBvAoc8jw==} engines: {node: '>=16.0.0'} hasBin: true + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -6707,6 +6864,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + puppeteer-core@24.35.0: + resolution: {integrity: sha512-vt1zc2ME0kHBn7ZDOqLvgvrYD5bqNv5y2ZNXzYnCv8DEtZGw/zKhljlrGuImxptZ4rq+QI9dFGrUIYqG4/IQzA==} + engines: {node: '>=18'} + pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} @@ -7209,6 +7370,9 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + streamx@2.23.0: + resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} + strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -7304,6 +7468,12 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@7.5.6: resolution: {integrity: sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==} engines: {node: '>=18'} @@ -7333,6 +7503,9 @@ packages: engines: {node: '>=10'} hasBin: true + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + threads@1.7.0: resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} @@ -7439,6 +7612,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -7691,6 +7867,9 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + webdriver-bidi-protocol@0.3.10: + resolution: {integrity: sha512-5LAE43jAVLOhB/QqX4bwSiv0Hg1HBfMmOuwBSXHdvg4GMGu9Y0lIq7p4R/yySu6w74WmaR4GM4H9t2IwLW7hgw==} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -7818,6 +7997,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} @@ -7860,6 +8051,9 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} @@ -7908,7 +8102,7 @@ snapshots: '@ai-sdk/provider-utils': 4.0.8(zod@4.3.5) zod: 4.3.5 - '@ai-sdk/gateway@3.0.17(zod@4.3.5)': + '@ai-sdk/gateway@3.0.18(zod@4.3.5)': dependencies: '@ai-sdk/provider': 3.0.4 '@ai-sdk/provider-utils': 4.0.8(zod@4.3.5) @@ -9363,14 +9557,14 @@ snapshots: '@better-auth/utils': 0.3.0 mongodb: 7.0.0(socks@2.8.7) - '@better-auth/passkey@1.5.0-beta.9(d8f2433ef8a40ee5e0f236b0c8d5ad31)': + '@better-auth/passkey@1.5.0-beta.9(f4468a6efc0da5cc1ed9d5bd84681aa4)': dependencies: '@better-auth/core': 1.5.0-beta.9(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 '@simplewebauthn/browser': 13.2.2 '@simplewebauthn/server': 13.2.2 - better-auth: 1.5.0-beta.9(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.154.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.17.2)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + better-auth: 1.5.0-beta.9(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.17.2)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) better-call: 1.2.0(zod@4.3.5) nanostores: 1.1.0 zod: 4.3.5 @@ -10138,12 +10332,12 @@ snapshots: '@orpc/interop@1.13.4': {} - '@orpc/json-schema@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))': + '@orpc/json-schema@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0)': dependencies: '@orpc/contract': 1.13.4(@opentelemetry/api@1.9.0) '@orpc/interop': 1.13.4 - '@orpc/openapi': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) - '@orpc/server': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) + '@orpc/openapi': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) + '@orpc/server': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) '@orpc/shared': 1.13.4(@opentelemetry/api@1.9.0) json-schema-typed: 8.0.2 transitivePeerDependencies: @@ -10161,13 +10355,13 @@ snapshots: transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/openapi@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))': + '@orpc/openapi@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0)': dependencies: '@orpc/client': 1.13.4(@opentelemetry/api@1.9.0) '@orpc/contract': 1.13.4(@opentelemetry/api@1.9.0) '@orpc/interop': 1.13.4 '@orpc/openapi-client': 1.13.4(@opentelemetry/api@1.9.0) - '@orpc/server': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) + '@orpc/server': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) '@orpc/shared': 1.13.4(@opentelemetry/api@1.9.0) '@orpc/standard-server': 1.13.4(@opentelemetry/api@1.9.0) json-schema-typed: 8.0.2 @@ -10178,7 +10372,7 @@ snapshots: - fastify - ws - '@orpc/server@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))': + '@orpc/server@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0)': dependencies: '@orpc/client': 1.13.4(@opentelemetry/api@1.9.0) '@orpc/contract': 1.13.4(@opentelemetry/api@1.9.0) @@ -10193,6 +10387,7 @@ snapshots: cookie: 1.1.1 optionalDependencies: crossws: 0.4.3(srvx@0.10.1) + ws: 8.19.0 transitivePeerDependencies: - '@opentelemetry/api' - fastify @@ -10257,12 +10452,12 @@ snapshots: transitivePeerDependencies: - '@opentelemetry/api' - '@orpc/zod@1.13.4(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.4(@opentelemetry/api@1.9.0))(@orpc/server@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)))(crossws@0.4.3(srvx@0.10.1))(zod@4.3.5)': + '@orpc/zod@1.13.4(@opentelemetry/api@1.9.0)(@orpc/contract@1.13.4(@opentelemetry/api@1.9.0))(@orpc/server@1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0))(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0)(zod@4.3.5)': dependencies: '@orpc/contract': 1.13.4(@opentelemetry/api@1.9.0) - '@orpc/json-schema': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) - '@orpc/openapi': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) - '@orpc/server': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1)) + '@orpc/json-schema': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) + '@orpc/openapi': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) + '@orpc/server': 1.13.4(@opentelemetry/api@1.9.0)(crossws@0.4.3(srvx@0.10.1))(ws@8.19.0) '@orpc/shared': 1.13.4(@opentelemetry/api@1.9.0) escape-string-regexp: 5.0.0 wildcard-match: 5.1.4 @@ -10641,6 +10836,21 @@ snapshots: react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + '@puppeteer/browsers@2.11.1': + dependencies: + debug: 4.4.3 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.7.3 + tar-fs: 3.1.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -11959,59 +12169,59 @@ snapshots: '@tanstack/query-core': 5.90.19 react: 19.2.3 - '@tanstack/react-router-ssr-query@1.153.2(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.153.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-router-ssr-query@1.154.2(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.154.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/query-core': 5.90.19 '@tanstack/react-query': 5.90.19(react@19.2.3) - '@tanstack/react-router': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-ssr-query-core': 1.153.2(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.153.2) + '@tanstack/react-router': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-ssr-query-core': 1.154.2(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.154.2) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: - '@tanstack/router-core' - '@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/history': 1.153.2 '@tanstack/react-store': 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-core': 1.153.2 + '@tanstack/router-core': 1.154.2 isbot: 5.1.33 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-client@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-start-client@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/react-router': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-core': 1.153.2 - '@tanstack/start-client-core': 1.153.2 + '@tanstack/react-router': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.154.2 + '@tanstack/start-client-core': 1.154.2 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-server@1.153.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-start-server@1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/history': 1.153.2 - '@tanstack/react-router': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/router-core': 1.153.2 - '@tanstack/start-client-core': 1.153.2 - '@tanstack/start-server-core': 1.153.2(crossws@0.4.3(srvx@0.10.1)) + '@tanstack/react-router': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.154.2 + '@tanstack/start-client-core': 1.154.2 + '@tanstack/start-server-core': 1.154.2(crossws@0.4.3(srvx@0.10.1)) react: 19.2.3 react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: - crossws - '@tanstack/react-start@1.154.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': + '@tanstack/react-start@1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': dependencies: - '@tanstack/react-router': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-start-client': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-start-server': 1.153.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-start-client': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-start-server': 1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/router-utils': 1.143.11 - '@tanstack/start-client-core': 1.153.2 - '@tanstack/start-plugin-core': 1.154.0(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) - '@tanstack/start-server-core': 1.153.2(crossws@0.4.3(srvx@0.10.1)) + '@tanstack/start-client-core': 1.154.2 + '@tanstack/start-plugin-core': 1.154.2(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + '@tanstack/start-server-core': 1.154.2(crossws@0.4.3(srvx@0.10.1)) pathe: 2.0.3 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -12030,7 +12240,7 @@ snapshots: react-dom: 19.2.3(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@tanstack/router-core@1.153.2': + '@tanstack/router-core@1.154.2': dependencies: '@tanstack/history': 1.153.2 '@tanstack/store': 0.8.0 @@ -12040,9 +12250,9 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-generator@1.153.2': + '@tanstack/router-generator@1.154.2': dependencies: - '@tanstack/router-core': 1.153.2 + '@tanstack/router-core': 1.154.2 '@tanstack/router-utils': 1.143.11 '@tanstack/virtual-file-routes': 1.145.4 prettier: 3.8.0 @@ -12053,7 +12263,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.153.2(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': + '@tanstack/router-plugin@1.154.2(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': dependencies: '@babel/core': 7.28.6 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) @@ -12061,8 +12271,8 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.28.6 '@babel/types': 7.28.6 - '@tanstack/router-core': 1.153.2 - '@tanstack/router-generator': 1.153.2 + '@tanstack/router-core': 1.154.2 + '@tanstack/router-generator': 1.154.2 '@tanstack/router-utils': 1.143.11 '@tanstack/virtual-file-routes': 1.145.4 babel-dead-code-elimination: 1.0.12 @@ -12070,15 +12280,15 @@ snapshots: unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) vite: rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0) transitivePeerDependencies: - supports-color - '@tanstack/router-ssr-query-core@1.153.2(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.153.2)': + '@tanstack/router-ssr-query-core@1.154.2(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.154.2)': dependencies: '@tanstack/query-core': 5.90.19 - '@tanstack/router-core': 1.153.2 + '@tanstack/router-core': 1.154.2 '@tanstack/router-utils@1.143.11': dependencies: @@ -12092,29 +12302,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/start-client-core@1.153.2': + '@tanstack/start-client-core@1.154.2': dependencies: - '@tanstack/router-core': 1.153.2 + '@tanstack/router-core': 1.154.2 '@tanstack/start-fn-stubs': 1.151.3 - '@tanstack/start-storage-context': 1.153.2 + '@tanstack/start-storage-context': 1.154.2 seroval: 1.4.2 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 '@tanstack/start-fn-stubs@1.151.3': {} - '@tanstack/start-plugin-core@1.154.0(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': + '@tanstack/start-plugin-core@1.154.2(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.28.6 '@babel/types': 7.28.6 '@rolldown/pluginutils': 1.0.0-beta.40 - '@tanstack/router-core': 1.153.2 - '@tanstack/router-generator': 1.153.2 - '@tanstack/router-plugin': 1.153.2(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + '@tanstack/router-core': 1.154.2 + '@tanstack/router-generator': 1.154.2 + '@tanstack/router-plugin': 1.154.2(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) '@tanstack/router-utils': 1.143.11 - '@tanstack/start-client-core': 1.153.2 - '@tanstack/start-server-core': 1.153.2(crossws@0.4.3(srvx@0.10.1)) + '@tanstack/start-client-core': 1.154.2 + '@tanstack/start-server-core': 1.154.2(crossws@0.4.3(srvx@0.10.1)) babel-dead-code-elimination: 1.0.12 cheerio: 1.1.2 exsolve: 1.0.8 @@ -12134,29 +12344,29 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-server-core@1.153.2(crossws@0.4.3(srvx@0.10.1))': + '@tanstack/start-server-core@1.154.2(crossws@0.4.3(srvx@0.10.1))': dependencies: '@tanstack/history': 1.153.2 - '@tanstack/router-core': 1.153.2 - '@tanstack/start-client-core': 1.153.2 - '@tanstack/start-storage-context': 1.153.2 + '@tanstack/router-core': 1.154.2 + '@tanstack/start-client-core': 1.154.2 + '@tanstack/start-storage-context': 1.154.2 h3-v2: h3@2.0.1-rc.7(crossws@0.4.3(srvx@0.10.1)) seroval: 1.4.2 tiny-invariant: 1.3.3 transitivePeerDependencies: - crossws - '@tanstack/start-storage-context@1.153.2': + '@tanstack/start-storage-context@1.154.2': dependencies: - '@tanstack/router-core': 1.153.2 + '@tanstack/router-core': 1.154.2 '@tanstack/store@0.8.0': {} '@tanstack/virtual-file-routes@1.145.4': {} - '@tanstack/zod-adapter@1.153.2(@tanstack/react-router@1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.5)': + '@tanstack/zod-adapter@1.154.2(@tanstack/react-router@1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.5)': dependencies: - '@tanstack/react-router': 1.153.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': 1.154.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) zod: 4.3.5 '@tediousjs/connection-string@0.5.0': {} @@ -12351,6 +12561,8 @@ snapshots: '@tiptap/extensions': 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))(@tiptap/pm@3.16.0) '@tiptap/pm': 3.16.0 + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@ts-morph/common@0.27.0': dependencies: fast-glob: 3.3.3 @@ -12473,6 +12685,11 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 25.0.9 + optional: true + '@typespec/ts-http-runtime@0.3.2': dependencies: http-proxy-agent: 7.0.2 @@ -12510,18 +12727,18 @@ snapshots: agent-base@7.1.4: {} - ai-sdk-ollama@3.1.1(ai@6.0.42(zod@4.3.5))(zod@4.3.5): + ai-sdk-ollama@3.1.1(ai@6.0.44(zod@4.3.5))(zod@4.3.5): dependencies: '@ai-sdk/provider': 3.0.4 '@ai-sdk/provider-utils': 4.0.8(zod@4.3.5) - ai: 6.0.42(zod@4.3.5) + ai: 6.0.44(zod@4.3.5) ollama: 0.6.3 transitivePeerDependencies: - zod - ai@6.0.42(zod@4.3.5): + ai@6.0.44(zod@4.3.5): dependencies: - '@ai-sdk/gateway': 3.0.17(zod@4.3.5) + '@ai-sdk/gateway': 3.0.18(zod@4.3.5) '@ai-sdk/provider': 3.0.4 '@ai-sdk/provider-utils': 4.0.8(zod@4.3.5) '@opentelemetry/api': 1.9.0 @@ -12584,6 +12801,10 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + ast-types@0.16.1: dependencies: tslib: 2.8.1 @@ -12600,6 +12821,8 @@ snapshots: aws-ssl-profiles@1.1.2: {} + b4a@1.7.3: {} + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.28.6 @@ -12641,16 +12864,55 @@ snapshots: balanced-match@1.0.2: {} + bare-events@2.8.2: {} + + bare-fs@4.5.2: + dependencies: + bare-events: 2.8.2 + bare-path: 3.0.0 + bare-stream: 2.7.0(bare-events@2.8.2) + bare-url: 2.3.2 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + optional: true + + bare-os@3.6.2: + optional: true + + bare-path@3.0.0: + dependencies: + bare-os: 3.6.2 + optional: true + + bare-stream@2.7.0(bare-events@2.8.2): + dependencies: + streamx: 2.23.0 + optionalDependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + optional: true + + bare-url@2.3.2: + dependencies: + bare-path: 3.0.0 + optional: true + base64-js@1.5.1: {} baseline-browser-mapping@2.9.16: {} + basic-ftp@5.1.0: {} + bcrypt@6.0.0: dependencies: node-addon-api: 8.5.0 node-gyp-build: 4.8.4 - better-auth@1.5.0-beta.9(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.154.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.17.2)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + better-auth@1.5.0-beta.9(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.17.2)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@better-auth/core': 1.5.0-beta.9(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/drizzle-adapter': 1.5.0-beta.9(@better-auth/core@1.5.0-beta.9(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.5))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.0)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))) @@ -12671,7 +12933,7 @@ snapshots: zod: 4.3.5 optionalDependencies: '@prisma/client': 7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3) - '@tanstack/react-start': 1.154.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + '@tanstack/react-start': 1.154.2(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) drizzle-kit: 1.0.0-beta.9-e89174b drizzle-orm: 1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)) mongodb: 7.0.0(socks@2.8.7) @@ -12741,6 +13003,8 @@ snapshots: bson@7.1.1: {} + buffer-crc32@0.2.13: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} @@ -12882,6 +13146,12 @@ snapshots: chownr@3.0.0: {} + chromium-bidi@12.0.1(devtools-protocol@0.0.1534754): + dependencies: + devtools-protocol: 0.0.1534754 + mitt: 3.0.1 + zod: 3.25.76 + citty@0.1.6: dependencies: consola: 3.4.2 @@ -13033,6 +13303,8 @@ snapshots: data-uri-to-buffer@4.0.1: {} + data-uri-to-buffer@6.0.2: {} + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -13098,6 +13370,12 @@ snapshots: defu@6.1.4: {} + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + denque@2.1.0: {} depd@2.0.0: {} @@ -13108,6 +13386,8 @@ snapshots: detect-node-es@1.1.0: {} + devtools-protocol@0.0.1534754: {} + diff@8.0.3: {} dom-serializer@2.0.0: @@ -13213,6 +13493,10 @@ snapshots: iconv-lite: 0.6.3 optional: true + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.18.4: dependencies: graceful-fs: 4.2.11 @@ -13376,11 +13660,21 @@ snapshots: escape-string-regexp@5.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + esm@3.2.25: optional: true esprima@4.0.1: {} + estraverse@5.3.0: {} + estree-walker@1.0.1: {} estree-walker@2.0.2: {} @@ -13391,6 +13685,12 @@ snapshots: event-target-shim@5.0.1: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + events@3.3.0: {} eventsource-parser@3.0.6: {} @@ -13467,6 +13767,16 @@ snapshots: exsolve@1.0.8: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + fast-check@3.23.2: dependencies: pure-rand: 6.1.0 @@ -13475,6 +13785,8 @@ snapshots: fast-equals@5.4.0: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -13499,6 +13811,10 @@ snapshots: dependencies: walk-up-path: 4.0.0 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -13550,9 +13866,9 @@ snapshots: forwarded@0.2.0: {} - framer-motion@12.27.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + framer-motion@12.28.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - motion-dom: 12.27.5 + motion-dom: 12.28.1 motion-utils: 12.27.2 tslib: 2.8.1 optionalDependencies: @@ -13638,6 +13954,10 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + get-stream@6.0.1: {} get-stream@9.0.1: @@ -13655,6 +13975,14 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-uri@6.0.5: + dependencies: + basic-ftp: 5.1.0 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + giget@2.0.0: dependencies: citty: 0.1.6 @@ -14239,6 +14567,8 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@7.18.3: {} + lru.min@1.1.3: {} magic-string@0.25.9: @@ -14349,6 +14679,8 @@ snapshots: dependencies: minipass: 7.1.2 + mitt@3.0.1: {} + monaco-editor@0.55.1: dependencies: dompurify: 3.2.7 @@ -14369,15 +14701,15 @@ snapshots: moo@0.5.2: {} - motion-dom@12.27.5: + motion-dom@12.28.1: dependencies: motion-utils: 12.27.2 motion-utils@12.27.2: {} - motion@12.27.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + motion@12.28.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - framer-motion: 12.27.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + framer-motion: 12.28.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tslib: 2.8.1 optionalDependencies: react: 19.2.3 @@ -14447,6 +14779,8 @@ snapshots: negotiator@1.0.0: {} + netmask@2.0.2: {} + nf3@0.3.5: {} nitro-nightly@3.0.1-20260120-140218-d2383f00(@azure/identity@4.13.0)(@electric-sql/pglite@0.3.2)(chokidar@4.0.3)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.17.2)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)))(lru-cache@11.2.4)(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(rolldown-vite@7.3.1(@types/node@25.0.9)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))(rolldown@1.0.0-beta.53)(rollup@2.79.2): @@ -14724,6 +15058,24 @@ snapshots: p-map@7.0.4: {} + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + package-json-from-dist@1.0.1: {} package-manager-detector@1.6.0: {} @@ -14777,6 +15129,8 @@ snapshots: pathe@2.0.3: {} + pend@1.2.0: {} + perfect-debounce@1.0.0: {} pg-cloudflare@1.3.0: @@ -14893,6 +15247,8 @@ snapshots: process@0.11.10: {} + progress@2.0.3: {} + promise-retry@2.0.1: dependencies: err-code: 2.0.3 @@ -15017,14 +15373,51 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + pseudolocale@2.2.0: dependencies: commander: 10.0.1 + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode.js@2.3.1: {} punycode@2.3.1: {} + puppeteer-core@24.35.0: + dependencies: + '@puppeteer/browsers': 2.11.1 + chromium-bidi: 12.0.1(devtools-protocol@0.0.1534754) + debug: 4.4.3 + devtools-protocol: 0.0.1534754 + typed-query-selector: 2.12.0 + webdriver-bidi-protocol: 0.3.10 + ws: 8.19.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + pure-rand@6.1.0: {} pvtsutils@1.3.6: @@ -15638,6 +16031,15 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + strict-event-emitter@0.5.1: {} string-width@4.2.3: @@ -15747,6 +16149,27 @@ snapshots: tapable@2.3.0: {} + tar-fs@3.1.1: + dependencies: + pump: 3.0.3 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.5.2 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.1.7: + dependencies: + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + tar@7.5.6: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -15803,6 +16226,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + text-decoder@1.2.3: + dependencies: + b4a: 1.7.3 + transitivePeerDependencies: + - react-native-b4a + threads@1.7.0: dependencies: callsites: 3.1.0 @@ -15931,6 +16360,8 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typed-query-selector@2.12.0: {} + typescript@5.9.3: {} uc.micro@2.1.0: {} @@ -16072,6 +16503,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + webdriver-bidi-protocol@0.3.10: {} + webidl-conversions@4.0.2: {} webidl-conversions@7.0.0: {} @@ -16285,6 +16718,8 @@ snapshots: wrappy@1.0.2: {} + ws@8.19.0: {} + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.0 @@ -16325,6 +16760,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yoctocolors-cjs@2.1.3: {} yoctocolors@2.1.2: {} diff --git a/src/components/animate-ui/components/buttons/button.tsx b/src/components/animate-ui/components/buttons/button.tsx index 86f03588a..359c38ce7 100644 --- a/src/components/animate-ui/components/buttons/button.tsx +++ b/src/components/animate-ui/components/buttons/button.tsx @@ -14,10 +14,9 @@ const buttonVariants = cva( accent: "bg-accent text-accent-foreground shadow-xs hover:bg-accent/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + outline: "border bg-background shadow-xs hover:bg-secondary/40 hover:text-secondary-foreground", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + ghost: "hover:bg-secondary/40 hover:text-secondary-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { diff --git a/src/components/animate-ui/primitives/radix/alert-dialog.tsx b/src/components/animate-ui/primitives/radix/alert-dialog.tsx index 5382a4302..cc3d6c62a 100644 --- a/src/components/animate-ui/primitives/radix/alert-dialog.tsx +++ b/src/components/animate-ui/primitives/radix/alert-dialog.tsx @@ -81,7 +81,7 @@ function AlertDialogContent({ onOpenAutoFocus, onCloseAutoFocus, onEscapeKeyDown, - transition = { type: "spring", stiffness: 150, damping: 25 }, + transition = { type: "spring", stiffness: 250, damping: 25 }, ...props }: AlertDialogContentProps) { const initialRotation = from === "bottom" || from === "left" ? "20deg" : "-20deg"; @@ -99,6 +99,7 @@ function AlertDialogContent({ diff --git a/src/components/animate-ui/primitives/radix/dialog.tsx b/src/components/animate-ui/primitives/radix/dialog.tsx index 63ce05599..9b95c65c5 100644 --- a/src/components/animate-ui/primitives/radix/dialog.tsx +++ b/src/components/animate-ui/primitives/radix/dialog.tsx @@ -77,7 +77,7 @@ function DialogContent({ onEscapeKeyDown, onPointerDownOutside, onInteractOutside, - transition = { type: "spring", stiffness: 150, damping: 25 }, + transition = { type: "spring", stiffness: 250, damping: 25 }, ...props }: DialogContentProps) { const initialRotation = from === "bottom" || from === "left" ? "20deg" : "-20deg"; @@ -97,6 +97,7 @@ function DialogContent({ diff --git a/src/components/resume/shared/get-section-component.tsx b/src/components/resume/shared/get-section-component.tsx index c35fe7e1e..35eee7cda 100644 --- a/src/components/resume/shared/get-section-component.tsx +++ b/src/components/resume/shared/get-section-component.tsx @@ -1,5 +1,7 @@ import { match } from "ts-pattern"; -import type { SectionType } from "@/schema/resume/data"; +import type { CustomSectionItem, SectionType } from "@/schema/resume/data"; +import { cn } from "@/utils/style"; +import { useResumeStore } from "../store/resume"; import { AwardsItem } from "./items/awards-item"; import { CertificationsItem } from "./items/certifications-item"; import { EducationItem } from "./items/education-item"; @@ -12,7 +14,6 @@ import { PublicationsItem } from "./items/publications-item"; import { ReferencesItem } from "./items/references-item"; import { SkillsItem } from "./items/skills-item"; import { VolunteerItem } from "./items/volunteer-item"; -import { PageCustomSection } from "./page-custom"; import { PageSection } from "./page-section"; import { PageSummary } from "./page-summary"; @@ -21,6 +22,44 @@ type SectionComponentProps = { itemClassName?: string; }; +// Helper to render item component based on type +function renderItemByType(type: SectionType, item: CustomSectionItem, itemClassName?: string) { + return match(type) + .with("profiles", () => ( + [0])} className={itemClassName} /> + )) + .with("experience", () => ( + [0])} className={itemClassName} /> + )) + .with("education", () => ( + [0])} className={itemClassName} /> + )) + .with("projects", () => ( + [0])} className={itemClassName} /> + )) + .with("skills", () => [0])} className={itemClassName} />) + .with("languages", () => ( + [0])} className={itemClassName} /> + )) + .with("interests", () => ( + [0])} className={itemClassName} /> + )) + .with("awards", () => [0])} className={itemClassName} />) + .with("certifications", () => ( + [0])} className={itemClassName} /> + )) + .with("publications", () => ( + [0])} className={itemClassName} /> + )) + .with("volunteer", () => ( + [0])} className={itemClassName} /> + )) + .with("references", () => ( + [0])} className={itemClassName} /> + )) + .exhaustive(); +} + export function getSectionComponent( section: "summary" | SectionType | (string & {}), { sectionClassName, itemClassName }: SectionComponentProps = {}, @@ -127,9 +166,39 @@ export function getSectionComponent( return ReferencesSection; }) .otherwise(() => { - const CustomSection = ({ id }: { id: string }) => ( - - ); - return CustomSection; + // Custom section - render based on its type + const CustomSectionComponent = ({ id }: { id: string }) => { + const customSection = useResumeStore((state) => state.resume.data.customSections.find((s) => s.id === id)); + + if (!customSection) return null; + if (customSection.hidden) return null; + if (customSection.items.length === 0) return null; + + const visibleItems = customSection.items.filter((item) => !item.hidden); + if (visibleItems.length === 0) return null; + + return ( +
+
{customSection.title}
+ +
+ {visibleItems.map((item) => ( +
+ {renderItemByType(customSection.type, item, itemClassName)} +
+ ))} +
+
+ ); + }; + return CustomSectionComponent; }); } diff --git a/src/components/resume/shared/page-custom.tsx b/src/components/resume/shared/page-custom.tsx deleted file mode 100644 index 54bc64e5a..000000000 --- a/src/components/resume/shared/page-custom.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { TiptapContent } from "@/components/input/rich-input"; -import { cn } from "@/utils/style"; -import { useResumeStore } from "../store/resume"; - -type PageCustomSectionProps = { - sectionId: string; - className?: string; -}; - -export function PageCustomSection({ sectionId, className }: PageCustomSectionProps) { - const section = useResumeStore((state) => - state.resume.data.customSections.find((section) => section.id === sectionId), - ); - - // biome-ignore lint/complexity/noUselessFragments: render empty fragment, instead of null - if (!section) return <>; - - return ( -
-
{section.title}
- -
- -
-
- ); -} diff --git a/src/components/resume/templates/azurill.tsx b/src/components/resume/templates/azurill.tsx index 42ef46088..aa33b9eaa 100644 --- a/src/components/resume/templates/azurill.tsx +++ b/src/components/resume/templates/azurill.tsx @@ -41,7 +41,7 @@ export function AzurillTemplate({ pageIndex, pageLayout }: TemplateProps) { const { main, sidebar, fullWidth } = pageLayout; return ( -
+
{isFirstPage &&
}
diff --git a/src/components/resume/templates/bronzor.tsx b/src/components/resume/templates/bronzor.tsx index 50b1590ec..6fb2770d0 100644 --- a/src/components/resume/templates/bronzor.tsx +++ b/src/components/resume/templates/bronzor.tsx @@ -23,7 +23,7 @@ export function BronzorTemplate({ pageIndex, pageLayout }: TemplateProps) { const { main, sidebar, fullWidth } = pageLayout; return ( -
+
{isFirstPage &&
}
diff --git a/src/components/resume/templates/kakuna.tsx b/src/components/resume/templates/kakuna.tsx index 43d553d40..b923506a1 100644 --- a/src/components/resume/templates/kakuna.tsx +++ b/src/components/resume/templates/kakuna.tsx @@ -20,7 +20,7 @@ export function KakunaTemplate({ pageIndex, pageLayout }: TemplateProps) { const { main, sidebar, fullWidth } = pageLayout; return ( -
+
{isFirstPage &&
}
diff --git a/src/components/resume/templates/onyx.tsx b/src/components/resume/templates/onyx.tsx index 750afeeb7..48323da60 100644 --- a/src/components/resume/templates/onyx.tsx +++ b/src/components/resume/templates/onyx.tsx @@ -17,7 +17,7 @@ export function OnyxTemplate({ pageIndex, pageLayout }: TemplateProps) { const { main, sidebar, fullWidth } = pageLayout; return ( -
+
{isFirstPage &&
}
diff --git a/src/components/resume/templates/pikachu.tsx b/src/components/resume/templates/pikachu.tsx index cc80836b2..e59e893da 100644 --- a/src/components/resume/templates/pikachu.tsx +++ b/src/components/resume/templates/pikachu.tsx @@ -24,7 +24,7 @@ export function PikachuTemplate({ pageIndex, pageLayout }: TemplateProps) { const { main, sidebar, fullWidth } = pageLayout; return ( -
+
+ ); +} + +function CustomSectionDropdownMenu({ section }: { section: CustomSection }) { + const confirm = useConfirm(); + const { openDialog } = useDialogStore(); + const updateResumeData = useResumeStore((state) => state.updateResumeData); + + const onToggleSectionVisibility = () => { updateResumeData((draft) => { const sectionIndex = draft.customSections.findIndex((_section) => _section.id === section.id); if (sectionIndex === -1) return; @@ -72,6 +169,14 @@ function CustomSectionItem({ section }: { section: CustomSection }) { }); }; + const onUpdateSection = () => { + openDialog("resume.sections.custom.update", section); + }; + + const onDuplicateSection = () => { + openDialog("resume.sections.custom.create", section); + }; + const onSetColumns = (value: string) => { updateResumeData((draft) => { const sectionIndex = draft.customSections.findIndex((_section) => _section.id === section.id); @@ -80,7 +185,7 @@ function CustomSectionItem({ section }: { section: CustomSection }) { }); }; - const onDelete = async () => { + const onDeleteSection = async () => { const confirmed = await confirm("Are you sure you want to delete this custom section?", { confirmText: "Delete", cancelText: "Cancel", @@ -90,7 +195,6 @@ function CustomSectionItem({ section }: { section: CustomSection }) { updateResumeData((draft) => { draft.customSections = draft.customSections.filter((_section) => _section.id !== section.id); - // remove from layout draft.metadata.layout.pages = draft.metadata.layout.pages.map((page) => ({ ...page, main: page.main.filter((id) => id !== section.id), @@ -100,84 +204,60 @@ function CustomSectionItem({ section }: { section: CustomSection }) { }; return ( - - + + + + - - - - + + + + {section.hidden ? : } + {section.hidden ? Show : Hide} + - - - - {section.hidden ? : } - {section.hidden ? Show : Hide} - + + + Update + - - - Update - + + + Duplicate + - - - Duplicate - + + + + Columns + - - - - Columns - + + + {[1, 2, 3, 4, 5, 6].map((column) => ( + + + + ))} + + + + - - - {[1, 2, 3, 4, 5, 6].map((column) => ( - - - - ))} - - - - + - - - - - - Delete - - - - - + + + + Delete + + + + ); } diff --git a/src/routes/builder/$resumeId/-sidebar/left/shared/section-item.tsx b/src/routes/builder/$resumeId/-sidebar/left/shared/section-item.tsx index fc5e101fe..325d578cf 100644 --- a/src/routes/builder/$resumeId/-sidebar/left/shared/section-item.tsx +++ b/src/routes/builder/$resumeId/-sidebar/left/shared/section-item.tsx @@ -1,37 +1,167 @@ import { Trans } from "@lingui/react/macro"; import { + ArrowBendUpRightIcon, CopySimpleIcon, DotsSixVerticalIcon, DotsThreeVerticalIcon, EyeClosedIcon, EyeIcon, + FileIcon, + FolderPlusIcon, PencilSimpleLineIcon, + PlusCircleIcon, PlusIcon, TrashSimpleIcon, } from "@phosphor-icons/react"; import { Reorder, useDragControls } from "motion/react"; +import { useMemo } from "react"; +import { Button, type ButtonProps } from "@/components/animate-ui/components/buttons/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/animate-ui/components/radix/dropdown-menu"; import { useResumeStore } from "@/components/resume/store/resume"; import { useDialogStore } from "@/dialogs/store"; import { useConfirm } from "@/hooks/use-confirm"; import type { SectionItem as SectionItemType, SectionType } from "@/schema/resume/data"; +import { + addItemToSection, + createCustomSectionWithItem, + createPageWithSection, + getCompatibleMoveTargets, + getSourceSectionTitle, + removeItemFromSource, +} from "@/utils/resume/move-item"; import { cn } from "@/utils/style"; +// ============================================================================ +// MoveItemSubmenu Component +// ============================================================================ + +type MoveItemSubmenuProps = { + type: SectionType; + item: SectionItemType; + customSectionId?: string; +}; + +/** + * Submenu component for moving items between sections/pages. + * Displays compatible targets grouped by page with options to: + * - Move to existing compatible section + * - Create new section on existing page + * - Create new page with new section + */ +function MoveItemSubmenu({ type, item, customSectionId }: MoveItemSubmenuProps) { + const resume = useResumeStore((state) => state.resume); + const updateResumeData = useResumeStore((state) => state.updateResumeData); + + /** Compute compatible move targets grouped by page */ + const moveTargets = useMemo( + () => getCompatibleMoveTargets(resume.data, type, customSectionId), + [resume.data, type, customSectionId], + ); + + /** Get the current section's title (used when creating new sections) */ + const currentSectionTitle = useMemo( + () => getSourceSectionTitle(resume.data, type, customSectionId), + [resume.data, type, customSectionId], + ); + + /** Handler: Move item to an existing section */ + const handleMoveToSection = (targetSectionId: string) => { + updateResumeData((draft) => { + const removedItem = removeItemFromSource(draft, item.id, type, customSectionId); + if (!removedItem) return; + addItemToSection(draft, removedItem, targetSectionId, type); + }); + }; + + /** Handler: Create a new custom section on an existing page and move the item there */ + const handleNewSectionOnPage = (pageIndex: number) => { + updateResumeData((draft) => { + const removedItem = removeItemFromSource(draft, item.id, type, customSectionId); + if (!removedItem) return; + createCustomSectionWithItem(draft, removedItem, type, currentSectionTitle, pageIndex); + }); + }; + + /** Handler: Create a new page with a new custom section and move the item there */ + const handleNewPage = () => { + updateResumeData((draft) => { + const removedItem = removeItemFromSource(draft, item.id, type, customSectionId); + if (!removedItem) return; + createPageWithSection(draft, removedItem, type, currentSectionTitle); + }); + }; + + return ( + + + + Move to + + + + {/* Render each page as a submenu */} + {moveTargets.map(({ pageIndex, sections }) => ( + + + + Page {pageIndex + 1} + + + + {/* Existing compatible sections on this page */} + {sections.map(({ sectionId, sectionTitle }) => ( + handleMoveToSection(sectionId)}> + {sectionTitle} + + ))} + + {/* Separator if there are existing sections */} + {sections.length > 0 && } + + {/* Option to create a new section on this page */} + handleNewSectionOnPage(pageIndex)}> + + New Section + + + + ))} + + + + {/* Option to create a new page with a new section */} + + + New Page + + + + ); +} + +// ============================================================================ +// SectionItem Component +// ============================================================================ + type Props = { type: SectionType; item: T; title: string; subtitle?: string; + customSectionId?: string; }; -export function SectionItem({ type, item, title, subtitle }: Props) { +export function SectionItem({ type, item, title, subtitle, customSectionId }: Props) { const confirm = useConfirm(); const controls = useDragControls(); const { openDialog } = useDialogStore(); @@ -39,20 +169,30 @@ export function SectionItem({ type, item, title, subt const onToggleVisibility = () => { updateResumeData((draft) => { - const section = draft.sections[type]; - if (!("items" in section)) return; - const index = section.items.findIndex((_item) => _item.id === item.id); - if (index === -1) return; - section.items[index].hidden = !section.items[index].hidden; + if (customSectionId) { + const section = draft.customSections.find((s) => s.id === customSectionId); + if (!section) return; + const index = section.items.findIndex((_item) => _item.id === item.id); + if (index === -1) return; + section.items[index].hidden = !section.items[index].hidden; + } else { + const section = draft.sections[type]; + if (!("items" in section)) return; + const index = section.items.findIndex((_item) => _item.id === item.id); + if (index === -1) return; + section.items[index].hidden = !section.items[index].hidden; + } }); }; const onUpdate = () => { - openDialog(`resume.sections.${type}.update`, item); + // Type assertion needed because TypeScript can't narrow the union type through template literals + openDialog(`resume.sections.${type}.update`, { item, customSectionId } as never); }; const onDuplicate = () => { - openDialog(`resume.sections.${type}.create`, item); + // Type assertion needed because TypeScript can't narrow the union type through template literals + openDialog(`resume.sections.${type}.create`, { item, customSectionId } as never); }; const onDelete = async () => { @@ -64,11 +204,19 @@ export function SectionItem({ type, item, title, subt if (!confirmed) return; updateResumeData((draft) => { - const section = draft.sections[type]; - if (!("items" in section)) return; - const index = section.items.findIndex((_item) => _item.id === item.id); - if (index === -1) return; - section.items.splice(index, 1); + if (customSectionId) { + const section = draft.customSections.find((s) => s.id === customSectionId); + if (!section) return; + const index = section.items.findIndex((_item) => _item.id === item.id); + if (index === -1) return; + section.items.splice(index, 1); + } else { + const section = draft.sections[type]; + if (!("items" in section)) return; + const index = section.items.findIndex((_item) => _item.id === item.id); + if (index === -1) return; + section.items.splice(index, 1); + } }); }; @@ -84,7 +232,7 @@ export function SectionItem({ type, item, title, subt className="group relative flex h-18 select-none border-b" >
{ e.preventDefault(); controls.start(e); @@ -96,7 +244,7 @@ export function SectionItem({ type, item, title, subt @@ -131,6 +279,8 @@ export function SectionItem({ type, item, title, subt Duplicate + + @@ -147,26 +297,32 @@ export function SectionItem({ type, item, title, subt ); } -type AddButtonProps = { +type AddButtonProps = Omit & { type: SectionType | "custom"; - children: React.ReactNode; + customSectionId?: string; }; -export function SectionAddItemButton({ type, children }: AddButtonProps) { +export function SectionAddItemButton({ type, customSectionId, className, children, ...props }: AddButtonProps) { const { openDialog } = useDialogStore(); const handleAdd = () => { - openDialog(`resume.sections.${type}.create`, undefined); + if (type === "custom") { + openDialog("resume.sections.custom.create", undefined); + } else { + openDialog(`resume.sections.${type}.create`, customSectionId ? { customSectionId } : undefined); + } }; return ( - + ); } diff --git a/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx b/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx index 682d88494..9c355b42e 100644 --- a/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx +++ b/src/routes/builder/$resumeId/-sidebar/right/sections/layout/pages.tsx @@ -289,7 +289,7 @@ function LayoutColumn({ pageIndex, columnId, items, disabled = false }: LayoutCo return ( -
+
{getColumnLabel(columnId)}
( data-dragging={isDragging ? "true" : undefined} className={cn( "group/item flex cursor-grab touch-none select-none items-center gap-x-2 rounded-md border border-border bg-background px-2 py-1.5 font-medium text-sm transition-all duration-200 ease-out", - "hover:bg-secondary/20 active:cursor-grabbing active:border-primary/60 active:bg-secondary/20", + "hover:bg-secondary/40 active:cursor-grabbing active:border-primary/60 active:bg-secondary/40", "data-[overlay=true]:cursor-grabbing data-[overlay=true]:border-primary/60 data-[overlay=true]:bg-background", "data-[dragging=true]:cursor-grabbing data-[dragging=true]:border-primary/60 data-[dragging=true]:bg-background", className, diff --git a/src/schema/resume/data.ts b/src/schema/resume/data.ts index f4781d4f6..706ce5d60 100644 --- a/src/schema/resume/data.ts +++ b/src/schema/resume/data.ts @@ -308,11 +308,46 @@ export type SectionType = keyof z.infer; export type SectionData = z.infer[T]; export type SectionItem = SectionData["items"][number]; +export const sectionTypeSchema = z.enum([ + "profiles", + "experience", + "education", + "projects", + "skills", + "languages", + "interests", + "awards", + "certifications", + "publications", + "volunteer", + "references", +]); + +export const customSectionItemSchema = z.union([ + profileItemSchema, + experienceItemSchema, + educationItemSchema, + projectItemSchema, + skillItemSchema, + languageItemSchema, + interestItemSchema, + awardItemSchema, + certificationItemSchema, + publicationItemSchema, + volunteerItemSchema, + referenceItemSchema, +]); + +export type CustomSectionItem = z.infer; + export const customSectionSchema = baseSectionSchema.extend({ id: z.string().describe("The unique identifier for the custom section. Usually generated as a UUID."), - content: z - .string() - .describe("The content of the custom section. This should be a HTML-formatted string. Leave blank to hide."), + type: sectionTypeSchema.describe( + "The type of items this custom section contains. Determines which item schema and form fields to use.", + ), + items: z + .array(customSectionItemSchema) + .describe("The items to display in the custom section. Items follow the schema of the section type."), }); export type CustomSection = z.infer; diff --git a/src/styles/globals.css b/src/styles/globals.css index 16169f2e2..4d0e5b474 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -165,7 +165,6 @@ } .resume-preview-container { - .page-section, .page-section .section-item { break-inside: avoid; } diff --git a/src/utils/env.ts b/src/utils/env.ts index 3c1187b52..9b233ab4c 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -15,9 +15,7 @@ export const env = createEnv({ PRINTER_APP_URL: z.url({ protocol: /https?/ }).optional(), // Printer - GOTENBERG_ENDPOINT: z.url({ protocol: /https?/ }), - GOTENBERG_USERNAME: z.string().min(1).optional(), - GOTENBERG_PASSWORD: z.string().min(1).optional(), + PRINTER_ENDPOINT: z.url({ protocol: /wss?/ }), // Database DATABASE_URL: z.url({ protocol: /postgres(ql)?/ }), diff --git a/src/utils/resume/move-item.ts b/src/utils/resume/move-item.ts new file mode 100644 index 000000000..162c55c29 --- /dev/null +++ b/src/utils/resume/move-item.ts @@ -0,0 +1,269 @@ +import type { WritableDraft } from "immer"; +import type { CustomSection, ResumeData, SectionItem, SectionType } from "@/schema/resume/data"; +import { generateId } from "@/utils/string"; +import { getSectionTitle as getDefaultSectionTitle } from "./section"; + +// ============================================================================ +// Types +// ============================================================================ + +/** Target section that an item can be moved to */ +export type MoveTargetSection = { + sectionId: string; + sectionTitle: string; + /** Whether this is a standard section (true) or custom section (false) */ + isStandard: boolean; +}; + +/** Page with its compatible sections for the move menu */ +export type MoveTargetPage = { + pageIndex: number; + sections: MoveTargetSection[]; +}; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/** + * Checks if a section ID belongs to a standard section. + * Standard sections have predefined keys like "experience", "education", etc. + */ +function isStandardSectionId(sectionId: string): sectionId is SectionType { + const standardSections: SectionType[] = [ + "profiles", + "experience", + "education", + "projects", + "skills", + "languages", + "interests", + "awards", + "certifications", + "publications", + "volunteer", + "references", + ]; + + return standardSections.includes(sectionId as SectionType); +} + +// ============================================================================ +// Public API +// ============================================================================ + +/** + * Gets the title of a section. + * For standard sections, returns the localized default title. + * For custom sections, returns the user-defined title. + * + * @param resumeData - The resume data object + * @param type - The section type + * @param customSectionId - The custom section ID (if applicable) + * @returns The section title + */ +export function getSourceSectionTitle(resumeData: ResumeData, type: SectionType, customSectionId?: string): string { + if (customSectionId) { + const customSection = resumeData.customSections.find((s) => s.id === customSectionId); + return customSection?.title ?? getDefaultSectionTitle(type); + } + + return getDefaultSectionTitle(type); +} + +/** + * Finds all compatible sections an item can be moved to. + * A section is compatible if it has the same type as the source section. + * + * @param resumeData - The resume data object + * @param sourceType - The type of the source section + * @param sourceSectionId - The ID of the source section (custom section ID or undefined for standard) + * @returns Array of pages with their compatible sections + */ +export function getCompatibleMoveTargets( + resumeData: ResumeData, + sourceType: SectionType, + sourceSectionId: string | undefined, +): MoveTargetPage[] { + const { pages } = resumeData.metadata.layout; + const result: MoveTargetPage[] = []; + + for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) { + const page = pages[pageIndex]; + const allSectionIds = [...page.main, ...page.sidebar]; + const compatibleSections: MoveTargetSection[] = []; + + for (const sectionId of allSectionIds) { + // Skip the source section itself + if (sectionId === sourceSectionId || (sourceSectionId === undefined && sectionId === sourceType)) { + continue; + } + + // Check if it's a standard section with matching type + if (isStandardSectionId(sectionId) && sectionId === sourceType) { + compatibleSections.push({ + sectionId, + sectionTitle: getDefaultSectionTitle(sectionId), + isStandard: true, + }); + continue; + } + + // Check if it's a custom section with matching type + const customSection = resumeData.customSections.find((s) => s.id === sectionId); + if (customSection && customSection.type === sourceType) { + compatibleSections.push({ + sectionId: customSection.id, + sectionTitle: customSection.title, + isStandard: false, + }); + } + } + + result.push({ pageIndex, sections: compatibleSections }); + } + + return result; +} + +/** + * Removes an item from its source section (standard or custom). + * + * @param draft - The immer draft of resume data + * @param itemId - The ID of the item to remove + * @param type - The section type + * @param customSectionId - The custom section ID (if applicable) + * @returns The removed item, or null if not found + */ +export function removeItemFromSource( + draft: WritableDraft, + itemId: string, + type: SectionType, + customSectionId?: string, +): SectionItem | null { + if (customSectionId) { + const section = draft.customSections.find((s) => s.id === customSectionId); + if (!section) return null; + + const index = section.items.findIndex((item) => item.id === itemId); + if (index === -1) return null; + + const [removed] = section.items.splice(index, 1); + return removed as SectionItem; + } + + const section = draft.sections[type]; + if (!("items" in section)) return null; + + const index = section.items.findIndex((item) => item.id === itemId); + if (index === -1) return null; + + const [removed] = section.items.splice(index, 1); + return removed as SectionItem; +} + +/** + * Adds an item to a target section. + * + * @param draft - The immer draft of resume data + * @param item - The item to add + * @param targetSectionId - The target section ID + * @param type - The section type + */ +export function addItemToSection( + draft: WritableDraft, + item: SectionItem, + targetSectionId: string, + type: SectionType, +): void { + // Check if target is a standard section + if (isStandardSectionId(targetSectionId) && targetSectionId === type) { + const section = draft.sections[type]; + if ("items" in section) { + section.items.push(item as never); + } + return; + } + + // Otherwise, it's a custom section + const customSection = draft.customSections.find((s) => s.id === targetSectionId); + if (customSection) { + customSection.items.push(item as never); + } +} + +/** + * Creates a new custom section with the given item and adds it to the specified page. + * + * @param draft - The immer draft of resume data + * @param item - The item to add to the new section + * @param type - The section type for the new custom section + * @param sectionTitle - The title for the new custom section + * @param targetPageIndex - The page index to add the section to + * @returns The ID of the newly created custom section + */ +export function createCustomSectionWithItem( + draft: WritableDraft, + item: SectionItem, + type: SectionType, + sectionTitle: string, + targetPageIndex: number, +): string { + const newSectionId = generateId(); + + // Create the new custom section + const newSection: CustomSection = { + id: newSectionId, + type, + title: sectionTitle, + columns: 1, + hidden: false, + items: [item as never], + }; + + draft.customSections.push(newSection as WritableDraft); + + // Add the section to the target page's main column + const page = draft.metadata.layout.pages[targetPageIndex]; + if (page) { + page.main.push(newSectionId); + } + + return newSectionId; +} + +/** + * Creates a new page with a custom section containing the given item. + * + * @param draft - The immer draft of resume data + * @param item - The item to add to the new section + * @param type - The section type for the new custom section + * @param sectionTitle - The title for the new custom section + */ +export function createPageWithSection( + draft: WritableDraft, + item: SectionItem, + type: SectionType, + sectionTitle: string, +): void { + const newSectionId = generateId(); + + // Create the new custom section + const newSection: CustomSection = { + id: newSectionId, + type, + title: sectionTitle, + columns: 1, + hidden: false, + items: [item as never], + }; + + draft.customSections.push(newSection as WritableDraft); + + // Create the new page with the section in the main column + draft.metadata.layout.pages.push({ + fullWidth: false, + main: [newSectionId], + sidebar: [], + }); +}