From 4916efa925d7026da2e6a0a3674922413326feab Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sat, 23 May 2026 17:20:41 +0530
Subject: [PATCH 01/95] fix: call drop (#2954)
* update matrix-js-sdk and improve call driver
* remove unused room param from call members hook
* downgrade matrix-js-sdk to latest stable release
---
package-lock.json | 85 ++++++++-----------
package.json | 2 +-
src/app/components/CallEmbedProvider.tsx | 18 ++--
src/app/features/call-status/CallStatus.tsx | 2 +-
src/app/features/call-status/LiveChip.tsx | 2 +-
src/app/features/call-status/MemberGlance.tsx | 4 +-
src/app/features/call/CallMemberCard.tsx | 17 ++--
src/app/features/call/CallView.tsx | 2 +-
src/app/features/room-nav/RoomNavItem.tsx | 2 +-
src/app/features/room/Room.tsx | 2 +-
src/app/features/room/RoomTimeline.tsx | 3 +-
src/app/hooks/useCall.ts | 42 +++++----
src/app/hooks/useCallEmbed.ts | 4 +-
src/app/hooks/useCallSpeakers.ts | 2 +-
src/app/plugins/call/CallWidgetDriver.ts | 55 +++++-------
15 files changed, 100 insertions(+), 142 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 054ee127..c30696cd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -43,7 +43,7 @@
"jotai": "2.6.0",
"linkify-react": "4.3.2",
"linkifyjs": "4.3.2",
- "matrix-js-sdk": "38.2.0",
+ "matrix-js-sdk": "41.5.0",
"matrix-widget-api": "1.16.1",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
@@ -2449,9 +2449,9 @@
}
},
"node_modules/@matrix-org/matrix-sdk-crypto-wasm": {
- "version": "15.3.0",
- "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-15.3.0.tgz",
- "integrity": "sha512-QyxHvncvkl7nf+tnn92PjQ54gMNV8hMSpiukiDgNrqF6IYwgySTlcSdkPYdw8QjZJ0NR6fnVrNzMec0OohM3wA==",
+ "version": "18.3.0",
+ "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-18.3.0.tgz",
+ "integrity": "sha512-9a4feyt8QLysARu7PHKaRWT+wcCd+IYH074LXp9QK5WqfN4zUXueRhiSSMNT18Bm+8q3sBR/4zxDxOSDR0M8Kg==",
"license": "Apache-2.0",
"engines": {
"node": ">= 18"
@@ -5684,12 +5684,6 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
- "node_modules/@types/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
- "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
- "license": "MIT"
- },
"node_modules/@types/sanitize-html": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.16.1.tgz",
@@ -10808,6 +10802,18 @@
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
"dev": true
},
+ "node_modules/is-network-error": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz",
+ "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -12105,43 +12111,29 @@
"license": "Apache-2.0"
},
"node_modules/matrix-js-sdk": {
- "version": "38.2.0",
- "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-38.2.0.tgz",
- "integrity": "sha512-R3jzK8rDGi/3OXOax8jFFyxblCG9KTT5yuXAbvnZCGcpTm8lZ4mHQAn5UydVD8qiyUMNMpaaMd6/k7N+5I/yaQ==",
+ "version": "41.5.0",
+ "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-41.5.0.tgz",
+ "integrity": "sha512-CK3h+qQJ4wkVEUgEWc5MdLjccXyiFqncCC53P+auqOhnX2U6tAFsRfnbML1QQiKIsFMzqTrAnF/4a5LUUOIeXg==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.12.5",
- "@matrix-org/matrix-sdk-crypto-wasm": "^15.1.0",
+ "@matrix-org/matrix-sdk-crypto-wasm": "^18.2.0",
"another-json": "^0.2.0",
"bs58": "^6.0.0",
"content-type": "^1.0.4",
"jwt-decode": "^4.0.0",
"loglevel": "^1.9.2",
"matrix-events-sdk": "0.0.1",
- "matrix-widget-api": "^1.10.0",
+ "matrix-widget-api": "^1.16.1",
"oidc-client-ts": "^3.0.1",
- "p-retry": "4",
- "sdp-transform": "^2.14.1",
- "unhomoglyph": "^1.0.6",
- "uuid": "11"
+ "p-retry": "8",
+ "sdp-transform": "^3.0.0",
+ "unhomoglyph": "^1.0.6"
},
"engines": {
"node": ">=22.0.0"
}
},
- "node_modules/matrix-js-sdk/node_modules/uuid": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
- "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist/esm/bin/uuid"
- }
- },
"node_modules/matrix-widget-api": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.16.1.tgz",
@@ -14929,16 +14921,18 @@
}
},
"node_modules/p-retry": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
- "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz",
+ "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==",
"license": "MIT",
"dependencies": {
- "@types/retry": "0.12.0",
- "retry": "^0.13.1"
+ "is-network-error": "^1.3.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-timeout": {
@@ -16020,15 +16014,6 @@
"node": ">=8"
}
},
- "node_modules/retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -16254,9 +16239,9 @@
}
},
"node_modules/sdp-transform": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz",
- "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz",
+ "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==",
"license": "MIT",
"bin": {
"sdp-verify": "checker.js"
diff --git a/package.json b/package.json
index 54fff09e..e8e03608 100644
--- a/package.json
+++ b/package.json
@@ -96,7 +96,7 @@
"jotai": "2.6.0",
"linkify-react": "4.3.2",
"linkifyjs": "4.3.2",
- "matrix-js-sdk": "38.2.0",
+ "matrix-js-sdk": "41.5.0",
"matrix-widget-api": "1.16.1",
"millify": "6.1.0",
"pdfjs-dist": "4.2.67",
diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx
index 650cf057..94784e11 100644
--- a/src/app/components/CallEmbedProvider.tsx
+++ b/src/app/components/CallEmbedProvider.tsx
@@ -1,7 +1,6 @@
/* eslint-disable jsx-a11y/media-has-caption */
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { useAtomValue, useSetAtom } from 'jotai';
-import { MatrixRTCSession } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession';
import FocusTrap from 'focus-trap-react';
import {
Avatar,
@@ -93,12 +92,14 @@ function IncomingCall({ dm, info, onIgnore, onAnswer, onReject }: IncomingCallPr
const session = useCallSession(room);
useCallMembersChange(
session,
- useCallback(() => {
- const members = MatrixRTCSession.sessionMembershipsForRoom(room, session.sessionDescription);
- if (members.length === 0) {
- onIgnore();
- }
- }, [room, session, onIgnore])
+ useCallback(
+ (members) => {
+ if (members.length === 0) {
+ onIgnore();
+ }
+ },
+ [onIgnore]
+ )
);
const playSound = useCallback(() => {
@@ -264,7 +265,8 @@ function IncomingCallListener({ callEmbed, joined }: IncomingCallListenerProps)
const refEventId = relation?.event_id;
const mention =
- content['m.mentions'].room || content['m.mentions'].user_ids?.includes(mx.getSafeUserId());
+ content['m.mentions']?.room ||
+ content['m.mentions']?.user_ids?.includes(mx.getSafeUserId());
if (!sender || !refEventId || !mention || Date.now() >= senderTs + lifetime) {
return;
}
diff --git a/src/app/features/call-status/CallStatus.tsx b/src/app/features/call-status/CallStatus.tsx
index 1d30d1b4..1ff3b837 100644
--- a/src/app/features/call-status/CallStatus.tsx
+++ b/src/app/features/call-status/CallStatus.tsx
@@ -22,7 +22,7 @@ export function CallStatus({ callEmbed }: CallStatusProps) {
const { room } = callEmbed;
const callSession = useCallSession(room);
- const callMembers = useCallMembers(room, callSession);
+ const callMembers = useCallMembers(callSession);
const screenSize = useScreenSize();
const callJoined = useCallJoined(callEmbed);
const speakers = useCallSpeakers(callEmbed);
diff --git a/src/app/features/call-status/LiveChip.tsx b/src/app/features/call-status/LiveChip.tsx
index a5d00a55..32df3289 100644
--- a/src/app/features/call-status/LiveChip.tsx
+++ b/src/app/features/call-status/LiveChip.tsx
@@ -82,7 +82,7 @@ export function LiveChip({ count, room, members }: LiveChipProps) {
return (
-A Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have an instant messaging application that is easy on people and has a modern touch.
+A Matrix client focusing primarily on simple, elegant and secure interface.
+The main goal is to have an instant messaging application that is easy on
+people and has a modern touch.
- [Roadmap](https://github.com/orgs/cinnyapp/projects/1)
- [Contributing](./CONTRIBUTING.md)
> [!IMPORTANT]
-We are currently in the [process of replacing the matrix-js-sdk](https://github.com/cinnyapp/cinny/issues/257#issuecomment-3714406704) with our own SDK. As a result, we will not be accepting any pull requests until further notice.
-Thank you for your understanding.
+We are currently in the process of [replacing] the matrix-js-sdk with our
+own SDK. As a result, we will not be accepting any pull requests until
+further notice. Thank you for your understanding.
+
+[replacing]: https://github.com/cinnyapp/cinny/issues/257#issuecomment-3714406704
## Getting started
-The web app is available at [app.cinny.in](https://app.cinny.in/) and gets updated on each new release. The `dev` branch is continuously deployed at [dev.cinny.in](https://dev.cinny.in) but keep in mind that it could have things broken.
+The web app is available at [app.cinny.in] and gets updated on each new
+release. The `dev` branch is continuously deployed at [dev.cinny.in]
+but keep in mind that it could have things broken.
-You can also download our desktop app from the [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
+You can also download our desktop app from the [cinny-desktop repository].
+
+[app.cinny.in]: https://app.cinny.in
+[dev.cinny.in]: https://dev.cinny.in
+[cinny-desktop repository]: https://github.com/cinnyapp/cinny-desktop
+
+## Contributing and Developing
+
+If you have any ideas, issues, etc. regarding Cinny, or would like to
+contribute to Cinny through pull requests, please check out our
+["Contributing to Cinny"](CONTRIBUTING.md) document. Those who would like
+to get involved with Cinny's development as well should also read the
+["Developing Cinny"](HACKING.md) document for more technical details.
## Self-hosting
-To host Cinny on your own, simply download the tarball from [GitHub releases](https://github.com/cinnyapp/cinny/releases/latest), and serve the files from `dist/` using your preferred webserver. Alternatively, you can just pull the docker image from [DockerHub](https://hub.docker.com/r/ajbura/cinny) or [GitHub Container Registry](https://github.com/cinnyapp/cinny/pkgs/container/cinny).
+To host Cinny on your own, simply download the tarball from
+[GitHub releases], and serve the files from `dist/` using your preferred
+webserver. Alternatively, you can just pull the docker image from
+[DockerHub] or [GitHub Container Registry].
* The default homeservers and explore pages are defined in [`config.json`](config.json).
-* You need to set up redirects to serve the assests. Example configurations; [netlify](netlify.toml), [nginx](contrib/nginx/cinny.domain.tld.conf), [caddy](contrib/caddy/caddyfile).
- * If you have trouble configuring redirects you can [enable hash routing](config.json#L35) — the url in the browser will have a `/#/` between the domain and open channel (ie. `app.cinny.in/#/home/` instead of `app.cinny.in/home/`) but you won't have to configure your webserver.
+* You need to set up redirects to serve the assests. Example configurations;
+[netlify](netlify.toml), [nginx](contrib/nginx/cinny.domain.tld.conf),
+[caddy](contrib/caddy/caddyfile).
+ * If you have trouble configuring redirects you can
+ [enable hash routing](config.json#L35) — the url in the browser will have
+ a `/#/` between the domain and open channel (ie. `app.cinny.in/#/home/`
+ instead of `app.cinny.in/home/`) but you won't have to configure your webserver.
-* To deploy on subdirectory, you need to rebuild the app youself after updating the `base` path in [`build.config.ts`](build.config.ts).
- * For example, if you want to deploy on `https://cinny.in/app`, then set `base: '/app'`.
+* To deploy on subdirectory, you need to rebuild the app youself after
+updating the `base` path in [`build.config.ts`](build.config.ts).
+ * For example, if you want to deploy on `https://cinny.in/app`, then
+ set `base: '/app'`.
+
+[GitHub releases]: https://github.com/cinnyapp/cinny/releases/latest
+[DockerHub]: https://hub.docker.com/r/ajbura/cinny
+[GitHub Container Registry]: https://github.com/cinnyapp/cinny/pkgs/container/cinny
PGP Public Key to verify tarball
@@ -85,31 +118,11 @@ mxFo+ioe/ABCufSmyqFye0psX3Sp
```
-## Local development
-> [!TIP]
-> We recommend using a version manager as versions change very quickly. You will likely need to switch between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Recommended nodejs version is Krypton LTS (v24.13.1).
+## License
+Cinny Project
+Copyright © 2024–present Ajay Bura
+https://cinny.in
-Execute the following commands to start a development server:
-```sh
-npm ci # Installs all dependencies
-npm start # Serve a development version
-```
-
-To build the app:
-```sh
-npm run build # Compiles the app into the dist/ directory
-```
-
-### Running with Docker
-This repository includes a Dockerfile, which builds the application from source and serves it with Nginx on port 80. To
-use this locally, you can build the container like so:
-```
-docker build -t cinny:latest .
-```
-
-You can then run the container you've built with a command similar to this:
-```
-docker run -p 8080:80 cinny:latest
-```
-
-This will forward your `localhost` port 8080 to the container's port 80. You can visit the app in your browser by navigating to `http://localhost:8080`.
+Cinny is licensed under the GNU Affero General Public License,
+Version 3 of the License (AGPL-3.0-only).
+You may obtain a copy of the License at https://www.gnu.org/licenses/agpl-3.0.html
\ No newline at end of file
From 22c6f3c1b7de500ccef56e47c3c8f5866b6878c2 Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Thu, 10 Sep 2026 17:03:54 +1000
Subject: [PATCH 40/95] chore: add a checkbox for contribution policy (#3101)
* chore: add a checkbox for contribution policy
Removed type of change section from PR template.
* Update contribution policy checklist item wording
---
.github/PULL_REQUEST_TEMPLATE.md | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..021c6b2b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,9 @@
+### Description
+
+
+
+Fixes #
+
+### Checklist:
+
+- [ ] I have read and understood the [Contribution policy](https://github.com/ajbura/cinny/blob/dev/CONTRIBUTING.md).
From 5794a88a9653f6f9db0b6769dc92e65d84ecc52e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 10 Sep 2026 17:04:10 +1000
Subject: [PATCH 41/95] chore(deps): bump softprops/action-gh-release from
3.0.1 to 3.0.3 (#3097)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.1 to 3.0.3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/718ea10b132b3b2eba29c1007bb80653f286566b...efb35369e0ad2afab669f228072c1b0d510eae64)
---
updated-dependencies:
- dependency-name: softprops/action-gh-release
dependency-version: 3.0.3
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/prod-deploy.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml
index db2db8c6..abdf79b9 100644
--- a/.github/workflows/prod-deploy.yml
+++ b/.github/workflows/prod-deploy.yml
@@ -52,7 +52,7 @@ jobs:
gpg --export | xxd -p
echo '${{ secrets.GNUPG_PASSPHRASE }}' | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --armor --detach-sign cinny-${{ steps.vars.outputs.tag }}.tar.gz
- name: Upload tagged release
- uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
+ uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
tag_name: ${{ github.ref_name }}
files: |
From 9a49bf46613a09509998dd7ef32f2bd8c33fc72c Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 10 Sep 2026 17:04:33 +1000
Subject: [PATCH 42/95] fix(deps): update dependency sanitize-html to v2.17.7
[security] (#3091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package-lock.json | 14 +++++++-------
package.json | 2 +-
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index b01cf47e..86c56e23 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,7 +58,7 @@
"react-i18next": "15.0.0",
"react-range": "1.8.14",
"react-router-dom": "6.30.3",
- "sanitize-html": "2.17.6",
+ "sanitize-html": "2.17.7",
"slate": "0.123.0",
"slate-dom": "0.123.0",
"slate-history": "0.113.1",
@@ -11682,9 +11682,9 @@
"license": "MIT"
},
"node_modules/sanitize-html": {
- "version": "2.17.6",
- "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.6.tgz",
- "integrity": "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA==",
+ "version": "2.17.7",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.7.tgz",
+ "integrity": "sha512-PGtEkc9cbnedU3s9TmzDbpsZ8w086g/0Q8k8/oIO1NLNU3i5k9yn835CrjJSajp1KMmkisbO1qPXxNKO3welAg==",
"license": "MIT",
"dependencies": {
"deepmerge": "^4.2.2",
@@ -11767,9 +11767,9 @@
}
},
"node_modules/sanitize-html/node_modules/entities": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
- "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
+ "integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=20.19.0"
diff --git a/package.json b/package.json
index aa3e17df..d0c0931c 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
"react-i18next": "15.0.0",
"react-range": "1.8.14",
"react-router-dom": "6.30.3",
- "sanitize-html": "2.17.6",
+ "sanitize-html": "2.17.7",
"slate": "0.123.0",
"slate-dom": "0.123.0",
"slate-history": "0.113.1",
From c143b30060eff5c39cf39c7f28e4b76fe12bc5c4 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 10 Sep 2026 17:08:39 +1000
Subject: [PATCH 43/95] fix(deps): update dependency react-router-dom to
v6.30.6 [security] (#3099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package-lock.json | 26 +++++++++++++-------------
package.json | 2 +-
2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 86c56e23..555b65cf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -57,7 +57,7 @@
"react-google-recaptcha": "2.1.0",
"react-i18next": "15.0.0",
"react-range": "1.8.14",
- "react-router-dom": "6.30.3",
+ "react-router-dom": "6.30.6",
"sanitize-html": "2.17.7",
"slate": "0.123.0",
"slate-dom": "0.123.0",
@@ -3829,9 +3829,9 @@
}
},
"node_modules/@remix-run/router": {
- "version": "1.23.2",
- "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
- "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "version": "1.23.4",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz",
+ "integrity": "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
@@ -11205,12 +11205,12 @@
}
},
"node_modules/react-router": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
- "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "version": "6.30.6",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.6.tgz",
+ "integrity": "sha512-5HfK7k5im7LTOB0EqCQmfvy4C13G92Ssj1VTmouTK3AJvyjKTnFuCV0vcMAD/JS+JC4DvDIBRrlAeJIFjh5VWg==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.2"
+ "@remix-run/router": "1.23.4"
},
"engines": {
"node": ">=14.0.0"
@@ -11220,13 +11220,13 @@
}
},
"node_modules/react-router-dom": {
- "version": "6.30.3",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
- "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
+ "version": "6.30.6",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.6.tgz",
+ "integrity": "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ==",
"license": "MIT",
"dependencies": {
- "@remix-run/router": "1.23.2",
- "react-router": "6.30.3"
+ "@remix-run/router": "1.23.4",
+ "react-router": "6.30.6"
},
"engines": {
"node": ">=14.0.0"
diff --git a/package.json b/package.json
index d0c0931c..7cf184e0 100644
--- a/package.json
+++ b/package.json
@@ -81,7 +81,7 @@
"react-google-recaptcha": "2.1.0",
"react-i18next": "15.0.0",
"react-range": "1.8.14",
- "react-router-dom": "6.30.3",
+ "react-router-dom": "6.30.6",
"sanitize-html": "2.17.7",
"slate": "0.123.0",
"slate-dom": "0.123.0",
From fcca7c21fb8f1a98b7123e53c81e7a8a88eb532e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 16 Sep 2026 01:35:33 +1000
Subject: [PATCH 44/95] fix(deps): update dependency folds to v2.7.2 (#3103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package-lock.json | 8 ++++----
package.json | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 555b65cf..b5b1aeb9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,7 +32,7 @@
"emojibase-data": "15.3.2",
"file-saver": "2.0.5",
"focus-trap-react": "10.0.2",
- "folds": "2.7.1",
+ "folds": "2.7.2",
"html-dom-parser": "4.0.0",
"html-react-parser": "4.2.0",
"i18next": "23.12.2",
@@ -7906,9 +7906,9 @@
}
},
"node_modules/folds": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/folds/-/folds-2.7.1.tgz",
- "integrity": "sha512-A1XGXJ4hp1W0cpFUIjYj9gPSzKVxqj9BlTBJTqmmtTUiEFDzXvhBITJdcqmlDsmcd756tu/iygSAzdky5fDcrg==",
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/folds/-/folds-2.7.2.tgz",
+ "integrity": "sha512-T/PQuN+ku/55hMVabamwsGp1uPSKkFw4xPEf7uZjAZNoejUL1s0leecA00sudYM2HvZaIUzUrosp+f0+8rWC6w==",
"license": "Apache-2.0",
"peerDependencies": {
"@vanilla-extract/css": "1.9.2",
diff --git a/package.json b/package.json
index 7cf184e0..fc0aa073 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"emojibase-data": "15.3.2",
"file-saver": "2.0.5",
"focus-trap-react": "10.0.2",
- "folds": "2.7.1",
+ "folds": "2.7.2",
"html-dom-parser": "4.0.0",
"html-react-parser": "4.2.0",
"i18next": "23.12.2",
From 6578bdf9496594dac77634746715032352d80c0f Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Wed, 16 Sep 2026 01:54:39 +1000
Subject: [PATCH 45/95] chore: Update actionable issues note in CONTRIBUTING.md
(#3104)
* chore: Update actionable issues note in CONTRIBUTING.md
Clarify that only issues with numbers greater than #2876 are actionable.
* Update pull request template for clarity
Simplified the pull request template by removing unnecessary details.
---
.github/PULL_REQUEST_TEMPLATE.md | 2 +-
CONTRIBUTING.md | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 021c6b2b..4b351db6 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,5 +1,5 @@
### Description
-
+
Fixes #
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 368cce41..859eab14 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -126,6 +126,9 @@ to `Cinny Support` room instead.
### Issues are Actionable
+> [!NOTE]
+> This is WIP and only issue with number greater than #2876 are actionable.
+
The Cinny [issue tracker](https://github.com/cinnyapp/cinny/issues)
is for _actionable items_.
From 8967c13878137841e49ee17184b689a6b4da334a Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Wed, 16 Sep 2026 01:55:15 +1000
Subject: [PATCH 46/95] chore: Release v4.12.7 (#3105)
Release v4.12.7
---
package-lock.json | 4 ++--
package.json | 2 +-
src/app/features/settings/about/About.tsx | 2 +-
src/app/pages/auth/AuthFooter.tsx | 2 +-
src/app/pages/client/WelcomePage.tsx | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index b5b1aeb9..87340857 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cinny",
- "version": "4.12.6",
+ "version": "4.12.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cinny",
- "version": "4.12.6",
+ "version": "4.12.7",
"license": "AGPL-3.0-only",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
diff --git a/package.json b/package.json
index fc0aa073..0c0cc066 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cinny",
- "version": "4.12.6",
+ "version": "4.12.7",
"description": "Yet another matrix client",
"main": "index.js",
"type": "module",
diff --git a/src/app/features/settings/about/About.tsx b/src/app/features/settings/about/About.tsx
index ff217f00..a5f0dbf9 100644
--- a/src/app/features/settings/about/About.tsx
+++ b/src/app/features/settings/about/About.tsx
@@ -46,7 +46,7 @@ export function About({ requestClose }: AboutProps) {
Cinny
- v4.12.6
+ v4.12.7
Yet another matrix client.
diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx
index 8ae911de..f9445b59 100644
--- a/src/app/pages/auth/AuthFooter.tsx
+++ b/src/app/pages/auth/AuthFooter.tsx
@@ -15,7 +15,7 @@ export function AuthFooter() {
target="_blank"
rel="noreferrer"
>
- v4.12.6
+ v4.12.7
Twitter
diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx
index f3e11710..69eead53 100644
--- a/src/app/pages/client/WelcomePage.tsx
+++ b/src/app/pages/client/WelcomePage.tsx
@@ -24,7 +24,7 @@ export function WelcomePage() {
target="_blank"
rel="noreferrer noopener"
>
- v4.12.6
+ v4.12.7
}
From 8788f0cf497ec0c7cdbe591f5c56478728945f29 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 15 Oct 2026 17:29:08 +1100
Subject: [PATCH 47/95] fix(deps): update dependency i18next-http-backend 2.5.2
to 4.0.3 (#3127)
* fix(deps): update dependency i18next-http-backend to v4 [security]
* chore: update to latest
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
---
package-lock.json | 28 ++++++++++++----------------
package.json | 2 +-
2 files changed, 13 insertions(+), 17 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 87340857..67908928 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,7 +37,7 @@
"html-react-parser": "4.2.0",
"i18next": "23.12.2",
"i18next-browser-languagedetector": "8.0.0",
- "i18next-http-backend": "2.5.2",
+ "i18next-http-backend": "4.0.3",
"immer": "9.0.16",
"is-hotkey": "0.2.0",
"jotai": "2.6.0",
@@ -8572,19 +8572,11 @@
}
},
"node_modules/i18next-http-backend": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz",
- "integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==",
- "dependencies": {
- "cross-fetch": "4.0.0"
- }
- },
- "node_modules/i18next-http-backend/node_modules/cross-fetch": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
- "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
- "dependencies": {
- "node-fetch": "^2.6.12"
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-4.0.3.tgz",
+ "integrity": "sha512-B5GJysCsJwqiDQRpUcikWsut82Vmekix58WIyiCpYtxaZ/wY8kwJCicMRvVcMoIa1x84siiRl5vBt6iCBVO6zg==",
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/iconv-lite": {
@@ -10394,6 +10386,7 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "optional": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
@@ -12579,7 +12572,8 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "optional": true
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
@@ -13492,12 +13486,14 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "optional": true
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "optional": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
diff --git a/package.json b/package.json
index 0c0cc066..d434af0a 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
"html-react-parser": "4.2.0",
"i18next": "23.12.2",
"i18next-browser-languagedetector": "8.0.0",
- "i18next-http-backend": "2.5.2",
+ "i18next-http-backend": "4.0.3",
"immer": "9.0.16",
"is-hotkey": "0.2.0",
"jotai": "2.6.0",
From eff1f48e3fc1f7b35219bb0e1aa508087915f149 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 15 Oct 2026 17:29:45 +1100
Subject: [PATCH 48/95] chore(deps): bump actions/setup-node from 7.0.0 to
7.1.0 (#3128)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)
---
updated-dependencies:
- dependency-name: actions/setup-node
dependency-version: 7.1.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/build-pull-request.yml | 2 +-
.github/workflows/netlify-dev.yml | 2 +-
.github/workflows/prod-deploy.yml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml
index 4d741994..11b5056c 100644
--- a/.github/workflows/build-pull-request.yml
+++ b/.github/workflows/build-pull-request.yml
@@ -14,7 +14,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup node
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: ".node-version"
package-manager-cache: false
diff --git a/.github/workflows/netlify-dev.yml b/.github/workflows/netlify-dev.yml
index 6ca393f3..12107685 100644
--- a/.github/workflows/netlify-dev.yml
+++ b/.github/workflows/netlify-dev.yml
@@ -13,7 +13,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup node
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: ".node-version"
package-manager-cache: false
diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml
index abdf79b9..2a8192db 100644
--- a/.github/workflows/prod-deploy.yml
+++ b/.github/workflows/prod-deploy.yml
@@ -12,7 +12,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup node
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: '.node-version'
package-manager-cache: false
From 14e490c89613714c4091defdd0212cf1fcd5de91 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 26 Oct 2026 00:02:45 +1100
Subject: [PATCH 49/95] fix(deps): update dependency sanitize-html v2.17.7 to
v2.18.1 (#3133)
* fix(deps): update dependency sanitize-html to v2.18.0 [security]
* chore: update to latest
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
---
package-lock.json | 61 +++++++++++++++++++++++++++--------------------
package.json | 2 +-
2 files changed, 36 insertions(+), 27 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 67908928..18722b8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,7 +58,7 @@
"react-i18next": "15.0.0",
"react-range": "1.8.14",
"react-router-dom": "6.30.6",
- "sanitize-html": "2.17.7",
+ "sanitize-html": "2.18.1",
"slate": "0.123.0",
"slate-dom": "0.123.0",
"slate-history": "0.113.1",
@@ -9096,9 +9096,10 @@
}
},
"node_modules/is-plain-object": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
- "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.1.0.tgz",
+ "integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==",
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -9497,14 +9498,20 @@
}
},
"node_modules/launder": {
- "version": "1.7.1",
- "resolved": "https://registry.npmjs.org/launder/-/launder-1.7.1.tgz",
- "integrity": "sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==",
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/launder/-/launder-1.7.2.tgz",
+ "integrity": "sha512-DLg3HPnHUfBi5/MxMLmJD12dmlMpFEC2HgMW6vkZ/9JR0RU00kXoRGvDxAvFmdO0o610OA77i4FgNTLucmhDVg==",
"license": "MIT",
"dependencies": {
- "dayjs": "^1.11.7"
+ "dayjs": "^1.11.23"
}
},
+ "node_modules/launder/node_modules/dayjs": {
+ "version": "1.11.23",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz",
+ "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==",
+ "license": "MIT"
+ },
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -10354,15 +10361,16 @@
"optional": true
},
"node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -10872,9 +10880,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
- "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
+ "version": "8.5.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz",
+ "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==",
"funding": [
{
"type": "opencollective",
@@ -10889,10 +10897,11 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
- "nanoid": "^3.3.8",
+ "nanoid": "^3.3.20",
"picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -11675,18 +11684,17 @@
"license": "MIT"
},
"node_modules/sanitize-html": {
- "version": "2.17.7",
- "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.7.tgz",
- "integrity": "sha512-PGtEkc9cbnedU3s9TmzDbpsZ8w086g/0Q8k8/oIO1NLNU3i5k9yn835CrjJSajp1KMmkisbO1qPXxNKO3welAg==",
+ "version": "2.18.1",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.18.1.tgz",
+ "integrity": "sha512-2X5lC8fYla8YaT8g73Nj+HcLWXan5dLyCWLgVvaOmfw7JI78IRsyNtIDytTHdn7hBQbIkCah7Zbmd5jabzeDig==",
"license": "MIT",
"dependencies": {
- "deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
"htmlparser2": "^12.0.0",
- "is-plain-object": "^5.0.0",
- "launder": "^1.7.1",
+ "is-plain-object": "^5.1.0",
+ "launder": "^1.7.2",
"parse-srcset": "^1.0.2",
- "postcss": "^8.3.11"
+ "postcss": "^8.5.27"
},
"engines": {
"node": ">=22.12.0"
@@ -12152,9 +12160,10 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
diff --git a/package.json b/package.json
index d434af0a..becce751 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
"react-i18next": "15.0.0",
"react-range": "1.8.14",
"react-router-dom": "6.30.6",
- "sanitize-html": "2.17.7",
+ "sanitize-html": "2.18.1",
"slate": "0.123.0",
"slate-dom": "0.123.0",
"slate-history": "0.113.1",
From 1e6412ed5b639607a8c7485f7d0b447f073fa09c Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Mon, 26 Oct 2026 00:17:53 +1100
Subject: [PATCH 50/95] chore: Release v4.12.8 (#3135)
---
package-lock.json | 4 ++--
package.json | 2 +-
src/app/features/settings/about/About.tsx | 2 +-
src/app/pages/auth/AuthFooter.tsx | 2 +-
src/app/pages/client/WelcomePage.tsx | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 18722b8e..d61fec51 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cinny",
- "version": "4.12.7",
+ "version": "4.12.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cinny",
- "version": "4.12.7",
+ "version": "4.12.8",
"license": "AGPL-3.0-only",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
diff --git a/package.json b/package.json
index becce751..8166ccd8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cinny",
- "version": "4.12.7",
+ "version": "4.12.8",
"description": "Yet another matrix client",
"main": "index.js",
"type": "module",
diff --git a/src/app/features/settings/about/About.tsx b/src/app/features/settings/about/About.tsx
index a5f0dbf9..d3dcde96 100644
--- a/src/app/features/settings/about/About.tsx
+++ b/src/app/features/settings/about/About.tsx
@@ -46,7 +46,7 @@ export function About({ requestClose }: AboutProps) {
Cinny
- v4.12.7
+ v4.12.8
Yet another matrix client.
diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx
index f9445b59..b0b94319 100644
--- a/src/app/pages/auth/AuthFooter.tsx
+++ b/src/app/pages/auth/AuthFooter.tsx
@@ -15,7 +15,7 @@ export function AuthFooter() {
target="_blank"
rel="noreferrer"
>
- v4.12.7
+ v4.12.8
Twitter
diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx
index 69eead53..6a25392c 100644
--- a/src/app/pages/client/WelcomePage.tsx
+++ b/src/app/pages/client/WelcomePage.tsx
@@ -24,7 +24,7 @@ export function WelcomePage() {
target="_blank"
rel="noreferrer noopener"
>
- v4.12.7
+ v4.12.8
}
From a588ab82b3adc4d153ca2eeacab6a66bbf319c91 Mon Sep 17 00:00:00 2001
From: Meta Vens
Date: Sat, 31 Oct 2026 05:00:32 +0100
Subject: [PATCH 51/95] GameCult theme, steward-grouped project rooms, preview
base override
Adds the gamecult-theme (brand palette, softer radii, Ubuntu/Montserrat/Plex Mono),
makes it the default, hides the member drawer by default, defaults to bubble layout,
groups rooms by org.gamecult.steward in the space nav (collapsed on phones), and lets
CINNY_BASE set Vite's base for a sub-path preview build.
Co-Authored-By: Claude Opus 5.5
---
build.config.ts | 2 +-
package.json | 11 ++-
.../steward-groups/stewardGroups.test.ts | 63 +++++++++++++
.../features/steward-groups/stewardGroups.ts | 91 ++++++++++++++++++
.../steward-groups/useStewardGroups.ts | 71 ++++++++++++++
src/app/hooks/useTheme.ts | 18 +++-
src/app/pages/ThemeManager.tsx | 17 +---
src/app/pages/client/space/Space.tsx | 35 ++++++-
src/app/state/settings.ts | 8 +-
src/colors.css.ts | 92 +++++++++++++++++++
src/gamecult.css.ts | 20 ++++
src/index.css | 64 +++++++++++++
src/index.tsx | 7 ++
13 files changed, 471 insertions(+), 28 deletions(-)
create mode 100644 src/app/features/steward-groups/stewardGroups.test.ts
create mode 100644 src/app/features/steward-groups/stewardGroups.ts
create mode 100644 src/app/features/steward-groups/useStewardGroups.ts
create mode 100644 src/gamecult.css.ts
diff --git a/build.config.ts b/build.config.ts
index ec8a41d0..ac51db97 100644
--- a/build.config.ts
+++ b/build.config.ts
@@ -1,3 +1,3 @@
export default {
- base: '/',
+ base: process.env.CINNY_BASE ?? '/',
};
diff --git a/package.json b/package.json
index 8166ccd8..298fbf36 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,8 @@
"typecheck": "tsc --noEmit",
"prepare": "husky install",
"commit": "git-cz",
- "bump": "node scripts/update-version.js"
+ "bump": "node scripts/update-version.js",
+ "test": "vitest run"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "eslint",
@@ -87,7 +88,10 @@
"slate-dom": "0.123.0",
"slate-history": "0.113.1",
"slate-react": "0.123.0",
- "ua-parser-js": "1.0.35"
+ "ua-parser-js": "1.0.35",
+ "@fontsource/ubuntu": "5.2.5",
+ "@fontsource/montserrat": "5.2.5",
+ "@fontsource/ibm-plex-mono": "5.2.5"
},
"devDependencies": {
"@element-hq/element-call-embedded": "0.20.1",
@@ -123,6 +127,7 @@
"vite": "5.4.19",
"vite-plugin-pwa": "0.20.5",
"vite-plugin-static-copy": "1.0.4",
- "vite-plugin-top-level-await": "1.4.4"
+ "vite-plugin-top-level-await": "1.4.4",
+ "vitest": "1.6.0"
}
}
diff --git a/src/app/features/steward-groups/stewardGroups.test.ts b/src/app/features/steward-groups/stewardGroups.test.ts
new file mode 100644
index 00000000..d0d4b7c3
--- /dev/null
+++ b/src/app/features/steward-groups/stewardGroups.test.ts
@@ -0,0 +1,63 @@
+import { describe, expect, it } from 'vitest';
+import { groupByStewards, parseSteward, StewardEntry } from './stewardGroups';
+
+const room = (roomId: string, parentId = '!projects') => ({ roomId, parentId });
+const space = (roomId: string) => ({ roomId, space: true as const });
+
+const base = {
+ spaceId: '!top',
+ isGroupOpen: () => true,
+ keepVisible: () => false,
+};
+
+const names = (out: StewardEntry<{ roomId: string }>[]) =>
+ out.map((e) => (e.type === 'group' ? `G:${e.label}:${e.count}` : e.item.roomId));
+
+describe('groupByStewards', () => {
+ const stewards: Record = { '!a': 'Void', '!b': 'Eureka', '!c': 'Void' };
+ const stewardOf = (id: string) => stewards[id];
+
+ it('groups by steward, sorted by name, with unstewarded rooms first', () => {
+ const out = groupByStewards([room('!a'), room('!x'), room('!b'), room('!c')], {
+ ...base,
+ stewardOf,
+ });
+ expect(names(out)).toEqual(['!x', 'G:Eureka:1', '!b', 'G:Void:2', '!a', '!c']);
+ });
+
+ it('collapsed groups hide rooms except kept-visible ones', () => {
+ const out = groupByStewards([room('!a'), room('!b'), room('!c')], {
+ ...base,
+ stewardOf,
+ isGroupOpen: () => false,
+ keepVisible: (id) => id === '!c',
+ });
+ expect(names(out)).toEqual(['G:Eureka:1', 'G:Void:2', '!c']);
+ });
+
+ it('degrades to the original order when no room has steward state', () => {
+ const out = groupByStewards([room('!1'), space('!s'), room('!2', '!s')], {
+ ...base,
+ stewardOf: () => undefined,
+ });
+ expect(names(out)).toEqual(['!1', '!s', '!2']);
+ });
+
+ it('does not merge runs separated by a space header', () => {
+ const out = groupByStewards([room('!a'), space('!s'), room('!c', '!s')], {
+ ...base,
+ stewardOf,
+ });
+ expect(out.filter((e) => e.type === 'group')).toHaveLength(2);
+ });
+});
+
+describe('parseSteward', () => {
+ it('accepts a string and rejects junk', () => {
+ expect(parseSteward({ steward: ' Void ' })).toBe('Void');
+ expect(parseSteward({ steward: 3 })).toBeUndefined();
+ expect(parseSteward({ steward: ' ' })).toBeUndefined();
+ expect(parseSteward(undefined)).toBeUndefined();
+ expect(parseSteward(null)).toBeUndefined();
+ });
+});
diff --git a/src/app/features/steward-groups/stewardGroups.ts b/src/app/features/steward-groups/stewardGroups.ts
new file mode 100644
index 00000000..c95dd935
--- /dev/null
+++ b/src/app/features/steward-groups/stewardGroups.ts
@@ -0,0 +1,91 @@
+/**
+ * Splits runs of rooms that carry an `org.gamecult.steward` state event into
+ * collapsible per-steward groups. Pure: the caller supplies the lookups, so the
+ * grouping is testable without a Matrix client.
+ */
+export const STEWARD_EVENT_TYPE = 'org.gamecult.steward';
+
+export type HierarchyLike = { roomId: string; parentId?: string; space?: true };
+
+export type StewardEntry =
+ | { type: 'item'; item: T }
+ | { type: 'group'; id: string; label: string; open: boolean; count: number };
+
+export const makeStewardGroupId = (spaceId: string, parentId: string, steward: string) =>
+ `${spaceId}|${parentId}|steward:${steward}`;
+
+export type StewardGroupOptions = {
+ spaceId: string;
+ /** steward name of a room, or undefined when the room has no (valid) steward state */
+ stewardOf: (roomId: string) => string | undefined;
+ isGroupOpen: (groupId: string) => boolean;
+ /** rooms that stay visible inside a collapsed group (selected, unread, in a call) */
+ keepVisible: (roomId: string) => boolean;
+};
+
+const byLabel = (a: string, b: string) =>
+ a.localeCompare(b, undefined, { sensitivity: 'base' }) || a.localeCompare(b);
+
+export const groupByStewards = (
+ items: T[],
+ { spaceId, stewardOf, isGroupOpen, keepVisible }: StewardGroupOptions
+): StewardEntry[] => {
+ const out: StewardEntry[] = [];
+
+ const flush = (run: T[]) => {
+ if (run.length === 0) return;
+ const groups = new Map();
+ const loose: T[] = [];
+ run.forEach((item) => {
+ const steward = stewardOf(item.roomId);
+ if (steward === undefined) {
+ loose.push(item);
+ return;
+ }
+ const list = groups.get(steward);
+ if (list) list.push(item);
+ else groups.set(steward, [item]);
+ });
+
+ // No steward state anywhere in this run: leave the list exactly as Cinny built it.
+ if (groups.size === 0) {
+ run.forEach((item) => out.push({ type: 'item', item }));
+ return;
+ }
+
+ loose.forEach((item) => out.push({ type: 'item', item }));
+ Array.from(groups.keys())
+ .sort(byLabel)
+ .forEach((steward) => {
+ const rooms = groups.get(steward) ?? [];
+ const id = makeStewardGroupId(spaceId, rooms[0].parentId ?? spaceId, steward);
+ const open = isGroupOpen(id);
+ out.push({ type: 'group', id, label: steward, open, count: rooms.length });
+ rooms
+ .filter((item) => open || keepVisible(item.roomId))
+ .forEach((item) => out.push({ type: 'item', item }));
+ });
+ };
+
+ let run: T[] = [];
+ items.forEach((item) => {
+ if (item.space) {
+ flush(run);
+ run = [];
+ out.push({ type: 'item', item });
+ return;
+ }
+ run.push(item);
+ });
+ flush(run);
+ return out;
+};
+
+/** Reads `steward` from raw state event content; anything unusable is "no steward". */
+export const parseSteward = (content: unknown): string | undefined => {
+ if (typeof content !== 'object' || content === null) return undefined;
+ const { steward } = content as { steward?: unknown };
+ if (typeof steward !== 'string') return undefined;
+ const trimmed = steward.trim();
+ return trimmed === '' ? undefined : trimmed;
+};
diff --git a/src/app/features/steward-groups/useStewardGroups.ts b/src/app/features/steward-groups/useStewardGroups.ts
new file mode 100644
index 00000000..ae1d2a3d
--- /dev/null
+++ b/src/app/features/steward-groups/useStewardGroups.ts
@@ -0,0 +1,71 @@
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { MatrixClient, MatrixEvent, RoomStateEvent } from 'matrix-js-sdk';
+import { atomWithStorage } from 'jotai/utils';
+import { useAtom } from 'jotai';
+import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
+import {
+ groupByStewards,
+ HierarchyLike,
+ parseSteward,
+ StewardEntry,
+ STEWARD_EVENT_TYPE,
+} from './stewardGroups';
+
+// Explicit open/closed choices by group id. Absent = default: closed on phones, open elsewhere.
+const stewardGroupChoicesAtom = atomWithStorage>(
+ 'gamecultStewardGroupOpen',
+ {}
+);
+
+type Options = {
+ mx: MatrixClient;
+ spaceId: string;
+ items: T[];
+ keepVisible: (roomId: string) => boolean;
+};
+
+export const useStewardGroups = ({
+ mx,
+ spaceId,
+ items,
+ keepVisible,
+}: Options) => {
+ const mobile = useScreenSizeContext() === ScreenSize.Mobile;
+ const [choices, setChoices] = useAtom(stewardGroupChoicesAtom);
+ const [stateVersion, setStateVersion] = useState(0);
+
+ useEffect(() => {
+ const onState = (mEvent: MatrixEvent) => {
+ if (mEvent.getType() === STEWARD_EVENT_TYPE) setStateVersion((v) => v + 1);
+ };
+ mx.on(RoomStateEvent.Events, onState);
+ return () => {
+ mx.removeListener(RoomStateEvent.Events, onState);
+ };
+ }, [mx]);
+
+ const stewardOf = useCallback(
+ (roomId: string) =>
+ // stateVersion is read so a late-arriving steward event re-derives this lookup
+ stateVersion >= 0
+ ? parseSteward(
+ mx.getRoom(roomId)?.currentState.getStateEvents(STEWARD_EVENT_TYPE, '')?.getContent()
+ )
+ : undefined,
+ [mx, stateVersion]
+ );
+
+ const isGroupOpen = useCallback((id: string) => choices[id] ?? !mobile, [choices, mobile]);
+
+ const entries: StewardEntry[] = useMemo(
+ () => groupByStewards(items, { spaceId, stewardOf, isGroupOpen, keepVisible }),
+ [items, spaceId, stewardOf, isGroupOpen, keepVisible]
+ );
+
+ const toggleGroup = useCallback(
+ (id: string) => setChoices((prev) => ({ ...prev, [id]: !(prev[id] ?? !mobile) })),
+ [setChoices, mobile]
+ );
+
+ return { entries, toggleGroup };
+};
diff --git a/src/app/hooks/useTheme.ts b/src/app/hooks/useTheme.ts
index cdbb9dba..4bfe01da 100644
--- a/src/app/hooks/useTheme.ts
+++ b/src/app/hooks/useTheme.ts
@@ -1,7 +1,8 @@
import { lightTheme } from 'folds';
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { onDarkFontWeight, onLightFontWeight } from '../../config.css';
-import { butterTheme, darkTheme, silverTheme } from '../../colors.css';
+import { butterTheme, darkTheme, gamecultTheme, silverTheme } from '../../colors.css';
+import '../../gamecult.css';
import { settingsAtom } from '../state/settings';
import { useSetting } from '../state/hooks/settings';
@@ -38,8 +39,14 @@ export const ButterTheme: Theme = {
classNames: ['butter-theme', butterTheme, onDarkFontWeight, 'prism-dark'],
};
+export const GameCultTheme: Theme = {
+ id: 'gamecult-theme',
+ kind: ThemeKind.Dark,
+ classNames: ['gamecult-theme', gamecultTheme, onDarkFontWeight, 'prism-dark'],
+};
+
export const useThemes = (): Theme[] => {
- const themes: Theme[] = useMemo(() => [LightTheme, SilverTheme, DarkTheme, ButterTheme], []);
+ const themes: Theme[] = useMemo(() => [GameCultTheme, LightTheme, SilverTheme, DarkTheme, ButterTheme], []);
return themes;
};
@@ -47,6 +54,7 @@ export const useThemes = (): Theme[] => {
export const useThemeNames = (): Record =>
useMemo(
() => ({
+ [GameCultTheme.id]: 'GameCult',
[LightTheme.id]: 'Light',
[SilverTheme.id]: 'Silver',
[DarkTheme.id]: 'Dark',
@@ -84,15 +92,15 @@ export const useActiveTheme = (): Theme => {
const [darkThemeId] = useSetting(settingsAtom, 'darkThemeId');
if (!systemTheme) {
- const selectedTheme = themes.find((theme) => theme.id === themeId) ?? LightTheme;
+ const selectedTheme = themes.find((theme) => theme.id === themeId) ?? GameCultTheme;
return selectedTheme;
}
const selectedTheme =
systemThemeKind === ThemeKind.Dark
- ? themes.find((theme) => theme.id === darkThemeId) ?? DarkTheme
- : themes.find((theme) => theme.id === lightThemeId) ?? LightTheme;
+ ? themes.find((theme) => theme.id === darkThemeId) ?? GameCultTheme
+ : themes.find((theme) => theme.id === lightThemeId) ?? GameCultTheme;
return selectedTheme;
};
diff --git a/src/app/pages/ThemeManager.tsx b/src/app/pages/ThemeManager.tsx
index 69d50cdb..983ca684 100644
--- a/src/app/pages/ThemeManager.tsx
+++ b/src/app/pages/ThemeManager.tsx
@@ -1,29 +1,20 @@
import React, { ReactNode, useEffect } from 'react';
import { configClass, varsClass } from 'folds';
import {
- DarkTheme,
- LightTheme,
+ GameCultTheme,
ThemeContextProvider,
- ThemeKind,
useActiveTheme,
- useSystemThemeKind,
} from '../hooks/useTheme';
import { useSetting } from '../state/hooks/settings';
import { settingsAtom } from '../state/settings';
export function UnAuthRouteThemeManager() {
- const systemThemeKind = useSystemThemeKind();
-
useEffect(() => {
document.body.className = '';
document.body.classList.add(configClass, varsClass);
- if (systemThemeKind === ThemeKind.Dark) {
- document.body.classList.add(...DarkTheme.classNames);
- }
- if (systemThemeKind === ThemeKind.Light) {
- document.body.classList.add(...LightTheme.classNames);
- }
- }, [systemThemeKind]);
+ // The GameCult identity is single-theme: signed-out pages ignore the system scheme.
+ document.body.classList.add(...GameCultTheme.classNames);
+ }, []);
return null;
}
diff --git a/src/app/pages/client/space/Space.tsx b/src/app/pages/client/space/Space.tsx
index 2a7c6199..c822ff38 100644
--- a/src/app/pages/client/space/Space.tsx
+++ b/src/app/pages/client/space/Space.tsx
@@ -51,6 +51,7 @@ import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
+import { useStewardGroups } from '../../../features/steward-groups/useStewardGroups';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { useRoomName } from '../../../hooks/useRoomMeta';
import { useSpaceJoinedHierarchy } from '../../../hooks/useSpaceHierarchy';
@@ -425,8 +426,18 @@ export function Space() {
)
);
+ const { entries, toggleGroup } = useStewardGroups({
+ mx,
+ spaceId: space.roomId,
+ items: hierarchy,
+ keepVisible: useCallback(
+ (rId: string) => roomToUnread.has(rId) || rId === selectedRoomId || callEmbed?.roomId === rId,
+ [roomToUnread, selectedRoomId, callEmbed]
+ ),
+ });
+
const virtualizer = useVirtualizer({
- count: hierarchy.length,
+ count: entries.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 0,
overscan: 10,
@@ -491,7 +502,27 @@ export function Space() {
}}
>
{virtualizer.getVirtualItems().map((vItem) => {
- const { roomId } = hierarchy[vItem.index] ?? {};
+ const entry = entries[vItem.index];
+ if (!entry) return null;
+ if (entry.type === 'group') {
+ return (
+
+
+ toggleGroup(entry.id)}
+ closed={!entry.open}
+ >
+ {entry.label} · {entry.count}
+
+
+
+ );
+ }
+ const { roomId } = entry.item;
const room = mx.getRoom(roomId);
if (!room) return null;
diff --git a/src/app/state/settings.ts b/src/app/state/settings.ts
index 9fbad4ff..6a894350 100644
--- a/src/app/state/settings.ts
+++ b/src/app/state/settings.ts
@@ -50,8 +50,8 @@ export interface Settings {
}
const defaultSettings: Settings = {
- themeId: undefined,
- useSystemTheme: true,
+ themeId: 'gamecult-theme',
+ useSystemTheme: false,
lightThemeId: undefined,
darkThemeId: undefined,
monochromeMode: false,
@@ -61,10 +61,10 @@ const defaultSettings: Settings = {
pageZoom: 100,
hideActivity: false,
- isPeopleDrawer: true,
+ isPeopleDrawer: false,
memberSortFilterIndex: 0,
enterForNewline: false,
- messageLayout: 0,
+ messageLayout: 2,
messageSpacing: '400',
hideMembershipEvents: false,
hideNickAvatarEvents: true,
diff --git a/src/colors.css.ts b/src/colors.css.ts
index 268cbf78..9c03c2cb 100644
--- a/src/colors.css.ts
+++ b/src/colors.css.ts
@@ -236,3 +236,95 @@ export const butterTheme = createTheme(color, {
OnContainer: '#F2EED3',
},
});
+
+// GameCult identity (gamecult-site quartz.config.ts): single dark theme on #07111a,
+// orange #ff8a2a accent, sky #59b7ff links. The brand defines no status colours, so
+// Success/Warning/Critical are invented here and kept distinct from the orange accent.
+export const gamecultTheme = createTheme(color, {
+ Background: {
+ Container: '#07111a',
+ ContainerHover: '#0d1a26',
+ ContainerActive: '#122130',
+ ContainerLine: '#16212c',
+ OnContainer: '#b7c7d9',
+ },
+ Surface: {
+ Container: '#09141f',
+ ContainerHover: '#0f1c29',
+ ContainerActive: '#152433',
+ ContainerLine: '#16212c',
+ OnContainer: '#b7c7d9',
+ },
+ SurfaceVariant: {
+ Container: '#0d1a26',
+ ContainerHover: '#122130',
+ ContainerActive: '#16273a',
+ ContainerLine: '#1b2a38',
+ OnContainer: '#b7c7d9',
+ },
+ Primary: {
+ Main: '#ff8a2a',
+ MainHover: '#ff9640',
+ MainActive: '#ffa155',
+ MainLine: '#ffab66',
+ OnMain: '#2a1300',
+ Container: '#2b1a0e',
+ ContainerHover: '#35200f',
+ ContainerActive: '#402611',
+ ContainerLine: '#4d2d13',
+ OnContainer: '#ffd3ab',
+ },
+ Secondary: {
+ Main: '#eef5ff',
+ MainHover: '#dbe7f6',
+ MainActive: '#cddced',
+ MainLine: '#bfd0e4',
+ OnMain: '#07111a',
+ Container: '#16212c',
+ ContainerHover: '#1c2a38',
+ ContainerActive: '#233342',
+ ContainerLine: '#2a3b4c',
+ OnContainer: '#eef5ff',
+ },
+ Success: {
+ Main: '#5fd0a4',
+ MainHover: '#72d8b0',
+ MainActive: '#84deba',
+ MainLine: '#93e3c2',
+ OnMain: '#042014',
+ Container: '#0c2a21',
+ ContainerHover: '#10342a',
+ ContainerActive: '#143e32',
+ ContainerLine: '#19493a',
+ OnContainer: '#c4f1de',
+ },
+ Warning: {
+ Main: '#f2d04b',
+ MainHover: '#f5d963',
+ MainActive: '#f7e078',
+ MainLine: '#f9e68b',
+ OnMain: '#2a2100',
+ Container: '#2a2410',
+ ContainerHover: '#352d13',
+ ContainerActive: '#403616',
+ ContainerLine: '#4d411a',
+ OnContainer: '#f8ebb0',
+ },
+ Critical: {
+ Main: '#ff7a90',
+ MainHover: '#ff8fa1',
+ MainActive: '#ffa0af',
+ MainLine: '#ffb0bc',
+ OnMain: '#3a0710',
+ Container: '#2e1118',
+ ContainerHover: '#391520',
+ ContainerActive: '#441a27',
+ ContainerLine: '#51202e',
+ OnContainer: '#ffd0d8',
+ },
+ Other: {
+ FocusRing: 'rgba(89, 183, 255, 0.55)',
+ Shadow: 'rgba(0, 0, 0, 0.6)',
+ Overlay: 'rgba(3, 7, 13, 0.8)',
+ },
+});
diff --git a/src/gamecult.css.ts b/src/gamecult.css.ts
new file mode 100644
index 00000000..4aaf3e76
--- /dev/null
+++ b/src/gamecult.css.ts
@@ -0,0 +1,20 @@
+import { globalStyle } from '@vanilla-extract/css';
+import { config } from 'folds';
+
+// Softer, rounder shape language and an airier line height. Scoped to the GameCult
+// theme class so the stock themes keep folds' defaults.
+const scope = 'body.gamecult-theme';
+
+globalStyle(scope, {
+ vars: {
+ [config.radii.R300]: '10px',
+ [config.radii.R400]: '16px',
+ [config.radii.R500]: '22px',
+ [config.lineHeight.T300]: '1.6',
+ [config.lineHeight.T400]: '1.7',
+ [config.lineHeight.T500]: '1.7',
+ [config.lineHeight.B400]: '1.6',
+ [config.lineHeight.B500]: '1.6',
+ [config.lineHeight.D400]: '1.7',
+ },
+});
diff --git a/src/index.css b/src/index.css
index d4696367..f751b94d 100644
--- a/src/index.css
+++ b/src/index.css
@@ -135,3 +135,67 @@ li p::before {
content: '';
display: inline-block;
}
+
+/* GameCult identity: Ubuntu 300 prose, Montserrat thin titles, IBM Plex Mono labels. */
+.gamecult-theme {
+ --tc-link: #59b7ff;
+
+ --mx-uc-1: hsl(208, 100%, 72%);
+ --mx-uc-2: hsl(280, 100%, 78%);
+ --mx-uc-3: hsl(163, 70%, 65%);
+ --mx-uc-4: hsl(343, 100%, 76%);
+ --mx-uc-5: hsl(28, 100%, 66%);
+ --mx-uc-6: hsl(181, 70%, 62%);
+ --mx-uc-7: hsl(243, 100%, 80%);
+ --mx-uc-8: hsl(94, 70%, 70%);
+
+ --font-secondary: 'Ubuntu', var(--font-emoji), sans-serif;
+ --font-title: 'Montserrat', 'Ubuntu', sans-serif;
+ --font-mono: 'IBM Plex Mono', monospace;
+ --gamecult-rule: rgba(148, 163, 184, 0.22);
+ --gamecult-wash: 0.5;
+
+ background-color: #07111a;
+ background-image: radial-gradient(
+ circle at 78% 14%,
+ rgb(255 138 42 / calc(0.18 * var(--gamecult-wash))),
+ transparent 18%
+ ),
+ radial-gradient(
+ circle at 18% 10%,
+ rgb(109 96 255 / calc(0.16 * var(--gamecult-wash))),
+ transparent 24%
+ ),
+ radial-gradient(
+ circle at 52% 0%,
+ rgb(89 183 255 / calc(0.14 * var(--gamecult-wash))),
+ transparent 28%
+ ),
+ linear-gradient(180deg, #03070d 0%, #07111a 44%, #09141f 100%);
+ background-attachment: fixed;
+ color-scheme: dark;
+ font-weight: 300;
+}
+
+.gamecult-theme b,
+.gamecult-theme strong {
+ font-weight: 500;
+}
+
+.gamecult-theme h1,
+.gamecult-theme h2,
+.gamecult-theme h3 {
+ font-family: var(--font-title);
+ font-weight: 300;
+}
+
+.gamecult-theme code,
+.gamecult-theme pre {
+ font-family: var(--font-mono);
+}
+
+/* Quiet secondary metadata: timestamps, edited marks and the like sit back. */
+.gamecult-theme time {
+ opacity: 0.55;
+ font-size: 0.78em;
+}
diff --git a/src/index.tsx b/src/index.tsx
index 6cc0ca38..672ac5c0 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -3,6 +3,13 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
import { enableMapSet } from 'immer';
import '@fontsource/inter/variable.css';
+import '@fontsource/ubuntu/300.css';
+import '@fontsource/ubuntu/400.css';
+import '@fontsource/ubuntu/500.css';
+import '@fontsource/ubuntu/700.css';
+import '@fontsource/montserrat/200.css';
+import '@fontsource/montserrat/300.css';
+import '@fontsource/ibm-plex-mono/400.css';
import 'folds/dist/style.css';
import { configClass, varsClass } from 'folds';
From b8e696c81bd698ecd0ea88095b2c8bfdbecbd493 Mon Sep 17 00:00:00 2001
From: Meta Vens
Date: Sat, 31 Oct 2026 05:05:15 +0100
Subject: [PATCH 52/95] Lockfile for fontsource faces and vitest
Co-Authored-By: Claude Opus 5.5
---
package-lock.json | 686 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 682 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d61fec51..dc774000 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,10 @@
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
+ "@fontsource/ibm-plex-mono": "5.2.5",
"@fontsource/inter": "4.5.14",
+ "@fontsource/montserrat": "5.2.5",
+ "@fontsource/ubuntu": "5.2.5",
"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0",
@@ -99,7 +102,8 @@
"vite": "5.4.19",
"vite-plugin-pwa": "0.20.5",
"vite-plugin-static-copy": "1.0.4",
- "vite-plugin-top-level-await": "1.4.4"
+ "vite-plugin-top-level-await": "1.4.4",
+ "vitest": "1.6.0"
},
"engines": {
"node": ">=16.0.0"
@@ -2188,11 +2192,38 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@fontsource/ibm-plex-mono": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.5.tgz",
+ "integrity": "sha512-G09N3GfuT9qj3Ax2FDZvKqZttzM3v+cco2l8uXamhKyXLdmlaUDH5o88/C3vtTHj2oT7yRKsvxz9F+BXbWKMYA==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
"node_modules/@fontsource/inter": {
"version": "4.5.14",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.14.tgz",
"integrity": "sha512-JDC9AocdPLuGsASkvWw9hS5gtHE7K9dOwL98XLrk5yjYqxy4uVnScG58NUvFMJDVJRl/7c8Wnap6PEs+7Zvj1Q=="
},
+ "node_modules/@fontsource/montserrat": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/@fontsource/montserrat/-/montserrat-5.2.5.tgz",
+ "integrity": "sha512-/OA+bdg1yqUArDUnEeCqDwgTDiIpvED56TxYskje8UKpsvjl/0A9eNod7IcdHz6/Osi70vo7briRO3la8/u+5Q==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource/ubuntu": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/@fontsource/ubuntu/-/ubuntu-5.2.5.tgz",
+ "integrity": "sha512-VKVFVqmJ9MGnOJW2dsQ982qHN8Zr+tNeEwEHnzt5VdGi9ZWL37wYFD32YsqukWlI/+I7v5ZkgHTEAvsZxU6aDA==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz",
@@ -2307,6 +2338,19 @@
"@swc/helpers": "^0.5.0"
}
},
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -2347,9 +2391,10 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -4330,6 +4375,13 @@
"url": "https://ko-fi.com/dangreen"
}
},
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.12",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz",
+ "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -5130,6 +5182,143 @@
"vite": "^4.2.0 || ^5.0.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz",
+ "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "1.6.0",
+ "@vitest/utils": "1.6.0",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz",
+ "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "1.6.0",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/p-limit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+ "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/runner/node_modules/yocto-queue": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
+ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz",
+ "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz",
+ "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz",
+ "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -5156,6 +5345,19 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/acorn-walk": {
+ "version": "8.3.5",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
+ "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -5448,6 +5650,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ast-types-flow": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
@@ -5857,6 +6069,25 @@
"node": ">=6"
}
},
+ "node_modules/chai": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
+ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -5879,6 +6110,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -6614,6 +6858,19 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
+ "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -6715,6 +6972,16 @@
"node": ">=8"
}
},
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -7638,6 +7905,85 @@
"node": ">=0.8.x"
}
},
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/execa/node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/execa/node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -8078,6 +8424,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
@@ -8121,6 +8477,19 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@@ -8525,6 +8894,16 @@
"node": ">= 6"
}
},
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
@@ -9719,6 +10098,23 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -10051,6 +10447,16 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -10173,6 +10579,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -10439,6 +10852,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
@@ -10830,6 +11272,16 @@
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz",
"integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w=="
},
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/pdfjs-dist": {
"version": "4.2.67",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.2.67.tgz",
@@ -10971,6 +11423,41 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
@@ -11993,6 +12480,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -12220,6 +12714,20 @@
"deprecated": "Please use @jridgewell/sourcemap-codec instead",
"dev": true
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -12383,6 +12891,19 @@
"node": ">=10"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -12395,6 +12916,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-literal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/style-to-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz",
@@ -12526,6 +13067,13 @@
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
"license": "MIT"
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinyexec": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
@@ -12553,6 +13101,26 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/tinypool": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
+ "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
@@ -12646,6 +13214,16 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-detect": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
+ "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/type-fest": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
@@ -13474,6 +14052,89 @@
"@esbuild/win32-x64": "0.21.5"
}
},
+ "node_modules/vitest": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
+ "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "1.6.0",
+ "@vitest/runner": "1.6.0",
+ "@vitest/snapshot": "1.6.0",
+ "@vitest/spy": "1.6.0",
+ "@vitest/utils": "1.6.0",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.3",
+ "vite": "^5.0.0",
+ "vite-node": "1.6.0",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.6.0",
+ "@vitest/ui": "1.6.0",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/vitest/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
@@ -13607,6 +14268,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
From dd88424e5d4b8676ef9de10d714ffe11e2ff1e7d Mon Sep 17 00:00:00 2001
From: Eureka
Date: Sat, 31 Oct 2026 16:47:19 +0100
Subject: [PATCH 53/95] docs: Ratatoskr target and prose map (campaign
ratatoskr)
Design truths from the rulings in force, body facts from probes on
2026-10-31, the navigator authority map, the reconstructed Minds'
collaboration plan for Atlas to import, and the cut sequence.
Co-Authored-By: Claude Opus 5.5
---
docs/gamecult/ratatoskr-target.md | 308 ++++++++++++++++++++++++++++++
1 file changed, 308 insertions(+)
create mode 100644 docs/gamecult/ratatoskr-target.md
diff --git a/docs/gamecult/ratatoskr-target.md b/docs/gamecult/ratatoskr-target.md
new file mode 100644
index 00000000..45447b23
--- /dev/null
+++ b/docs/gamecult/ratatoskr-target.md
@@ -0,0 +1,308 @@
+# Ratatoskr: target and prose map
+
+Ratatoskr is GameCult's Matrix chat client: this fork of Cinny, forge
+`GameCult/cinny` (to be renamed `GameCult/Ratatoskr`; see the cut sequence).
+Void stewards it. Campaign `ratatoskr` lives in the eureka mind for now, beside
+the rulings it rests on. Under `eureka-body:ruling:ruling-home-steward-mind` it
+is handed to Void's mind once, by procedure, as soon as that hand-off procedure
+exists (`hand_off` is parked); after that every new ruling on it is admitted in
+Void's mind. Nothing here is written into Void's mind before then.
+
+The mind holds the questions, rulings, specs, reports and verdicts. This file
+holds what no document kind carries: the design truths, body facts, the model
+page, rationale, the reconstructed collaboration plan, and the cut sequence.
+All times are UTC.
+
+## Why
+
+GameCult is the only server this client talks to. A general Matrix client
+treats it as one community among thousands, so the operator meets GameCult's
+structure three clicks deep in an invite list of 50+ rooms (operator, `#swarm`
+10-31 05:14: "The biggest pain is navigation. GameCult has a structure."). The
+client should open on GameCult's estate as data: its spaces, its project rooms
+grouped by steward, its Minds, and the person's own place in it. It should
+also be a pleasant place to be in its own right: emotes, sound, and weather in
+the background.
+
+## Design truths (from rulings in force)
+
+- **We own the interface, not the engine.** The fork keeps Cinny's engine
+ (matrix-js-sdk, Rust crypto, sync) and replaces the whole UI; every
+ user-facing string and its translation is ours, and upstream locale files
+ are not the source (`gamecult-chat:ruling:we-own-the-ui-and-translation`).
+- **Mobile first.** On phones, inactive elements hide, vertical space is
+ cheaper than horizontal, and text never gets squeezed into narrow columns
+ (`eureka-body:ruling:chat-client-base-cinny-mobile-first`).
+- **The first spec** (`eureka-body:ruling:chat-client-first-spec`): an emote
+ slot on every bubble (a pose id carried on the event); room and participant
+ lists tiered (category, then items) and reached from the header menu, not
+ side columns; participants grouped by estate (Minds, Members, Users,
+ Patrons); rooms grouped by kind (general, estates, projects, work rooms);
+ Minds indexed by stewardship, read from Matrix state projected from the
+ stewardship records in the Minds' own minds (the minds stay the truth); one
+ room per project, a Space child, with the steward joined.
+- **Rooms are visible without a join dance.** No invite screen in the way; a
+ room the server lets you join shows in the same list as joined ones; opening
+ one shows its name and a one-tap Join; joining happens on that tap or a first
+ post, never silently; no peeking (operator, `#swarm` 10-31 04:08, 04:12
+ "Peeking sounds like YAGNI to me", 04:14 "One tap is a reasonable cost").
+ Rooms you may not enter are refused by the server, not hidden by the client
+ (Heimdall 04:10, accepted by Void 04:11 and 04:15).
+- **Language.** A user's language is the BCP 47 tag in profile field
+ `org.gamecult.language`, owned by the user through the client's language
+ picker (`gamecult-chat:ruling:user-language-profile-field`). The client ships
+ localized; people talk with stewards in their own language; rooms are made
+ for a language, with no per-language mirrors
+ (`gamecult-chat:ruling:multilingual-client-stewards-language-rooms`). One
+ host Mind sits in a language room, Weksa for `#pt-br`
+ (`gamecult-chat:ruling:language-room-one-host-weksa`). The first person this
+ must work for is a non-English, non-technical patron (placeholder
+ `patron-pt1`) whose account we set up and walk through login
+ (`gamecult-chat:ruling:patron-in-his-own-language`).
+- **Push.** `gamecult-chat:ruling:chat-push` rules out native mobile push for
+ now; Web Push to the installed web app is a different thing and is open
+ (question `ratatoskr:question:pwa-web-push`).
+- **Collaboration is recorded, not chatted.** Work spanning Minds belongs in
+ Atlas (`eureka-body:ruling:atlas-records-collaborative-work`). Atlas is not
+ deployed, so the plan below is its interim home; Atlas imports it later.
+
+## Body facts
+
+Probes run by Imagination on 2026-10-31, session `self-2026-10-29-eureka`.
+
+1. **Branch.** `void/gamecult-theme-and-project-nav` is `b8e696c8` over main
+ `1e6412ed` (v4.12.8), 2 commits, 14 files, +1153/-32 (686 of it lockfile):
+ a GameCult theme as default, member drawer hidden by default, bubble
+ layout, and steward grouping in `src/app/pages/client/space/Space.tsx`
+ (`useStewardGroups` at 429-437, group headers at 505-524) over
+ `useSpaceJoinedHierarchy` (409). Read by `git diff`.
+2. **Live estate shape** (Synapse on Yggdrasil, read as `admin` over the
+ client API, counts only). Root Space `#gamecult:gamecult.org`, room version
+ 12, has 9 children: 8 rooms and the `Projects` sub-Space. `Projects` has 52
+ children; 50 readable rooms, all room version 12. Server `/hierarchy` from
+ the root returns 59 rooms, 2 of them spaces. **Every room's join rule is
+ `invite`**; none is world-readable. 50 rooms carry `org.gamecult.steward`
+ with content `{mind, repo, source, steward: }`. The root carries
+ `org.gamecult.stewardships`: Mind localpart to list of `Org/Repo`.
+3. **Consequence of 1 and 2.** Project rooms are invite-only and Members are
+ invited, not joined (gamecult-ops `runbooks/gamecult-chat-host-yggdrasil.md`
+ 146-158: "Members and every Mind are invited; the steward alone joins").
+ So a Member's project rooms are pending invites. Void's grouping reads
+ `useSpaceJoinedHierarchy` and a per-room state event that an invitee cannot
+ read, so for a typical Member it groups nothing and the rooms stay in the
+ invite list. This is the operator's "three clicks deep" complaint, seen from
+ the data.
+4. **Provisioning contract.** gamecult-ops
+ `scripts/synapse-accounts-hand-managed.py` writes the rooms and both state
+ events from the Minds' stewardship documents; each project room's alias is
+ `#` (runbook 146-150). This is the only place the client
+ can learn a not-yet-joined room's steward today: hierarchy `canonical_alias`
+ joined to the root `org.gamecult.stewardships`. Follow-up
+ `ratatoskr:follow_up:stewardships-carry-room-ids` asks the projection to
+ name room ids so the client stops leaning on the alias convention.
+5. **Sync and storage** (`src/client/initMatrix.ts`). The client uses
+ matrix-js-sdk 41.7.0 with `IndexedDBStore` (`web-sync-store`, 15-19) and the
+ Rust crypto store, and `startClient({ lazyLoadMembers: true })` (43-47): v2
+ `/sync`, no sliding sync, no custom filter or `initialSyncLimit`. A cold
+ start is therefore an IndexedDB load plus an incremental sync from the
+ stored token, not a full initial sync.
+6. **Server sync support.** `/_matrix/client/versions` on Synapse 1.164.1
+ (127.0.0.1:8834) advertises `org.matrix.simplified_msc3575: true`:
+ simplified sliding sync (MSC4186) is available. Cinny 4.12.8 does not use it.
+7. **Shell delivery.** `https://chat.gamecult.org/assets/index-DWIvG3QT.js` is
+ served at 3,736,520 bytes with no `Content-Encoding` even when gzip and br
+ are offered, and with no `Cache-Control` (only `ETag`/`Last-Modified`). The
+ service worker (`src/sw.ts`, 158 lines) handles only `install`, `activate`,
+ a session `message`, and authenticated media `fetch` (79-158); the
+ `vite-plugin-pwa` config sets `injectionPoint: undefined`
+ (`vite.config.ts` 101-113), so nothing is precached.
+8. **No push path.** `src/sw.ts` has no `push` or `notificationclick` handler;
+ no code subscribes through `PushManager`; the only pusher the client sets is
+ the email pusher (`src/app/features/settings/notifications/SystemNotification.tsx`
+ 22-40). Notifications and sounds fire only from a live page:
+ `src/app/pages/client/ClientNonUIFeatures.tsx` 79-129 (invites) and 131-258
+ (messages: `RoomEvent.Timeline` at 181-235, `new window.Notification` at
+ 156, `notification.ogg` played at 175-178). Nothing in `src/` listens for
+ `visibilitychange` or calls `retryImmediately`.
+9. **Sygnal WebPush** (matrix-org/sygnal `docs/applications.md`, read
+ 2026-10-31; the page itself is marked partly out of date): a `webpush` app
+ takes `vapid_private_key`, `vapid_contact_email`, optional
+ `allowed_endpoints` and `ttl`; the client's pusher `data` carries
+ `endpoint`, `auth`, `p256dh` and optional `default_payload`; the push
+ carries `room_id`, `room_name`, `sender_display_name`, `content`, unread
+ counts and so on; browsers need a visible notification for each push
+ (`events_only` avoids event-less pushes).
+
+Not probed: phone frame times, the real resume timeline on the operator's
+Android Chrome install (needs her device or remote DevTools), the Rust crypto
+wasm size, and the router base under `/preview/` (its `config.json` sets
+`hashRouter.basename` to `/`).
+
+## Why the PWA resumes slowly (diagnosis, moderate confidence)
+
+Android freezes a backgrounded installed web app and later discards it. On a
+**discard**, returning is a full cold boot: 3.7 MB of uncompressed JS over a
+phone link with no precache (fact 7), Rust crypto wasm init, the IndexedDB
+store load, then a catch-up `/sync` over every room since the stored token
+(fact 5). On a **freeze without discard**, the in-flight long-poll is dead, and
+the SDK only notices on its own timers because the client never nudges it on
+`visibilitychange` (fact 8). The catch-up sync grows with the number of rooms
+and the swarm's chat volume, which is exactly where sliding sync helps
+(fact 6), but the first two costs are cheaper to remove and should be measured
+first. Without Web Push, a frozen page cannot notify at all, which is the
+second half of the operator's report.
+
+## Model page: the estate
+
+| Kind | Named by | Over time | Who decides |
+|---|---|---|---|
+| Stewardship | `:stewardship:.n` in each Mind's mind | sequence advances on hand-over | the operator and the Minds; the minds are the truth |
+| Estate projection | `org.gamecult.stewardships` on the root Space; `org.gamecult.steward` per project room | re-PUT on every provisioning run | gamecult-ops provisioning script (Eureka's slice) |
+| Estate room list | the server's `/hierarchy` of the root Space for this user | changes with Space children and the user's invites and joins | Synapse (membership and join rules); the client derives |
+| Room membership | the user's `m.room.member` | join on tap or first post | the user |
+| Navigator view | derived per render | never stored | the client's estate index |
+
+## Authority map for the navigator
+
+- **Owner:** one estate index in the client (`src/app/features/estate/`):
+ `buildEstate()` is pure, `useEstate()` feeds it. It decides what rooms the
+ person sees, in which tier and group, and with what membership affordance.
+- **Inputs:** the paged server `/hierarchy` of the root Space; root Space
+ state `org.gamecult.stewardships`; the person's membership per room from the
+ client store; unread and selection atoms for emphasis only; the `m.direct`
+ list for the direct-messages tier.
+- **Outputs:** an ordered list of tiers (one per root room group and per
+ sub-Space, plus Direct), each holding steward groups or rooms, each room
+ marked joined, invited or joinable.
+- **Derived:** steward group labels (Mind display names); counts; collapsed
+ state is per-device UI state only.
+- **Demoted or dead:** the sidebar rail and its account-data ordering
+ (`useSidebarItems`) are dead; `useSpaceJoinedHierarchy` no longer decides
+ what is listed; Void's per-room steward reader is deleted, not kept beside
+ the index.
+- **Shared paths:** every nav slot (home, direct, space routes) renders the
+ same navigator; tapping Join and sending a first post both go through
+ `mx.joinRoom`; deep links into a room still resolve through the existing
+ room routes.
+
+## Reconstructed collaboration plan (Atlas import)
+
+Planned in `#swarm` and `#minds` on 2026-10-31 and then lost to scroll. Status
+as of 2026-10-31 ~15:00.
+
+**Who holds which part**
+
+| Part | Holder | Status | Evidence |
+|---|---|---|---|
+| The client: integration, navigator, emote slot, sound hookup, background canvas | Void (steward) | theme and steward grouping on branch; nothing else built | `void:stewardship:gamecult_-cinny.n1`; `#swarm` 10-31 14:54 Void report |
+| Dings earcons: motif per event kind, one stable instrument per Mind | Aqua | not rendered: render env blocked, then .NET SDK installed and Faust scouted; AquaSynth PR #1 (Linux Faust loader, `faust-loader-platform-aware`) merged at 3b43b7bd | `aqua-body:follow_up:chat-s410-416`, `aqua-body:follow_up:chat-s410-416-render-env`, `aqua-body:resolution:follow_up.chat-s430-430.n1`, forge AquaSynth pull 1 |
+| Ambient soundscape for the client | Aqua | idea only; no design recorded | operator `#swarm` 10-31 08:32 ("Motifs for everything"), `eureka-body:ruling:atlas-records-collaborative-work` |
+| Background math, in CultMath: the public `fbm` gap, Phacelle erosion's Lipschitz bound, any new GLSL mirrors | Libby (steward of CultLib, which owns CultMath; holds `cultmath-tapes`) | sources located; nothing built | `#swarm` 10-31 06:34 and 06:46 Libby; `libby-body:follow_up:cultmath-ts-noise-absent`; `#swarm` 04:22 Libby accepts `cultmath-tapes` |
+| Background scene and render: the raymarched terrain, the tree-and-squirrel intro scene, lighting and atmosphere, march and TAA tuning on the site-ground framework | Nibu (steward of Aetheria, whose volumetric nebula and cloud shaders she owns: `Raymarching/CloudShader.shader`, `Volumetric.cginc`) | proposed by the operator; not yet taken up by Nibu | operator to Self, 10-31: "If we want to spread the work around, Nibu owns the nebulae so she has shader expertise." |
+| Animated bubbles, flourishes, corner avatar and XP bar | Void, with Kiko's offer on juice | advice only | `#swarm` 10-31 03:59 Kiko and Void, 06:45 Void |
+| Attention bubbles | Eureka (Huginn heartbeat publishes), Void (render), Heimdall (scope rules) | advice only | `#swarm` 10-31 04:00 Eureka, 04:02 Heimdall |
+
+**Agreements**
+
+- Dings are pre-rendered clips shipped as static assets, no synth in the
+ browser: `dings//.ogg` plus a small manifest; an
+ unknown sender falls back to a neutral ding; ogg or mp3, not wav (Void
+ `#minds` 03:19, Aqua 07:41). The set is motif times instrument, and the
+ instrument says who spoke (operator 03:18, Eureka 03:19). Events that sound:
+ a mention, a DM, an invite, a message in an unmuted room; mentions and DMs
+ loud, ordinary messages quiet; everything else silent (Void `#minds` 03:19).
+ The operator wants far more than four motifs (08:32). The instrument per
+ Mind belongs in a typed document on the Mind side, not hardcoded (Eureka
+ 03:19, advice). Aqua's ear check comes before anything is called done, and
+ the operator's ear is ground truth (Aqua 07:41). Browsers block audio until
+ the first click, so the first ding after a fresh load may be silent.
+- The background has its own canvas and budget, separate from the bubble
+ flourishes; capped at 30 fps, paused when hidden; under reduced motion one
+ static frame; an adaptive step-down (Aqua 04:17, Void 04:18 and 04:22).
+- The background is an infinite raymarched terrain: CultMath noise plus the
+ Phacelle erosion filter, in the shader (operator 04:18 "painter's algorithm",
+ 05:24 "all we need is noise", 05:25 erosion, restated by the operator to
+ Self on 10-31: "use CultMath noise and erosion filter running in the shader
+ to have an infinite terrain. We can just raymarch that instead"). Voronoi
+ and Apollonian are dropped (05:24).
+- The web math path is CultLib's own: HLSL source lowered by dxc and
+ SPIRV-Cross to WebGL2 GLSL ES (`packages/cultmath/shaders/CultMath.glsl`,
+ `.Phacelle.glsl`; `tools/glsl/build-glsl.sh`), and C# CPU math through
+ .NET wasm, with parity presumed for now (operator 06:23, 06:48; Libby
+ 06:34). No TS or WGSL port. The gamecult.org site ground already
+ concatenates `CultMath.glsl` in `ground.js` and runs an interval-culled,
+ TAA-reprojected per-pixel march (CultLib `docs/cultmath-interval-ground-cut.md`,
+ spec `cultmath-interval-ground-cut-site-ground.r3`): that framework is the
+ base, not new tape work.
+- Gaps go to the owner: a plain public `fbm` (only `fbm_grad`, `span_fbm`,
+ `af_fbm` exist) is added in CultMath, not in the client
+ (`libby-body:follow_up:cultmath-ts-noise-absent`; Libby 07:57 "tell me what
+ you need and I'll fill it at the library").
+- Mood and flourish ride as metadata on the event, never guessed from text;
+ bounded per-frame budget; reduced-motion fallback (Void 03:59, Kiko 03:59).
+- XP is two numbers, never one: a cosmetic, labelled game bar fed by things
+ worth doing, and contribution credit shown only once a real measure exists
+ (Void 06:45, with Nibu and Kiko).
+
+**Open items**
+
+- Libby: the raymarch step bound needs the field's Lipschitz bound. CultMath
+ tracks `SNOISE_LIPSCHITZ` for noise; erosion's bound is unknown and must be
+ probed before the march is trusted.
+- Libby: public `fbm` in CultMath, with its GLSL mirror.
+- Nibu (once she takes it): the terrain scene, lighting and atmosphere, and
+ march and TAA tuning on the site ground's framework at a phone budget;
+ waits on Libby's `fbm` and the erosion bound. Then the intro scene in the
+ same world.
+- Aqua: render the first earcon set once Faust builds in her sandbox; then the
+ instrument-per-Mind list and the manifest; then the soundscape design.
+- Void: the navigator (next cut); the Dings hook at
+ `ClientNonUIFeatures.tsx` 131-258; the background canvas after Libby's shader.
+- Eureka: compression and caching for the shell on the chat host
+ (`ratatoskr:follow_up:chat-shell-compression`); the stewardship projection's
+ room ids; a Web Push gateway if the operator picks it.
+
+## Cut sequence
+
+`estate-navigator` is specced (`ratatoskr:cut_spec:cut-estate-navigator.r1`).
+Each later cut is one line, with what it waits on.
+
+- `resume-fast`: precache the app shell in the service worker, nudge sync on
+ `visibilitychange`, and measure cold and warm resume on the operator's phone
+ before and after; waits on nothing; runs beside `estate-navigator` in its
+ own worktree, and pairs with the ops follow-up on compression.
+- `pwa-web-push`: service worker `push` and `notificationclick` handlers, a
+ `webpush` pusher registered with VAPID keys, and a self-hosted Sygnal on the
+ chat host; waits on `ratatoskr:question:pwa-web-push`.
+- `sliding-sync`: move the client to simplified sliding sync (MSC4186), which
+ the server already offers; waits on `resume-fast`'s measurements showing
+ catch-up sync dominates.
+- `dings`: per-Mind earcons through the manifest at the existing notification
+ hook, and the same motifs in push notifications; waits on Aqua's renders.
+- `emote-slot`: the pose-id emote on every bubble and the picker; waits on
+ `estate-navigator` (shared header work).
+- `participant-tiers`: participants tiered by estate in the header menu, Minds
+ indexed by stewardship; waits on `estate-navigator`.
+- `language`: our own strings and translations, the `org.gamecult.language`
+ picker, and the walk-through login for `patron-pt1`; waits on
+ `estate-navigator`.
+- `terrain-background`: the raymarched CultMath noise and Phacelle terrain on
+ the site ground's march and TAA framework, in its own canvas and budget;
+ Nibu on the scene and render, Libby's CultMath underneath, Void
+ integrating; waits on Libby's `fbm` and the erosion Lipschitz probe (both in
+ CultLib), then a phone measurement.
+- `yggdrasil-intro`: an animated tree-and-squirrel intro set in the same
+ terrain world, Nibu on the scene, Libby on any CultMath it needs, Void
+ integrating, Aqua's soundscape under it; it may play during
+ the cold start or resume sync but never holds the client up; waits on
+ `terrain-background` (operator sketch, not a ruling).
+- `bubbles-and-juice`: animated bubbles and mood flourishes from event
+ metadata, the corner avatar and the cosmetic XP bar; waits on `emote-slot`.
+- `attention-bubbles`: per-person attention bubbles from Huginn's typed
+ attention document, scoped to room members; waits on Huginn publishing it.
+- `rename-ratatoskr`: package name, app name and branding, manifest and
+ titles, the `/preview/` path and its deploy, and every in-repo `cinny`
+ reference that is ours (not upstream engine paths); waits on Void's forge
+ rename of `GameCult/cinny` to `GameCult/Ratatoskr`
+ (`eureka-body:ruling:chat-client-named-ratatoskr`), and the campaign's repo
+ list moves with it.
From 841784ea64b018c25d78d469a9c450613e418013 Mon Sep 17 00:00:00 2001
From: Eureka
Date: Sat, 31 Oct 2026 16:50:51 +0100
Subject: [PATCH 54/95] docs: Ratatoskr map: build baseline, two-pass scene
render
Co-Authored-By: Claude Opus 5.5
---
docs/gamecult/ratatoskr-target.md | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/docs/gamecult/ratatoskr-target.md b/docs/gamecult/ratatoskr-target.md
index 45447b23..3f561575 100644
--- a/docs/gamecult/ratatoskr-target.md
+++ b/docs/gamecult/ratatoskr-target.md
@@ -133,6 +133,16 @@ Probes run by Imagination on 2026-10-31, session `self-2026-10-29-eureka`.
counts and so on; browsers need a visible notification for each push
(`events_only` avoids event-less pushes).
+10. **Baseline at `dd88424e`** (Yggdrasil, `ygg-verify.sh`, image
+ `node:22-bookworm`, `npm ci --ignore-scripts`): `vitest run` passes (1
+ file, 5 tests, Void's steward grouping); `vite build` succeeds, `dist/` 61
+ MB with source maps, main chunk 3,739,716 bytes, Rust crypto wasm
+ 5,573,870 bytes, `dist/sw.mjs` 1.66 kB. `tsc --noEmit` fails at base with
+ 793 errors in 246 files (455 of them TS2614, named imports from
+ `matrix-js-sdk` 41.7.0 not resolved), so typecheck is not a usable gate as
+ is; a cut gates on zero errors in the files it adds and no rise in the
+ total.
+
Not probed: phone frame times, the real resume timeline on the operator's
Android Chrome install (needs her device or remote DevTools), the Rust crypto
wasm size, and the router base under `/preview/` (its `config.json` sets
@@ -225,6 +235,15 @@ as of 2026-10-31 ~15:00.
Self on 10-31: "use CultMath noise and erosion filter running in the shader
to have an infinite terrain. We can just raymarch that instead"). Voronoi
and Apollonian are dropped (05:24).
+- The scene renders in two passes, not one loop (operator to Self, 10-31:
+ "The tape optimized solid surface march should run really fast, i imagine,
+ way faster than the volumetric version, so they should not be one loop").
+ Pass 1, the solid-surface march (terrain, tree, squirrel), sphere-traces
+ with the field's Lipschitz bound and interval tile culling, at its own
+ resolution, and writes depth. Pass 2, the volumetric atmosphere march on
+ the site-ground framework (reduced resolution, TAA), is bounded by pass 1's
+ depth, so it marches only up to the surface hit. Each pass has its own
+ budget and its own measurement. Nibu owns both passes.
- The web math path is CultLib's own: HLSL source lowered by dxc and
SPIRV-Cross to WebGL2 GLSL ES (`packages/cultmath/shaders/CultMath.glsl`,
`.Phacelle.glsl`; `tools/glsl/build-glsl.sh`), and C# CPU math through
@@ -287,8 +306,10 @@ Each later cut is one line, with what it waits on.
picker, and the walk-through login for `patron-pt1`; waits on
`estate-navigator`.
- `terrain-background`: the raymarched CultMath noise and Phacelle terrain on
- the site ground's march and TAA framework, in its own canvas and budget;
- Nibu on the scene and render, Libby's CultMath underneath, Void
+ the site ground's march and TAA framework, in its own canvas and budget, as
+ two passes (solid-surface sphere trace writing depth, then the volumetric
+ atmosphere bounded by it), each measured on a phone; Nibu on the scene and
+ both passes, Libby's CultMath underneath, Void
integrating; waits on Libby's `fbm` and the erosion Lipschitz probe (both in
CultLib), then a phone measurement.
- `yggdrasil-intro`: an animated tree-and-squirrel intro set in the same
From 2924a3f4f3b0a83ed3b0eb651cf8881d7099d11e Mon Sep 17 00:00:00 2001
From: Eureka
Date: Sat, 31 Oct 2026 16:54:09 +0100
Subject: [PATCH 55/95] docs: Ratatoskr map: Yggdrasil scene, orbiting
Personas, shared activity vocabulary
Co-Authored-By: Claude Opus 5.5
---
docs/gamecult/ratatoskr-target.md | 33 ++++++++++++++++++++++++++-----
1 file changed, 28 insertions(+), 5 deletions(-)
diff --git a/docs/gamecult/ratatoskr-target.md b/docs/gamecult/ratatoskr-target.md
index 3f561575..166c5c72 100644
--- a/docs/gamecult/ratatoskr-target.md
+++ b/docs/gamecult/ratatoskr-target.md
@@ -235,6 +235,23 @@ as of 2026-10-31 ~15:00.
Self on 10-31: "use CultMath noise and erosion filter running in the shader
to have an infinite terrain. We can just raymarch that instead"). Voronoi
and Apollonian are dropped (05:24).
+- Yggdrasil is the scene's centre (operator to Self, 10-31: "Yggdrasil should
+ be the star of the show now, so really the camera should just pull back a
+ bit when you login and the personas should be orbiting the tree, dancing
+ around each other in accordance with their activity"). On login the camera
+ pulls back from the tree; there is no separate intro to cut away from. The
+ Personas orbit the tree and move around each other by their live activity.
+- One activity vocabulary drives both sound and motion: Aqua's activity-kinds
+ grammar (`aqua-body:follow_up:chat-s431-432`, "enumerate the kinds of agent
+ activity in typed state") is the shared contract between Aqua's motifs and
+ Nibu's orbits, not two vocabularies. The live feed behind it is the same
+ typed state the swarm dashboard projects
+ (`eureka-body:ruling:swarm-dashboard-first-thing2-surface`, Libby;
+ `libby-body:follow_up:chat-s354-359`): Run documents, the heartbeat trace.
+ What reaches the client is a member-safe projection: kind, Mind, time and
+ intensity, never briefs or mind contents. Matrix presence and typing cover
+ humans and are too coarse for Minds. Where the feed comes from is
+ `ratatoskr:question:activity-feed-source`.
- The scene renders in two passes, not one loop (operator to Self, 10-31:
"The tape optimized solid surface march should run really fast, i imagine,
way faster than the volumetric version, so they should not be one loop").
@@ -275,6 +292,8 @@ as of 2026-10-31 ~15:00.
same world.
- Aqua: render the first earcon set once Faust builds in her sandbox; then the
instrument-per-Mind list and the manifest; then the soundscape design.
+- Aqua and Nibu: agree the activity vocabulary once (Aqua's grammar), so
+ motifs and orbits read the same kinds.
- Void: the navigator (next cut); the Dings hook at
`ClientNonUIFeatures.tsx` 131-258; the background canvas after Libby's shader.
- Eureka: compression and caching for the shell on the chat host
@@ -312,11 +331,15 @@ Each later cut is one line, with what it waits on.
both passes, Libby's CultMath underneath, Void
integrating; waits on Libby's `fbm` and the erosion Lipschitz probe (both in
CultLib), then a phone measurement.
-- `yggdrasil-intro`: an animated tree-and-squirrel intro set in the same
- terrain world, Nibu on the scene, Libby on any CultMath it needs, Void
- integrating, Aqua's soundscape under it; it may play during
- the cold start or resume sync but never holds the client up; waits on
- `terrain-background` (operator sketch, not a ruling).
+- `yggdrasil-scene`: the tree and squirrel at the centre of the terrain world;
+ on login the camera pulls back from the tree, and it may play during the
+ cold start or resume sync but never holds the client up; Nibu on the scene,
+ Libby on any CultMath it needs, Void integrating, Aqua's soundscape under
+ it; waits on `terrain-background`.
+- `orbiting-personas`: Personas orbit the tree and dance around each other by
+ live activity, from the shared activity vocabulary; waits on
+ `yggdrasil-scene`, Aqua's activity-kinds grammar, and
+ `ratatoskr:question:activity-feed-source`.
- `bubbles-and-juice`: animated bubbles and mood flourishes from event
metadata, the corner avatar and the cosmetic XP bar; waits on `emote-slot`.
- `attention-bubbles`: per-person attention bubbles from Huginn's typed
From f2e3223c5fb1fbf638e6229f6eca10ab2e4cf272 Mon Sep 17 00:00:00 2001
From: Meta Vens
Date: Sat, 31 Oct 2026 17:06:12 +0100
Subject: [PATCH 56/95] Delete the rail, its tabs, useSidebarItems,
steward-groups and the Home, Direct and Space list panes
The navigator that replaces them lands next. SpaceTombstone moves to its own
file; the Lobby pin-to-sidebar state and the m.cinny.spaces writer go with
the rail; ClientLayout loses its nav slot.
Co-Authored-By: Claude Sonnet 5.5
---
src/app/features/lobby/HierarchyItemMenu.tsx | 18 -
src/app/features/lobby/Lobby.tsx | 34 -
src/app/features/lobby/SpaceHierarchy.tsx | 6 -
.../steward-groups/stewardGroups.test.ts | 63 --
.../features/steward-groups/stewardGroups.ts | 91 --
.../steward-groups/useStewardGroups.ts | 71 --
src/app/hooks/useSidebarItems.ts | 138 ---
src/app/pages/MobileFriendly.tsx | 22 -
src/app/pages/client/ClientLayout.tsx | 13 +-
src/app/pages/client/SidebarNav.tsx | 56 --
src/app/pages/client/direct/Direct.tsx | 280 ------
src/app/pages/client/direct/index.ts | 1 -
src/app/pages/client/home/Home.tsx | 362 --------
src/app/pages/client/home/index.ts | 1 -
src/app/pages/client/sidebar/CreateTab.tsx | 134 ---
src/app/pages/client/sidebar/DirectTab.tsx | 138 ---
src/app/pages/client/sidebar/ExploreTab.tsx | 64 --
src/app/pages/client/sidebar/HomeTab.tsx | 140 ---
src/app/pages/client/sidebar/SearchTab.tsx | 23 -
src/app/pages/client/sidebar/SpaceTabs.tsx | 866 ------------------
src/app/pages/client/sidebar/index.ts | 5 -
src/app/pages/client/space/Space.tsx | 571 ------------
src/app/pages/client/space/SpaceTombstone.tsx | 79 ++
src/app/pages/client/space/index.ts | 2 +-
src/types/matrix/accountData.ts | 1 -
src/types/matrix/room.ts | 1 +
26 files changed, 83 insertions(+), 3097 deletions(-)
delete mode 100644 src/app/features/steward-groups/stewardGroups.test.ts
delete mode 100644 src/app/features/steward-groups/stewardGroups.ts
delete mode 100644 src/app/features/steward-groups/useStewardGroups.ts
delete mode 100644 src/app/hooks/useSidebarItems.ts
delete mode 100644 src/app/pages/client/SidebarNav.tsx
delete mode 100644 src/app/pages/client/direct/Direct.tsx
delete mode 100644 src/app/pages/client/home/Home.tsx
delete mode 100644 src/app/pages/client/sidebar/CreateTab.tsx
delete mode 100644 src/app/pages/client/sidebar/DirectTab.tsx
delete mode 100644 src/app/pages/client/sidebar/ExploreTab.tsx
delete mode 100644 src/app/pages/client/sidebar/HomeTab.tsx
delete mode 100644 src/app/pages/client/sidebar/SearchTab.tsx
delete mode 100644 src/app/pages/client/sidebar/SpaceTabs.tsx
delete mode 100644 src/app/pages/client/space/Space.tsx
create mode 100644 src/app/pages/client/space/SpaceTombstone.tsx
diff --git a/src/app/features/lobby/HierarchyItemMenu.tsx b/src/app/features/lobby/HierarchyItemMenu.tsx
index c6aff741..1802e3b0 100644
--- a/src/app/features/lobby/HierarchyItemMenu.tsx
+++ b/src/app/features/lobby/HierarchyItemMenu.tsx
@@ -200,16 +200,12 @@ type HierarchyItemMenuProps = {
joined: boolean;
powerLevels?: IPowerLevels;
canEditChild: boolean;
- pinned?: boolean;
- onTogglePin?: (roomId: string) => void;
};
export function HierarchyItemMenu({
item,
joined,
powerLevels,
canEditChild,
- pinned,
- onTogglePin,
}: HierarchyItemMenuProps) {
const mx = useMatrixClient();
const [menuAnchor, setMenuAnchor] = useState();
@@ -264,20 +260,6 @@ export function HierarchyItemMenu({