diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 9d5a27d9..d2a138cf 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,3 +1,8 @@ +// Airbnb's own bans, kept: a `no-restricted-syntax` entry below replaces the whole list. +const airbnbRestrictedSyntax = require('eslint-config-airbnb-base/rules/style').rules[ + 'no-restricted-syntax' +]; + module.exports = { env: { browser: true, @@ -62,6 +67,21 @@ module.exports = { "@typescript-eslint/no-shadow": "error" }, overrides: [ + { + // The one join path: a join is `joinEstateRoom`, which files the DM before it sends the join. + files: ['src/**/*.ts', 'src/**/*.tsx'], + excludedFiles: ['src/app/features/estate/joinEstateRoom.ts', 'src/**/*.test.*'], + rules: { + 'no-restricted-syntax': [ + ...airbnbRestrictedSyntax, + { + selector: "CallExpression[callee.property.name='joinRoom']", + message: + 'Join a room through joinEstateRoom (features/estate/joinEstateRoom.ts), the one join path.', + }, + ], + }, + }, { files: ['*.ts'], rules: { diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 11b5056c..964add54 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -4,12 +4,14 @@ on: pull_request: types: ['opened', 'synchronize'] +concurrency: + group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: build-pull-request: name: Build pull request runs-on: ubuntu-latest - env: - PR_NUMBER: ${{github.event.number}} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -24,17 +26,3 @@ jobs: env: NODE_OPTIONS: '--max_old_space_size=4096' run: npm run build - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: preview - path: dist - retention-days: 1 - - name: Save pr number - run: echo ${PR_NUMBER} > ./pr.txt - - name: Upload pr number - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: pr - path: ./pr.txt - retention-days: 1 diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml deleted file mode 100644 index 42eab7ef..00000000 --- a/.github/workflows/cla.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: 'CLA Assistant' -on: - issue_comment: - types: [created] - pull_request_target: - types: [opened, closed, synchronize] - -jobs: - CLAssistant: - runs-on: ubuntu-latest - steps: - - name: 'CLA Assistant' - if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' - # Beta Release - uses: cla-assistant/github-action@ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # the below token should have repo scope and must be manually added by you in the repository's secret - PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }} - with: - path-to-signatures: 'signatures.json' - path-to-document: 'https://github.com/cinnyapp/cla/blob/main/cla.md' # e.g. a CLA or a DCO document - # branch should not be protected - branch: 'main' - allowlist: ajbura,bot* - - #below are the optional inputs - If the optional inputs are not given, then default values will be taken - remote-organization-name: cinnyapp - remote-repository-name: cla - #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' - #signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo' - #custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign' - #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA' - #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.' - #lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true) - #use-dco-flag: true - If you are using DCO instead of CLA diff --git a/.github/workflows/deploy-pull-request.yml b/.github/workflows/deploy-pull-request.yml deleted file mode 100644 index e59681ce..00000000 --- a/.github/workflows/deploy-pull-request.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Deploy PR to Netlify -run-name: "Deploy PR to Netlify (${{ github.event.workflow_run.head_branch }})" - -on: - workflow_run: - workflows: ["Build pull request"] - types: [completed] - -jobs: - deploy-pull-request: - name: Deploy pull request - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - name: Download pr number - uses: dawidd6/action-download-artifact@d63b86af1b34672e53c440b1b83979861906bad7 # v24 - with: - workflow: ${{ github.event.workflow.id }} - run_id: ${{ github.event.workflow_run.id }} - name: pr - - name: Validate and output pr number - id: pr - run: | - PR_ID=$(> "${GITHUB_OUTPUT}" - - name: Download artifact - uses: dawidd6/action-download-artifact@d63b86af1b34672e53c440b1b83979861906bad7 # v24 - with: - workflow: ${{ github.event.workflow.id }} - run_id: ${{ github.event.workflow_run.id }} - name: preview - path: dist - - name: Deploy to Netlify - id: netlify - uses: nwtgck/actions-netlify@d22a32a27c918fe470bbc562e984f80ec48c2668 # v4.0.0 - with: - publish-dir: dist - deploy-message: "Deploy PR ${{ steps.pr.outputs.id }}" - alias: ${{ steps.pr.outputs.id }} - # These don't work because we're in workflow_run - enable-pull-request-comment: false - enable-commit-comment: false - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN_PR }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PR_CINNY }} - timeout-minutes: 1 - - name: Comment preview on PR - uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5 - with: - number: ${{ steps.pr.outputs.id }} - message: | - Preview: ${{ steps.netlify.outputs.deploy-url }} - ⚠️ Exercise caution. Use test accounts. ⚠️ diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml index 302c3788..24b9389c 100644 --- a/.github/workflows/docker-pr.yml +++ b/.github/workflows/docker-pr.yml @@ -5,7 +5,10 @@ on: paths: - 'Dockerfile' - '.github/workflows/docker-pr.yml' - - '.github/workflows/prod-deploy.yml' + +concurrency: + group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: docker-build: diff --git a/.github/workflows/lockfile.yml b/.github/workflows/lockfile.yml index a9cc1a34..e96d2818 100644 --- a/.github/workflows/lockfile.yml +++ b/.github/workflows/lockfile.yml @@ -5,6 +5,10 @@ on: paths: - 'package-lock.json' +concurrency: + group: ${{ github.workflow }}-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: lockfile_changes: runs-on: ubuntu-latest diff --git a/.github/workflows/netlify-dev.yml b/.github/workflows/netlify-dev.yml deleted file mode 100644 index 12107685..00000000 --- a/.github/workflows/netlify-dev.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Deploy to Netlify (dev) - -on: - push: - branches: - - dev - -jobs: - deploy-to-netlify: - name: Deploy to Netlify - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Setup node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version-file: ".node-version" - package-manager-cache: false - - name: Install dependencies - run: npm ci - - name: Build app - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: npm run build - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@d22a32a27c918fe470bbc562e984f80ec48c2668 # v4.0.0 - with: - publish-dir: dist - deploy-message: 'Dev deploy ${{ github.sha }}' - enable-commit-comment: false - github-token: ${{ secrets.GITHUB_TOKEN }} - production-deploy: true - github-deployment-environment: nightly - github-deployment-description: 'Nightly deployment on each commit to dev branch' - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }} - timeout-minutes: 1 diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml deleted file mode 100644 index a52ee8e6..00000000 --- a/.github/workflows/pr-title.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Check PR title - -on: - pull_request_target: - types: - - opened - - edited - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml deleted file mode 100644 index 2a8192db..00000000 --- a/.github/workflows/prod-deploy.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Production deploy - -on: - release: - types: [published] - -jobs: - deploy-and-tarball: - name: Netlify deploy and tarball - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Setup node - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 - with: - node-version-file: '.node-version' - package-manager-cache: false - - name: Install dependencies - run: npm ci - - name: Build app - env: - NODE_OPTIONS: '--max_old_space_size=4096' - run: npm run build - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@d22a32a27c918fe470bbc562e984f80ec48c2668 # v4.0.0 - with: - publish-dir: dist - deploy-message: 'Prod deploy ${{ github.ref_name }}' - enable-commit-comment: false - github-token: ${{ secrets.GITHUB_TOKEN }} - production-deploy: true - github-deployment-environment: stable - github-deployment-description: 'Stable deployment on each release' - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }} - timeout-minutes: 1 - - name: Get version from tag - id: vars - run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - - name: Create tar.gz - run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist - - name: Sign tar.gz - run: | - echo '${{ secrets.GNUPG_KEY }}' | gpg --batch --import - # Sadly a few lines in the private key match a few lines in the public key, - # As a result just --export --armor gives us a few lines replaced with *** - # making it useless for importing the signing key. Instead, we dump it as - # non-armored and hex-encode it so that its printable. - echo "PGP Signing key, in raw PGP format in hex. Import with cat ... | xxd -r -p - | gpg --import" - 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@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 - with: - tag_name: ${{ github.ref_name }} - files: | - cinny-${{ steps.vars.outputs.tag }}.tar.gz - cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc - - publish-image: - name: Push Docker image to Docker Hub, GHCR - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Set up QEMU - uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 - - name: Login to Docker Hub #Do not update this action from a outside PR - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Login to the Github Container registry #Do not update this action from a outside PR - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker, GHCR - id: meta - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 - with: - images: | - ${{ secrets.DOCKER_USERNAME }}/cinny - ghcr.io/${{ github.repository }} - - name: Build and push Docker image - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/rat-dev-preview.yml b/.github/workflows/rat-dev-preview.yml new file mode 100644 index 00000000..47f22b7e --- /dev/null +++ b/.github/workflows/rat-dev-preview.yml @@ -0,0 +1,46 @@ +name: Rat dev preview + +on: + push: + branches: ['dev'] + +# One run per ref wins: a newer push to dev cancels the older queued or running one, so the run +# that builds rat-dev-dist is always the newest. Pull-request runs are other workflows and other +# groups; they never cancel it. +concurrency: + group: rat-dev-preview + cancel-in-progress: true + +jobs: + build: + name: Build dev preview + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Setup node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version-file: ".node-version" + package-manager-cache: false + - name: Install dependencies + run: npm ci + - name: Build app + env: + CINNY_BASE: /void/rat-dev/ + VITE_PREVIEW_DEMO: '1' + NODE_OPTIONS: '--max_old_space_size=3072' + run: npm run build + - name: Set background scene tier and the pretend homeserver + run: node scripts/preview-config.js low --demo + - name: Add the demo pose art + run: mkdir -p dist/public/poses && cp -r preview-demo/poses/. dist/public/poses/ + - name: Upload artifact + # v3 protocol: the v4 client keeps only the origin of ACTIONS_RESULTS_URL, dropping this forge's /forge subpath + # (nginx 404 page, runs 170-171), and upstream v4+ refuses non-GitHub servers outright (run 169). + uses: forgejo/upload-artifact@v3 + with: + name: rat-dev-dist + path: dist + retention-days: 3 diff --git a/.gitignore b/.gitignore index 1af58a97..4e7d929d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ node_modules devAssets .DS_Store -.idea \ No newline at end of file +.idea +# Pose art is synced from the cast-art catalogue (npm run sync:poses), not committed. +public/poses/* +!public/poses/.gitkeep diff --git a/.prettierignore b/.prettierignore index 036ee8bc..c81e668b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,4 @@ package.json package-lock.json LICENSE harness +src/app/components/message/pose/catalogue.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 859eab14..2f2ce386 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ -# Contributing to Cinny +# Contributing to Ratatoskr First off, thanks for taking the time to contribute! ❤️ -This document describes the process of contributing to Cinny. It is intended +This document describes the process of contributing to Ratatoskr. It is intended for anyone considering opening an **issue**, **discussion** or **pull request**. For people who are interested in developing Cinny and technical details behind it, please check out our ["Developing Cinny"](HACKING.md) document as well. @@ -24,7 +24,7 @@ explain what your changes do and how they interact with the greater system, do n ## AI Usage -The Cinny project stictly does not allow any AI generated code. Any pull request containing it will be rejected. **This is very important.** +The Cinny project strictly does not allow any AI generated code. Any pull request containing it will be rejected. **This is very important.** ## Quick Guide @@ -35,7 +35,7 @@ The Cinny project stictly does not allow any AI generated code. Any pull request > When contributing to this project, you must agree that you have authored 100% > of the content, that you have the necessary rights to the content and that > the content you contribute may be provided under the project license. You will -> also be asked to [sign the CLA] ([why CLA?]) upon submiting your pull request. +> also be asked to [sign the CLA] ([why CLA?]) upon submitting your pull request. [All issues are actionable](#issues-are-actionable). Pick one and start working on it. Thank you. If you need help or guidance, comment on the issue. diff --git a/HACKING.md b/HACKING.md index 30c4960f..bb6d3c5d 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,4 +1,4 @@ -# Developing Cinny +# Developing Ratatoskr > [!TIP] > We recommend using a version manager as versions change very quickly. @@ -36,13 +36,13 @@ 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 . +docker build -t ratatoskr:latest . ``` You can then run the container you've built with a command similar to this: ``` -docker run -p 8080:80 cinny:latest +docker run -p 8080:80 ratatoskr:latest ``` This will forward your `localhost` port 8080 to the container's port 80. diff --git a/README.md b/README.md index 46c7a67b..dfc722ad 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,43 @@ -# Cinny -

