From 3de49512e4435b17a19e3366ae7789dbe19d3b90 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 29 May 2026 08:48:59 -0500 Subject: [PATCH 01/24] chore(#9): prepare public repo, container CI, and releases (#10) Closes #9 (Repository Foundation milestone). Adds the public-repo collateral ahead of flipping visibility: - Apache-2.0 `LICENSE` + `NOTICE`; `package.json` metadata (description, license, repository, homepage, bugs). - `.github/`: CI (validate + docker + release jobs), issue/PR templates, CODEOWNERS, dependabot. - `Dockerfile` + nginx static-serve config + `.dockerignore`. - `CONTRIBUTING`, `SECURITY`, `CODE_OF_CONDUCT`; README/AGENTS/docs refresh. Known follow-ups handled on a separate cleanup branch (not in scope here): - CI `validate` currently runs test/build/audit; `lint` + `typecheck` steps land with the ESLint/Prettier setup. - Code-level branding fixes (`src/App.tsx` footer, `src/geometry/profile.ts` + manifest Tracefinity framing). --- .dockerignore | 9 + .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 45 +++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 29 +++ .github/dependabot.yml | 17 ++ .github/pull_request_template.md | 13 ++ .github/release.yml | 38 ++++ .github/workflows/ci.yml | 177 ++++++++++++++++++ .gitignore | 5 + AGENTS.md | 4 +- CODE_OF_CONDUCT.md | 31 ++++ CONTRIBUTING.md | 41 +++++ Dockerfile | 22 +++ LICENSE | 201 +++++++++++++++++++++ NOTICE | 5 + README.md | 31 +++- SECURITY.md | 26 +++ docker/nginx.conf | 16 ++ docs/AGENT-HANDOFF.md | 11 +- docs/SESSION-STATE.md | 13 +- docs/TODO.md | 22 ++- package-lock.json | 56 ++---- package.json | 13 +- 24 files changed, 771 insertions(+), 60 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/release.yml create mode 100644 .github/workflows/ci.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 SECURITY.md create mode 100644 docker/nginx.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fad6efe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.ai/ +.git/ +.github/ +.idea/ +coverage/ +dist/ +node_modules/ +npm-debug.log* +result.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e7d84f9 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @KofTwentyTwo diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..0377373 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,45 @@ +name: Bug report +description: Report a reproducible problem in Benchfinity. +title: "Bug: " +labels: + - bug +body: + - type: textarea + id: summary + attributes: + label: Summary + description: What happened? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: List the exact steps needed to reproduce the problem. + placeholder: | + 1. Open... + 2. Set... + 3. Export... + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior + validations: + required: true + - type: input + id: browser + attributes: + label: Browser and OS + placeholder: "Chrome on macOS" + - type: textarea + id: notes + attributes: + label: Additional context diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..cbfacf5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/BenchFinity/workbench/security/advisories/new + about: Report security issues privately when private vulnerability reporting is available. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..ecbcc6f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,29 @@ +name: Feature request +description: Suggest an improvement or new Benchfinity capability. +title: "Feature: " +labels: + - enhancement +body: + - type: textarea + id: problem + attributes: + label: Problem or workflow + description: What user workflow should this improve? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed solution + description: Describe the behavior you want. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + description: What should be true when this is complete? diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c44f90c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 + groups: + npm-development: + dependency-type: development + npm-production: + dependency-type: production + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..6d815cf --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Summary + +- + +## Validation + +- [ ] `npm run test` +- [ ] `npm run build` +- [ ] `npm audit` + +## Notes + +- diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..8affdde --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,38 @@ +# GitHub release note generation. +changelog: + exclude: + labels: + - ignore-for-release + - duplicate + - invalid + authors: + - dependabot + - dependabot[bot] + categories: + - title: Breaking Changes + labels: + - breaking-change + - breaking + - title: Features + labels: + - enhancement + - feature + - title: Fixes + labels: + - bug + - fix + - title: CI and Release + labels: + - ci + - release + - docker + - title: Documentation + labels: + - documentation + - docs + - title: Dependencies + labels: + - dependencies + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..88ffc96 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,177 @@ +name: CI + +on: + pull_request: + branches: + - develop + - main + - "release/**" + - "rc/**" + push: + branches: + - develop + - main + - "feature/**" + - "release/**" + - "rc/**" + workflow_dispatch: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: validate + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Test + run: npm run test + + - name: Build + run: npm run build + + - name: Audit dependencies + run: npm audit + + docker: + name: docker + runs-on: ubuntu-latest + needs: validate + if: github.event_name == 'push' + permissions: + contents: read + packages: write + outputs: + image: ${{ steps.meta.outputs.image }} + tags: ${{ steps.meta.outputs.tags }} + version: ${{ steps.meta.outputs.version }} + release_tag: ${{ steps.meta.outputs.release_tag }} + release_name: ${{ steps.meta.outputs.release_name }} + prerelease: ${{ steps.meta.outputs.prerelease }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Compute image tags + id: meta + shell: bash + run: | + version="$(node -p "require('./package.json').version")" + short_sha="${GITHUB_SHA::7}" + image="ghcr.io/${GITHUB_REPOSITORY,,}" + branch="${GITHUB_REF_NAME}" + release_tag="" + release_name="" + prerelease="false" + + if [[ "${branch}" == "main" ]]; then + tags="${image}:${version}" + release_tag="v${version}" + release_name="Benchfinity v${version}" + elif [[ "${branch}" == "develop" ]]; then + tags="${image}:${version}-SNAPSHOT.${short_sha}"$'\n'"${image}:develop" + elif [[ "${branch}" == release/* || "${branch}" == rc/* ]]; then + tags="${image}:${version}-rc.${short_sha}" + release_tag="v${version}-rc.${short_sha}" + release_name="Benchfinity v${version} RC ${short_sha}" + prerelease="true" + elif [[ "${branch}" == feature/* ]]; then + safe_branch="$(printf '%s' "${branch#feature/}" | tr '[:upper:]' '[:lower:]' | sed -E 's#[^a-z0-9_.-]+#-#g; s#-+#-#g; s#(^-|-$)##g')" + tags="${image}:${version}-${safe_branch}-${short_sha}-SNAPSHOT" + else + safe_branch="$(printf '%s' "${branch}" | tr '[:upper:]' '[:lower:]' | sed -E 's#[^a-z0-9_.-]+#-#g; s#-+#-#g; s#(^-|-$)##g')" + tags="${image}:${version}-${safe_branch}-${short_sha}-SNAPSHOT" + fi + + { + echo "image=${image}" + echo "version=${version}" + echo "release_tag=${release_tag}" + echo "release_name=${release_name}" + echo "prerelease=${prerelease}" + echo "tags<> "${GITHUB_OUTPUT}" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: | + org.opencontainers.image.title=Benchfinity + org.opencontainers.image.description=Gridfinity-compatible baseplate generator + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.version=${{ steps.meta.outputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max + + release: + name: release + runs-on: ubuntu-latest + needs: docker + if: github.event_name == 'push' && needs.docker.outputs.release_tag != '' + permissions: + contents: write + steps: + - name: Publish GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.docker.outputs.release_tag }} + RELEASE_NAME: ${{ needs.docker.outputs.release_name }} + PRERELEASE: ${{ needs.docker.outputs.prerelease }} + IMAGE: ${{ needs.docker.outputs.image }} + IMAGE_TAGS: ${{ needs.docker.outputs.tags }} + shell: bash + run: | + if gh release view "${TAG}" -R "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + echo "Release ${TAG} already exists" + exit 0 + fi + + body="$(printf 'Container image:\n\n%s\n\nCommit: %s\n' "${IMAGE_TAGS}" "${GITHUB_SHA}")" + + gh api -X POST "repos/${GITHUB_REPOSITORY}/releases" \ + -f tag_name="${TAG}" \ + -f target_commitish="${GITHUB_SHA}" \ + -f name="${RELEASE_NAME}" \ + -f body="${body}" \ + -F prerelease="${PRERELEASE}" \ + -F generate_release_notes=true diff --git a/.gitignore b/.gitignore index 45337be..d157c23 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ node_modules/ dist/ .DS_Store result.json +.ai/ +.idea/ +coverage/ +.vite/ +npm-debug.log* diff --git a/AGENTS.md b/AGENTS.md index 5ffe9fc..a869051 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,9 @@ ## Project Context -Benchfinity is a personal KofTwentyTwo project for generating Gridfinity-compatible baseplates, split print bundles, and Bambu Studio-style 3MF files. V1 is a local browser app. The next phase is the Workbench version with accounts, projects, saved designs, export history, and a QQQ/Postgres backend. +Benchfinity is a BenchFinity project for generating Gridfinity-compatible baseplates, split print bundles, and Bambu Studio-style 3MF files. V1 is a local browser app. The next phase is the Workbench version with accounts, projects, saved designs, export history, and a QQQ/Postgres backend. -No external issue ticket is required for this personal project unless James asks for one. +Use GitHub Issues for new project work unless James explicitly asks to skip issue tracking. ## Required Startup Reads diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a23e8ad --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,31 @@ +# Code of Conduct + +## Our Pledge + +Project participants are expected to make participation in this community respectful and harassment-free for everyone, regardless of background, identity, experience level, or viewpoint. + +## Our Standards + +Expected behavior includes: + +- Being respectful and constructive. +- Giving and accepting feedback in good faith. +- Focusing discussion on the project and the work. +- Taking responsibility when a mistake affects others. + +Unacceptable behavior includes: + +- Harassment, intimidation, or discriminatory language. +- Personal attacks or sustained disruption. +- Publishing private information without permission. +- Conduct that would reasonably be considered inappropriate in a professional setting. + +## Enforcement + +Instances of unacceptable behavior may be reported through the repository owner profile or other maintainer contact path. Reports will be reviewed as promptly and fairly as practical. + +Maintainers may remove comments, close issues, reject contributions, or restrict participation when needed to protect the project community. + +## Attribution + +This code is adapted from the Contributor Covenant, version 2.0. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0bd9b99 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to Benchfinity + +Benchfinity is a TypeScript, React, and Vite app for generating Gridfinity-compatible workbench baseplates. Contributions should keep the existing V1 generator stable while moving the Workbench roadmap forward through GitHub Issues. + +## Development Setup + +Prerequisites: + +- Node.js 22 or newer +- npm +- Git + +Commands: + +```bash +npm install +npm run test +npm run build +npm audit +``` + +Use `npm run dev` for local browser verification. + +## Workflow + +- Work from an issue when possible. +- Branch from `develop` using `feature/{issue-number}-{short-description}`. +- Keep pull requests focused and small enough to review. +- Use conventional commit subjects. +- Do not use the Bambu Studio CLI for automated validation on this project. Validate 3MF behavior through tests, package inspection, and manual GUI import when needed. + +## Quality Bar + +- Keep `npm run test`, `npm run build`, and `npm audit` green. +- Preserve existing STL, ZIP, 3MF, geometry, and preview behavior unless the issue explicitly changes it. +- Add focused tests for geometry, export, validation, and persistence behavior when touched. +- Keep `src/App.tsx` centered on top-level state and orchestration. Use `src/components`, `src/geometry`, and `src/export` for focused implementation. + +## Legal + +By contributing, you agree that your contribution is your original work, that you have the right to submit it, and that it can be distributed under this repository's license. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..958bc33 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM node:22-alpine AS deps +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +FROM node:22-alpine AS builder +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +RUN npm run build + +FROM nginxinc/nginx-unprivileged:1.27-alpine + +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 8080 + +HEALTHCHECK CMD wget -qO- http://127.0.0.1:8080/ || exit 1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d303041 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on or derived from the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. Do not include + the brackets. The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same printed page as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 James Maes + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..9055ab5 --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Benchfinity +Copyright 2026 James Maes + +Benchfinity is a project for generating Gridfinity-compatible +workbench baseplates and export bundles. diff --git a/README.md b/README.md index 4308528..ca695b5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Benchfinity -Browser app for generating Gridfinity-compatible workbench baseplates as STL, ZIP, and 3MF files. +[![CI](https://github.com/BenchFinity/workbench/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/BenchFinity/workbench/actions/workflows/ci.yml) +[![License](https://img.shields.io/github/license/BenchFinity/workbench)](LICENSE) + +Benchfinity is a browser app for generating Gridfinity-compatible workbench baseplates as STL, split ZIP bundles, and Bambu Studio-style 3MF files. + +The V1 app runs locally in the browser. The next Workbench phase adds accounts, projects, saved designs, export history, and a QQQ/Postgres backend. ## Commands @@ -9,8 +14,20 @@ npm install npm run dev npm run test npm run build +npm audit +``` + +## Docker + +Benchfinity publishes container images to GitHub Container Registry. + +```bash +docker build -t benchfinity-workbench . +docker run --rm -p 8080:8080 benchfinity-workbench ``` +The production image serves the built Vite app on port `8080`. Published images use `ghcr.io/benchfinity/workbench`. + ## Architecture - `src/App.tsx` owns top-level state, defaults, validation, and export orchestration. @@ -42,3 +59,15 @@ npm run build - Printer bed presets grouped by brand, with Bambu Lab H2C left-nozzle selected by default and a `Custom` option for manual dimensions. - Edge-open underside connector notches with a separate connector key STL. - Bambu Studio-style 3MF export with one printable plate per generated tile, plus STL export for single plates and ZIP export for split plates. + +## Branch And Release Flow + +- `develop` is the default branch for integration work. +- `feature/*` branches publish snapshot container images tagged with the sanitized branch name and short commit SHA. +- `develop` publishes a snapshot image and the `develop` image tag. +- `release/*` and `rc/*` branches publish RC images and prerelease GitHub Releases. +- `main` publishes the clean package version image tag and a stable GitHub Release. + +## License + +Benchfinity is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..15efb85 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,26 @@ +# Security Policy + +## Supported Versions + +Benchfinity is pre-1.0 software. Security fixes are applied to the active development line on `develop` and to the latest published release when one exists. + +## Reporting a Vulnerability + +Do not report security vulnerabilities in public issues. + +Use GitHub private vulnerability reporting if it is available for this repository, or contact the maintainer directly through the repository owner profile. + +Please include: + +- A description of the vulnerability. +- Steps to reproduce it. +- The affected version, branch, or commit. +- Any known impact or workaround. + +The maintainer will acknowledge valid reports as quickly as practical and coordinate a fix before public disclosure. + +## Security Expectations + +- Do not commit secrets, tokens, credentials, or private project files. +- Do not include sensitive printer, account, or artifact data in logs. +- Keep dependency audit findings at zero before release handoff. diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..368b15f --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /assets/ { + try_files $uri =404; + add_header Cache-Control "public, max-age=31536000, immutable"; + } +} diff --git a/docs/AGENT-HANDOFF.md b/docs/AGENT-HANDOFF.md index 31f92f8..701d648 100644 --- a/docs/AGENT-HANDOFF.md +++ b/docs/AGENT-HANDOFF.md @@ -4,6 +4,8 @@ Benchfinity V1 is complete, audited, tested, and pushed as the foundation for the larger Workbench phase. The local app generates Gridfinity-compatible baseplates, preview meshes, STL exports, split ZIP exports, and Bambu Studio-style 3MF packages with one plate per tile and connector-key objects when split. +The repository now uses `develop` as the default branch. Workbench VNext planning has moved into GitHub Issues #1 through #8, grouped by the `Workbench VNext` milestone and the `Benchfinity Roadmap` project. Repository setup work is tracked in GitHub Issue #9. The repository home is `BenchFinity/workbench`. + ## Most Important Ground Truth - Use `Benchfinity` as the product and repo name. @@ -31,10 +33,11 @@ Expected results: ## Next Best Work 1. Start the Workbench phase from `docs/WORKBENCH-VNEXT.md`. -2. Decide backend repo/app shape for QQQ/Postgres integration. -3. Add account, project, workbench item, baseplate design, printer profile, and export artifact persistence. -4. Keep the existing V1 generator as the first workbench item type rather than rewriting the geometry/export core. -5. Add an artifact-storage decision before server-side generation. +2. Finish and merge GitHub Issue #9 on `feature/9-public-repo-ci`. +3. Return to GitHub Issue #1 for the backend repo/app shape decision. +4. Add account, project, workbench item, baseplate design, printer profile, and export artifact persistence through the roadmap issues. +5. Keep the existing V1 generator as the first workbench item type rather than rewriting the geometry/export core. +6. Add an artifact-storage decision before server-side generation. ## Open Cautions diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index ae18a51..0ca8005 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -1,10 +1,10 @@ # Session State ## Current Branch -`feature/no-ticket-gridfinity-baseplate-generator` +`feature/9-public-repo-ci` ## Status -V1 implementation is complete and the audit follow-up is closed. The codebase is structured as a stable foundation for the larger Workbench/Benchfinity phase. +V1 implementation is complete and the audit follow-up is closed. The repository now uses `develop` as the default branch, and Workbench VNext work is tracked in GitHub Issues under the `Workbench VNext` milestone and the `Benchfinity Roadmap` project. Repository foundation work is tracked in GitHub Issue #9. ## Implemented - Benchfinity Vite, React, TypeScript app scaffold. @@ -34,6 +34,8 @@ V1 implementation is complete and the audit follow-up is closed. The codebase is - Unit/export tests, including Bambu-style 3MF package structure and a manifold edge regression for generated tile meshes. - Regression coverage confirms open-bottom split tiles keep connector pads aligned with the underside notches. - `docs/WORKBENCH-VNEXT.md` captures the QQQ/Postgres-backed account, project, and workbench direction for the next version. +- GitHub Issues #1 through #8 mirror the Workbench VNext TODO list. +- GitHub Issue #9 tracks public repository setup, Docker packaging, CI, GHCR image publishing, and release automation. ## Verification - `npm run test` passes, 34 tests. @@ -43,3 +45,10 @@ V1 implementation is complete and the audit follow-up is closed. The codebase is - Do not use the installed Bambu Studio CLI for automated validation. Its `--info` mode can trigger macOS crash reports even when the log says the 3MF loaded. Use package-structure tests and manual Bambu Studio GUI import checks instead. - Regression checks confirm split 3MF exports have one plate record per tile plus connector plate, and that build transforms spread the objects into non-overlapping plate lanes. - Regression checks cover rotated single STL exports, rotated split ZIP STL exports, connector-key quantities, 3MF metadata content types, connector-key 3MF object placement, and connector-key plate validation. + +## GitHub Workflow +- Default branch: `develop`. +- Active feature branch: `feature/9-public-repo-ci`. +- Work issue: #9, `Prepare public repo, container CI, and releases`. +- Roadmap project: `Benchfinity Roadmap`. +- Repository home: `BenchFinity/workbench`. diff --git a/docs/TODO.md b/docs/TODO.md index 0c3046b..e833366 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -21,11 +21,17 @@ ## Benchfinity Workbench Next Phase -- [ ] Confirm backend repo/app shape for QQQ/Postgres. -- [ ] Define account, user, membership, project, workbench item, baseplate design, printer profile, and export artifact tables. -- [ ] Decide artifact storage for generated STL, ZIP, and 3MF files. -- [ ] Build the signed-in app shell with account/project navigation. -- [ ] Preserve the V1 generator as the first workbench item type. -- [ ] Persist baseplate design inputs and derived layout metadata. -- [ ] Add export history with download actions. -- [ ] Add backend and frontend tests for account scoping, project CRUD, design save/load, and export metadata. +The Workbench backlog now lives in GitHub Issues under the `Workbench VNext` milestone and the `Benchfinity Roadmap` GitHub Project. + +- [ ] #1 Confirm Workbench backend repo and app shape. +- [ ] #2 Define Workbench persistence model. +- [ ] #3 Decide export artifact storage. +- [ ] #4 Build signed-in Workbench app shell. +- [ ] #5 Preserve V1 generator as first Workbench item type. +- [ ] #6 Persist baseplate design inputs and derived metadata. +- [ ] #7 Add export history and download actions. +- [ ] #8 Add Workbench backend and frontend test coverage. + +## Repository Foundation + +- [ ] #9 Prepare public repo, container CI, and releases. diff --git a/package-lock.json b/package-lock.json index 606f94a..a8cfcee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { - "name": "benchfinity", + "name": "benchfinity-workbench", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "benchfinity", + "name": "benchfinity-workbench", "version": "0.1.0", + "license": "Apache-2.0", "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.4.0", @@ -56,6 +57,7 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -877,6 +879,7 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -1019,9 +1022,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1036,9 +1036,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1053,9 +1050,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1070,9 +1064,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1087,9 +1078,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1104,9 +1092,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1121,9 +1106,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1138,9 +1120,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1155,9 +1134,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1172,9 +1148,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1189,9 +1162,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1206,9 +1176,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1223,9 +1190,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1416,6 +1380,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1450,6 +1415,7 @@ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz", "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==", "license": "MIT", + "peer": true, "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", @@ -1689,6 +1655,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -2254,6 +2221,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2317,6 +2285,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2326,6 +2295,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2555,7 +2525,8 @@ "version": "0.183.2", "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz", "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/three-mesh-bvh": { "version": "0.8.3", @@ -2775,6 +2746,7 @@ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index 7f6fb0e..c445023 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,18 @@ { - "name": "benchfinity", + "name": "benchfinity-workbench", "private": true, "version": "0.1.0", + "description": "Browser app for generating Gridfinity-compatible workbench baseplates, split print bundles, and Bambu Studio-style 3MF files.", + "license": "Apache-2.0", + "author": "James Maes", + "homepage": "https://github.com/BenchFinity/workbench#readme", + "repository": { + "type": "git", + "url": "https://github.com/BenchFinity/workbench.git" + }, + "bugs": { + "url": "https://github.com/BenchFinity/workbench/issues" + }, "type": "module", "scripts": { "dev": "vite", From 30391d324a0ac1bbfc5213df6cdeacfd01b39972 Mon Sep 17 00:00:00 2001 From: James Maes Date: Fri, 29 May 2026 11:43:11 -0500 Subject: [PATCH 02/24] chore: repo cleanup, CI gates, and type-seam hardening (#18) Branding/ownership, ESLint+Prettier+tsconfig split with CI gates, dedup, const-casing, versioned BaseplateDesign + applyPrinterToInput, why-comments, and CLAUDE.md. Refs #6, #9. --- .editorconfig | 12 + .github/CODEOWNERS | 2 +- .github/workflows/ci.yml | 9 + .gitignore | 3 + .nvmrc | 1 + .prettierignore | 5 + .prettierrc.json | 3 + CLAUDE.md | 22 + README.md | 2 +- docs/PLAN-gridfinity-baseplate-generator.md | 15 + docs/SESSION-STATE.md | 7 +- docs/WORKBENCH-VNEXT.md | 24 + eslint.config.js | 28 + package-lock.json | 1208 ++++++++++++++++++- package.json | 18 +- src/App.tsx | 43 +- src/components/FormControls.tsx | 34 +- src/components/PlateControls.tsx | 93 +- src/components/PlatePreview.tsx | 7 +- src/components/SettingsDialog.tsx | 96 +- src/design.ts | 23 + src/export/bundle.ts | 43 +- src/export/export.test.ts | 8 +- src/export/filenames.ts | 7 +- src/export/stl.ts | 5 +- src/export/threeMf.ts | 6 +- src/export/threeMf/bambuMetadata.ts | 21 +- src/export/threeMf/constants.ts | 42 +- src/export/threeMf/coreXml.ts | 62 +- src/export/threeMf/format.ts | 8 +- src/export/threeMf/mesh.ts | 2 + src/export/threeMf/package.ts | 63 +- src/export/threeMf/placement.ts | 10 +- src/geometry/connectors.ts | 19 +- src/geometry/layout.ts | 30 +- src/geometry/model.test.ts | 8 +- src/geometry/model.ts | 109 +- src/geometry/profile.ts | 13 +- src/geometry/types.ts | 6 +- src/settings.test.ts | 52 +- src/settings.ts | 94 +- src/styles.css | 17 +- src/validation.ts | 5 +- tsconfig.app.json | 27 + tsconfig.json | 22 +- tsconfig.node.json | 19 + vite.config.ts | 2 +- 47 files changed, 1957 insertions(+), 398 deletions(-) create mode 100644 .editorconfig create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 CLAUDE.md create mode 100644 eslint.config.js create mode 100644 src/design.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.node.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f15441a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e7d84f9..092d3d3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @KofTwentyTwo +* @BenchFinity/maintainers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88ffc96..f9ef96d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,15 @@ jobs: - name: Install dependencies run: npm ci + - name: Lint + run: npm run lint + + - name: Format check + run: npm run format:check + + - name: Typecheck + run: npm run typecheck + - name: Test run: npm run test diff --git a/.gitignore b/.gitignore index d157c23..0cfa5dd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ result.json coverage/ .vite/ npm-debug.log* +*.tsbuildinfo +.env +.env.* diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ff70601 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +dist +coverage +node_modules +package-lock.json +*.tsbuildinfo diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..963354f --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "printWidth": 120 +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8a8de32 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,22 @@ +# CLAUDE.md + +Benchfinity (org `BenchFinity`, repo `workbench`) is a client-only Vite + React + TypeScript + Three.js app that generates Gridfinity-compatible baseplates and exports STL, split ZIP, and Bambu Studio-style 3MF files. This file is the policy layer for agents and contributors; `AGENTS.md` is the operational guide and the source of truth for required startup reads, commands, validation rules, and architecture boundaries. Read `AGENTS.md` first. + +## Naming + +- The product and repo brand is `Benchfinity`. The org is `BenchFinity`. `KofTwentyTwo` is a personal handle, not the brand. +- `Gridfinity` and `Tracefinity` are compatibility references, never the product name. Describe output as "Gridfinity-compatible", verified against the external Tracefinity standard. + +## Workflow + +- Default branch is `develop` (gitflow). Do not commit to `main` or `develop`; work on a `feature/` branch and open PRs into `develop`. +- Roadmap work is tracked in GitHub Issues under the `Workbench VNext` and `Repository Foundation` milestones (#1-#9). A ticket is optional for small personal work; when one applies, reference it in the PR and commit body (`Refs #6`, `Closes #9`). +- Conventional commits, GPG-signed. No AI attribution in commit messages or content. No emojis. + +## Hard constraint (issue #5) + +The V1 generator core (`src/geometry/*`, `src/validation.ts`, `src/export/*`) is pure and free of React, DOM, and storage. Preserve it as the first Workbench item type during VNext: wrap it behind the persistence layer rather than rewriting the geometry/export math. The serializable design unit is `BaseplateDesign` (`src/design.ts`, carries `schemaVersion`); persist `PlateInput` and the derived `PlateLayout`, never the Three.js meshes (recompute those on load). + +## Gates + +`npm run lint`, `npm run format:check`, `npm run typecheck`, `npm run test`, and `npm run build` must all pass; CI enforces them on every PR. The current test baseline is 34. diff --git a/README.md b/README.md index ca695b5..0901f9e 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The production image serves the built Vite app on port `8080`. Published images - Exact finished envelope with centered Gridfinity cells and solid perimeter padding. - Project name input used in exported filenames, manifest, and README. -- KofTwentyTwo project footer. +- Benchfinity footer linking to benchfinity.com. - Settings dialog for saved defaults in local storage. - Pre-export validation for project name, dimensions, margin, printable fit, and generated tiles. - Standard `42mm` Gridfinity pitch by default. diff --git a/docs/PLAN-gridfinity-baseplate-generator.md b/docs/PLAN-gridfinity-baseplate-generator.md index 5181d80..748f3af 100644 --- a/docs/PLAN-gridfinity-baseplate-generator.md +++ b/docs/PLAN-gridfinity-baseplate-generator.md @@ -1,12 +1,15 @@ # PLAN: Gridfinity Baseplate Generator ## Goal + Build a browser-based app that accepts a desired finished footprint, printer bed limits, and Gridfinity cell size, then previews and exports one or more printable baseplate files. ## Approach + Use a client-only TypeScript web app with a shared geometry pipeline for preview and export. The app computes the largest standard Gridfinity grid that fits inside the requested finished size, centers it inside that envelope, fills the remaining perimeter with padding, splits it into bed-safe tiles when needed, renders the assembled result in 3D, and exports STL, ZIP, and 3MF assets. ## Product Scope + - Inputs: - Project name for exported filenames and bundle metadata. - Desired finished width and depth, with inch and mm support. @@ -25,6 +28,7 @@ Use a client-only TypeScript web app with a shared geometry pipeline for preview - Derived dimensions and tile count summary. ## V1 Decisions + - Canonical compatibility target: Tracefinity-compatible standard Gridfinity bins, using Tracefinity release `0.4.0` from 2026-05-26 as the current public reference point. - Baseplate profile: standard Gridfinity baseplate socket profile on a `42mm x 42mm` pitch, with `6mm x 2mm` magnet pockets enabled by default in the Tracefinity preset and configurable off for faster drawer baseplates. - Finished size behavior: exact requested envelope with the largest whole-cell Gridfinity grid centered inside it and solid perimeter padding around the edges. @@ -36,6 +40,7 @@ Use a client-only TypeScript web app with a shared geometry pipeline for preview - User defaults: settings dialog saves startup and reset defaults to browser local storage. ## Tracefinity Compatibility Notes + Tracefinity currently documents Gridfinity units as `42mm x 42mm`; bin height uses `7mm` units plus a `5mm` base; standard magnets are `6mm x 2mm`; and generated bins conform to the Gridfinity spec. Its public repository lists release `0.4.0` as latest on 2026-05-26 and describes generated bins as Gridfinity-compatible with proper base profile, magnet holes, and stacking lip. Practical v1 rule: generated baseplates must accept Tracefinity bins without requiring special Tracefinity-specific bin settings. Magnet pockets are configurable, but the Tracefinity preset defaults them on and the baseplate socket geometry and pitch should remain standard. @@ -47,6 +52,7 @@ References: - `https://github.com/gridfinity-unofficial/specification` ## Sizing Rules + All internal geometry should use millimeters. For a requested size: @@ -85,6 +91,7 @@ Example, `22in x 10.5in` with `42mm` cells: - Padding: `6.4mm` left/right and `7.35mm` front/back. ## Splitting Strategy + Split preferably on Gridfinity cell boundaries so each tile remains standard and predictable. When perimeter padding is present, include the padding in the outermost tiles only. Derived values: @@ -108,6 +115,7 @@ Rules: - If the bed cannot fit at least one grid cell plus required edge geometry, show a blocking validation error. ## Assembly Strategy + Start with connector choices that do not change the top Gridfinity interface. Recommended MVP connector: @@ -131,6 +139,7 @@ Later connector options: - No connector geometry for users mounting to a board. ## Geometry Pipeline + Create a small geometry core that produces indexed triangle meshes from typed inputs. Suggested core types: @@ -182,6 +191,7 @@ Implementation options: Recommendation: start with direct mesh generation if the chosen baseplate profile can be expressed without heavy boolean operations. Move to ManifoldJS if connector sockets, holes, and chamfers become fragile. ## Preview + Use Three.js for the viewport. Preview features: @@ -197,6 +207,7 @@ Preview features: The preview should consume the same generated tile mesh data used by exporters, not a simplified duplicate model. ## Export + STL: - Export one STL for unsplit plates. @@ -218,6 +229,7 @@ ZIP bundle: - `connector-*.stl` when applicable. ## Proposed Stack + - Vite, React, TypeScript. - Three.js for preview. - Optional `@react-three/fiber` if component-based scene composition is useful. @@ -230,6 +242,7 @@ ZIP bundle: - Playwright for viewport smoke tests and export flow checks. ## Files Affected + Initial implementation will likely add: - `package.json` and lockfile. @@ -240,6 +253,7 @@ Initial implementation will likely add: - `docs/` for planning and geometry notes. ## Steps + 1. [x] Confirm exact Gridfinity baseplate profile and first connector strategy. 2. [x] Scaffold TypeScript web app. 3. [x] Implement unit conversion, centered padded envelope sizing, and derived dimension summary. @@ -253,4 +267,5 @@ Initial implementation will likely add: 11. [x] Add browser smoke tests for preview and export. ## Open Questions + - None for v1 planning. Remaining choices are implementation details unless requirements change. diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index 0ca8005..e553a4e 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -1,15 +1,18 @@ # Session State ## Current Branch + `feature/9-public-repo-ci` ## Status + V1 implementation is complete and the audit follow-up is closed. The repository now uses `develop` as the default branch, and Workbench VNext work is tracked in GitHub Issues under the `Workbench VNext` milestone and the `Benchfinity Roadmap` project. Repository foundation work is tracked in GitHub Issue #9. ## Implemented + - Benchfinity Vite, React, TypeScript app scaffold. - Project name input used in filenames and export metadata. -- Footer identifies the app as a KofTwentyTwo project. +- Footer shows the Benchfinity name and a link to benchfinity.com. - Settings dialog persists startup and reset defaults in browser local storage. - Pre-export validation blocks invalid project names, dimensions, margins, oversized grids, layouts without printable tiles, and connector-key plates that do not fit the selected bed. - Centered padded envelope sizing. @@ -38,6 +41,7 @@ V1 implementation is complete and the audit follow-up is closed. The repository - GitHub Issue #9 tracks public repository setup, Docker packaging, CI, GHCR image publishing, and release automation. ## Verification + - `npm run test` passes, 34 tests. - `npm run build` passes with a Vite chunk-size warning caused by Three.js dependencies. - `npm audit` reports 0 vulnerabilities after updating Vitest to 4.1.7. @@ -47,6 +51,7 @@ V1 implementation is complete and the audit follow-up is closed. The repository - Regression checks cover rotated single STL exports, rotated split ZIP STL exports, connector-key quantities, 3MF metadata content types, connector-key 3MF object placement, and connector-key plate validation. ## GitHub Workflow + - Default branch: `develop`. - Active feature branch: `feature/9-public-repo-ci`. - Work issue: #9, `Prepare public repo, container CI, and releases`. diff --git a/docs/WORKBENCH-VNEXT.md b/docs/WORKBENCH-VNEXT.md index 533508b..e5f94e0 100644 --- a/docs/WORKBENCH-VNEXT.md +++ b/docs/WORKBENCH-VNEXT.md @@ -1,9 +1,11 @@ # Workbench VNext Requirements ## Goal + Turn the v1 single-purpose browser generator into a QQQ-backed Workbench where signed-in users can manage accounts, projects, generated baseplates, export history, and reusable printer/settings profiles. ## Product Shape + VNext should preserve the fast v1 generation flow, but add persistence and navigation around it. - Users belong to an account. @@ -16,6 +18,7 @@ VNext should preserve the fast v1 generation flow, but add persistence and navig ## Account And Project Model ### Account + Represents the billing/security boundary, even if billing is deferred. - `id` @@ -26,6 +29,7 @@ Represents the billing/security boundary, even if billing is deferred. - default units and printer profile ### User + Represents a person who can sign in. - `id` @@ -36,6 +40,7 @@ Represents a person who can sign in. - last active account ### Account Membership + Joins users to accounts. - `accountId` @@ -45,6 +50,7 @@ Joins users to accounts. - invite state if invitation flow is included in MVP ### Project + Organizes related generated designs. - `id` @@ -58,6 +64,7 @@ Organizes related generated designs. - archived flag ### Workbench Item + Generic project-scoped thing. VNext starts with baseplates, but the model should not block future generators. - `id` @@ -72,6 +79,7 @@ Generic project-scoped thing. VNext starts with baseplates, but the model should - `updatedAt` ### Baseplate Design + Typed payload for the existing generator state. - `workbenchItemId` @@ -85,6 +93,7 @@ Typed payload for the existing generator state. - preview camera/view options if worth preserving ### Export Artifact + Versioned generated output. - `id` @@ -105,6 +114,7 @@ Versioned generated output. Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens. Use Postgres as the durable source of truth. ### QQQ Tables + - `account` - `user` - `account_membership` @@ -115,6 +125,7 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - `export_artifact` ### QQQ Processes + - `create_project` - `duplicate_project` - `archive_project` @@ -125,6 +136,7 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - `set_default_printer_profile` ### Postgres Notes + - Use UUID primary keys. - Scope all project, item, design, profile, and artifact queries by `account_id`. - Store generator inputs as structured columns for important filters plus a JSONB payload for versioned design settings. @@ -133,6 +145,7 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - Keep artifact binary storage outside Postgres unless files are small enough for early MVP convenience. Persist metadata in Postgres either way. ### Security Rules + - Account membership controls all access. - Owners/admins can manage account settings and members. - Members can create and edit projects and workbench items. @@ -142,12 +155,14 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens ## UI Navigation ### App Shell + - Left sidebar for account switcher, project list, and primary navigation. - Top bar for current project/item name, save status, export action, and user menu. - Main region for the active workbench. - Right panel for settings, validation, dimensions, and export history. ### Primary Routes + - `/accounts` - `/a/{accountSlug}/projects` - `/a/{accountSlug}/p/{projectSlug}` @@ -155,12 +170,14 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - `/a/{accountSlug}/settings` ### Project View + - Search and filter project items. - Create new baseplate design. - Duplicate, rename, archive, and open items. - Show last modified time, printable size, tile count, and latest export type. ### Workbench Layout + - Center: 3D preview, using the existing mesh pipeline. - Left or top control strip: core dimensions, printer preset, envelope mode, and profile. - Right inspector: validation, derived dimensions, tile breakdown, connector settings, and export history. @@ -184,6 +201,7 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens ## Phased Implementation ### Phase 1: Persistence Foundation + - Add QQQ/Postgres app skeleton. - Define account, user, membership, project, workbench item, baseplate design, printer profile, and export artifact tables. - Implement account-scoped security filters. @@ -191,6 +209,7 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - Keep artifact generation client-side if that shortens the first slice. ### Phase 2: Project Workbench + - Add signed-in app shell. - Add project list and project detail views. - Add create, rename, duplicate, and archive flows. @@ -198,23 +217,27 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - Preserve the existing v1 generator behavior inside a workbench item. ### Phase 3: Export History + - Persist export metadata. - Attach generated artifacts to a workbench item. - Add export history panel with download links. - Add input hash/version labeling so users can tell whether an export matches the current design. ### Phase 4: Server-Side Generation + - Move export generation to backend process if browser generation becomes too slow or artifact persistence requires trusted generation. - Add job status, progress, retries, and failure messages. - Store artifacts in object storage with Postgres metadata. ### Phase 5: Team And Operations + - Add invitation flow and membership management. - Add account settings. - Add activity events. - Add operational dashboards for failed generation jobs and storage growth. ## Open Questions + - What identity provider should VNext use: QQQ-native auth, external OAuth, or a simple early access login? - Should the first MVP allow multiple accounts per user, or start with exactly one account per user? - Are exports generated in the browser and uploaded, or generated on the backend from saved design input? @@ -225,6 +248,7 @@ Use QQQ for CRUD metadata, process actions, permissions, and admin-style screens - Should Workbench support anonymous drafts that can later be claimed by a signed-in account? ## MVP Acceptance Criteria + - A signed-in user can open the app, select an account, and create a project. - A user can create a Gridfinity baseplate workbench item inside a project. - The workbench loads the existing v1 controls, preview, validation, and export behavior. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..e57c13a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import prettier from "eslint-config-prettier"; + +export default tseslint.config( + { ignores: ["dist", "coverage", "node_modules"] }, + js.configs.recommended, + ...tseslint.configs.recommended, + reactHooks.configs.flat["recommended-latest"], + { + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2022, + globals: { ...globals.browser, ...globals.node }, + }, + plugins: { + "react-refresh": reactRefresh, + }, + rules: { + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + }, + }, + // Keep formatting concerns out of ESLint; Prettier owns them. + prettier, +); diff --git a/package-lock.json b/package-lock.json index a8cfcee..d22878e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,18 @@ "three": "^0.183.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", + "eslint": "^10.4.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "prettier": "^3.8.3", "typescript": "^5.7.2", + "typescript-eslint": "^8.60.0", "vite": "^6.0.3", "vitest": "^4.1.7" } @@ -57,7 +65,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -766,6 +773,200 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -879,7 +1080,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -1362,6 +1562,13 @@ "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1369,6 +1576,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/offscreencanvas": { "version": "2019.7.3", "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", @@ -1380,7 +1594,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1415,7 +1628,6 @@ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz", "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==", "license": "MIT", - "peer": true, "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", @@ -1431,6 +1643,249 @@ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@use-gesture/core": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", @@ -1583,6 +2038,46 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1593,6 +2088,16 @@ "node": ">=12" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1635,6 +2140,19 @@ "require-from-string": "^2.0.2" } }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -1655,7 +2173,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -1807,6 +2324,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/detect-gpu": { "version": "5.0.70", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", @@ -1878,14 +2402,215 @@ "@esbuild/win32-x64": "0.25.12" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz", + "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">=6" + "node": ">=4.0" } }, "node_modules/estree-walker": { @@ -1898,6 +2623,16 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -1908,6 +2643,27 @@ "node": ">=12.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1932,6 +2688,57 @@ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "license": "MIT" }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1957,12 +2764,55 @@ "node": ">=6.9.0" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glsl-noise": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", "license": "MIT" }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hls.js": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz", @@ -1989,18 +2839,61 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -2051,6 +2944,27 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2076,6 +2990,30 @@ "setimmediate": "^1.0.5" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -2085,6 +3023,22 @@ "immediate": "~3.0.5" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2139,6 +3093,22 @@ "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", "license": "MIT" }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2165,6 +3135,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.46", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", @@ -2186,12 +3163,72 @@ ], "license": "MIT" }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2221,7 +3258,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2264,6 +3300,32 @@ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", "license": "ISC" }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2280,12 +3342,21 @@ "lie": "^3.0.2" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2295,7 +3366,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2525,8 +3595,7 @@ "version": "0.183.2", "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz", "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/three-mesh-bvh": { "version": "0.8.3", @@ -2634,6 +3703,19 @@ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tunnel-rat": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", @@ -2671,6 +3753,19 @@ } } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -2685,6 +3780,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", + "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.0", + "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2716,6 +3835,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -2746,7 +3875,6 @@ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -2949,6 +4077,16 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -2956,6 +4094,42 @@ "dev": true, "license": "ISC" }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zustand": { "version": "5.0.13", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", diff --git a/package.json b/package.json index c445023..798918b 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,18 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "tsc -b && vite build", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "tsc -b", "test": "vitest run", "preview": "vite preview" }, + "engines": { + "node": ">=22" + }, "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.4.0", @@ -30,10 +38,18 @@ "three": "^0.183.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", + "eslint": "^10.4.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "prettier": "^3.8.3", "typescript": "^5.7.2", + "typescript-eslint": "^8.60.0", "vite": "^6.0.3", "vitest": "^4.1.7" } diff --git a/src/App.tsx b/src/App.tsx index da5679c..fb55ef5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,8 @@ import { buildExportFilename } from "./export/filenames"; import { createConnectorKeyPart, createPlateModels } from "./geometry/model"; import { deriveLayout } from "./geometry/layout"; import type { PlateInput } from "./geometry/types"; -import { CUSTOM_PRINTER_ID, findPrinterPreset, groupPrintersByBrand } from "./printers"; +import { CUSTOM_PRINTER_ID, groupPrintersByBrand } from "./printers"; +import { DESIGN_SCHEMA_VERSION, applyPrinterToInput } from "./design"; import { FACTORY_DEFAULTS, loadSavedDefaults, saveDefaults, type AppDefaults } from "./settings"; import { validateExport } from "./validation"; @@ -17,8 +18,8 @@ export function App() { const initialDefaults = useMemo(() => loadSavedDefaults(), []); const [savedDefaults, setSavedDefaults] = useState(initialDefaults); const [draftDefaults, setDraftDefaults] = useState(initialDefaults); - const [input, setInput] = useState(initialDefaults.input); - const [selectedPrinterId, setSelectedPrinterId] = useState(initialDefaults.selectedPrinterId); + const [input, setInput] = useState(initialDefaults.design.input); + const [selectedPrinterId, setSelectedPrinterId] = useState(initialDefaults.design.selectedPrinterId); const [exploded, setExploded] = useState(initialDefaults.exploded); const [settingsOpen, setSettingsOpen] = useState(false); const [exporting, setExporting] = useState(false); @@ -51,17 +52,7 @@ export function App() { setSelectedPrinterId(printerId); setStatus(null); setDownloadInfo(null); - - const preset = findPrinterPreset(printerId); - - if (preset) { - setInput((current) => ({ - ...current, - bedWidth: preset.bedWidth, - bedDepth: preset.bedDepth, - bedUnit: preset.unit, - })); - } + setInput((current) => applyPrinterToInput(current, printerId)); }; const reset = () => { @@ -80,8 +71,8 @@ export function App() { }; const applyDefaults = (defaults: AppDefaults) => { - setSelectedPrinterId(defaults.selectedPrinterId); - setInput(defaults.input); + setSelectedPrinterId(defaults.design.selectedPrinterId); + setInput(defaults.design.input); setExploded(defaults.exploded); }; @@ -96,8 +87,11 @@ export function App() { const useCurrentAsDefaults = () => { setDraftDefaults({ - input, - selectedPrinterId, + design: { + schemaVersion: DESIGN_SCHEMA_VERSION, + input, + selectedPrinterId, + }, exploded, }); }; @@ -116,7 +110,12 @@ export function App() { setStatus(null); try { - const exportFilename = buildExportFilename(input.projectName, layout.cols, layout.rows, exportFileExtension(format, models)); + const exportFilename = buildExportFilename( + input.projectName, + layout.cols, + layout.rows, + exportFileExtension(format, models), + ); const blob = await createExportBlob({ format, input, @@ -170,7 +169,11 @@ export function App() { void }) { +export function TextField({ + label, + value, + onChange, +}: { + label: string; + value: string; + onChange: (value: string) => void; +}) { return (