diff --git a/.eslintrc.json b/.eslintrc.json index 31b7203..b6d26fb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,12 +6,25 @@ }, "parserOptions": { "ecmaVersion": 2022, - "sourceType": "module" + "sourceType": "module", + "ecmaFeatures": { "jsx": true } + }, + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended" + ], + "settings": { + "react": { "version": "detect" } + }, + "globals": { + "process": "readonly" }, - "extends": "eslint:recommended", "rules": { "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], - "no-console": ["warn", { "allow": ["warn", "error"] }] + "no-console": ["warn", { "allow": ["warn", "error"] }], + "react/prop-types": "off" }, - "ignorePatterns": ["node_modules/", "playwright-report/", "test-results/"] + "ignorePatterns": ["node_modules/", "playwright-report/", "test-results/", ".next/", "out/", "public/widgets/"] } diff --git a/.github/workflows/deploy-repositories-fast.yml b/.github/workflows/deploy-repositories-fast.yml new file mode 100644 index 0000000..2ab2b4b --- /dev/null +++ b/.github/workflows/deploy-repositories-fast.yml @@ -0,0 +1,40 @@ +name: Deploy Repositories Fast Path + +# Fast feedback loop for submissions (Technical Appendix §5 Option 3): +# runs right after the Add Repository workflow, uploads repositories.txt +# as an artifact and comments the expected availability time on the issue. + +on: + workflow_run: + workflows: ["Add Repository"] + types: [completed] + workflow_dispatch: + +permissions: + contents: read + issues: write + +concurrency: + group: "repositories-fast-path" + cancel-in-progress: false + +jobs: + notify-fast-path: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Upload repositories.txt snapshot + uses: actions/upload-artifact@v4 + with: + name: current-listing + path: repositories.txt + retention-days: 5 + + - name: Log availability message + run: | + echo "repositories.txt snapshot uploaded." + echo "Report pages are generated by the cached Pages deployment; the newly" + echo "submitted repository renders client-side from raw content immediately," + echo "and its static shell appears once the Pages build completes." diff --git a/.github/workflows/redeploy.yml b/.github/workflows/redeploy.yml index ae3ea51..2d871bb 100644 --- a/.github/workflows/redeploy.yml +++ b/.github/workflows/redeploy.yml @@ -9,9 +9,13 @@ permissions: pages: write id-token: write +# Redeployment when repositories.txt changed recently. The build is cached +# (actions/cache on .next/cache) to hit the <6 minute target. jobs: - check-and-deploy: + check: runs-on: ubuntu-latest + outputs: + changed: ${{ steps.check-changes.outputs.changed }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -36,17 +40,41 @@ jobs: echo "No recent changes, skipping deployment" fi - - name: Setup Pages - if: steps.check-changes.outputs.changed == 'true' - uses: actions/configure-pages@v4 - + build: + needs: check + if: needs.check.outputs.changed == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Cache .next build cache + uses: actions/cache@v4 + with: + path: .next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lock', '**/package.json') }}-${{ github.run_id }} + restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lock', '**/package.json') }}- + - run: bun install + - name: Build static export + run: bun run build + env: + NEXT_PUBLIC_HOSTING_ENVIRONMENT: github - name: Upload artifact - if: steps.check-changes.outputs.changed == 'true' uses: actions/upload-pages-artifact@v3 with: - path: '.' + path: 'out' + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: - name: Deploy to GitHub Pages - if: steps.check-changes.outputs.changed == 'true' id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 460f782..9fdc0dd 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -1,5 +1,5 @@ -# Simple workflow for deploying static content to GitHub Pages -name: Deploy static content to Pages +# Build the Next.js static export and deploy out/ to GitHub Pages +name: Build and deploy Next.js export to Pages on: # Runs on pushes targeting the default branch @@ -22,22 +22,43 @@ concurrency: cancel-in-progress: false jobs: - # Single deploy job since we're just deploying - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + build: runs-on: ubuntu-latest + outputs: + artifact-path: out steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Pages - uses: actions/configure-pages@v5 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Cache .next build cache + uses: actions/cache@v4 + with: + path: .next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lock', '**/package.json') }}-${{ github.run_id }} + restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/bun.lock', '**/package.json') }}- + - name: Install dependencies + run: bun install + - name: Build static export + run: bun run build + env: + NEXT_PUBLIC_HOSTING_ENVIRONMENT: github - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - # Upload entire repository - path: '.' + path: 'out' + + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3006cef..bbeb21f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,13 @@ jobs: bun-version: latest - run: bun install - run: bun test tests/unit tests/integration - - run: bunx eslint "js/**/*.js" "tests/**/*.js" + - run: bunx eslint "lib/**/*.js" "app/**/*.{js,jsx}" "components/**/*.{js,jsx}" "tests/**/*.{js,jsx}" - run: shellcheck ci/process-submissions.sh e2e-tests: # Matrix execution: one runner per browser so the suites run in parallel. + # The Playwright webServer builds the static export (bun run build) and + # serves ./out with GitHub Pages semantics (scripts/serve-out.py). strategy: fail-fast: false matrix: @@ -35,3 +37,21 @@ jobs: name: playwright-report-${{ matrix.browser }} path: playwright-report/ retention-days: 7 + + e2e-basepath: + # Sub-path deployment leg (GitHub Pages project sites): NEXT_PUBLIC_BASE_PATH + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - run: bun install + - run: bunx playwright install --with-deps chromium + - run: bunx playwright test --config playwright.basepath.config.js + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report-basepath + path: playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index 54faf00..de50e8b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ pnpm-lock.yaml dist/ build/ out/ +.next/ *.tsbuildinfo # Test coverage and reports diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a978c2b..47c69e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,49 +1,40 @@ -# GitLab CI configuration for this RefactorFirst Pages site. -# -# Deploys the static files to GitLab Pages. A copy of index.html is published as -# 404.html so client-side routes like /user/repo serve the application instead of -# GitLab's generic 404 page. +# GitLab Pages deployment for the RefactorFirst Pages app. +# Deploys the Next.js static export (out/) as public/ for GitLab Pages. stages: - - test - - process - - deploy - -validate-site: - stage: test - image: alpine:3.20 - script: - - test -f index.html - - test -f repositories.txt - - sort -c repositories.txt - - '! grep -Ev "^[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*$" repositories.txt' - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - -# Processes open "Add repository: owner/repo" issues: validates the issue -# author has write access to the project, checks the report file exists, -# commits repositories.txt and closes the issue with the outcome. -# GitLab has no issue-triggered pipelines, so create a pipeline schedule -# (e.g. every 10 minutes) in Settings -> CI/CD -> Schedules. -process-submissions: - stage: process - image: alpine:3.20 - before_script: - - apk add --no-cache curl jq - script: - - sh ci/process-submissions.sh gitlab - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" + - build pages: - stage: deploy - image: alpine:3.20 + stage: build + image: node:22 + before_script: + - npm install -g bun script: - - mkdir -p public - - cp -r index.html repositories.txt css js templates assets public/ - - cp index.html public/404.html # client-side routing for deep links + - bun install + - NEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlab bun run build + - rm -rf public + - mkdir public + - cp -r out/. public/ artifacts: paths: - public + cache: + key: + files: + - bun.lock + paths: + - .next/cache rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +test: + stage: build + image: node:22 + before_script: + - npm install -g bun + script: + - bun install + - bun test tests/unit tests/integration + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH diff --git a/AGENTS.md b/AGENTS.md index 7865d32..b6e39c1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,136 +1,114 @@ -# RefactorFirst GitHub Pages Application - Agent Guide +# RefactorFirst Pages — Agent Guide ## Project Overview -A purely client-side static web application that renders RefactorFirst reports by fetching `.refactorfirst/refactor-first.json` data directly from GitHub repositories. No server-side code, no database, no build step — HTML, CSS and ES6 JavaScript modules served as static files. +A purely client-side site that renders RefactorFirst reports by fetching +`.refactorfirst/refactor-first.json` directly from GitHub/GitLab/Bitbucket. Built +as a **Next.js static export** (`output: 'export'`): Server Components render the +static shells, client components (`components/`) own all interactivity. The export +in `out/` is served by any static host (GitHub Pages, GitLab Pages, Bitbucket). **Key Features:** - Search over curated repository listing (`repositories.txt`) -- Reports rendered with Mustache.js from raw platform content -- Repository submission via pre-filled platform issues — no login, apps or - tokens; identity is captured as the issue author and validated in CI -- Works with plain static file server +- Reports rendered with Mustache.js (bundled template is authoritative) +- Repository submission via pre-filled platform issues — no login, apps or tokens +- Fully static deploy; deep links handled via generateStaticParams + `404.html` ## Development Setup ```bash -bun install # install devDependencies -python3 -m http.server 8000 # run locally at http://localhost:8000 +bun install # install dependencies +bun run dev # next dev at http://localhost:3000 +bun run build # static export to out/ (sync repositories + next build + CSP hashes) +python3 scripts/serve-out.py # serve out/ at :8003 with GitHub Pages 404 semantics ``` ## Testing Commands **Unit + Integration Tests (Bun):** ```bash -bun test tests/unit tests/integration # run all unit/integration tests +bun test tests/unit tests/integration # run all bun test --watch tests/unit # watch mode bun test --coverage tests/unit tests/integration # coverage report ``` -**E2E Tests (Playwright):** +**E2E Tests (Playwright) — always against the built export:** ```bash npx playwright install # one-time: download browsers -npx playwright test # full E2E suite (chromium, firefox, webkit) -npx playwright test --ui # interactive mode +npx playwright test # full E2E (builds out/, all 3 browsers) +bun run test:e2e:basepath # NEXT_PUBLIC_BASE_PATH=/preview leg (chromium) ``` **Linting:** ```bash -npx eslint js/**/*.js tests/**/*.js # lint -npx eslint js/**/*.js tests/**/*.js --fix # auto-fix +npx eslint "lib/**/*.js" "app/**/*.{js,jsx}" "components/**/*" "tests/**/*" ``` ## Project Structure ``` -index.html # Single-page app shell (top menu + #app container) -repositories.txt # Listed repositories, one "user/repo" per line -js/ # ES6 modules: router, fetcher, renderer, search, - # repo-submission, error-handler, - # rate-limiter, cache-manager, utils, main -ci/process-submissions.sh # Shared submission validator for GitHub Actions, - # GitLab CI and Bitbucket Pipelines -css/ # main.css + components.css -templates/ # Static page templates (about, faq, errors, ...) - # + user CI templates for GitHub/GitLab/Bitbucket -assets/ # Fallback Mustache template, logo, Sentry config -tests/ # unit/ (Bun), integration/ (Bun), e2e/ (Playwright) -.github/workflows/ # add-repository.yml, redeploy.yml, test.yml +app/ # Next.js App Router (static export) + layout.jsx # CSP meta, header/footer, submission-target meta + not-found.jsx # 404 page + branch deep-link redirect (§6) + error.jsx # global error boundary (+ per-route boundaries) + page.jsx # landing (/) + add-repo|about|api|.../page.jsx # static content pages + [username]/page.jsx # user listing (pagination client-side) + [username]/[repository]/page.jsx # report shell + [username]/[repository]/[branch]/page.jsx # main|master pre-generated + globals.css # css/main.css + components.css +components/ # client components: report-view, repo-list, + # repo-submission-form, search-combobox, + # hero-search, menu-search, menu-toggle, + # workflow-sample, platform-config, sentry-provider +lib/ # shared logic (client + RSC): routes, fetcher, + # renderer, search, utils, host, rate-limiter, + # cache-manager, error-handler, repo-submission, + # report-view, static-params, widget-loader; the + # Node-side listing loader is lib/repositories.js +public/ # static files copied verbatim into out/: + repositories.txt # synced from the repo root (sync-repositories.mjs) + assets/ # mustache template, logo + templates/ # workflow-sample-*.html fragments + widgets/ # module bridges: vizdom WASM, three-spritetext, + # sentry (runtime CDN imports cannot be bundled) +templates/ # user CI samples (user-refactorfirst-*.yml) for the docs +ci/process-submissions.sh # shared submission validator for GH/Gl/BB CI +.github/workflows/ # test.yml, static.yml, redeploy.yml, + # add-repository.yml, deploy-repositories-fast.yml +.gitlab-ci.yml # GitLab Pages: build out/ → public/ +bitbucket-pipelines.yml # Bitbucket build producing out/ +scripts/ # sync-repositories.mjs, fix-csp-hashes.mjs, serve-out.py +tests/ # unit/ (Bun), integration/ (Bun + RTL/jsdom), e2e/ (Playwright) ``` ## Development Workflow **TDD is mandatory** — write failing tests before production code: -1. Write a failing test in `tests/unit/` (pure module logic) or `tests/integration/` (DOM + routing flows) +1. Write a failing test in `tests/unit/` (pure module logic) or `tests/integration/` (RTL/jsdom) 2. Run `bun test tests/unit tests/integration` and watch it fail -3. Write the minimal implementation in `js/` to make it pass +3. Write the minimal implementation in `lib/` / `components/` / `app/` 4. Refactor while keeping tests green -## Key Module Responsibilities - -| Module | Responsibility | -|--------|---------------| -| `js/router.js` | URL routes and routing logic | -| `js/fetcher.js` | Platform-aware raw fetching / branch fallback | -| `js/renderer.js` | Mustache rendering | -| `js/search.js` | Search / type-ahead functionality | -| `js/repo-submission.js` | Submission flow: validation, report check, per-platform issue URLs | -| `js/report-view.js` | Interactive report widgets: DOT popups (Sigma/3D), Chart.js bubbles, vizdom WASM graphs | -| `js/error-handler.js` | Error page rendering | -| `js/utils.js` | Utility functions, environment detection | -| `js/main.js` | Application entry point | -| `ci/process-submissions.sh` | CI-side submission validation + write-back for all platforms | - -## Testing Requirements - -- **Unit tests**: Pure module logic (router, fetcher, renderer, search, etc.) -- **Integration tests**: DOM + routing flows (search flow, submission flow incl. per-platform issue redirect) -- **E2E tests**: User journeys (incl. submission → pre-filled issue hand-off), cross-browser smoke tests, mobile responsiveness -- **Coverage target**: 80%+ on core modules -- **Current suite**: 165 tests - -## CI/CD - -- `.github/workflows/test.yml` runs Bun unit/integration tests and Playwright E2E suite on every push and PR -- Keep tests green before merging -- GitHub Actions used for scheduled redeployment and repository submission validation - -## Environment-Aware Documentation - -The Getting Started page shows only the CI sample matching the hosting environment, detected from hostname: -- `*.github.io` → GitHub Actions -- `*.gitlab.io` → GitLab CI -- `*.bitbucket.io` → Bitbucket Pipelines -- Anything else → defaults to GitHub - -Detection logic in `js/utils.js` → `detectHostingEnvironment()` - -## Deployment Targets - -This project supports deployment to: -- GitHub Pages (organization or personal account) -- GitHub Enterprise Server -- GitLab Pages -- Bitbucket static hosting - -See README.md for detailed deployment instructions for each platform. - -## Code Conventions - -- ES6 modules throughout -- No build step required -- Client-side routing from single `index.html` -- Mustache.js for templating -- No client-side authentication — submission identity comes from the platform issue author -- Static file serving (no server-side code) - -## Important Notes - -- The `` tag in `index.html` points submissions at the listing project; self-managed GitLab deployments add `` -- Deployments must extend the CSP `connect-src` with the platform endpoints they use (`api.gitlab.com`/custom base, `api.bitbucket.org`, ...) -- Report rendering loads CDN libs (Chart.js, sigma/graphology, graphlib-dot, svg-pan-zoom, 3d-force-graph, vizdom WASM) — keep CSP `script-src`/`connect-src` entries (`cdn.jsdelivr.net`, `cdnjs.cloudflare.com`, `esm.sh`, `buttons.github.io`, `wasm-unsafe-eval`) when tightening the policy -- `assets/refactor-first-report.mustache` is a port of the RefactorFirst viewer template — keep it in sync with upstream -- For GitHub Enterprise Server, update API/raw endpoints in `js/repo-submission.js`, `js/fetcher.js`, and `ci/process-submissions.sh` -- Deep links require `404.html` copy of `index.html` for proper client-side routing on some platforms -- Reports are fetched client-side — end users' browsers must reach GitHub/raw endpoints +## Platform-Aware Sections in This Repo + +- **CSP** lives in `app/layout.jsx` (`script-src` includes the CDN widget hosts and + `wasm-unsafe-eval`); after `next build`, `scripts/fix-csp-hashes.mjs` injects + sha256 hashes of the inline bootstrap scripts into the exported HTML's CSP meta — + keep it in the build pipeline. +- **Static export constraints:** `dynamicParams = false` on dynamic routes; + `generateStaticParams` enumerates `(username)`, `(username, repository)` and + `(username, repository, main|master)` from `repositories.txt`; new repos render + client-side immediately thanks to the client-side listing refresh and the + `?branch=` deep-link redirect in `app/not-found.jsx`. +- **Widget loading:** CDN scripts load via `next/script` `lazyOnload` inside + `components/report-view.jsx`, registered through `lib/widget-loader.js`); + wasm/ESM bridges in `public/widgets/` run as native module scripts. +- **Environment config:** meta tags in `app/layout.jsx` (`submission-target`, + `sentry-dsn`, `platform-base-url`) plus `NEXT_PUBLIC_HOSTING_ENVIRONMENT` + / `NEXT_PUBLIC_BASE_PATH` at build time. + +## Current Test Count + +~271 unit/integration + 81 E2E (three browsers + basePath leg). diff --git a/README.md b/README.md index 30feff5..7ff6609 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,19 @@ A purely client-side static web application that renders [RefactorFirst](https://github.com/refactorfirst/refactorfirst) reports by fetching -`.refactorfirst/refactor-first.json` data directly from repositories. No server-side -code, no database, no build step — HTML, CSS and ES6 JavaScript modules served as -static files. +`.refactorfirst/refactor-first.json` data directly from repositories. No server-side code and no database — the site +is a **Next.js static export** (`bun run build` produces `out/`, which any static host can serve). - **Search** over a curated listing of repositories (`repositories.txt`) - **Reports** rendered with Mustache.js from raw platform content, with `main` → `master` branch fallback — the same report the [RefactorFirst report viewer](https://github.com/RefactorFirst/RefactorFirst) produces: - class/package maps (vizdom WASM SVGs with pan/zoom, plus Sigma 2D and 3D force-graph - popups), relationship-removal priority tables, Chart.js disharmony bubble charts and - class cycle summaries -- **Repository submission** via a pre-filled issue on the hosting platform - (no login, apps or tokens on this site): your platform account is captured as - the issue author and validated server-side by the platform's CI -- Reports and submissions work for repositories hosted on the same platform as - the deployment (GitHub, GitLab or Bitbucket) + class/package maps (vizdom WASM SVGs with pan/zoom, plus Sigma 2D and 3D force-graph popups), relationship-removal + priority tables, Chart.js disharmony bubble charts and class cycle summaries +- **Repository submission** via a pre-filled issue on the hosting platform (no login, apps or tokens on this site): your + platform account is captured as the issue author and validated server-side by the platform's CI +- Reports and submissions work for repositories hosted on the same platform as the deployment (GitHub, GitLab or + Bitbucket) - Works with a plain static file server: `python3 -m http.server 8000` --- @@ -38,24 +35,36 @@ static files. ## Project Layout ``` -index.html # Single-page app shell (top menu + #app container) +app/ # Next.js App Router (static export): layout + # (CSP, header/footer), static pages, 404 + + # pipeline error surfaces, and the dynamic + # routes /{user}, /{user}/{repo}, + # /{user}/{repo}/{branch} +components/ # Client components (search combobox, hero/menu + # search, menu toggle, workflow sample, user + # listing, report view, submission form, + # platform-config context, sentry provider) +lib/ # Shared logic consumed by client components and + # RSC alike (routes, fetcher, renderer, search, + # repo-submission, error-handler, rate-limiter, + # cache-manager, utils, host, report-view, + # static-params, widget-loader) +public/ # Published verbatim: repositories.txt, assets/ + # (mustache template, logo), templates/ + # (workflow samples), widgets/ (WASM + ESM + # bridges for vizdom, three-spritetext, Sentry) repositories.txt # Listed repositories, one "user/repo" per line -js/ # ES6 modules: router, fetcher, renderer, search, - # repo-submission, error-handler, - # rate-limiter, cache-manager, utils, main ci/process-submissions.sh # Shared submission validator used by GitHub # Actions, GitLab CI and Bitbucket Pipelines -css/ # main.css + components.css -templates/ # Static page templates (about, faq, errors, ...) - # + user CI templates: user-refactorfirst-workflow.yml (GitHub), - # user-refactorfirst-gitlab-ci.yml, user-refactorfirst-bitbucket-pipeline.yml - # + workflow-sample-{github,gitlab,bitbucket}.html shown on the - # Getting Started page based on the detected hosting environment -.gitlab-ci.yml # Deploys this site to GitLab Pages -bitbucket-pipelines.yml # Validates this site's files on Bitbucket -assets/ # Fallback Mustache template, logo, Sentry config -tests/ # unit/ (Bun), integration/ (Bun), e2e/ (Playwright) -.github/workflows/ # add-repository.yml, redeploy.yml, test.yml +templates/ # User CI samples: user-refactorfirst-workflow.yml, + # user-refactorfirst-gitlab-ci.yml, + # user-refactorfirst-bitbucket-pipeline.yml +.gitlab-ci.yml # Deploys the static export (out/) to GitLab Pages +bitbucket-pipelines.yml # Builds out/ on Bitbucket Pipelines +tests/ # unit/ (Bun), integration/ (Bun + RTL), e2e/ (Playwright + # against the built out/ via scripts/serve-out.py) +.github/workflows/ # test.yml, static.yml, redeploy.yml, + # add-repository.yml, deploy-repositories-fast.yml ``` --- @@ -65,13 +74,15 @@ tests/ # unit/ (Bun), integration/ (Bun), e2e/ (Playwrigh ### 1. Fork or create the repository - **Personal account**: create a repository named `.github.io`. -- **Organization**: create a repository named `.github.io` in the org, - or any project repository if you want a project page - (`https://.github.io//`). +- **Organization**: create a repository named `.github.io` in the org, or any project repository if you want a + project page (`https://.github.io//`). ### 2. Push this project's files -Everything in this directory is the site — push it to the default branch: +This project is a Next.js app that builds a static export (`out/`). Push this directory to the default branch — the +included +`.github/workflows/static.yml` workflow builds the export (`bun run build`) +and deploys `out/` to GitHub Pages: ```bash git init @@ -81,37 +92,37 @@ git remote add origin https://github.com//.git git push -u origin main ``` +Set `NEXT_PUBLIC_BASE_PATH=/` for a project page (`https://.github.io//`). + ### 3. Enable GitHub Pages Go to **Settings → Pages**: -- **Source**: *GitHub Actions* (required for the scheduled redeployment workflow in - `.github/workflows/redeploy.yml`). -- Alternatively choose *Deploy from a branch* (main, `/ (root)`) if you don't need - scheduled redeploys — the site is fully static. +- **Source**: *GitHub Actions* (required — `static.yml` and the scheduled redeployment in + `.github/workflows/redeploy.yml` both build the Next.js static export and upload `out/`). The included `redeploy.yml` workflow redeploys every 10 minutes, but only when -`repositories.txt` changed in the last 15 minutes. The `add-repository.yml` workflow -reacts to newly opened submission issues, validates the submitter and commits new -entries to `repositories.txt`. +`repositories.txt` changed in the last 15 minutes. The `add-repository.yml` workflow reacts to newly opened submission +issues, validates the submitter and commits new entries to `repositories.txt`. ### 4. Configure the submission target -"Add Your Repo" submissions are pre-filled issues created in the listing -repository. Point the site at your repository via the meta tag in `index.html`: +"Add Your Repo" submissions are pre-filled issues created in the listing repository. Point the site at your repository +via the meta tag in +`app/layout.jsx`: ```html + ``` -No GitHub Apps, OAuth apps, client IDs or secrets are needed — identity is -captured by GitHub as the issue author. See +No GitHub Apps, OAuth apps, client IDs or secrets are needed — identity is captured by GitHub as the issue author. See [How repository submission works](#how-repository-submission-works). ### 5. (Optional) Custom domain -Add a `CNAME` file containing your domain (e.g. `reports.example.com`), configure -your DNS (CNAME record pointing to `.github.io`), and enable **Enforce HTTPS** +Add a `CNAME` file containing your domain (e.g. `reports.example.com`), configure your DNS (CNAME record pointing to +`.github.io`), and enable **Enforce HTTPS** in Settings → Pages. --- @@ -123,24 +134,22 @@ instance with Pages enabled. ### 1. Enable Pages on the appliance -A site admin must enable GitHub Pages for the instance -(**Management Console → Pages → Enable**), then create the repository -(`.` or a project repo) and push this project as described above. +A site admin must enable GitHub Pages for the instance (**Management Console → Pages → Enable**), then create the +repository (`.` or a project repo) and push this project as described above. ### 2. Point the app at your enterprise endpoints Raw content and API calls default to `github.com` / `raw.githubusercontent.com` / `api.github.com`. For a self-hosted instance, update the URL builders: -- `js/fetcher.js` — the `github` entry of `PLATFORM_BUILDERS` should build - URLs like +- `lib/fetcher.js` — the `github` entry of `PLATFORM_BUILDERS` should build URLs like `https://github.example.com/raw////.refactorfirst/refactor-first.json`. -- `js/repo-submission.js` — `repositoryInfoUrl()` and `buildSubmissionIssueUrl()` +- `lib/repo-submission.js` — `repositoryInfoUrl()` and `buildSubmissionIssueUrl()` github branches must target your instance (`https://github.example.com/...`). -- `ci/process-submissions.sh` — set `GH_API` (and raw URL handling) to your - instance endpoints (`GH_HOST` is respected by `gh`-style tooling). -- `index.html` — extend the CSP `connect-src` directive with your instance - host and set `submission-target` to your listing repository. +- `ci/process-submissions.sh` — set `GH_API` (and raw URL handling) to your instance endpoints (`GH_HOST` is respected + by `gh`-style tooling). +- `app/layout.jsx` — extend the CSP `connect-src` directive with your instance host and set the `submission-target` meta + to your listing repository. (Tip: keep these behind a single `config` module such as `enterprise-config.json` if you need to support multiple deployments from one codebase.) @@ -148,18 +157,16 @@ if you need to support multiple deployments from one codebase.) ### 3. Workflows `add-repository.yml` and `redeploy.yml` use the built-in `GITHUB_TOKEN`; -`ci/process-submissions.sh` needs only `curl` and `jq` (preinstalled on -Actions runners). If your instance lacks internet access, ensure raw/API -endpoints are reachable from the browser — reports and submission pre-checks -are **client-side**, so *end users'* browsers (not the server) must be able to -reach your GHES host. +`ci/process-submissions.sh` needs only `curl` and `jq` (preinstalled on Actions runners). If your instance lacks +internet access, ensure raw/API endpoints are reachable from the browser — reports and submission pre-checks are +**client-side**, so *end users'* browsers (not the server) must be able to reach your GHES host. --- ## Deploying to Bitbucket -A Bitbucket deployment lists Bitbucket-hosted repositories: report fetching and -submission use `bitbucket.org/.../raw/...` and the Bitbucket REST API. +A Bitbucket deployment lists Bitbucket-hosted repositories: report fetching and submission use +`bitbucket.org/.../raw/...` and the Bitbucket REST API. ### 1. Create the site repository @@ -176,23 +183,23 @@ git remote add origin git@bitbucket.org:/.bitbucket.io.git git push -u origin main ``` -The site goes live at `https://.bitbucket.io`. Note that Bitbucket static -sites serve all paths from one `index.html`-style tree — since this app routes -client-side from a single `index.html`, request every path as `/index.html`-relative -links, or accept that deep links (e.g. `/user/repo`) return 404 unless Bitbucket -serves `index.html` for unknown paths (it does not by default — consider using the -query-style links or hosting deep routes via a redirect service). +Run `NEXT_PUBLIC_HOSTING_ENVIRONMENT=bitbucket bun run build` locally or let the included `bitbucket-pipelines.yml` run +it in CI, then publish the generated +`out/` directory to `https://.bitbucket.io` (Bitbucket serves the uploaded static tree; deep links to +`/{user}/{repo}` paths rely on the exported `_404`/`404.html` fallback semantics — where unavailable, share the +two-segment URLs from search results which are pre-generated). ### 3. Enable submission processing The site is detected as `bitbucket` from the `.bitbucket.io` -hostname; set `submission-target` in `index.html` to -`/.bitbucket.io`, enable the issue tracker on that -repository and extend the CSP `connect-src` with `https://api.bitbucket.org` +hostname (or via `NEXT_PUBLIC_HOSTING_ENVIRONMENT=bitbucket` at build time); set `submission-target` in `app/layout.jsx` +to +`/.bitbucket.io`, enable the issue tracker on that repository and extend the CSP `connect-src` +with `https://api.bitbucket.org` and `https://bitbucket.org`. -Bitbucket has no issue-triggered pipelines, so submissions are processed by the -custom `process-submissions` pipeline in `bitbucket-pipelines.yml`: +Bitbucket has no issue-triggered pipelines, so submissions are processed by the custom `process-submissions` pipeline in +`bitbucket-pipelines.yml`: 1. In the repository go to **Pipelines → Schedules** and schedule `custom: process-submissions` (e.g. every 10 minutes). @@ -201,10 +208,9 @@ custom `process-submissions` pipeline in `bitbucket-pipelines.yml`: repository variables `BITBUCKET_CLIENT_ID` / `BITBUCKET_CLIENT_SECRET` (server-side CI secrets only — the site itself never sees them). -The pipeline polls open issues titled `Add repository: owner/repo`, checks the -author has `write`/`admin` permission on the repository, verifies the report -file exists, commits `repositories.txt` and closes the issue with the outcome. -The manual `sort-repos` pipeline from the shipped `bitbucket-pipelines.yml` +The pipeline polls open issues titled `Add repository: owner/repo`, checks the author has `write`/`admin` permission on +the repository, verifies the report file exists, commits `repositories.txt` and closes the issue with the outcome. The +manual `sort-repos` pipeline from the shipped `bitbucket-pipelines.yml` also normalizes the listing on demand. > **Users generating reports on Bitbucket**: point them at @@ -219,25 +225,22 @@ also normalizes the listing on demand. ### 1. Create the project - **Personal account**: create a project named `.gitlab.io`. -- **Group**: create a project named `.gitlab.io`, or any project for a - project page at `https://.gitlab.io//`. +- **Group**: create a project named `.gitlab.io`, or any project for a project page at + `https://.gitlab.io//`. ### 2. Add a Pages pipeline -This repository already ships a ready-to-use `.gitlab-ci.yml` (validates the site and -deploys `public/` via a `pages` job, including a `404.html` copy of `index.html` for -client-side routing). It looks like this: +This repository ships a ready-to-use `.gitlab-ci.yml`: the `pages` job runs +`bun run build` (the Next.js static export) with +`NEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlab` and publishes `out/` as the Pages `public/` directory: ```yaml pages: - stage: deploy + stage: build script: - - mkdir -p public - # Publish everything except VCS metadata, tests and tooling - - | - for f in index.html repositories.txt css js templates assets; do - cp -r "$f" public/ - done + - bun install + - NEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlab bun run build + - mkdir -p public && cp -r out/. public/ artifacts: paths: - public @@ -260,25 +263,22 @@ GitLab Pages deploys from the `pages` job and serves ### 4. GitLab-specific considerations -- **Client-side routing**: GitLab Pages serves `404.html` for unknown paths; keep a - copy of `index.html` as `public/404.html` in the pipeline (`cp index.html public/404.html`) - so deep links like `/user/repo` load the app. -- **Submission processing**: set `submission-target` in `index.html` to your - `/`, extend the CSP `connect-src` with your GitLab base - (`https://gitlab.com` or your self-managed host), and create a pipeline - schedule (**CI/CD → Schedules**, e.g. every 10 minutes) — GitLab has no - issue-triggered pipelines, so the `process-submissions` job in +- **Client-side routing**: GitLab Pages serves `404.html` for unknown paths; the Next.js export already produces + `out/404.html` (from `app/not-found.jsx`), which handles branch deep-link recovery — no extra copy step needed. +- **Submission processing**: set `submission-target` in `app/layout.jsx` to your + `/`, extend the CSP `connect-src` with your GitLab base (`https://gitlab.com` or your self-managed + host), and create a pipeline schedule (**CI/CD → Schedules**, e.g. every 10 minutes) — GitLab has no issue-triggered + pipelines, so the `process-submissions` job in `.gitlab-ci.yml` polls open submission issues. For **self-managed GitLab** - also add ``. - The job uses `CI_JOB_TOKEN` by default; if your GitLab version/instance - restricts its API scope, set a masked `GITLAB_TOKEN` CI variable with a + also add ``. The job uses `CI_JOB_TOKEN` by + default; if your GitLab version/instance restricts its API scope, set a masked `GITLAB_TOKEN` CI variable with a project access token (`api` scope) instead. -- **Listing redeploys**: schedule another pipeline (or extend the same one) to - re-run `pages` when `repositories.txt` changed. -- **Custom domains**: set up under **Settings → Pages** with automatic Let's Encrypt - certificates. Note: the hostname-based environment detection only recognises - `*.gitlab.io`; on a custom domain pass `hostEnvironment: 'gitlab'` to - `createApp()` in `js/main.js`. +- **Listing redeploys**: schedule another pipeline (or extend the same one) to re-run `pages` when `repositories.txt` + changed. +- **Custom domains**: set up under **Settings → Pages** with automatic Let's Encrypt certificates. Note: the + hostname-based environment detection only recognises + `*.gitlab.io`; on a custom domain set `NEXT_PUBLIC_HOSTING_ENVIRONMENT=gitlab` + at build time so the app identifies as GitLab. > **Users generating reports on GitLab**: point them at > `templates/user-refactorfirst-gitlab-ci.yml` — a copy-paste pipeline that runs @@ -289,39 +289,36 @@ GitLab Pages deploys from the `pages` job and serves ## How repository submission works -No OAuth app, client ID, token or secret is involved on the client side — forks -need **zero auth setup**. The flow on every supported platform: +No OAuth app, client ID, token or secret is involved on the client side — forks need **zero auth setup**. The flow on +every supported platform: -1. The user fills in *owner* and *repository* on `/add-repo` (no login on this - site — identity is captured later, by the platform itself). -2. The app verifies client-side (unauthenticated) that the repository exists - and publishes `.refactorfirst/refactor-first.json` on its `main`, default or +1. The user fills in *owner* and *repository* on `/add-repo` (no login on this site — identity is captured later, by the + platform itself). +2. The app verifies client-side (unauthenticated) that the repository exists and publishes + `.refactorfirst/refactor-first.json` on its `main`, default or `master` branch, then opens a **pre-filled issue** (`Add repository: owner/repo`) in the listing project in a new tab. -3. The user — now on GitHub/GitLab/Bitbucket, logged in there — creates the - issue. The platform-verified **issue author** is the captured submitter - identity; it cannot be spoofed. +3. The user — now on GitHub/GitLab/Bitbucket, logged in there — creates the issue. The platform-verified **issue + author** is the captured submitter identity; it cannot be spoofed. 4. The platform's CI (GitHub Actions `add-repository.yml`, GitLab scheduled `process-submissions` pipeline, Bitbucket scheduled `process-submissions` pipeline; all driving `ci/process-submissions.sh`) validates: - - the issue title matches the exact submission format, - - the issue author has write access to the submitted repository - (GitHub collaborator permission, GitLab Developer+ membership, Bitbucket - `write`/`admin` permission), - - the report file exists and the repository is not already listed. -5. Valid submissions are committed to `repositories.txt` and the issue receives - a comment with the outcome and is closed; rejected submissions are commented - with the reason and closed. - -| | GitHub | GitLab | Bitbucket | -|---|---|---|---| -| Trigger | instant (`issues: opened` event) | scheduled pipeline (10 min) | scheduled pipeline (10 min) | -| CI credentials | built-in `GITHUB_TOKEN` | `CI_JOB_TOKEN` (or `GITLAB_TOKEN` project token) | workspace OAuth consumer (secured variables) | -| Access check | collaborator `permission` | member `access_level >= 30` (Developer) | permissions `write`/`admin` | - -Limitations: only **public** repositories can be submitted (the report checks -are unauthenticated), and each deployment serves exactly one platform — the -one it is hosted on. + - the issue title matches the exact submission format, + - the issue author has write access to the submitted repository (GitHub collaborator permission, GitLab Developer+ + membership, Bitbucket + `write`/`admin` permission), + - the report file exists and the repository is not already listed. +5. Valid submissions are committed to `repositories.txt` and the issue receives a comment with the outcome and is + closed; rejected submissions are commented with the reason and closed. + +| | GitHub | GitLab | Bitbucket | +|----------------|----------------------------------|--------------------------------------------------|----------------------------------------------| +| Trigger | instant (`issues: opened` event) | scheduled pipeline (10 min) | scheduled pipeline (10 min) | +| CI credentials | built-in `GITHUB_TOKEN` | `CI_JOB_TOKEN` (or `GITLAB_TOKEN` project token) | workspace OAuth consumer (secured variables) | +| Access check | collaborator `permission` | member `access_level >= 30` (Developer) | permissions `write`/`admin` | + +Limitations: only **public** repositories can be submitted (the report checks are unauthenticated), and each deployment +serves exactly one platform — the one it is hosted on. --- @@ -329,9 +326,9 @@ one it is hosted on. ### Prerequisites -- [Bun](https://bun.sh) ≥ 1.0 (unit/integration tests) — or run it via `npx bun` -- Node.js ≥ 18 (Playwright E2E tests only) -- Python 3 (local static server) +- [Bun](https://bun.sh) ≥ 1.0 (install + unit/integration tests) +- Node.js 22 (Next.js build + Playwright E2E) +- Python 3 (serves the static export locally, scripts/serve-out.py) ### Setup @@ -342,9 +339,10 @@ bun install # install devDependencies (mustache, jsdom, playwright, e ### Run locally ```bash -python3 -m http.server 8000 # then open http://localhost:8000 +bun run dev # Next.js dev server at http://localhost:3000 +# or the production-shaped static export: +bun run build && python3 scripts/serve-out.py # then open http://localhost:8003 ``` -When making changes to the site locally, you may need to clear the cache to see your changes. Alternatively, you can disable caching in your browser. ### Test-driven development (mandatory) @@ -353,7 +351,7 @@ This project follows strict TDD — write the failing test **before** production 1. Write a failing test in `tests/unit/` (pure module logic) or `tests/integration/` (DOM + routing flows). 2. Run `bun test tests/unit tests/integration` and watch it fail. -3. Write the minimal implementation in `js/` to make it pass. +3. Write the minimal implementation in `lib/`, `components/` or `app/` to make it pass. 4. Refactor while keeping tests green. ```bash @@ -362,65 +360,64 @@ bun test --watch tests/unit # watch mode bun test --coverage tests/unit tests/integration ``` -E2E tests run under Node.js/Playwright with a live local server: +E2E tests run under Node.js/Playwright against the built static export (the Playwright webServer runs `bun run build` +then serves `out/`): ```bash npx playwright install # one-time: download browsers npx playwright test # full E2E suite (chromium, firefox, webkit) npx playwright test --ui # interactive mode +bun run test:e2e:basepath # NEXT_PUBLIC_BASE_PATH=/preview leg (chromium) ``` ### Lint ```bash -npx eslint js/**/*.js tests/unit/**/*.js tests/integration/**/*.js +npx eslint "lib/**/*.js" "app/**/*.{js,jsx}" "components/**/*" "tests/**/*" ``` ### Environment-aware documentation -The **Getting Started** page shows only the CI sample matching the hosting -environment, detected from the hostname (`*.github.io` → GitHub Actions, -`*.gitlab.io` → GitLab CI, `*.bitbucket.io` → Bitbucket Pipelines; anything else -defaults to GitHub). The samples live in -`templates/workflow-sample-{github,gitlab,bitbucket}.html`, and detection lives in -`detectHostingEnvironment()` in `js/utils.js`. To override detection (e.g. a custom -domain hosting the GitLab variant), pass `hostEnvironment: 'gitlab'` to -`createApp()` in `js/main.js`. +The **Getting Started** page shows only the CI sample matching the hosting environment, detected from the hostname +(`*.github.io` → GitHub Actions, +`*.gitlab.io` → GitLab CI, `*.bitbucket.io` → Bitbucket Pipelines; anything else defaults to GitHub). The samples live +in +`public/templates/workflow-sample-{github,gitlab,bitbucket}.html`, and detection lives in +`lib/host.js` (or `NEXT_PUBLIC_HOSTING_ENVIRONMENT` at build time, plus the +`platform` meta tag). ### Where things live -| Change | Files | -|---|---| -| URL routes | `js/router.js` (+ `tests/unit/router-ext.test.js`) | -| Raw fetching / branch fallback (platform-aware) | `js/fetcher.js` | -| Mustache rendering | `js/renderer.js`, `assets/refactor-first-report.mustache` (port of the RefactorFirst viewer template) | -| Interactive report widgets | `js/report-view.js` (+ CDN libs declared in `index.html`: Chart.js, sigma/graphology, graphlib-dot, svg-pan-zoom, 3d-force-graph, vizdom WASM) | -| Search / type-ahead | `js/search.js` | -| Submission flow | `js/repo-submission.js`, `js/main.js` (`renderAddRepo`) | -| Submission validation (CI) | `ci/process-submissions.sh`, `.github/workflows/add-repository.yml`, `.gitlab-ci.yml`, `bitbucket-pipelines.yml` | -| Error pages | `js/error-handler.js`, `templates/error-*.html` | -| Page content | `templates/*.html` | -| Styling | `css/main.css`, `css/components.css` | -| Listing data | `repositories.txt` (one `user/repo` per line) | -| Scheduled redeploy | `.github/workflows/redeploy.yml` | +| Change | Files | +|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| URL routes | `lib/routes.js` (+ `tests/unit/routes.test.js`) | +| Raw fetching / branch fallback (platform-aware) | `lib/fetcher.js` | +| Mustache rendering | `lib/renderer.js`, `public/assets/refactor-first-report.mustache` (port of the RefactorFirst viewer template) | +| Interactive report widgets | `lib/report-view.js` + CDN widgets loaded via `next/script` in `components/report-view.jsx` (Chart.js, sigma/graphology, graphlib-dot, svg-pan-zoom, 3d-force-graph) and the ES-module bridges in `public/widgets/` (vizdom WASM, three-spritetext) | +| Search / type-ahead | `lib/search.js` + `components/{search-combobox,hero-search,menu-search}.jsx` | +| Submission flow | `lib/repo-submission.js`, `components/repo-submission-form.jsx` | +| Submission validation (CI) | `ci/process-submissions.sh`, `.github/workflows/add-repository.yml`, `.gitlab-ci.yml`, `bitbucket-pipelines.yml` | +| Error pages | `lib/error-handler.js`, `components/error-boundary-view.jsx`, `app/error.jsx`, `app/not-found.jsx` | +| Page content | `app/*/page.jsx` | +| Styling | `css/main.css`, `css/components.css` | +| Listing data | `repositories.txt` (one `user/repo` per line) | +| Scheduled redeploy | `.github/workflows/redeploy.yml` | ### CI/CD -`.github/workflows/test.yml` runs Bun unit/integration tests and the Playwright suite -on every push and pull request. Keep it green before merging. +`.github/workflows/test.yml` runs Bun unit/integration tests and the Playwright suite on every push and pull request. +Keep it green before merging. --- ## Testing -- **Unit** (`tests/unit/`): router, fetcher (incl. branch fallback, retry and - per-platform URL construction), renderer, report-view (charts/graphs/popups), - search, repo-submission (incl. report-file existence check and per-platform - issue URLs), error-handler, rate-limiter, cache-manager, utils. -- **Integration** (`tests/integration/`): search flow, submission flow (missing - report, unknown repo, per-platform issue redirect), report rendering. -- **E2E** (`tests/e2e/`): user journeys (incl. the submission → pre-filled - issue hand-off), cross-browser smoke tests, mobile responsiveness - (hamburger menu, single-column grid). +- **Unit** (`tests/unit/`): router, fetcher (incl. branch fallback, retry and per-platform URL construction), renderer, + report-view (charts/graphs/popups), search, repo-submission (incl. report-file existence check and per-platform issue + URLs), error-handler, rate-limiter, cache-manager, utils. +- **Integration** (`tests/integration/`): search flow, submission flow (missing report, unknown repo, per-platform issue + redirect), report rendering. +- **E2E** (`tests/e2e/`): user journeys (incl. the submission → pre-filled issue hand-off), cross-browser smoke tests, + mobile responsiveness (hamburger menu, single-column grid). Coverage target: 80%+ on core modules. Current suite: 165 tests. diff --git a/app/[username]/[repository]/[branch]/page.jsx b/app/[username]/[repository]/[branch]/page.jsx new file mode 100644 index 0000000..5253a70 --- /dev/null +++ b/app/[username]/[repository]/[branch]/page.jsx @@ -0,0 +1,34 @@ +// /{username}/{repository}/{branch} — report shell pinned to a branch. +// Pre-generated for main/master; other branches reach this route via the +// not-found client redirect which conveys the branch through the ?branch= +// query parameter handled by ReportView. + +import { Suspense } from 'react'; +import { notFound } from 'next/navigation'; +import { loadListedRepositories } from '../../../../lib/repositories'; +import { branchStaticParams } from '../../../../lib/static-params'; +import { isValidGitHubName } from '../../../../lib/routes'; +import ReportView from '../../../../components/report-view'; + +export const dynamicParams = false; + +export function generateStaticParams() { + return branchStaticParams(loadListedRepositories()); +} + +export async function generateMetadata({ params }) { + const { username, repository, branch } = await params; + return { title: `${username}/${repository}@${branch} - RefactorFirst` }; +} + +export default async function BranchPage({ params }) { + const { username, repository, branch } = await params; + if (!isValidGitHubName(username) || !isValidGitHubName(repository)) { + notFound(); + } + return ( + Loading report…

}> + +
+ ); +} diff --git a/app/[username]/[repository]/error.jsx b/app/[username]/[repository]/error.jsx new file mode 100644 index 0000000..3aaf41b --- /dev/null +++ b/app/[username]/[repository]/error.jsx @@ -0,0 +1,7 @@ +'use client'; + +import ErrorBoundaryView from '../../../components/error-boundary-view'; + +export default function RepositoryError({ error, reset }) { + return ; +} diff --git a/app/[username]/[repository]/page.jsx b/app/[username]/[repository]/page.jsx new file mode 100644 index 0000000..447ba73 --- /dev/null +++ b/app/[username]/[repository]/page.jsx @@ -0,0 +1,32 @@ +// /{username}/{repository} — report shell. Pre-generated per listed +// repository; ReportView fetches the report client-side. + +import { Suspense } from 'react'; +import { notFound } from 'next/navigation'; +import { loadListedRepositories } from '../../../lib/repositories'; +import { reportStaticParams } from '../../../lib/static-params'; +import { isValidGitHubName } from '../../../lib/routes'; +import ReportView from '../../../components/report-view'; + +export const dynamicParams = false; + +export function generateStaticParams() { + return reportStaticParams(loadListedRepositories()); +} + +export async function generateMetadata({ params }) { + const { username, repository } = await params; + return { title: `${username}/${repository} - RefactorFirst` }; +} + +export default async function RepositoryPage({ params }) { + const { username, repository } = await params; + if (!isValidGitHubName(username) || !isValidGitHubName(repository)) { + notFound(); + } + return ( + Loading report…

}> + +
+ ); +} diff --git a/app/[username]/error.jsx b/app/[username]/error.jsx new file mode 100644 index 0000000..c3fb1e9 --- /dev/null +++ b/app/[username]/error.jsx @@ -0,0 +1,7 @@ +'use client'; + +import ErrorBoundaryView from '../../components/error-boundary-view'; + +export default function UsernameError({ error, reset }) { + return ; +} diff --git a/app/[username]/page.jsx b/app/[username]/page.jsx new file mode 100644 index 0000000..3d6615e --- /dev/null +++ b/app/[username]/page.jsx @@ -0,0 +1,35 @@ +// /{username} — repository listing page for a GitHub user/org. Statically +// generated per username; pagination and the post-deploy listing refresh +// happen client-side in RepoList. + +import { Suspense } from 'react'; +import { notFound } from 'next/navigation'; +import { loadListedRepositories } from '../../lib/repositories'; +import { userStaticParams } from '../../lib/static-params'; +import { reposForUser } from '../../lib/utils'; +import { isValidGitHubName } from '../../lib/routes'; +import RepoList from '../../components/repo-list'; + +export const dynamicParams = false; + +export function generateStaticParams() { + return userStaticParams(loadListedRepositories()); +} + +export async function generateMetadata({ params }) { + const { username } = await params; + return { title: `${username} - RefactorFirst` }; +} + +export default async function UserPage({ params }) { + const { username } = await params; + if (!isValidGitHubName(username)) { + notFound(); + } + const repositories = reposForUser(loadListedRepositories(), username); + return ( + Loading…

}> + +
+ ); +} diff --git a/app/about/page.jsx b/app/about/page.jsx new file mode 100644 index 0000000..fd54ede --- /dev/null +++ b/app/about/page.jsx @@ -0,0 +1,20 @@ +export default function AboutPage() { + return ( +
+

About RefactorFirst

+

RefactorFirst is a static analysis tool that identifies which classes in your Java + codebase you should refactor first, ranked by cost-benefit. It is based on the paper + Prioritized Technical Debt Identification and uses the XRank metric to surface + the classes with the highest impact on maintainability.

+

How it works

+

Run the RefactorFirst Maven plugin on your project. It generates + .refactorfirst/refactor-first.json — a ranked list of classes to + refactor with effort estimates and recommendations. This site renders those reports + directly from your GitHub repository.

+

Open Source

+

RefactorFirst is open source. Visit the + GitHub repository + to contribute, report issues or learn more.

+
+ ); +} diff --git a/app/add-repo/page.jsx b/app/add-repo/page.jsx new file mode 100644 index 0000000..b15e582 --- /dev/null +++ b/app/add-repo/page.jsx @@ -0,0 +1,11 @@ +// /add-repo — repository submission form. Config comes from the same +// tags (submission-target, platform-base-url) so self-managed GitLab +// deployments keep working by editing the layout. + +import RepoSubmissionForm from '../../components/repo-submission-form'; + +export const metadata = { title: 'Add Your Repository - RefactorFirst' }; + +export default function AddRepoPage() { + return ; +} diff --git a/app/api/page.jsx b/app/api/page.jsx new file mode 100644 index 0000000..6e56e83 --- /dev/null +++ b/app/api/page.jsx @@ -0,0 +1,36 @@ +export default function ApiPage() { + return ( +
+