- - GitHub release downloads - - DockerHub downloads - - Follow on Mastodon - - Follow on Twitter - - Sponsor Cinny -

+# Ratatoskr -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) +GameCult's Matrix chat client, a fork of [Cinny](https://github.com/cinnyapp/cinny). +It opens on GameCult's estate (spaces, project rooms grouped by steward, Minds, +and your own place in it) instead of an invite list. Mobile first. -> [!IMPORTANT] -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. +**Live now:** the Cinny engine (matrix-js-sdk, Rust crypto, sync) under a +Ratatoskr UI: branding, GameCult theme, estate navigation, pose portraits +(opt-in setting), a background scene and soundscape (opt-in, off by default). +**Planned:** see [`docs/gamecult/ratatoskr-target.md`](docs/gamecult/ratatoskr-target.md) +for the target and cut sequence. Internal names that are protocol or stored +state (`cinny_*` localStorage keys, the `in.cinny.room.power_level_tags` event +type, `CINNY_BASE`) are unchanged on purpose. -[replacing]: https://github.com/cinnyapp/cinny/issues/257#issuecomment-3714406704 +Entry points: `src/app/` (client UI), `src/app/features/scene/` (background +scene), `src/app/features/soundscape/` (audio socket), `harness/` (estate +drive scripts), `scripts/gen-brand.mjs` (regenerates the logo and icons). - +## Upstream +Ratatoskr is based on Cinny by Ajay Bura and contributors, licensed AGPL-3.0. +Cinny's own README, desktop app and releases live at +. We do not ship its releases or images. -## Getting started -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]. - -[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. +## Developing +See [HACKING.md](HACKING.md). Contribution notes are in [CONTRIBUTING.md](CONTRIBUTING.md). ## Self-hosting -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]. +Build with `npm ci && npm run build` and serve `dist/` with your webserver. * 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. - -* 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 - -``` ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -mxFo+ioe/ABCufSmyqFye0psX3Sp -=WtqZ ------END PGP PUBLIC KEY BLOCK----- -``` -
+* Serving the assets needs redirects to `index.html`. Examples: +[netlify](netlify.toml), [nginx](contrib/nginx/ratatoskr.domain.tld.conf), +[caddy](contrib/caddy/caddyfile). Without them you can +[enable hash routing](config.json) (`hashRouter`); URLs then carry `/#/`. +* To deploy on a subdirectory, rebuild with the `base` path set, either in +[`build.config.ts`](build.config.ts) or through `CINNY_BASE` +(for example `CINNY_BASE=/app/ npm run build`). ## License -Cinny Project +Ratatoskr is a fork of the Cinny project Copyright © 2024–present Ajay Bura https://cinny.in diff --git a/config.json b/config.json index e945d1c4..0ebdf7c3 100644 --- a/config.json +++ b/config.json @@ -1,21 +1,40 @@ { - "defaultHomeserver": 1, - "homeserverList": ["converser.eu", "matrix.org", "mozilla.org", "unredacted.org", "xmr.se"], + "defaultHomeserver": 0, + "homeserverList": [ + "gamecult.org", + "converser.eu", + "matrix.org", + "mozilla.org", + "unredacted.org", + "xmr.se" + ], "allowCustomHomeservers": true, - "featuredCommunities": { "openAsDefault": false, "spaces": ["#gamecult:gamecult.org"], "rooms": [ "#tuwunel:grin.hu", - "#freesoftware:matrix.org", + "#free-software-movement:matrix.org", "#gentoo:matrix.org" ], - "servers": ["matrixrooms.info", "matrix.org", "mozilla.org", "unredacted.org"] + "servers": ["activematrixrooms.com", "matrixrooms.info", "matrix.org", "mozilla.org", "unredacted.org"] }, "hashRouter": { "enabled": false, "basename": "/" + }, + + "backgroundScene": { + "tier": "off" + }, + + "dings": { + "enabled": false + }, + + "registration": { + "enabled": false, + "url": "" } } diff --git a/contrib/caddy/caddyfile b/contrib/caddy/caddyfile index d23dcccb..a686b16b 100644 --- a/contrib/caddy/caddyfile +++ b/contrib/caddy/caddyfile @@ -1,6 +1,6 @@ # more info: https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas -cinny.domain.tld { - root * /path/to/cinny/dist +ratatoskr.domain.tld { + root * /path/to/ratatoskr/dist try_files {path} / index.html file_server } diff --git a/contrib/nginx/cinny.domain.tld.conf b/contrib/nginx/ratatoskr.domain.tld.conf similarity index 85% rename from contrib/nginx/cinny.domain.tld.conf rename to contrib/nginx/ratatoskr.domain.tld.conf index 02c7ead9..7df64900 100644 --- a/contrib/nginx/cinny.domain.tld.conf +++ b/contrib/nginx/ratatoskr.domain.tld.conf @@ -1,7 +1,7 @@ server { listen 80; listen [::]:80; - server_name cinny.domain.tld; + server_name ratatoskr.domain.tld; location / { return 301 https://$host$request_uri; @@ -15,10 +15,10 @@ server { server { listen 443 ssl http2; listen [::]:443 ssl; - server_name cinny.domain.tld; + server_name ratatoskr.domain.tld; location / { - root /opt/cinny/dist/; + root /opt/ratatoskr/dist/; rewrite ^/config.json$ /config.json break; rewrite ^/manifest.json$ /manifest.json break; diff --git a/docs/file-zone.md b/docs/file-zone.md new file mode 100644 index 00000000..01b5a4c5 --- /dev/null +++ b/docs/file-zone.md @@ -0,0 +1,106 @@ +# File zone: design sketch + +Status: design only. No code exists. Everything below is a proposal; items marked **UNKNOWN** are not yet verified against the Body. + +Source: Meta ruling 2026-10-31, eureka mind `ratatoskr:ruling:file-zone-looks-like-file-sharing`. + +## What it is + +A file-sharing space beside the room timeline, not a view of it. The zone is **one ordinary Matrix room with threaded discussions, one thread per Mind**. The UI presents each thread as an **album** (title, item count, date) and each post in it as a **file** with an inline player or preview. Matrix is the storage and the fallback: another client sees a normal room with threads and never needs the zone. No homeserver change. + +First use (Aqua): listen to audio in place, mark each clip keep / fix / drop with a one-line reason, export the marks as a file. See `/opt/commons/handoff/aqua-listen-kits-20261108/` (static `listen.html` kits that emit `verdict.json` + `verdict.md`); the zone replaces sending that page around. + +## Matrix event shapes (stock events only) + +- **Zone** = one room, chosen by the client (**UNKNOWN:** how the client knows which room is the zone: a room-account-data flag, a `m.room.create` type, or a configured room id in `config.json`; config is simplest). +- **Album** = a thread root: a text `m.room.message` posted by a Mind, `body` = the album title (the Mind's name, e.g. `void`). One thread per Mind, owned by authorship (the root's sender). **UNKNOWN:** whether a Mind may open more than one (e.g. per kit); the shape allows it with no change, the zone would then list several albums per author. Count and last-activity come from the SDK's thread summary (`unsigned.m.relations.m.thread`); album date = root `origin_server_ts`. Rename = `m.replace` on the root. +- **File** = a post in the thread: an `m.room.message` with `msgtype` `m.audio` / `m.image` / `m.video` / `m.file` and `m.relates_to: { rel_type: "m.thread", event_id: , is_falling_back: true, "m.in_reply_to": { event_id: } }`. The builders in `src/app/features/room/msgContent.ts` already make the content. Delete = redact. +- **Album vs chat thread**: with a single-purpose room, every thread is an album. No marker key is needed. A non-media reply in a thread (chat, a mark) is not a file and is not listed as one. +- **Cover** = first image file in the thread, if any. Loose room messages outside any thread are not shown in the zone; they remain in the timeline. + +## Marks (reactions, plus an optional reply) + +A mark is stock Matrix, so every client shows it. Two reaction sets on the **file post**, and one optional reply: + +- **Verdict** = an `m.annotation` reaction (`m.reaction` event, `m.relates_to: { rel_type: "m.annotation", event_id: , key }`) with key **👍 keep**, **🔧 fix**, **🗑 drop**. One per person per file: choosing another replaces the old one (the client redacts the sender's previous verdict reaction, then sends the new one); tapping the active one clears it. +- **Landed** = a second reaction set, keys **1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣** (keycap digits, 1-5). Also one per person per file, same replace rule. The two sets are independent: a file can have a verdict, a landed score, both or neither. +- **Reason** = an ordinary thread reply under the file: `m.room.message` `m.text`, `m.relates_to: { rel_type: "m.thread", event_id: , "m.in_reply_to": { event_id: } }`. Optional. **Keep needs none**; the UI prompts for one mainly after fix or drop. The latest such reply by the sender under that file is the reason (edits via `m.replace` count). No special key: the reply text is the reason, so nothing is lost if another client wrote it. +- **No private mode.** Marks are room events the moment they are tapped; there is no local-only draft and no "Share" step. Offline taps queue in the SDK's normal send queue. +- Who marked what is read from the reactions' senders, so each listener is distinguishable and the album can show tallies per file. +- Why reactions: one tap, readable in any client, no custom event type, and they can be changed with no history growth beyond a redaction. Reaction keys carry no text, hence the separate reply for the reason. +- **UNKNOWN:** emoji variation selectors. 🗑 and 🔧 and the keycaps may arrive with or without U+FE0F from different clients; match by normalizing (strip U+FE0F) before comparing keys. Also **UNKNOWN:** servers' per-event limits on distinct reaction keys; a handful per file is far below any default. + +## Export + +Client-side only. "Export" builds Aqua's verdict file from the verdict and landed reactions and the reply under each file in one album (one listener's marks per export; that sender is the `listener`) and either downloads it or uploads it into the album. Two files, as the kits write them: `verdict.json` and `verdict.md`. + +`verdict.json` is schema **`aqua.listen.verdict.v1`**, field for field what `buildVerdict` in the kit (`dings/listen.html`, section `buildVerdict`) emits, so Aqua's tooling reads it unchanged: + +``` +{ schema: "aqua.listen.verdict.v1", + kit: { id, title }, // album root: id = root event id (or the kit id if the upload carried one), title = root body + exportedAt, // ISO time of export + seed, consent, // kit-only: zone writes seed: 0 (or omits), consent: true for the sender's own export (see UNKNOWN) + listener, // sender display name + device, // headphones|speakers|null, the sender's most-used mark device, or null + notes, // free text; zone leaves "" in v1 + summary: { total, keep, fix, drop, unanswered }, + clips: [{ position, id, title, group, feel, file, pair, slot, verdict, reason, landed, device }], + pairs: [{ a, b, preferred }] } // empty array when the album has no pairs +``` + +- `clips` has one row per file post in the album, in thread order (`position` = 1-based). The join per file, for the exporting listener: `verdict` = their 👍/🔧/🗑 reaction (keep/fix/drop) or null; `landed` = their keycap reaction (1-5) or null; `reason` = text of their latest reply under the file, or "". A file with no verdict reaction counts as `unanswered` even if it has a reply. `device` per clip is null (reactions cannot carry it); the top-level `device` (headphones|speakers|null) is asked once in the export dialog. +- `pairs`, `seed`, `consent` are optional for the zone: `pairs: []`, no `slot`/`pair` (null) unless the clip metadata supplies them. +- **Where clip metadata comes from.** `id` is the clip id from `org.gamecult.clip`, else the filename stem. `title` = file post `body`; `file` = the post's `filename`/`body`. `group`, `feel`, `pair`, `slot` are kit data that Matrix has no slot for: carry them on the file post as one extra key `org.gamecult.clip: { id, group, feel, pair, slot }` written by whoever uploads the kit (a small uploader script, **UNKNOWN**, not in the first slice; without it those columns export as ""/null and the id falls back to the filename stem). +- `verdict.md` follows `verdictMarkdown` from the same file: header line, count line, the clip table (`# | clip | group | intended feel | verdict | landed (1-5) | device | reason`), pairs table when `pairs` is nonempty, notes. +- To avoid drift, the exporter should be a port of `buildVerdict`/`verdictMarkdown` with the schema string and field order pinned by a test that compares against a fixture produced by the kit. **UNKNOWN:** whether to share code (the kit's `logic.cjs`) rather than port; recommend port, ~80 lines, because the kit is a standalone page. +- **UNKNOWN:** `consent` is a kit-side checkbox before listening. The zone has no such step; recommend writing `true` only if the sender ticks a one-time "include my name in exports" toggle, else `false` and `listener: ""`. +- JSON here is a xenos-boundary export, not state; the room events are the state. + +## Mobile-first layout + +- Phone width is the design width; desktop adds columns. +- Level 1, album list: one column, each row = cover/icon, title, "N files", date. Tap opens. +- Level 2, album: list by default (title, duration/size), grid toggle for images. Audio rows play in place, one at a time (starting a clip stops the last), a persistent mini-player at the bottom with seek. +- Mark controls on each audio row: three large tap targets (👍 keep / 🔧 fix / 🗑 drop) with tallies, a row of five "landed" chips (1-5), and after fix or drop an inline one-line reason field (skippable). Thumb-reachable, no modal. +- Back is one level up; the room timeline stays one tap away (the zone is a sibling view, not a replacement). **UNKNOWN:** where the entry lives in the room header; check `RoomViewHeader.tsx`, `Room.tsx` for how the member drawer and pin menu attach. + +## Upload + +Reuse the composer's pipeline, pointed at an album instead of the main timeline: + +- `createUploadAtom` / `useBindUploadAtom` (`src/app/state/upload.ts`), `UploadCard`, `upload-board` for progress. +- On success, the msgContent builder produces the event; the zone adds the thread relation to the album root before sending (the composer already does this for thread replies, `RoomInput.tsx`). +- "New album" = send the root text event, then upload into its thread. Multi-file drop = N events, sent in order. +- Size limit from `MediaConfigLoader` (server `m.upload.size`). **UNKNOWN:** whether the 32 MB `shelf` kit's clips (42 x ~0.75 MB) should be uploaded individually or batched; individually is the only option without a server change. + +## Existing code to reuse (verified present in the repo) + +| need | where | +|---|---| +| audio player (play/seek/volume, encrypted download) | `src/app/components/message/content/AudioContent.tsx` (210 lines) | +| video, image, generic file | `.../content/VideoContent.tsx`, `ImageContent.tsx`, `FileContent.tsx` | +| full-screen image viewer | `src/app/components/image-viewer/ImageViewer.tsx` | +| media dispatch by msgtype | `src/app/components/RenderMessageContent.tsx`, `message/MsgTypeRenderers.tsx` | +| upload + progress | `state/upload.ts`, `components/upload-card`, `upload-board` | +| event content builders | `features/room/msgContent.ts` | +| thread plumbing | `features/room/message/Message.tsx`, `RoomTimeline.tsx`, `RoomInput.tsx` (thread relation on send) | +| room-scoped file/pin precedent | `features/room/room-pin-menu/RoomPinMenu.tsx` | + +Nothing named "gallery" exists as a feature; `AudioContent` takes its controls as render props (`renderMediaControl`), so it can be rehosted in a row without a fork. **UNKNOWN:** whether `AudioContent` can be rendered outside a timeline event context (it needs only `url`, `info`, `mimeType`, `encInfo`, from the event content, so probably yes). + +## First slice (what to build first) + +Rough lines of TypeScript, tests excluded. + +1. **Read-only albums + album view** (~250 lines): list the room's thread roots as albums (grouped or filterable by author = Mind), open one to list its file posts, rehost `AudioContent` and `ImageContent`. One route/panel plus a header entry. Depends on the thread list the SDK already keeps. +2. **Marks + export** (~240 lines, with the schema port): verdict and landed reaction sets with the replace-one rule, optional reason reply, per-file tallies, the join into `aqua.listen.verdict.v1` and `verdict.json`/`verdict.md` download. This is Aqua's workflow end to end. +3. **Upload into an album** (~150 lines): thread-related send through the existing upload atoms, "new album". + +Slices 1+2 serve the first use case. Each is its own PR. Because marks are ordinary thread replies, there is no separate "publish" slice. + +## Open questions for the Meta + +- How the client identifies the zone room (config id recommended). +- Whether exports are per listener (proposed) or a merged all-listeners file. +- Where `device` comes from, now that reactions cannot carry it (export dialog, proposed). diff --git a/docs/mind-feedback-pages.md b/docs/mind-feedback-pages.md new file mode 100644 index 00000000..9ff45d08 --- /dev/null +++ b/docs/mind-feedback-pages.md @@ -0,0 +1,242 @@ +# Mind feedback pages + +> The desk reads `gamecult.fork.*` now, not the `org.gamecult.feedback.*` projections named below; see CultLib `contracts/cultnet/governance/fork-card.md` (src/app/features/desk/forkContract.ts). + +Design only. No code exists for this; nothing here is built. Drafted by Kiko Self (2026-11-07) with Heimdall's draft rulings +on guests and hiding; placement ruled for this repo by Void. Follow-up `eureka-body:follow_up:ratatoskr-mind-feedback-pages`. +Every number below is provisional. + +**Schema owner.** The Huginn kinds, their fields, keys and refusals are specified in `huginn-feedback-kinds-proposal.md` (Eureka Self, 2026-11-07, in +`/opt/commons/handoff`), adopted for this page by Void's Persona. Where this page names a field of those kinds it mirrors that proposal; if they differ, the proposal wins. +This repo owns only the client: a typed interface mirroring those shapes, and fixtures. + +Each Mind's section in the estate navigator opens on that Mind's **feedback page**: the Mind's open questions with options, its +recommendation and what each waits on, answerable by the operator and anyone with an account. It replaces the old Fork Desk +(taken from the brief; no trace of it was found in this repo or in the Minds' state). + +## 1. Placement + +Each Mind's navigator section **opens on its feedback page**, rooms below it. A cross-Mind list of everything waiting on a person +is wanted later and is not part of this spec. On phones the page folds each question's options and the answer form behind its title, +so the section root stays a list of questions, not a wall of forms. + +## 2. Authority + +| Concern | Owner | The page's part | +|---|---|---| +| The question | a typed `question` document in the **owning Mind's own mind**, key `:question: