diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index cb55d89..ea1950a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,5 +1,9 @@
# Dependabot configuration
# Docs: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+#
+# Grouping policy: collapse all minor+patch bumps into ONE PR per ecosystem so
+# the review queue stays small; majors get their own grouped PR per ecosystem so
+# breaking changes still get individual scrutiny.
version: 2
updates:
@@ -19,47 +23,17 @@ updates:
- "dependencies"
- "javascript"
groups:
- # Keep TanStack libs in lockstep — they release as a family
- tanstack:
+ npm-minor-patch:
patterns:
- - "@tanstack/*"
- # Tauri JS bindings
- tauri:
+ - "*"
+ update-types:
+ - "minor"
+ - "patch"
+ npm-major:
patterns:
- - "@tauri-apps/*"
- # Test stack
- testing:
- patterns:
- - "vitest"
- - "@vitest/*"
- - "@testing-library/*"
- - "jsdom"
- - "@playwright/*"
- # Build/lint tooling
- tooling:
- patterns:
- - "@biomejs/*"
- - "turbo"
- - "vite"
- - "@vitejs/*"
- - "typescript"
- # React core
- react:
- patterns:
- - "react"
- - "react-dom"
- - "@types/react"
- - "@types/react-dom"
- # Semantic-release ecosystem
- semantic-release:
- patterns:
- - "semantic-release"
- - "@semantic-release/*"
- # Commitlint + husky
- commit-tooling:
- patterns:
- - "@commitlint/*"
- - "husky"
+ - "*"
+ update-types:
+ - "major"
# ─── Rust / Cargo (Tauri desktop shell) ───────────────────────────────────
- package-ecosystem: "cargo"
@@ -77,16 +51,17 @@ updates:
- "dependencies"
- "rust"
groups:
- tauri-core:
+ cargo-minor-patch:
patterns:
- - "tauri"
- - "tauri-*"
- tokio:
+ - "*"
+ update-types:
+ - "minor"
+ - "patch"
+ cargo-major:
patterns:
- - "tokio"
- - "tokio-*"
- - "futures"
- - "futures-*"
+ - "*"
+ update-types:
+ - "major"
# ─── GitHub Actions workflow pins ─────────────────────────────────────────
- package-ecosystem: "github-actions"
@@ -103,3 +78,7 @@ updates:
labels:
- "dependencies"
- "github-actions"
+ groups:
+ github-actions:
+ patterns:
+ - "*"
diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index 4591b1b..9b0394d 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -16,8 +16,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- - uses: docker/setup-qemu-action@v3
- - uses: docker/setup-buildx-action@v3
+ - uses: docker/setup-qemu-action@v4
+ - uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v3
with:
@@ -35,7 +35,7 @@ jobs:
type=raw,value=latest
type=sha,format=short
- - uses: docker/build-push-action@v6
+ - uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
@@ -44,3 +44,34 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+
+ publish-chart:
+ name: Package & push Helm chart to GHCR
+ runs-on: ubuntu-latest
+ needs: [publish]
+ if: startsWith(github.ref, 'refs/tags/')
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: azure/setup-helm@v4
+
+ - name: Derive chart version
+ id: version
+ run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
+
+ - name: Log in to GHCR (Helm OCI)
+ run: |
+ echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \
+ --username "${{ github.actor }}" \
+ --password-stdin
+
+ - name: Package chart
+ run: |
+ helm package charts/openconcho \
+ --version "${{ steps.version.outputs.VERSION }}" \
+ --app-version "${{ steps.version.outputs.VERSION }}"
+
+ - name: Push chart
+ run: |
+ helm push "openconcho-${{ steps.version.outputs.VERSION }}.tgz" \
+ oci://ghcr.io/${{ github.repository_owner }}/charts
diff --git a/AGENTS.md b/AGENTS.md
index eb70138..a802716 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -16,6 +16,11 @@ Frontend UI for self-hosted Honcho instances — browse memories, peers, session
| `make typecheck` | tsc --noEmit |
| `make test` | Vitest (unit + integration), excludes `e2e/` |
| `make test-e2e` | Playwright e2e (uncached) |
+| `make smoke-docker` | Local: build image + hermetic smoke test of the `/api` proxy (Docker required) |
+| `make up` | Run the web container from source (dev-forward, builds) at :8080 |
+| `make prod` | Run the web container from the published image (pulls `ghcr…:latest`) |
+| `make down` | Stop + remove the web container (dev or prod) |
+| `make clean` | `down` + remove the locally built image |
| `make check` | lint + typecheck + test |
| `pnpm --filter @openconcho/desktop cargo-check` | Local Rust/Tauri compile check before pushing desktop changes |
| `pnpm --filter @openconcho/web generate:api` | Regen `src/api/schema.d.ts` from `openapi.json` |
@@ -33,6 +38,7 @@ Frontend UI for self-hosted Honcho instances — browse memories, peers, session
| `packages/web/src/test/` | Vitest unit/integration tests + setup |
| `packages/web/e2e/` | Playwright e2e specs |
| `packages/desktop/` | Tauri shell that bundles the built web app |
+| `charts/openconcho/` | Helm 3 chart for self-hosting on Kubernetes (OCI artifact on GHCR) |
| `.claude/rules/` | Coding conventions (auto-loaded; stack-agnostic, applies to all agents) |
| `docs/` | Architecture and references |
@@ -64,6 +70,7 @@ Before pushing any change under `packages/desktop/**` or `packages/desktop/src-t
## Key Constraints
- **No hardcoded URLs** — connection config lives in `localStorage` under `openconcho:instances` (multi-instance store; legacy `openconcho:config` is auto-migrated)
+- **Web CORS via a same-origin `/api` proxy** — the web build issues all Honcho calls to `/api/*` with an `X-Honcho-Upstream` header (the active instance's URL); nginx (docker) and a Vite middleware (dev) forward server-side. Transport is resolved by `dispatchFor` in `src/lib/dispatch.ts`: web → relative `/api` + header; Tauri → absolute URL + reqwest. Optional `OPENCONCHO_UPSTREAM_ALLOWLIST` guards the proxy when exposed.
- **Local git hooks** — `.husky/pre-commit` runs a secret scan + Biome on staged files; `.husky/pre-push` runs `pnpm check`. Your commits and pushes trigger these.
- **TanStack Router flat-route params** — always cast `params` as `as never` at `navigate()` and `` callsites
- **`framer-motion` Variants typing** — import `type Variants` and annotate objects; never use `as const` on variant objects
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 281204a..36e1884 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,47 @@
+# [0.15.0](https://github.com/offendingcommit/openconcho/compare/v0.14.0...v0.15.0) (2026-06-03)
+
+
+### Bug Fixes
+
+* **helm:** guard tmpfs blocks when empty, cap volume names at 63 chars ([d5a65d7](https://github.com/offendingcommit/openconcho/commit/d5a65d73b59378f5ce39bf76e0572da478cecbda))
+* **helm:** pdb mutual exclusion, ingress null rules guard, hpa nil utilization guard ([b4939bd](https://github.com/offendingcommit/openconcho/commit/b4939bd57f2dba5ebca9efcd42901457512e70e4))
+* **helm:** pin busybox:1.36, add -T 10 timeout, use --spider, add activeDeadlineSeconds ([8fac5d0](https://github.com/offendingcommit/openconcho/commit/8fac5d060f45b68141917efad4afe499ca2fda56))
+* **helm:** use http://json-schema.org/draft-07/schema# for Helm compatibility ([8d41455](https://github.com/offendingcommit/openconcho/commit/8d41455e39db51617d7476e5cc48577eb7fff158))
+
+
+### Features
+
+* **helm:** add _helpers.tpl with name, label, and imageTag partials ([0268275](https://github.com/offendingcommit/openconcho/commit/02682750ab766851570eae58eb0b92761b98724f))
+* **helm:** add Deployment template with read-only FS, tmpfs, probes ([514e1d4](https://github.com/offendingcommit/openconcho/commit/514e1d46c0248bfae5da1f2ceb12ca8799a81468))
+* **helm:** add NOTES.txt with access instructions and NetworkPolicy/Ingress warning ([ce211df](https://github.com/offendingcommit/openconcho/commit/ce211df48cc59dfe933eb7a1b1415591b0e9f7fa))
+* **helm:** add optional HPA, PDB, and NetworkPolicy templates ([b0b648b](https://github.com/offendingcommit/openconcho/commit/b0b648bdcf64732c0a713bd8e45077c5f1b39ba6))
+* **helm:** add optional Ingress template ([9aa106c](https://github.com/offendingcommit/openconcho/commit/9aa106cede7d5719ee2cbc48c1c677491deea568))
+* **helm:** add Service and ServiceAccount templates ([ee916ea](https://github.com/offendingcommit/openconcho/commit/ee916eabc485f37cdc56ffbdd8d9004f33f3a7b7))
+* **helm:** add test-healthz and test-spa-root helm test jobs ([ee4630e](https://github.com/offendingcommit/openconcho/commit/ee4630e79ca588ee0f9cb167ac0f58ae4b8223cc))
+* **helm:** chart scaffold — Chart.yaml, values, schema ([4112270](https://github.com/offendingcommit/openconcho/commit/411227046a3dee125a555a0d1a426afed0e74ec3))
+
+# [0.14.0](https://github.com/offendingcommit/openconcho/compare/v0.13.1...v0.14.0) (2026-06-02)
+
+
+### Bug Fixes
+
+* **docker:** derive nginx resolver from container DNS ([66b299a](https://github.com/offendingcommit/openconcho/commit/66b299a28e912bc2f8c2922b40292696c4f7d81a))
+* **docker:** drop dead HONCHO_UPSTREAM and same-origin default ([a2854ab](https://github.com/offendingcommit/openconcho/commit/a2854ab8ea0a9eec2a06838fb394a0264f7dd80d))
+* **web:** enforce upstream allowlist in vite dev proxy ([b4fac95](https://github.com/offendingcommit/openconcho/commit/b4fac95f37da3985dbc4fbf64d04dd509ec86c2c))
+* **web:** raise connection-test timeout for cold upstreams ([409d7d8](https://github.com/offendingcommit/openconcho/commit/409d7d8be7f5cc94421dce32a54105ea48bfd44b))
+* **web:** strip content-encoding from vite dev proxy responses ([6b602c0](https://github.com/offendingcommit/openconcho/commit/6b602c05bb81721dfc102b3f97112b2cf58d4d60))
+
+
+### Features
+
+* **docker:** header-driven /api reverse proxy in nginx ([753c978](https://github.com/offendingcommit/openconcho/commit/753c978f56dab61d0c15b25b56ecf438cdc5ae88))
+* **docker:** render SSRF allowlist map from env ([0af1ad9](https://github.com/offendingcommit/openconcho/commit/0af1ad923cd2aa61a201d65ce4f19acb13858790))
+* **docker:** split compose into dev-forward build and prod pull ([c9bd2db](https://github.com/offendingcommit/openconcho/commit/c9bd2db07d84e0eedffeadcc6f2bc15c628eb251))
+* **web:** add dispatchFor transport helper for same-origin proxy ([9945e4c](https://github.com/offendingcommit/openconcho/commit/9945e4cf148aec6fc47bb853e8661c339c52ff32))
+* **web:** dev /api proxy middleware mirroring nginx ([ab8a1ba](https://github.com/offendingcommit/openconcho/commit/ab8a1ba866728ff972544c1d912fed59ba03a4a7))
+* **web:** route checkConnection and discovery through the proxy ([9893230](https://github.com/offendingcommit/openconcho/commit/9893230cde3d11ce73350bd12fffae236ee9adff))
+* **web:** route web build through same-origin /api proxy ([0935099](https://github.com/offendingcommit/openconcho/commit/0935099bc28468a21183f5f03105645f4ac8aa8a))
+
## [0.13.1](https://github.com/offendingcommit/openconcho/compare/v0.13.0...v0.13.1) (2026-05-29)
diff --git a/Dockerfile b/Dockerfile
index 8c15d47..6ac2e92 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -37,9 +37,10 @@ COPY --chown=101:101 docker/nginx.conf.template /etc/nginx/templates/default.con
# --chmod=0755 so nginx's docker-entrypoint.d actually executes it.
COPY --chown=101:101 --chmod=0755 docker/40-openconcho-config.sh /docker-entrypoint.d/40-openconcho-config.sh
-# Defaults target the Honcho service in a typical Compose stack; override per deploy.
-ENV HONCHO_UPSTREAM=http://api:8000 \
- OPENCONCHO_DEFAULT_HONCHO_URL=same-origin
+# Empty default → clean first run (configure the instance in Settings). Override per
+# deploy to seed the first instance; the browser routes via /api with an
+# X-Honcho-Upstream header. Optional OPENCONCHO_UPSTREAM_ALLOWLIST guards the proxy.
+ENV OPENCONCHO_DEFAULT_HONCHO_URL=""
EXPOSE 8080
diff --git a/Makefile b/Makefile
index b04432f..1f17e46 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,8 @@
.PHONY: bootstrap dev dev-web dev-desktop \
build test test-e2e lint lint-fix typecheck check \
- ci-web ci-desktop install help
+ ci-web ci-desktop smoke-docker \
+ up prod down clean install help
help:
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS=":.*?## "}; {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
@@ -47,5 +48,20 @@ ci-web: ## CI: lint + typecheck + test + build for @openconcho/web
ci-desktop: ## CI: cargo-check for @openconcho/desktop
pnpm ci:desktop
+smoke-docker: ## Local: build the image + smoke-test the /api proxy (Docker required)
+ bash docker/smoke-test.sh
+
+up: ## Run the web container from source (dev profile, builds) at :8080
+ docker compose --profile dev up -d --build
+
+prod: ## Run the web container from the published image (prod profile, pulls latest)
+ docker compose --profile prod up -d
+
+down: ## Stop + remove the web container (either profile)
+ docker compose --profile dev --profile prod down --remove-orphans
+
+clean: down ## down + remove the locally built image
+ -docker image rm openconcho-web:local
+
install: ## pnpm install (no playwright)
pnpm install
diff --git a/README.md b/README.md
index f88becb..fb2dfd1 100644
--- a/README.md
+++ b/README.md
@@ -89,20 +89,58 @@ pnpm --filter @openconcho/desktop dev
### Docker (web app)
-Run the web UI in a container — handy for adding it to a self-hosted Honcho
-Compose stack. The image serves the SPA and reverse-proxies the Honcho API under
-its own origin, so the browser makes same-origin requests (no CORS to configure).
+The container serves the SPA and reverse-proxies the Honcho API under its own
+origin: the browser calls `/api` same-origin and names the upstream in an
+`X-Honcho-Upstream` header, so there's no browser CORS to configure.
+
+Two Compose modes (the published image is `ghcr.io/offendingcommit/openconcho-web`):
```bash
-docker run --rm -p 8080:8080 \
- -e HONCHO_UPSTREAM=http://host.docker.internal:8000 \
- ghcr.io/offendingcommit/openconcho-web:latest
+# Dev-forward — build from this repo and run your local changes:
+OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net make up
+
+# Production — pull the latest published image instead of building:
+OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net make prod
+
+make down # stop + remove (dev or prod)
+make clean # down + drop the locally built image
# → http://localhost:8080
```
-To drop it into a Honcho Compose stack, use the `openconcho` service in
-[`docker-compose.yml`](docker-compose.yml). Full details, env vars, and the CORS
-options are in [`docs/docker.md`](docs/docker.md).
+Both modes live in one [`docker-compose.yml`](docker-compose.yml) as Compose
+profiles: `make up` runs the `dev` profile (`build: .`), `make prod` runs the
+`prod` profile (pulls `ghcr…:latest`). `OPENCONCHO_DEFAULT_HONCHO_URL` seeds the first instance
+(absolute URL); `OPENCONCHO_UPSTREAM_ALLOWLIST` is an optional SSRF guard
+(comma-separated host globs) for when you expose the proxy. Full details and env
+vars are in [`docs/docker.md`](docs/docker.md).
+
+### Kubernetes (Helm)
+
+The chart is published as an OCI artifact to GHCR on every tagged release.
+
+```bash
+helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
+ --version 0.14.0 \
+ --create-namespace --namespace openconcho \
+ --set honcho.defaultUrl=https://honcho.example.com
+```
+
+Enable an Ingress and TLS:
+
+```bash
+helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
+ --version 0.14.0 \
+ --create-namespace --namespace openconcho \
+ --set honcho.defaultUrl=https://honcho.example.com \
+ --set ingress.enabled=true \
+ --set ingress.className=nginx \
+ --set 'ingress.hosts[0].host=openconcho.example.com' \
+ --set 'ingress.hosts[0].paths[0].path=/' \
+ --set 'ingress.tls[0].secretName=openconcho-tls' \
+ --set 'ingress.tls[0].hosts[0]=openconcho.example.com'
+```
+
+Full chart documentation, configuration reference, and an ArgoCD Application example are in [`charts/openconcho/README.md`](charts/openconcho/README.md).
### Connecting to your instance
diff --git a/charts/openconcho/Chart.yaml b/charts/openconcho/Chart.yaml
new file mode 100644
index 0000000..12a4099
--- /dev/null
+++ b/charts/openconcho/Chart.yaml
@@ -0,0 +1,16 @@
+apiVersion: v2
+name: openconcho
+description: Self-hosted UI for Honcho — browse memories, peers, sessions, conclusions, and chat with memory context.
+type: application
+version: 0.14.0
+appVersion: "0.14.0"
+keywords:
+ - honcho
+ - memory
+ - ai
+home: https://github.com/offendingcommit/openconcho
+sources:
+ - https://github.com/offendingcommit/openconcho
+maintainers:
+ - name: offendingcommit
+ url: https://github.com/offendingcommit
diff --git a/charts/openconcho/README.md b/charts/openconcho/README.md
new file mode 100644
index 0000000..82a0529
--- /dev/null
+++ b/charts/openconcho/README.md
@@ -0,0 +1,229 @@
+# openconcho Helm Chart
+
+Helm 3 chart for self-hosting the [openconcho](https://github.com/offendingcommit/openconcho) web UI on Kubernetes.
+
+The chart deploys a single nginx-unprivileged container (port 8080, UID 101) that serves the React SPA and reverse-proxies Honcho API calls under `/api` to avoid browser CORS issues.
+
+## Prerequisites
+
+- Kubernetes 1.25+
+- Helm 3.10+
+- A running [Honcho](https://github.com/plastic-labs/honcho) instance reachable from within the cluster (or via a configured ingress)
+
+## Installing
+
+Add the chart repository:
+
+```bash
+helm registry login ghcr.io --username --password
+```
+
+Install the chart:
+
+```bash
+helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
+ --version 0.14.0 \
+ --set honcho.defaultUrl=https://honcho.example.com
+```
+
+Or with a values file (recommended):
+
+```bash
+helm install openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
+ --version 0.14.0 \
+ -f my-values.yaml
+```
+
+## Upgrading
+
+```bash
+helm upgrade openconcho oci://ghcr.io/offendingcommit/charts/openconcho \
+ --version \
+ -f my-values.yaml
+```
+
+## Uninstalling
+
+```bash
+helm uninstall openconcho
+```
+
+## Configuration
+
+All values with their defaults are documented in [`values.yaml`](values.yaml). Key options:
+
+| Value | Default | Description |
+|---|---|---|
+| `replicaCount` | `1` | Number of pod replicas |
+| `image.repository` | `ghcr.io/offendingcommit/openconcho-web` | Container image |
+| `image.tag` | `""` | Tag; defaults to chart `appVersion` |
+| `image.pullPolicy` | `IfNotPresent` | Image pull policy |
+| `honcho.defaultUrl` | `""` | Honcho URL pre-seeded in the UI |
+| `honcho.upstreamAllowlist` | `""` | SSRF guard (comma-separated host globs) |
+| `service.type` | `ClusterIP` | `ClusterIP` / `NodePort` / `LoadBalancer` |
+| `service.port` | `80` | Service port |
+| `ingress.enabled` | `false` | Enable Ingress resource |
+| `ingress.className` | `""` | IngressClass name |
+| `autoscaling.enabled` | `false` | Enable HorizontalPodAutoscaler |
+| `podDisruptionBudget.enabled` | `false` | Enable PodDisruptionBudget |
+| `networkPolicy.enabled` | `false` | Enable NetworkPolicy (same-namespace only) |
+| `resources.requests.memory` | `32Mi` | Memory request |
+| `resources.limits.memory` | `128Mi` | Memory limit |
+
+## Examples
+
+### Minimal (ClusterIP, no ingress)
+
+```yaml
+honcho:
+ defaultUrl: http://honcho.honcho.svc.cluster.local:8000
+```
+
+### With Ingress and TLS (cert-manager)
+
+```yaml
+honcho:
+ defaultUrl: https://honcho.example.com
+
+ingress:
+ enabled: true
+ className: nginx
+ annotations:
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+ hosts:
+ - host: openconcho.example.com
+ paths:
+ - path: /
+ pathType: Prefix
+ tls:
+ - secretName: openconcho-tls
+ hosts:
+ - openconcho.example.com
+```
+
+### With autoscaling and disruption budget
+
+```yaml
+replicaCount: 2
+
+autoscaling:
+ enabled: true
+ minReplicas: 2
+ maxReplicas: 10
+ targetCPUUtilizationPercentage: 70
+
+podDisruptionBudget:
+ enabled: true
+ minAvailable: 1
+```
+
+### With NetworkPolicy
+
+> **Note:** When `networkPolicy.enabled=true` and `ingress.enabled=true`, you must add
+> a policy that allows traffic from the ingress-controller namespace. Run
+> `helm status ` for the exact `kubectl edit` command after install.
+
+```yaml
+networkPolicy:
+ enabled: true
+
+ingress:
+ enabled: true
+ className: nginx
+ hosts:
+ - host: openconcho.example.com
+ paths:
+ - path: /
+ pathType: Prefix
+```
+
+### Private registry
+
+```yaml
+image:
+ repository: registry.example.com/myorg/openconcho-web
+ tag: "0.14.0"
+ pullPolicy: Always
+
+imagePullSecrets:
+ - name: registry-credentials
+```
+
+## ArgoCD Application
+
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: openconcho
+ namespace: argocd
+spec:
+ project: default
+ source:
+ repoURL: ghcr.io/offendingcommit/charts
+ chart: openconcho
+ targetRevision: 0.14.0
+ helm:
+ valuesObject:
+ honcho:
+ defaultUrl: https://honcho.example.com
+ ingress:
+ enabled: true
+ className: nginx
+ annotations:
+ cert-manager.io/cluster-issuer: letsencrypt-prod
+ hosts:
+ - host: openconcho.example.com
+ paths:
+ - path: /
+ pathType: Prefix
+ tls:
+ - secretName: openconcho-tls
+ hosts:
+ - openconcho.example.com
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: openconcho
+ syncPolicy:
+ automated:
+ prune: true
+ selfHeal: true
+ syncOptions:
+ - CreateNamespace=true
+```
+
+> OCI chart sources require ArgoCD 2.10+ (OCI Helm support GA).
+
+## Helm tests
+
+After install, run the bundled tests to verify the deployment is healthy:
+
+```bash
+helm test openconcho
+```
+
+Two test pods run and exit 0 on success:
+
+| Test | What it checks |
+|---|---|
+| `test-healthz` | `GET /healthz` body equals `ok` |
+| `test-spa-root` | `GET /` returns HTTP 200 |
+
+Pass `--logs` to see output from failing pods:
+
+```bash
+helm test openconcho --logs
+```
+
+## Security posture
+
+| Control | Value |
+|---|---|
+| Run as UID/GID | 101 (nginx-unprivileged) |
+| `runAsNonRoot` | `true` |
+| `readOnlyRootFilesystem` | `true` |
+| Linux capabilities | all dropped |
+| `seccompProfile` | `RuntimeDefault` |
+| `allowPrivilegeEscalation` | `false` |
+| `automountServiceAccountToken` | `false` |
+| Writable paths | `/var/cache/nginx`, `/var/run`, `/tmp` (tmpfs) |
diff --git a/charts/openconcho/templates/NOTES.txt b/charts/openconcho/templates/NOTES.txt
new file mode 100644
index 0000000..7cc7ea7
--- /dev/null
+++ b/charts/openconcho/templates/NOTES.txt
@@ -0,0 +1,40 @@
+OpenConcho {{ .Chart.AppVersion }} deployed to namespace {{ .Release.Namespace }}.
+
+{{- if .Values.ingress.enabled }}
+Access:
+{{- range .Values.ingress.hosts }}
+ http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
+{{- end }}
+{{- else if eq .Values.service.type "NodePort" }}
+Access (NodePort):
+ export NODE_PORT=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} -o jsonpath="{.spec.ports[0].nodePort}")
+ export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+ echo "http://$NODE_IP:$NODE_PORT"
+{{- else if eq .Values.service.type "LoadBalancer" }}
+Access (LoadBalancer — IP may take a few minutes):
+ export LB_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }} --template '{{ "{{" }}range (index .status.loadBalancer.ingress 0){{ "}}" }}{{ "{{" }}.{{ "}}" }}{{ "{{" }}end{{ "}}" }}')
+ echo "http://$LB_IP:{{ .Values.service.port }}"
+{{- else }}
+Access (port-forward):
+ kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "openconcho.fullname" . }} 8080:{{ .Values.service.port }}
+ Then open http://localhost:8080
+{{- end }}
+
+Run Helm tests to verify the deployment:
+ helm test {{ .Release.Name }}
+
+{{- if and .Values.networkPolicy.enabled .Values.ingress.enabled }}
+
+WARNING: NetworkPolicy + Ingress are both enabled.
+The default NetworkPolicy allows port {{ .Values.service.containerPort }} only from pods within
+namespace {{ .Release.Namespace }}. Ingress controllers typically run in a separate
+namespace (ingress-nginx, kube-system, etc.) and will be blocked.
+To allow ingress-controller traffic, add a namespaceSelector rule:
+
+ kubectl edit networkpolicy --namespace {{ .Release.Namespace }} {{ include "openconcho.fullname" . }}
+
+ # Under spec.ingress[0].from, add:
+ - namespaceSelector:
+ matchLabels:
+ kubernetes.io/metadata.name:
+{{- end }}
diff --git a/charts/openconcho/templates/_helpers.tpl b/charts/openconcho/templates/_helpers.tpl
new file mode 100644
index 0000000..ef8fb6a
--- /dev/null
+++ b/charts/openconcho/templates/_helpers.tpl
@@ -0,0 +1,46 @@
+{{- define "openconcho.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{- define "openconcho.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{- define "openconcho.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{- define "openconcho.labels" -}}
+helm.sh/chart: {{ include "openconcho.chart" . }}
+{{ include "openconcho.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{- define "openconcho.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "openconcho.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{- define "openconcho.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "openconcho.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
+
+{{- define "openconcho.imageTag" -}}
+{{- .Values.image.tag | default .Chart.AppVersion }}
+{{- end }}
diff --git a/charts/openconcho/templates/deployment.yaml b/charts/openconcho/templates/deployment.yaml
new file mode 100644
index 0000000..900d459
--- /dev/null
+++ b/charts/openconcho/templates/deployment.yaml
@@ -0,0 +1,84 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "openconcho.fullname" . }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+spec:
+ {{- if not .Values.autoscaling.enabled }}
+ replicas: {{ .Values.replicaCount }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "openconcho.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ {{- with .Values.podAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "openconcho.selectorLabels" . | nindent 8 }}
+ {{- with .Values.podLabels }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ serviceAccountName: {{ include "openconcho.serviceAccountName" . }}
+ securityContext:
+ {{- toYaml .Values.podSecurityContext | nindent 8 }}
+ containers:
+ - name: {{ .Chart.Name }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
+ image: "{{ .Values.image.repository }}:{{ include "openconcho.imageTag" . }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ ports:
+ - name: http
+ containerPort: {{ .Values.service.containerPort }}
+ protocol: TCP
+ env:
+ - name: OPENCONCHO_DEFAULT_HONCHO_URL
+ value: {{ .Values.honcho.defaultUrl | quote }}
+ - name: OPENCONCHO_UPSTREAM_ALLOWLIST
+ value: {{ .Values.honcho.upstreamAllowlist | quote }}
+ livenessProbe:
+ {{- toYaml .Values.livenessProbe | nindent 12 }}
+ readinessProbe:
+ {{- toYaml .Values.readinessProbe | nindent 12 }}
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+ {{- if .Values.tmpfsMounts }}
+ volumeMounts:
+ {{- range .Values.tmpfsMounts }}
+ - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
+ mountPath: {{ .mountPath }}
+ {{- end }}
+ {{- end }}
+ {{- if .Values.tmpfsMounts }}
+ volumes:
+ {{- range .Values.tmpfsMounts }}
+ - name: {{ .mountPath | trimPrefix "/" | replace "/" "-" | trunc 63 | trimSuffix "-" }}
+ emptyDir:
+ medium: Memory
+ {{- end }}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.topologySpreadConstraints }}
+ topologySpreadConstraints:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
diff --git a/charts/openconcho/templates/hpa.yaml b/charts/openconcho/templates/hpa.yaml
new file mode 100644
index 0000000..43a9b83
--- /dev/null
+++ b/charts/openconcho/templates/hpa.yaml
@@ -0,0 +1,24 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: {{ include "openconcho.fullname" . }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: {{ include "openconcho.fullname" . }}
+ minReplicas: {{ .Values.autoscaling.minReplicas }}
+ maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+ {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+ metrics:
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- end }}
+{{- end }}
diff --git a/charts/openconcho/templates/ingress.yaml b/charts/openconcho/templates/ingress.yaml
new file mode 100644
index 0000000..27404eb
--- /dev/null
+++ b/charts/openconcho/templates/ingress.yaml
@@ -0,0 +1,39 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "openconcho.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ $fullName }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if .Values.ingress.className }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- toYaml .Values.ingress.tls | nindent 4 }}
+ {{- end }}
+ {{- if .Values.ingress.hosts }}
+ rules:
+ {{- range .Values.ingress.hosts }}
+ - host: {{ .host | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path }}
+ pathType: {{ .pathType | default "Prefix" }}
+ backend:
+ service:
+ name: {{ $fullName }}
+ port:
+ number: {{ $svcPort }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/charts/openconcho/templates/networkpolicy.yaml b/charts/openconcho/templates/networkpolicy.yaml
new file mode 100644
index 0000000..b7dfe4a
--- /dev/null
+++ b/charts/openconcho/templates/networkpolicy.yaml
@@ -0,0 +1,20 @@
+{{- if .Values.networkPolicy.enabled }}
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: {{ include "openconcho.fullname" . }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+spec:
+ podSelector:
+ matchLabels:
+ {{- include "openconcho.selectorLabels" . | nindent 6 }}
+ policyTypes:
+ - Ingress
+ ingress:
+ - ports:
+ - port: {{ .Values.service.containerPort }}
+ protocol: TCP
+ from:
+ - podSelector: {}
+{{- end }}
diff --git a/charts/openconcho/templates/pdb.yaml b/charts/openconcho/templates/pdb.yaml
new file mode 100644
index 0000000..de4b44b
--- /dev/null
+++ b/charts/openconcho/templates/pdb.yaml
@@ -0,0 +1,17 @@
+{{- if .Values.podDisruptionBudget.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+ name: {{ include "openconcho.fullname" . }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+spec:
+ {{- if not (kindIs "invalid" .Values.podDisruptionBudget.maxUnavailable) }}
+ maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
+ {{- else }}
+ minAvailable: {{ .Values.podDisruptionBudget.minAvailable | default 1 }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "openconcho.selectorLabels" . | nindent 6 }}
+{{- end }}
diff --git a/charts/openconcho/templates/service.yaml b/charts/openconcho/templates/service.yaml
new file mode 100644
index 0000000..a542306
--- /dev/null
+++ b/charts/openconcho/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "openconcho.fullname" . }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ ports:
+ - port: {{ .Values.service.port }}
+ targetPort: http
+ protocol: TCP
+ name: http
+ selector:
+ {{- include "openconcho.selectorLabels" . | nindent 4 }}
diff --git a/charts/openconcho/templates/serviceaccount.yaml b/charts/openconcho/templates/serviceaccount.yaml
new file mode 100644
index 0000000..433c583
--- /dev/null
+++ b/charts/openconcho/templates/serviceaccount.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "openconcho.serviceAccountName" . }}
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+ {{- with .Values.serviceAccount.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
+{{- end }}
diff --git a/charts/openconcho/templates/tests/test-healthz.yaml b/charts/openconcho/templates/tests/test-healthz.yaml
new file mode 100644
index 0000000..5a512e4
--- /dev/null
+++ b/charts/openconcho/templates/tests/test-healthz.yaml
@@ -0,0 +1,25 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "{{ include "openconcho.fullname" . }}-test-healthz"
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+ annotations:
+ helm.sh/hook: test
+ helm.sh/hook-delete-policy: before-hook-creation
+spec:
+ restartPolicy: Never
+ activeDeadlineSeconds: 60
+ containers:
+ - name: healthz
+ image: busybox:1.36
+ command:
+ - sh
+ - -c
+ - |
+ RESPONSE=$(wget -T 10 -qO- http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/healthz)
+ if [ "$RESPONSE" != "ok" ]; then
+ echo "FAIL: expected 'ok', got '$RESPONSE'"
+ exit 1
+ fi
+ echo "PASS: /healthz returned 'ok'"
diff --git a/charts/openconcho/templates/tests/test-spa-root.yaml b/charts/openconcho/templates/tests/test-spa-root.yaml
new file mode 100644
index 0000000..e6acda8
--- /dev/null
+++ b/charts/openconcho/templates/tests/test-spa-root.yaml
@@ -0,0 +1,24 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "{{ include "openconcho.fullname" . }}-test-spa-root"
+ labels:
+ {{- include "openconcho.labels" . | nindent 4 }}
+ annotations:
+ helm.sh/hook: test
+ helm.sh/hook-delete-policy: before-hook-creation
+spec:
+ restartPolicy: Never
+ activeDeadlineSeconds: 60
+ containers:
+ - name: spa-root
+ image: busybox:1.36
+ command:
+ - sh
+ - -c
+ - |
+ if ! wget -T 10 -q --spider http://{{ include "openconcho.fullname" . }}:{{ .Values.service.port }}/; then
+ echo "FAIL: GET / did not return HTTP 200"
+ exit 1
+ fi
+ echo "PASS: / returned HTTP 200"
diff --git a/charts/openconcho/values.schema.json b/charts/openconcho/values.schema.json
new file mode 100644
index 0000000..2b15f7b
--- /dev/null
+++ b/charts/openconcho/values.schema.json
@@ -0,0 +1,67 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "properties": {
+ "replicaCount": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "image": {
+ "type": "object",
+ "required": ["repository", "pullPolicy"],
+ "properties": {
+ "repository": { "type": "string", "minLength": 1 },
+ "tag": { "type": "string" },
+ "pullPolicy": {
+ "type": "string",
+ "enum": ["Always", "IfNotPresent", "Never"]
+ }
+ }
+ },
+ "service": {
+ "type": "object",
+ "required": ["type", "port", "containerPort"],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["ClusterIP", "NodePort", "LoadBalancer"]
+ },
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+ "containerPort": { "type": "integer", "minimum": 1, "maximum": 65535 }
+ }
+ },
+ "honcho": {
+ "type": "object",
+ "properties": {
+ "defaultUrl": { "type": "string" },
+ "upstreamAllowlist": { "type": "string" }
+ }
+ },
+ "autoscaling": {
+ "type": "object",
+ "properties": {
+ "enabled": { "type": "boolean" },
+ "minReplicas": { "type": "integer", "minimum": 1 },
+ "maxReplicas": { "type": "integer", "minimum": 1 },
+ "targetCPUUtilizationPercentage": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 100
+ }
+ }
+ },
+ "podDisruptionBudget": {
+ "type": "object",
+ "properties": {
+ "enabled": { "type": "boolean" },
+ "minAvailable": { "type": "integer", "minimum": 0 }
+ }
+ },
+ "networkPolicy": {
+ "type": "object",
+ "properties": {
+ "enabled": { "type": "boolean" }
+ }
+ }
+ }
+}
diff --git a/charts/openconcho/values.yaml b/charts/openconcho/values.yaml
new file mode 100644
index 0000000..e27a03a
--- /dev/null
+++ b/charts/openconcho/values.yaml
@@ -0,0 +1,178 @@
+# Number of pod replicas. Increase for high availability or use autoscaling instead.
+replicaCount: 1
+
+image:
+ # Container image repository. Override to use a custom registry or fork.
+ repository: ghcr.io/offendingcommit/openconcho-web
+ # Image tag. Defaults to the chart appVersion when left empty.
+ tag: ""
+ pullPolicy: IfNotPresent
+
+# Secrets for pulling images from private registries.
+# Example: [{ name: my-registry-secret }]
+imagePullSecrets: []
+
+# Override the name portion used in resource names and labels.
+nameOverride: ""
+# Override the full resource name (normally release-name + chart-name).
+fullnameOverride: ""
+
+serviceAccount:
+ # Create a dedicated ServiceAccount for the pod.
+ create: true
+ # Disable automatic ServiceAccount token mounting — the app never calls the Kubernetes API.
+ automount: false
+ # Annotations to add to the ServiceAccount (e.g. for IRSA, Workload Identity, Vault).
+ annotations: {}
+ # Use a pre-existing ServiceAccount instead of creating one. Ignored when create is true.
+ name: ""
+
+# Annotations applied to every pod (not the Deployment). Useful for Prometheus scraping,
+# Vault agent injection, Datadog unified service tagging, etc.
+podAnnotations: {}
+# Extra labels applied to every pod.
+podLabels: {}
+
+# Pod-level security context shared by all containers.
+# UID/GID 101 matches the nginx-unprivileged base image — do not change without rebuilding.
+podSecurityContext:
+ runAsNonRoot: true
+ runAsUser: 101
+ runAsGroup: 101
+ fsGroup: 101
+ seccompProfile:
+ type: RuntimeDefault
+
+# Container-level security context.
+securityContext:
+ allowPrivilegeEscalation: false
+ readOnlyRootFilesystem: true
+ capabilities:
+ drop: [ALL]
+
+# Directories mounted as ephemeral tmpfs (in-memory) to satisfy nginx's write requirements
+# when the root filesystem is read-only. Add entries for any additional writable paths.
+tmpfsMounts:
+ - mountPath: /var/cache/nginx
+ - mountPath: /var/run
+ - mountPath: /tmp
+
+service:
+ # Kubernetes Service type. Options: ClusterIP | NodePort | LoadBalancer
+ type: ClusterIP
+ # Port exposed by the Service (what the Ingress or other pods target).
+ port: 80
+ # Port the container actually listens on (nginx-unprivileged default).
+ containerPort: 8080
+
+ingress:
+ enabled: false
+ # IngressClass name. Leave empty to accept the cluster default.
+ # Examples: nginx | traefik | alb | kong
+ className: ""
+ # Annotations forwarded verbatim to the Ingress resource.
+ # Example (cert-manager + nginx-ingress):
+ # kubernetes.io/ingress.class: nginx
+ # cert-manager.io/cluster-issuer: letsencrypt-prod
+ annotations: {}
+ hosts:
+ - host: openconcho.example.com
+ paths:
+ - path: /
+ pathType: Prefix
+ # TLS configuration. Provide a Secret containing the certificate.
+ # Example:
+ # - secretName: openconcho-tls
+ # hosts:
+ # - openconcho.example.com
+ tls: []
+
+honcho:
+ # Default Honcho instance URL pre-populated in the UI on first load.
+ # Users can change or add instances at runtime; this only seeds the initial value.
+ # Example: https://honcho.example.com
+ defaultUrl: ""
+ # Optional SSRF guard: comma-separated host globs the nginx proxy is allowed to forward to.
+ # Leave empty to allow any upstream. Applies only when the proxy is publicly reachable.
+ # Example: honcho.example.com,*.internal.example.com
+ upstreamAllowlist: ""
+
+# CPU and memory requests / limits for the web container.
+# The SPA is static HTML/JS, so memory is the primary concern and CPU is negligible at rest.
+resources:
+ requests:
+ cpu: 50m
+ memory: 32Mi
+ limits:
+ memory: 128Mi
+
+livenessProbe:
+ httpGet:
+ path: /healthz
+ port: http
+ initialDelaySeconds: 5
+ periodSeconds: 10
+
+readinessProbe:
+ httpGet:
+ path: /healthz
+ port: http
+ initialDelaySeconds: 5
+ periodSeconds: 10
+
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 5
+ # Scale up when average CPU utilization across pods exceeds this percentage.
+ targetCPUUtilizationPercentage: 80
+
+podDisruptionBudget:
+ enabled: false
+ # Minimum number of pods that must remain available during voluntary disruptions
+ # (node drains, rolling upgrades). Set maxUnavailable instead to flip the direction.
+ minAvailable: 1
+ # Uncomment to use maxUnavailable instead — cannot set both simultaneously.
+ # maxUnavailable: 1
+
+# Restrict pod-to-pod traffic at the network layer. When enabled, only pods in the
+# same namespace may reach the web container.
+# WARNING: if ingress is also enabled, you must add a policy that allows traffic from
+# the ingress-controller namespace — run `helm status ` to see the reminder.
+networkPolicy:
+ enabled: false
+
+# Spread pods across failure domains (availability zones, nodes, etc.) to reduce
+# the blast radius of a single-node or single-zone failure.
+# Example (zone spread):
+# - maxSkew: 1
+# topologyKey: topology.kubernetes.io/zone
+# whenUnsatisfiable: DoNotSchedule
+# labelSelector:
+# matchLabels:
+# app.kubernetes.io/name: openconcho
+topologySpreadConstraints: []
+
+# Constrain pods to nodes whose labels match these key/value pairs.
+# Example: kubernetes.io/arch: amd64
+nodeSelector: {}
+
+# Allow pods to be scheduled on tainted nodes.
+# Example:
+# - key: dedicated
+# operator: Equal
+# value: web
+# effect: NoSchedule
+tolerations: []
+
+# Advanced pod affinity / anti-affinity rules.
+# Example (soft anti-affinity — prefer spreading across different nodes):
+# podAntiAffinity:
+# preferredDuringSchedulingIgnoredDuringExecution:
+# - weight: 100
+# podAffinityTerm:
+# labelSelector:
+# matchLabels:
+# app.kubernetes.io/name: openconcho
+# topologyKey: kubernetes.io/hostname
+affinity: {}
diff --git a/docker-compose.yml b/docker-compose.yml
index d1d37c2..34b0e40 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,29 +1,48 @@
-# Run OpenConcho's web UI with `docker compose up`.
+# OpenConcho web UI — one file, two Compose profiles (dev builds, prod pulls).
#
-# Standalone: serves the SPA on http://localhost:8080 and reverse-proxies the
-# Honcho API under the same origin (no browser CORS). By default it points at a
-# Honcho running on the host at :8000 — override HONCHO_UPSTREAM for anything else:
+# make up # profile dev: build from THIS repo + run → http://localhost:8080
+# make prod # profile prod: pull ghcr…:latest instead of building
+# make down # stop + remove (either profile)
+# make clean # down + drop the locally built image
#
-# HONCHO_UPSTREAM=https://honcho.example.net docker compose up
+# The SPA issues all Honcho calls same-origin to /api; nginx forwards each to the
+# URL named in the per-request X-Honcho-Upstream header (no browser CORS). Seed the
+# first instance with OPENCONCHO_DEFAULT_HONCHO_URL:
#
-# To fold this into an existing Honcho Compose stack, drop the `openconcho`
-# service into that project, set HONCHO_UPSTREAM to the api service
-# (e.g. http://api:8000), and add `depends_on: { api: { condition: service_healthy } }`.
+# OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net make up
+#
+# To fold into an existing Honcho Compose stack, point the seed at the api service
+# (e.g. http://api:8000 — nginx resolves it on the compose network).
+
+# Shared config (defined once); both profiles reference it via a YAML merge.
+x-openconcho: &openconcho
+ environment:
+ # Absolute URL seeding the first instance; the browser sends it as the
+ # X-Honcho-Upstream header and nginx forwards there (no browser CORS).
+ OPENCONCHO_DEFAULT_HONCHO_URL: ${OPENCONCHO_DEFAULT_HONCHO_URL:-http://host.docker.internal:8000}
+ # Optional SSRF guard. Unset = forward anywhere (safe for the localhost-only
+ # binding below). Set comma-separated host globs before exposing the proxy:
+ # OPENCONCHO_UPSTREAM_ALLOWLIST: honcho.example.net,*.honcho.dev
+ OPENCONCHO_UPSTREAM_ALLOWLIST: ${OPENCONCHO_UPSTREAM_ALLOWLIST:-}
+ ports:
+ - "127.0.0.1:8080:8080"
+ # Lets the default host.docker.internal upstream resolve on Linux too
+ # (Docker Desktop / Colima provide it automatically).
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ restart: unless-stopped
+
services:
+ # Dev-forward — builds from source so you run your local changes (`make up`).
openconcho:
+ <<: *openconcho
+ profiles: ["dev"]
+ build: .
+ image: openconcho-web:local
+
+ # Production — pulls the published image instead of building (`make prod`).
+ openconcho-prod:
+ <<: *openconcho
+ profiles: ["prod"]
image: ghcr.io/offendingcommit/openconcho-web:latest
- # Or build from this repo instead of pulling the published image:
- # build: .
- environment:
- # nginx reverse-proxies /v3 and /health to this upstream (the Honcho API).
- HONCHO_UPSTREAM: ${HONCHO_UPSTREAM:-http://host.docker.internal:8000}
- # The SPA defaults its Honcho base URL to its own origin, so requests flow
- # through the proxy above — no browser CORS, token never leaves the origin.
- OPENCONCHO_DEFAULT_HONCHO_URL: same-origin
- ports:
- - "127.0.0.1:8080:8080"
- # Lets the default host.docker.internal upstream resolve on Linux too
- # (Docker Desktop / Colima provide it automatically).
- extra_hosts:
- - "host.docker.internal:host-gateway"
- restart: unless-stopped
+ pull_policy: always
diff --git a/docker/40-openconcho-config.sh b/docker/40-openconcho-config.sh
index 864de84..88656ca 100644
--- a/docker/40-openconcho-config.sh
+++ b/docker/40-openconcho-config.sh
@@ -1,7 +1,8 @@
#!/bin/sh
# Regenerate the SPA's runtime config from the environment at container start.
# Lets one prebuilt image target any Honcho backend without a rebuild.
-# OPENCONCHO_DEFAULT_HONCHO_URL — absolute URL, "same-origin", or empty.
+# OPENCONCHO_DEFAULT_HONCHO_URL — absolute URL seeding the first instance, or empty.
+# OPENCONCHO_UPSTREAM_ALLOWLIST — optional comma-separated host globs (SSRF guard).
# Runs from /docker-entrypoint.d before nginx starts. Requires the html dir to
# be writable (default); skip or bind-mount config.js when running --read-only.
set -eu
@@ -9,3 +10,33 @@ set -eu
cat > /usr/share/nginx/html/config.js < /etc/nginx/conf.d/00-resolver.conf
+
+# Render the SSRF allowlist into an nginx map for $allow_upstream.
+# Unset/empty OPENCONCHO_UPSTREAM_ALLOWLIST → open (default 1), fine for the
+# localhost-bound default. Set it (comma-separated host globs) before exposing
+# the proxy (e.g. behind a tunnel) to reject non-matching upstreams.
+ALLOWLIST_CONF=/etc/nginx/conf.d/allowlist_map.conf
+if [ -z "${OPENCONCHO_UPSTREAM_ALLOWLIST:-}" ]; then
+ printf 'map $http_x_honcho_upstream $allow_upstream { default 1; }\n' > "$ALLOWLIST_CONF"
+else
+ {
+ printf 'map $http_x_honcho_upstream $allow_upstream {\n'
+ printf ' default 0;\n'
+ IFS=','
+ for host in $OPENCONCHO_UPSTREAM_ALLOWLIST; do
+ host=$(printf '%s' "$host" | tr -d ' ')
+ [ -z "$host" ] && continue
+ esc=$(printf '%s' "$host" | sed -e 's/[.]/\\./g' -e 's#[*]#[^/]*#g')
+ printf ' "~^https?://%s(:[0-9]+)?(/.*)?$" 1;\n' "$esc"
+ done
+ printf '}\n'
+ } > "$ALLOWLIST_CONF"
+fi
diff --git a/docker/nginx.conf.template b/docker/nginx.conf.template
index c0dd2f6..04abd34 100644
--- a/docker/nginx.conf.template
+++ b/docker/nginx.conf.template
@@ -1,6 +1,6 @@
-# OpenConcho — nginx site config (envsubst template).
-# The nginx image renders ${HONCHO_UPSTREAM} from the environment at start.
-# Serves the React SPA and reverse-proxies the Honcho API under the same origin.
+# OpenConcho — nginx site config.
+# Serves the React SPA and header-driven same-origin /api proxy to Honcho.
+# The browser sends X-Honcho-Upstream per request; nginx forwards server-side (no browser CORS).
server {
listen 8080;
@@ -12,23 +12,28 @@ server {
# Don't leak the nginx version.
server_tokens off;
- # Same-origin reverse proxy to Honcho so the browser never sees a
- # cross-origin request (no CORS). The variable + Docker resolver let nginx
- # start even when the upstream isn't resolvable yet, re-resolving per request.
- resolver 127.0.0.11 ipv6=off valid=10s;
- set $honcho_upstream "${HONCHO_UPSTREAM}";
+ # The resolver (required for per-request DNS with a runtime-variable proxy_pass)
+ # is rendered by the entrypoint into conf.d from the container's own DNS, so it
+ # works on both user-defined networks (127.0.0.11) and the default bridge.
- # `^~` so these win over the static-asset regex below.
- location ^~ /v3/ {
- proxy_pass $honcho_upstream$request_uri;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }
- location = /health {
- proxy_pass $honcho_upstream/health;
- proxy_set_header Host $host;
+ # Header-driven same-origin proxy: the browser names the Honcho upstream per
+ # request via X-Honcho-Upstream, so the browser never makes a cross-origin call.
+ # $allow_upstream is provided by the allowlist map in conf.d (entrypoint-rendered).
+ location ^~ /api/ {
+ set $upstream $http_x_honcho_upstream;
+ if ($upstream = "") {
+ add_header X-Honcho-Proxy-Reject "no-upstream" always;
+ return 421;
+ }
+ if ($allow_upstream = 0) {
+ add_header X-Honcho-Proxy-Reject "allowlist" always;
+ return 403;
+ }
+ rewrite ^/api/(.*)$ /$1 break;
+ proxy_pass $upstream;
+ proxy_ssl_server_name on;
+ proxy_set_header Host $proxy_host;
+ proxy_set_header X-Honcho-Upstream "";
}
# Runtime config — regenerated per container start, must never be cached.
diff --git a/docker/smoke-test.sh b/docker/smoke-test.sh
new file mode 100755
index 0000000..f933c63
--- /dev/null
+++ b/docker/smoke-test.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+# Hermetic container smoke test for the same-origin /api proxy.
+#
+# Builds the image, then stands up a stub upstream + the openconcho container on
+# a shared Docker network and asserts the proxy forwards correctly. Fully
+# self-contained — no external Honcho or tailnet needed. Local-only (requires a
+# Docker daemon); not part of PR CI, like the desktop cargo-check preflight.
+#
+# Idempotent: removes its own containers/network on entry and exit. Exits non-zero
+# on any failed assertion.
+#
+# Usage: make smoke-docker (or: bash docker/smoke-test.sh)
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+IMAGE="openconcho-web:smoke"
+NET="oc-smoke-net"
+UPSTREAM="oc-smoke-upstream"
+APP="oc-smoke-app"
+PORT="${SMOKE_PORT:-18080}"
+# Echo server: returns request method/path/headers as JSON for any verb.
+STUB_IMAGE="mendhak/http-https-echo:31"
+FAIL=0
+
+cleanup() {
+ docker rm -f "$APP" "$UPSTREAM" >/dev/null 2>&1 || true
+ docker network rm "$NET" >/dev/null 2>&1 || true
+}
+trap cleanup EXIT
+cleanup
+
+wait_ready() { # url
+ for _ in $(seq 1 30); do
+ curl -fsS "$1" >/dev/null 2>&1 && return 0
+ sleep 0.5
+ done
+ echo " FAIL: container did not become ready at $1"
+ FAIL=1
+}
+
+check() { # label expected actual
+ if [ "$2" = "$3" ]; then echo " PASS: $1 ($3)"; else echo " FAIL: $1 — expected $2, got $3"; FAIL=1; fi
+}
+
+echo "==> build image"
+docker build -t "$IMAGE" . >/dev/null
+
+echo "==> create network + stub upstream"
+docker network create "$NET" >/dev/null
+docker run -d --name "$UPSTREAM" --network "$NET" -e HTTP_PORT=8080 "$STUB_IMAGE" >/dev/null
+
+echo "==> start openconcho (default-open allowlist)"
+docker run -d --name "$APP" --network "$NET" -p "$PORT:8080" \
+ -e "OPENCONCHO_DEFAULT_HONCHO_URL=http://$UPSTREAM:8080" "$IMAGE" >/dev/null
+wait_ready "http://localhost:$PORT/healthz"
+
+echo "==> assertions"
+check "healthz 200" 200 "$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/healthz")"
+check "SPA served 200" 200 "$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/")"
+check "config.js injected" 200 "$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/config.js")"
+
+# Proxy forwards POST /api/v3/test -> stub, stripping the /api prefix.
+body=$(curl -s "http://localhost:$PORT/api/v3/test" \
+ -H "X-Honcho-Upstream: http://$UPSTREAM:8080" -H 'content-type: application/json' -X POST -d '{}')
+if echo "$body" | grep -q '/v3/test'; then echo " PASS: /api forwards + strips prefix"; else echo " FAIL: forward/strip — body: $body"; FAIL=1; fi
+# Routing header must NOT leak to the upstream.
+if echo "$body" | grep -qi 'x-honcho-upstream'; then echo " FAIL: X-Honcho-Upstream leaked upstream"; FAIL=1; else echo " PASS: X-Honcho-Upstream cleared upstream"; fi
+# Missing routing header -> 421.
+check "missing header 421" 421 "$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/api/v3/test" -X POST -d '{}')"
+
+echo "==> restart with a non-matching allowlist"
+docker rm -f "$APP" >/dev/null
+docker run -d --name "$APP" --network "$NET" -p "$PORT:8080" \
+ -e "OPENCONCHO_UPSTREAM_ALLOWLIST=*.honcho.dev" "$IMAGE" >/dev/null
+wait_ready "http://localhost:$PORT/healthz"
+check "allowlist reject 403" 403 "$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/api/v3/test" \
+ -H "X-Honcho-Upstream: http://$UPSTREAM:8080" -X POST -d '{}')"
+reject=$(curl -s -D- -o /dev/null "http://localhost:$PORT/api/v3/test" \
+ -H "X-Honcho-Upstream: http://$UPSTREAM:8080" -X POST -d '{}' | grep -i 'X-Honcho-Proxy-Reject' | tr -d '\r')
+if echo "$reject" | grep -qi 'allowlist'; then echo " PASS: reject sentinel header present"; else echo " FAIL: missing reject sentinel — got: $reject"; FAIL=1; fi
+
+if [ "$FAIL" = 0 ]; then echo "==> SMOKE TEST PASSED"; else echo "==> SMOKE TEST FAILED"; exit 1; fi
diff --git a/docs/docker.md b/docs/docker.md
index 321ee44..be31dd3 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -5,19 +5,55 @@ builds the static bundle, then `nginx-unprivileged` serves it on port `8080` as
a non-root user) that also **reverse-proxies the Honcho API under its own
origin**, so the browser never makes a cross-origin request.
-## Add it to a Honcho Compose stack (recommended)
+## How the proxy works
-Honcho's self-hosting path is Docker Compose. Drop the `openconcho` service from
-[`docker-compose.yml`](../docker-compose.yml) into the project that runs your
-Honcho `api`:
+The browser issues every Honcho call same-origin to `/api/*` and names the real
+upstream per request in an `X-Honcho-Upstream` header (sourced from the active
+instance's base URL). nginx strips `/api`, forwards to that upstream server-side,
+and returns the response. Because the browser→nginx hop is same-origin, **no CORS
+applies**; the nginx→Honcho hop is server-side, where CORS is irrelevant. The
+frontend stays the source of truth for which instance to talk to, so the
+multi-instance switcher and the Fleet view keep working.
+
+## Compose: dev vs prod profiles
+
+One [`docker-compose.yml`](../docker-compose.yml) with two profiles; the shared
+config (env, ports, extra_hosts) is defined once via a YAML anchor:
+
+- **`dev` profile** — `build: .`, runs **your local source** (`make up`).
+- **`prod` profile** — pulls the **published image**
+ (`ghcr.io/offendingcommit/openconcho-web:latest`, `pull_policy: always`) (`make prod`).
+
+```bash
+make up # build from source + run → http://localhost:8080
+make prod # pull ghcr…:latest instead of building
+make down # stop + remove (either profile)
+make clean # down + drop the locally built image
+```
+
+`make up` expands to `docker compose --profile dev up -d --build` and `make prod`
+to `docker compose --profile prod up -d`. A bare `docker compose up` (no profile)
+starts nothing — use the make targets or pass `--profile`. Set env inline or via a
+`.env` file:
+
+```bash
+OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net make prod
+```
+
+The published image is multi-arch (amd64 + arm64); the first publish creates a
+private GHCR package — make it public for unauthenticated pulls.
+
+## Add it to an existing Honcho Compose stack
+
+Drop the `openconcho` service into the project that runs your Honcho `api`,
+pointing the seed at the api service (nginx resolves it on the compose network):
```yaml
services:
openconcho:
image: ghcr.io/offendingcommit/openconcho-web:latest
environment:
- HONCHO_UPSTREAM: http://api:8000 # nginx proxies /v3 + /health here
- OPENCONCHO_DEFAULT_HONCHO_URL: same-origin
+ OPENCONCHO_DEFAULT_HONCHO_URL: http://api:8000
ports:
- "127.0.0.1:8080:8080"
depends_on:
@@ -26,17 +62,16 @@ services:
restart: unless-stopped
```
-`OPENCONCHO_DEFAULT_HONCHO_URL: same-origin` makes the UI default its Honcho
-base URL to its own origin, so API calls go through the proxy → **no browser
-CORS, and the API token never leaves the origin.** The published image is
-multi-arch (amd64 + arm64); the first publish creates a private GHCR package —
-make it public if you want unauthenticated pulls.
+`OPENCONCHO_DEFAULT_HONCHO_URL` seeds the UI's first instance with an absolute
+URL. The browser sends that URL in the `X-Honcho-Upstream` header; nginx (on the
+compose network) forwards to it — **no browser CORS, and the API token never
+leaves the origin.**
-## Standalone
+## Standalone (no compose)
```bash
-docker build -t openconcho-web .
-docker run --rm -p 8080:8080 -e HONCHO_UPSTREAM=http://host.docker.internal:8000 openconcho-web
+docker run --rm -p 8080:8080 -e OPENCONCHO_DEFAULT_HONCHO_URL=http://host.docker.internal:8000 \
+ ghcr.io/offendingcommit/openconcho-web:latest
# → http://localhost:8080 · GET /healthz returns "ok"
```
@@ -44,24 +79,26 @@ Runtime knobs (no rebuild needed):
| Env | Default | Meaning |
|-----|---------|---------|
-| `HONCHO_UPSTREAM` | `http://api:8000` | Where nginx proxies `/v3` and `/health` |
-| `OPENCONCHO_DEFAULT_HONCHO_URL` | `same-origin` | SPA's default base URL — `same-origin`, an absolute URL, or empty (configure in Settings) |
+| `OPENCONCHO_DEFAULT_HONCHO_URL` | _(empty)_ | Absolute URL seeding the first instance; empty = configure in Settings |
+| `OPENCONCHO_UPSTREAM_ALLOWLIST` | _(empty)_ | Optional SSRF guard: comma-separated host globs (e.g. `honcho.example.net,*.honcho.dev`). Empty = forward anywhere |
Hardened run adds `--read-only --cap-drop ALL --security-opt no-new-privileges`
-with `--tmpfs /tmp --tmpfs /var/cache/nginx`. Note: the runtime config writes
-`config.js` into the web root at start, which a read-only root blocks — under
-`--read-only` either bind-mount `config.js` or leave
-`OPENCONCHO_DEFAULT_HONCHO_URL` empty and set the URL in Settings.
+with `--tmpfs /tmp --tmpfs /var/cache/nginx`. Note: the entrypoint writes
+`config.js` and the allowlist map at start, which a read-only root blocks — under
+`--read-only` either bind-mount those paths or leave the env empty and configure
+the URL in Settings.
+
+## SSRF: when to set the allowlist
+
+The header-driven proxy forwards to whatever upstream the client names. With the
+default `127.0.0.1:8080` binding only your own machine can reach nginx, so leaving
+the allowlist open is fine. **Before exposing the proxy** (e.g. behind a tunnel),
+set `OPENCONCHO_UPSTREAM_ALLOWLIST` to the host globs you trust — non-matching
+upstreams are rejected with `403` and an `X-Honcho-Proxy-Reject: allowlist` header.
## CORS, the short version
-The desktop app routes HTTP through Rust and bypasses browser CORS; the web
-build doesn't. The Compose setup above **solves CORS via the same-origin proxy**
-— nothing to configure on Honcho. If instead you point the UI at a *different*
-origin (absolute `OPENCONCHO_DEFAULT_HONCHO_URL` or a URL typed in Settings),
-allow that origin in Honcho's FastAPI `CORSMiddleware`:
-
-```python
-app.add_middleware(CORSMiddleware, allow_origins=["https://your-ui-origin"],
- allow_methods=["*"], allow_headers=["*"])
-```
+The desktop app routes HTTP through Rust (reqwest) and bypasses browser CORS; the
+web build solves it with the same-origin `/api` proxy above — **nothing to
+configure on Honcho.** The proxy makes a Honcho-side `CORSMiddleware` unnecessary
+regardless of which instance you point at.
diff --git a/docs/superpowers/plans/2026-06-02-web-api-proxy.md b/docs/superpowers/plans/2026-06-02-web-api-proxy.md
new file mode 100644
index 0000000..6ff4bbc
--- /dev/null
+++ b/docs/superpowers/plans/2026-06-02-web-api-proxy.md
@@ -0,0 +1,782 @@
+# Header-Driven `/api` Proxy — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Eliminate browser CORS for the web build by routing Honcho API calls through a same-origin, header-driven reverse proxy, while leaving the Tauri desktop path untouched and preserving Fleet aggregation.
+
+**Architecture:** A single `dispatchFor(instance)` helper decides transport at runtime — web mode returns `baseUrl="/api"` plus an `X-Honcho-Upstream` header (the real Honcho URL); Tauri mode returns the absolute URL and reqwest. nginx (docker) and a Vite middleware (dev) read the header and forward server-side, so the browser never makes a cross-origin request. Instances are still stored as absolute URLs — only dispatch changes.
+
+**Tech Stack:** React 19, openapi-fetch, TanStack Query, Vitest, Biome, nginx (envsubst template), Vite dev server, Tauri v2.
+
+**Spec:** `docs/superpowers/specs/2026-06-02-honcho-api-proxy-design.md`
+
+**Baseline gate (run before starting AND after every task):**
+`make ci-web` (lint + typecheck + test + build). Targeted test during a task: `pnpm --filter @openconcho/web exec vitest run `.
+
+**Commit discipline:** conventional commits, one logical change per commit, body lines ≤100 chars, no AI attribution. Branch `feat/web-api-proxy` (already created; spec already committed there).
+
+> **Amendment (2026-06-02, post-execution):** Tasks 1–3 are implemented and committed
+> (`d4452ab`, `9945e4c`, `0935099`). During execution a design correction was made:
+> the web-mode base is **absolute same-origin** (`${location.origin}/api`), not the bare
+> relative `"/api"`. Reason: a relative base makes `openapi-fetch` call
+> `new Request("/api/...")`, which throws `ERR_INVALID_URL` under node/undici and is
+> fragile in the browser. `dispatchFor` now returns `${location.origin}${API_PREFIX}`
+> in web mode. Consequently `fleet.test.tsx` **was** updated (the original claim that it
+> "stays green untouched" was wrong — the transport contract genuinely changed) and any
+> test that inspects the dispatched URL asserts the absolute same-origin form. Tests mock
+> `@/lib/http` (not `globalThis.fetch`), because `httpFetch` captures the fetch reference
+> at module load, so `vi.stubGlobal("fetch", …)` would not intercept it.
+
+---
+
+## File Structure
+
+| File | Responsibility | Action |
+|------|----------------|--------|
+| `packages/web/src/lib/platform.ts` | Single `isTauri()` predicate (leaf module, no app imports) | Create |
+| `packages/web/src/lib/dispatch.ts` | `dispatchFor()` + proxy constants — the one transport decision | Create |
+| `packages/web/src/lib/http.ts` | Keep `httpFetch`; consume `isTauri` from platform | Modify |
+| `packages/web/src/lib/discovery.ts` | Re-export `isTauri` from platform; route probe via `dispatchFor` | Modify |
+| `packages/web/src/api/client.ts` | Active-instance client via `dispatchFor` | Modify |
+| `packages/web/src/api/scopedClient.ts` | Scoped client (Fleet/seed-kits) via `dispatchFor` | Modify |
+| `packages/web/src/lib/config.ts` | `checkConnection` via `dispatchFor` + proxy-reject handling | Modify |
+| `packages/web/src/lib/runtimeConfig.ts` | Drop `same-origin` sentinel | Modify |
+| `packages/web/vite.config.ts` | Dev `/api` proxy middleware mirroring nginx | Modify |
+| `docker/nginx.conf.template` | Header-driven `^~ /api/` block (replaces `/v3` + `/health`) | Modify |
+| `docker/40-openconcho-config.sh` | Render allowlist `map` for `$allow_upstream` | Modify |
+| `docker-compose.yml` | Retire `HONCHO_UPSTREAM`; document allowlist env | Modify |
+| `AGENTS.md`, `README.md` | Proxy contract + env vars | Modify |
+| `packages/web/src/test/dispatch.test.ts` | Unit tests for `dispatchFor` | Create |
+| `packages/web/src/test/check-connection.test.ts` | Unit tests for `checkConnection` proxy behavior | Create |
+
+---
+
+## Task 1: Extract `isTauri()` into a leaf module
+
+Prevents an import cycle: `discovery.ts` will later import `dispatchFor`, and `dispatch.ts` needs `isTauri`. A leaf `platform.ts` breaks the cycle and gives one canonical predicate (WIOCHE).
+
+**Files:**
+- Create: `packages/web/src/lib/platform.ts`
+- Test: `packages/web/src/test/platform.test.ts`
+- Modify: `packages/web/src/lib/http.ts`, `packages/web/src/lib/discovery.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+`packages/web/src/test/platform.test.ts`:
+```ts
+import { afterEach, describe, expect, it } from "vitest";
+import { isTauri } from "@/lib/platform";
+
+describe("isTauri", () => {
+ afterEach(() => {
+ delete (window as unknown as Record).__TAURI_INTERNALS__;
+ });
+
+ it("returns false in a plain browser/jsdom environment", () => {
+ expect(isTauri()).toBe(false);
+ });
+
+ it("returns true when the Tauri internals global is present", () => {
+ (window as unknown as Record).__TAURI_INTERNALS__ = {};
+ expect(isTauri()).toBe(true);
+ });
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/platform.test.ts`
+Expected: FAIL — cannot resolve `@/lib/platform`.
+
+- [ ] **Step 3: Create the module**
+
+`packages/web/src/lib/platform.ts`:
+```ts
+/** True when running inside the Tauri desktop shell (WebView with injected internals). */
+export function isTauri(): boolean {
+ return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
+}
+```
+
+- [ ] **Step 4: Point existing consumers at the canonical predicate**
+
+In `packages/web/src/lib/http.ts`, replace the inline const with the shared predicate (call it at module load to preserve current behavior):
+```ts
+import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
+import { isTauri } from "@/lib/platform";
+
+// Route fetch through Rust (reqwest) when running in Tauri — bypasses WebView CORS enforcement.
+// Falls back to native browser fetch during plain web dev.
+export const httpFetch: typeof globalThis.fetch = isTauri()
+ ? (tauriFetch as typeof globalThis.fetch)
+ : globalThis.fetch;
+```
+
+In `packages/web/src/lib/discovery.ts`, delete the local `isTauri` function (lines 8-10) and re-export the canonical one so existing importers keep working. Add at the top, after the `httpFetch` import:
+```ts
+export { isTauri } from "@/lib/platform";
+```
+
+- [ ] **Step 5: Run tests + lint + typecheck**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/platform.test.ts && make lint && make typecheck`
+Expected: PASS; no type errors.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add packages/web/src/lib/platform.ts packages/web/src/lib/http.ts \
+ packages/web/src/lib/discovery.ts packages/web/src/test/platform.test.ts
+git commit -m "refactor(web): extract isTauri into a leaf platform module"
+```
+
+---
+
+## Task 2: `dispatchFor` helper + proxy constants
+
+The heart of the design. Decides transport per instance.
+
+**Files:**
+- Create: `packages/web/src/lib/dispatch.ts`
+- Test: `packages/web/src/test/dispatch.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+`packages/web/src/test/dispatch.test.ts`:
+```ts
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+const mockIsTauri = vi.fn();
+vi.mock("@/lib/platform", () => ({ isTauri: () => mockIsTauri() }));
+
+import {
+ API_PREFIX,
+ dispatchFor,
+ PROXY_REJECT_HEADER,
+ UPSTREAM_HEADER,
+} from "@/lib/dispatch";
+
+afterEach(() => mockIsTauri.mockReset());
+
+describe("dispatchFor — web mode", () => {
+ it("targets the /api prefix and carries the upstream header", () => {
+ mockIsTauri.mockReturnValue(false);
+ const d = dispatchFor({ baseUrl: "https://honcho.example.net/", token: "" });
+ expect(d.baseUrl).toBe(API_PREFIX);
+ expect(d.headers[UPSTREAM_HEADER]).toBe("https://honcho.example.net");
+ expect(d.headers.Authorization).toBeUndefined();
+ });
+
+ it("adds Authorization only when a token is present", () => {
+ mockIsTauri.mockReturnValue(false);
+ const d = dispatchFor({ baseUrl: "https://honcho.example.net", token: "sk-1" });
+ expect(d.headers.Authorization).toBe("Bearer sk-1");
+ });
+});
+
+describe("dispatchFor — tauri mode", () => {
+ it("targets the absolute URL with no upstream header", () => {
+ mockIsTauri.mockReturnValue(true);
+ const d = dispatchFor({ baseUrl: "https://honcho.example.net", token: "sk-1" });
+ expect(d.baseUrl).toBe("https://honcho.example.net");
+ expect(d.headers[UPSTREAM_HEADER]).toBeUndefined();
+ expect(d.headers.Authorization).toBe("Bearer sk-1");
+ });
+});
+
+describe("proxy reject header constant", () => {
+ it("is the agreed sentinel name", () => {
+ expect(PROXY_REJECT_HEADER).toBe("X-Honcho-Proxy-Reject");
+ });
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/dispatch.test.ts`
+Expected: FAIL — cannot resolve `@/lib/dispatch`.
+
+- [ ] **Step 3: Create the helper**
+
+`packages/web/src/lib/dispatch.ts`:
+```ts
+import { httpFetch } from "@/lib/http";
+import { isTauri } from "@/lib/platform";
+
+/** Same-origin path prefix the web build issues all Honcho calls through. */
+export const API_PREFIX = "/api";
+/** Request header naming the real Honcho upstream for the proxy to forward to. */
+export const UPSTREAM_HEADER = "X-Honcho-Upstream";
+/** Response header the proxy sets on its OWN refusals (so they aren't read as upstream auth). */
+export const PROXY_REJECT_HEADER = "X-Honcho-Proxy-Reject";
+
+export interface Dispatch {
+ baseUrl: string;
+ headers: Record;
+ fetch: typeof globalThis.fetch;
+}
+
+function normalizeUpstream(url: string): string {
+ return url.trim().replace(/\/+$/, "");
+}
+
+/**
+ * Resolve how to issue a request for an instance.
+ * - Web: same-origin `/api` + `X-Honcho-Upstream` header (proxy forwards server-side, no CORS).
+ * - Tauri: the absolute instance URL via reqwest (no browser same-origin policy).
+ */
+export function dispatchFor(instance: { baseUrl: string; token?: string }): Dispatch {
+ const headers: Record = { "Content-Type": "application/json" };
+ if (instance.token) headers.Authorization = `Bearer ${instance.token}`;
+
+ if (isTauri()) {
+ return { baseUrl: instance.baseUrl, headers, fetch: httpFetch };
+ }
+
+ headers[UPSTREAM_HEADER] = normalizeUpstream(instance.baseUrl);
+ return { baseUrl: API_PREFIX, headers, fetch: httpFetch };
+}
+```
+
+- [ ] **Step 4: Run test to verify it passes**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/dispatch.test.ts`
+Expected: PASS (5 assertions across 4 tests).
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add packages/web/src/lib/dispatch.ts packages/web/src/test/dispatch.test.ts
+git commit -m "feat(web): add dispatchFor transport helper for same-origin proxy"
+```
+
+---
+
+## Task 3: Route the API clients through `dispatchFor`
+
+**Files:**
+- Modify: `packages/web/src/api/client.ts`, `packages/web/src/api/scopedClient.ts`
+
+- [ ] **Step 1: Rewrite `client.ts`**
+
+`packages/web/src/api/client.ts`:
+```ts
+import createClient from "openapi-fetch";
+import { loadConfig } from "@/lib/config";
+import { dispatchFor } from "@/lib/dispatch";
+import type { paths } from "./schema.d.ts";
+
+export function createHonchoClient() {
+ const config = loadConfig() ?? { baseUrl: "http://localhost:8000", token: "" };
+ const { baseUrl, headers, fetch } = dispatchFor(config);
+ return createClient({ baseUrl, headers, fetch });
+}
+
+export const client = {
+ get current() {
+ return createHonchoClient();
+ },
+};
+```
+
+- [ ] **Step 2: Rewrite `scopedClient.ts`**
+
+`packages/web/src/api/scopedClient.ts`:
+```ts
+import createClient from "openapi-fetch";
+import type { Instance } from "@/lib/config";
+import { dispatchFor } from "@/lib/dispatch";
+import type { paths } from "./schema.d.ts";
+
+export type ScopedClient = ReturnType>;
+
+/**
+ * Create an openapi-fetch client bound to a specific instance. Use for views that
+ * query non-active instances (e.g. the Fleet side-by-side comparison). Each scoped
+ * client self-routes via its own X-Honcho-Upstream header in web mode.
+ */
+export function createScopedClient(instance: Instance): ScopedClient {
+ const { baseUrl, headers, fetch } = dispatchFor(instance);
+ return createClient({ baseUrl, headers, fetch });
+}
+```
+
+- [ ] **Step 3: Verify the full web suite stays green**
+
+Run: `pnpm --filter @openconcho/web exec vitest run && make typecheck && make lint`
+Expected: PASS — existing `fleet.test.tsx`, `seed-kits.test.ts`, `app.test.tsx` still pass (transport swap is invisible to them).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add packages/web/src/api/client.ts packages/web/src/api/scopedClient.ts
+git commit -m "feat(web): route api clients through dispatchFor"
+```
+
+---
+
+## Task 4: `checkConnection` + discovery via the proxy, with reject handling
+
+**Files:**
+- Modify: `packages/web/src/lib/config.ts` (`checkConnection`), `packages/web/src/lib/discovery.ts` (`suggestNameForInstance`)
+- Test: `packages/web/src/test/check-connection.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+`packages/web/src/test/check-connection.test.ts`:
+```ts
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+// Mock the platform predicate (web mode) and the fetch boundary. We mock
+// @/lib/http — NOT globalThis.fetch — because httpFetch captures the fetch
+// reference at module load, so vi.stubGlobal would not be observed by dispatchFor.
+const { mockIsTauri, httpFetchMock } = vi.hoisted(() => ({
+ mockIsTauri: vi.fn(() => false),
+ httpFetchMock: vi.fn(),
+}));
+vi.mock("@/lib/platform", () => ({ isTauri: () => mockIsTauri() }));
+vi.mock("@/lib/http", () => ({ httpFetch: httpFetchMock }));
+
+import { checkConnection } from "@/lib/config";
+
+afterEach(() => {
+ httpFetchMock.mockReset();
+ mockIsTauri.mockReturnValue(false);
+});
+
+describe("checkConnection — web proxy mode", () => {
+ it("calls the absolute same-origin /api path with the upstream header", async () => {
+ httpFetchMock.mockResolvedValue(new Response("{}", { status: 200 }));
+ const res = await checkConnection("https://honcho.example.net", "sk-1");
+ expect(res.status).toBe("ok");
+ const [url, init] = httpFetchMock.mock.calls[0];
+ expect(String(url)).toBe(`${location.origin}/api/v3/workspaces/list`);
+ expect((init.headers as Record)["X-Honcho-Upstream"]).toBe(
+ "https://honcho.example.net",
+ );
+ expect((init.headers as Record).Authorization).toBe("Bearer sk-1");
+ });
+
+ it("maps an upstream 401 to auth-required", async () => {
+ httpFetchMock.mockResolvedValue(new Response("{}", { status: 401 }));
+ const res = await checkConnection("https://honcho.example.net");
+ expect(res.status).toBe("auth-required");
+ });
+
+ it("treats a proxy reject as unreachable, not auth-required", async () => {
+ httpFetchMock.mockResolvedValue(
+ new Response("", { status: 403, headers: { "X-Honcho-Proxy-Reject": "allowlist" } }),
+ );
+ const res = await checkConnection("https://blocked.example.net");
+ expect(res.status).toBe("unreachable");
+ expect(res.message).toMatch(/allowlist/i);
+ });
+});
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/check-connection.test.ts`
+Expected: FAIL — current `checkConnection` fetches `${baseUrl}/v3/...` directly (no `/api`, no upstream header, no reject handling).
+
+- [ ] **Step 3: Rewrite `checkConnection`**
+
+In `packages/web/src/lib/config.ts`, replace the `checkConnection` body (keep the signature). Add the import `import { dispatchFor, PROXY_REJECT_HEADER } from "@/lib/dispatch";` at the top (and remove the now-unused `httpFetch` import if nothing else in the file uses it):
+```ts
+export async function checkConnection(
+ baseUrl: string,
+ token?: string,
+): Promise<{ status: HealthStatus; message: string }> {
+ try {
+ const { baseUrl: base, headers, fetch } = dispatchFor({ baseUrl, token });
+ const res = await fetch(`${base}/v3/workspaces/list`, {
+ method: "POST",
+ headers,
+ body: JSON.stringify({}),
+ signal: AbortSignal.timeout(5000),
+ });
+
+ const reject = res.headers.get(PROXY_REJECT_HEADER);
+ if (reject) {
+ return { status: "unreachable", message: `Proxy refused upstream (${reject})` };
+ }
+ if (res.ok) return { status: "ok", message: "Connected successfully" };
+ if (res.status === 401 || res.status === 403) {
+ return { status: "auth-required", message: "Authentication required — provide an API token" };
+ }
+ return { status: "unreachable", message: `Server returned ${res.status}` };
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : "Unknown error";
+ if (msg.includes("AbortError") || msg.includes("timeout")) {
+ return { status: "unreachable", message: "Connection timed out" };
+ }
+ return { status: "unreachable", message: `Cannot reach server: ${msg}` };
+ }
+}
+```
+
+- [ ] **Step 4: Route the discovery probe through `dispatchFor` too**
+
+In `packages/web/src/lib/discovery.ts`, rewrite `suggestNameForInstance` to dispatch consistently. Add `import { dispatchFor } from "@/lib/dispatch";` and replace the fetch line:
+```ts
+export async function suggestNameForInstance(baseUrl: string): Promise {
+ try {
+ const { baseUrl: base, headers, fetch } = dispatchFor({ baseUrl });
+ const res = await fetch(`${base}/v3/workspaces/list?page=1&page_size=1`, {
+ method: "POST",
+ headers,
+ body: JSON.stringify({}),
+ signal: AbortSignal.timeout(2000),
+ });
+ if (!res.ok) return null;
+ const data = (await res.json()) as { items?: Array<{ id?: string }> };
+ const wsId = data.items?.[0]?.id;
+ if (typeof wsId === "string" && wsId.length > 0) {
+ return deriveNameFromWorkspaceId(wsId);
+ }
+ return null;
+ } catch {
+ return null;
+ }
+}
+```
+(Leave the top-of-file `httpFetch` import only if still referenced; otherwise remove it. `discovery.ts` no longer needs `httpFetch` after this change — remove the import.)
+
+- [ ] **Step 5: Run tests + gate**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/check-connection.test.ts && pnpm --filter @openconcho/web exec vitest run && make typecheck && make lint`
+Expected: PASS.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add packages/web/src/lib/config.ts packages/web/src/lib/discovery.ts \
+ packages/web/src/test/check-connection.test.ts
+git commit -m "feat(web): route checkConnection and discovery through the proxy"
+```
+
+---
+
+## Task 5: Drop the `same-origin` sentinel from runtime config
+
+In header mode the default instance needs a real absolute URL (it becomes the header value); `same-origin` was glue for the retired `/v3` proxy.
+
+**Files:**
+- Modify: `packages/web/src/lib/runtimeConfig.ts`
+- Test: `packages/web/src/test/runtime-config.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+`packages/web/src/test/runtime-config.test.ts`:
+```ts
+import { afterEach, describe, expect, it } from "vitest";
+import { runtimeDefaultBaseUrl } from "@/lib/runtimeConfig";
+
+const KEY = "__OPENCONCHO_DEFAULT_HONCHO_URL__";
+
+afterEach(() => {
+ delete (globalThis as Record)[KEY];
+});
+
+describe("runtimeDefaultBaseUrl", () => {
+ it("returns an injected absolute URL verbatim", () => {
+ (globalThis as Record)[KEY] = "https://honcho.example.net";
+ expect(runtimeDefaultBaseUrl()).toBe("https://honcho.example.net");
+ });
+
+ it("returns null when unset or empty", () => {
+ expect(runtimeDefaultBaseUrl()).toBeNull();
+ (globalThis as Record)[KEY] = " ";
+ expect(runtimeDefaultBaseUrl()).toBeNull();
+ });
+});
+```
+
+- [ ] **Step 2: Run test to verify current behavior is covered/fails appropriately**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/runtime-config.test.ts`
+Expected: PASS for absolute/empty (existing behavior), but the goal is to simplify; proceed to remove the sentinel branch.
+
+- [ ] **Step 3: Simplify the module**
+
+`packages/web/src/lib/runtimeConfig.ts`:
+```ts
+const GLOBAL_KEY = "__OPENCONCHO_DEFAULT_HONCHO_URL__";
+
+/**
+ * Runtime-injected default Honcho base URL for container deployments.
+ *
+ * The Docker image writes `/config.js` from `OPENCONCHO_DEFAULT_HONCHO_URL` at
+ * container start, so one prebuilt image can target any backend without a rebuild.
+ * The web build proxies this URL via the same-origin `/api` reverse proxy (no CORS).
+ *
+ * - an absolute URL → that URL (seeds the first instance)
+ * - empty / unset → null (no default; the user configures in Settings)
+ */
+export function runtimeDefaultBaseUrl(): string | null {
+ const raw = (globalThis as Record)[GLOBAL_KEY];
+ if (typeof raw !== "string" || raw.trim() === "") return null;
+ return raw.trim();
+}
+```
+
+- [ ] **Step 4: Run test + gate**
+
+Run: `pnpm --filter @openconcho/web exec vitest run src/test/runtime-config.test.ts && make typecheck && make lint`
+Expected: PASS.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add packages/web/src/lib/runtimeConfig.ts packages/web/src/test/runtime-config.test.ts
+git commit -m "refactor(web): drop same-origin sentinel from runtime config"
+```
+
+---
+
+## Task 6: nginx header-driven `/api` proxy
+
+**Files:**
+- Modify: `docker/nginx.conf.template`
+
+- [ ] **Step 1: Replace the `/v3` + `/health` blocks with the `/api` block**
+
+In `docker/nginx.conf.template`, delete the `location ^~ /v3/ { ... }` and `location = /health { ... }` blocks (lines 21-32) and the `set $honcho_upstream ...` line. Keep the `resolver` line. Insert:
+```nginx
+ # Header-driven same-origin proxy: the browser names the Honcho upstream per
+ # request via X-Honcho-Upstream, so the browser never makes a cross-origin call.
+ # $allow_upstream is provided by the allowlist map in conf.d (entrypoint-rendered).
+ location ^~ /api/ {
+ set $upstream $http_x_honcho_upstream;
+ if ($upstream = "") {
+ add_header X-Honcho-Proxy-Reject "no-upstream" always;
+ return 421;
+ }
+ if ($allow_upstream = 0) {
+ add_header X-Honcho-Proxy-Reject "allowlist" always;
+ return 403;
+ }
+ rewrite ^/api/(.*)$ /$1 break;
+ proxy_pass $upstream;
+ proxy_ssl_server_name on;
+ proxy_set_header Host $proxy_host;
+ proxy_set_header X-Honcho-Upstream "";
+ }
+```
+
+- [ ] **Step 2: Validate template renders to valid nginx syntax**
+
+Run (renders the template with a dummy upstream and an open allowlist map, then `nginx -t`):
+```bash
+docker run --rm -e HONCHO_UPSTREAM=http://x:8000 -v "$PWD/docker":/d nginxinc/nginx-unprivileged:stable sh -c '
+ mkdir -p /etc/nginx/conf.d
+ echo "map \$http_x_honcho_upstream \$allow_upstream { default 1; }" > /etc/nginx/conf.d/allowlist_map.conf
+ envsubst "\$HONCHO_UPSTREAM" < /d/nginx.conf.template > /etc/nginx/conf.d/default.conf
+ nginx -t'
+```
+Expected: `nginx: configuration file /etc/nginx/nginx.conf test is successful`.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add docker/nginx.conf.template
+git commit -m "feat(docker): header-driven /api reverse proxy in nginx"
+```
+
+---
+
+## Task 7: Entrypoint renders the allowlist map
+
+**Files:**
+- Modify: `docker/40-openconcho-config.sh`
+
+- [ ] **Step 1: Append allowlist-map rendering**
+
+In `docker/40-openconcho-config.sh`, after the existing `config.js` heredoc, append:
+```sh
+# Render the SSRF allowlist into an nginx map for $allow_upstream.
+# Unset/empty OPENCONCHO_UPSTREAM_ALLOWLIST → open (default 1), fine for the
+# localhost-bound default. Set it (comma-separated host globs) before exposing
+# the proxy (e.g. behind a tunnel) to reject non-matching upstreams.
+ALLOWLIST_CONF=/etc/nginx/conf.d/allowlist_map.conf
+if [ -z "${OPENCONCHO_UPSTREAM_ALLOWLIST:-}" ]; then
+ printf 'map $http_x_honcho_upstream $allow_upstream { default 1; }\n' > "$ALLOWLIST_CONF"
+else
+ {
+ printf 'map $http_x_honcho_upstream $allow_upstream {\n'
+ printf ' default 0;\n'
+ IFS=','
+ for host in $OPENCONCHO_UPSTREAM_ALLOWLIST; do
+ host=$(printf '%s' "$host" | tr -d ' ')
+ [ -z "$host" ] && continue
+ esc=$(printf '%s' "$host" | sed -e 's/[.]/\\./g' -e 's/[*]/[^/]*/g')
+ printf ' "~^https?://%s(:[0-9]+)?(/.*)?$" 1;\n' "$esc"
+ done
+ printf '}\n'
+ } > "$ALLOWLIST_CONF"
+fi
+```
+
+- [ ] **Step 2: Validate generated map syntax (allowlist set)**
+
+Run:
+```bash
+docker run --rm -e OPENCONCHO_UPSTREAM_ALLOWLIST="honcho.example.net,*.honcho.dev" \
+ -e HONCHO_UPSTREAM=http://x:8000 -v "$PWD/docker":/d nginxinc/nginx-unprivileged:stable sh -c '
+ mkdir -p /etc/nginx/conf.d
+ export OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net
+ sh /d/40-openconcho-config.sh || true
+ envsubst "\$HONCHO_UPSTREAM" < /d/nginx.conf.template > /etc/nginx/conf.d/default.conf
+ nginx -t && cat /etc/nginx/conf.d/allowlist_map.conf'
+```
+Expected: `nginx -t` success; printed map contains regex lines for both hosts.
+Note: the script writes `config.js` to `/usr/share/nginx/html`; if that dir is absent in this bare check, the heredoc line may error — that is fine for syntax validation (the `|| true` guards it). The allowlist block still runs.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add docker/40-openconcho-config.sh
+git commit -m "feat(docker): render SSRF allowlist map from env"
+```
+
+---
+
+## Task 8: Vite dev proxy middleware (dev/CI parity)
+
+**Files:**
+- Modify: `packages/web/vite.config.ts`
+
+- [ ] **Step 1: Add a `configureServer` plugin mirroring nginx**
+
+In `packages/web/vite.config.ts`, add this plugin factory above `defineConfig` and include `honchoApiProxy()` in the `plugins` array (after `react()`):
+```ts
+import type { Plugin } from "vite";
+
+function honchoApiProxy(): Plugin {
+ const HEADER = "x-honcho-upstream";
+ return {
+ name: "honcho-api-proxy",
+ configureServer(server) {
+ server.middlewares.use("/api", async (req, res) => {
+ const upstream = req.headers[HEADER];
+ if (typeof upstream !== "string" || upstream.trim() === "") {
+ res.statusCode = 421;
+ res.setHeader("X-Honcho-Proxy-Reject", "no-upstream");
+ res.end();
+ return;
+ }
+ const target = upstream.replace(/\/+$/, "") + (req.url ?? "");
+ const chunks: Buffer[] = [];
+ for await (const c of req) chunks.push(c as Buffer);
+ try {
+ const upstreamRes = await fetch(target, {
+ method: req.method,
+ headers: {
+ "content-type": req.headers["content-type"] ?? "application/json",
+ ...(req.headers.authorization
+ ? { authorization: req.headers.authorization }
+ : {}),
+ },
+ body: ["GET", "HEAD"].includes(req.method ?? "") ? undefined : Buffer.concat(chunks),
+ });
+ res.statusCode = upstreamRes.status;
+ upstreamRes.headers.forEach((v, k) => res.setHeader(k, v));
+ res.end(Buffer.from(await upstreamRes.arrayBuffer()));
+ } catch (e) {
+ res.statusCode = 502;
+ res.end(`proxy error: ${e instanceof Error ? e.message : String(e)}`);
+ }
+ });
+ },
+ };
+}
+```
+Update the plugins line to:
+```ts
+ plugins: [tanstackRouter({ autoCodeSplitting: true }), react(), honchoApiProxy(), tailwindcss()],
+```
+
+- [ ] **Step 2: Typecheck + lint (the config is type-checked by the build)**
+
+Run: `make typecheck && make lint`
+Expected: PASS (no `any`, `Plugin` typed).
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add packages/web/vite.config.ts
+git commit -m "feat(web): dev /api proxy middleware mirroring nginx"
+```
+
+---
+
+## Task 9: Compose env + docs
+
+**Files:**
+- Modify: `docker-compose.yml`, `AGENTS.md`, `README.md`
+
+- [ ] **Step 1: Update `docker-compose.yml`**
+
+Replace the `environment:` block and its comments so the upstream is no longer a single env var. New `environment:` section:
+```yaml
+ environment:
+ # The SPA seeds its first instance from this absolute URL; the browser then
+ # routes all calls same-origin through /api, and nginx forwards them to the
+ # URL named per-request in the X-Honcho-Upstream header (no browser CORS).
+ OPENCONCHO_DEFAULT_HONCHO_URL: ${OPENCONCHO_DEFAULT_HONCHO_URL:-http://host.docker.internal:8000}
+ # Optional SSRF guard. Unset = forward anywhere (safe for the localhost-only
+ # binding below). Set comma-separated host globs before exposing the proxy:
+ # OPENCONCHO_UPSTREAM_ALLOWLIST: honcho.example.net,*.honcho.dev
+ OPENCONCHO_UPSTREAM_ALLOWLIST: ${OPENCONCHO_UPSTREAM_ALLOWLIST:-}
+```
+Update the top-of-file comment block: replace the `HONCHO_UPSTREAM=...` example with `OPENCONCHO_DEFAULT_HONCHO_URL=https://honcho.example.net docker compose up` and note the per-request header model.
+
+- [ ] **Step 2: Update `AGENTS.md` Key Constraints**
+
+Replace the CORS-relevant bullet(s) with:
+```markdown
+- **Web CORS is handled by a same-origin `/api` proxy** — the browser issues all
+ Honcho calls to `/api/*` with an `X-Honcho-Upstream` header; nginx (docker) and a
+ Vite middleware (dev) forward server-side. Tauri bypasses CORS via reqwest and uses
+ absolute URLs. Optional `OPENCONCHO_UPSTREAM_ALLOWLIST` guards the proxy when exposed.
+```
+
+- [ ] **Step 3: Update `README.md`**
+
+In the Docker/run section, document `OPENCONCHO_DEFAULT_HONCHO_URL` (absolute URL seed) and `OPENCONCHO_UPSTREAM_ALLOWLIST` (optional, comma-separated host globs), and remove any `HONCHO_UPSTREAM` references.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add docker-compose.yml AGENTS.md README.md
+git commit -m "docs: document the /api proxy contract and env vars"
+```
+
+---
+
+## Task 10: Final CI-parity gate + branch readiness
+
+**Files:** none (verification only)
+
+- [ ] **Step 1: Run the full web gate**
+
+Run: `make ci-web`
+Expected: lint, typecheck, test, and build all PASS.
+
+- [ ] **Step 2: Confirm no `HONCHO_UPSTREAM` or `same-origin` references remain**
+
+Run: `grep -rn "HONCHO_UPSTREAM\|same-origin" docker docker-compose.yml packages/web/src README.md AGENTS.md`
+Expected: no matches in code/config (only the spec/plan under `docs/` may mention them historically).
+
+- [ ] **Step 3: Confirm clean tree**
+
+Run: `git status --short`
+Expected: empty (all work committed).
+
+---
+
+## Self-Review (completed by author)
+
+- **Spec coverage:** dispatch helper (T2/T3), checkConnection+discovery (T4), nginx header proxy + SNI + reject header (T6), allowlist map (T7), vite parity (T8), env migration + docs (T9), Fleet preserved (T3 — scopedClient routed; existing fleet.test.tsx asserted green), runtime sentinel drop (T5). All spec sections mapped.
+- **Placeholder scan:** none — every code step shows full content.
+- **Type consistency:** `dispatchFor`, `Dispatch`, `API_PREFIX`, `UPSTREAM_HEADER`, `PROXY_REJECT_HEADER` are defined in T2 and consumed verbatim in T3/T4. `isTauri()` defined T1, consumed T2.
diff --git a/docs/superpowers/specs/2026-06-02-honcho-api-proxy-design.md b/docs/superpowers/specs/2026-06-02-honcho-api-proxy-design.md
new file mode 100644
index 0000000..164abdc
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-02-honcho-api-proxy-design.md
@@ -0,0 +1,243 @@
+# Header-Driven `/api` Proxy for Web CORS — Design
+
+- **Date:** 2026-06-02
+- **Status:** Approved (design) — pending spec review
+- **Scope:** One concern — eliminate browser CORS for the web build by routing
+ Honcho API calls through a same-origin, header-driven reverse proxy. Preserve
+ existing Fleet aggregation. No new aggregation features (deferred).
+
+## Problem
+
+The web build (`@openconcho/web`) talks to Honcho directly from the browser. When
+the configured instance URL is a different origin than the page (e.g. a self-hosted
+Honcho at `https://honcho.example.net` while the UI runs on
+`http://localhost:8080`), the browser issues a CORS preflight on the `Authorization`
+header and the request fails — Honcho ships no `CORSMiddleware`.
+
+The desktop (Tauri) build is unaffected: it routes fetch through Rust/`reqwest`
+(`packages/web/src/lib/http.ts`), which has no browser same-origin policy.
+
+The repo already had a partial mitigation (a static `^~ /v3/` nginx proxy keyed to a
+single `HONCHO_UPSTREAM`), but it (a) supported only one backend and (b) was bypassed
+the moment a user typed an absolute URL into Settings — which is the bug that surfaced.
+
+### Evidence gathered
+
+- Browser → `honcho.example.net` is reachable; the CORS error proves the
+ request reached Honcho and only the browser policy check failed.
+- A Docker container under Colima **also** reaches the tailnet: `docker run ...
+ curl https://honcho.example.net/health` returned **HTTP 200**, connected over the
+ tailnet on `:443` with TLS verified. So a container-side proxy is viable on this
+ host (Colima forwards container egress through the host's tailnet routing).
+
+## Decisions
+
+1. **Coexist by runtime mode.** Tauri keeps absolute-URL + `reqwest`. The web build
+ (docker **and** `make dev-web`) routes through a same-origin `/api` proxy. One
+ build; behavior chosen at runtime by `isTauri()`.
+2. **Header-driven routing.** The browser names the target upstream per request via
+ an `X-Honcho-Upstream` header (sourced from the active/scoped instance's
+ `baseUrl`). The proxy is a stateless forwarder; the frontend stays the single
+ source of truth for instances. No server-side slug→upstream map.
+3. **SSRF posture: optional allowlist, open by default.** Unset
+ `OPENCONCHO_UPSTREAM_ALLOWLIST` ⇒ forward anywhere (safe for the default
+ `127.0.0.1:8080` binding). Set it (host globs) before exposing the proxy (e.g.
+ behind `cloudflared`) to reject non-matching upstreams.
+4. **Aggregation: preserve, don't extend.** The existing Fleet dashboard
+ (`compareQueries.ts`, `fleetAggregates.ts`, `FleetDashboard`/`FleetRow`) must keep
+ working identically. New cross-instance merge/dedup/search is explicitly a
+ **non-goal** of this PR.
+
+## Architecture
+
+```
+WEB (docker + dev):
+ browser ──same-origin──▶ /api/v3/... (openapi-fetch base = "/api")
+ X-Honcho-Upstream: https://honcho.example.net (from instance.baseUrl)
+ Authorization: Bearer … (unchanged, when set)
+ │ proxy: validate header, allowlist-check, strip "/api",
+ │ proxy_pass $upstream, set SNI/Host, drop routing header
+ ▼
+ https://honcho.example.net/v3/... (server-side hop — no CORS)
+
+TAURI:
+ webview ──reqwest──▶ https://honcho.example.net/v3/... (unchanged)
+```
+
+**Why a custom header is free here:** `X-Honcho-Upstream` rides a *same-origin*
+request (browser → `/api`), so it triggers no CORS preflight. Preflight only fires
+cross-origin — the exact condition this design removes.
+
+**Why the instance store is unchanged:** instances still persist an absolute
+`baseUrl` (`z.string().url()` stays valid). We change only *how a request is
+dispatched*, not what is stored. In web mode the instance URL stops being the fetch
+target and becomes the header value.
+
+## Components
+
+### A. Centralized dispatch helper (new) — `src/lib/dispatch.ts`
+
+Single source of truth for "how to issue a request for an instance," replacing four
+ad-hoc constructions.
+
+```ts
+export const API_PREFIX = "/api";
+export const UPSTREAM_HEADER = "X-Honcho-Upstream";
+
+export interface Dispatch {
+ baseUrl: string; // "/api" (web) | instance.baseUrl (tauri)
+ headers: Record; // Content-Type, Authorization?, X-Honcho-Upstream?
+ fetch: typeof globalThis.fetch; // globalThis.fetch (web) | tauriFetch (tauri)
+}
+
+export function dispatchFor(
+ instance: Pick,
+): Dispatch;
+```
+
+- **Web:** `baseUrl = API_PREFIX`; headers include `UPSTREAM_HEADER =
+ normalizedUpstream(instance.baseUrl)` (trailing slash stripped) and `Authorization`
+ when `token` is non-empty; `fetch = globalThis.fetch`.
+- **Tauri:** `baseUrl = instance.baseUrl`; no upstream header; `fetch = tauriFetch`.
+
+`API_PREFIX` and `UPSTREAM_HEADER` are named constants (WIOCHE) referenced by both
+the frontend and documented for the proxy.
+
+### B. Consumers of the helper (the four dispatch sites)
+
+| Site | File | Change |
+|------|------|--------|
+| Active-instance client | `src/api/client.ts` | build client from `dispatchFor(loadConfig())` |
+| Scoped client (Fleet/compare, seed-kits) | `src/api/scopedClient.ts` | build client from `dispatchFor(instance)` |
+| Connection health check | `src/lib/config.ts` `checkConnection` | fetch `${baseUrl}/v3/workspaces/list` via `dispatchFor` (hits `/api/...` in web) |
+| Discovery name probe | `src/lib/discovery.ts` `suggestNameForInstance` | same, via `dispatchFor` |
+
+`compareQueries.ts` and `fleetAggregates.ts` need **no changes** — they go through
+`createScopedClient`, so the transport swap is invisible to them. `FleetRow.tsx:114`
+uses `instance.baseUrl` only to render a hostname label (cosmetic) — untouched.
+
+### C. nginx proxy — `docker/nginx.conf.template`
+
+Replace the `^~ /v3/` and `= /health` upstream blocks with one header-driven block:
+
+```nginx
+resolver 127.0.0.11 ipv6=off valid=10s;
+
+# Rendered by the entrypoint from OPENCONCHO_UPSTREAM_ALLOWLIST.
+# Unset → default 1 (open). Set → 1 only for matching hosts, else 0.
+# (map block injected here)
+
+location ^~ /api/ {
+ set $upstream $http_x_honcho_upstream;
+ if ($upstream = "") { return 421; } # misdirected: no target named
+ if ($allow_upstream = 0) { return 403; } # allowlist reject
+ rewrite ^/api/(.*)$ /$1 break; # strip /api, keep /v3/...
+ proxy_pass $upstream;
+ proxy_ssl_server_name on; # SNI for HTTPS upstreams
+ proxy_set_header Host $proxy_host; # upstream host, not localhost:8080
+ proxy_set_header X-Honcho-Upstream ""; # never leak routing header upstream
+}
+```
+
+`Authorization` and other client headers pass through by nginx default. The
+container's own `/healthz` liveness endpoint is unchanged.
+
+### D. Vite dev parity — `vite.config.ts`
+
+A `configureServer` middleware mirrors nginx for `make dev-web`: read
+`X-Honcho-Upstream`, forward `/api/*` (prefix stripped) to it, drop the routing
+header, apply the same allowlist if configured. Result: dev behaves identically to
+the docker image (local/CI parity).
+
+### E. Config / env — `docker/40-openconcho-config.sh`, `docker-compose.yml`
+
+- `OPENCONCHO_DEFAULT_HONCHO_URL` keeps seeding the first instance, now an **absolute
+ URL only**. Drop the `same-origin` sentinel (glue for the retired `/v3` proxy) from
+ `src/lib/runtimeConfig.ts`.
+- **Retire `HONCHO_UPSTREAM`** from compose — the upstream now comes from the header.
+- New optional `OPENCONCHO_UPSTREAM_ALLOWLIST` (comma-separated host globs, e.g.
+ `honcho.example.net,*.honcho.dev`). The entrypoint renders it into the
+ nginx `map` for `$allow_upstream`; unset ⇒ map default 1.
+
+## Data flow (Fleet aggregation, web mode)
+
+```
+FleetDashboard → useScoped*(instanceA) → createScopedClient(A) → dispatchFor(A)
+ POST /api/v3/... X-Honcho-Upstream: A ─┐
+ → useScoped*(instanceB) → createScopedClient(B) → dispatchFor(B) ├▶ nginx → A,B
+ POST /api/v3/... X-Honcho-Upstream: B ─┘ (concurrent)
+fleetAggregates.ts merges results (transport-agnostic). B down ⇒ only B's column errors.
+```
+
+Query keys in `compareQueries.ts` are already scoped by `instance.id`, so caches
+never collide across columns. Stateless per-request routing isolates partial
+failures per instance.
+
+## Error handling
+
+The proxy must not let its own refusals masquerade as upstream responses. Both
+proxy-origin refusals carry a sentinel response header **`X-Honcho-Proxy-Reject`**
+(value: `no-upstream` | `allowlist`). `checkConnection` treats any response bearing
+that header as `unreachable` with the reject reason — **regardless of status code** —
+so an allowlist `403` is never mis-mapped to the upstream's auth `403`.
+
+- **No `X-Honcho-Upstream`** (misconfigured web request) → proxy `421` +
+ `X-Honcho-Proxy-Reject: no-upstream`. Fail loud, not silent.
+- **Allowlist reject** → proxy `403` + `X-Honcho-Proxy-Reject: allowlist`. The
+ sentinel header is what disambiguates it from the upstream's own `401/403` (which
+ arrive without the header and still map to `auth-required`).
+- **Upstream unreachable / TLS failure** → nginx `502`; UI shows "Cannot reach
+ server." This is the symptom if a future host's container is *not* on the tailnet —
+ a network problem, not CORS (documented caveat: proxy portability depends on the
+ container host being able to route to the upstream).
+
+## Testing (TDD)
+
+Unit (mock only `isTauri` + the fetch boundary, per project test rules):
+
+1. `dispatchFor` (web): `baseUrl === "/api"`, sets `X-Honcho-Upstream` to the
+ normalized instance URL, sets `Authorization` only when token non-empty, uses
+ `globalThis.fetch`.
+2. `dispatchFor` (tauri): `baseUrl === instance.baseUrl`, no upstream header, uses
+ `tauriFetch`.
+3. `checkConnection` (web): issues to `/api/v3/workspaces/list` with the upstream
+ header; maps `ok` / `401|403` / other correctly.
+4. **Aggregation regression:** two scoped clients for distinct instances emit two
+ distinct `X-Honcho-Upstream` values; existing `src/test/fleet.test.tsx` stays
+ green.
+
+Integration (nginx): compose up against a stub upstream — assert (a) header →
+forward with `/api` stripped, (b) missing header → 421, (c) allowlist miss → 403,
+(d) `X-Honcho-Upstream` absent on the upstream-side request.
+
+## Non-goals (deferred to separate PRs)
+
+- New cross-instance aggregation intelligence (unified merged lists, dedup, conflict
+ resolution, cross-instance search).
+- Running Tailscale inside the Colima VM / any host-networking change (not needed —
+ reachability confirmed on the target host).
+- Adding `CORSMiddleware` to Honcho (the proxy keeps openconcho self-contained and
+ backend-agnostic; this is the deliberate alternative *not* taken).
+
+## Files touched
+
+- `packages/web/src/lib/dispatch.ts` (new)
+- `packages/web/src/api/client.ts`
+- `packages/web/src/api/scopedClient.ts`
+- `packages/web/src/lib/config.ts`
+- `packages/web/src/lib/discovery.ts`
+- `packages/web/src/lib/runtimeConfig.ts`
+- `packages/web/vite.config.ts`
+- `docker/nginx.conf.template`
+- `docker/40-openconcho-config.sh`
+- `docker-compose.yml`
+- tests: `packages/web/src/test/` (new dispatch + checkConnection cases; keep
+ `fleet.test.tsx`, `settings-form.test.tsx` green)
+- docs: `AGENTS.md`, `README` (env vars + the proxy contract)
+
+## Constraints honored
+
+- Web-only change → PR CI (web checks) covers it; no desktop `cargo-check` needed
+ (Tauri path unchanged).
+- No hardcoded URLs (upstream comes from instance config / runtime env).
+- One concern per PR (proxy only); conventional commits; push under `offendingcommit`.
diff --git a/package.json b/package.json
index 5833617..2b1a904 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "openconcho",
"private": true,
- "version": "0.13.1",
+ "version": "0.15.0",
"packageManager": "pnpm@10.33.2",
"engines": {
"node": ">=22",
@@ -27,17 +27,17 @@
},
"devDependencies": {
"@biomejs/biome": "catalog:",
- "@commitlint/cli": "~20.5.2",
- "@commitlint/config-conventional": "~20.5.0",
- "@semantic-release/changelog": "^6.0.0",
- "@semantic-release/commit-analyzer": "^13.0.0",
+ "@commitlint/cli": "~20.5.3",
+ "@commitlint/config-conventional": "~20.5.3",
+ "@semantic-release/changelog": "^6.0.3",
+ "@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/exec": "^7.1.0",
- "@semantic-release/git": "^10.0.0",
- "@semantic-release/github": "^10.0.0",
+ "@semantic-release/git": "^10.0.1",
+ "@semantic-release/github": "^10.3.5",
"@semantic-release/npm": "^13.1.5",
- "@semantic-release/release-notes-generator": "^14.0.0",
+ "@semantic-release/release-notes-generator": "^14.1.1",
"husky": "~9.1.7",
"semantic-release": "catalog:",
- "turbo": "^2"
+ "turbo": "^2.9.16"
}
}
diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock
index 5503060..dcd6a4b 100644
--- a/packages/desktop/src-tauri/Cargo.lock
+++ b/packages/desktop/src-tauri/Cargo.lock
@@ -78,9 +78,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "base64"
@@ -117,9 +117,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.11.1"
+version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a"
dependencies = [
"serde_core",
]
@@ -144,9 +144,9 @@ dependencies = [
[[package]]
name = "brotli"
-version = "8.0.2"
+version = "8.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
+checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -155,19 +155,28 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "5.0.0"
+version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
+checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
-name = "bumpalo"
-version = "3.20.2"
+name = "bs58"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytemuck"
@@ -196,7 +205,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"cairo-sys-rs",
"glib",
"libc",
@@ -259,9 +268,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.61"
+version = "1.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -348,12 +357,6 @@ dependencies = [
"tiny-keccak",
]
-[[package]]
-name = "convert_case"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
-
[[package]]
name = "cookie"
version = "0.18.1"
@@ -415,7 +418,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"core-foundation 0.10.1",
"core-graphics-types",
"foreign-types",
@@ -428,7 +431,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"core-foundation 0.10.1",
"libc",
]
@@ -482,23 +485,6 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "cssparser"
-version = "0.29.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa"
-dependencies = [
- "cssparser-macros",
- "dtoa-short",
- "itoa",
- "matches",
- "phf 0.10.1",
- "proc-macro2",
- "quote",
- "smallvec",
- "syn 1.0.109",
-]
-
[[package]]
name = "cssparser"
version = "0.36.0"
@@ -508,7 +494,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
- "phf 0.13.1",
+ "phf",
"smallvec",
]
@@ -524,14 +510,20 @@ dependencies = [
[[package]]
name = "ctor"
-version = "0.2.9"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
+checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
dependencies = [
- "quote",
- "syn 2.0.117",
+ "ctor-proc-macro",
+ "dtor",
]
+[[package]]
+name = "ctor-proc-macro"
+version = "0.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
+
[[package]]
name = "darling"
version = "0.23.0"
@@ -572,6 +564,17 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
+[[package]]
+name = "dbus"
+version = "0.9.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b942602992bb7acfd1f51c49811c58a610ef9181b6e66f3e519d79b540a3bf73"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "deranged"
version = "0.5.8"
@@ -582,19 +585,6 @@ dependencies = [
"serde_core",
]
-[[package]]
-name = "derive_more"
-version = "0.99.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
-dependencies = [
- "convert_case",
- "proc-macro2",
- "quote",
- "rustc_version",
- "syn 2.0.117",
-]
-
[[package]]
name = "derive_more"
version = "2.1.1"
@@ -653,7 +643,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"block2",
"libc",
"objc2",
@@ -661,9 +651,9 @@ dependencies = [
[[package]]
name = "displaydoc"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
@@ -718,12 +708,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89"
dependencies = [
"bit-set",
- "cssparser 0.36.0",
+ "cssparser",
"foldhash 0.2.0",
- "html5ever 0.38.0",
+ "html5ever",
"precomputed-hash",
- "selectors 0.36.1",
- "tendril 0.5.0",
+ "selectors",
+ "tendril",
]
[[package]]
@@ -750,6 +740,21 @@ dependencies = [
"dtoa",
]
+[[package]]
+name = "dtor"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4"
+dependencies = [
+ "dtor-proc-macro",
+]
+
+[[package]]
+name = "dtor-proc-macro"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
+
[[package]]
name = "dunce"
version = "1.0.5"
@@ -764,14 +769,14 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "embed-resource"
-version = "3.0.8"
+version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45"
+checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb"
dependencies = [
"cc",
"memchr",
"rustc_version",
- "toml 0.9.12+spec-1.1.0",
+ "toml 1.1.2+spec-1.1.0",
"vswhom",
"winreg",
]
@@ -913,16 +918,6 @@ dependencies = [
"percent-encoding",
]
-[[package]]
-name = "futf"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
-dependencies = [
- "mac",
- "new_debug_unreachable",
-]
-
[[package]]
name = "futures"
version = "0.3.32"
@@ -1011,15 +1006,6 @@ dependencies = [
"slab",
]
-[[package]]
-name = "fxhash"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-dependencies = [
- "byteorder",
-]
-
[[package]]
name = "gdk"
version = "0.18.2"
@@ -1129,17 +1115,6 @@ dependencies = [
"version_check",
]
-[[package]]
-name = "getrandom"
-version = "0.1.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.9.0+wasi-snapshot-preview1",
-]
-
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -1149,7 +1124,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"wasm-bindgen",
]
@@ -1218,7 +1193,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"futures-channel",
"futures-core",
"futures-executor",
@@ -1330,9 +1305,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
+checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
dependencies = [
"atomic-waker",
"bytes",
@@ -1370,9 +1345,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.17.0"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
@@ -1392,18 +1367,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-[[package]]
-name = "html5ever"
-version = "0.29.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
-dependencies = [
- "log",
- "mac",
- "markup5ever 0.14.1",
- "match_token",
-]
-
[[package]]
name = "html5ever"
version = "0.38.0"
@@ -1411,14 +1374,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
dependencies = [
"log",
- "markup5ever 0.38.0",
+ "markup5ever",
]
[[package]]
name = "http"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
dependencies = [
"bytes",
"itoa",
@@ -1455,9 +1418,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "hyper"
-version = "1.9.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
"atomic-waker",
"bytes",
@@ -1546,7 +1509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
dependencies = [
"byteorder",
- "png",
+ "png 0.17.16",
]
[[package]]
@@ -1656,9 +1619,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -1682,7 +1645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
- "hashbrown 0.17.0",
+ "hashbrown 0.17.1",
"serde",
"serde_core",
]
@@ -1702,16 +1665,6 @@ version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
-[[package]]
-name = "iri-string"
-version = "0.7.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
-dependencies = [
- "memchr",
- "serde",
-]
-
[[package]]
name = "is-docker"
version = "0.2.0"
@@ -1806,9 +1759,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.95"
+version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
+checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
dependencies = [
"cfg-if",
"futures-util",
@@ -1844,23 +1797,11 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"serde",
"unicode-segmentation",
]
-[[package]]
-name = "kuchikiki"
-version = "0.8.8-speedreader"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2"
-dependencies = [
- "cssparser 0.29.6",
- "html5ever 0.29.1",
- "indexmap 2.14.0",
- "selectors 0.24.0",
-]
-
[[package]]
name = "leb128fmt"
version = "0.1.0"
@@ -1897,6 +1838,15 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+[[package]]
+name = "libdbus-sys"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
+dependencies = [
+ "pkg-config",
+]
+
[[package]]
name = "libloading"
version = "0.7.4"
@@ -1909,9 +1859,9 @@ dependencies = [
[[package]]
name = "libredox"
-version = "0.1.16"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
+checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
dependencies = [
"libc",
]
@@ -1939,9 +1889,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
[[package]]
name = "lru-slab"
@@ -1949,26 +1899,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
-[[package]]
-name = "mac"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
-
-[[package]]
-name = "markup5ever"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18"
-dependencies = [
- "log",
- "phf 0.11.3",
- "phf_codegen 0.11.3",
- "string_cache 0.8.9",
- "string_cache_codegen 0.5.4",
- "tendril 0.4.3",
-]
-
[[package]]
name = "markup5ever"
version = "0.38.0"
@@ -1976,32 +1906,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862"
dependencies = [
"log",
- "tendril 0.5.0",
+ "tendril",
"web_atoms",
]
-[[package]]
-name = "match_token"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "matches"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
-
[[package]]
name = "memchr"
-version = "2.8.0"
+version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "memoffset"
@@ -2030,20 +1943,20 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
dependencies = [
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "muda"
-version = "0.17.2"
+version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177"
+checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c"
dependencies = [
"crossbeam-channel",
"dpi",
@@ -2054,10 +1967,10 @@ dependencies = [
"objc2-core-foundation",
"objc2-foundation",
"once_cell",
- "png",
+ "png 0.18.1",
"serde",
"thiserror 2.0.18",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2066,7 +1979,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"jni-sys 0.3.1",
"log",
"ndk-sys",
@@ -2075,12 +1988,6 @@ dependencies = [
"thiserror 1.0.69",
]
-[[package]]
-name = "ndk-context"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
-
[[package]]
name = "ndk-sys"
version = "0.6.0+11769913"
@@ -2096,17 +2003,11 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
-[[package]]
-name = "nodrop"
-version = "0.1.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
-
[[package]]
name = "num-conv"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-traits"
@@ -2155,20 +2056,41 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"block2",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
+dependencies = [
+ "bitflags 2.12.1",
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"dispatch2",
"objc2",
]
@@ -2179,13 +2101,45 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
+[[package]]
+name = "objc2-core-image"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-text"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
+dependencies = [
+ "bitflags 2.12.1",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+]
+
[[package]]
name = "objc2-encode"
version = "4.1.0"
@@ -2207,7 +2161,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"block2",
"libc",
"objc2",
@@ -2220,7 +2174,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"objc2",
"objc2-core-foundation",
]
@@ -2231,7 +2185,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
@@ -2243,9 +2197,28 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
+ "block2",
"objc2",
+ "objc2-cloud-kit",
+ "objc2-core-data",
"objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-core-image",
+ "objc2-core-location",
+ "objc2-core-text",
+ "objc2-foundation",
+ "objc2-quartz-core",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
+dependencies = [
+ "objc2",
"objc2-foundation",
]
@@ -2255,7 +2228,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"block2",
"objc2",
"objc2-app-kit",
@@ -2271,9 +2244,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "open"
-version = "5.3.4"
+version = "5.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd"
+checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c"
dependencies = [
"dunce",
"is-wsl",
@@ -2283,7 +2256,7 @@ dependencies = [
[[package]]
name = "openconcho"
-version = "0.13.1"
+version = "0.15.0"
dependencies = [
"futures",
"serde",
@@ -2382,105 +2355,25 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-[[package]]
-name = "phf"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
-dependencies = [
- "phf_shared 0.8.0",
-]
-
-[[package]]
-name = "phf"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
-dependencies = [
- "phf_macros 0.10.0",
- "phf_shared 0.10.0",
- "proc-macro-hack",
-]
-
-[[package]]
-name = "phf"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
-dependencies = [
- "phf_macros 0.11.3",
- "phf_shared 0.11.3",
-]
-
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
- "phf_macros 0.13.1",
- "phf_shared 0.13.1",
+ "phf_macros",
+ "phf_shared",
"serde",
]
-[[package]]
-name = "phf_codegen"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
-dependencies = [
- "phf_generator 0.8.0",
- "phf_shared 0.8.0",
-]
-
-[[package]]
-name = "phf_codegen"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
-dependencies = [
- "phf_generator 0.11.3",
- "phf_shared 0.11.3",
-]
-
[[package]]
name = "phf_codegen"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
dependencies = [
- "phf_generator 0.13.1",
- "phf_shared 0.13.1",
-]
-
-[[package]]
-name = "phf_generator"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
-dependencies = [
- "phf_shared 0.8.0",
- "rand 0.7.3",
-]
-
-[[package]]
-name = "phf_generator"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
-dependencies = [
- "phf_shared 0.10.0",
- "rand 0.8.6",
-]
-
-[[package]]
-name = "phf_generator"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
-dependencies = [
- "phf_shared 0.11.3",
- "rand 0.8.6",
+ "phf_generator",
+ "phf_shared",
]
[[package]]
@@ -2490,34 +2383,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
dependencies = [
"fastrand",
- "phf_shared 0.13.1",
-]
-
-[[package]]
-name = "phf_macros"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
-dependencies = [
- "phf_generator 0.10.0",
- "phf_shared 0.10.0",
- "proc-macro-hack",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "phf_macros"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
-dependencies = [
- "phf_generator 0.11.3",
- "phf_shared 0.11.3",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "phf_shared",
]
[[package]]
@@ -2526,47 +2392,20 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
dependencies = [
- "phf_generator 0.13.1",
- "phf_shared 0.13.1",
+ "phf_generator",
+ "phf_shared",
"proc-macro2",
"quote",
"syn 2.0.117",
]
-[[package]]
-name = "phf_shared"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
-dependencies = [
- "siphasher 0.3.11",
-]
-
-[[package]]
-name = "phf_shared"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
-dependencies = [
- "siphasher 0.3.11",
-]
-
-[[package]]
-name = "phf_shared"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
-dependencies = [
- "siphasher 1.0.2",
-]
-
[[package]]
name = "phf_shared"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
- "siphasher 1.0.2",
+ "siphasher",
]
[[package]]
@@ -2583,9 +2422,9 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "plist"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
+checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"
dependencies = [
"base64 0.22.1",
"indexmap 2.14.0",
@@ -2607,6 +2446,19 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "png"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
+dependencies = [
+ "bitflags 2.12.1",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
[[package]]
name = "potential_utf"
version = "0.1.5"
@@ -2673,7 +2525,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
- "toml_edit 0.25.11+spec-1.1.0",
+ "toml_edit 0.25.12+spec-1.1.0",
]
[[package]]
@@ -2700,12 +2552,6 @@ dependencies = [
"version_check",
]
-[[package]]
-name = "proc-macro-hack"
-version = "0.5.20+deprecated"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
-
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -2733,9 +2579,9 @@ dependencies = [
[[package]]
name = "quick-xml"
-version = "0.38.4"
+version = "0.39.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
dependencies = [
"memchr",
]
@@ -2769,7 +2615,7 @@ dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
- "rand 0.9.4",
+ "rand",
"ring",
"rustc-hash",
"rustls",
@@ -2816,59 +2662,14 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
-[[package]]
-name = "rand"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
-dependencies = [
- "getrandom 0.1.16",
- "libc",
- "rand_chacha 0.2.2",
- "rand_core 0.5.1",
- "rand_hc",
- "rand_pcg",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
-dependencies = [
- "libc",
- "rand_chacha 0.3.1",
- "rand_core 0.6.4",
-]
-
[[package]]
name = "rand"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [
- "rand_chacha 0.9.0",
- "rand_core 0.9.5",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.5.1",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.6.4",
+ "rand_chacha",
+ "rand_core",
]
[[package]]
@@ -2878,25 +2679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
- "rand_core 0.9.5",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-dependencies = [
- "getrandom 0.1.16",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom 0.2.17",
+ "rand_core",
]
[[package]]
@@ -2908,24 +2691,6 @@ dependencies = [
"getrandom 0.3.4",
]
-[[package]]
-name = "rand_hc"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
-dependencies = [
- "rand_core 0.5.1",
-]
-
-[[package]]
-name = "rand_pcg"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
-dependencies = [
- "rand_core 0.5.1",
-]
-
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@@ -2938,7 +2703,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
]
[[package]]
@@ -3046,9 +2811,9 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.13.2"
+version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
+checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -3119,9 +2884,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.39"
+version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e"
+checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
"once_cell",
"ring",
@@ -3230,40 +2995,22 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "selectors"
-version = "0.24.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416"
-dependencies = [
- "bitflags 1.3.2",
- "cssparser 0.29.6",
- "derive_more 0.99.20",
- "fxhash",
- "log",
- "phf 0.8.0",
- "phf_codegen 0.8.0",
- "precomputed-hash",
- "servo_arc 0.2.0",
- "smallvec",
-]
-
[[package]]
name = "selectors"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
dependencies = [
- "bitflags 2.11.1",
- "cssparser 0.36.0",
- "derive_more 2.1.1",
+ "bitflags 2.12.1",
+ "cssparser",
+ "derive_more",
"log",
"new_debug_unreachable",
- "phf 0.13.1",
- "phf_codegen 0.13.1",
+ "phf",
+ "phf_codegen",
"precomputed-hash",
"rustc-hash",
- "servo_arc 0.4.3",
+ "servo_arc",
"smallvec",
]
@@ -3332,9 +3079,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -3386,11 +3133,12 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.18.0"
+version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
+checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2"
dependencies = [
"base64 0.22.1",
+ "bs58",
"chrono",
"hex",
"indexmap 1.9.3",
@@ -3405,9 +3153,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.18.0"
+version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65"
+checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac"
dependencies = [
"darling",
"proc-macro2",
@@ -3437,16 +3185,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "servo_arc"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741"
-dependencies = [
- "nodrop",
- "stable_deref_trait",
-]
-
[[package]]
name = "servo_arc"
version = "0.4.3"
@@ -3480,9 +3218,9 @@ dependencies = [
[[package]]
name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "sigchld"
@@ -3523,15 +3261,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "siphasher"
-version = "0.3.11"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
-
-[[package]]
-name = "siphasher"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
@@ -3547,9 +3279,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
-version = "0.6.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
"windows-sys 0.61.2",
@@ -3609,19 +3341,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-[[package]]
-name = "string_cache"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
-dependencies = [
- "new_debug_unreachable",
- "parking_lot",
- "phf_shared 0.11.3",
- "precomputed-hash",
- "serde",
-]
-
[[package]]
name = "string_cache"
version = "0.9.0"
@@ -3630,30 +3349,18 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
dependencies = [
"new_debug_unreachable",
"parking_lot",
- "phf_shared 0.13.1",
+ "phf_shared",
"precomputed-hash",
]
-[[package]]
-name = "string_cache_codegen"
-version = "0.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
-dependencies = [
- "phf_generator 0.11.3",
- "phf_shared 0.11.3",
- "proc-macro2",
- "quote",
-]
-
[[package]]
name = "string_cache_codegen"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
dependencies = [
- "phf_generator 0.13.1",
- "phf_shared 0.13.1",
+ "phf_generator",
+ "phf_shared",
"proc-macro2",
"quote",
]
@@ -3688,7 +3395,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
- "quote",
"unicode-ident",
]
@@ -3729,7 +3435,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"core-foundation 0.9.4",
"system-configuration-sys",
]
@@ -3759,15 +3465,16 @@ dependencies = [
[[package]]
name = "tao"
-version = "0.34.8"
+version = "0.35.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20"
+checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"block2",
"core-foundation 0.10.1",
"core-graphics",
"crossbeam-channel",
+ "dbus",
"dispatch2",
"dlopen2",
"dpi",
@@ -3778,13 +3485,14 @@ dependencies = [
"libc",
"log",
"ndk",
- "ndk-context",
"ndk-sys",
"objc2",
"objc2-app-kit",
"objc2-foundation",
+ "objc2-ui-kit",
"once_cell",
"parking_lot",
+ "percent-encoding",
"raw-window-handle",
"tao-macros",
"unicode-segmentation",
@@ -3814,9 +3522,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
-version = "2.10.3"
+version = "2.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d"
+checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28"
dependencies = [
"anyhow",
"bytes",
@@ -3842,7 +3550,7 @@ dependencies = [
"percent-encoding",
"plist",
"raw-window-handle",
- "reqwest 0.13.2",
+ "reqwest 0.13.4",
"serde",
"serde_json",
"serde_repr",
@@ -3865,9 +3573,9 @@ dependencies = [
[[package]]
name = "tauri-build"
-version = "2.5.6"
+version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d"
+checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7"
dependencies = [
"anyhow",
"cargo_toml",
@@ -3881,22 +3589,21 @@ dependencies = [
"serde_json",
"tauri-utils",
"tauri-winres",
- "toml 0.9.12+spec-1.1.0",
"walkdir",
]
[[package]]
name = "tauri-codegen"
-version = "2.5.5"
+version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29"
+checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9"
dependencies = [
"base64 0.22.1",
"brotli",
"ico",
"json-patch",
"plist",
- "png",
+ "png 0.17.16",
"proc-macro2",
"quote",
"semver",
@@ -3914,9 +3621,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
-version = "2.5.5"
+version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7"
+checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -3928,9 +3635,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
-version = "2.5.4"
+version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa"
+checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e"
dependencies = [
"anyhow",
"glob",
@@ -3939,7 +3646,6 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
- "toml 0.9.12+spec-1.1.0",
"walkdir",
]
@@ -3966,9 +3672,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
-version = "2.5.0"
+version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36e1ec28b79f3d0683f4507e1615c36292c0ea6716668770d4396b9b39871ed8"
+checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371"
dependencies = [
"anyhow",
"dunce",
@@ -3984,15 +3690,15 @@ dependencies = [
"tauri-plugin",
"tauri-utils",
"thiserror 2.0.18",
- "toml 0.9.12+spec-1.1.0",
+ "toml 1.1.2+spec-1.1.0",
"url",
]
[[package]]
name = "tauri-plugin-http"
-version = "2.5.8"
+version = "2.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfba7d4ec72763f9d1fdf73c217747f01e2c84b08b87a8cacd2f94f35853f84d"
+checksum = "b5bd512048e1985b7ec78f96d99083e2ddaf7e0d906b2b63c44ce5bb8b894067"
dependencies = [
"bytes",
"cookie_store",
@@ -4035,9 +3741,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
-version = "2.10.1"
+version = "2.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2"
+checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef"
dependencies = [
"cookie",
"dpi",
@@ -4060,9 +3766,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
-version = "2.10.1"
+version = "2.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e"
+checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
dependencies = [
"gtk",
"http",
@@ -4086,24 +3792,24 @@ dependencies = [
[[package]]
name = "tauri-utils"
-version = "2.8.3"
+version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d"
+checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95"
dependencies = [
"anyhow",
"brotli",
"cargo_metadata",
"ctor",
+ "dom_query",
"dunce",
"glob",
- "html5ever 0.29.1",
"http",
"infer",
"json-patch",
- "kuchikiki",
"log",
"memchr",
- "phf 0.11.3",
+ "phf",
+ "plist",
"proc-macro2",
"quote",
"regex",
@@ -4115,7 +3821,7 @@ dependencies = [
"serde_with",
"swift-rs",
"thiserror 2.0.18",
- "toml 0.9.12+spec-1.1.0",
+ "toml 1.1.2+spec-1.1.0",
"url",
"urlpattern",
"uuid",
@@ -4124,24 +3830,13 @@ dependencies = [
[[package]]
name = "tauri-winres"
-version = "0.3.5"
+version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0"
+checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6"
dependencies = [
"dunce",
"embed-resource",
- "toml 0.9.12+spec-1.1.0",
-]
-
-[[package]]
-name = "tendril"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
-dependencies = [
- "futf",
- "mac",
- "utf-8",
+ "toml 1.1.2+spec-1.1.0",
]
[[package]]
@@ -4261,9 +3956,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.52.1"
+version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",
@@ -4335,6 +4030,21 @@ dependencies = [
"winnow 0.7.15",
]
+[[package]]
+name = "toml"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
+dependencies = [
+ "indexmap 2.14.0",
+ "serde_core",
+ "serde_spanned 1.1.1",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 1.0.3",
+]
+
[[package]]
name = "toml_datetime"
version = "0.6.3"
@@ -4388,14 +4098,14 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.25.11+spec-1.1.0"
+version = "0.25.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
+checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
dependencies = [
"indexmap 2.14.0",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
- "winnow 1.0.2",
+ "winnow 1.0.3",
]
[[package]]
@@ -4404,7 +4114,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
- "winnow 1.0.2",
+ "winnow 1.0.3",
]
[[package]]
@@ -4430,20 +4140,20 @@ dependencies = [
[[package]]
name = "tower-http"
-version = "0.6.8"
+version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"bytes",
"futures-util",
"http",
"http-body",
- "iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
+ "url",
]
[[package]]
@@ -4491,9 +4201,9 @@ dependencies = [
[[package]]
name = "tray-icon"
-version = "0.21.3"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c"
+checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773"
dependencies = [
"crossbeam-channel",
"dirs",
@@ -4505,10 +4215,10 @@ dependencies = [
"objc2-core-graphics",
"objc2-foundation",
"once_cell",
- "png",
+ "png 0.18.1",
"serde",
"thiserror 2.0.18",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4525,9 +4235,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
-version = "1.20.0"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unic-char-property"
@@ -4578,9 +4288,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-segmentation"
-version = "1.13.2"
+version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unicode-xid"
@@ -4633,9 +4343,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
-version = "1.23.1"
+version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
+checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
dependencies = [
"getrandom 0.4.2",
"js-sys",
@@ -4694,12 +4404,6 @@ dependencies = [
"try-lock",
]
-[[package]]
-name = "wasi"
-version = "0.9.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
-
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -4726,9 +4430,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
+checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
dependencies = [
"cfg-if",
"once_cell",
@@ -4739,9 +4443,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.68"
+version = "0.4.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
+checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4749,9 +4453,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
+checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4759,9 +4463,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
+checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -4772,9 +4476,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.118"
+version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
+checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
dependencies = [
"unicode-ident",
]
@@ -4820,7 +4524,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"hashbrown 0.15.5",
"indexmap 2.14.0",
"semver",
@@ -4828,9 +4532,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.95"
+version = "0.3.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
+checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4852,10 +4556,10 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538"
dependencies = [
- "phf 0.13.1",
- "phf_codegen 0.13.1",
- "string_cache 0.9.0",
- "string_cache_codegen 0.6.1",
+ "phf",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
]
[[package]]
@@ -5420,9 +5124,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
[[package]]
name = "winnow"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
dependencies = [
"memchr",
]
@@ -5501,7 +5205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
- "bitflags 2.11.1",
+ "bitflags 2.12.1",
"indexmap 2.14.0",
"log",
"serde",
@@ -5539,9 +5243,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "wry"
-version = "0.54.4"
+version = "0.55.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc"
+checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
dependencies = [
"base64 0.22.1",
"block2",
@@ -5627,18 +5331,18 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.8.48"
+version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
+checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.48"
+version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
+checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
dependencies = [
"proc-macro2",
"quote",
@@ -5647,9 +5351,9 @@ dependencies = [
[[package]]
name = "zerofrom"
-version = "0.1.7"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
diff --git a/packages/desktop/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml
index 9bc728b..8d0a923 100644
--- a/packages/desktop/src-tauri/Cargo.toml
+++ b/packages/desktop/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "openconcho"
-version = "0.13.1"
+version = "0.15.0"
edition = "2021"
[lib]
diff --git a/packages/web/src/api/client.ts b/packages/web/src/api/client.ts
index 7652996..514b426 100644
--- a/packages/web/src/api/client.ts
+++ b/packages/web/src/api/client.ts
@@ -1,21 +1,12 @@
import createClient from "openapi-fetch";
import { loadConfig } from "@/lib/config";
-import { httpFetch } from "@/lib/http";
+import { dispatchFor } from "@/lib/dispatch";
import type { paths } from "./schema.d.ts";
export function createHonchoClient() {
- const config = loadConfig();
- const baseUrl = config?.baseUrl ?? "http://localhost:8000";
- const token = config?.token ?? "";
-
- const headers: Record = {
- "Content-Type": "application/json",
- };
- if (token) {
- headers.Authorization = `Bearer ${token}`;
- }
-
- return createClient({ baseUrl, headers, fetch: httpFetch });
+ const config = loadConfig() ?? { baseUrl: "http://localhost:8000", token: "" };
+ const { baseUrl, headers, fetch } = dispatchFor(config);
+ return createClient({ baseUrl, headers, fetch });
}
export const client = {
diff --git a/packages/web/src/api/scopedClient.ts b/packages/web/src/api/scopedClient.ts
index c37a9af..44c8764 100644
--- a/packages/web/src/api/scopedClient.ts
+++ b/packages/web/src/api/scopedClient.ts
@@ -1,18 +1,16 @@
import createClient from "openapi-fetch";
import type { Instance } from "@/lib/config";
-import { httpFetch } from "@/lib/http";
+import { dispatchFor } from "@/lib/dispatch";
import type { paths } from "./schema.d.ts";
export type ScopedClient = ReturnType>;
/**
- * Create an openapi-fetch client bound to a specific instance. Use for views
- * that need to query non-active instances (e.g. side-by-side comparison).
- * For single-instance access, prefer `client.current` which tracks the active
- * instance via localStorage.
+ * Create an openapi-fetch client bound to a specific instance. Use for views that
+ * query non-active instances (e.g. the Fleet side-by-side comparison). Each scoped
+ * client self-routes via its own X-Honcho-Upstream header in web mode.
*/
export function createScopedClient(instance: Instance): ScopedClient {
- const headers: Record = { "Content-Type": "application/json" };
- if (instance.token) headers.Authorization = `Bearer ${instance.token}`;
- return createClient({ baseUrl: instance.baseUrl, headers, fetch: httpFetch });
+ const { baseUrl, headers, fetch } = dispatchFor(instance);
+ return createClient({ baseUrl, headers, fetch });
}
diff --git a/packages/web/src/lib/config.ts b/packages/web/src/lib/config.ts
index 6dda1dd..faa9b10 100644
--- a/packages/web/src/lib/config.ts
+++ b/packages/web/src/lib/config.ts
@@ -1,5 +1,5 @@
import { z } from "zod";
-import { httpFetch } from "@/lib/http";
+import { dispatchFor, PROXY_REJECT_HEADER } from "@/lib/dispatch";
import { runtimeDefaultBaseUrl } from "@/lib/runtimeConfig";
const LEGACY_KEY = "openconcho:config";
@@ -7,6 +7,13 @@ const STORE_KEY = "openconcho:instances";
export const HONCHO_CLOUD_URL = "https://api.honcho.dev";
+/**
+ * Connection-test timeout. Generous because a cold/idle self-hosted Honcho (DB
+ * pool spin-up, tunnel wake) can take several seconds on its first request — a
+ * tight 5s budget reported live-and-reachable instances as "Connection timed out".
+ */
+export const CONNECTION_TIMEOUT_MS = 15_000;
+
function normalizeBaseUrl(url: string): string {
return url.trim().replace(/\/+$/, "").toLowerCase();
}
@@ -16,7 +23,7 @@ export function isCloudInstance(instance: Pick): boolean {
}
export const configSchema = z.object({
- baseUrl: z.string().url({ message: "Must be a valid URL" }),
+ baseUrl: z.url({ message: "Must be a valid URL" }),
token: z.string().optional().default(""),
});
@@ -25,7 +32,7 @@ export type Config = z.infer;
export const instanceSchema = z.object({
id: z.string().min(1),
name: z.string().min(1, { message: "Name is required" }),
- baseUrl: z.string().url({ message: "Must be a valid URL" }),
+ baseUrl: z.url({ message: "Must be a valid URL" }),
token: z.string().optional().default(""),
});
@@ -162,21 +169,21 @@ export type HealthStatus = "ok" | "auth-required" | "unreachable" | "checking";
export async function checkConnection(
baseUrl: string,
token?: string,
-): Promise<{
- status: HealthStatus;
- message: string;
-}> {
+ timeoutMs: number = CONNECTION_TIMEOUT_MS,
+): Promise<{ status: HealthStatus; message: string }> {
try {
- const headers: Record = { "Content-Type": "application/json" };
- if (token) headers.Authorization = `Bearer ${token}`;
-
- const res = await httpFetch(`${baseUrl}/v3/workspaces/list`, {
+ const { baseUrl: base, headers, fetch } = dispatchFor({ baseUrl, token });
+ const res = await fetch(`${base}/v3/workspaces/list`, {
method: "POST",
headers,
body: JSON.stringify({}),
- signal: AbortSignal.timeout(5000),
+ signal: AbortSignal.timeout(timeoutMs),
});
+ const reject = res.headers.get(PROXY_REJECT_HEADER);
+ if (reject) {
+ return { status: "unreachable", message: `Proxy refused upstream (${reject})` };
+ }
if (res.ok) return { status: "ok", message: "Connected successfully" };
if (res.status === 401 || res.status === 403) {
return { status: "auth-required", message: "Authentication required — provide an API token" };
diff --git a/packages/web/src/lib/discovery.ts b/packages/web/src/lib/discovery.ts
index 83bc365..6784f80 100644
--- a/packages/web/src/lib/discovery.ts
+++ b/packages/web/src/lib/discovery.ts
@@ -1,14 +1,13 @@
-import { httpFetch } from "@/lib/http";
+import { dispatchFor } from "@/lib/dispatch";
+import { isTauri } from "@/lib/platform";
+
+export { isTauri } from "@/lib/platform";
export interface DiscoveredInstance {
port: number;
base_url: string;
}
-export function isTauri(): boolean {
- return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
-}
-
/**
* Probe localhost ports for running Honcho instances. Desktop-only — the web
* build can't port-scan due to CORS, so this returns an empty list when not
@@ -38,9 +37,10 @@ export function deriveNameFromWorkspaceId(workspaceId: string): string {
*/
export async function suggestNameForInstance(baseUrl: string): Promise {
try {
- const res = await httpFetch(`${baseUrl}/v3/workspaces/list?page=1&page_size=1`, {
+ const { baseUrl: base, headers, fetch } = dispatchFor({ baseUrl });
+ const res = await fetch(`${base}/v3/workspaces/list?page=1&page_size=1`, {
method: "POST",
- headers: { "Content-Type": "application/json" },
+ headers,
body: JSON.stringify({}),
signal: AbortSignal.timeout(2000),
});
diff --git a/packages/web/src/lib/dispatch.ts b/packages/web/src/lib/dispatch.ts
new file mode 100644
index 0000000..3701e18
--- /dev/null
+++ b/packages/web/src/lib/dispatch.ts
@@ -0,0 +1,47 @@
+import { httpFetch } from "@/lib/http";
+import { isTauri } from "@/lib/platform";
+
+/** Same-origin path prefix the web build issues all Honcho calls through. */
+export const API_PREFIX = "/api";
+/** Request header naming the real Honcho upstream for the proxy to forward to. */
+export const UPSTREAM_HEADER = "X-Honcho-Upstream";
+/** Response header the proxy sets on its OWN refusals (so they aren't read as upstream auth). */
+export const PROXY_REJECT_HEADER = "X-Honcho-Proxy-Reject";
+
+export interface Dispatch {
+ baseUrl: string;
+ headers: Record;
+ fetch: typeof globalThis.fetch;
+}
+
+function normalizeUpstream(url: string): string {
+ return url.trim().replace(/\/+$/, "");
+}
+
+/**
+ * Absolute same-origin base for the web proxy. Absolute (origin + `/api`), not the
+ * bare relative `/api`, so openapi-fetch and node/undici can construct a Request
+ * without an ambient document base — and so it resolves identically in the browser,
+ * behind a tunnel, and under jsdom.
+ */
+function webApiBase(): string {
+ const origin = typeof location !== "undefined" ? location.origin : "";
+ return `${origin}${API_PREFIX}`;
+}
+
+/**
+ * Resolve how to issue a request for an instance.
+ * - Web: same-origin `/api` + `X-Honcho-Upstream` header (proxy forwards server-side, no CORS).
+ * - Tauri: the absolute instance URL via reqwest (no browser same-origin policy).
+ */
+export function dispatchFor(instance: { baseUrl: string; token?: string }): Dispatch {
+ const headers: Record = { "Content-Type": "application/json" };
+ if (instance.token) headers.Authorization = `Bearer ${instance.token}`;
+
+ if (isTauri()) {
+ return { baseUrl: instance.baseUrl, headers, fetch: httpFetch };
+ }
+
+ headers[UPSTREAM_HEADER] = normalizeUpstream(instance.baseUrl);
+ return { baseUrl: webApiBase(), headers, fetch: httpFetch };
+}
diff --git a/packages/web/src/lib/http.ts b/packages/web/src/lib/http.ts
index 96c325a..41ba6b8 100644
--- a/packages/web/src/lib/http.ts
+++ b/packages/web/src/lib/http.ts
@@ -1,12 +1,8 @@
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
+import { isTauri } from "@/lib/platform";
// Route fetch through Rust (reqwest) when running in Tauri — bypasses WebView CORS enforcement.
-// Falls back to native browser fetch during plain web dev (`pnpm dev:web`).
-const isTauri = Boolean(
- typeof window !== "undefined" &&
- (window as unknown as Record).__TAURI_INTERNALS__,
-);
-
-export const httpFetch: typeof globalThis.fetch = isTauri
+// Falls back to native browser fetch during plain web dev.
+export const httpFetch: typeof globalThis.fetch = isTauri()
? (tauriFetch as typeof globalThis.fetch)
: globalThis.fetch;
diff --git a/packages/web/src/lib/platform.ts b/packages/web/src/lib/platform.ts
new file mode 100644
index 0000000..6755142
--- /dev/null
+++ b/packages/web/src/lib/platform.ts
@@ -0,0 +1,4 @@
+/** True when running inside the Tauri desktop shell (WebView with injected internals). */
+export function isTauri(): boolean {
+ return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
+}
diff --git a/packages/web/src/lib/runtimeConfig.ts b/packages/web/src/lib/runtimeConfig.ts
index 235822b..c2990dd 100644
--- a/packages/web/src/lib/runtimeConfig.ts
+++ b/packages/web/src/lib/runtimeConfig.ts
@@ -1,24 +1,17 @@
const GLOBAL_KEY = "__OPENCONCHO_DEFAULT_HONCHO_URL__";
-const SAME_ORIGIN = "same-origin";
/**
* Runtime-injected default Honcho base URL for container deployments.
*
- * The Docker image writes `/config.js` from the `OPENCONCHO_DEFAULT_HONCHO_URL`
- * env at container start, so one prebuilt image can target any backend without
- * a rebuild (Vite envs are baked at build time and can't do this).
+ * The Docker image writes `/config.js` from `OPENCONCHO_DEFAULT_HONCHO_URL` at
+ * container start, so one prebuilt image can target any backend without a rebuild.
+ * The web build proxies this URL via the same-origin `/api` reverse proxy (no CORS).
*
- * - `"same-origin"` → the page's own origin (pairs with the nginx `/v3` reverse
- * proxy, so the browser makes same-origin requests and CORS never applies)
- * - an absolute URL → that URL
- * - empty / unset → `null` (no default; the user configures in Settings)
+ * - an absolute URL → that URL (seeds the first instance)
+ * - empty / unset → null (no default; the user configures in Settings)
*/
export function runtimeDefaultBaseUrl(): string | null {
const raw = (globalThis as Record)[GLOBAL_KEY];
if (typeof raw !== "string" || raw.trim() === "") return null;
- const value = raw.trim();
- if (value === SAME_ORIGIN) {
- return typeof location !== "undefined" ? location.origin : null;
- }
- return value;
+ return raw.trim();
}
diff --git a/packages/web/src/test/check-connection.test.ts b/packages/web/src/test/check-connection.test.ts
new file mode 100644
index 0000000..f12f688
--- /dev/null
+++ b/packages/web/src/test/check-connection.test.ts
@@ -0,0 +1,74 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+// Mock the platform predicate (web mode) and the fetch boundary. We mock
+// @/lib/http — NOT globalThis.fetch — because httpFetch captures the fetch
+// reference at module load, so vi.stubGlobal would not be observed by dispatchFor.
+const { mockIsTauri, httpFetchMock } = vi.hoisted(() => ({
+ mockIsTauri: vi.fn(() => false),
+ httpFetchMock: vi.fn(),
+}));
+vi.mock("@/lib/platform", () => ({ isTauri: () => mockIsTauri() }));
+vi.mock("@/lib/http", () => ({ httpFetch: httpFetchMock }));
+
+import { checkConnection } from "@/lib/config";
+
+afterEach(() => {
+ httpFetchMock.mockReset();
+ mockIsTauri.mockReturnValue(false);
+});
+
+describe("checkConnection — web proxy mode", () => {
+ it("calls the absolute same-origin /api path with the upstream header", async () => {
+ httpFetchMock.mockResolvedValue(new Response("{}", { status: 200 }));
+ const res = await checkConnection("https://honcho.example.net", "sk-1");
+ expect(res.status).toBe("ok");
+ const [url, init] = httpFetchMock.mock.calls[0];
+ expect(String(url)).toBe(`${location.origin}/api/v3/workspaces/list`);
+ expect((init.headers as Record)["X-Honcho-Upstream"]).toBe(
+ "https://honcho.example.net",
+ );
+ expect((init.headers as Record).Authorization).toBe("Bearer sk-1");
+ });
+
+ it("maps an upstream 401 to auth-required", async () => {
+ httpFetchMock.mockResolvedValue(new Response("{}", { status: 401 }));
+ const res = await checkConnection("https://honcho.example.net");
+ expect(res.status).toBe("auth-required");
+ });
+
+ it("treats a proxy reject as unreachable, not auth-required", async () => {
+ httpFetchMock.mockResolvedValue(
+ new Response("", { status: 403, headers: { "X-Honcho-Proxy-Reject": "allowlist" } }),
+ );
+ const res = await checkConnection("https://blocked.example.net");
+ expect(res.status).toBe("unreachable");
+ expect(res.message).toMatch(/allowlist/i);
+ });
+});
+
+describe("checkConnection — timeout budget", () => {
+ // A fetch that resolves after `ms`, but rejects early if the abort signal fires —
+ // mirrors how a real slow upstream interacts with AbortSignal.timeout.
+ function delayedFetch(ms: number) {
+ return (_url: string, init: { signal?: AbortSignal }) =>
+ new Promise((resolve, reject) => {
+ const timer = setTimeout(() => resolve(new Response("{}", { status: 200 })), ms);
+ init.signal?.addEventListener("abort", () => {
+ clearTimeout(timer);
+ reject(new DOMException("The operation timed out", "TimeoutError"));
+ });
+ });
+ }
+
+ it("reports unreachable when the upstream is slower than the timeout budget", async () => {
+ httpFetchMock.mockImplementation(delayedFetch(80));
+ const res = await checkConnection("https://slow.example.net", undefined, 20);
+ expect(res.status).toBe("unreachable");
+ });
+
+ it("succeeds when a slow upstream responds within the (cold-start) budget", async () => {
+ httpFetchMock.mockImplementation(delayedFetch(20));
+ const res = await checkConnection("https://slow.example.net", undefined, 200);
+ expect(res.status).toBe("ok");
+ });
+});
diff --git a/packages/web/src/test/dispatch.test.ts b/packages/web/src/test/dispatch.test.ts
new file mode 100644
index 0000000..1fbfdc7
--- /dev/null
+++ b/packages/web/src/test/dispatch.test.ts
@@ -0,0 +1,40 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+const { mockIsTauri } = vi.hoisted(() => ({ mockIsTauri: vi.fn() }));
+vi.mock("@/lib/platform", () => ({ isTauri: () => mockIsTauri() }));
+
+import { API_PREFIX, dispatchFor, PROXY_REJECT_HEADER, UPSTREAM_HEADER } from "@/lib/dispatch";
+
+afterEach(() => mockIsTauri.mockReset());
+
+describe("dispatchFor — web mode", () => {
+ it("targets the absolute same-origin /api base and carries the upstream header", () => {
+ mockIsTauri.mockReturnValue(false);
+ const d = dispatchFor({ baseUrl: "https://honcho.example.net/", token: "" });
+ expect(d.baseUrl).toBe(`${location.origin}${API_PREFIX}`);
+ expect(d.headers[UPSTREAM_HEADER]).toBe("https://honcho.example.net");
+ expect(d.headers.Authorization).toBeUndefined();
+ });
+
+ it("adds Authorization only when a token is present", () => {
+ mockIsTauri.mockReturnValue(false);
+ const d = dispatchFor({ baseUrl: "https://honcho.example.net", token: "sk-1" });
+ expect(d.headers.Authorization).toBe("Bearer sk-1");
+ });
+});
+
+describe("dispatchFor — tauri mode", () => {
+ it("targets the absolute URL with no upstream header", () => {
+ mockIsTauri.mockReturnValue(true);
+ const d = dispatchFor({ baseUrl: "https://honcho.example.net", token: "sk-1" });
+ expect(d.baseUrl).toBe("https://honcho.example.net");
+ expect(d.headers[UPSTREAM_HEADER]).toBeUndefined();
+ expect(d.headers.Authorization).toBe("Bearer sk-1");
+ });
+});
+
+describe("proxy reject header constant", () => {
+ it("is the agreed sentinel name", () => {
+ expect(PROXY_REJECT_HEADER).toBe("X-Honcho-Proxy-Reject");
+ });
+});
diff --git a/packages/web/src/test/fleet.test.tsx b/packages/web/src/test/fleet.test.tsx
index 5d39309..d1bf000 100644
--- a/packages/web/src/test/fleet.test.tsx
+++ b/packages/web/src/test/fleet.test.tsx
@@ -103,23 +103,25 @@ describe("scoped option builders", () => {
(httpFetch as ReturnType).mockClear();
});
- it("scopes queue status requests by instance baseUrl and token", async () => {
+ it("scopes queue status requests via the same-origin proxy, upstream header, and token", async () => {
const { httpFetch } = await import("@/lib/http");
const opts = scopedQueueStatusOptions(neo, "ws-1");
await opts.queryFn();
const req = (httpFetch as ReturnType).mock.calls[0][0] as Request;
- expect(req.url).toBe("http://localhost:8001/v3/workspaces/ws-1/queue/status");
+ expect(req.url).toBe(`${location.origin}/api/v3/workspaces/ws-1/queue/status`);
+ expect(req.headers.get("X-Honcho-Upstream")).toBe("http://localhost:8001");
expect(req.headers.get("Authorization")).toBe("Bearer neo-token");
});
- it("scopes conclusions-count requests by instance baseUrl and token", async () => {
+ it("scopes conclusions-count requests via the same-origin proxy, upstream header, and token", async () => {
const { httpFetch } = await import("@/lib/http");
const opts = scopedConclusionsCountOptions(iris, "ws-9");
await opts.queryFn();
const req = (httpFetch as ReturnType).mock.calls[0][0] as Request;
- expect(req.url.startsWith("http://localhost:8002/v3/workspaces/ws-9/conclusions/list")).toBe(
+ expect(req.url.startsWith(`${location.origin}/api/v3/workspaces/ws-9/conclusions/list`)).toBe(
true,
);
+ expect(req.headers.get("X-Honcho-Upstream")).toBe("http://localhost:8002");
expect(req.headers.get("Authorization")).toBe("Bearer iris-token");
});
diff --git a/packages/web/src/test/instances.test.ts b/packages/web/src/test/instances.test.ts
new file mode 100644
index 0000000..9042e45
--- /dev/null
+++ b/packages/web/src/test/instances.test.ts
@@ -0,0 +1,125 @@
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import {
+ addInstance,
+ deleteInstance,
+ getActiveInstance,
+ loadConfig,
+ loadStore,
+ setActiveInstance,
+ updateInstance,
+} from "@/lib/config";
+
+const STORE_KEY = "openconcho:instances";
+const LEGACY_KEY = "openconcho:config";
+
+beforeEach(() => localStorage.clear());
+afterEach(() => localStorage.clear());
+
+describe("instance store — add + active selection", () => {
+ it("makes the first added instance active", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ expect(loadStore().activeId).toBe(a.id);
+ });
+
+ it("does not steal active focus when adding more instances", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
+ expect(loadStore().activeId).toBe(a.id);
+ });
+
+ it("appends instances in insertion order", () => {
+ addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
+ expect(loadStore().instances.map((i) => i.name)).toEqual(["A", "B"]);
+ });
+});
+
+describe("instance store — switching active", () => {
+ it("switches the active instance", () => {
+ addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ const b = addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
+ setActiveInstance(b.id);
+ expect(getActiveInstance()?.id).toBe(b.id);
+ });
+
+ it("ignores an unknown id", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ setActiveInstance("does-not-exist");
+ expect(getActiveInstance()?.id).toBe(a.id);
+ });
+});
+
+describe("instance store — deletion", () => {
+ it("falls back to the first remaining when the active instance is deleted", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ const b = addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
+ setActiveInstance(b.id);
+ deleteInstance(b.id);
+ expect(loadStore().activeId).toBe(a.id);
+ });
+
+ it("leaves the active id unchanged when a non-active instance is deleted", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ const b = addInstance({ name: "B", baseUrl: "https://b.example.net", token: "" });
+ deleteInstance(b.id);
+ expect(getActiveInstance()?.id).toBe(a.id);
+ });
+
+ it("clears the active id when the last instance is removed", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ deleteInstance(a.id);
+ expect(loadStore().activeId).toBeNull();
+ });
+
+ it("returns null config once every instance is gone", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ deleteInstance(a.id);
+ expect(loadConfig()).toBeNull();
+ });
+});
+
+describe("instance store — update", () => {
+ it("patches the named fields", () => {
+ const a = addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ updateInstance(a.id, { name: "Renamed", token: "sk-1" });
+ expect(loadStore().instances[0]).toMatchObject({ name: "Renamed", token: "sk-1" });
+ });
+
+ it("no-ops on an unknown id", () => {
+ addInstance({ name: "A", baseUrl: "https://a.example.net", token: "" });
+ updateInstance("nope", { name: "X" });
+ expect(loadStore().instances[0].name).toBe("A");
+ });
+});
+
+describe("instance store — active config", () => {
+ it("reflects the active instance's url and token", () => {
+ addInstance({ name: "A", baseUrl: "https://a.example.net", token: "sk-a" });
+ expect(loadConfig()).toEqual({ baseUrl: "https://a.example.net", token: "sk-a" });
+ });
+});
+
+describe("instance store — legacy migration", () => {
+ it("migrates the legacy single-config key into the instances store", () => {
+ localStorage.setItem(
+ LEGACY_KEY,
+ JSON.stringify({ baseUrl: "https://legacy.example.net", token: "sk-legacy" }),
+ );
+ const store = loadStore();
+ expect(store.instances[0]).toMatchObject({
+ name: "Default",
+ baseUrl: "https://legacy.example.net",
+ token: "sk-legacy",
+ });
+ });
+
+ it("removes the legacy key after migrating", () => {
+ localStorage.setItem(
+ LEGACY_KEY,
+ JSON.stringify({ baseUrl: "https://legacy.example.net", token: "" }),
+ );
+ loadStore();
+ expect(localStorage.getItem(LEGACY_KEY)).toBeNull();
+ expect(localStorage.getItem(STORE_KEY)).toBeTruthy();
+ });
+});
diff --git a/packages/web/src/test/platform.test.ts b/packages/web/src/test/platform.test.ts
new file mode 100644
index 0000000..5be6805
--- /dev/null
+++ b/packages/web/src/test/platform.test.ts
@@ -0,0 +1,17 @@
+import { afterEach, describe, expect, it } from "vitest";
+import { isTauri } from "@/lib/platform";
+
+describe("isTauri", () => {
+ afterEach(() => {
+ delete (window as unknown as Record).__TAURI_INTERNALS__;
+ });
+
+ it("returns false in a plain browser/jsdom environment", () => {
+ expect(isTauri()).toBe(false);
+ });
+
+ it("returns true when the Tauri internals global is present", () => {
+ (window as unknown as Record).__TAURI_INTERNALS__ = {};
+ expect(isTauri()).toBe(true);
+ });
+});
diff --git a/packages/web/src/test/runtime-config.test.ts b/packages/web/src/test/runtime-config.test.ts
index a13e936..695ac8a 100644
--- a/packages/web/src/test/runtime-config.test.ts
+++ b/packages/web/src/test/runtime-config.test.ts
@@ -8,22 +8,14 @@ afterEach(() => {
});
describe("runtimeDefaultBaseUrl", () => {
- it("returns null when the global is unset", () => {
- expect(runtimeDefaultBaseUrl()).toBeNull();
- });
-
- it("returns null when the global is blank", () => {
- (globalThis as Record)[KEY] = " ";
- expect(runtimeDefaultBaseUrl()).toBeNull();
- });
-
- it("returns an absolute URL verbatim", () => {
+ it("returns an injected absolute URL verbatim", () => {
(globalThis as Record)[KEY] = "https://honcho.example.net";
expect(runtimeDefaultBaseUrl()).toBe("https://honcho.example.net");
});
- it("resolves 'same-origin' to the page origin", () => {
- (globalThis as Record)[KEY] = "same-origin";
- expect(runtimeDefaultBaseUrl()).toBe(location.origin);
+ it("returns null when unset or empty", () => {
+ expect(runtimeDefaultBaseUrl()).toBeNull();
+ (globalThis as Record)[KEY] = " ";
+ expect(runtimeDefaultBaseUrl()).toBeNull();
});
});
diff --git a/packages/web/src/test/security.test.ts b/packages/web/src/test/security.test.ts
index 7fc40c1..dcd551c 100644
--- a/packages/web/src/test/security.test.ts
+++ b/packages/web/src/test/security.test.ts
@@ -27,11 +27,11 @@ describe("security URL helpers", () => {
expect(isSecureTokenTransport("http://localhost:8000")).toBe(true);
expect(isSecureTokenTransport("http://127.0.0.1:8000")).toBe(true);
expect(isSecureTokenTransport("http://192.168.1.50:8000")).toBe(false);
- expect(isSecureTokenTransport("http://100.67.206.76:8000")).toBe(false);
+ expect(isSecureTokenTransport("http://192.0.2.10:8000")).toBe(false);
});
it("returns a user-facing error for insecure token transport", () => {
- expect(tokenTransportError("http://100.67.206.76:8000")).toMatch(/HTTPS/);
+ expect(tokenTransportError("http://192.0.2.10:8000")).toMatch(/HTTPS/);
expect(tokenTransportError("https://honcho.example.com")).toBeNull();
});
});
diff --git a/packages/web/src/test/settings-form.test.tsx b/packages/web/src/test/settings-form.test.tsx
index 3ea55f3..8c33cc3 100644
--- a/packages/web/src/test/settings-form.test.tsx
+++ b/packages/web/src/test/settings-form.test.tsx
@@ -58,7 +58,7 @@ describe("SettingsForm — self-hosted preset", () => {
renderForm();
const baseUrl = screen.getByPlaceholderText("http://localhost:8000");
await user.clear(baseUrl);
- await user.type(baseUrl, "http://100.67.206.76:8000");
+ await user.type(baseUrl, "http://192.0.2.10:8000");
await user.type(
screen.getByPlaceholderText(/required only if your instance has auth enabled/i),
"secret-token",
diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts
index 1267586..4622347 100644
--- a/packages/web/vite.config.ts
+++ b/packages/web/vite.config.ts
@@ -1,10 +1,10 @@
import { readFileSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
import tailwindcss from "@tailwindcss/vite";
import { tanstackRouter } from "@tanstack/router-plugin/vite";
import react from "@vitejs/plugin-react";
-import path from "path";
-import { fileURLToPath } from "url";
-import { defineConfig } from "vite";
+import { defineConfig, type Plugin } from "vite";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const host = process.env.TAURI_DEV_HOST;
@@ -12,9 +12,80 @@ const { version } = JSON.parse(
readFileSync(path.resolve(__dirname, "../../package.json"), "utf-8"),
) as { version: string };
+// Dev-mode mirror of the nginx /api reverse proxy: read X-Honcho-Upstream and
+// forward /api/* there, so `make dev-web` behaves identically to the docker image
+// (same-origin requests, no browser CORS). Connect strips the /api mount prefix,
+// so req.url is already the upstream path (e.g. /v3/workspaces/list).
+function honchoApiProxy(): Plugin {
+ const HEADER = "x-honcho-upstream";
+ // Mirror nginx's allowlist (spec §D): unset/empty => open; otherwise only
+ // matching upstream hosts forward. Glob `*` -> any non-slash run, like nginx.
+ const raw = process.env.OPENCONCHO_UPSTREAM_ALLOWLIST?.trim();
+ const allowlist: RegExp[] | null = raw
+ ? raw
+ .split(",")
+ .map((h) => h.trim())
+ .filter(Boolean)
+ .map((host) => {
+ const esc = host.replace(/[.]/g, "\\.").replace(/[*]/g, "[^/]*");
+ return new RegExp(`^https?://${esc}(:[0-9]+)?(/.*)?$`);
+ })
+ : null;
+ return {
+ name: "honcho-api-proxy",
+ configureServer(server) {
+ server.middlewares.use("/api", async (req, res) => {
+ const upstream = req.headers[HEADER];
+ if (typeof upstream !== "string" || upstream.trim() === "") {
+ res.statusCode = 421;
+ res.setHeader("X-Honcho-Proxy-Reject", "no-upstream");
+ res.end();
+ return;
+ }
+ if (allowlist && !allowlist.some((re) => re.test(upstream))) {
+ res.statusCode = 403;
+ res.setHeader("X-Honcho-Proxy-Reject", "allowlist");
+ res.end();
+ return;
+ }
+ const target = upstream.replace(/\/+$/, "") + (req.url ?? "");
+ const chunks: Buffer[] = [];
+ for await (const c of req) chunks.push(c as Buffer);
+ try {
+ const upstreamRes = await fetch(target, {
+ method: req.method,
+ headers: {
+ "content-type": req.headers["content-type"] ?? "application/json",
+ ...(req.headers.authorization ? { authorization: req.headers.authorization } : {}),
+ },
+ body: ["GET", "HEAD"].includes(req.method ?? "") ? undefined : Buffer.concat(chunks),
+ });
+ res.statusCode = upstreamRes.status;
+ // undici's fetch auto-decompresses the body, so the original
+ // content-encoding/length no longer describe what we re-send —
+ // drop those (and hop-by-hop headers) to avoid ERR_CONTENT_DECODING_FAILED.
+ const SKIP = new Set([
+ "content-encoding",
+ "content-length",
+ "transfer-encoding",
+ "connection",
+ ]);
+ upstreamRes.headers.forEach((v, k) => {
+ if (!SKIP.has(k.toLowerCase())) res.setHeader(k, v);
+ });
+ res.end(Buffer.from(await upstreamRes.arrayBuffer()));
+ } catch (e) {
+ res.statusCode = 502;
+ res.end(`proxy error: ${e instanceof Error ? e.message : String(e)}`);
+ }
+ });
+ },
+ };
+}
+
export default defineConfig({
clearScreen: false,
- plugins: [tanstackRouter({ autoCodeSplitting: true }), react(), tailwindcss()],
+ plugins: [tanstackRouter({ autoCodeSplitting: true }), react(), honchoApiProxy(), tailwindcss()],
define: {
__APP_VERSION__: JSON.stringify(version),
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4678022..2060c0a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,8 +7,8 @@ settings:
catalogs:
default:
'@biomejs/biome':
- specifier: ^2.4.0
- version: 2.4.13
+ specifier: ^2.4.16
+ version: 2.4.16
'@playwright/test':
specifier: ^1.59.1
version: 1.59.1
@@ -31,11 +31,11 @@ catalogs:
specifier: ^6.0.1
version: 6.0.1
'@vitest/coverage-v8':
- specifier: ^4.0.0
- version: 4.1.7
+ specifier: ^4.1.8
+ version: 4.1.8
jsdom:
- specifier: ^26.1.0
- version: 26.1.0
+ specifier: ^29.1.1
+ version: 29.1.1
react:
specifier: ^19.2.5
version: 19.2.5
@@ -43,7 +43,7 @@ catalogs:
specifier: ^19.2.5
version: 19.2.5
semantic-release:
- specifier: ^25.0.0
+ specifier: ^25.0.3
version: 25.0.3
typescript:
specifier: ~6.0.2
@@ -52,8 +52,8 @@ catalogs:
specifier: ^8.0.10
version: 8.0.10
vitest:
- specifier: ^4.0.0
- version: 4.1.5
+ specifier: ^4.1.8
+ version: 4.1.8
zod:
specifier: ^4.0.0
version: 4.3.6
@@ -66,34 +66,34 @@ importers:
devDependencies:
'@biomejs/biome':
specifier: 'catalog:'
- version: 2.4.13
+ version: 2.4.16
'@commitlint/cli':
- specifier: ~20.5.2
- version: 20.5.2(@types/node@25.6.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)
+ specifier: ~20.5.3
+ version: 20.5.3(@types/node@25.6.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)
'@commitlint/config-conventional':
- specifier: ~20.5.0
- version: 20.5.0
+ specifier: ~20.5.3
+ version: 20.5.3
'@semantic-release/changelog':
- specifier: ^6.0.0
+ specifier: ^6.0.3
version: 6.0.3(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/commit-analyzer':
- specifier: ^13.0.0
+ specifier: ^13.0.1
version: 13.0.1(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/exec':
specifier: ^7.1.0
version: 7.1.0(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/git':
- specifier: ^10.0.0
+ specifier: ^10.0.1
version: 10.0.1(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/github':
- specifier: ^10.0.0
+ specifier: ^10.3.5
version: 10.3.5(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/npm':
specifier: ^13.1.5
version: 13.1.5(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/release-notes-generator':
- specifier: ^14.0.0
- version: 14.1.0(semantic-release@25.0.3(typescript@6.0.3))
+ specifier: ^14.1.1
+ version: 14.1.1(semantic-release@25.0.3(typescript@6.0.3))
husky:
specifier: ~9.1.7
version: 9.1.7
@@ -101,8 +101,8 @@ importers:
specifier: 'catalog:'
version: 25.0.3(typescript@6.0.3)
turbo:
- specifier: ^2
- version: 2.9.6
+ specifier: ^2.9.16
+ version: 2.9.16
packages/desktop:
dependencies:
@@ -233,10 +233,10 @@ importers:
version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
'@vitest/coverage-v8':
specifier: 'catalog:'
- version: 4.1.7(vitest@4.1.5)
+ version: 4.1.8(vitest@4.1.8)
jsdom:
specifier: 'catalog:'
- version: 26.1.0
+ version: 29.1.1
openapi-typescript:
specifier: ^7.8.0
version: 7.13.0(typescript@6.0.3)
@@ -248,7 +248,7 @@ importers:
version: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)
vitest:
specifier: 'catalog:'
- version: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
+ version: 4.1.8(@types/node@25.6.0)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
packages:
@@ -267,13 +267,29 @@ packages:
'@adobe/css-tools@4.4.4':
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
- '@asamuzakjp/css-color@3.2.0':
- resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+ '@asamuzakjp/css-color@5.1.11':
+ resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/dom-selector@7.1.1':
+ resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/generational-cache@1.0.1':
+ resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ '@asamuzakjp/nwsapi@2.3.9':
+ resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.29.0':
resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
engines: {node: '>=6.9.0'}
@@ -358,6 +374,10 @@ packages:
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.28.6':
resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
engines: {node: '>=6.9.0'}
@@ -378,82 +398,86 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
- '@biomejs/biome@2.4.13':
- resolution: {integrity: sha512-gLXOwkOBBg0tr7bDsqlkIh4uFeKuMjxvqsrb1Tukww1iDmHcfr4Uu8MoQxp0Rcte+69+osRNWXwHsu/zxT6XqA==}
+ '@biomejs/biome@2.4.16':
+ resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
engines: {node: '>=14.21.3'}
hasBin: true
- '@biomejs/cli-darwin-arm64@2.4.13':
- resolution: {integrity: sha512-2KImO1jhNFBa2oWConyr0x6flxbQpGKv6902uGXpYM62Xyem8U80j441SyUJ8KyngsmKbQjeIv1q2CQfDkNnYg==}
+ '@biomejs/cli-darwin-arm64@2.4.16':
+ resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [darwin]
- '@biomejs/cli-darwin-x64@2.4.13':
- resolution: {integrity: sha512-BKrJklbaFN4p1Ts4kPBczo+PkbsHQg57kmJ+vON9u2t6uN5okYHaSr7h/MutPCWQgg2lglaWoSmm+zhYW+oOkg==}
+ '@biomejs/cli-darwin-x64@2.4.16':
+ resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [darwin]
- '@biomejs/cli-linux-arm64-musl@2.4.13':
- resolution: {integrity: sha512-U5MsuBQW25dXaYtqWWSPM3P96H6Y+fHuja3TQpMNnylocHW0tEbtFTDlUj6oM+YJLntvEkQy4grBvQNUD4+RCg==}
+ '@biomejs/cli-linux-arm64-musl@2.4.16':
+ resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@biomejs/cli-linux-arm64@2.4.13':
- resolution: {integrity: sha512-NzkUDSqfvMBrPplKgVr3aXLHZ2NEELvvF4vZxXulEylKWIGqlvNEcwUcj9OLrn75TD3lJ/GIqCVlBwd1MZCuYQ==}
+ '@biomejs/cli-linux-arm64@2.4.16':
+ resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@biomejs/cli-linux-x64-musl@2.4.13':
- resolution: {integrity: sha512-Z601MienRgTBDza/+u2CH3RSrWoXo9rtr8NK6A4KJzqGgfxx+H3VlyLgTJ4sRo40T3pIsqpTmiOQEvYzQvBRvQ==}
+ '@biomejs/cli-linux-x64-musl@2.4.16':
+ resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
libc: [musl]
- '@biomejs/cli-linux-x64@2.4.13':
- resolution: {integrity: sha512-Az3ZZedYRBo9EQzNnD9SxFcR1G5QsGo6VEc2hIyVPZ1rdKwee/7E9oeBBZFpE8Z44ekxsDQBqbiWGW5ShOhUSQ==}
+ '@biomejs/cli-linux-x64@2.4.16':
+ resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@biomejs/cli-win32-arm64@2.4.13':
- resolution: {integrity: sha512-Px9PS2B5/Q183bUwy/5VHqp3J2lzdOCeVGzMpphYfl8oSa7VDCqenBdqWpy6DCy/en4Rbf/Y1RieZF6dJPcc9A==}
+ '@biomejs/cli-win32-arm64@2.4.16':
+ resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
engines: {node: '>=14.21.3'}
cpu: [arm64]
os: [win32]
- '@biomejs/cli-win32-x64@2.4.13':
- resolution: {integrity: sha512-tTcMkXyBrmHi9BfrD2VNHs/5rYIUKETqsBlYOvSAABwBkJhSDVb5e7wPukftsQbO3WzQkXe6kaztC6WtUOXSoQ==}
+ '@biomejs/cli-win32-x64@2.4.16':
+ resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==}
engines: {node: '>=14.21.3'}
cpu: [x64]
os: [win32]
+ '@bramus/specificity@2.4.2':
+ resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==}
+ hasBin: true
+
'@colors/colors@1.5.0':
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'}
- '@commitlint/cli@20.5.2':
- resolution: {integrity: sha512-IXr5xd3IX8SEG936P8gcpozRplkDeDSwJlt8UvoY1winwIy2udTbQ/cOCgbaaxcjdDqVoS29VUcz/wkwnSozbA==}
+ '@commitlint/cli@20.5.3':
+ resolution: {integrity: sha512-OJdL0EXWD5y9LPa0nr/geOwzaS8BsdaybKkcloB0JgsguGxNv2R+hC2FTPqrAcprg35zF33KOQerY0x8W1aesA==}
engines: {node: '>=v18'}
hasBin: true
- '@commitlint/config-conventional@20.5.0':
- resolution: {integrity: sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==}
+ '@commitlint/config-conventional@20.5.3':
+ resolution: {integrity: sha512-j34Qqeaa152chJgz2ysyk0BCpHenJn1lV0Rx0VXf8k3ccQcED+48EZrzMvo9jLmJUyBrrBwvu89I+2er4gW7QQ==}
engines: {node: '>=v18'}
'@commitlint/config-validator@20.5.0':
resolution: {integrity: sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==}
engines: {node: '>=v18'}
- '@commitlint/ensure@20.5.0':
- resolution: {integrity: sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==}
+ '@commitlint/ensure@20.5.3':
+ resolution: {integrity: sha512-4i4AgNvH62owG9MwSiWKrle7HGNpBHHdLnWFIp5fTsHUYe5kRuh15t08L/0pdbbrRk8JKXQxxN4hZQcn+szkrw==}
engines: {node: '>=v18'}
'@commitlint/execute-rule@20.0.0':
@@ -468,12 +492,12 @@ packages:
resolution: {integrity: sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==}
engines: {node: '>=v18'}
- '@commitlint/lint@20.5.0':
- resolution: {integrity: sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==}
+ '@commitlint/lint@20.5.3':
+ resolution: {integrity: sha512-M7JbWBNr2gXKaPc4i/KipsuW1gkDHpj35KPjWtKy3Z+2AQw5wu1gBi1LIO0uoaij67CqY4K8PxPZSGens4evCw==}
engines: {node: '>=v18'}
- '@commitlint/load@20.5.2':
- resolution: {integrity: sha512-zmr0RGDz7vThxW1I8ohb9yBjnGuH9mqwJpn21hInjGla+IlLOkS9ey0+dD5HlkzFlY0lX2NYdA2lDW6/0rO7Gw==}
+ '@commitlint/load@20.5.3':
+ resolution: {integrity: sha512-1FDZWuKyu98Myb8i7Tp31jPU2rZpOwAdYRyJcy2KoGg7Xk2A+bgHN8smhMaaNSNkmE8fwt53BokywZq8Gv/5XQ==}
engines: {node: '>=v18'}
'@commitlint/message@20.4.3':
@@ -488,12 +512,12 @@ packages:
resolution: {integrity: sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==}
engines: {node: '>=v18'}
- '@commitlint/resolve-extends@20.5.2':
- resolution: {integrity: sha512-8EhSCU9eNos/5cI1yg64GW79UH1c64O69AfStCsj4zqy6An/qIphVEXj4/+2M6056T8coz00f+UXFn4WUUP1HQ==}
+ '@commitlint/resolve-extends@20.5.3':
+ resolution: {integrity: sha512-+ogW9v/u9JqpvAgTrLra/YTFo0KkjU6iNblF89pPsj4NebNc+DAWctsludwezI8YnsjBmfHpApSwcXprN/f/ew==}
engines: {node: '>=v18'}
- '@commitlint/rules@20.5.0':
- resolution: {integrity: sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==}
+ '@commitlint/rules@20.5.3':
+ resolution: {integrity: sha512-MPlMnb9D3wbszYMp+1hPtuhtPJndRo6I6yfkZVA4+jR8w7Kqp0u2u/Y+gzbaItx5Lltq5rw7FSZQWJMoXUC4NQ==}
engines: {node: '>=v18'}
'@commitlint/to-lines@20.0.0':
@@ -520,33 +544,41 @@ packages:
conventional-commits-parser:
optional: true
- '@csstools/color-helpers@5.1.0':
- resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
- engines: {node: '>=18'}
+ '@csstools/color-helpers@6.0.2':
+ resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==}
+ engines: {node: '>=20.19.0'}
- '@csstools/css-calc@2.1.4':
- resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
- engines: {node: '>=18'}
+ '@csstools/css-calc@3.2.1':
+ resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.5
- '@csstools/css-tokenizer': ^3.0.4
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
- '@csstools/css-color-parser@3.1.0':
- resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
- engines: {node: '>=18'}
+ '@csstools/css-color-parser@4.1.1':
+ resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.5
- '@csstools/css-tokenizer': ^3.0.4
+ '@csstools/css-parser-algorithms': ^4.0.0
+ '@csstools/css-tokenizer': ^4.0.0
- '@csstools/css-parser-algorithms@3.0.5':
- resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
- engines: {node: '>=18'}
+ '@csstools/css-parser-algorithms@4.0.0':
+ resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
- '@csstools/css-tokenizer': ^3.0.4
+ '@csstools/css-tokenizer': ^4.0.0
- '@csstools/css-tokenizer@3.0.4':
- resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
- engines: {node: '>=18'}
+ '@csstools/css-syntax-patches-for-csstree@1.1.4':
+ resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==}
+ peerDependencies:
+ css-tree: ^3.2.1
+ peerDependenciesMeta:
+ css-tree:
+ optional: true
+
+ '@csstools/css-tokenizer@4.0.0':
+ resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
+ engines: {node: '>=20.19.0'}
'@emnapi/core@1.10.0':
resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
@@ -713,6 +745,15 @@ packages:
cpu: [x64]
os: [win32]
+ '@exodus/bytes@1.15.1':
+ resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ peerDependencies:
+ '@noble/hashes': ^1.8.0 || ^2.0.0
+ peerDependenciesMeta:
+ '@noble/hashes':
+ optional: true
+
'@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
@@ -853,8 +894,8 @@ packages:
resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==}
engines: {node: '>= 20'}
- '@octokit/request@10.0.8':
- resolution: {integrity: sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==}
+ '@octokit/request@10.0.10':
+ resolution: {integrity: sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==}
engines: {node: '>= 20'}
'@octokit/request@9.2.4':
@@ -1347,8 +1388,8 @@ packages:
peerDependencies:
semantic-release: '>=20.1.0'
- '@semantic-release/github@12.0.6':
- resolution: {integrity: sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==}
+ '@semantic-release/github@12.0.8':
+ resolution: {integrity: sha512-tej5AAgK5X9wHRoDmYhecMXEHEkFeGOY1XsEblKxu8pIQwahzf1STYyr7iPU6Lpbg6C5I3N2w/ocXrBo+L7jhw==}
engines: {node: ^22.14.0 || >= 24.10.0}
peerDependencies:
semantic-release: '>=24.1.0'
@@ -1359,8 +1400,8 @@ packages:
peerDependencies:
semantic-release: '>=20.1.0'
- '@semantic-release/release-notes-generator@14.1.0':
- resolution: {integrity: sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==}
+ '@semantic-release/release-notes-generator@14.1.1':
+ resolution: {integrity: sha512-Pbd2e2XRMUD0OxehHpgd5/YghsE76cddkRHSoDvKLK+OCy4Ewxn49rWR631MEUU01lgwF/uyVXvbnVuu6+Z6VA==}
engines: {node: '>=20.8.1'}
peerDependencies:
semantic-release: '>=20.1.0'
@@ -1670,33 +1711,33 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
- '@turbo/darwin-64@2.9.6':
- resolution: {integrity: sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg==}
+ '@turbo/darwin-64@2.9.16':
+ resolution: {integrity: sha512-jLjApWTSNd7JZ5JaLYfelW1ytnGQOvB7ivl+2RD1xQvJTbi8I9gBjzcga7tDZVPyaxpl10YTfJt3BrYXR18KDw==}
cpu: [x64]
os: [darwin]
- '@turbo/darwin-arm64@2.9.6':
- resolution: {integrity: sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw==}
+ '@turbo/darwin-arm64@2.9.16':
+ resolution: {integrity: sha512-YPgrn+5HIGzrx0O2a631SV4MBQUe4W/DafMFUuBVgaU32PW9/OTT0ehviF0QSxTXuRJlHvW2eUTemddF5/spmw==}
cpu: [arm64]
os: [darwin]
- '@turbo/linux-64@2.9.6':
- resolution: {integrity: sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA==}
+ '@turbo/linux-64@2.9.16':
+ resolution: {integrity: sha512-vAEf1H6l26lTpl9FJ/peQo1NUB8RC0sbEJJz5mPcUhHA2bPDup2x3CZPgo/bH8S4cUcBLm4FN3UHd5iUO2RAew==}
cpu: [x64]
os: [linux]
- '@turbo/linux-arm64@2.9.6':
- resolution: {integrity: sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g==}
+ '@turbo/linux-arm64@2.9.16':
+ resolution: {integrity: sha512-xDBLR2PZg4BrQOchfG6svgpv5FCNJ2TOtT2psLdEJcdKo1BH+pnPs9Xj6pvUjgfkHbuvBOfeE4R6tvxMoQKDHQ==}
cpu: [arm64]
os: [linux]
- '@turbo/windows-64@2.9.6':
- resolution: {integrity: sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g==}
+ '@turbo/windows-64@2.9.16':
+ resolution: {integrity: sha512-NBAJnaUiGdgkSzQwUIdOvkCkcpTSu58G/sBGa0mvBtzfvFOOgrQwepKOOQ8cp6sWM6OcKDNFj2p1dsZA1OWjPg==}
cpu: [x64]
os: [win32]
- '@turbo/windows-arm64@2.9.6':
- resolution: {integrity: sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A==}
+ '@turbo/windows-arm64@2.9.16':
+ resolution: {integrity: sha512-Y7SJppD0Z8wjO3Ec0ZGd9KQ4Yv0BMnA8CIowj5Vp+OEVsosXDG2weK6/t1RRLfJmc2Ozrnd6y4DOgQys+mn3WQ==}
cpu: [arm64]
os: [win32]
@@ -1769,20 +1810,20 @@ packages:
babel-plugin-react-compiler:
optional: true
- '@vitest/coverage-v8@4.1.7':
- resolution: {integrity: sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==}
+ '@vitest/coverage-v8@4.1.8':
+ resolution: {integrity: sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==}
peerDependencies:
- '@vitest/browser': 4.1.7
- vitest: 4.1.7
+ '@vitest/browser': 4.1.8
+ vitest: 4.1.8
peerDependenciesMeta:
'@vitest/browser':
optional: true
- '@vitest/expect@4.1.5':
- resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==}
+ '@vitest/expect@4.1.8':
+ resolution: {integrity: sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==}
- '@vitest/mocker@4.1.5':
- resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==}
+ '@vitest/mocker@4.1.8':
+ resolution: {integrity: sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -1792,26 +1833,20 @@ packages:
vite:
optional: true
- '@vitest/pretty-format@4.1.5':
- resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==}
+ '@vitest/pretty-format@4.1.8':
+ resolution: {integrity: sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==}
- '@vitest/pretty-format@4.1.7':
- resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==}
+ '@vitest/runner@4.1.8':
+ resolution: {integrity: sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==}
- '@vitest/runner@4.1.5':
- resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==}
+ '@vitest/snapshot@4.1.8':
+ resolution: {integrity: sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==}
- '@vitest/snapshot@4.1.5':
- resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==}
+ '@vitest/spy@4.1.8':
+ resolution: {integrity: sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==}
- '@vitest/spy@4.1.5':
- resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==}
-
- '@vitest/utils@4.1.5':
- resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==}
-
- '@vitest/utils@4.1.7':
- resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==}
+ '@vitest/utils@4.1.8':
+ resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==}
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
@@ -1822,6 +1857,10 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
+ agent-base@9.0.0:
+ resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==}
+ engines: {node: '>= 20'}
+
aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
@@ -1923,6 +1962,9 @@ packages:
before-after-hook@4.0.0:
resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==}
+ bidi-js@1.0.3:
+ resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -2051,6 +2093,10 @@ packages:
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
+ content-type@2.0.0:
+ resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
+ engines: {node: '>=18'}
+
conventional-changelog-angular@8.3.1:
resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==}
engines: {node: '>=18'}
@@ -2111,19 +2157,19 @@ packages:
resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==}
engines: {node: '>=12'}
+ css-tree@3.2.1:
+ resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
css.escape@1.5.1:
resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
- cssstyle@4.6.0:
- resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
- engines: {node: '>=18'}
-
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
- data-urls@5.0.0:
- resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
- engines: {node: '>=18'}
+ data-urls@7.0.0:
+ resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
@@ -2195,9 +2241,9 @@ packages:
resolution: {integrity: sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==}
engines: {node: '>=10.13.0'}
- entities@6.0.1:
- resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
- engines: {node: '>=0.12'}
+ entities@8.0.0:
+ resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
+ engines: {node: '>=20.19.0'}
env-ci@11.2.0:
resolution: {integrity: sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==}
@@ -2217,6 +2263,9 @@ packages:
es-module-lexer@2.1.0:
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
+ es-toolkit@1.47.0:
+ resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==}
+
esbuild@0.27.7:
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
engines: {node: '>=18'}
@@ -2262,9 +2311,6 @@ packages:
fast-content-type-parse@2.0.1:
resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==}
- fast-content-type-parse@3.0.0:
- resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==}
-
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -2272,8 +2318,8 @@ packages:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
- fast-uri@3.1.0:
- resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+ fast-uri@3.1.2:
+ resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -2325,11 +2371,8 @@ packages:
react-dom:
optional: true
- from2@2.3.0:
- resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
-
- fs-extra@11.3.4:
- resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
+ fs-extra@11.3.5:
+ resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==}
engines: {node: '>=14.14'}
fsevents@2.3.2:
@@ -2354,8 +2397,8 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
- get-east-asian-width@1.5.0:
- resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
+ get-east-asian-width@1.6.0:
+ resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
engines: {node: '>=18'}
get-nonce@1.0.1:
@@ -2366,10 +2409,6 @@ packages:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
- get-stream@7.0.1:
- resolution: {integrity: sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==}
- engines: {node: '>=16'}
-
get-stream@8.0.1:
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
engines: {node: '>=16'}
@@ -2437,13 +2476,13 @@ packages:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
- hosted-git-info@9.0.2:
- resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==}
+ hosted-git-info@9.0.3:
+ resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==}
engines: {node: ^20.17.0 || >=22.9.0}
- html-encoding-sniffer@4.0.0:
- resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
- engines: {node: '>=18'}
+ html-encoding-sniffer@6.0.0:
+ resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@@ -2455,10 +2494,18 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
+ http-proxy-agent@9.0.0:
+ resolution: {integrity: sha512-FcF8VhXYLQcxWCnt/cCpT2apKsRDUGeVEeMqGu4HSTu29U8Yw0TLOjdYIlDsYk3IkUh+taX4IDWpPcCqKDhCjA==}
+ engines: {node: '>= 20'}
+
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
+ https-proxy-agent@9.0.0:
+ resolution: {integrity: sha512-/MVmHp58WkOypgFhCLk4fzpPcFQvTJ/e6LBI7irpIO2HfxUbpmYoHF+KzipzJpxxzJu7aJNWQ0xojJ/dzV2G5g==}
+ engines: {node: '>= 20'}
+
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -2476,10 +2523,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
- iconv-lite@0.6.3:
- resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
- engines: {node: '>=0.10.0'}
-
ignore@7.0.5:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
@@ -2520,10 +2563,6 @@ packages:
inline-style-parser@0.2.7:
resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
- into-stream@7.0.0:
- resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==}
- engines: {node: '>=12'}
-
is-alphabetical@2.0.1:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
@@ -2596,8 +2635,8 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
- issue-parser@7.0.1:
- resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==}
+ issue-parser@7.0.2:
+ resolution: {integrity: sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==}
engines: {node: ^18.17 || >=20.6.1}
istanbul-lib-coverage@3.2.2:
@@ -2634,9 +2673,13 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
- jsdom@26.1.0:
- resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
- engines: {node: '>=18'}
+ js-yaml@4.2.0:
+ resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
+ hasBin: true
+
+ jsdom@29.1.1:
+ resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0}
peerDependencies:
canvas: ^3.0.0
peerDependenciesMeta:
@@ -2756,9 +2799,6 @@ packages:
lodash-es@4.18.1:
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
- lodash.camelcase@4.3.0:
- resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
-
lodash.capitalize@4.2.1:
resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==}
@@ -2771,24 +2811,9 @@ packages:
lodash.isstring@4.0.1:
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
- lodash.kebabcase@4.1.1:
- resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
-
- lodash.mergewith@4.6.2:
- resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
-
- lodash.snakecase@4.1.1:
- resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
-
- lodash.startcase@4.4.0:
- resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
-
lodash.uniqby@4.7.0:
resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==}
- lodash.upperfirst@4.3.1:
- resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==}
-
lodash@4.18.1:
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
@@ -2798,8 +2823,8 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
- lru-cache@11.3.5:
- resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==}
+ lru-cache@11.5.1:
+ resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
engines: {node: 20 || >=22}
lru-cache@5.1.1:
@@ -2891,6 +2916,9 @@ packages:
mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+ mdn-data@2.27.1:
+ resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
+
meow@13.2.0:
resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
engines: {node: '>=18'}
@@ -3056,8 +3084,8 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- normalize-url@9.0.0:
- resolution: {integrity: sha512-z9nC87iaZXXySbWWtTHfCFJyFvKaUAW6lODhikG7ILSbVgmwuFjUqkgnheHvAUcGedO29e2QGBRXMUD64aurqQ==}
+ normalize-url@9.0.1:
+ resolution: {integrity: sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg==}
engines: {node: '>=20'}
npm-run-path@4.0.1:
@@ -3072,8 +3100,8 @@ packages:
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
engines: {node: '>=18'}
- npm@11.13.0:
- resolution: {integrity: sha512-cRmhaghDWA1lFgl3Ug4/VxDJdPBK/U+tNtnrl9kXunFqhWw1x4xL5txkNn7qzPuVfvXOmXyjHpMwsuk2uisbkg==}
+ npm@11.16.0:
+ resolution: {integrity: sha512-A74XL8OxmcegZDMWPkWb5bEQppg8HdYwW3rBD2sPoS4UQHVajfaxBkqyzLeJ3wR0kZ+5xoTjItxXaF7eIXUsyw==}
engines: {node: ^20.17.0 || >=22.9.0}
hasBin: true
bundledDependencies:
@@ -3143,9 +3171,6 @@ packages:
- validate-npm-package-name
- which
- nwsapi@2.2.23:
- resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
-
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -3185,10 +3210,6 @@ packages:
resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==}
engines: {node: '>=18'}
- p-is-promise@3.0.0:
- resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==}
- engines: {node: '>=8'}
-
p-limit@1.3.0:
resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==}
engines: {node: '>=4'}
@@ -3249,8 +3270,8 @@ packages:
parse5@6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
- parse5@7.3.0:
- resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+ parse5@8.0.1:
+ resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==}
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
@@ -3464,18 +3485,12 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
- rrweb-cssom@0.8.0:
- resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
-
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
- safer-buffer@2.1.2:
- resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
-
saxes@6.0.0:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
@@ -3496,8 +3511,8 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
- semver@7.7.4:
- resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ semver@7.8.1:
+ resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
engines: {node: '>=10'}
hasBin: true
@@ -3693,36 +3708,40 @@ packages:
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
- tinyexec@1.1.1:
- resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
+ tinyexec@1.2.4:
+ resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
engines: {node: '>=18'}
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
- tldts-core@6.1.86:
- resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+ tldts-core@7.4.2:
+ resolution: {integrity: sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==}
- tldts@6.1.86:
- resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ tldts@7.4.2:
+ resolution: {integrity: sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==}
hasBin: true
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
- tough-cookie@5.1.2:
- resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ tough-cookie@6.0.1:
+ resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==}
engines: {node: '>=16'}
- tr46@5.1.1:
- resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
- engines: {node: '>=18'}
+ tr46@6.0.0:
+ resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
+ engines: {node: '>=20'}
traverse@0.6.8:
resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==}
@@ -3746,8 +3765,8 @@ packages:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
- turbo@2.9.6:
- resolution: {integrity: sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==}
+ turbo@2.9.16:
+ resolution: {integrity: sha512-NqgRQy6j6dPYcdSdv0q1g9QsZg7SWg87RERM8otw/1AtKU2yTFVClOM7cbwKzOonZr/Ek1blTBucw64L9H0Bwg==}
hasBin: true
type-fest@1.4.0:
@@ -3762,8 +3781,8 @@ packages:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
- type-fest@5.6.0:
- resolution: {integrity: sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==}
+ type-fest@5.7.0:
+ resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==}
engines: {node: '>=20'}
typescript@6.0.3:
@@ -3779,12 +3798,12 @@ packages:
undici-types@7.19.2:
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
- undici@6.25.0:
- resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==}
+ undici@6.26.0:
+ resolution: {integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==}
engines: {node: '>=18.17'}
- undici@7.25.0:
- resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==}
+ undici@7.27.0:
+ resolution: {integrity: sha512-+t2Z/GwkZQDtu00813aP66ygViGtPHKhhoFZpQKpKrE+9jIgES+Zw+mFNaDWOVRKiuJjuqKHzD3B1sfGg8+ZOQ==}
engines: {node: '>=20.18.1'}
unicode-emoji-modifier-base@1.0.0:
@@ -3929,20 +3948,20 @@ packages:
yaml:
optional: true
- vitest@4.1.5:
- resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==}
+ vitest@4.1.8:
+ resolution: {integrity: sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
- '@vitest/browser-playwright': 4.1.5
- '@vitest/browser-preview': 4.1.5
- '@vitest/browser-webdriverio': 4.1.5
- '@vitest/coverage-istanbul': 4.1.5
- '@vitest/coverage-v8': 4.1.5
- '@vitest/ui': 4.1.5
+ '@vitest/browser-playwright': 4.1.8
+ '@vitest/browser-preview': 4.1.8
+ '@vitest/browser-webdriverio': 4.1.8
+ '@vitest/coverage-istanbul': 4.1.8
+ '@vitest/coverage-v8': 4.1.8
+ '@vitest/ui': 4.1.8
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -3977,25 +3996,20 @@ packages:
web-worker@1.5.0:
resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
- webidl-conversions@7.0.0:
- resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
- engines: {node: '>=12'}
+ webidl-conversions@8.0.1:
+ resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
+ engines: {node: '>=20'}
webpack-virtual-modules@0.6.2:
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
- whatwg-encoding@3.1.1:
- resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
- engines: {node: '>=18'}
- deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+ whatwg-mimetype@5.0.0:
+ resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==}
+ engines: {node: '>=20'}
- whatwg-mimetype@4.0.0:
- resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
- engines: {node: '>=18'}
-
- whatwg-url@14.2.0:
- resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
- engines: {node: '>=18'}
+ whatwg-url@16.0.1:
+ resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
@@ -4018,18 +4032,6 @@ packages:
resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
engines: {node: '>=18'}
- ws@8.20.0:
- resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
- 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
-
xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
@@ -4102,19 +4104,31 @@ snapshots:
'@actions/http-client@4.0.1':
dependencies:
tunnel: 0.0.6
- undici: 6.25.0
+ undici: 6.26.0
'@actions/io@3.0.2': {}
'@adobe/css-tools@4.4.4': {}
- '@asamuzakjp/css-color@3.2.0':
+ '@asamuzakjp/css-color@5.1.11':
dependencies:
- '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-tokenizer': 3.0.4
- lru-cache: 10.4.3
+ '@asamuzakjp/generational-cache': 1.0.1
+ '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
+
+ '@asamuzakjp/dom-selector@7.1.1':
+ dependencies:
+ '@asamuzakjp/generational-cache': 1.0.1
+ '@asamuzakjp/nwsapi': 2.3.9
+ bidi-js: 1.0.3
+ css-tree: 3.2.1
+ is-potential-custom-element-name: 1.0.1
+
+ '@asamuzakjp/generational-cache@1.0.1': {}
+
+ '@asamuzakjp/nwsapi@2.3.9': {}
'@babel/code-frame@7.29.0':
dependencies:
@@ -4122,6 +4136,12 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
'@babel/compat-data@7.29.0': {}
'@babel/core@7.29.0':
@@ -4215,6 +4235,8 @@ snapshots:
'@babel/runtime@7.29.2': {}
+ '@babel/runtime@7.29.7': {}
+
'@babel/template@7.28.6':
dependencies:
'@babel/code-frame': 7.29.0
@@ -4245,52 +4267,56 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
- '@biomejs/biome@2.4.13':
+ '@biomejs/biome@2.4.16':
optionalDependencies:
- '@biomejs/cli-darwin-arm64': 2.4.13
- '@biomejs/cli-darwin-x64': 2.4.13
- '@biomejs/cli-linux-arm64': 2.4.13
- '@biomejs/cli-linux-arm64-musl': 2.4.13
- '@biomejs/cli-linux-x64': 2.4.13
- '@biomejs/cli-linux-x64-musl': 2.4.13
- '@biomejs/cli-win32-arm64': 2.4.13
- '@biomejs/cli-win32-x64': 2.4.13
+ '@biomejs/cli-darwin-arm64': 2.4.16
+ '@biomejs/cli-darwin-x64': 2.4.16
+ '@biomejs/cli-linux-arm64': 2.4.16
+ '@biomejs/cli-linux-arm64-musl': 2.4.16
+ '@biomejs/cli-linux-x64': 2.4.16
+ '@biomejs/cli-linux-x64-musl': 2.4.16
+ '@biomejs/cli-win32-arm64': 2.4.16
+ '@biomejs/cli-win32-x64': 2.4.16
- '@biomejs/cli-darwin-arm64@2.4.13':
+ '@biomejs/cli-darwin-arm64@2.4.16':
optional: true
- '@biomejs/cli-darwin-x64@2.4.13':
+ '@biomejs/cli-darwin-x64@2.4.16':
optional: true
- '@biomejs/cli-linux-arm64-musl@2.4.13':
+ '@biomejs/cli-linux-arm64-musl@2.4.16':
optional: true
- '@biomejs/cli-linux-arm64@2.4.13':
+ '@biomejs/cli-linux-arm64@2.4.16':
optional: true
- '@biomejs/cli-linux-x64-musl@2.4.13':
+ '@biomejs/cli-linux-x64-musl@2.4.16':
optional: true
- '@biomejs/cli-linux-x64@2.4.13':
+ '@biomejs/cli-linux-x64@2.4.16':
optional: true
- '@biomejs/cli-win32-arm64@2.4.13':
+ '@biomejs/cli-win32-arm64@2.4.16':
optional: true
- '@biomejs/cli-win32-x64@2.4.13':
+ '@biomejs/cli-win32-x64@2.4.16':
optional: true
+ '@bramus/specificity@2.4.2':
+ dependencies:
+ css-tree: 3.2.1
+
'@colors/colors@1.5.0':
optional: true
- '@commitlint/cli@20.5.2(@types/node@25.6.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)':
+ '@commitlint/cli@20.5.3(@types/node@25.6.0)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3)':
dependencies:
'@commitlint/format': 20.5.0
- '@commitlint/lint': 20.5.0
- '@commitlint/load': 20.5.2(@types/node@25.6.0)(typescript@6.0.3)
+ '@commitlint/lint': 20.5.3
+ '@commitlint/load': 20.5.3(@types/node@25.6.0)(typescript@6.0.3)
'@commitlint/read': 20.5.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
'@commitlint/types': 20.5.0
- tinyexec: 1.1.1
+ tinyexec: 1.2.4
yargs: 17.7.2
transitivePeerDependencies:
- '@types/node'
@@ -4298,7 +4324,7 @@ snapshots:
- conventional-commits-parser
- typescript
- '@commitlint/config-conventional@20.5.0':
+ '@commitlint/config-conventional@20.5.3':
dependencies:
'@commitlint/types': 20.5.0
conventional-changelog-conventionalcommits: 9.3.1
@@ -4308,14 +4334,10 @@ snapshots:
'@commitlint/types': 20.5.0
ajv: 8.20.0
- '@commitlint/ensure@20.5.0':
+ '@commitlint/ensure@20.5.3':
dependencies:
'@commitlint/types': 20.5.0
- lodash.camelcase: 4.3.0
- lodash.kebabcase: 4.1.1
- lodash.snakecase: 4.1.1
- lodash.startcase: 4.4.0
- lodash.upperfirst: 4.3.1
+ es-toolkit: 1.47.0
'@commitlint/execute-rule@20.0.0': {}
@@ -4327,25 +4349,25 @@ snapshots:
'@commitlint/is-ignored@20.5.0':
dependencies:
'@commitlint/types': 20.5.0
- semver: 7.7.4
+ semver: 7.8.1
- '@commitlint/lint@20.5.0':
+ '@commitlint/lint@20.5.3':
dependencies:
'@commitlint/is-ignored': 20.5.0
'@commitlint/parse': 20.5.0
- '@commitlint/rules': 20.5.0
+ '@commitlint/rules': 20.5.3
'@commitlint/types': 20.5.0
- '@commitlint/load@20.5.2(@types/node@25.6.0)(typescript@6.0.3)':
+ '@commitlint/load@20.5.3(@types/node@25.6.0)(typescript@6.0.3)':
dependencies:
'@commitlint/config-validator': 20.5.0
'@commitlint/execute-rule': 20.0.0
- '@commitlint/resolve-extends': 20.5.2
+ '@commitlint/resolve-extends': 20.5.3
'@commitlint/types': 20.5.0
cosmiconfig: 9.0.1(typescript@6.0.3)
cosmiconfig-typescript-loader: 6.3.0(@types/node@25.6.0)(cosmiconfig@9.0.1(typescript@6.0.3))(typescript@6.0.3)
+ es-toolkit: 1.47.0
is-plain-obj: 4.1.0
- lodash.mergewith: 4.6.2
picocolors: 1.1.1
transitivePeerDependencies:
- '@types/node'
@@ -4365,23 +4387,23 @@ snapshots:
'@commitlint/types': 20.5.0
git-raw-commits: 5.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)
minimist: 1.2.8
- tinyexec: 1.1.1
+ tinyexec: 1.2.4
transitivePeerDependencies:
- conventional-commits-filter
- conventional-commits-parser
- '@commitlint/resolve-extends@20.5.2':
+ '@commitlint/resolve-extends@20.5.3':
dependencies:
'@commitlint/config-validator': 20.5.0
'@commitlint/types': 20.5.0
+ es-toolkit: 1.47.0
global-directory: 5.0.0
import-meta-resolve: 4.2.0
- lodash.mergewith: 4.6.2
resolve-from: 5.0.0
- '@commitlint/rules@20.5.0':
+ '@commitlint/rules@20.5.3':
dependencies:
- '@commitlint/ensure': 20.5.0
+ '@commitlint/ensure': 20.5.3
'@commitlint/message': 20.4.3
'@commitlint/to-lines': 20.0.0
'@commitlint/types': 20.5.0
@@ -4401,30 +4423,34 @@ snapshots:
dependencies:
'@simple-libs/child-process-utils': 1.0.2
'@simple-libs/stream-utils': 1.2.0
- semver: 7.7.4
+ semver: 7.8.1
optionalDependencies:
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.4.0
- '@csstools/color-helpers@5.1.0': {}
+ '@csstools/color-helpers@6.0.2': {}
- '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
- '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-tokenizer': 3.0.4
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
- '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
dependencies:
- '@csstools/color-helpers': 5.1.0
- '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-tokenizer': 3.0.4
+ '@csstools/color-helpers': 6.0.2
+ '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
+ '@csstools/css-tokenizer': 4.0.0
- '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)':
dependencies:
- '@csstools/css-tokenizer': 3.0.4
+ '@csstools/css-tokenizer': 4.0.0
- '@csstools/css-tokenizer@3.0.4': {}
+ '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)':
+ optionalDependencies:
+ css-tree: 3.2.1
+
+ '@csstools/css-tokenizer@4.0.0': {}
'@emnapi/core@1.10.0':
dependencies:
@@ -4520,6 +4546,8 @@ snapshots:
'@esbuild/win32-x64@0.27.7':
optional: true
+ '@exodus/bytes@1.15.1': {}
+
'@floating-ui/core@1.7.5':
dependencies:
'@floating-ui/utils': 0.2.11
@@ -4597,7 +4625,7 @@ snapshots:
dependencies:
'@octokit/auth-token': 6.0.0
'@octokit/graphql': 9.0.3
- '@octokit/request': 10.0.8
+ '@octokit/request': 10.0.10
'@octokit/request-error': 7.1.0
'@octokit/types': 16.0.0
before-after-hook: 4.0.0
@@ -4621,7 +4649,7 @@ snapshots:
'@octokit/graphql@9.0.3':
dependencies:
- '@octokit/request': 10.0.8
+ '@octokit/request': 10.0.10
'@octokit/types': 16.0.0
universal-user-agent: 7.0.3
@@ -4675,12 +4703,12 @@ snapshots:
dependencies:
'@octokit/types': 16.0.0
- '@octokit/request@10.0.8':
+ '@octokit/request@10.0.10':
dependencies:
'@octokit/endpoint': 11.0.3
'@octokit/request-error': 7.1.0
'@octokit/types': 16.0.0
- fast-content-type-parse: 3.0.0
+ content-type: 2.0.0
json-with-bigint: 3.5.8
universal-user-agent: 7.0.3
@@ -5069,7 +5097,7 @@ snapshots:
dependencies:
'@semantic-release/error': 3.0.0
aggregate-error: 3.1.0
- fs-extra: 11.3.4
+ fs-extra: 11.3.5
lodash: 4.18.1
semantic-release: 25.0.3(typescript@6.0.3)
@@ -5130,7 +5158,7 @@ snapshots:
globby: 14.1.0
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6(supports-color@10.2.2)
- issue-parser: 7.0.1
+ issue-parser: 7.0.2
lodash-es: 4.18.1
mime: 4.1.0
p-filter: 4.1.0
@@ -5139,7 +5167,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@semantic-release/github@12.0.6(semantic-release@25.0.3(typescript@6.0.3))':
+ '@semantic-release/github@12.0.8(semantic-release@25.0.3(typescript@6.0.3))':
dependencies:
'@octokit/core': 7.0.6
'@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6)
@@ -5149,15 +5177,15 @@ snapshots:
aggregate-error: 5.0.0
debug: 4.4.3(supports-color@10.2.2)
dir-glob: 3.0.1
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6(supports-color@10.2.2)
- issue-parser: 7.0.1
+ http-proxy-agent: 9.0.0
+ https-proxy-agent: 9.0.0
+ issue-parser: 7.0.2
lodash-es: 4.18.1
mime: 4.1.0
p-filter: 4.1.0
semantic-release: 25.0.3(typescript@6.0.3)
- tinyglobby: 0.2.16
- undici: 7.25.0
+ tinyglobby: 0.2.17
+ undici: 7.27.0
url-join: 5.0.0
transitivePeerDependencies:
- supports-color
@@ -5169,28 +5197,26 @@ snapshots:
aggregate-error: 5.0.0
env-ci: 11.2.0
execa: 9.6.1
- fs-extra: 11.3.4
+ fs-extra: 11.3.5
lodash-es: 4.18.1
nerf-dart: 1.0.0
- normalize-url: 9.0.0
- npm: 11.13.0
+ normalize-url: 9.0.1
+ npm: 11.16.0
rc: 1.2.8
read-pkg: 10.1.0
registry-auth-token: 5.1.1
semantic-release: 25.0.3(typescript@6.0.3)
- semver: 7.7.4
+ semver: 7.8.1
tempy: 3.2.0
- '@semantic-release/release-notes-generator@14.1.0(semantic-release@25.0.3(typescript@6.0.3))':
+ '@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.3(typescript@6.0.3))':
dependencies:
conventional-changelog-angular: 8.3.1
conventional-changelog-writer: 8.4.0
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.4.0
debug: 4.4.3(supports-color@10.2.2)
- get-stream: 7.0.1
import-from-esm: 2.0.0
- into-stream: 7.0.0
lodash-es: 4.18.1
read-package-up: 11.0.0
semantic-release: 25.0.3(typescript@6.0.3)
@@ -5428,8 +5454,8 @@ snapshots:
'@testing-library/dom@10.4.1':
dependencies:
- '@babel/code-frame': 7.29.0
- '@babel/runtime': 7.29.2
+ '@babel/code-frame': 7.29.7
+ '@babel/runtime': 7.29.7
'@types/aria-query': 5.0.4
aria-query: 5.3.0
dom-accessibility-api: 0.5.16
@@ -5460,22 +5486,22 @@ snapshots:
dependencies:
'@testing-library/dom': 10.4.1
- '@turbo/darwin-64@2.9.6':
+ '@turbo/darwin-64@2.9.16':
optional: true
- '@turbo/darwin-arm64@2.9.6':
+ '@turbo/darwin-arm64@2.9.16':
optional: true
- '@turbo/linux-64@2.9.6':
+ '@turbo/linux-64@2.9.16':
optional: true
- '@turbo/linux-arm64@2.9.6':
+ '@turbo/linux-arm64@2.9.16':
optional: true
- '@turbo/windows-64@2.9.6':
+ '@turbo/windows-64@2.9.16':
optional: true
- '@turbo/windows-arm64@2.9.6':
+ '@turbo/windows-arm64@2.9.16':
optional: true
'@tybys/wasm-util@0.10.1':
@@ -5539,10 +5565,10 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-rc.7
vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)
- '@vitest/coverage-v8@4.1.7(vitest@4.1.5)':
+ '@vitest/coverage-v8@4.1.8(vitest@4.1.8)':
dependencies:
'@bcoe/v8-coverage': 1.0.2
- '@vitest/utils': 4.1.7
+ '@vitest/utils': 4.1.8
ast-v8-to-istanbul: 1.0.2
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
@@ -5551,56 +5577,46 @@ snapshots:
obug: 2.1.1
std-env: 4.1.0
tinyrainbow: 3.1.0
- vitest: 4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
+ vitest: 4.1.8(@types/node@25.6.0)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
- '@vitest/expect@4.1.5':
+ '@vitest/expect@4.1.8':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
- '@vitest/spy': 4.1.5
- '@vitest/utils': 4.1.5
+ '@vitest/spy': 4.1.8
+ '@vitest/utils': 4.1.8
chai: 6.2.2
tinyrainbow: 3.1.0
- '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))':
+ '@vitest/mocker@4.1.8(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))':
dependencies:
- '@vitest/spy': 4.1.5
+ '@vitest/spy': 4.1.8
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)
- '@vitest/pretty-format@4.1.5':
+ '@vitest/pretty-format@4.1.8':
dependencies:
tinyrainbow: 3.1.0
- '@vitest/pretty-format@4.1.7':
+ '@vitest/runner@4.1.8':
dependencies:
- tinyrainbow: 3.1.0
-
- '@vitest/runner@4.1.5':
- dependencies:
- '@vitest/utils': 4.1.5
+ '@vitest/utils': 4.1.8
pathe: 2.0.3
- '@vitest/snapshot@4.1.5':
+ '@vitest/snapshot@4.1.8':
dependencies:
- '@vitest/pretty-format': 4.1.5
- '@vitest/utils': 4.1.5
+ '@vitest/pretty-format': 4.1.8
+ '@vitest/utils': 4.1.8
magic-string: 0.30.21
pathe: 2.0.3
- '@vitest/spy@4.1.5': {}
+ '@vitest/spy@4.1.8': {}
- '@vitest/utils@4.1.5':
+ '@vitest/utils@4.1.8':
dependencies:
- '@vitest/pretty-format': 4.1.5
- convert-source-map: 2.0.0
- tinyrainbow: 3.1.0
-
- '@vitest/utils@4.1.7':
- dependencies:
- '@vitest/pretty-format': 4.1.7
+ '@vitest/pretty-format': 4.1.8
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
@@ -5608,6 +5624,8 @@ snapshots:
agent-base@7.1.4: {}
+ agent-base@9.0.0: {}
+
aggregate-error@3.1.0:
dependencies:
clean-stack: 2.2.0
@@ -5621,7 +5639,7 @@ snapshots:
ajv@8.20.0:
dependencies:
fast-deep-equal: 3.1.3
- fast-uri: 3.1.0
+ fast-uri: 3.1.2
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
@@ -5699,6 +5717,10 @@ snapshots:
before-after-hook@4.0.0: {}
+ bidi-js@1.0.3:
+ dependencies:
+ require-from-string: 2.0.2
+
binary-extensions@2.3.0: {}
bottleneck@2.19.5: {}
@@ -5835,6 +5857,8 @@ snapshots:
ini: 1.3.8
proto-list: 1.2.4
+ content-type@2.0.0: {}
+
conventional-changelog-angular@8.3.1:
dependencies:
compare-func: 2.0.0
@@ -5849,7 +5873,7 @@ snapshots:
conventional-commits-filter: 5.0.0
handlebars: 4.7.9
meow: 13.2.0
- semver: 7.7.4
+ semver: 7.8.1
conventional-commits-filter@5.0.0: {}
@@ -5877,7 +5901,7 @@ snapshots:
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.1
- js-yaml: 4.1.1
+ js-yaml: 4.2.0
parse-json: 5.2.0
optionalDependencies:
typescript: 6.0.3
@@ -5892,19 +5916,21 @@ snapshots:
dependencies:
type-fest: 1.4.0
- css.escape@1.5.1: {}
-
- cssstyle@4.6.0:
+ css-tree@3.2.1:
dependencies:
- '@asamuzakjp/css-color': 3.2.0
- rrweb-cssom: 0.8.0
+ mdn-data: 2.27.1
+ source-map-js: 1.2.1
+
+ css.escape@1.5.1: {}
csstype@3.2.3: {}
- data-urls@5.0.0:
+ data-urls@7.0.0:
dependencies:
- whatwg-mimetype: 4.0.0
- whatwg-url: 14.2.0
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
debug@4.4.3(supports-color@10.2.2):
dependencies:
@@ -5961,7 +5987,7 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.3
- entities@6.0.1: {}
+ entities@8.0.0: {}
env-ci@11.2.0:
dependencies:
@@ -5978,6 +6004,8 @@ snapshots:
es-module-lexer@2.1.0: {}
+ es-toolkit@1.47.0: {}
+
esbuild@0.27.7:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.7
@@ -6064,8 +6092,6 @@ snapshots:
fast-content-type-parse@2.0.1: {}
- fast-content-type-parse@3.0.0: {}
-
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
@@ -6076,7 +6102,7 @@ snapshots:
merge2: 1.4.1
micromatch: 4.0.8
- fast-uri@3.1.0: {}
+ fast-uri@3.1.2: {}
fastq@1.20.1:
dependencies:
@@ -6118,12 +6144,7 @@ snapshots:
react: 19.2.5
react-dom: 19.2.5(react@19.2.5)
- from2@2.3.0:
- dependencies:
- inherits: 2.0.4
- readable-stream: 2.3.8
-
- fs-extra@11.3.4:
+ fs-extra@11.3.5:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.1
@@ -6141,14 +6162,12 @@ snapshots:
get-caller-file@2.0.5: {}
- get-east-asian-width@1.5.0: {}
+ get-east-asian-width@1.6.0: {}
get-nonce@1.0.1: {}
get-stream@6.0.1: {}
- get-stream@7.0.1: {}
-
get-stream@8.0.1: {}
get-stream@9.0.1:
@@ -6243,13 +6262,15 @@ snapshots:
dependencies:
lru-cache: 10.4.3
- hosted-git-info@9.0.2:
+ hosted-git-info@9.0.3:
dependencies:
- lru-cache: 11.3.5
+ lru-cache: 11.5.1
- html-encoding-sniffer@4.0.0:
+ html-encoding-sniffer@6.0.0:
dependencies:
- whatwg-encoding: 3.1.1
+ '@exodus/bytes': 1.15.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
html-escaper@2.0.2: {}
@@ -6262,6 +6283,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ http-proxy-agent@9.0.0:
+ dependencies:
+ agent-base: 9.0.0
+ debug: 4.4.3(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
https-proxy-agent@7.0.6(supports-color@10.2.2):
dependencies:
agent-base: 7.1.4
@@ -6269,6 +6297,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ https-proxy-agent@9.0.0:
+ dependencies:
+ agent-base: 9.0.0
+ debug: 4.4.3(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
human-signals@2.1.0: {}
human-signals@5.0.0: {}
@@ -6277,10 +6312,6 @@ snapshots:
husky@9.1.7: {}
- iconv-lite@0.6.3:
- dependencies:
- safer-buffer: 2.1.2
-
ignore@7.0.5: {}
import-fresh@3.3.1:
@@ -6311,11 +6342,6 @@ snapshots:
inline-style-parser@0.2.7: {}
- into-stream@7.0.0:
- dependencies:
- from2: 2.3.0
- p-is-promise: 3.0.0
-
is-alphabetical@2.0.1: {}
is-alphanumerical@2.0.1:
@@ -6363,7 +6389,7 @@ snapshots:
isexe@2.0.0: {}
- issue-parser@7.0.1:
+ issue-parser@7.0.2:
dependencies:
lodash.capitalize: 4.2.1
lodash.escaperegexp: 4.1.2
@@ -6398,32 +6424,35 @@ snapshots:
dependencies:
argparse: 2.0.1
- jsdom@26.1.0:
+ js-yaml@4.2.0:
dependencies:
- cssstyle: 4.6.0
- data-urls: 5.0.0
+ argparse: 2.0.1
+
+ jsdom@29.1.1:
+ dependencies:
+ '@asamuzakjp/css-color': 5.1.11
+ '@asamuzakjp/dom-selector': 7.1.1
+ '@bramus/specificity': 2.4.2
+ '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1)
+ '@exodus/bytes': 1.15.1
+ css-tree: 3.2.1
+ data-urls: 7.0.0
decimal.js: 10.6.0
- html-encoding-sniffer: 4.0.0
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6(supports-color@10.2.2)
+ html-encoding-sniffer: 6.0.0
is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.23
- parse5: 7.3.0
- rrweb-cssom: 0.8.0
+ lru-cache: 11.5.1
+ parse5: 8.0.1
saxes: 6.0.0
symbol-tree: 3.2.4
- tough-cookie: 5.1.2
+ tough-cookie: 6.0.1
+ undici: 7.27.0
w3c-xmlserializer: 5.0.0
- webidl-conversions: 7.0.0
- whatwg-encoding: 3.1.1
- whatwg-mimetype: 4.0.0
- whatwg-url: 14.2.0
- ws: 8.20.0
+ webidl-conversions: 8.0.1
+ whatwg-mimetype: 5.0.0
+ whatwg-url: 16.0.1
xml-name-validator: 5.0.0
transitivePeerDependencies:
- - bufferutil
- - supports-color
- - utf-8-validate
+ - '@noble/hashes'
jsesc@3.1.0: {}
@@ -6508,8 +6537,6 @@ snapshots:
lodash-es@4.18.1: {}
- lodash.camelcase@4.3.0: {}
-
lodash.capitalize@4.2.1: {}
lodash.escaperegexp@4.1.2: {}
@@ -6518,25 +6545,15 @@ snapshots:
lodash.isstring@4.0.1: {}
- lodash.kebabcase@4.1.1: {}
-
- lodash.mergewith@4.6.2: {}
-
- lodash.snakecase@4.1.1: {}
-
- lodash.startcase@4.4.0: {}
-
lodash.uniqby@4.7.0: {}
- lodash.upperfirst@4.3.1: {}
-
lodash@4.18.1: {}
longest-streak@3.1.0: {}
lru-cache@10.4.3: {}
- lru-cache@11.3.5: {}
+ lru-cache@11.5.1: {}
lru-cache@5.1.1:
dependencies:
@@ -6557,7 +6574,7 @@ snapshots:
magicast@0.5.3:
dependencies:
'@babel/parser': 7.29.7
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
source-map-js: 1.2.1
make-asynchronous@1.1.0:
@@ -6568,7 +6585,7 @@ snapshots:
make-dir@4.0.0:
dependencies:
- semver: 7.7.4
+ semver: 7.8.1
markdown-table@3.0.4: {}
@@ -6738,6 +6755,8 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
+ mdn-data@2.27.1: {}
+
meow@13.2.0: {}
merge-stream@2.0.0: {}
@@ -6986,18 +7005,18 @@ snapshots:
normalize-package-data@6.0.2:
dependencies:
hosted-git-info: 7.0.2
- semver: 7.7.4
+ semver: 7.8.1
validate-npm-package-license: 3.0.4
normalize-package-data@8.0.0:
dependencies:
- hosted-git-info: 9.0.2
- semver: 7.7.4
+ hosted-git-info: 9.0.3
+ semver: 7.8.1
validate-npm-package-license: 3.0.4
normalize-path@3.0.0: {}
- normalize-url@9.0.0: {}
+ normalize-url@9.0.1: {}
npm-run-path@4.0.1:
dependencies:
@@ -7012,9 +7031,7 @@ snapshots:
path-key: 4.0.0
unicorn-magic: 0.3.0
- npm@11.13.0: {}
-
- nwsapi@2.2.23: {}
+ npm@11.16.0: {}
object-assign@4.1.1: {}
@@ -7054,8 +7071,6 @@ snapshots:
dependencies:
p-map: 7.0.4
- p-is-promise@3.0.0: {}
-
p-limit@1.3.0:
dependencies:
p-try: 1.0.0
@@ -7095,7 +7110,7 @@ snapshots:
parse-json@5.2.0:
dependencies:
- '@babel/code-frame': 7.29.0
+ '@babel/code-frame': 7.29.7
error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
@@ -7116,9 +7131,9 @@ snapshots:
parse5@6.0.1: {}
- parse5@7.3.0:
+ parse5@8.0.1:
dependencies:
- entities: 6.0.1
+ entities: 8.0.0
path-exists@3.0.0: {}
@@ -7254,14 +7269,14 @@ snapshots:
dependencies:
find-up-simple: 1.0.1
read-pkg: 10.1.0
- type-fest: 5.6.0
+ type-fest: 5.7.0
read-pkg@10.1.0:
dependencies:
'@types/normalize-package-data': 2.4.4
normalize-package-data: 8.0.0
parse-json: 8.3.0
- type-fest: 5.6.0
+ type-fest: 5.7.0
unicorn-magic: 0.4.0
read-pkg@9.0.1:
@@ -7362,16 +7377,12 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17
- rrweb-cssom@0.8.0: {}
-
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
safe-buffer@5.1.2: {}
- safer-buffer@2.1.2: {}
-
saxes@6.0.0:
dependencies:
xmlchars: 2.2.0
@@ -7382,9 +7393,9 @@ snapshots:
dependencies:
'@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/error': 4.0.0
- '@semantic-release/github': 12.0.6(semantic-release@25.0.3(typescript@6.0.3))
+ '@semantic-release/github': 12.0.8(semantic-release@25.0.3(typescript@6.0.3))
'@semantic-release/npm': 13.1.5(semantic-release@25.0.3(typescript@6.0.3))
- '@semantic-release/release-notes-generator': 14.1.0(semantic-release@25.0.3(typescript@6.0.3))
+ '@semantic-release/release-notes-generator': 14.1.1(semantic-release@25.0.3(typescript@6.0.3))
aggregate-error: 5.0.0
cosmiconfig: 9.0.1(typescript@6.0.3)
debug: 4.4.3(supports-color@10.2.2)
@@ -7395,7 +7406,7 @@ snapshots:
get-stream: 6.0.1
git-log-parser: 1.2.1
hook-std: 4.0.0
- hosted-git-info: 9.0.2
+ hosted-git-info: 9.0.3
import-from-esm: 2.0.0
lodash-es: 4.18.1
marked: 15.0.12
@@ -7405,7 +7416,7 @@ snapshots:
p-reduce: 3.0.0
read-package-up: 12.0.0
resolve-from: 5.0.0
- semver: 7.7.4
+ semver: 7.8.1
signale: 1.4.0
yargs: 18.0.0
transitivePeerDependencies:
@@ -7416,7 +7427,7 @@ snapshots:
semver@6.3.1: {}
- semver@7.7.4: {}
+ semver@7.8.1: {}
seroval-plugins@1.5.2(seroval@1.5.2):
dependencies:
@@ -7492,7 +7503,7 @@ snapshots:
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
- get-east-asian-width: 1.5.0
+ get-east-asian-width: 1.6.0
strip-ansi: 7.2.0
string_decoder@1.1.1:
@@ -7593,30 +7604,35 @@ snapshots:
tinybench@2.9.0: {}
- tinyexec@1.1.1: {}
+ tinyexec@1.2.4: {}
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+
tinyrainbow@3.1.0: {}
- tldts-core@6.1.86: {}
+ tldts-core@7.4.2: {}
- tldts@6.1.86:
+ tldts@7.4.2:
dependencies:
- tldts-core: 6.1.86
+ tldts-core: 7.4.2
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
- tough-cookie@5.1.2:
+ tough-cookie@6.0.1:
dependencies:
- tldts: 6.1.86
+ tldts: 7.4.2
- tr46@5.1.1:
+ tr46@6.0.0:
dependencies:
punycode: 2.3.1
@@ -7637,14 +7653,14 @@ snapshots:
tunnel@0.0.6: {}
- turbo@2.9.6:
+ turbo@2.9.16:
optionalDependencies:
- '@turbo/darwin-64': 2.9.6
- '@turbo/darwin-arm64': 2.9.6
- '@turbo/linux-64': 2.9.6
- '@turbo/linux-arm64': 2.9.6
- '@turbo/windows-64': 2.9.6
- '@turbo/windows-arm64': 2.9.6
+ '@turbo/darwin-64': 2.9.16
+ '@turbo/darwin-arm64': 2.9.16
+ '@turbo/linux-64': 2.9.16
+ '@turbo/linux-arm64': 2.9.16
+ '@turbo/windows-64': 2.9.16
+ '@turbo/windows-arm64': 2.9.16
type-fest@1.4.0: {}
@@ -7652,7 +7668,7 @@ snapshots:
type-fest@4.41.0: {}
- type-fest@5.6.0:
+ type-fest@5.7.0:
dependencies:
tagged-tag: 1.0.0
@@ -7663,9 +7679,9 @@ snapshots:
undici-types@7.19.2: {}
- undici@6.25.0: {}
+ undici@6.26.0: {}
- undici@7.25.0: {}
+ undici@7.27.0: {}
unicode-emoji-modifier-base@1.0.0: {}
@@ -7783,15 +7799,15 @@ snapshots:
jiti: 2.6.1
tsx: 4.21.0
- vitest@4.1.5(@types/node@25.6.0)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)):
+ vitest@4.1.8(@types/node@25.6.0)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)):
dependencies:
- '@vitest/expect': 4.1.5
- '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
- '@vitest/pretty-format': 4.1.5
- '@vitest/runner': 4.1.5
- '@vitest/snapshot': 4.1.5
- '@vitest/spy': 4.1.5
- '@vitest/utils': 4.1.5
+ '@vitest/expect': 4.1.8
+ '@vitest/mocker': 4.1.8(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0))
+ '@vitest/pretty-format': 4.1.8
+ '@vitest/runner': 4.1.8
+ '@vitest/snapshot': 4.1.8
+ '@vitest/spy': 4.1.8
+ '@vitest/utils': 4.1.8
es-module-lexer: 2.1.0
expect-type: 1.3.0
magic-string: 0.30.21
@@ -7800,15 +7816,15 @@ snapshots:
picomatch: 4.0.4
std-env: 4.1.0
tinybench: 2.9.0
- tinyexec: 1.1.1
- tinyglobby: 0.2.16
+ tinyexec: 1.2.4
+ tinyglobby: 0.2.17
tinyrainbow: 3.1.0
vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(tsx@4.21.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.6.0
- '@vitest/coverage-v8': 4.1.7(vitest@4.1.5)
- jsdom: 26.1.0
+ '@vitest/coverage-v8': 4.1.8(vitest@4.1.8)
+ jsdom: 29.1.1
transitivePeerDependencies:
- msw
@@ -7818,20 +7834,19 @@ snapshots:
web-worker@1.5.0: {}
- webidl-conversions@7.0.0: {}
+ webidl-conversions@8.0.1: {}
webpack-virtual-modules@0.6.2: {}
- whatwg-encoding@3.1.1:
- dependencies:
- iconv-lite: 0.6.3
+ whatwg-mimetype@5.0.0: {}
- whatwg-mimetype@4.0.0: {}
-
- whatwg-url@14.2.0:
+ whatwg-url@16.0.1:
dependencies:
- tr46: 5.1.1
- webidl-conversions: 7.0.0
+ '@exodus/bytes': 1.15.1
+ tr46: 6.0.0
+ webidl-conversions: 8.0.1
+ transitivePeerDependencies:
+ - '@noble/hashes'
which@2.0.2:
dependencies:
@@ -7856,8 +7871,6 @@ snapshots:
string-width: 7.2.0
strip-ansi: 7.2.0
- ws@8.20.0: {}
-
xml-name-validator@5.0.0: {}
xmlchars@2.2.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index f3f6482..e3822fd 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -5,7 +5,7 @@ allowBuilds:
catalog:
# Standard tooling
- "@biomejs/biome": "^2.4.0"
+ "@biomejs/biome": "^2.4.16"
# Testing
"@playwright/test": "^1.59.1"
@@ -15,16 +15,16 @@ catalog:
"@types/react": "^19.2.14"
"@types/react-dom": "^19.2.3"
"@vitejs/plugin-react": "^6.0.1"
- "@vitest/coverage-v8": "^4.0.0"
- jsdom: "^26.1.0"
+ "@vitest/coverage-v8": "^4.1.8"
+ jsdom: "^29.1.1"
# React
react: "^19.2.5"
react-dom: "^19.2.5"
- semantic-release: "^25.0.0"
+ semantic-release: "^25.0.3"
typescript: "~6.0.2"
# Vite
vite: "^8.0.10"
- vitest: "^4.0.0"
+ vitest: "^4.1.8"
zod: "^4.0.0"
diff --git a/scripts/secret-scan.sh b/scripts/secret-scan.sh
index 69f4517..f74265a 100755
--- a/scripts/secret-scan.sh
+++ b/scripts/secret-scan.sh
@@ -52,12 +52,17 @@ check_pattern "Honcho-style JWT (likely)" 'eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_
check_pattern "RSA/EC/DSA/OpenSSH private key block" '-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'
check_pattern "Generic hardcoded password" '(password|passwd|pwd)[[:space:]]*[:=][[:space:]]*["'\'']\w{8,}["'\'']'
+# Environment-specific values — keep live infra out of committed code/docs/PRs.
+# Use examples instead (honcho.example.net; 192.0.2.x per RFC 5737 TEST-NET).
+check_pattern "Tailnet hostname (env-specific; use example.net)" '[A-Za-z0-9-]+\.ts\.net'
+check_pattern "Tailnet/CGNAT IP (env-specific; use 192.0.2.x)" '100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.[0-9]{1,3}\.[0-9]{1,3}'
+
if [ $FOUND -eq 1 ]; then
- printf '\n\033[31m✗ Secret scan: potential secrets in staged changes\033[0m\n' >&2
+ printf '\n\033[31m✗ Secret scan: potential secrets or environment-specific values in staged changes\033[0m\n' >&2
printf '%b' "$FINDINGS" >&2
printf '\n' >&2
printf 'If this is a false positive, bypass with: \033[33mgit commit --no-verify\033[0m\n' >&2
- printf 'Otherwise: remove the secret, rotate the credential, and re-stage.\n\n' >&2
+ printf 'Otherwise: remove the secret/value (use an example), rotate if a credential, and re-stage.\n\n' >&2
exit 1
fi