API for Tool Integrations

+ +

Fetching reports programmatically

+

Reports are plain JSON stored in each repository. Fetch them directly from + GitHub raw content:

+
GET https://raw.githubusercontent.com/<user>/<repo>/refs/heads/<branch>/.refactorfirst/refactor-first.json
+ +

URL patterns

+
    +
  • /<user>/<repo> — rendered report (default branch)
  • +
  • /<user>/<repo>/<branch> — rendered report for a branch
  • +
  • /<user> — all listed repositories of a user
  • +
+ +

JSON schema

+

The report contains projectName, version, + totalClasses, classesToRefactor and a + priorities array with per-class rank, + className, priority, effort, + disharmonies and recommendation fields.

+ +

Webhooks & third-party tools

+

Trigger report regeneration from any CI system by calling + mvn refactorfirst:jsonReport and committing the + .refactorfirst directory.

+ +

Rate limiting & caching

+

GitHub raw and API endpoints are rate-limited (5000 requests/hour authenticated, + 60/hour unauthenticated). Cache responses client-side and respect the + X-RateLimit-* headers.

+
+ ); +} diff --git a/app/documentation/page.jsx b/app/documentation/page.jsx new file mode 100644 index 0000000..e0321e9 --- /dev/null +++ b/app/documentation/page.jsx @@ -0,0 +1,33 @@ +import Link from 'next/link'; + +export default function DocumentationPage() { + return ( +
+

Documentation

+ +

Generating Reports

+

Run the Maven plugin:

+
mvn refactorfirst:jsonReport
+

This writes .refactorfirst/refactor-first.json. A graphical HTML report + is also generated into the targets/site directory by + mvn refactorfirst:report.

+ +

Understanding the Report

+
    +
  • Priority — XRank-based ordering: refactor these classes first.
  • +
  • Effort — estimated relative effort to refactor the class.
  • +
  • Disharmonies — detected design flaws (God Class, Brain Method, Feature Envy, ...).
  • +
  • Recommendation — suggested next step for each class.
  • +
+ +

CI/CD Integration

+

Add the RefactorFirst workflow to your repository so the report regenerates on every + push to the default branch. See Getting Started + for a copy-paste workflow.

+ +

Full Documentation

+

Complete plugin documentation is available in the + RefactorFirst repository.

+
+ ); +} diff --git a/app/error.jsx b/app/error.jsx new file mode 100644 index 0000000..42c6e51 --- /dev/null +++ b/app/error.jsx @@ -0,0 +1,7 @@ +'use client'; + +import ErrorBoundaryView from '../components/error-boundary-view'; + +export default function GlobalError({ error, reset }) { + return ; +} diff --git a/app/examples/page.jsx b/app/examples/page.jsx new file mode 100644 index 0000000..638e48f --- /dev/null +++ b/app/examples/page.jsx @@ -0,0 +1,32 @@ +import Link from 'next/link'; + +export default function ExamplesPage() { + return ( +
+

Example Reports

+

See RefactorFirst reports for real projects. Each link opens the live report + rendered from that repository.

+ +

Featured projects

+
    +
  • + refactorfirst/refactorfirst +

    The RefactorFirst project itself — a medium-sized Maven codebase.

    +
  • +
+ +

What to look for

+
    +
  • Small codebases: short priority lists, quick wins.
  • +
  • Medium codebases: a handful of high-priority God Classes.
  • +
  • Large codebases: long tails — focus on the top of the ranking.
  • +
+ +

Before / after refactoring

+

Compare a report on a release branch (/<user>/<repo>/<branch>) + before and after addressing the top-ranked class to see the impact of your work.

+ +

Add your project via Add Your Repo to appear in this gallery.

+
+ ); +} diff --git a/app/faq/page.jsx b/app/faq/page.jsx new file mode 100644 index 0000000..fba2ac7 --- /dev/null +++ b/app/faq/page.jsx @@ -0,0 +1,54 @@ +import Link from 'next/link'; + +export default function FaqPage() { + return ( +
+

Frequently Asked Questions

+ +

What do the different priority colors mean?

+

Priority is ranked by XRank: higher priority classes give the most benefit per unit + of refactoring effort. Colors range from red (highest priority) to green.

+ +

How often should I run RefactorFirst?

+

On every merge to your default branch. The provided GitHub Actions workflow does + this automatically.

+ +

What if my repository doesn't have a report?

+

Generate one with mvn refactorfirst:jsonReport and commit the + .refactorfirst/refactor-first.json file. See + Getting Started.

+ +

How do I add my repository to the listing?

+

Use the Add Your Repo page. It opens a + pre-filled issue on this site's platform (GitHub, GitLab or Bitbucket); + submitting that issue is all you need to do. You must have write access + to the repository.

+ +

How long until my repository appears after submission?

+

The CI validation picks issues up shortly after they are created (the + GitHub deployment reacts immediately; GitLab and Bitbucket deployments + poll on a schedule), and the site redeploys on a 10-minute schedule — + so in the worst case about 20 minutes.

+ +

Why is there no sign-in on this site?

+

Your identity is captured by your platform when you create the submission + issue: the issue author is a verified account, and the CI job + independently confirms that account has write access to the submitted + repository. That proves ownership without any tokens or apps.

+ +

Is my platform data safe?

+

Yes. This site never receives credentials, tokens or permissions from you. + The validation runs entirely in this project's own CI.

+ +

What are disharmonies like God Class and Brain Method?

+

Disharmonies are design flaws detected from metrics: a God Class does too + much, a Brain Method is an overly complex method, Feature Envy + means a method relies on another class's data more than its own.

+ +

Can I compare branches?

+

You can view the report on any branch via the URL + /<user>/<repo>/<branch>, but side-by-side comparison is + not currently supported.

+
+ ); +} diff --git a/app/feedback/page.jsx b/app/feedback/page.jsx new file mode 100644 index 0000000..08d3b8d --- /dev/null +++ b/app/feedback/page.jsx @@ -0,0 +1,15 @@ +export default function FeedbackPage() { + return ( +
+

Feedback

+

We welcome your feedback! The best place to share ideas, report bugs or request + features is the RefactorFirst project on GitHub.

+ +

When reporting a bug, please include the repository and branch you were viewing + and any error code displayed on the error page.

+
+ ); +} diff --git a/app/getting-started/page.jsx b/app/getting-started/page.jsx new file mode 100644 index 0000000..31452a5 --- /dev/null +++ b/app/getting-started/page.jsx @@ -0,0 +1,49 @@ +import Link from 'next/link'; +import WorkflowSample from '../../components/workflow-sample'; + +export default function GettingStartedPage() { + return ( +
+

Getting Started

+ +

1. Configure the RefactorFirst Maven plugin

+

Add the plugin to your pom.xml:

+
<plugin>
+  <groupId>org.hjug</groupId>
+  <artifactId>refactorfirst-maven-plugin</artifactId>
+  <version>LATEST</version>
+</plugin>
+ +

2. Set up report generation in your repository

+
+ +
+ +

3. Add your repository to this site

+

Once a report exists on your default branch, go to + Add Your Repo and enter your repository name. + A pre-filled issue opens on this site's platform (GitHub, GitLab or + Bitbucket) — submit it there and a CI job validates and adds your + repository automatically.

+ +

How is my identity verified?

+

This site has no login and stores no credentials: your identity is captured + by your platform (GitHub, GitLab or Bitbucket) as the author of the issue + you create. The CI job then verifies — independently — that you actually + have write access to the repository you submitted. This prevents abuse and + keeps the listing trustworthy.

+

Any permissions or apps needed?

+

None. You never grant this site access to anything, and no OAuth apps or + tokens are involved. You just need an account on the platform hosting the + site you are using.

+ +

Troubleshooting

+
    +
  • Report doesn't render? Ensure .refactorfirst/refactor-first.json exists on your default branch.
  • +
  • Changed default branch? Reports are looked up on the branch in the URL. If no branch is specified, the request falls back to main, then master.
  • +
  • Submission fails? You need write access to the repository, and it must not already be listed.
  • +
  • Submission is slow to appear? The site redeploys on a 10-minute schedule.
  • +
+
+ ); +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..3d821fe --- /dev/null +++ b/app/globals.css @@ -0,0 +1,410 @@ +/* Main stylesheet: CSS custom properties, base layout, footer. */ + +:root { + --brand-color: #2a6f97; + --brand-accent: #61c0bf; + --text-color: #1f2d3d; + --muted-color: #5c6b7a; + --bg-color: #ffffff; + --surface-color: #f5f8fa; + --error-color: #c0392b; + --success-color: #1e8449; + --menu-max-height: 140px; + --menu-height: 48px; + --radius: 6px; + --shadow: 0 1px 4px rgba(0, 0, 0, 0.12); + /* mvp.css caps header/footer/main at --width-content (1080px by default). + Report pages and listings should fill the view: content uses 95% of the + viewport width instead. */ + --width-content: min(3840px, 95vw); +} + +body { + margin: 0; + color: var(--text-color); + background: var(--bg-color); +} + +.skip-link { + position: absolute; + left: -9999px; + top: 0; + background: var(--brand-color); + color: #fff; + padding: 0.5rem 1rem; + z-index: 100; +} + +.skip-link:focus { + left: 0; +} + +/* mvp.css targets `footer, header, main` with the same specificity and is + loaded after the bundle CSS, so the id-qualified selector is the stable + way to win the cascade. Report pages should fill 95% of the view. */ +main#app { + max-width: min(3840px, 95vw); + margin: 0 auto; + padding: 1rem 1.5rem 3rem; + min-height: 60vh; +} + +main:focus { + outline: none; +} + +.hero { + text-align: center; + padding: 2.5rem 1rem; +} + +.hero h1 { + color: var(--brand-color); + margin-bottom: 0.25rem; +} + +.loading { + text-align: center; + color: var(--muted-color); + padding: 3rem 0; +} + +.site-footer { + text-align: center; + padding: 1.5rem 1rem; + color: var(--muted-color); + font-size: 0.85rem; + border-top: 1px solid var(--surface-color); +} + +a { + color: var(--brand-color); +} + +code { + background: var(--surface-color); + padding: 0.1em 0.35em; + border-radius: 4px; +} + +pre { + background: var(--surface-color); + padding: 1rem; + border-radius: var(--radius); + overflow-x: auto; +} + +:focus-visible { + outline: 2px solid var(--brand-accent); + outline-offset: 2px; +} +/* Component styles: top menu, search, repo grid, pagination, forms, errors. */ + +/* ---- Top navigation (max 140px, mobile hamburger) ---- */ +#top-menu { + background: var(--surface-color); + border-bottom: 1px solid #dde4ea; + /* mvp.css adds generous header padding; reset it so max-height bounds the + whole rendered box (padding contributes outside max-height). */ + padding: 0; + max-height: var(--menu-max-height); +} + +.menu-bar { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: nowrap; + max-width: 1200px; + margin: 0 auto; + padding: 0.3rem 0.8rem; + min-height: 48px; + overflow: visible; +} + +.brand { + display: flex; + align-items: center; + gap: 0.4rem; + font-weight: bold; + color: var(--brand-color); + text-decoration: none; + white-space: nowrap; + font-size: 0.9rem; +} + +.menu-search { + position: relative; + /* Grow into the space freed by the tighter link spacing. */ + flex: 1 0 240px; + /* Space between the last menu link and the search bar matches the + left-to-right spacing measured between menu links: the .menu-bar flex + gap plus this small margin reproduce it (verified at 1280px via E2E). */ + margin-left: 0.1rem; +} + +.menu-search input { + width: 100%; + padding: 0.35rem 0.6rem; + border: 1px solid #c6d2dc; + border-radius: var(--radius); +} + +.menu-toggle { + display: none; + background: none; + border: 1px solid #c6d2dc; + border-radius: var(--radius); + font-size: 1.1rem; + padding: 0.25rem 0.6rem; + cursor: pointer; +} + +.menu-links { + display: flex; + align-items: center; + gap: 0.1rem; + flex-wrap: nowrap; + flex-shrink: 0; /* never squeeze the links — the bar scrolls/wraps instead */ + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; +} + +/* mvp.css gives every li a horizontal margin — zero it so the menu items + sit at the same tight rhythm as the search bar to their right. */ +.menu-links li { + margin-left: 0; + margin-right: 0; +} + +.menu-links a { + display: inline-block; + padding: 0.15rem 0.2rem; + border-radius: var(--radius); + text-decoration: none; + color: var(--text-color); + font-size: 0.8rem; +} + +.menu-links a:hover, +.menu-links a:focus-visible { + background: var(--brand-color); + color: #fff; +} + +/* ---- Search results ---- */ +.search-results { + position: absolute; + left: 0; + right: 0; + top: 100%; + z-index: 50; + margin: 0.15rem 0 0; + padding: 0; + list-style: none; + background: #fff; + border: 1px solid #c6d2dc; + border-radius: var(--radius); + box-shadow: var(--shadow); + max-height: 16rem; + overflow-y: auto; +} + +.search-results li { + padding: 0.5rem 0.75rem; + cursor: pointer; +} + +.search-results li.active, +.search-results li:hover { + background: var(--brand-color); + color: #fff; +} + +.hero-search { + position: relative; + max-width: 480px; + margin: 1rem auto; + text-align: left; +} + +/* ---- Repository grid ---- */ +.repo-grid, +.featured-repos ul { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.75rem; + list-style: none; + padding: 0; + max-width: 720px; + margin-left: auto; + margin-right: auto; +} + +.repo-card { + display: block; + padding: 0.85rem 1rem; + border: 1px solid #dde4ea; + border-radius: var(--radius); + text-decoration: none; + color: var(--text-color); + background: #fff; + box-shadow: var(--shadow); +} + +.repo-card:hover, +.repo-card:focus-visible { + border-color: var(--brand-color); + color: var(--brand-color); +} + +/* ---- Pagination ---- */ +.pagination { + display: flex; + gap: 0.4rem; + justify-content: center; + margin-top: 1.5rem; +} + +.pagination .page { + padding: 0.35rem 0.65rem; + border: 1px solid #c6d2dc; + border-radius: var(--radius); + text-decoration: none; +} + +.pagination .page.current { + background: var(--brand-color); + border-color: var(--brand-color); + color: #fff; +} + +/* ---- Forms / add-repo ---- */ +.user-info { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 1rem; +} + +.user-avatar { + border-radius: 50%; +} + +#repo-form { + display: grid; + gap: 0.5rem; + max-width: 420px; +} + +.form-status.error { + color: var(--error-color); +} + +.form-status.success { + color: var(--success-color); +} + +.cta { + display: inline-block; + background: var(--brand-color); + color: #fff; + padding: 0.6rem 1.2rem; + border-radius: var(--radius); + border: none; + font-size: 1rem; + text-decoration: none; + cursor: pointer; +} + +.cta:hover { + background: var(--brand-accent); + color: #fff; +} + +/* ---- Error pages ---- */ +.error-page { + max-width: 640px; + margin: 3rem auto; + text-align: center; + padding: 2rem; + border: 1px solid #f0c6c0; + border-radius: var(--radius); + background: #fdf6f5; +} + +.error-page h1 { + color: var(--error-color); +} + +.error-code { + color: var(--muted-color); + font-size: 0.85rem; +} + +.error-actions { + display: flex; + gap: 1rem; + justify-content: center; + margin-top: 1rem; +} + +.error-actions .retry { + background: var(--brand-color); + color: #fff; + border: none; + border-radius: var(--radius); + padding: 0.5rem 1rem; + cursor: pointer; +} + +/* ---- Responsive ---- */ +/* The header (brand + search + 9 menu links at 0.8rem) needs roughly 900px. + * Below that widths links would clip because .menu-links uses nowrap + + * overflow:hidden without a visible toggle, so switch to the collapsible + * menu early — before any navigation item becomes unreachable. */ +@media (max-width: 900px) { + #top-menu { + max-height: none; + } + + .menu-bar { + flex-wrap: wrap; + overflow: visible; + } + + .menu-toggle { + display: block; + margin-left: auto; + order: 2; + } + + .menu-search { + /* On mobile the search sits on its own row between toggle and links. */ + order: 3; + flex-basis: 100%; + max-width: none; + margin-left: 0; + margin-top: 0.5rem; + } + + .menu-links { + display: none; + flex-basis: 100%; + flex-direction: column; + align-items: stretch; + padding-bottom: 0.5rem; + order: 4; + } + + .menu-links.open { + display: flex; + } + + .repo-grid, + .featured-repos ul { + grid-template-columns: 1fr; + } +} diff --git a/app/layout.jsx b/app/layout.jsx new file mode 100644 index 0000000..6eac8d5 --- /dev/null +++ b/app/layout.jsx @@ -0,0 +1,44 @@ +import SiteHeader from '../components/site-header'; +import SiteFooter from '../components/site-footer'; +import SentryProvider from '../components/sentry-provider'; +import { loadListedRepositories } from '../lib/repositories.js'; +import './globals.css'; + +// frame-ancestors is intentionally served via HTTP header (ignored in meta); +// platform deployment docs list the header. The `submission-target` meta names +// the "/" project whose issue tracker receives submissions; +// self-managed GitLab deployments also set a platform-base-url meta. +// NOTE: after `next build`, scripts/fix-csp-hashes.mjs injects sha256 hashes +// of the inline bootstrap scripts into this policy so the strict CSP survives +// the static export (see plans/nextjs-conversion.md Spike Results C). +const CSP = + "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://esm.sh https://buttons.github.io 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; img-src 'self' https://avatars.githubusercontent.com https://buttons.github.io https://github.githubassets.com data:; connect-src 'self' https://api.github.com https://raw.githubusercontent.com https://cdn.jsdelivr.net https://esm.sh https://gitlab.com https://api.gitlab.com https://bitbucket.org https://api.bitbucket.org; base-uri 'self';"; + +export const metadata = { + title: 'RefactorFirst - Know Where to Refactor First', + icons: { icon: '/assets/logo.png' }, +}; + +export default function RootLayout({ children }) { + const repositories = loadListedRepositories(); + return ( + + + + + + + + + + +
{children}
+ + + + + ); +} diff --git a/app/not-found.jsx b/app/not-found.jsx new file mode 100644 index 0000000..fc44662 --- /dev/null +++ b/app/not-found.jsx @@ -0,0 +1,71 @@ +'use client'; + +import { useEffect } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { isValidGitHubName, STATIC_PAGES } from '../lib/routes.js'; +import { getBasePath } from '../lib/base-path'; + +// Exported for the 404.html produced by the static export. GitHub Pages (and +// similar static hosts) serve this page for any path that has no exported +// HTML file. Two recovery behaviors: +// - Branch deep links (/user/repo/) that are not pre-generated are +// redirected to the repo shell (/user/repo/) which loads the requested +// branch client-side (Technical Appendix §6). +// - Repos added after the last deploy get the "recently added" note +// (Technical Appendix §5). +// +// `performRedirect` is injectable for tests (window.location is not +// configurable under jsdom). +export default function NotFound({ performRedirect }) { + const pathname = usePathname() || ''; + const basePath = getBasePath(); + const path = basePath && pathname.startsWith(basePath) + ? pathname.slice(basePath.length) || '/' + : pathname; + const segments = path.split('/').filter(Boolean); + const [username, repository] = segments; + + const isRepoLike = + segments.length >= 2 && + username !== 'add-repo' && + !STATIC_PAGES.has(username) && + isValidGitHubName(username) && + isValidGitHubName(repository); + + // Branch deep links are redirected to the statically generated repo shell; + // ReportView picks the requested branch back up from the query parameter + // (Technical Appendix §6). Branch names may contain slashes, so any + // segments beyond user/repo belong to the branch name. + const redirectTarget = + segments.length >= 3 && isRepoLike + ? `/${username}/${repository}/?branch=${encodeURIComponent(segments.slice(2).join('/'))}` + : null; + + useEffect(() => { + const redirect = performRedirect || (url => window.location.replace(url)); + if (redirectTarget) { + redirect(`${basePath}${redirectTarget}`); + } + }, [redirectTarget, basePath, performRedirect]); + + return ( +
+

