10 Commits
Author SHA1 Message Date
IliesAllaliandClaude Opus 5.5 1b13b093ba 1.5.8 : changelog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:08:57 +01:00
IliesAllaliandClaude Opus 5.5 c1734501ae Onglets de familles : noms, repère et liens entre onglets (#192)
- Nom d'un onglet : le nom le plus porté par ses membres (naissance, sinon usage), et non plus celui
  de la seule personne de référence. Deux onglets
  au même nom : le plus petit prend le nom suivant le plus porté chez lui, les prénoms de la charnière
  en dernier recours.
- Changer d'onglet garde la personne charnière à sa place et à son zoom : seules les familles autour changent.
- Survol d'un onglet : la charnière s'allume dans l'arbre affiché (halo de la recherche), sans texte.
- Barre rangée comme l'arbre (chaque famille après celle par laquelle on y entre), groupée par branche
  (10 px dans une branche, 16 px entre branches), trait d'union entre deux onglets dont le second part
  du premier, terracotta sur le chemin de l'onglet ouvert.
- Pastille de retour : même place que celle de l'aller, flèche retournée (« ‹ Famille X · n »).
- Nombre d'un onglet : « n personnes » au survol.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:08:25 +01:00
b444c1ad39 build(deps): bump @node-rs/bcrypt from 1.10.7 to 2.0.0 in /backend (#197)
* build(deps): bump @node-rs/bcrypt from 1.10.7 to 2.0.0 in /backend

Bumps [@node-rs/bcrypt](https://github.com/napi-rs/node-rs) from 1.10.7 to 2.0.0.
- [Release notes](https://github.com/napi-rs/node-rs/releases)
- [Commits](https://github.com/napi-rs/node-rs/compare/@node-rs/bcrypt@1.10.7...@node-rs/bcrypt@2.0.0)

---
updated-dependencies:
- dependency-name: "@node-rs/bcrypt"
  dependency-version: 2.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* bcrypt 2 : le coût passe en options (hash(value, { cost }))

Les empreintes déjà enregistrées restent valables : une empreinte faite en 1.10.7 se vérifie en 2.0.
Les nouvelles sortent en $2b$ au lieu de $2y$, même coût.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: IliesAllali <pro.allali.ilies@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:28:22 +01:00
dependabot[bot] 04dc7f0e88 build(deps): bump the landing-minor-patch group (#196)
Bumps the landing-minor-patch group in /landing with 2 updates: [posthog-js](https://github.com/PostHog/posthog-js) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `posthog-js` from 1.438.12 to 1.438.21
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.438.12...posthog-js@1.438.21)

Updates `vite` from 8.3.4 to 8.3.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.5/packages/vite)

---
updated-dependencies:
- dependency-name: posthog-js
  dependency-version: 1.438.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: landing-minor-patch
- dependency-name: vite
  dependency-version: 8.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: landing-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-29 12:24:13 +01:00
dependabot[bot] 1c78c043bd build(deps): bump the frontend-minor-patch group (#195)
Bumps the frontend-minor-patch group in /frontend with 4 updates: [@lucide/icons](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide), [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react), [posthog-js](https://github.com/PostHog/posthog-js) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `@lucide/icons` from 1.59.0 to 1.61.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.61.0/packages/lucide)

Updates `lucide-react` from 1.59.0 to 1.61.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.61.0/packages/lucide-react)

Updates `posthog-js` from 1.438.12 to 1.438.21
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.438.12...posthog-js@1.438.21)

Updates `vite` from 8.3.4 to 8.3.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.5/packages/vite)

---
updated-dependencies:
- dependency-name: "@lucide/icons"
  dependency-version: 1.61.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-minor-patch
- dependency-name: lucide-react
  dependency-version: 1.61.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-minor-patch
- dependency-name: posthog-js
  dependency-version: 1.438.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-patch
- dependency-name: vite
  dependency-version: 8.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-29 12:24:08 +01:00
dependabot[bot] cd93a470b8 build(deps): bump dotenv in /backend in the backend-minor-patch group (#194)
Bumps the backend-minor-patch group in /backend with 1 update: [dotenv](https://github.com/motdotla/dotenv).


Updates `dotenv` from 18.0.8 to 18.0.9
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v18.0.8...v18.0.9)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 18.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: backend-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-29 12:24:04 +01:00
dependabot[bot] b39d1f68e8 build(deps): bump docker/login-action (#193)
Bumps the actions-minor-patch group with 1 update: [docker/login-action](https://github.com/docker/login-action).


Updates `docker/login-action` from 4.6.0 to 4.7.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/dbcb813823bdd20940b903addbd779551569679f...81c6f93d4bc991e510b342a00b419b3bb4efc5c6)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-29 12:23:54 +01:00
IliesAllaliandClaude Opus 5.5 d013c8b8fc Âge : l'anniversaire compte une fois passé (#198)
L'âge d'une personne vivante était l'année en cours moins l'année de naissance : né en décembre,
on prenait un an dès janvier. Même défaut pour l'âge au décès et les âges de l'onglet Vie.
Quand les deux dates sont complètes, l'âge se compte au jour près (utils/age.js) ; une année seule
garde la différence des années.

Fixes #198

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:23:45 +01:00
IliesAllaliandClaude Opus 5.5 3dfc8625e9 README : OIDC, seule la mise en place réelle avec Pocket ID est citée (#153)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:40:39 +01:00
IliesAllaliandClaude Opus 5.5 1a65f28b21 README : OIDC n'est plus expérimental, le conteneur api doit joindre le fournisseur (#153)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:22:23 +01:00
19 changed files with 639 additions and 335 deletions

No files matched your search

+2 -2
View File
@@ -72,7 +72,7 @@ jobs:
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -120,7 +120,7 @@ jobs:
persist-credentials: false
# Tant que les paquets ne sont pas publics, le pull demande une connexion.
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
- uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+17
View File
@@ -2,6 +2,23 @@
Notable changes to Parenthèse. Dates are in YYYY-MM-DD format.
## 1.5.8 (2026-10-29)
### Family tabs
- A tab is named after the surname most of its members carry (birth name, otherwise current name), not after the person it starts from. When two tabs would share a name, the smaller one takes its next most common surname (#192).
- Switching tabs keeps the person who joins the two families at the same place and zoom, only the families around change. Hovering a tab lights that person up in the tree (#192).
- The tab bar follows the shape of the tree, grouped by branch, with a line between a tab and the one it comes from. The way-back badge sits where the way-in badge was (#192).
### Profile
- The age counts the birthday: with a full birth date, a person born in December is not a year older from January. Same for the age at death and the ages in the Life tab. With only a year, the age stays the difference between the years (#198).
### Dependencies
- `@node-rs/bcrypt` 2.0: the cost is passed as an option. Stored passwords still verify, new hashes are `$2b$` with the same cost (#197).
- Minor updates for the frontend, the landing, the backend and the CI (#193, #194, #195, #196).
## 1.5.7 (2026-10-28)
### Phone
+3 -1
View File
@@ -83,7 +83,7 @@ Only the first name, last name and birth year leave the server, and the panel sa
#### Sign in with your identity provider (OIDC)
*Experimental in 1.2.0: tested against a certified OpenID provider, feedback from real setups is welcome in the issues.*
*Used with Pocket ID on a real setup (#153).*
Parenthèse can hand sign-in to an OpenID Connect provider you already run (Authentik, Authelia, Keycloak, Pocket ID, Zitadel…), so your family reuses one account and its two-factor settings. Create an OIDC application (confidential client, authorization code flow) whose redirect URI is the public address of Parenthèse followed by `/auth/oidc/callback`, then set in `.env`:
@@ -95,6 +95,8 @@ OIDC_REDIRECT_URI=https://tree.your-domain.com/auth/oidc/callback
OIDC_PROVIDER_NAME=Authentik # shown on the button
```
The `api` container talks to the provider itself, to read its configuration and exchange the sign-in code. If your Docker network is marked `internal`, give `api` a network that can reach the provider, not just `web`.
The sign-in screen then shows "Sign in with Authentik". On a first sign-in, Parenthèse links the provider account to the Parenthèse account with the same email, or creates one when `ALLOW_REGISTRATION` allows it. With registration closed, the provider only signs in people who already have an account (the first account of an empty instance is still accepted). Emails are only trusted when the provider marks them verified (`email_verified`); `OIDC_REQUIRE_VERIFIED_EMAIL=false` lifts that check, for a provider that doesn't send the claim and where nobody picks their own address. Once linked, an account stays tied to the provider's user id, not to the email.
`PASSWORD_LOGIN=false` removes Parenthèse passwords altogether: sign-in goes through the provider only (the API refuses to start without `OIDC_ISSUER`). Accounts created through the provider have no password; to delete one, its owner types their email instead. Accounts that already have a password are not linked to the provider by email: before switching, each owner opens their account and clicks « Se connecter aussi avec … » ("Also sign in with …"), then signs in with the provider once. `OIDC_SCOPES` (default `openid email profile`) and `OIDC_APP_URL` (where the app opens after sign-in, by default the origin of `OIDC_REDIRECT_URI`) rarely need changing.
+72 -120
View File
@@ -12,11 +12,11 @@
"@fastify/cors": "^11.3.0",
"@fastify/jwt": "^10.2.5",
"@fastify/rate-limit": "^11.2.0",
"@node-rs/bcrypt": "^1.10.7",
"@node-rs/bcrypt": "^2.0.0",
"@prisma/adapter-pg": "^7.10.1",
"@prisma/client": "^7.10.1",
"blurhash": "^2.0.5",
"dotenv": "^18.0.8",
"dotenv": "^18.0.9",
"fastify": "^5.12.7",
"openid-client": "^6.8.8",
"pg": "^8.23.1",
@@ -62,17 +62,6 @@
"@electric-sql/pglite": "0.4.3"
}
},
"node_modules/@emnapi/core": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.3",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
@@ -83,16 +72,6 @@
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
@@ -1281,22 +1260,10 @@
"node": ">=8"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@node-rs/bcrypt": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-1.10.7.tgz",
"integrity": "sha512-1wk0gHsUQC/ap0j6SJa2K34qNhomxXRcEe3T8cI5s+g6fgHBgLTN7U9LzWTG/HE6G4+2tWWLeCabk1wiYGEQSA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt/-/bcrypt-2.0.0.tgz",
"integrity": "sha512-perB+8PsvBsJeUJ0pE2GgOFAZZ3IXSfKhQ3Pkr+y/L5yZm6W727659g+ZtHmlhIi9obs3y2euUMjVqRB/DVlvA==",
"license": "MIT",
"engines": {
"node": ">= 10"
@@ -1306,26 +1273,25 @@
"url": "https://github.com/sponsors/Brooooooklyn"
},
"optionalDependencies": {
"@node-rs/bcrypt-android-arm-eabi": "1.10.7",
"@node-rs/bcrypt-android-arm64": "1.10.7",
"@node-rs/bcrypt-darwin-arm64": "1.10.7",
"@node-rs/bcrypt-darwin-x64": "1.10.7",
"@node-rs/bcrypt-freebsd-x64": "1.10.7",
"@node-rs/bcrypt-linux-arm-gnueabihf": "1.10.7",
"@node-rs/bcrypt-linux-arm64-gnu": "1.10.7",
"@node-rs/bcrypt-linux-arm64-musl": "1.10.7",
"@node-rs/bcrypt-linux-x64-gnu": "1.10.7",
"@node-rs/bcrypt-linux-x64-musl": "1.10.7",
"@node-rs/bcrypt-wasm32-wasi": "1.10.7",
"@node-rs/bcrypt-win32-arm64-msvc": "1.10.7",
"@node-rs/bcrypt-win32-ia32-msvc": "1.10.7",
"@node-rs/bcrypt-win32-x64-msvc": "1.10.7"
"@node-rs/bcrypt-android-arm-eabi": "2.0.0",
"@node-rs/bcrypt-android-arm64": "2.0.0",
"@node-rs/bcrypt-darwin-arm64": "2.0.0",
"@node-rs/bcrypt-darwin-x64": "2.0.0",
"@node-rs/bcrypt-freebsd-x64": "2.0.0",
"@node-rs/bcrypt-linux-arm-gnueabihf": "2.0.0",
"@node-rs/bcrypt-linux-arm64-gnu": "2.0.0",
"@node-rs/bcrypt-linux-arm64-musl": "2.0.0",
"@node-rs/bcrypt-linux-x64-gnu": "2.0.0",
"@node-rs/bcrypt-linux-x64-musl": "2.0.0",
"@node-rs/bcrypt-win32-arm64-msvc": "2.0.0",
"@node-rs/bcrypt-win32-ia32-msvc": "2.0.0",
"@node-rs/bcrypt-win32-x64-msvc": "2.0.0"
}
},
"node_modules/@node-rs/bcrypt-android-arm-eabi": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm-eabi/-/bcrypt-android-arm-eabi-1.10.7.tgz",
"integrity": "sha512-8dO6/PcbeMZXS3VXGEtct9pDYdShp2WBOWlDvSbcRwVqyB580aCBh0BEFmKYtXLzLvUK8Wf+CG3U6sCdILW1lA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm-eabi/-/bcrypt-android-arm-eabi-2.0.0.tgz",
"integrity": "sha512-BGxkS6uXxDphPoMWevOVCXd3A07x45o1p4uYoXTjcvG7K+6iZXaXzOL5ZrGMUP71BAJ6b89/QStjuhGlMmuxOQ==",
"cpu": [
"arm"
],
@@ -1339,9 +1305,9 @@
}
},
"node_modules/@node-rs/bcrypt-android-arm64": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm64/-/bcrypt-android-arm64-1.10.7.tgz",
"integrity": "sha512-UASFBS/CucEMHiCtL/2YYsAY01ZqVR1N7vSb94EOvG5iwW7BQO06kXXCTgj+Xbek9azxixrCUmo3WJnkJZ0hTQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-android-arm64/-/bcrypt-android-arm64-2.0.0.tgz",
"integrity": "sha512-aKS02ZDZ26mLM5resokCCXHFhmlixytN0G7GbR1Xm3kq76mL9munu7vS0xk/5OszEls69o7oHH+NDe8xWyolnQ==",
"cpu": [
"arm64"
],
@@ -1355,9 +1321,9 @@
}
},
"node_modules/@node-rs/bcrypt-darwin-arm64": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-arm64/-/bcrypt-darwin-arm64-1.10.7.tgz",
"integrity": "sha512-DgzFdAt455KTuiJ/zYIyJcKFobjNDR/hnf9OS7pK5NRS13Nq4gLcSIIyzsgHwZHxsJWbLpHmFc1H23Y7IQoQBw==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-arm64/-/bcrypt-darwin-arm64-2.0.0.tgz",
"integrity": "sha512-gHKuT+80v2jAeJujfmJPbbDt5zzE/ne5x/eoG52Twyfx/1bCWAN+cZoDEnzuD0Yduw484bDFtgq5PKXBPV2PYw==",
"cpu": [
"arm64"
],
@@ -1371,9 +1337,9 @@
}
},
"node_modules/@node-rs/bcrypt-darwin-x64": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-x64/-/bcrypt-darwin-x64-1.10.7.tgz",
"integrity": "sha512-SPWVfQ6sxSokoUWAKWD0EJauvPHqOGQTd7CxmYatcsUgJ/bruvEHxZ4bIwX1iDceC3FkOtmeHO0cPwR480n/xA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-darwin-x64/-/bcrypt-darwin-x64-2.0.0.tgz",
"integrity": "sha512-v8xBU+AZshfQ5fDNUvUDQsmc1mYGASnHXD2yVxjuDvKu/gaEHEz5gHtTFqzkNR9NPdWx3YxJ1UiW8fwSuN4XCQ==",
"cpu": [
"x64"
],
@@ -1387,9 +1353,9 @@
}
},
"node_modules/@node-rs/bcrypt-freebsd-x64": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-freebsd-x64/-/bcrypt-freebsd-x64-1.10.7.tgz",
"integrity": "sha512-gpa+Ixs6GwEx6U6ehBpsQetzUpuAGuAFbOiuLB2oo4N58yU4AZz1VIcWyWAHrSWRs92O0SHtmo2YPrMrwfBbSw==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-freebsd-x64/-/bcrypt-freebsd-x64-2.0.0.tgz",
"integrity": "sha512-XSFyizSOH0CWw+60XL4xaeOIRn5F9bgd919EvaIXQcjV7mWuhWbtGc2Z1eOcI8o7Yzh6LSkcIbZJ4fT094S7ww==",
"cpu": [
"x64"
],
@@ -1403,9 +1369,9 @@
}
},
"node_modules/@node-rs/bcrypt-linux-arm-gnueabihf": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm-gnueabihf/-/bcrypt-linux-arm-gnueabihf-1.10.7.tgz",
"integrity": "sha512-kYgJnTnpxrzl9sxYqzflobvMp90qoAlaX1oDL7nhNTj8OYJVDIk0jQgblj0bIkjmoPbBed53OJY/iu4uTS+wig==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm-gnueabihf/-/bcrypt-linux-arm-gnueabihf-2.0.0.tgz",
"integrity": "sha512-YYXkdl4rQuTayFB7MeQva8/H5AVeaaIkzuTNtD0RDEjBSQCKYMalM23pP673ScCvBAWLuiYHtviMITdubiER6Q==",
"cpu": [
"arm"
],
@@ -1419,12 +1385,15 @@
}
},
"node_modules/@node-rs/bcrypt-linux-arm64-gnu": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-gnu/-/bcrypt-linux-arm64-gnu-1.10.7.tgz",
"integrity": "sha512-7cEkK2RA+gBCj2tCVEI1rDSJV40oLbSq7bQ+PNMHNI6jCoXGmj9Uzo7mg7ZRbNZ7piIyNH5zlJqutjo8hh/tmA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-gnu/-/bcrypt-linux-arm64-gnu-2.0.0.tgz",
"integrity": "sha512-jLbuJ/MVa4xvFV5lmd71xx6DOfkQvpj4zR0X0AuOopofP0z6SmyA/zKQOrVrfDUCbBwAHoi22VD9umfU8HsrGA==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1435,12 +1404,15 @@
}
},
"node_modules/@node-rs/bcrypt-linux-arm64-musl": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-musl/-/bcrypt-linux-arm64-musl-1.10.7.tgz",
"integrity": "sha512-X7DRVjshhwxUqzdUKDlF55cwzh+wqWJ2E/tILvZPboO3xaNO07Um568Vf+8cmKcz+tiZCGP7CBmKbBqjvKN/Pw==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-musl/-/bcrypt-linux-arm64-musl-2.0.0.tgz",
"integrity": "sha512-31ORIUbGuBPGyqvZMppQc99l7hdKZrhDxtfsqFcWyeQ5ie4iR7QHv7FGXj+uTLQtJDFVBQQFpVIXTL4jNW7OPQ==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1451,12 +1423,15 @@
}
},
"node_modules/@node-rs/bcrypt-linux-x64-gnu": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-gnu/-/bcrypt-linux-x64-gnu-1.10.7.tgz",
"integrity": "sha512-LXRZsvG65NggPD12hn6YxVgH0W3VR5fsE/o1/o2D5X0nxKcNQGeLWnRzs5cP8KpoFOuk1ilctXQJn8/wq+Gn/Q==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-gnu/-/bcrypt-linux-x64-gnu-2.0.0.tgz",
"integrity": "sha512-Kyo2iGkvtGDcfPcqjRQXS1DYDucpAMJbIw0aPmdiKFLAt8tdOeafK2+zfwuWq8/BDs/n9jKV2ajlda53AqFHMA==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1467,12 +1442,15 @@
}
},
"node_modules/@node-rs/bcrypt-linux-x64-musl": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-musl/-/bcrypt-linux-x64-musl-1.10.7.tgz",
"integrity": "sha512-tCjHmct79OfcO3g5q21ME7CNzLzpw1MAsUXCLHLGWH+V6pp/xTvMbIcLwzkDj6TI3mxK6kehTn40SEjBkZ3Rog==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-x64-musl/-/bcrypt-linux-x64-musl-2.0.0.tgz",
"integrity": "sha512-NkQN7yL4yRQOPpK80SAgHDlYE352QJmnSP3oKp1SXHS/G92b1IwjGlTB2+9ZKIGEDdqxnyI3dRzP0P/Sabv9zg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -1482,26 +1460,10 @@
"node": ">= 10"
}
},
"node_modules/@node-rs/bcrypt-wasm32-wasi": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-wasm32-wasi/-/bcrypt-wasm32-wasi-1.10.7.tgz",
"integrity": "sha512-4qXSihIKeVXYglfXZEq/QPtYtBUvR8d3S85k15Lilv3z5B6NSGQ9mYiNleZ7QHVLN2gEc5gmi7jM353DMH9GkA==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^0.2.5"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@node-rs/bcrypt-win32-arm64-msvc": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-arm64-msvc/-/bcrypt-win32-arm64-msvc-1.10.7.tgz",
"integrity": "sha512-FdfUQrqmDfvC5jFhntMBkk8EI+fCJTx/I1v7Rj+Ezlr9rez1j1FmuUnywbBj2Cg15/0BDhwYdbyZ5GCMFli2aQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-arm64-msvc/-/bcrypt-win32-arm64-msvc-2.0.0.tgz",
"integrity": "sha512-7BzjgPzfE9GktBafMaOTQR5klfcQ4SnzLS5dPKKdXEJ/rHilKbqm8EBszhGimlBMkutIBd53OE7MAqFVhtgIZw==",
"cpu": [
"arm64"
],
@@ -1515,9 +1477,9 @@
}
},
"node_modules/@node-rs/bcrypt-win32-ia32-msvc": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-ia32-msvc/-/bcrypt-win32-ia32-msvc-1.10.7.tgz",
"integrity": "sha512-lZLf4Cx+bShIhU071p5BZft4OvP4PGhyp542EEsb3zk34U5GLsGIyCjOafcF/2DGewZL6u8/aqoxbSuROkgFXg==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-ia32-msvc/-/bcrypt-win32-ia32-msvc-2.0.0.tgz",
"integrity": "sha512-nbbwy8LOd4NxSJ2HrLTrXq5zNy0B6+LtX9wberLp9+DOQZVBfZF7PHlH4KlXi9aHrOpZHoXZNGIhWANtOpJlNA==",
"cpu": [
"ia32"
],
@@ -1531,9 +1493,9 @@
}
},
"node_modules/@node-rs/bcrypt-win32-x64-msvc": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-x64-msvc/-/bcrypt-win32-x64-msvc-1.10.7.tgz",
"integrity": "sha512-hdw7tGmN1DxVAMTzICLdaHpXjy+4rxaxnBMgI8seG1JL5e3VcRGsd1/1vVDogVp2cbsmgq+6d6yAY+D9lW/DCg==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@node-rs/bcrypt-win32-x64-msvc/-/bcrypt-win32-x64-msvc-2.0.0.tgz",
"integrity": "sha512-9KdovFbXWVUhhdsUjtMR8WLw7+U4dCFMyYFRogmdpe+a4SMy8IcN8rv3azxmGYgtoH5wW63BQ0COKZq6C7O96g==",
"cpu": [
"x64"
],
@@ -2234,16 +2196,6 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz",
"integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -3364,9 +3316,9 @@
}
},
"node_modules/dotenv": {
"version": "18.0.8",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.8.tgz",
"integrity": "sha512-2KDeoFoK/zRtcnbunCDlLG5VWVMcyqeQc6iK65o231nfNAcuGrKoqUQfJFUtBI+QlShk5HKIHxwebBtUl3rEfA==",
"version": "18.0.9",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.9.tgz",
"integrity": "sha512-3fVOW4cB02R066PUSwn8O4TJyz9NckQl7F81blIbaq9jKBm2tSmOKky7ZHIXzGvkVPXCSn6jkpziBPZdGFs8Tw==",
"license": "BSD-2-Clause",
"bin": {
"dotenv": "dist/index.cjs"
+2 -2
View File
@@ -21,11 +21,11 @@
"@fastify/cors": "^11.3.0",
"@fastify/jwt": "^10.2.5",
"@fastify/rate-limit": "^11.2.0",
"@node-rs/bcrypt": "^1.10.7",
"@node-rs/bcrypt": "^2.0.0",
"@prisma/adapter-pg": "^7.10.1",
"@prisma/client": "^7.10.1",
"blurhash": "^2.0.5",
"dotenv": "^18.0.8",
"dotenv": "^18.0.9",
"fastify": "^5.12.7",
"openid-client": "^6.8.8",
"pg": "^8.23.1",
+1 -1
View File
@@ -8,7 +8,7 @@ import { env } from '../config/env.js'
const HASH_ROUNDS = 12
export async function hashPassword(value: string): Promise<string> {
return hash(value, HASH_ROUNDS)
return hash(value, { cost: HASH_ROUNDS })
}
export async function verifyPassword(value: string, storedHash: string): Promise<boolean> {
+181 -96
View File
@@ -8,10 +8,10 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"@lucide/icons": "^1.59.0",
"@lucide/icons": "^1.61.0",
"blurhash": "^2.0.5",
"lucide-react": "^1.59.0",
"posthog-js": "^1.438.12",
"lucide-react": "^1.61.0",
"posthog-js": "^1.438.21",
"react": "^19.3.1",
"react-dom": "^19.3.1"
},
@@ -26,7 +26,7 @@
"eslint-plugin-react-refresh": "^0.5.7",
"globals": "^17.13.0",
"tailwindcss": "^4.3.3",
"vite": "^8.3.4",
"vite": "^8.3.5",
"vitest": "^5.0.5"
}
},
@@ -554,15 +554,15 @@
"license": "MIT"
},
"node_modules/@lucide/icons": {
"version": "1.59.0",
"resolved": "https://registry.npmjs.org/@lucide/icons/-/icons-1.59.0.tgz",
"integrity": "sha512-olGAqAKEtttGTAyjz+pmKEppYLu7X0jUtpLwt6JBvl9TMZaHnG7q6ECWVXh7rvt1ZLAsRASsuvtE4JtWU0Q2Ow==",
"version": "1.61.0",
"resolved": "https://registry.npmjs.org/@lucide/icons/-/icons-1.61.0.tgz",
"integrity": "sha512-YNm6W6kz5/zlr63oNCJJY3BjSYU01L6HbGvOkKH3NNmu2vKGkCxSHLPx55FdSU9aY7jwwdvCfgU2d5bq4mNLig==",
"license": "ISC"
},
"node_modules/@oxc-project/types": {
"version": "0.152.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz",
"integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==",
"version": "0.155.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz",
"integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==",
"dev": true,
"license": "MIT",
"funding": {
@@ -570,19 +570,20 @@
}
},
"node_modules/@posthog/browser-common": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.5.tgz",
"integrity": "sha512-oXnBgZvM8UQ84IcBLfGP0WSGd9rA0PCGR37H8Pw0NszJRwxwbJnFXIUih0pvI/ze2eGmzkIEC9i3SwC+bMAgjQ==",
"version": "0.9.11",
"resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.11.tgz",
"integrity": "sha512-ZCwJIgiDBRoEAlrlYxaIIIAUfITmBDpWDQJp+IZmAcTyvJka5/ebEKu8uV+jSSA/RhpvDKKte1WmhLUbkIWQjA==",
"license": "MIT",
"dependencies": {
"@posthog/core": "^1.59.1",
"@posthog/types": "^1.416.0"
"@posthog/core": "^1.60.1",
"@posthog/types": "^1.416.0",
"preact": "^10.29.3"
}
},
"node_modules/@posthog/core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.59.1.tgz",
"integrity": "sha512-P39YgzQVDh6oz6wAzQXEnoHgTYpM7gifonmVKq1scAJco79h31XmmNVurRq9tTmmyIrr1uX8FHT0riCsyfV9AQ==",
"version": "1.60.1",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.60.1.tgz",
"integrity": "sha512-UpswaG2957O6Ywfx1aCeI8BbXcfoKduKwq1ytBml80AQuAmgK672OmhDcldA7wg4vGP49LpDTgxcNbLvZwQOqw==",
"license": "MIT",
"dependencies": {
"@posthog/types": "^1.416.0"
@@ -595,9 +596,9 @@
"license": "MIT"
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz",
"integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz",
"integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==",
"cpu": [
"arm"
],
@@ -612,9 +613,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz",
"integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz",
"integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==",
"cpu": [
"arm64"
],
@@ -629,9 +630,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz",
"integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz",
"integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==",
"cpu": [
"arm64"
],
@@ -646,9 +647,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz",
"integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz",
"integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==",
"cpu": [
"x64"
],
@@ -663,9 +664,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz",
"integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz",
"integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==",
"cpu": [
"x64"
],
@@ -680,9 +681,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz",
"integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz",
"integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==",
"cpu": [
"arm"
],
@@ -697,13 +698,16 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz",
"integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz",
"integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -714,13 +718,16 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz",
"integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz",
"integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -731,13 +738,16 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz",
"integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz",
"integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -748,13 +758,16 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz",
"integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz",
"integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -765,13 +778,16 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz",
"integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz",
"integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -782,13 +798,16 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz",
"integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz",
"integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -799,9 +818,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz",
"integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz",
"integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==",
"cpu": [
"arm64"
],
@@ -816,9 +835,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz",
"integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz",
"integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==",
"cpu": [
"arm64"
],
@@ -833,9 +852,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz",
"integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz",
"integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==",
"cpu": [
"x64"
],
@@ -1350,6 +1369,72 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.11.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.11.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
@@ -2627,9 +2712,9 @@
}
},
"node_modules/lucide-react": {
"version": "1.59.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.59.0.tgz",
"integrity": "sha512-5qgMyyCPU6OvnKpLS2t693+ZtiqA8oaSweWoUows0BLFKFf6V75m4+CptpmohZHpD5IuOVIBXHBBiv2vpLJmtw==",
"version": "1.61.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.61.0.tgz",
"integrity": "sha512-v2SQPPPNjnr+BlhL9Spuzmo5yR+hLkftfuE4E3jaxdNPgSH0hJKPcLJBP7yAta87jQNPsSERS59PJjCJ/CorBA==",
"license": "ISC",
"peerDependencies": {
"@types/react": "*",
@@ -2836,13 +2921,13 @@
}
},
"node_modules/posthog-js": {
"version": "1.438.12",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.438.12.tgz",
"integrity": "sha512-hjhvrtqbk6pFlbtNEGk0FrWKKFdeXe/WWA/LppN0f+uDoHKR9/SZJOxUHSVCGYbGTJvNRoDjjQvZuTcq4oKSlg==",
"version": "1.438.21",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.438.21.tgz",
"integrity": "sha512-KFrb9SOhyWg65OYOJ6dIxog26AqyD3ETj5pin/dWBcGll3pqa3FnqoJxq5IkHC6dtms/YjRCj6cT06u9j+EkoA==",
"license": "(Apache-2.0 AND MIT)",
"dependencies": {
"@posthog/browser-common": "^0.9.5",
"@posthog/core": "^1.59.1",
"@posthog/browser-common": "^0.9.11",
"@posthog/core": "^1.60.1",
"@posthog/types": "^1.416.0",
"core-js": "^3.49.0",
"dompurify": "^3.4.13",
@@ -2950,13 +3035,13 @@
}
},
"node_modules/rolldown": {
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz",
"integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz",
"integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.152.0",
"@oxc-project/types": "=0.155.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -2966,21 +3051,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.12",
"@rolldown/binding-android-arm64": "1.2.12",
"@rolldown/binding-darwin-arm64": "1.2.12",
"@rolldown/binding-darwin-x64": "1.2.12",
"@rolldown/binding-freebsd-x64": "1.2.12",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.12",
"@rolldown/binding-linux-arm64-gnu": "1.2.12",
"@rolldown/binding-linux-arm64-musl": "1.2.12",
"@rolldown/binding-linux-ppc64-gnu": "1.2.12",
"@rolldown/binding-linux-s390x-gnu": "1.2.12",
"@rolldown/binding-linux-x64-gnu": "1.2.12",
"@rolldown/binding-linux-x64-musl": "1.2.12",
"@rolldown/binding-openharmony-arm64": "1.2.12",
"@rolldown/binding-win32-arm64-msvc": "1.2.12",
"@rolldown/binding-win32-x64-msvc": "1.2.12"
"@rolldown/binding-android-arm-eabi": "1.2.15",
"@rolldown/binding-android-arm64": "1.2.15",
"@rolldown/binding-darwin-arm64": "1.2.15",
"@rolldown/binding-darwin-x64": "1.2.15",
"@rolldown/binding-freebsd-x64": "1.2.15",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.15",
"@rolldown/binding-linux-arm64-gnu": "1.2.15",
"@rolldown/binding-linux-arm64-musl": "1.2.15",
"@rolldown/binding-linux-ppc64-gnu": "1.2.15",
"@rolldown/binding-linux-s390x-gnu": "1.2.15",
"@rolldown/binding-linux-x64-gnu": "1.2.15",
"@rolldown/binding-linux-x64-musl": "1.2.15",
"@rolldown/binding-openharmony-arm64": "1.2.15",
"@rolldown/binding-win32-arm64-msvc": "1.2.15",
"@rolldown/binding-win32-x64-msvc": "1.2.15"
}
},
"node_modules/scheduler": {
@@ -3149,16 +3234,16 @@
}
},
"node_modules/vite": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz",
"integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.29",
"rolldown": "~1.2.12",
"postcss": "^8.5.30",
"rolldown": "~1.2.14",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -3175,7 +3260,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.7.1",
"@vitejs/devtools": "^0.7.1 || ^0.8.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
+4 -4
View File
@@ -12,10 +12,10 @@
"test:watch": "vitest"
},
"dependencies": {
"@lucide/icons": "^1.59.0",
"@lucide/icons": "^1.61.0",
"blurhash": "^2.0.5",
"lucide-react": "^1.59.0",
"posthog-js": "^1.438.12",
"lucide-react": "^1.61.0",
"posthog-js": "^1.438.21",
"react": "^19.3.1",
"react-dom": "^19.3.1"
},
@@ -30,7 +30,7 @@
"eslint-plugin-react-refresh": "^0.5.7",
"globals": "^17.13.0",
"tailwindcss": "^4.3.3",
"vite": "^8.3.4",
"vite": "^8.3.5",
"vitest": "^5.0.5"
}
}
+7
View File
@@ -119,3 +119,10 @@
body:has(.expandable-search.expanded) .family-tabs { top: 80px; max-width: calc(100vw - 20px); }
.family-tab { font-size: 14px; padding: 6px 12px 7px; }
}
/* Trait d'union entre deux onglets voisins quand le second part du premier (planche A, variante 2) */
.family-tabs { gap: 0; }
.family-tab + .family-tab:not([style]) { margin-left: 6px; }
.family-tab--suite { position: relative; }
.family-tab--suite::before { content: ""; position: absolute; right: 100%; top: 50%; width: 10px; height: 1.3px; transform: translateY(-50%); background: #E3A590; pointer-events: none; }
.family-tab--trait-on::before { background: #D2694A; }
+73 -5
View File
@@ -50,6 +50,33 @@ const centerX = (node) => node.x + node.width / 2
const easeOutQuint = (t) => 1 - (1 - t) ** 5
const prefersReducedMotion = () => typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
// Barre des familles rangée comme l'arbre (Patch, 28/10, planche A variante 2 « le trait d'union ») : chaque
// famille juste après celle par laquelle on y entre, la famille de départ en tête. Les onglets d'une même branche
// (sous-arbre d'un enfant direct de la famille de départ) se serrent à 10 px, 16 px entre les branches ; un trait
// relie deux voisins quand le second part du premier (jamais vers la famille de départ, sa place en tête suffit).
// Le trait qui mène à l'onglet ouvert est terracotta, les autres couleur fil.
function barTabs(tabs, activeKey) {
if (!tabs.length) return []
const root = tabs[0]
const kids = new Map()
for (const tb of tabs.slice(1)) { const pk = tabs.some((x) => x.key === tb.parentKey) ? tb.parentKey : root.key; if (!kids.has(pk)) kids.set(pk, []); kids.get(pk).push(tb) }
const order = []
const visit = (tb) => { order.push(tb); for (const c of kids.get(tb.key) || []) visit(c) }
visit(root)
for (const tb of tabs) if (!order.includes(tb)) order.push(tb)
const byKey = new Map(tabs.map((tb) => [tb.key, tb]))
const pathTo = (tb) => { const out = []; for (let t = tb; t; t = byKey.get(t.parentKey)) out.unshift(t.key); return out }
const branchOf = (tb) => { const p = pathTo(tb); return p[1] || p[0] }
const active = byKey.get(activeKey) || root
const lit = new Set(pathTo(active))
return order.map((tb, i) => {
const prev = order[i - 1]
const gap = i === 0 ? 0 : (prev !== root && branchOf(tb) === branchOf(prev) ? 10 : 16)
const trait = i > 0 && prev !== root && tb.parentKey === prev.key ? (lit.has(tb.key) ? 'on' : 'off') : null
return { tb, gap, trait }
})
}
const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlightIds = [], graphRevision = 0, annotationHandlers = null, transformReadRef = null, zoomApiRef = null, focusPersonId = null, assetRevision = 0, reorderEnabled = false, onReorderSiblings = null, familyTabs = true, fullNames = false, annotationViewRef = null, onAnnotationsAnchored = null, selfPersonId = null, ghosts = null, initialFamilyKey = null, onFamilyChange = null }) => {
const canvasRef = useRef(null)
const layoutDataRef = useRef(null)
@@ -94,7 +121,32 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
setActiveKey(focusPersonId != null ? String(focusPersonId) : null)
}, [focusPersonId])
// Onglet choisi par la personne (barre, badge, recherche) : le lien d'arrivée ne s'impose plus
const chooseTab = useCallback((key) => { linkFamilyRef.current = null; pendingFamilyRef.current = null; frameOnRef.current = null; setActiveKey(key) }, [])
// Repère du changement d'onglet (Patch, 28/10 : « on perd un peu rapidement le fil ») : la personne qui relie
// les deux familles garde sa place et son zoom à l'écran, seules les familles autour changent
const anchorRef = useRef(null)
const linkPeople = (key) => {
const list = tabsCacheRef.current.tabs
const target = list.find((tb) => tb.key === String(key)), cur = activeTabRef.current || list[0]
return [target?.referenceId, ...(target?.hinges || []), cur?.referenceId, ...(cur?.hinges || [])].filter((x) => x != null).map(String)
}
const rememberAnchor = (key) => {
const layout = layoutDataRef.current, t = transformRef.current
if (!layout || !t) return
const ids = linkPeople(key)
const node = layout.children.find((n) => n._type === 'person' && ids.includes(String(n._data?.id)))
anchorRef.current = node ? { id: String(node._data.id), sx: (node.x + node.width / 2) * t.scale + t.x, sy: (node.y + node.height / 2) * t.scale + t.y, scale: t.scale } : null
}
const chooseTab = useCallback((key) => { rememberAnchor(key); linkFamilyRef.current = null; pendingFamilyRef.current = null; frameOnRef.current = null; setActiveKey(key) }, [])
// Survol d'un onglet : la personne par qui l'on y passe s'allume dans l'arbre affiché, sans texte (Patch : « que ça les suggère »)
const hoverTab = (key) => {
const layout = layoutDataRef.current
if (!layout) return
const present = new Set(layout.children.filter((n) => n._type === 'person').map((n) => String(n._data?.id)))
const ids = linkPeople(key).filter((id) => present.has(id))
searchHighlightSetRef.current = new Set([...searchHighlightIdsRef.current.map((id) => `p-${id}`), ...ids.map((id) => `p-${id}`)])
needsRedrawRef.current = true
}
const leaveTab = () => { searchHighlightSetRef.current = new Set(searchHighlightIdsRef.current.map((id) => `p-${id}`)); needsRedrawRef.current = true }
// Lu au calcul du cadrage et par le réordonnancement : la référence de l'arbre affiché
const focusPersonIdRef = useRef(focusPersonId)
// La barre défile quand les onglets débordent : l'onglet ouvert se met en vue (sans animation) et les bords
@@ -630,7 +682,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
const parent = tabsCacheRef.current.tabs.find((tb) => tb.key === tab.parentKey)
for (const h of (tab.hinges || [tab.referenceId])) {
const node = result.children.find((n) => n.id === `p-${h}`)
if (parent && node) node._familyTab = { anchorId: node.id, label: parent.label, count: parent.count, tabKey: parent.key, place: 'above' }
if (parent && node) node._familyTab = { anchorId: node.id, label: parent.label, count: parent.count, tabKey: parent.key, place: 'above', back: true }
}
}
@@ -684,6 +736,15 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
offsetY = cssH * (cssW < 768 ? 0.4 : 0.42) - (focusNode.y + focusNode.height / 2) * scale
}
const anc = anchorRef.current
anchorRef.current = null
const ancNode = anc && personNodes.find((n) => String(n._data?.id) === anc.id)
if (ancNode) {
scale = anc.scale
offsetX = anc.sx - (ancNode.x + ancNode.width / 2) * scale
offsetY = anc.sy - (ancNode.y + ancNode.height / 2) * scale
}
const init = { x: offsetX, y: offsetY, scale }
initialTransformRef.current = init
transformRef.current = init
@@ -803,7 +864,9 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
}, [selectedPersonId])
// Sync searchHighlightIds
const searchHighlightIdsRef = useRef(searchHighlightIds)
useEffect(() => {
searchHighlightIdsRef.current = searchHighlightIds
searchHighlightSetRef.current = new Set(searchHighlightIds.map(id => `p-${id}`))
}, [searchHighlightIds])
@@ -1259,18 +1322,23 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
)}
{familyTabs && tabs.length > 1 && (
<nav ref={tabsBarRef} onScroll={updateTabsEdges} className={`family-tabs${annotationHandlers ? ' family-tabs--lowered' : ''}${tabsEdges}`} aria-label={tr('Familles')}>
{tabs.map((tb) => {
{barTabs(tabs, activeKey).map(({ tb, gap, trait }) => {
const active = tb.key === (tabs.some((x) => x.key === activeKey) ? activeKey : tabs[0].key)
return (
<button
key={tb.key}
type="button"
className={`family-tab${active ? ' is-active' : ''}`}
style={gap ? { marginLeft: gap } : undefined}
className={`family-tab${active ? ' is-active' : ''}${trait ? ` family-tab--suite family-tab--trait-${trait}` : ''}`}
aria-current={active ? 'page' : undefined}
onClick={() => chooseTab(tb.key)}
onMouseEnter={() => { if (!active) hoverTab(tb.key) }}
onMouseLeave={leaveTab}
onFocus={() => { if (!active) hoverTab(tb.key) }}
onBlur={leaveTab}
>
<span className="family-tab-name">{tb.label ? tr('Famille {name}', { name: tb.label }) : tr('Famille')}</span>
<span className="family-tab-count">{tb.count}</span>
<span className="family-tab-count" title={tr('{n} personnes', { n: tb.count })}>{tb.count}</span>
</button>
)
})}
+3 -2
View File
@@ -18,6 +18,7 @@ import { t, dateLocale } from '../i18n/index.js'
import { formatPlace } from '../utils/place.js'
import { firstNameLabel, fullNameLabel, isFirstNameUnknown } from '../utils/personName.js'
import { isYearOnly } from '../utils/dateInput.js'
import { ageInYears } from '../utils/age.js'
import DateField from './DateField.jsx'
import { runOnce } from '../utils/saveOnLeave.js'
import { normalizeSex, SEX_CHOICES } from '../utils/personSex.js'
@@ -448,9 +449,9 @@ const PersonCard = ({
const hasDeathYear = Number.isFinite(deathYearNumber)
const age = hasBirthYear
? person.isAlive
? new Date().getFullYear() - birthYearNumber
? ageInYears(person.birthDate ?? birthYearNumber)
: hasDeathYear
? deathYearNumber - birthYearNumber
? ageInYears(person.birthDate ?? birthYearNumber, person.deathDate ?? deathYearNumber)
: null
: null
+32 -6
View File
@@ -127,6 +127,20 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
const result = computeLayout(graphData, referenceId)
const nodes = result.children
const byId = new Map(nodes.map(n => [n.id, n]))
// Nom d'une famille : celui que portent le plus ses membres (nom de naissance, sinon nom), pas celui de la seule
// personne de référence. Ex. : 175 personnes dont 5 portent le nom de la référence. Égalité : le nom
// de secours (celui de la référence) s'il est parmi les premiers, sinon le premier rencontré.
const nomMajoritaire = (ids, secours = '') => {
const n = new Map()
for (const id of ids) {
const d = byId.get(id)?._type === 'person' ? byId.get(id)._data : null
const nom = (d?.birthName || d?.lastName || '').trim()
if (nom) n.set(nom, (n.get(nom) || 0) + 1)
}
let best = '', max = 0
for (const [nom, c] of n) if (c > max) { best = nom; max = c }
return secours && n.get(secours) === max ? secours : (best || secours)
}
// Chaînes de couples (composantes d'une rangée) telles que computeLayout les a ordonnées
const chainOf = new Map()
@@ -416,8 +430,8 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
unplace(block.members)
forgetExternal(snap.externalBefore, snap.externalAfter)
const A = byId.get(id)
const label = A._data?.birthName || byId.get(pcPersons[0])?._data?.lastName || A._data?.lastName || ''
const members = new Set([...extra, ...nested].filter(m => !keep.has(m)))
const label = nomMajoritaire(members, A._data?.birthName || byId.get(pcPersons[0])?._data?.lastName || A._data?.lastName || '')
registerTab({ anchorId: pcPersons[0], pu: parentUnionOf.get(pcPersons[0]) || pu, label, count: members.size, members, hinges: pcPersons.slice(1), hingeEdges: [] })
for (const x of pcPersons) byId.get(x)._familyTab = { anchorId: pcPersons[0], label, count: members.size, tabKey: pcPersons[0].replace(/^p-/, ''), place: 'above' }
const saved = anchor
@@ -461,7 +475,7 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
anchor = saved
if (block.width > FAMILY_TAB_PX && !familyTabs) {
const A = byId.get(id)
bigFamilies.push({ anchorId: id, label: A._data?.birthName || A._data?.lastName || '', count: [...block.members].filter(m => byId.get(m)?._type === 'person').length })
bigFamilies.push({ anchorId: id, label: nomMajoritaire(block.members, A._data?.birthName || A._data?.lastName || ''), count: [...block.members].filter(m => byId.get(m)?._type === 'person').length })
}
if (block.width > FAMILY_TAB_PX && familyTabs) {
// Trop large pour cet arbre : la famille devient un onglet et n'est pas dessinée ici. Sa construction ne
@@ -474,8 +488,8 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
forgetExternal(externalBefore, new Set(externalEdges))
const A = byId.get(id)
const partners = [byId.get(pu)?._data?.p1Key, byId.get(pu)?._data?.p2Key].map(k => byId.get(k)?._data).filter(Boolean)
const label = A._data?.birthName || partners.find(d => d.lastName)?.lastName || A._data?.lastName || ''
const members = new Set([...[...block.members].filter(m => byId.get(m)?._type === 'person'), ...nested])
const label = nomMajoritaire(members, A._data?.birthName || partners.find(d => d.lastName)?.lastName || A._data?.lastName || '')
registerTab({ anchorId: id, pu, label, count: members.size, members, hinges: [], hingeEdges: [[pu, id]] })
A._familyTab = { anchorId: id, label, count: members.size, tabKey: id.replace(/^p-/, ''), place: 'above' }
continue
@@ -818,7 +832,7 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
// la barre d'onglets ; un onglet est le même arbre recalculé avec le conjoint comme référence.
const drawn = nodes.filter(n => n._type === 'person')
result.families = [
{ id: 'main', label: referenceNode?._data?.lastName || drawn[0]?._data?.lastName || '', count: drawn.length, referenceId: result.referenceId ?? null },
{ id: 'main', label: nomMajoritaire(drawn.map(n => n.id), referenceNode?._data?.lastName || drawn[0]?._data?.lastName || ''), count: drawn.length, referenceId: result.referenceId ?? null },
...tabs.map(tb => ({ id: tb.anchorId, anchorId: tb.anchorId, label: tb.label, count: tb.count, members: [...tb.members].map(m => m.replace(/^p-/, '')), hinges: [tb.anchorId, ...tb.hinges].map(m => m.replace(/^p-/, '')) })),
...bigFamilies.map(f => ({ id: f.anchorId, anchorId: f.anchorId, label: f.label, count: f.count, members: [], drawn: true })),
]
@@ -925,14 +939,26 @@ export function familyTabsOf(graphData = null, referenceId = null) {
// Deux onglets au même nom (homonymes, ou une branche sortie de sa propre lignée) : sauf le plus grand, chacun
// prend les prénoms de sa charnière, ceux que porte la pastille qui y mène (Patch, 20/10 : « plusieurs fois
// les mêmes familles »)
const firstName = new Map((graphData?.persons || globalPersons).map(p => [String(p.id), p.firstName]))
// 28/10 (Patch : « ptet le Joseph et Marie ») : l'onglet le plus petit prend d'abord le nom suivant le plus
// porté chez lui et pas encore pris par un autre onglet ; les prénoms de la charnière ne servent qu'en dernier recours.
const persons = graphData?.persons || globalPersons
const firstName = new Map(persons.map(p => [String(p.id), p.firstName]))
const familyName = new Map(persons.map(p => [String(p.id), (p.birthName || p.lastName || '').trim()]))
const namesByCount = (tb) => {
const n = new Map()
for (const id of tb.personIds || []) { const nom = familyName.get(String(id).replace(/^p-/, '')); if (nom) n.set(nom, (n.get(nom) || 0) + 1) }
return [...n].sort((x, y) => y[1] - x[1]).map(([nom]) => nom)
}
const byLabel = new Map()
for (const tb of tabs) { if (!byLabel.has(tb.label)) byLabel.set(tb.label, []); byLabel.get(tb.label).push(tb) }
const taken = new Set(tabs.map(tb => tb.label))
for (const group of byLabel.values()) {
if (group.length < 2) continue
const biggest = group.reduce((a, b) => (b.count > a.count ? b : a))
for (const tb of group) {
for (const tb of [...group].sort((a, b) => b.count - a.count)) {
if (tb === biggest) continue
const next = namesByCount(tb).find(nom => !taken.has(nom))
if (next) { tb.label = next; taken.add(next); continue }
const names = [...new Set((tb.hinges || [tb.referenceId]).map(id => firstName.get(String(id))).filter(Boolean))].slice(0, 2)
if (names.length) tb.label = `${tb.label} · ${names.join(' et ')}`
}
+4 -1
View File
@@ -697,7 +697,10 @@ function drawPersonNode(ctx, node, pos, nodeProgress, {
// la charnière peut avoir des enfants dessous, jamais rien au-dessus que le fil de ses parents.
if (withDetail && node._familyTab) {
const tab = node._familyTab
const text = `${tab.label ? t('Famille {name}', { name: tab.label }) : t('Famille')} · ${tab.count} ›`
// Retour vers la famille d'où l'on vient : même place que la pastille de l'aller (la charnière ne bouge pas),
// flèche retournée (Patch, 28/10, planche D)
const name = tab.label ? t('Famille {name}', { name: tab.label }) : t('Famille')
const text = tab.back ? `‹ ${name} · ${tab.count}` : `${name} · ${tab.count} ›`
const py = cy - PERSON_R * personScale - RENVOI_CHILD_GAP
familyPills.push({ text, cx, cy: py, anchorId: tab.anchorId, tabKey: tab.tabKey, alpha: nodeProgress * labelAlpha })
}
+1
View File
@@ -1,6 +1,7 @@
// Traductions anglaises, clé = texte français exact passé à t().
export default {
'Famille {name}': 'The {name} family',
'{n} personnes': '{n} people',
'Famille': 'Family',
'Familles': 'Families',
'Affichage': 'Display',
+26
View File
@@ -0,0 +1,26 @@
import { isYearOnly } from './dateInput.js'
// Âge en années révolues (#198) : l'anniversaire compte une fois passé, pas dès le 1er janvier.
// Une année seule (rangée au 1er janvier) n'a pas de jour : on garde la différence des années.
const partsOf = (value) => {
if (value === null || value === undefined || value === '') return null
if (Number.isFinite(value)) return { year: Number(value), md: null }
const text = String(value).slice(0, 10)
const m = text.match(/^(\d{4})(?:-(\d{2})-(\d{2}))?/)
if (!m) return null
return { year: Number(m[1]), md: m[2] && !isYearOnly(text) ? `${m[2]}-${m[3]}` : null }
}
const today = () => {
const now = new Date()
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
}
// end absent = aujourd'hui ; null si la naissance (ou la fin donnée) manque
export function ageInYears(birth, end = today()) {
const from = partsOf(birth)
const to = partsOf(end)
if (!from || !to) return null
const age = to.year - from.year
return from.md && to.md && to.md < from.md ? age - 1 : age
}
+25
View File
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { ageInYears } from './age.js'
describe('ageInYears', () => {
it("ne compte l'anniversaire qu'une fois passé (#198)", () => {
expect(ageInYears('1980-12-15', '2026-10-29')).toBe(45)
expect(ageInYears('1980-12-15', '2026-12-15')).toBe(46)
expect(ageInYears('1980-03-02T00:00:00.000Z', '2026-10-29')).toBe(46)
})
it('garde la différence des années pour une année seule', () => {
expect(ageInYears('1980-01-01', '2026-10-29')).toBe(46)
expect(ageInYears(1980, '2026-10-29')).toBe(46)
expect(ageInYears('1920-05-10', 1995)).toBe(75)
})
it('calcule un âge au décès au jour près', () => {
expect(ageInYears('1920-05-10', '1995-05-09T00:00:00.000Z')).toBe(74)
})
it('rend null sans naissance', () => {
expect(ageInYears(null)).toBe(null)
expect(ageInYears('', '2026-10-29')).toBe(null)
})
})
+11 -5
View File
@@ -8,6 +8,7 @@
import { t, dateLocale } from '../i18n/index.js'
import { firstNameLabel } from './personName.js'
import { formatPlace } from './place.js'
import { ageInYears } from './age.js'
const EVENT_LABELS = {
baptism: t('Baptême'),
@@ -90,7 +91,11 @@ export function describeDate(date, end, precision) {
}
}
const ageAt = (birthYear, year) => (birthYear && year && year > birthYear ? t('{age} ans', { age: year - birthYear }) : '')
// Âge à une date, au jour près quand les deux dates sont complètes (#198), sinon à l'année
const ageAt = (birth, end) => {
const age = ageInYears(birth, end || null)
return age > 0 ? t('{age} ans', { age }) : ''
}
const joinMeta = (...parts) => parts.filter(Boolean).join(' · ')
// Le prénom d'un proche, cliquable dans la frise : il ouvre sa fiche
@@ -123,6 +128,7 @@ export function buildLifeTimeline(person, family = {}, life = null) {
const lifeUnions = new Map((life?.unions || []).map((u) => [String(u.id), u]))
const events = life?.events || []
const birthYear = yearOf(person.birthDate) ?? person.birthYear ?? null
const birthDate = person.birthDate || birthYear
const deathYear = yearOf(person.deathDate) ?? person.deathYear ?? null
const lastYear = deathYear ?? new Date().getFullYear()
const within = (year) => year && (!birthYear || year >= birthYear) && year <= lastYear
@@ -165,14 +171,14 @@ export function buildLifeTimeline(person, family = {}, life = null) {
startDatePrecision: detail.startDatePrecision || null, startPlace: detail.startPlace || '',
},
meta: year
? joinMeta(place, detail.startDatePrecision ? describeDate(detail.startDate, null, detail.startDatePrecision) : '', ageAt(birthYear, year))
? joinMeta(place, detail.startDatePrecision ? describeDate(detail.startDate, null, detail.startDatePrecision) : '', ageAt(birthDate, detail.startDate || year))
: joinMeta(place, t('date inconnue')),
note: extra?.notes || '', sources: extra?.sources || '',
...milestoneFields(extra, { type: 'marriage', unionId: union.id }),
})
const hasEndEvent = events.some((e) => String(e.unionId) === String(union.id) && ['divorce', 'annulment'].includes(e.type))
if (union.endYear && !hasEndEvent) {
out.push({ key: `union-end-${union.id}`, year: union.endYear, order: 3, kind: 'minor', title: t('Fin de l’union avec {name}', { name: union.partner?.firstName || partnerName }), meta: ageAt(birthYear, union.endYear) })
out.push({ key: `union-end-${union.id}`, year: union.endYear, order: 3, kind: 'minor', title: t('Fin de l’union avec {name}', { name: union.partner?.firstName || partnerName }), meta: ageAt(birthDate, union.endYear) })
}
if (union.partner && within(yearOf(union.partner.deathDate) ?? union.partner.deathYear)) {
const year2 = yearOf(union.partner.deathDate) ?? union.partner.deathYear
@@ -190,7 +196,7 @@ export function buildLifeTimeline(person, family = {}, life = null) {
}
out.push({
key: `event-${event.id}`, year, order: 1, kind: 'minor', title,
meta: joinMeta(formatPlace(event.place || ''), year ? describeDate(event.date, event.dateEnd, event.datePrecision) : t('date inconnue'), ageAt(birthYear, year)),
meta: joinMeta(formatPlace(event.place || ''), year ? describeDate(event.date, event.dateEnd, event.datePrecision) : t('date inconnue'), ageAt(birthDate, event.date || year)),
note: event.notes || '', sources: event.sources || '',
mediaIds: event.mediaIds || [],
// Seuls les moments propres à la personne se modifient depuis l'onglet (pas ceux d'une union)
@@ -219,7 +225,7 @@ export function buildLifeTimeline(person, family = {}, life = null) {
out.push({
key: 'death', year: deathYear, order: 9, kind: 'mile',
title: place ? t('Décès à {place}', { place: place.split(',')[0] }) : t('Décès'),
meta: deathYear ? joinMeta(describeDate(person.deathDate, details.deathDateEnd, details.deathDatePrecision), ageAt(birthYear, deathYear)) : t('date inconnue'),
meta: deathYear ? joinMeta(describeDate(person.deathDate, details.deathDateEnd, details.deathDatePrecision), ageAt(birthDate, person.deathDate || deathYear)) : t('date inconnue'),
note: extra?.notes || '', sources: extra?.sources || '',
...milestoneFields(extra, { type: 'death' }),
})
+173 -88
View File
@@ -8,7 +8,7 @@
"name": "landing",
"version": "0.0.0",
"dependencies": {
"posthog-js": "^1.438.12",
"posthog-js": "^1.438.21",
"react": "^19.3.1",
"react-dom": "^19.3.1"
},
@@ -16,7 +16,7 @@
"@tailwindcss/vite": "^4.3.3",
"@vitejs/plugin-react": "^6.1.2",
"tailwindcss": "^4.3.3",
"vite": "^8.3.4"
"vite": "^8.3.5"
}
},
"node_modules/@jridgewell/gen-mapping": {
@@ -70,9 +70,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.153.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz",
"integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==",
"version": "0.155.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.155.0.tgz",
"integrity": "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==",
"dev": true,
"license": "MIT",
"funding": {
@@ -80,19 +80,20 @@
}
},
"node_modules/@posthog/browser-common": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.5.tgz",
"integrity": "sha512-oXnBgZvM8UQ84IcBLfGP0WSGd9rA0PCGR37H8Pw0NszJRwxwbJnFXIUih0pvI/ze2eGmzkIEC9i3SwC+bMAgjQ==",
"version": "0.9.11",
"resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.11.tgz",
"integrity": "sha512-ZCwJIgiDBRoEAlrlYxaIIIAUfITmBDpWDQJp+IZmAcTyvJka5/ebEKu8uV+jSSA/RhpvDKKte1WmhLUbkIWQjA==",
"license": "MIT",
"dependencies": {
"@posthog/core": "^1.59.1",
"@posthog/types": "^1.416.0"
"@posthog/core": "^1.60.1",
"@posthog/types": "^1.416.0",
"preact": "^10.29.3"
}
},
"node_modules/@posthog/core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.59.1.tgz",
"integrity": "sha512-P39YgzQVDh6oz6wAzQXEnoHgTYpM7gifonmVKq1scAJco79h31XmmNVurRq9tTmmyIrr1uX8FHT0riCsyfV9AQ==",
"version": "1.60.1",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.60.1.tgz",
"integrity": "sha512-UpswaG2957O6Ywfx1aCeI8BbXcfoKduKwq1ytBml80AQuAmgK672OmhDcldA7wg4vGP49LpDTgxcNbLvZwQOqw==",
"license": "MIT",
"dependencies": {
"@posthog/types": "^1.416.0"
@@ -105,9 +106,9 @@
"license": "MIT"
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz",
"integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.15.tgz",
"integrity": "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw==",
"cpu": [
"arm"
],
@@ -122,9 +123,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz",
"integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.15.tgz",
"integrity": "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg==",
"cpu": [
"arm64"
],
@@ -139,9 +140,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz",
"integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.15.tgz",
"integrity": "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA==",
"cpu": [
"arm64"
],
@@ -156,9 +157,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz",
"integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.15.tgz",
"integrity": "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A==",
"cpu": [
"x64"
],
@@ -173,9 +174,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz",
"integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.15.tgz",
"integrity": "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ==",
"cpu": [
"x64"
],
@@ -190,9 +191,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz",
"integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.15.tgz",
"integrity": "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ==",
"cpu": [
"arm"
],
@@ -207,13 +208,16 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz",
"integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.15.tgz",
"integrity": "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -224,13 +228,16 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz",
"integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.15.tgz",
"integrity": "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -241,13 +248,16 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz",
"integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.15.tgz",
"integrity": "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -258,13 +268,16 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz",
"integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.15.tgz",
"integrity": "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -275,13 +288,16 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz",
"integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.15.tgz",
"integrity": "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -292,13 +308,16 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz",
"integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.15.tgz",
"integrity": "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -309,9 +328,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz",
"integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.15.tgz",
"integrity": "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w==",
"cpu": [
"arm64"
],
@@ -326,9 +345,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz",
"integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.15.tgz",
"integrity": "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg==",
"cpu": [
"arm64"
],
@@ -343,9 +362,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz",
"integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.15.tgz",
"integrity": "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ==",
"cpu": [
"x64"
],
@@ -850,6 +869,72 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.11.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.11.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
@@ -1348,13 +1433,13 @@
}
},
"node_modules/posthog-js": {
"version": "1.438.12",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.438.12.tgz",
"integrity": "sha512-hjhvrtqbk6pFlbtNEGk0FrWKKFdeXe/WWA/LppN0f+uDoHKR9/SZJOxUHSVCGYbGTJvNRoDjjQvZuTcq4oKSlg==",
"version": "1.438.21",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.438.21.tgz",
"integrity": "sha512-KFrb9SOhyWg65OYOJ6dIxog26AqyD3ETj5pin/dWBcGll3pqa3FnqoJxq5IkHC6dtms/YjRCj6cT06u9j+EkoA==",
"license": "(Apache-2.0 AND MIT)",
"dependencies": {
"@posthog/browser-common": "^0.9.5",
"@posthog/core": "^1.59.1",
"@posthog/browser-common": "^0.9.11",
"@posthog/core": "^1.60.1",
"@posthog/types": "^1.416.0",
"core-js": "^3.49.0",
"dompurify": "^3.4.13",
@@ -1423,13 +1508,13 @@
}
},
"node_modules/rolldown": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz",
"integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==",
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.15.tgz",
"integrity": "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.153.0",
"@oxc-project/types": "=0.155.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -1439,21 +1524,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.13",
"@rolldown/binding-android-arm64": "1.2.13",
"@rolldown/binding-darwin-arm64": "1.2.13",
"@rolldown/binding-darwin-x64": "1.2.13",
"@rolldown/binding-freebsd-x64": "1.2.13",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.13",
"@rolldown/binding-linux-arm64-gnu": "1.2.13",
"@rolldown/binding-linux-arm64-musl": "1.2.13",
"@rolldown/binding-linux-ppc64-gnu": "1.2.13",
"@rolldown/binding-linux-s390x-gnu": "1.2.13",
"@rolldown/binding-linux-x64-gnu": "1.2.13",
"@rolldown/binding-linux-x64-musl": "1.2.13",
"@rolldown/binding-openharmony-arm64": "1.2.13",
"@rolldown/binding-win32-arm64-msvc": "1.2.13",
"@rolldown/binding-win32-x64-msvc": "1.2.13"
"@rolldown/binding-android-arm-eabi": "1.2.15",
"@rolldown/binding-android-arm64": "1.2.15",
"@rolldown/binding-darwin-arm64": "1.2.15",
"@rolldown/binding-darwin-x64": "1.2.15",
"@rolldown/binding-freebsd-x64": "1.2.15",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.15",
"@rolldown/binding-linux-arm64-gnu": "1.2.15",
"@rolldown/binding-linux-arm64-musl": "1.2.15",
"@rolldown/binding-linux-ppc64-gnu": "1.2.15",
"@rolldown/binding-linux-s390x-gnu": "1.2.15",
"@rolldown/binding-linux-x64-gnu": "1.2.15",
"@rolldown/binding-linux-x64-musl": "1.2.15",
"@rolldown/binding-openharmony-arm64": "1.2.15",
"@rolldown/binding-win32-arm64-msvc": "1.2.15",
"@rolldown/binding-win32-x64-msvc": "1.2.15"
}
},
"node_modules/scheduler": {
@@ -1542,16 +1627,16 @@
}
},
"node_modules/vite": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz",
"integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.29",
"rolldown": "~1.2.12",
"postcss": "^8.5.30",
"rolldown": "~1.2.14",
"tinyglobby": "^0.2.17"
},
"bin": {
@@ -1568,7 +1653,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.7.1",
"@vitejs/devtools": "^0.7.1 || ^0.8.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
+2 -2
View File
@@ -10,7 +10,7 @@
"test": "node --test"
},
"dependencies": {
"posthog-js": "^1.438.12",
"posthog-js": "^1.438.21",
"react": "^19.3.1",
"react-dom": "^19.3.1"
},
@@ -18,6 +18,6 @@
"@tailwindcss/vite": "^4.3.3",
"@vitejs/plugin-react": "^6.1.2",
"tailwindcss": "^4.3.3",
"vite": "^8.3.4"
"vite": "^8.3.5"
}
}