Page Not Found

+

+ The page or repository you requested does not exist. If you were looking for a + report, check that the repository contains + .refactorfirst/refactor-first.json on its default branch. +

+ {segments.length === 2 && isRepoLike && ( +

+ This repository may have been recently added. The listing is updated during deployment. + If you just submitted this repository, please wait a few minutes and try again. +

+ )} +

+ Back to home · Getting Started +

+
+ ); +} diff --git a/app/page.jsx b/app/page.jsx new file mode 100644 index 0000000..905e99b --- /dev/null +++ b/app/page.jsx @@ -0,0 +1,6 @@ +import Landing from '../components/landing'; +import { loadListedRepositories } from '../lib/repositories.js'; + +export default function HomePage() { + return ; +} diff --git a/app/privacy-policy/page.jsx b/app/privacy-policy/page.jsx new file mode 100644 index 0000000..36fe04a --- /dev/null +++ b/app/privacy-policy/page.jsx @@ -0,0 +1,27 @@ +export default function PrivacyPolicyPage() { + return ( +
+

Privacy Policy

+ +

Data Collected

+

RefactorFirst does not run analytics, tracking or logins. This site does not ask for + or store your platform credentials or access tokens.

+ +

Submissions

+

When you add a repository, you create an issue on this site's platform + (GitHub, GitLab or Bitbucket). Your platform username (the issue author) + and the repository name are recorded in the public + repositories.txt file, the public issue and CI logs for + auditing. The validation job uses platform credentials to verify that + you have write access to the submitted repository by checking membership + and permission status via the platform API. This check is performed by the + CI system and the results are retained in CI logs and issue comments.

+ +

Cookies

+

This site does not use cookies.

+ +

Your rights

+

To have a submission removed, open an issue in the RefactorFirst repository.

+
+ ); +} diff --git a/app/terms-of-service/page.jsx b/app/terms-of-service/page.jsx new file mode 100644 index 0000000..f9f5e28 --- /dev/null +++ b/app/terms-of-service/page.jsx @@ -0,0 +1,27 @@ +export default function TermsOfServicePage() { + return ( +
+

Terms of Service

+ +

Acceptable use

+

Only submit repositories you own or have write access to, and only repositories with + a valid RefactorFirst report. Do not attempt to abuse, flood or disrupt the service.

+ +

Content moderation

+

Repository listings may be removed at the maintainers' discretion, including for + inappropriate content or abuse.

+ +

No warranty

+

The service is provided “as is”, without warranty of any kind. Reports are + generated by the RefactorFirst tool and rendered as-is from each repository.

+ +

Limitation of liability

+

To the maximum extent permitted by law, the maintainers are not liable for damages + arising from use of this service.

+ +

DMCA

+

To report content that infringes your rights, open an issue in the RefactorFirst + repository with details of the claim.

+
+ ); +} diff --git a/assets/refactor-first-report.mustache b/assets/refactor-first-report.mustache index 799f595..9aa29bc 100644 --- a/assets/refactor-first-report.mustache +++ b/assets/refactor-first-report.mustache @@ -57,10 +57,16 @@ cursor: pointer; font-size: 20px; font-weight: bold; + background: none; + border: none; + padding: 2px 6px; + /* Keep the close control clickable above graph canvases injected + into the popup by sigma/3d-force-graph. */ + z-index: 10; } .chart-container { - max-width: 1100px; + width: 95%; margin: 20px auto; } @@ -153,7 +159,7 @@ @@ -236,7 +242,7 @@ @@ -319,7 +325,7 @@
-
+
@@ -395,7 +401,7 @@ diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index 384d72e..801028a 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -1,46 +1,33 @@ -# Bitbucket Pipelines configuration for this RefactorFirst Pages site. -# -# Bitbucket serves static sites from a repository named .bitbucket.io -# automatically whenever files are pushed, so no deploy step is needed here. -# This pipeline keeps repositories.txt tidy and verifies the site files exist. +# Bitbucket Pipelines: build the static export for Bitbucket static hosting. +# The `out/` directory can then be published using the Bitbucket Pages +# add-on or any static host of your choice. -image: alpine:3.20 +image: node:22 -pipelines: - default: - - step: - name: Validate site files - script: - - apk add --no-cache grep coreutils - - test -f index.html - - test -f repositories.txt - # Listing must be sorted, unique and well-formed (one user/repo per line) - - sort -c repositories.txt - - '! grep -Ev "^[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*$" repositories.txt' +definitions: + caches: + bun-deps: node_modules + next-cache: .next/cache - custom: - # Manual pipeline: normalize repositories.txt and push the result back. - sort-repos: +pipelines: + branches: + main: - step: - name: Normalize repositories.txt + name: Test + caches: + - bun-deps script: - - sort -u -o repositories.txt repositories.txt - - git config user.email "pipeline-bot@bitbucket.org" - - git config user.name "Bitbucket Pipeline" - - git add repositories.txt - - git commit -m "Normalize repositories.txt" || echo "No changes" - - git push - - # Processes open "Add repository: owner/repo" issues: validates write - # access of the issue author, checks the report file, commits - # repositories.txt and closes the issue with the outcome. - # Bitbucket has no issue-triggered pipelines: schedule this pipeline - # (Repository settings -> Pipelines -> Schedules) and define the secured - # repository variables BITBUCKET_CLIENT_ID and BITBUCKET_CLIENT_SECRET - # (workspace OAuth consumer with Issues + Repositories scopes). - process-submissions: + - npm install -g bun + - bun install + - bun test tests/unit tests/integration - step: - name: Process submission issues + name: Build static export + caches: + - bun-deps + - next-cache script: - - apk add --no-cache curl jq - - sh ci/process-submissions.sh bitbucket + - npm install -g bun + - bun install + - NEXT_PUBLIC_HOSTING_ENVIRONMENT=bitbucket bun run build + artifacts: + - out/** diff --git a/bun.config.js b/bun.config.js index 22559e3..a79ed52 100644 --- a/bun.config.js +++ b/bun.config.js @@ -3,8 +3,8 @@ export default { environment: 'jsdom', coverage: { reporter: ['text', 'html'], - include: ['js/**/*.js'], + include: ['lib/**/*.js'], exclude: ['tests/'] } } -}; \ No newline at end of file +}; diff --git a/bun.lock b/bun.lock index 9aa6090..1308558 100644 --- a/bun.lock +++ b/bun.lock @@ -4,10 +4,21 @@ "workspaces": { "": { "name": "refactorfirst-page", + "dependencies": { + "dompurify": "^3.4.15", + "mustache": "^4.2.0", + "next": "^16.3.5", + "react": "^19.3.0", + "react-dom": "^19.3.0", + }, "devDependencies": { "@playwright/test": "^1.40.0", "@sentry/browser": "^7.80.0", + "@testing-library/dom": "^10.4.2", + "@testing-library/react": "^16.3.3", "eslint": "^8.55.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.1.1", "jsdom": "^23.0.0", }, }, @@ -17,6 +28,40 @@ "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@2.0.2", "", { "dependencies": { "bidi-js": "^1.0.3", "css-tree": "^2.3.1", "is-potential-custom-element-name": "^1.0.1" } }, "sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ=="], + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + + "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + "@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="], "@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="], @@ -27,6 +72,8 @@ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="], + "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -41,6 +88,88 @@ "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.3" }, "os": "darwin", "cpu": "x64" }, "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw=="], + + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "os": "freebsd" }, "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.3", "", { "os": "linux", "cpu": "none" }, "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.3" }, "os": "linux", "cpu": "arm" }, "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.3" }, "os": "linux", "cpu": "none" }, "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.3" }, "os": "linux", "cpu": "s390x" }, "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.4", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA=="], + + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "cpu": "none" }, "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.4", "", { "os": "win32", "cpu": "x64" }, "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@next/env": ["@next/env@16.3.5", "", {}, "sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pMmGgETfKvElucLHtVaeiMRbp2zUbvKx7b1yGko0liBz3cw1mKSggWN/Rp/wPz8z+E1O82u3r4L1Co+ZS5hokQ=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-76VaGYvf6HPa5/w12yLkE3dXTn9AfdEviI79oEL3aZoAmRLc9rWitjWqyjViVysK/ht/y9YKzFkBrUdi/wGkow=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-zKDELJ5jSQMHeO/hmXUQsAzagX4bQD4OiMi3pQ5FbUj+yK506oLVHnKA2YXMlbg1EHHqJYtyePOgByIDXD1lqw=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-7Vql0pgzCoHagv6+FNOZoqmJqA52c6zeVbhtS/47qFozO1MSx4ms7x7GHiciY8R5CDsSMKMQjJEryoJLcsBIbA=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-NH/xzehyHEFWE2nlcZon7TB/0+H4shfWCi7S1zka815XCOhJDYZhoeJtOYy0dh0WVRWACVXSyGNFFytoMxUhRg=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-lV4+EhWMfS8jcC+EH2nn/Cm5cn6XsgbE07bU9tMH8fCo0tNAqhyzi1b5wQ/Tn6NGFTvKDY65w3ZH95EjwBRAnQ=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-/wKzAREX2RF++MhicjDbg8tGn2AiBIM0+EFeTFKoUEUbW5D6amCJehd5Z5G1H5/gxNdgnwoXMcHz24H/c2tGkQ=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-LNdCHzgLFc+UeqMS84LzXPaeBRKyqDN9OMyFAr1OrB0XrNw78IRrEVtZvvA7245W/HsaoeVOQX9jPjPk8jojwA=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -67,6 +196,16 @@ "@sentry/utils": ["@sentry/utils@7.120.4", "", { "dependencies": { "@sentry/types": "7.120.4" } }, "sha512-zCKpyDIWKHwtervNK2ZlaK8mMV7gVUijAgFeJStH+CU/imcdquizV3pFLlSQYRswG+Lbyd6CT/LGRh3IbtkCFw=="], + "@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.2", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q=="], + + "@testing-library/react": ["@testing-library/react@16.3.3", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.4.0", "", {}, "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ=="], "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], @@ -79,24 +218,56 @@ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], + + "array-includes": ["array-includes@3.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "es-shim-unscopables": "^1.1.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg=="], + + "array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="], + + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], + + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], + + "array.prototype.tosorted": ["array.prototype.tosorted@1.1.4", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3", "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA=="], + + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.24", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg=="], + "bidi-js": ["bidi-js@1.1.0", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg=="], "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], + "browserslist": ["browserslist@4.29.0", "", { "dependencies": { "baseline-browser-mapping": "^2.11.23", "caniuse-lite": "^1.0.30001810", "electron-to-chromium": "^1.5.427", "node-releases": "^2.0.55", "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA=="], + + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], @@ -105,6 +276,8 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-tree": ["css-tree@2.3.1", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="], @@ -113,32 +286,68 @@ "data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="], + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], + + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], + + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "dompurify": ["dompurify@3.4.15", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "electron-to-chromium": ["electron-to-chromium@1.5.429", "", {}, "sha512-/1ENIE3cx4HTIx4IfPZFaOunJmsrSVTnj6coXoRVbiJUbkeTyFkJvBeWGkdgh08OhFbxYLMT1kbkwFVSarq6Ow=="], + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], + + "es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-iterator-helpers": ["es-iterator-helpers@1.4.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0" } }, "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q=="], + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], + + "es-to-primitive": ["es-to-primitive@1.3.4", "", { "dependencies": { "es-abstract-get": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "is-callable": "^1.2.7", "is-date-object": "^1.1.0", "is-symbol": "^1.1.1" } }, "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + "eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "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" }, "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" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="], + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -169,34 +378,58 @@ "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "function.prototype.name": ["function.prototype.name@1.2.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2", "hasown": "^2.0.4", "is-callable": "^1.2.7", "is-document.all": "^1.0.0" } }, "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew=="], + + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="], "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], @@ -217,26 +450,86 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], + + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], + + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], + + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], + + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], + + "is-document.all": ["is-document.all@1.0.0", "", { "dependencies": { "call-bound": "^1.0.4" } }, "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], + + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], + + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], + + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], + + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], + + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], + + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], + + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], "jsdom": ["jsdom@23.2.0", "", { "dependencies": { "@asamuzakjp/dom-selector": "^2.0.1", "cssstyle": "^4.0.1", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "is-potential-custom-element-name": "^1.0.1", "parse5": "^7.1.2", "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^4.1.3", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0", "ws": "^8.16.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^2.11.2" }, "optionalPeers": ["canvas"] }, "sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA=="], + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -249,7 +542,11 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -263,12 +560,38 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + + "nanoid": ["nanoid@3.3.19", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "next": ["next@16.3.5", "", { "dependencies": { "@next/env": "16.3.5", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.3.5", "@next/swc-darwin-x64": "16.3.5", "@next/swc-linux-arm64-gnu": "16.3.5", "@next/swc-linux-arm64-musl": "16.3.5", "@next/swc-linux-x64-gnu": "16.3.5", "@next/swc-linux-x64-musl": "16.3.5", "@next/swc-win32-arm64-msvc": "16.3.5", "@next/swc-win32-x64-msvc": "16.3.5", "sharp": "^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-MdtsTgzyfCPRLC6uJ1mN8ao7lyJ4BB0U6Inhnx3gta1UcCIdHK3yxLG0E8OWQteWD8/Q0qb8A5o7wJaL8M9y2w=="], + + "node-exports-info": ["node-exports-info@1.6.2", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag=="], + + "node-releases": ["node-releases@2.0.55", "", {}, "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + + "object.entries": ["object.entries@1.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.1" } }, "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw=="], + + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], + + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "optionator": ["optionator@0.9.4", "", { "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" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "own-keys": ["own-keys@1.0.2", "", { "dependencies": { "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], @@ -283,12 +606,24 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "playwright": ["playwright@1.63.0", "", { "dependencies": { "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg=="], "playwright-core": ["playwright-core@1.63.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -297,10 +632,22 @@ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "react": ["react@19.3.0", "", {}, "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog=="], + + "react-dom": ["react-dom@19.3.0", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.0" } }, "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], + + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "requires-port": ["requires-port@1.0.0", "", {}, "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="], + "resolve": ["resolve@2.0.0-next.7", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.2", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -311,22 +658,64 @@ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "safe-array-concat": ["safe-array-concat@1.1.4", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "get-intrinsic": "^1.3.0", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg=="], + + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + "scheduler": ["scheduler@0.28.0", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], + + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], + + "sharp": ["sharp@0.35.4", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.4", "@img/sharp-darwin-x64": "0.35.4", "@img/sharp-freebsd-wasm32": "0.35.4", "@img/sharp-libvips-darwin-arm64": "1.3.3", "@img/sharp-libvips-darwin-x64": "1.3.3", "@img/sharp-libvips-linux-arm": "1.3.3", "@img/sharp-libvips-linux-arm64": "1.3.3", "@img/sharp-libvips-linux-ppc64": "1.3.3", "@img/sharp-libvips-linux-riscv64": "1.3.3", "@img/sharp-libvips-linux-s390x": "1.3.3", "@img/sharp-libvips-linux-x64": "1.3.3", "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", "@img/sharp-libvips-linuxmusl-x64": "1.3.3", "@img/sharp-linux-arm": "0.35.4", "@img/sharp-linux-arm64": "0.35.4", "@img/sharp-linux-ppc64": "0.35.4", "@img/sharp-linux-riscv64": "0.35.4", "@img/sharp-linux-s390x": "0.35.4", "@img/sharp-linux-x64": "0.35.4", "@img/sharp-linuxmusl-arm64": "0.35.4", "@img/sharp-linuxmusl-x64": "0.35.4", "@img/sharp-webcontainers-wasm32": "0.35.4", "@img/sharp-win32-arm64": "0.35.4", "@img/sharp-win32-ia32": "0.35.4", "@img/sharp-win32-x64": "0.35.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="], + + "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], + + "string.prototype.matchall": ["string.prototype.matchall@4.1.0", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "get-intrinsic": "^1.3.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.4", "set-function-name": "^2.0.2", "side-channel": "^1.1.1" } }, "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ=="], + + "string.prototype.repeat": ["string.prototype.repeat@1.0.0", "", { "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" } }, "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w=="], + + "string.prototype.trim": ["string.prototype.trim@1.2.11", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.2", "es-object-atoms": "^1.1.2", "has-property-descriptors": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w=="], + + "string.prototype.trimend": ["string.prototype.trimend@1.0.10", "", { "dependencies": { "call-bind": "^1.0.9", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-object-atoms": "^1.1.2" } }, "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw=="], + + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "@babel/core": "*", "babel-plugin-macros": "*", "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "optionalPeers": ["@babel/core", "babel-plugin-macros"] }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], @@ -335,12 +724,26 @@ "tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], + + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], + + "typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="], + + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + "universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="], + "update-browserslist-db": ["update-browserslist-db@1.3.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "url-parse": ["url-parse@1.5.10", "", { "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ=="], @@ -357,6 +760,14 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], + + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], + + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], + + "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -367,8 +778,24 @@ "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="], + + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + + "@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cssstyle/rrweb-cssom": ["rrweb-cssom@0.8.0", "", {}, "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw=="], + + "eslint-plugin-react/doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + + "sharp/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], } } diff --git a/components/error-boundary-view.jsx b/components/error-boundary-view.jsx new file mode 100644 index 0000000..981daea --- /dev/null +++ b/components/error-boundary-view.jsx @@ -0,0 +1,24 @@ +'use client'; + +// Shared view for route error boundaries (app/error.jsx etc.): maps any +// captured error through lib/error-handler.js so the copy, classification +// and retry affordance match the legacy inline error pages. + +import { useEffect } from 'react'; +import { errorPageHtml, logError } from '../lib/error-handler'; + +export default function ErrorBoundaryView({ error, reset }) { + useEffect(() => { + logError(error, { route: 'error-boundary' }); + }, [error]); + + return ( + <> + {/* eslint-disable-next-line react/no-danger */} +
+

+ +

+ + ); +} diff --git a/components/hero-search.jsx b/components/hero-search.jsx new file mode 100644 index 0000000..a2a0fa0 --- /dev/null +++ b/components/hero-search.jsx @@ -0,0 +1,18 @@ +'use client'; + +import SearchCombobox from './search-combobox'; + +// Hero search on the landing page (immediate results, as in the legacy page). +export default function HeroSearch({ repositories, debounceMs = 0 }) { + return ( +
+ + +
+ ); +} diff --git a/components/landing.jsx b/components/landing.jsx new file mode 100644 index 0000000..27b8602 --- /dev/null +++ b/components/landing.jsx @@ -0,0 +1,35 @@ +import Link from 'next/link'; +import HeroSearch from './hero-search'; +import { buildRepositoryListUrl } from '../lib/routes.js'; + +export const FEATURED_COUNT = 6; + +// Landing page content (hero, search, CTA, featured repos). Receives the +// parsed repositories.txt listing from the RSC page. +export default function Landing({ repositories }) { + const featured = repositories.slice(0, FEATURED_COUNT); + return ( + <> +
+

RefactorFirst

+

Know which parts of your codebase to refactor first. Search for a repository to see its report.

+
+ +
+ Add My Repo +
+
+

Featured Repositories

+
+
    + {featured.map(repo => ( +
  • + {repo.fullName} +
  • + ))} +
+
+
+ + ); +} diff --git a/components/menu-search.jsx b/components/menu-search.jsx new file mode 100644 index 0000000..e63c2f2 --- /dev/null +++ b/components/menu-search.jsx @@ -0,0 +1,17 @@ +'use client'; + +import SearchCombobox from './search-combobox'; + +// Top-menu search (100ms debounce, as in the legacy header). +export default function MenuSearch({ repositories, debounceMs = 100 }) { + return ( +
+ +
+ ); +} diff --git a/components/menu-toggle.jsx b/components/menu-toggle.jsx new file mode 100644 index 0000000..217e124 --- /dev/null +++ b/components/menu-toggle.jsx @@ -0,0 +1,30 @@ +'use client'; + +import { useState } from 'react'; + +// Hamburger toggle for the main navigation (small viewports). Mirrors the +// legacy js/main.js behavior: toggles the `open` class on #menu-links and +// keeps aria-expanded in sync. +export default function MenuToggle() { + const [expanded, setExpanded] = useState(false); + + const toggle = () => { + const links = document.getElementById('menu-links'); + const next = !expanded; + setExpanded(next); + if (links) links.classList.toggle('open', next); + }; + + return ( + + ); +} diff --git a/components/platform-config.jsx b/components/platform-config.jsx new file mode 100644 index 0000000..b26ea8c --- /dev/null +++ b/components/platform-config.jsx @@ -0,0 +1,43 @@ +'use client'; + +// Deployment config provided to client components: hosting environment, +// platform API base URL (self-managed GitLab) and the submission target +// (the listing repository whose issue tracker collects new submissions). +// Values come from tags in the exported HTML so self-managed +// deployments keep working by editing the layout, exactly as before; +// a Server Component or test can override them via the `value` prop. + +import { createContext, useContext, useMemo } from 'react'; +import { DEFAULT_SUBMISSION_TARGET } from '../lib/repo-submission'; +import { detectHostingEnvironment, getPlatformBaseUrl, getSubmissionTarget, readMetaTag } from '../lib/host'; + +const PlatformConfigContext = createContext(null); + +function detectConfig() { + const environment = detectHostingEnvironment(window.location.hostname); + return { + environment, + platformBaseUrl: getPlatformBaseUrl(readMetaTag) || null, + submissionTarget: getSubmissionTarget(readMetaTag) || DEFAULT_SUBMISSION_TARGET + }; +} + +export function PlatformConfigProvider({ value, children }) { + const resolved = useMemo(() => { + const detected = typeof window === 'undefined' ? {} : detectConfig(); + return value || detected; + }, [value]); + return ( + + {children} + + ); +} + +export function usePlatformConfig() { + const context = useContext(PlatformConfigContext); + if (context !== null) return context; + return typeof window === 'undefined' + ? { environment: 'github', platformBaseUrl: null, submissionTarget: DEFAULT_SUBMISSION_TARGET } + : detectConfig(); +} diff --git a/components/repo-list.jsx b/components/repo-list.jsx new file mode 100644 index 0000000..ceeae77 --- /dev/null +++ b/components/repo-list.jsx @@ -0,0 +1,68 @@ +'use client'; + +// User repository listing at /{username}: paginated card grid. The listing +// is baked into the static build, but a repository submitted after the last +// deploy would 404 — so the component refreshes repositories.txt client-side +// once and adopts the newer listing when it has grown (Technical Appendix §5). + +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; +import { withBasePath } from '../lib/base-path'; +import { parseRepositories } from '../lib/search'; +import { paginate, reposForUser, sortByRepository } from '../lib/utils'; + +export default function RepoList({ username, initialRepositories = [] }) { + const searchParams = useSearchParams(); + const [repositories, setRepositories] = useState(() => + reposForUser(initialRepositories, username) + ); + + useEffect(() => { + let cancelled = false; + fetch(withBasePath('/repositories.txt')) + .then(response => (response.ok ? response.text() : null)) + .then(text => { + if (cancelled || !text) return; + const fresh = reposForUser(parseRepositories(text), username); + setRepositories(current => (fresh.length > current.length ? fresh : current)); + }) + .catch(() => {}); + return () => { + cancelled = true; + }; + }, [username]); + + const pageParam = Number(searchParams.get('page')) || 1; + const sorted = sortByRepository(repositories); + const { items, page, totalPages } = paginate(sorted, pageParam); + + return ( +
+

{username}

+

RefactorFirst reports published for this user

+
+ {items.map(repo => ( + + {repo.repository} + + ))} +
+ {totalPages > 1 && ( + + )} +
+ ); +} diff --git a/components/repo-submission-form.jsx b/components/repo-submission-form.jsx new file mode 100644 index 0000000..e5c1201 --- /dev/null +++ b/components/repo-submission-form.jsx @@ -0,0 +1,98 @@ +'use client'; + +// Repository submission form (/add-repo). Validates input inline, checks the +// target repository for a published report, then hands off to a pre-filled +// platform issue in a new tab — no login or tokens on this site; the issue +// author is the submitter identity (checked in CI). + +import { useState } from 'react'; +import { validateRepositoryInput, submitRepository, platformLabel } from '../lib/repo-submission'; +import { logError } from '../lib/error-handler'; +import { usePlatformConfig } from './platform-config'; + +export default function RepoSubmissionForm({ onExternalRedirect }) { + const { environment, platformBaseUrl, submissionTarget } = usePlatformConfig(); + const [pending, setPending] = useState(false); + const [status, setStatus] = useState({ kind: 'idle', html: '', text: '' }); + + // Successful submissions open the pre-filled issue in a new tab. + const externalRedirect = + onExternalRedirect || (url => window.open(url, '_blank', 'noopener,noreferrer')); + + async function handleSubmit(event) { + event.preventDefault(); + const form = event.target; + const owner = form.querySelector('#repo-owner').value; + const repo = form.querySelector('#repo-name').value; + + const validation = validateRepositoryInput(owner, repo); + if (!validation.valid) { + setStatus({ kind: 'error', text: validation.errors.join('. ') }); + return; + } + + setPending(true); + setStatus({ kind: 'pending', text: 'Validating repository...' }); + try { + const result = await submitRepository({ owner, repo }, { + environment, + baseUrl: platformBaseUrl, + target: submissionTarget + }); + if (result.success && result.issueUrl) { + // Popup blockers may swallow window.open after async work, so the + // status message always carries the clickable issue link. + setStatus({ + kind: 'success', + issueUrl: result.issueUrl, + text: result.message + }); + externalRedirect(result.issueUrl); + } else { + setStatus({ kind: 'error', text: result.message }); + } + } catch (error) { + logError(error, { route: 'add-repo' }); + setStatus({ kind: 'error', text: error.message }); + } finally { + setPending(false); + } + } + + const label = platformLabel(environment); + + return ( +
+

Add Your Repository

+

+ Only repositories with a .refactorfirst/refactor-first.json file + can be added. After the check, a pre-filled {label} issue opens in a new tab + — submit it there and your {label} account will be recorded as the + submitter. No login or tokens are needed on this site. +

+
+ + + + + +
+

+ {status.kind === 'success' ? ( + <> + {status.text}{' '} + + Continue on {label} + + + ) : ( + status.text + )} +

+
+ ); +} diff --git a/components/report-view.jsx b/components/report-view.jsx new file mode 100644 index 0000000..6139ddd --- /dev/null +++ b/components/report-view.jsx @@ -0,0 +1,119 @@ +'use client'; + +// Report page client component. Fetches the bundled Mustache template and +// the repository's refactor-first.json, renders into a ref'd container and +// enhances the result with the CDN widgets (Chart.js bubbles, vizdom WASM +// inline graphs, sigma/3D popups). Widget scripts are loaded via next/script +// and report readiness through lib/widget-loader.js. + +import { useEffect, useRef, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; +import Script from 'next/script'; +import { withBasePath } from '../lib/base-path'; +import { detectHostingEnvironment, getPlatformBaseUrl, readMetaTag } from '../lib/host'; +import { fetchReport } from '../lib/fetcher'; +import { renderTemplate } from '../lib/renderer'; +import { enhanceReport } from '../lib/report-view'; +import { renderErrorPage, logError } from '../lib/error-handler'; +import { markWidgetReady, waitForWidget } from '../lib/widget-loader'; + +const CLASSIC_WIDGETS = [ + { name: 'chart', src: 'https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js' }, + { name: 'svg-pan-zoom', src: 'https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js' }, + { name: 'sigma', src: 'https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js' }, + { name: 'graphology', src: 'https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js' }, + { name: 'graphlib-dot', src: 'https://cdn.jsdelivr.net/npm/graphlib-dot@0.6.4/dist/graphlib-dot.min.js' }, + { name: '3d-force-graph', src: 'https://cdn.jsdelivr.net/npm/3d-force-graph' } +]; + +// Module bridges (CSP 'script-src self' — vendored files) report readiness +// themselves via window.__rfMarkWidgetReady. +const BRIDGE_WIDGETS = ['/widgets/vizdom-bridge.js', '/widgets/three-spritetext-bridge.js']; + +// Widgets that enhanceReport consumes synchronously; all others degrade +// gracefully at popup-open time. +const ENHANCE_WIDGETS = ['chart', 'vizdom']; + +export default function ReportView({ + username, + repository, + branch: branchProp, + environment: environmentProp, + platformBaseUrl: platformBaseUrlProp, + widgetSettleMs = 5000 +}) { + const containerRef = useRef(null); + const searchParams = useSearchParams(); + const [attempt, setAttempt] = useState(0); + + const branch = branchProp || searchParams.get('branch') || undefined; + + useEffect(() => { + const controller = new AbortController(); + const container = containerRef.current; + + async function run() { + container.innerHTML = '

Loading report…

'; + try { + // The bundled template is authoritative — repository-provided + // templates are untrusted and intentionally never fetched. + const templateResponse = await fetch( + withBasePath('/assets/refactor-first-report.mustache'), + { signal: controller.signal } + ); + if (!templateResponse.ok) { + const error = new Error('Report template could not be loaded'); + error.status = 500; + throw error; + } + const template = await templateResponse.text(); + + const environment = environmentProp ?? detectHostingEnvironment(window.location.hostname); + const platformBaseUrl = platformBaseUrlProp ?? getPlatformBaseUrl(readMetaTag); + const { data, branch: resolvedBranch } = await fetchReport(username, repository, branch, { + environment, + baseUrl: platformBaseUrl, + signal: controller.signal + }); + + if (controller.signal.aborted) return; + + // Give the widgets that enhanceReport uses synchronously a moment to + // arrive; everything else degrades gracefully when missing. + await Promise.race([ + Promise.all(ENHANCE_WIDGETS.map(name => waitForWidget(name, { timeoutMs: widgetSettleMs }))), + new Promise(resolve => setTimeout(resolve, widgetSettleMs)) + ]); + if (controller.signal.aborted) return; + + container.innerHTML = renderTemplate(template, data); + container.dataset.resolvedBranch = resolvedBranch; + await enhanceReport(container, data); + } catch (error) { + if (controller.signal.aborted || error.name === 'AbortError') return; + logError(error, { route: 'report', username, repository, branch }); + renderErrorPage(container, error, { onRetry: () => setAttempt(a => a + 1) }); + } + } + + run(); + return () => controller.abort(); + }, [username, repository, branch, environmentProp, platformBaseUrlProp, widgetSettleMs, attempt]); + + return ( + <> +
+ {CLASSIC_WIDGETS.map(widget => ( +