From 5d74251032dbcd259fc85c67efea78daff6c0fcc Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Wed, 4 Nov 2026 22:14:35 +0800 Subject: [PATCH 1/3] Unify Wiki and Studio and remove deployment scaffolding --- .dockerignore | 24 - .gitea/workflows/brand.yml | 27 - .gitea/workflows/creative-workspace.yml | 80 - .gitea/workflows/python.yml | 23 - .gitea/workflows/waterfall-wiki.yml | 71 - .../workflows/wiki-image-retention-checks.yml | 19 - AGENTS.md | 15 +- Dockerfile | 41 - README.md | 108 +- apps/python/Dockerfile | 8 - apps/python/pyproject.toml | 12 - apps/python/src/prompt_tools/__init__.py | 0 apps/python/src/prompt_tools/export.py | 70 - apps/python/tests/test_export.py | 13 - apps/studio/Dockerfile | 23 - apps/studio/fixtures/autumn-journal.json | 163 - apps/studio/next-env.d.ts | 7 - apps/studio/next.config.ts | 4 - apps/studio/package.json | 29 - apps/studio/pnpm-lock.yaml | 1033 ---- apps/studio/postcss.config.mjs | 3 - apps/studio/scripts/graphic-smoke.cjs | 24 - apps/studio/scripts/infinite-canvas-smoke.cjs | 128 - apps/studio/scripts/smoke.mjs | 29 - apps/studio/src/lib/canvas-document.ts | 78 - apps/studio/src/lib/layout-parameters.ts | 32 - apps/studio/src/lib/series.ts | 42 - apps/studio/src/pages/_app.tsx | 8 - apps/studio/src/pages/_document.tsx | 3 - apps/studio/src/pages/index.tsx | 3 - apps/studio/src/styles/brand.css | 24 - apps/studio/tests/canvas-document.test.mjs | 65 - apps/studio/tests/graphic-document.test.mjs | 8 - apps/studio/tests/layout-images.test.mjs | 27 - apps/studio/tests/layout-parameters.test.mjs | 7 - apps/studio/tests/native-layout.test.mjs | 19 - apps/studio/tests/series.test.mjs | 12 - apps/studio/tsconfig.json | 21 - apps/waterfall-wiki/.env.example | 30 +- apps/waterfall-wiki/Dockerfile | 39 - .../fixtures/autumn-journal.json | 163 - apps/waterfall-wiki/next.config.ts | 9 +- apps/waterfall-wiki/package.json | 6 +- .../scripts/container-health.mjs | 9 - .../scripts/import-character-packs.mjs | 38 - .../scripts/prepare-standalone.mjs | 4 - .../scripts/publish-learning-series.ts | 5 - apps/waterfall-wiki/scripts/smoke.mjs | 65 - .../scripts/verify-character-pack.mjs | 6 - .../src/lib/studio-design-documentation.ts | 3 +- apps/waterfall-wiki/src/pages/_app.tsx | 6 +- .../src/pages/api/studio-ui}/export/[slug].ts | 12 +- .../src/pages/api/studio-ui}/health.ts | 2 +- .../src/pages/api/studio-ui}/index.ts | 2 +- .../src/pages/api/studio-ui}/layouts/[id].ts | 12 +- .../src/pages/api/studio-ui}/layouts/index.ts | 4 +- .../src/pages/graphic/index.tsx | 10 +- .../src/pages/infinite_canvas/index.tsx | 14 +- .../src/pages/studio}/designs/[id].tsx | 8 +- .../src/pages/studio}/ideas/[slug].tsx | 8 +- .../src/pages/studio}/layouts/[layoutId].tsx | 12 +- .../src/pages/studio}/template-layout.tsx | 14 +- .../src/studio}/components/Brand.tsx | 0 .../src/studio}/components/LayoutCanvas.tsx | 0 .../studio}/components/LayoutSelection.tsx | 0 .../studio}/components/LayoutWorkbench.tsx | 0 .../studio}/components/NativeCurvedCopy.tsx | 0 .../src/studio}/components/NativePreview.tsx | 0 .../src/studio}/components/NativeResult.tsx | 0 .../src/studio}/components/NativeSeries.tsx | 0 .../src/studio}/components/Ref17Cover.tsx | 0 .../src/studio}/components/SourceCover.tsx | 0 .../src/studio}/components/TemplateTrial.tsx | 0 .../src/studio}/components/ThemeToggle.tsx | 0 .../components/canvas/CanvasAgentPanel.tsx | 0 .../components/canvas/CanvasComposer.tsx | 0 .../components/canvas/CanvasNodeView.tsx | 0 .../src/studio}/content/ideas.ts | 0 .../src/studio}/content/standard-layout.json | 0 .../studio}/content/templates/layouts.json | 0 .../src/studio}/content/templates/ref-17.json | 0 .../studio}/content/templates/selection.json | 0 .../src/studio}/content/tool-links.ts | 0 .../src/studio}/lib/api-documentation.ts | 0 .../src/studio/lib/canvas-document.ts | 1 + .../src/studio}/lib/design-server.ts | 2 +- .../src/studio}/lib/graphic-document.ts | 0 .../src/studio}/lib/layout-images-client.ts | 0 .../src/studio}/lib/layout-images.ts | 4 +- .../src/studio/lib/layout-parameters.ts | 1 + .../src/studio}/lib/native-layout.ts | 0 .../src/studio}/lib/render-cover.ts | 0 apps/waterfall-wiki/src/studio/lib/series.ts | 1 + .../studio}/styles/canvas-agent.module.css | 0 .../studio}/styles/canvas-composer.module.css | 0 .../src/studio}/styles/canvas-node.module.css | 0 .../src/studio}/styles/globals.css | 11 +- .../src/studio}/styles/graphic.module.css | 0 .../studio}/styles/infinite-canvas.module.css | 0 .../tests/account-integration.ts | 51 - apps/waterfall-wiki/tests/account.test.ts | 7 - .../tests/api-documentation.test.ts | 64 - .../tests/api-publication.test.ts | 28 - apps/waterfall-wiki/tests/auth.test.ts | 62 - .../waterfall-wiki/tests/brand-navigation.mjs | 70 - .../tests/brand-navigation.test.mjs | 95 - .../tests/brand-ui-integration.ts | 75 - .../tests/canvas-integration.ts | 87 - .../tests/canvas-ui-integration.ts | 122 - .../tests/character-pack.test.mjs | 3 - apps/waterfall-wiki/tests/discovery.test.ts | 31 - apps/waterfall-wiki/tests/explore.test.ts | 17 - .../tests/flat-note-migration.ts | 38 - .../tests/guidance-integration.ts | 87 - .../tests/guidance-publishing.test.ts | 10 - .../tests/guidance-workspace-ui.ts | 380 -- .../tests/guidance-workspace.test.ts | 13 - apps/waterfall-wiki/tests/guidance.test.ts | 16 - apps/waterfall-wiki/tests/health.test.ts | 15 - apps/waterfall-wiki/tests/integration.test.ts | 311 -- .../tests/learning-series.test.ts | 16 - apps/waterfall-wiki/tests/model.test.ts | 8 - .../tests/note-markdown.test.tsx | 18 - apps/waterfall-wiki/tests/preview.test.ts | 8 - apps/waterfall-wiki/tests/skill-docs.test.ts | 59 - apps/waterfall-wiki/tests/wiki.test.ts | 5 - apps/waterfall-wiki/tsconfig.json | 24 +- compose.creative-review.yml | 20 - compose.yml | 127 - content/layout-reference/README.md | 19 - content/layout-reference/annotations.json | 2726 ---------- content/layout-reference/group-text.ts | 18 - content/layout-reference/manifest.json | 4792 ----------------- content/layout-reference/ocr-evidence.json | 493 -- content/layout-reference/post-ids.json | 30 - content/layout-reference/renderer.tsx | 24 - content/layout-reference/verify.mjs | 17 - content/studio-examples/autumn-journal.json | 163 - content/studio-tools/design-review.md | 11 - content/studio-tools/layouts-api.md | 216 - content/studio-tools/tutorial.md | 129 - docs/api.md | 16 - docs/ci-and-entrypoints.md | 21 - docs/data-model.md | 33 - docs/dynamic-code-login.md | 46 - docs/evidence/canvas-rebuild/desktop.png | Bin 142735 -> 0 bytes docs/evidence/canvas-rebuild/light.png | Bin 153414 -> 0 bytes docs/evidence/canvas-rebuild/mobile.png | Bin 45328 -> 0 bytes docs/explore-stage-files.md | 55 - docs/flat-notes-stage.md | 35 - docs/guidance.md | 19 - docs/learning-series.md | 34 - docs/linked-guidance/acceptance.md | 38 - docs/linked-guidance/design-system.md | 67 - docs/linked-guidance/product-plan.md | 89 - docs/linked-guidance/protocol.md | 56 - docs/linked-guidance/publishing.md | 16 - docs/monorepo.md | 38 - docs/note-editor-stage.md | 28 - docs/note-preview-stage.md | 21 - docs/plans/canvas-rebuild.md | 27 - docs/plans/infinite-canvas.md | 23 - docs/premium-layouts.md | 26 - docs/s3-media.md | 43 - docs/stage-files.md | 159 - docs/studio-graphic/plan.md | 31 - docs/wiki-image-retention.md | 61 - docs/wiki-saas/DESIGN.md | 46 - package.json | 7 +- packages/brand/Dockerfile | 12 - packages/brand/README.md | 41 - packages/brand/docs/colors.md | 71 - packages/brand/docs/integration.md | 63 - packages/brand/docs/logo-preview.png | Bin 87090 -> 0 bytes packages/brand/docs/logo.md | 35 - packages/brand/docs/overview.md | 22 - packages/brand/package.json | 7 - packages/brand/scripts/build.mjs | 8 - packages/brand/scripts/prepare-app.mjs | 4 +- packages/brand/tests/assets.py | 15 - packages/brand/tests/brand.test.mjs | 91 - packages/brand/tests/tailwind.mjs | 13 - packages/brand/tests/tokens.test.mts | 8 - packages/brand/tests/tsconfig.json | 12 - pnpm-workspace.yaml | 1 - scripts/auth-admin.sh | 15 - scripts/build-studio-layouts.mjs | 11 - scripts/check-studio-contracts.mjs | 9 - scripts/cleanup-integration.sh | 22 - scripts/cleanup-review-images.sh | 19 - scripts/deliver-studio-series.sh | 29 - scripts/deploy-python.sh | 7 - scripts/deploy-studio.sh | 24 - scripts/deploy.sh | 150 - scripts/export-studio-series.cjs | 37 - scripts/infra.sh | 17 - scripts/integration.sh | 58 - scripts/layout-reference-cover.mjs | 20 - scripts/layout-shadow-covers.mjs | 38 - scripts/managed-media.mjs | 27 - scripts/prepare-document-covers.mjs | 16 - scripts/publish-document-covers.mjs | 42 - scripts/publish-layout-reference.mjs | 68 - scripts/publish-layout-studio-tutorial.mjs | 14 - scripts/publish-shadow-covers.mjs | 46 - scripts/publish-studio-guide.sh | 14 - scripts/publish-studio-library.mjs | 41 - scripts/publish-studio-tools.mjs | 18 - scripts/recognize-layout-text.swift | 22 - scripts/save-studio-series.mjs | 17 - scripts/segment-layout-reference.ts | 129 - scripts/test-integration-cleanup.py | 80 - scripts/test-review-image-cleanup.py | 73 - scripts/verify-studio-library.mjs | 54 - scripts/verify-studio-production.sh | 52 - scripts/verify-studio-visual.cjs | 107 - scripts/wiki-image-lifecycle.py | 210 - tests/managed-media.test.mjs | 39 - tests/test_wiki_image_lifecycle.py | 168 - 219 files changed, 127 insertions(+), 16313 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .gitea/workflows/brand.yml delete mode 100644 .gitea/workflows/creative-workspace.yml delete mode 100644 .gitea/workflows/python.yml delete mode 100644 .gitea/workflows/waterfall-wiki.yml delete mode 100644 .gitea/workflows/wiki-image-retention-checks.yml delete mode 100644 Dockerfile delete mode 100644 apps/python/Dockerfile delete mode 100644 apps/python/pyproject.toml delete mode 100644 apps/python/src/prompt_tools/__init__.py delete mode 100644 apps/python/src/prompt_tools/export.py delete mode 100644 apps/python/tests/test_export.py delete mode 100644 apps/studio/Dockerfile delete mode 100644 apps/studio/fixtures/autumn-journal.json delete mode 100644 apps/studio/next-env.d.ts delete mode 100644 apps/studio/next.config.ts delete mode 100644 apps/studio/package.json delete mode 100644 apps/studio/pnpm-lock.yaml delete mode 100644 apps/studio/postcss.config.mjs delete mode 100644 apps/studio/scripts/graphic-smoke.cjs delete mode 100644 apps/studio/scripts/infinite-canvas-smoke.cjs delete mode 100644 apps/studio/scripts/smoke.mjs delete mode 100644 apps/studio/src/lib/canvas-document.ts delete mode 100644 apps/studio/src/lib/layout-parameters.ts delete mode 100644 apps/studio/src/lib/series.ts delete mode 100644 apps/studio/src/pages/_app.tsx delete mode 100644 apps/studio/src/pages/_document.tsx delete mode 100644 apps/studio/src/pages/index.tsx delete mode 100644 apps/studio/src/styles/brand.css delete mode 100644 apps/studio/tests/canvas-document.test.mjs delete mode 100644 apps/studio/tests/graphic-document.test.mjs delete mode 100644 apps/studio/tests/layout-images.test.mjs delete mode 100644 apps/studio/tests/layout-parameters.test.mjs delete mode 100644 apps/studio/tests/native-layout.test.mjs delete mode 100644 apps/studio/tests/series.test.mjs delete mode 100644 apps/studio/tsconfig.json delete mode 100644 apps/waterfall-wiki/Dockerfile delete mode 100644 apps/waterfall-wiki/fixtures/autumn-journal.json delete mode 100644 apps/waterfall-wiki/scripts/container-health.mjs delete mode 100644 apps/waterfall-wiki/scripts/import-character-packs.mjs delete mode 100644 apps/waterfall-wiki/scripts/prepare-standalone.mjs delete mode 100644 apps/waterfall-wiki/scripts/publish-learning-series.ts delete mode 100644 apps/waterfall-wiki/scripts/smoke.mjs delete mode 100644 apps/waterfall-wiki/scripts/verify-character-pack.mjs rename apps/{studio/src/pages/api => waterfall-wiki/src/pages/api/studio-ui}/export/[slug].ts (82%) rename apps/{studio/src/pages/api => waterfall-wiki/src/pages/api/studio-ui}/health.ts (84%) rename apps/{studio/src/pages/api => waterfall-wiki/src/pages/api/studio-ui}/index.ts (92%) rename apps/{studio/src/pages/api => waterfall-wiki/src/pages/api/studio-ui}/layouts/[id].ts (92%) rename apps/{studio/src/pages/api => waterfall-wiki/src/pages/api/studio-ui}/layouts/index.ts (89%) rename apps/{studio => waterfall-wiki}/src/pages/graphic/index.tsx (98%) rename apps/{studio => waterfall-wiki}/src/pages/infinite_canvas/index.tsx (97%) rename apps/{studio/src/pages => waterfall-wiki/src/pages/studio}/designs/[id].tsx (63%) rename apps/{studio/src/pages => waterfall-wiki/src/pages/studio}/ideas/[slug].tsx (82%) rename apps/{studio/src/pages => waterfall-wiki/src/pages/studio}/layouts/[layoutId].tsx (64%) rename apps/{studio/src/pages => waterfall-wiki/src/pages/studio}/template-layout.tsx (65%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/Brand.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/LayoutCanvas.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/LayoutSelection.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/LayoutWorkbench.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/NativeCurvedCopy.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/NativePreview.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/NativeResult.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/NativeSeries.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/Ref17Cover.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/SourceCover.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/TemplateTrial.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/ThemeToggle.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/canvas/CanvasAgentPanel.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/canvas/CanvasComposer.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/components/canvas/CanvasNodeView.tsx (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/content/ideas.ts (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/content/standard-layout.json (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/content/templates/layouts.json (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/content/templates/ref-17.json (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/content/templates/selection.json (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/content/tool-links.ts (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/lib/api-documentation.ts (100%) create mode 100644 apps/waterfall-wiki/src/studio/lib/canvas-document.ts rename apps/{studio/src => waterfall-wiki/src/studio}/lib/design-server.ts (93%) rename apps/{studio/src => waterfall-wiki/src/studio}/lib/graphic-document.ts (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/lib/layout-images-client.ts (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/lib/layout-images.ts (65%) create mode 100644 apps/waterfall-wiki/src/studio/lib/layout-parameters.ts rename apps/{studio/src => waterfall-wiki/src/studio}/lib/native-layout.ts (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/lib/render-cover.ts (100%) create mode 100644 apps/waterfall-wiki/src/studio/lib/series.ts rename apps/{studio/src => waterfall-wiki/src/studio}/styles/canvas-agent.module.css (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/styles/canvas-composer.module.css (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/styles/canvas-node.module.css (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/styles/globals.css (92%) rename apps/{studio/src => waterfall-wiki/src/studio}/styles/graphic.module.css (100%) rename apps/{studio/src => waterfall-wiki/src/studio}/styles/infinite-canvas.module.css (100%) delete mode 100644 apps/waterfall-wiki/tests/account-integration.ts delete mode 100644 apps/waterfall-wiki/tests/account.test.ts delete mode 100644 apps/waterfall-wiki/tests/api-documentation.test.ts delete mode 100644 apps/waterfall-wiki/tests/api-publication.test.ts delete mode 100644 apps/waterfall-wiki/tests/auth.test.ts delete mode 100644 apps/waterfall-wiki/tests/brand-navigation.mjs delete mode 100644 apps/waterfall-wiki/tests/brand-navigation.test.mjs delete mode 100644 apps/waterfall-wiki/tests/brand-ui-integration.ts delete mode 100644 apps/waterfall-wiki/tests/canvas-integration.ts delete mode 100644 apps/waterfall-wiki/tests/canvas-ui-integration.ts delete mode 100644 apps/waterfall-wiki/tests/character-pack.test.mjs delete mode 100644 apps/waterfall-wiki/tests/discovery.test.ts delete mode 100644 apps/waterfall-wiki/tests/explore.test.ts delete mode 100644 apps/waterfall-wiki/tests/flat-note-migration.ts delete mode 100644 apps/waterfall-wiki/tests/guidance-integration.ts delete mode 100644 apps/waterfall-wiki/tests/guidance-publishing.test.ts delete mode 100644 apps/waterfall-wiki/tests/guidance-workspace-ui.ts delete mode 100644 apps/waterfall-wiki/tests/guidance-workspace.test.ts delete mode 100644 apps/waterfall-wiki/tests/guidance.test.ts delete mode 100644 apps/waterfall-wiki/tests/health.test.ts delete mode 100644 apps/waterfall-wiki/tests/integration.test.ts delete mode 100644 apps/waterfall-wiki/tests/learning-series.test.ts delete mode 100644 apps/waterfall-wiki/tests/model.test.ts delete mode 100644 apps/waterfall-wiki/tests/note-markdown.test.tsx delete mode 100644 apps/waterfall-wiki/tests/preview.test.ts delete mode 100644 apps/waterfall-wiki/tests/skill-docs.test.ts delete mode 100644 apps/waterfall-wiki/tests/wiki.test.ts delete mode 100644 compose.creative-review.yml delete mode 100644 compose.yml delete mode 100644 content/layout-reference/README.md delete mode 100644 content/layout-reference/annotations.json delete mode 100644 content/layout-reference/group-text.ts delete mode 100644 content/layout-reference/manifest.json delete mode 100644 content/layout-reference/ocr-evidence.json delete mode 100644 content/layout-reference/post-ids.json delete mode 100644 content/layout-reference/renderer.tsx delete mode 100644 content/layout-reference/verify.mjs delete mode 100644 content/studio-examples/autumn-journal.json delete mode 100644 content/studio-tools/design-review.md delete mode 100644 content/studio-tools/layouts-api.md delete mode 100644 content/studio-tools/tutorial.md delete mode 100644 docs/api.md delete mode 100644 docs/ci-and-entrypoints.md delete mode 100644 docs/data-model.md delete mode 100644 docs/dynamic-code-login.md delete mode 100644 docs/evidence/canvas-rebuild/desktop.png delete mode 100644 docs/evidence/canvas-rebuild/light.png delete mode 100644 docs/evidence/canvas-rebuild/mobile.png delete mode 100644 docs/explore-stage-files.md delete mode 100644 docs/flat-notes-stage.md delete mode 100644 docs/guidance.md delete mode 100644 docs/learning-series.md delete mode 100644 docs/linked-guidance/acceptance.md delete mode 100644 docs/linked-guidance/design-system.md delete mode 100644 docs/linked-guidance/product-plan.md delete mode 100644 docs/linked-guidance/protocol.md delete mode 100644 docs/linked-guidance/publishing.md delete mode 100644 docs/monorepo.md delete mode 100644 docs/note-editor-stage.md delete mode 100644 docs/note-preview-stage.md delete mode 100644 docs/plans/canvas-rebuild.md delete mode 100644 docs/plans/infinite-canvas.md delete mode 100644 docs/premium-layouts.md delete mode 100644 docs/s3-media.md delete mode 100644 docs/stage-files.md delete mode 100644 docs/studio-graphic/plan.md delete mode 100644 docs/wiki-image-retention.md delete mode 100644 docs/wiki-saas/DESIGN.md delete mode 100644 packages/brand/Dockerfile delete mode 100644 packages/brand/README.md delete mode 100644 packages/brand/docs/colors.md delete mode 100644 packages/brand/docs/integration.md delete mode 100644 packages/brand/docs/logo-preview.png delete mode 100644 packages/brand/docs/logo.md delete mode 100644 packages/brand/docs/overview.md delete mode 100644 packages/brand/scripts/build.mjs delete mode 100644 packages/brand/tests/assets.py delete mode 100644 packages/brand/tests/brand.test.mjs delete mode 100644 packages/brand/tests/tailwind.mjs delete mode 100644 packages/brand/tests/tokens.test.mts delete mode 100644 packages/brand/tests/tsconfig.json delete mode 100644 scripts/auth-admin.sh delete mode 100644 scripts/build-studio-layouts.mjs delete mode 100644 scripts/check-studio-contracts.mjs delete mode 100644 scripts/cleanup-integration.sh delete mode 100644 scripts/cleanup-review-images.sh delete mode 100644 scripts/deliver-studio-series.sh delete mode 100644 scripts/deploy-python.sh delete mode 100644 scripts/deploy-studio.sh delete mode 100644 scripts/deploy.sh delete mode 100644 scripts/export-studio-series.cjs delete mode 100644 scripts/infra.sh delete mode 100644 scripts/integration.sh delete mode 100644 scripts/layout-reference-cover.mjs delete mode 100644 scripts/layout-shadow-covers.mjs delete mode 100644 scripts/managed-media.mjs delete mode 100644 scripts/prepare-document-covers.mjs delete mode 100644 scripts/publish-document-covers.mjs delete mode 100644 scripts/publish-layout-reference.mjs delete mode 100644 scripts/publish-layout-studio-tutorial.mjs delete mode 100644 scripts/publish-shadow-covers.mjs delete mode 100644 scripts/publish-studio-guide.sh delete mode 100644 scripts/publish-studio-library.mjs delete mode 100644 scripts/publish-studio-tools.mjs delete mode 100644 scripts/recognize-layout-text.swift delete mode 100644 scripts/save-studio-series.mjs delete mode 100644 scripts/segment-layout-reference.ts delete mode 100644 scripts/test-integration-cleanup.py delete mode 100644 scripts/test-review-image-cleanup.py delete mode 100644 scripts/verify-studio-library.mjs delete mode 100644 scripts/verify-studio-production.sh delete mode 100644 scripts/verify-studio-visual.cjs delete mode 100644 scripts/wiki-image-lifecycle.py delete mode 100644 tests/managed-media.test.mjs delete mode 100644 tests/test_wiki_image_lifecycle.py diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 4401a39..0000000 --- a/.dockerignore +++ /dev/null @@ -1,24 +0,0 @@ -.git -**/.next -**/node_modules -**/dist -coverage -.env -.env.* -!.env.example -*.tsbuildinfo -.DS_Store -.agents -.codex - -# Documentation and CI metadata are not application build inputs. -/docs -/.gitea -/README.md -/AGENTS.md - -**/__pycache__ -**/.venv - -# Local authoring assets are uploaded via APIs, never application build inputs. -/content diff --git a/.gitea/workflows/brand.yml b/.gitea/workflows/brand.yml deleted file mode 100644 index da4a5aa..0000000 --- a/.gitea/workflows/brand.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Endoleap brand checks -on: - pull_request: - branches: [main] - paths: - - 'packages/brand/**' - - 'pnpm-workspace.yaml' - - 'package.json' - - 'AGENTS.md' - - '.gitea/workflows/brand.yml' - push: - branches: [main] - paths: - - 'packages/brand/**' - - 'pnpm-workspace.yaml' - - '.gitea/workflows/brand.yml' -concurrency: - group: brand-${{ gitea.event.pull_request.number || gitea.ref }} - cancel-in-progress: true -jobs: - verify: - runs-on: ces-control - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - name: Verify brand assets, Tailwind compilation and typed exports in Docker - run: docker build -f packages/brand/Dockerfile -t "endoleap-brand-check:${{ gitea.sha }}" . diff --git a/.gitea/workflows/creative-workspace.yml b/.gitea/workflows/creative-workspace.yml deleted file mode 100644 index ee90d9a..0000000 --- a/.gitea/workflows/creative-workspace.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: creative workspace review checks -on: - pull_request: - branches: [main] - paths: - - 'apps/studio/**' - - 'apps/waterfall-wiki/**' - - '.gitea/workflows/**' - - 'scripts/*studio*' - - 'compose.creative-review.yml' - - 'scripts/integration.sh' - - 'scripts/cleanup-integration.sh' - - 'scripts/test-integration-cleanup.py' - - 'scripts/cleanup-review-images.sh' - - 'scripts/test-review-image-cleanup.py' -concurrency: - group: creative-workspace-${{ gitea.event.pull_request.number }} - cancel-in-progress: true -jobs: - verify-deployed-canvas: - runs-on: ces-control - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - name: Verify production acceptance against the designated deployed server - run: | - node --check scripts/verify-studio-visual.cjs - node --check apps/studio/scripts/infinite-canvas-smoke.cjs - bash -n scripts/verify-studio-production.sh - bash scripts/verify-studio-production.sh - - name: Save server acceptance evidence - id: acceptance_evidence - if: always() - uses: actions/upload-artifact@v3 - with: - name: studio-production-acceptance - path: artifacts/studio-acceptance/ - if-no-files-found: ignore - retention-days: 7 - - name: Remove temporary acceptance output after upload - if: always() && steps.acceptance_evidence.outcome == 'success' - run: rm -rf artifacts/studio-acceptance - verify: - runs-on: ces-control - timeout-minutes: 60 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Check patch formatting - run: | - git diff --check HEAD^ HEAD - bash -n scripts/integration.sh scripts/cleanup-integration.sh scripts/verify-studio-production.sh scripts/cleanup-review-images.sh - node --check scripts/verify-studio-visual.cjs - python3 scripts/test-integration-cleanup.py - python3 scripts/test-review-image-cleanup.py - node --test apps/waterfall-wiki/tests/brand-navigation.test.mjs - - name: Verify Studio types, tests, build and smoke - run: docker build -f apps/studio/Dockerfile -t "creative-studio-review:${{ gitea.sha }}" . - - name: Build Wiki integration and runtime images - run: | - docker build --target integration -t "creative-wiki-tests:${{ gitea.sha }}" . - docker build --target runtime -t "creative-wiki-review:${{ gitea.sha }}" . - - name: Verify isolated PostgreSQL, S3 and browser integration - env: - STUDIO_REVIEW_IMAGE: creative-studio-review:${{ gitea.sha }} - WATERFALL_WIKI_IMAGE: creative-wiki-review:${{ gitea.sha }} - WATERFALL_WIKI_TEST_IMAGE: creative-wiki-tests:${{ gitea.sha }} - GITEA_RUN_ID: ${{ gitea.run_id }} - run: bash scripts/integration.sh - - name: Clean this run disposable integration resources even after cancellation - if: always() - env: - GITEA_RUN_ID: ${{ gitea.run_id }} - run: bash scripts/cleanup-integration.sh - - name: Remove this run disposable review image tags - if: always() - env: - REVIEW_SHA: ${{ gitea.sha }} - run: bash scripts/cleanup-review-images.sh diff --git a/.gitea/workflows/python.yml b/.gitea/workflows/python.yml deleted file mode 100644 index bb5b5d6..0000000 --- a/.gitea/workflows/python.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: python CI and deployment -on: - push: - branches: [main] - paths: - - 'apps/python/**' - - 'scripts/deploy-python.sh' - - '.gitea/workflows/python.yml' - -jobs: - verify-and-deploy: - if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push' - runs-on: ces-control - timeout-minutes: 45 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Build and verify python - run: docker build -f apps/python/Dockerfile -t "prompt-python:${{ gitea.sha }}" . - - name: Release python - if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push' - run: bash scripts/deploy-python.sh "${{ gitea.sha }}" diff --git a/.gitea/workflows/waterfall-wiki.yml b/.gitea/workflows/waterfall-wiki.yml deleted file mode 100644 index dba9b29..0000000 --- a/.gitea/workflows/waterfall-wiki.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: creative workspace CI and deployment -on: - push: - branches: [main] - paths: - - 'apps/waterfall-wiki/**' - - 'apps/studio/**' - - 'scripts/*studio*' - - 'content/studio-examples/**' - - 'content/studio-tools/layouts-api.md' - - '.gitea/workflows/waterfall-wiki.yml' - - 'Dockerfile' - - 'compose.yml' - - 'scripts/deploy.sh' - - 'scripts/wiki-image-lifecycle.py' - - 'scripts/infra.sh' - - 'scripts/auth-admin.sh' - - 'scripts/integration.sh' - - 'scripts/cleanup-integration.sh' - - 'scripts/test-integration-cleanup.py' - - 'pnpm-workspace.yaml' - -jobs: - verify-and-deploy: - if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push' - runs-on: ces-control - timeout-minutes: 60 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Check shared content and documentation contracts - run: | - python3 scripts/test-integration-cleanup.py - bash -n scripts/integration.sh scripts/cleanup-integration.sh - node scripts/check-studio-contracts.mjs - node --check scripts/export-studio-series.cjs - node --check scripts/save-studio-series.mjs - bash -n scripts/publish-studio-guide.sh scripts/deliver-studio-series.sh - - name: Build and verify Studio - run: docker build -f apps/studio/Dockerfile -t "prompt-studio:${{ gitea.sha }}" . - - name: Build, verify, deploy and retain wiki images - env: - RELEASE_SHA: ${{ gitea.sha }} - run: | - if [ "$(id -u)" -eq 0 ]; then - python3 scripts/wiki-image-lifecycle.py ci --sha "$RELEASE_SHA" - else - sudo -n python3 scripts/wiki-image-lifecycle.py ci --sha "$RELEASE_SHA" - fi - - name: Release Studio after Wiki routes are deployed - run: bash scripts/deploy-studio.sh "${{ gitea.sha }}" - - name: Verify the deployed creative workspace - run: bash scripts/verify-studio-production.sh - - name: Save server acceptance evidence - id: acceptance_evidence - if: always() - uses: actions/upload-artifact@v3 - with: - name: studio-production-acceptance - path: artifacts/studio-acceptance/ - if-no-files-found: ignore - retention-days: 7 - - name: Remove temporary acceptance output after upload - if: always() && steps.acceptance_evidence.outcome == 'success' - run: rm -rf artifacts/studio-acceptance - - name: Clean this run disposable integration resources even after cancellation - if: always() - env: - GITEA_RUN_ID: ${{ gitea.run_id }} - run: bash scripts/cleanup-integration.sh diff --git a/.gitea/workflows/wiki-image-retention-checks.yml b/.gitea/workflows/wiki-image-retention-checks.yml deleted file mode 100644 index 88cc317..0000000 --- a/.gitea/workflows/wiki-image-retention-checks.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Wiki image retention checks -on: - push: - branches: [main] - paths: - - 'scripts/wiki-image-lifecycle.py' - - 'tests/test_wiki_image_lifecycle.py' - - '.gitea/workflows/wiki-image-retention-checks.yml' -jobs: - retention-policy: - if: gitea.ref == 'refs/heads/main' && gitea.event_name == 'push' - runs-on: ces-control - timeout-minutes: 5 - steps: - - uses: actions/checkout@v4 - - name: Check retention without building or accessing the Docker daemon - run: | - bash -n scripts/deploy.sh - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_wiki_image_lifecycle.py' -v diff --git a/AGENTS.md b/AGENTS.md index 26c295d..8bb360c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,16 +8,7 @@ This block is written and re-added by `next dev` — verify at `node_modules/nex -## 交付与视觉验收 +## 构建与维护 -- 用户指定服务器为唯一运行与验收入口:http://8.211.154.134:18131/。 -- 页面改动必须先 push,等待 ces-control 的 CI/CD 成功部署,再直接访问服务器检查效果。 -- 不启动本地网页预览,不使用 localhost / 127.0.0.1 页面进行视觉验收,也不把本地预览链接交付给用户。 -- 类型检查、单元测试等不提供网页预览的检查可以本地执行;完整运行检查由 Docker CI 完成。 - -## 啡跃 Endoleap 品牌与主题 - -- 共享品牌包位于 [`packages/brand`](packages/brand/README.md)。修改品牌色、主题或标志前,阅读包内的 [品牌边界](packages/brand/docs/overview.md)、[配色与交互规范](packages/brand/docs/colors.md)、[Tailwind / shadcn 接入](packages/brand/docs/integration.md) 和 [Logo 规则](packages/brand/docs/logo.md)。不在仓库根 docs 重复维护一套品牌规范。 -- 四套正式主题为 home-light、home-dark、canvas-light、canvas-dark;不创建 drafts,不恢复 proposed 命名。画布 Light / Dark 背景保持中性近白 / 近黑,用户素材与作品不整体染色。 -- 基础色只在 tokens/palette.json 定义,主题引用色板键;Logo 轮廓只在 tokens/geometry.json 定义。保持 CSS、类型声明、资产与清单一致,不通过图片模型重画已固定的图形。 -- 本地仅运行不生成产物的轻量读取检查。品牌导出、Tailwind/TypeScript 编译和 SVG 栅格化检查必须通过已确认的 ces-control Docker CI 执行,不在本机启动预览或构建。 +- 构建和网页运行使用用户确认的远程环境。本地可执行差异审查和无需构建的轻量检查。 +- Wiki 和 Studio 共用 `apps/waterfall-wiki` 的 Pages Router、登录保护和品牌包。保留已发布的路由、媒体标识和数据库迁移。 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 245d4e4..0000000 --- a/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM node:24-bookworm-slim AS base -LABEL io.entroia.project="waterfall-wiki" -ENV NEXT_TELEMETRY_DISABLED=1 -WORKDIR /app -RUN apt-get update \ - && apt-get install -y --no-install-recommends ffmpeg ca-certificates chromium fonts-noto-cjk \ - && rm -rf /var/lib/apt/lists/* - -FROM base AS dependencies -WORKDIR /repo/apps/waterfall-wiki -LABEL io.entroia.wiki.build="true" -RUN corepack enable && corepack prepare pnpm@11.25.0 --activate -COPY apps/waterfall-wiki/pnpm-lock.yaml ./ -COPY pnpm-workspace.yaml /repo/pnpm-workspace.yaml -COPY packages/brand/ /repo/packages/brand/ -COPY apps/waterfall-wiki/package.json ./ -RUN pnpm --filter @prompt/waterfall-wiki install --frozen-lockfile --fetch-timeout=300000 --network-concurrency=8 - -FROM dependencies AS build -# .dockerignore excludes docs and operational notes without extra legacy COPY snapshots. -COPY apps/waterfall-wiki/ . -RUN pnpm typecheck && pnpm test && pnpm build && pnpm test:smoke - -FROM build AS integration -USER node -CMD ["npm", "run", "test:integration"] - -FROM base AS runtime -WORKDIR /app/apps/waterfall-wiki -ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000 \ - FFMPEG_PATH=/usr/bin/ffmpeg FFPROBE_PATH=/usr/bin/ffprobe -COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/.next/standalone/ /app/ -COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/dist/worker.cjs /repo/apps/waterfall-wiki/dist/bootstrap.cjs /repo/apps/waterfall-wiki/dist/publish-learning-series.cjs /repo/apps/waterfall-wiki/dist/auth-cli.cjs ./ -COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/db/ ./db/ -COPY --chown=node:node apps/waterfall-wiki/scripts/container-health.mjs ./ -COPY --chown=node:node apps/waterfall-wiki/scripts/import-character-packs.mjs apps/waterfall-wiki/scripts/verify-character-pack.mjs ./scripts/ -COPY --chown=node:node apps/waterfall-wiki/seed/characters/ ./seed/characters/ -COPY --chown=node:node apps/waterfall-wiki/src/content/managed-media.json ./src/content/managed-media.json -USER node -EXPOSE 3000 -CMD ["node", "server.js"] diff --git a/README.md b/README.md index 1b6a690..013ef80 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,26 @@ -# PROMPT +# 啡跃 Endoleap -把提示词、参考图、分步文档和创作工具收进同一个可连接的知识库。人可以浏览瀑布流、搜索和编辑;AI 可以从同一篇 post 读取纯文本、图片与 API 说明,再调用 Studio 生成作品。 +啡跃把笔记、参考素材和创作工作台放在同一个 Next.js 应用里。应用位于 `apps/waterfall-wiki`,共享主题与品牌资源位于 `packages/brand`。 -**PROMPT 是网站与项目品牌;`waterfall-wiki` 是知识库应用的内部名称。** 目录、包、CI、镜像和 Compose 服务使用内部名称,网站导航与页面标题使用 PROMPT。 - -线上入口:[PROMPT](http://8.211.154.134:18131/)。内容、媒体、API 与 Studio 共用登录入口。 - -## 从一篇 post 开始 - -每篇内容都有稳定的 `/explore/` 地址。标题、标签、正文和封面可以修改,链接保持不变。数据库内容可以保留 24 位 public ID,内部仍使用 UUID;旧 `/p/`、`/skills/...` 等地址保留兼容跳转。 - -- **浏览**:首页按不同笔记的反向引用数优先排序。重复引用和自引用不重复计数,代码、图片与外站链接不冒充笔记引用。详情可以查看引用来源。 -- **搜索**:标题、简介、正文、普通标签和系统标签共同匹配。支持多个关键词及双引号短语,先按相关性、再按引用数和时间排序;先筛选再分页。 -- **标签**:普通标签使用 `#中国风`、`#贴纸` 等名称,可以创建、添加或移除。系统标签是固定列表,目前为“文档入口”和“参考图”,只能搜索并勾选,不能通过 `#` 创建。系统标签显示在图片角标,普通标签显示在文字区;分类页顶部按系统标签筛选。 -- **编辑**:卡片与详情都能直接编辑标签;完整编辑器可以修改标题、简介、Markdown。保存使用 revision 检测并发冲突,避免旧窗口覆盖新修改。 -- **素材**:原件和加工结果都是普通资产,可被多篇 post 引用。文档封面与实际参考素材分开,子文档可以有自己的封面,同时保留原参考图。 - -## 给 AI 的入口 - -在同一个 post 地址加 `?agent_mode=True` 可以读取纯文本正文和媒体链接。业务 API 的相同参数会先返回接口说明;已有查询参数时使用 `&agent_mode=True`。这些入口同样需要登录或有效 Bearer JWT。 - -| 地址 | 用途 | +| 入口 | 用途 | | --- | --- | -| `/explore/` | 人看的 post;加 `?agent_mode=True` 读纯文本 | +| `/` | 浏览内容,打开 `/explore/` 查看笔记 | +| `/guide` | 浏览指导文档 | +| `/graphic` | 图文排版 | +| `/infinite_canvas` | 无限画布 | | `/api?agent_mode=True` | 业务 API 目录 | -| `/api/explore-feed` | 统一搜索、系统/普通标签筛选与分页 | -| `/api/notes/` | GET 读取;PUT 编辑全文;PATCH 仅更新标签 | -| `/api/tags` | GET 查看普通/系统标签;POST 创建或复用普通标签 | -| `/api/upload` | 上传媒体;`assetOnly=true` 独立保存,不创建 post | -| `/api/posts` | 用正文和已有资产创建普通 post | -| `/api/media/` | 媒体原件与衍生文件,支持 Range | -| `/api/preview` | 有尺寸上限的 WebP 预览,不接受任意远程 URL | +| `/studio/api` | Studio API 目录 | -标签 PATCH 请求为 `{ "tags": ["标签 UUID"], "revision": "GET 返回的 revision" }`。它不会要求用户重输正文。Tag 对象的 `kind` 为 `normal` 或 `system`;系统标签还有稳定的 `system_key`。普通创建接口不允许提交 `kind` 或 `system_key`。 +## 运行 -Wiki 中的教程是普通 post 与媒体数据,不需要为每份资料增加专用网页路由、JSON 文件端点或代码注册。 +在已确认的远程环境准备 Node.js 24、pnpm 11.25.0、PostgreSQL、S3 兼容对象存储和 FFmpeg。参考 [环境变量示例](apps/waterfall-wiki/.env.example),将变量注入 Web、初始化和媒体 worker 进程。邮箱注册与找回密码需要可用的 SMTP 服务。 -## Studio:从教程进入工具 - -Studio 负责交互式排版与无状态渲染,知识库负责教程、来源和入口。`/studio` 返回 PROMPT 首页;请从对应 post 打开具体工具。 - -27 套排版工具:`/studio/layouts/ref-01` 至 `/studio/layouts/ref-27`,每套对应原图 T/I/D 构件。目录与参数 API 为 `/studio/api/layouts` 和 `/studio/api/layouts/`;POST 参数生成 SVG。效果通过 `/api/studio/designs` 存入 Wiki 现有 PostgreSQL,返回独立 ID,`/studio/designs/` 可再次打开;PATCH 携带 revision 更新同一效果。Studio 不需要数据库连接或第二套数据库。详见 [27 套排版 API 教程](content/studio-tools/layouts-api.md)。 - -其他试版工具: - -- `/studio/ideas/learning-skills-template`:ref-17 同一母版的四张占位封面。 -- `/studio/ideas/layout-selection`:ref-01、ref-07、ref-26 三种构图;可修改文字与配色,核对构件边界。 -- `/studio/api/export/`:GET 读取方案或初始 SVG;POST 校验自定义参数并生成 SVG/JSON。详细说明读取该地址的 `?agent_mode=True`。 - -渲染不自动保存作品,也不自动发布 post。需要保留的结果应下载后再通过普通媒体和 post API 发布。原图的 T/I/D 构件、画板比例与来源保留为可读取数据;文字替换和矢量重建的范围应在教程中明确。 - -## 仓库结构 - -| 路径 | 职责 | -| --- | --- | -| `apps/waterfall-wiki` | Next.js Pages Router 知识库、搜索、标签、媒体 API、认证与后台视频加工 | -| `apps/studio` | 独立 Next.js 创作工具与封面渲染 API | -| `apps/python` | 按需运行的 PNG/视频导出 CLI,没有常驻网络服务 | -| `content` | 本地作者资料、教程及排版源图,不打入 Wiki 运行镜像 | -| `scripts` | CI 部署、运维、API 发布与作者工具 | -| `docs` | 数据模型、接口、认证与部署说明 | - -两个 JavaScript 应用分别维护冻结的 pnpm 锁文件。根 Dockerfile 是 waterfall-wiki 的构建入口;应用目录的 Dockerfile 与它保持一致。 - -## 环境与检查 - -运行环境:Node.js 24、pnpm 11.25.0、PostgreSQL 17、S3 兼容对象存储;视频处理使用 FFmpeg,部分导出使用 Chromium。Python 工具需要 Python 3.12+。 +在仓库根目录运行 `pnpm install --frozen-lockfile` 和 `pnpm build`。然后在 `apps/waterfall-wiki` 目录准备数据库和存储,初始化管理员认证并启动 Web。 ```sh -pnpm install -pnpm --dir apps/waterfall-wiki typecheck -pnpm --dir apps/waterfall-wiki test -pnpm --dir apps/studio typecheck -bash -n scripts/deploy.sh scripts/integration.sh scripts/deploy-studio.sh +node dist/bootstrap.cjs +node dist/auth-cli.cjs init +pnpm start ``` -完整集成测试只针对显式隔离的数据库、对象存储和生产容器运行,覆盖存储、视频加工、编辑冲突、真实搜索与引用、标签规则和登录保护。不要把生产存储配置拿来运行集成测试。 - -本项目以指定服务器为唯一网页运行与验收入口:先推送,再等待 ces-control CI/CD,最后直接检查服务器。不要启动本地网页预览,不用 localhost 页面作为视觉验收或交付入口。具体约定见 [AGENTS.md](AGENTS.md)。 - -## 部署与持久化 - -Web 应用由 `waterfall-wiki.yml` 统一发布,两个应用的路径都能触发它:先检查并构建 Studio,再完成 Wiki 的类型检查、单元测试、生产构建、冒烟及隔离 PostgreSQL/S3 集成测试并部署 Wiki,随后部署 Studio,最后在指定服务器验收。顺序在同一个 ces-control 作业内保证,避免独立作业互相等待。PR 检查也运行隔离集成测试,但不部署。Python 检查仍由 `python.yml` 承担;文档与作者数据本身不触发应用部署。 - -| 服务 | 入口/端口 | -| --- | --- | -| `waterfall-wiki` | 公网 `18131`,也是 Studio 的统一入口 | -| PostgreSQL | `127.0.0.1:18132` | -| 对象存储 | `127.0.0.1:18133` | -| Studio | `127.0.0.1:18134`;编辑器公网入口为 `/graphic`、`/infinite_canvas`,资源和 API 保留 `/studio` 代理 | -| Python | 无网络端口,按需运行 | - -生产 Compose 项目仍为 `prompt`,部署根目录仍为 `/opt/prompt`。既有 `prompt_postgres_data`、`prompt_objects`、`prompt_auth_data` 卷与数据库身份保留;应用更名不会重新创建一套空存储。Compose 应用服务改为 `waterfall-wiki` 和 `waterfall-wiki-worker`,镜像参数为 `WATERFALL_WIKI_IMAGE` / `WATERFALL_WIKI_TEST_IMAGE`。 - -部署在停止写入后备份数据库,再运行迁移和启动新版。失败时恢复兼容旧镜像的备份并回滚服务。生产不执行 `down -v`,也不清空对象文件。早期合集验证会遍历分页,不假定旧文章一定在第一页。 - -## 登录与自动化 - -共享验证器的 6 位 TOTP 动态码登录,JWT 保存在 HttpOnly Cookie 中;这属于单因素动态码登录,不是 2FA。密钥保存在服务器认证卷,不进入 Git、镜像或 HTTP 二维码接口。 - -服务器管理员在交互式 SSH 终端运行 `sudo /opt/prompt/auth qr` 绑定验证器。不要把二维码、动态密钥或 JWT 粘贴到聊天、日志和仓库。服务端自动化可使用 `node auth-cli.cjs run <脚本>`,在进程内为本机 API 请求附加短期凭据,不输出令牌。 - -更多说明:[登录与 SSH 绑定](docs/dynamic-code-login.md) · [API](docs/api.md) · [数据模型](docs/data-model.md) · [项目结构与部署](docs/monorepo.md)。 +媒体 worker 在同一目录作为独立进程运行 `node dist/worker.cjs`。绑定管理员验证器时,在远程交互终端执行 `node dist/auth-cli.cjs qr`。已有实例复用原数据库、对象存储和私有认证文件。 diff --git a/apps/python/Dockerfile b/apps/python/Dockerfile deleted file mode 100644 index 7a025c0..0000000 --- a/apps/python/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM python:3.12-slim -RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg fonts-noto-cjk && rm -rf /var/lib/apt/lists/* -WORKDIR /app -COPY apps/python/ . -RUN pip install --no-cache-dir . && python -m unittest discover -s tests -v -RUN useradd --create-home exporter -USER exporter -ENTRYPOINT ["prompt-export"] diff --git a/apps/python/pyproject.toml b/apps/python/pyproject.toml deleted file mode 100644 index 5a4fe45..0000000 --- a/apps/python/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[build-system] -requires = ["setuptools==75.8.0"] -build-backend = "setuptools.build_meta" -[project] -name = "prompt-tools" -version = "0.1.0" -requires-python = ">=3.12" -dependencies = ["Pillow==11.1.0"] -[project.scripts] -prompt-export = "prompt_tools.export:main" -[tool.setuptools.packages.find] -where = ["src"] diff --git a/apps/python/src/prompt_tools/__init__.py b/apps/python/src/prompt_tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/apps/python/src/prompt_tools/export.py b/apps/python/src/prompt_tools/export.py deleted file mode 100644 index 031173c..0000000 --- a/apps/python/src/prompt_tools/export.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Render a Studio JSON bundle into PNG slides and optionally MP4.""" -import argparse -import json -import subprocess -from pathlib import Path -from PIL import Image, ImageDraw, ImageFont - - -def validate(bundle): - if bundle.get("version") != 1 or not isinstance(bundle.get("slides"), list) or not bundle["slides"]: - raise ValueError("Expected a version 1 Studio bundle with slides") - for slide in bundle["slides"]: - if not all(isinstance(slide.get(k), str) for k in ("heading", "body", "color")): - raise ValueError("Invalid slide") - return bundle["slides"] - - -def wrap(draw, text, font, width): - lines = [] - for paragraph in text.split("\n"): - line = "" - for char in paragraph: - if line and draw.textlength(line + char, font=font) > width: - lines.append(line) - line = "" - line += char - lines.append(line) - return lines - - -def render(bundle, output, font_path): - slides = validate(bundle) - output.mkdir(parents=True, exist_ok=True) - heading = ImageFont.truetype(str(font_path), 76) - body = ImageFont.truetype(str(font_path), 38) - for index, slide in enumerate(slides, 1): - image = Image.new("RGB", (1080, 1440), slide["color"]) - draw = ImageDraw.Draw(image) - draw.text((80, 80), f"PROMPT / {index:02}", font=body, fill="#353535") - y = 330 - for text, font, gap in [(slide["heading"], heading, 108), (slide["body"], body, 65)]: - for line in wrap(draw, text, font, 920): - if y + gap > 1360: - raise ValueError(f"Slide {index} overflows; split the text into more slides") - draw.text((80, y), line, font=font, fill="#242424") - y += gap - y += 70 - image.save(output / f"slide-{index:03}.png") - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("bundle", type=Path) - parser.add_argument("--output", type=Path, required=True) - parser.add_argument("--font", type=Path, required=True, help="CJK font TTF/OTF/TTC") - parser.add_argument("--video", action="store_true") - args = parser.parse_args() - bundle = json.loads(args.bundle.read_text()) - render(bundle, args.output, args.font) - if args.video: - # Use an explicit temporary input sequence so old PNGs cannot leak into a video. - import tempfile - import shutil - with tempfile.TemporaryDirectory() as staging: - for index in range(1, len(bundle["slides"]) + 1): - shutil.copy(args.output / f"slide-{index:03}.png", Path(staging) / f"slide-{index:03}.png") - subprocess.run(["ffmpeg", "-y", "-framerate", "1/4", "-i", str(Path(staging) / "slide-%03d.png"), "-vf", "fps=30,format=yuv420p", "-c:v", "libx264", "-movflags", "+faststart", str(args.output / "video.mp4")], check=True) - -if __name__ == "__main__": - main() diff --git a/apps/python/tests/test_export.py b/apps/python/tests/test_export.py deleted file mode 100644 index 8edf8b2..0000000 --- a/apps/python/tests/test_export.py +++ /dev/null @@ -1,13 +0,0 @@ -import unittest -from prompt_tools.export import validate, wrap -class ExportTests(unittest.TestCase): - def test_bad_bundle(self): - for bundle in [{}, {"version": 1, "slides": []}, {"version": 1, "slides": [{}]}]: - with self.assertRaises(ValueError): validate(bundle) - def test_wrap_preserves_content(self): - class Draw: - def textlength(self, text, font): return len(text) - self.assertEqual(wrap(Draw(), "中文内容测试", None, 3), ["中文内", "容测试"]) - def test_valid_bundle(self): - slides = [{"heading": "标题", "body": "正文", "color": "#fff"}] - self.assertEqual(validate({"version": 1, "slides": slides}), slides) diff --git a/apps/studio/Dockerfile b/apps/studio/Dockerfile deleted file mode 100644 index 37ada5b..0000000 --- a/apps/studio/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM node:24-bookworm-slim AS build -WORKDIR /repo/apps/studio -RUN corepack enable && corepack prepare pnpm@11.25.0 --activate -COPY apps/studio/pnpm-lock.yaml ./ -COPY pnpm-workspace.yaml /repo/pnpm-workspace.yaml -COPY packages/brand/ /repo/packages/brand/ -COPY apps/studio/package.json ./ -RUN pnpm --filter @prompt/studio install --frozen-lockfile --fetch-timeout=300000 --network-concurrency=8 -COPY apps/studio/ . -# Compare the two public parameter validators inside the isolated CI build. -COPY apps/waterfall-wiki/src/lib/layout-parameters.ts /repo/apps/waterfall-wiki/src/lib/layout-parameters.ts -COPY apps/waterfall-wiki/src/lib/series.ts /repo/apps/waterfall-wiki/src/lib/series.ts -COPY apps/waterfall-wiki/src/lib/canvas-document.ts /repo/apps/waterfall-wiki/src/lib/canvas-document.ts -RUN pnpm typecheck && pnpm test && pnpm build && pnpm test:smoke -FROM node:24-bookworm-slim -WORKDIR /app/apps/studio -ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000 -COPY --from=build --chown=node:node /repo/apps/studio/.next/standalone/ /app/ -COPY --from=build --chown=node:node /repo/apps/studio/.next/static/ ./.next/static/ -COPY --from=build --chown=node:node /repo/apps/studio/public/ ./public/ -USER node -EXPOSE 3000 -CMD ["node", "server.js"] diff --git a/apps/studio/fixtures/autumn-journal.json b/apps/studio/fixtures/autumn-journal.json deleted file mode 100644 index 9d02696..0000000 --- a/apps/studio/fixtures/autumn-journal.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "schemaVersion": 2, - "name": "把秋天装进一页 · 银杏观察手记", - "document": { - "version": 1, - "brief": { - "topic": "银杏与秋日观察手记", - "audience": "喜欢日常摄影与视觉手帐的小红书读者", - "intent": "以封面、观察方法和行动结尾组成三页可发布图文", - "designIntent": "奶油纸底、森林墨色、银杏金点缀;宋体大标题与克制的无衬线正文;所有页面保留相同页边距和节奏", - "imageStrategy": "生成一幅原创银杏静物插画作为系列母图;每页复用素材,保留裁切配置,不把正文烘焙进图片" - }, - "style": { - "version": "autumn-journal/1", - "palette": { - "paper": "#F3EFE5", - "ink": "#253C32", - "muted": "#718074", - "accent": "#C3A050" - }, - "typography": { - "title": { - "fontFamily": "serif", - "fontSize": 8.4, - "fontWeight": 600, - "lineHeight": 1.15, - "letterSpacing": 0 - }, - "body": { - "fontFamily": "sans", - "fontSize": 3.1, - "fontWeight": 400, - "lineHeight": 1.65, - "letterSpacing": 0 - }, - "caption": { - "fontFamily": "sans", - "fontSize": 1.7, - "fontWeight": 500, - "lineHeight": 1.2, - "letterSpacing": 0 - } - }, - "spacing": { - "margin": 10, - "gap": 4 - } - }, - "sources": [], - "pages": [ - { - "key": "cover", - "role": "cover", - "layoutId": "standard-01", - "intent": "用银杏静物与两行大标题建立主题", - "bindings": { - "T01": "title", - "T02": "body", - "T03": "caption" - }, - "parameters": { - "texts": { - "T01": { - "value": "把秋天\n装进一页" - }, - "T02": { - "value": "不必走很远。\n一片银杏,就是今天的风景。" - }, - "T03": { - "value": "SEASONAL NOTES / 01" - } - }, - "images": { - "I01": { - "fit": "cover", - "cropX": 50, - "cropY": 50, - "zoom": 1 - } - }, - "decorations": {} - } - }, - { - "key": "observe", - "role": "body", - "layoutId": "standard-01", - "intent": "用三个观察动作提供可实践的方法", - "bindings": { - "T01": "title", - "T02": "body", - "T03": "caption" - }, - "parameters": { - "texts": { - "T01": { - "value": "先看见\n再记录" - }, - "T02": { - "value": "看叶脉,看光落下的方向。\n留住一片金黄,也留出一半空白。\n把感受写成短句,画面就有了呼吸。" - }, - "T03": { - "value": "FIELD NOTES / 02" - } - }, - "images": { - "I01": { - "fit": "cover", - "cropX": 65, - "cropY": 40, - "zoom": 1.25 - } - }, - "decorations": {} - } - }, - { - "key": "ending", - "role": "ending", - "layoutId": "standard-01", - "intent": "以轻量行动和提问收尾", - "bindings": { - "T01": "title", - "T02": "body", - "T03": "caption" - }, - "parameters": { - "texts": { - "T01": { - "value": "留一点白\n给下一次", - "align": "middle" - }, - "T02": { - "value": "今天,捡起一片叶子。\n用三个词记录它的颜色、纹理与心情。\n你的秋天,会从哪一页开始?", - "align": "middle" - }, - "T03": { - "value": "YOUR TURN / 03" - } - }, - "images": { - "I01": { - "fit": "cover", - "cropX": 65, - "cropY": 50, - "zoom": 1 - } - }, - "decorations": {}, - "layers": { - "I01": { - "x": 40, - "y": 55, - "width": 40, - "height": 40, - "radius": 20 - } - } - } - } - ] - } -} diff --git a/apps/studio/next-env.d.ts b/apps/studio/next-env.d.ts deleted file mode 100644 index 15849f6..0000000 --- a/apps/studio/next-env.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// -/// -import "./.next/types/routes.d.ts"; -import "./.next/types/root-params.d.ts"; - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/apps/studio/next.config.ts b/apps/studio/next.config.ts deleted file mode 100644 index 67416ad..0000000 --- a/apps/studio/next.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { resolve } from "node:path"; -import type { NextConfig } from "next"; -const config: NextConfig = { turbopack: { root: resolve(process.cwd(), "../..") }, basePath: "/studio", output: "standalone", outputFileTracingRoot: resolve(process.cwd(), "../.."), poweredByHeader: false }; -export default config; diff --git a/apps/studio/package.json b/apps/studio/package.json deleted file mode 100644 index 0b75aab..0000000 --- a/apps/studio/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@prompt/studio", - "private": true, - "scripts": { - "dev": "pnpm brand:prepare && next dev -p 3001", - "build": "pnpm brand:prepare && next build", - "start": "next start", - "typecheck": "next typegen && tsc --noEmit", - "test": "node --test tests/*.test.mjs", - "test:smoke": "node scripts/smoke.mjs", - "brand:prepare": "node ../../packages/brand/scripts/prepare-app.mjs" - }, - "dependencies": { - "next": "16.4.2", - "react": "19.3.1", - "react-dom": "19.3.1", - "lucide-react": "1.61.0", - "@endoleap/brand": "workspace:*" - }, - "devDependencies": { - "@tailwindcss/postcss": "4.3.3", - "@types/node": "24.19.2", - "@types/react": "19.3.0", - "@types/react-dom": "19.3.0", - "tailwindcss": "4.3.3", - "typescript": "5.9.3" - }, - "packageManager": "pnpm@11.25.0" -} diff --git a/apps/studio/pnpm-lock.yaml b/apps/studio/pnpm-lock.yaml deleted file mode 100644 index d65df4b..0000000 --- a/apps/studio/pnpm-lock.yaml +++ /dev/null @@ -1,1033 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@endoleap/brand': - specifier: workspace:* - version: link:../../packages/brand - lucide-react: - specifier: 1.61.0 - version: 1.61.0(@types/react@19.3.0)(react@19.3.1) - next: - specifier: 16.4.2 - version: 16.4.2(@types/node@24.19.2)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) - react: - specifier: 19.3.1 - version: 19.3.1 - react-dom: - specifier: 19.3.1 - version: 19.3.1(react@19.3.1) - devDependencies: - '@tailwindcss/postcss': - specifier: 4.3.3 - version: 4.3.3 - '@types/node': - specifier: 24.19.2 - version: 24.19.2 - '@types/react': - specifier: 19.3.0 - version: 19.3.0 - '@types/react-dom': - specifier: 19.3.0 - version: 19.3.0(@types/react@19.3.0) - tailwindcss: - specifier: 4.3.3 - version: 4.3.3 - typescript: - specifier: 5.9.3 - version: 5.9.3 - -packages: - - '@alloc/quick-lru@5.3.0': - resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==} - engines: {node: '>=10'} - - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.35.5': - resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.35.5': - resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [darwin] - - '@img/sharp-freebsd-wasm32@0.35.5': - resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} - engines: {node: '>=20.9.0'} - os: [freebsd] - - '@img/sharp-libvips-darwin-arm64@1.3.4': - resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.3.4': - resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.3.4': - resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.3.4': - resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.3.4': - resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.3.4': - resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.3.4': - resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.3.4': - resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.3.4': - resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.3.4': - resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.35.5': - resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.35.5': - resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} - engines: {node: '>=20.9.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.35.5': - resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} - engines: {node: '>=20.9.0'} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.35.5': - resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} - engines: {node: '>=20.9.0'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.35.5': - resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} - engines: {node: '>=20.9.0'} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.35.5': - resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.35.5': - resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.35.5': - resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.35.5': - resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} - engines: {node: '>=20.9.0'} - - '@img/sharp-webcontainers-wasm32@0.35.5': - resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} - engines: {node: '>=20.9.0'} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.35.5': - resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.35.5': - resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} - engines: {node: ^20.9.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.35.5': - resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.6.0': - resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@next/env@16.4.2': - resolution: {integrity: sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==} - - '@next/swc-darwin-arm64@16.4.2': - resolution: {integrity: sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@16.4.2': - resolution: {integrity: sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@16.4.2': - resolution: {integrity: sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@16.4.2': - resolution: {integrity: sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@16.4.2': - resolution: {integrity: sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@16.4.2': - resolution: {integrity: sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@16.4.2': - resolution: {integrity: sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.4.2': - resolution: {integrity: sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@swc/helpers@0.5.23': - resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} - - '@tailwindcss/node@4.3.3': - resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - - '@tailwindcss/oxide-android-arm64@4.3.3': - resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.3.3': - resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.3.3': - resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.3.3': - resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - - '@types/node@24.19.2': - resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} - - '@types/react-dom@19.3.0': - resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} - peerDependencies: - '@types/react': ^19.3.0 - - '@types/react@19.3.0': - resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - - baseline-browser-mapping@2.11.32: - resolution: {integrity: sha512-UvuxFU9P3hM29LrQdwu0cD6w6/+SFr+UlB9s/knI7+O2rK9xFxKUwSDWxGdbbqQ9/V7BJ5s0NI5nQAE4uJp3gQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - caniuse-lite@1.0.30001819: - resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - enhanced-resolve@5.26.0: - resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} - engines: {node: '>=10.13.0'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lucide-react@1.61.0: - resolution: {integrity: sha512-v2SQPPPNjnr+BlhL9Spuzmo5yR+hLkftfuE4E3jaxdNPgSH0hJKPcLJBP7yAta87jQNPsSERS59PJjCJ/CorBA==} - peerDependencies: - '@types/react': '*' - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - nanoid@3.3.20: - resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - next@16.4.2: - resolution: {integrity: sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==} - engines: {node: '>=20.9.0'} - hasBin: true - 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 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - postcss@8.5.23: - resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.31: - resolution: {integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==} - engines: {node: ^10 || ^12 || >=14} - - react-dom@19.3.1: - resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} - peerDependencies: - react: ^19.3.1 - - react@19.3.1: - resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} - engines: {node: '>=0.10.0'} - - scheduler@0.28.0: - resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - sharp@0.35.5: - resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} - engines: {node: '>=20.9.0'} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - source-map-js@1.2.2: - resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} - engines: {node: '>=0.10.0'} - - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - - tailwindcss@4.3.3: - resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - -snapshots: - - '@alloc/quick-lru@5.3.0': {} - - '@emnapi/runtime@1.11.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@img/colour@1.1.0': - optional: true - - '@img/sharp-darwin-arm64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.4 - optional: true - - '@img/sharp-darwin-x64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.4 - optional: true - - '@img/sharp-freebsd-wasm32@0.35.5': - dependencies: - '@img/sharp-wasm32': 0.35.5 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.3.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.3.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.3.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.3.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.3.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.3.4': - optional: true - - '@img/sharp-linux-arm64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.4 - optional: true - - '@img/sharp-linux-arm@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.4 - optional: true - - '@img/sharp-linux-ppc64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.4 - optional: true - - '@img/sharp-linux-riscv64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.4 - optional: true - - '@img/sharp-linux-s390x@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.4 - optional: true - - '@img/sharp-linux-x64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.4 - optional: true - - '@img/sharp-wasm32@0.35.5': - dependencies: - '@emnapi/runtime': 1.11.3 - optional: true - - '@img/sharp-webcontainers-wasm32@0.35.5': - dependencies: - '@img/sharp-wasm32': 0.35.5 - optional: true - - '@img/sharp-win32-arm64@0.35.5': - optional: true - - '@img/sharp-win32-ia32@0.35.5': - optional: true - - '@img/sharp-win32-x64@0.35.5': - optional: true - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.6.0': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.6.0 - - '@next/env@16.4.2': {} - - '@next/swc-darwin-arm64@16.4.2': - optional: true - - '@next/swc-darwin-x64@16.4.2': - optional: true - - '@next/swc-linux-arm64-gnu@16.4.2': - optional: true - - '@next/swc-linux-arm64-musl@16.4.2': - optional: true - - '@next/swc-linux-x64-gnu@16.4.2': - optional: true - - '@next/swc-linux-x64-musl@16.4.2': - optional: true - - '@next/swc-win32-arm64-msvc@16.4.2': - optional: true - - '@next/swc-win32-x64-msvc@16.4.2': - optional: true - - '@swc/helpers@0.5.23': - dependencies: - tslib: 2.8.1 - - '@tailwindcss/node@4.3.3': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.26.0 - jiti: 2.7.0 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.2 - tailwindcss: 4.3.3 - - '@tailwindcss/oxide-android-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide@4.3.3': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-x64': 4.3.3 - '@tailwindcss/oxide-freebsd-x64': 4.3.3 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-x64-musl': 4.3.3 - '@tailwindcss/oxide-wasm32-wasi': 4.3.3 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - - '@tailwindcss/postcss@4.3.3': - dependencies: - '@alloc/quick-lru': 5.3.0 - '@tailwindcss/node': 4.3.3 - '@tailwindcss/oxide': 4.3.3 - postcss: 8.5.31 - tailwindcss: 4.3.3 - - '@types/node@24.19.2': - dependencies: - undici-types: 7.24.6 - - '@types/react-dom@19.3.0(@types/react@19.3.0)': - dependencies: - '@types/react': 19.3.0 - - '@types/react@19.3.0': - dependencies: - csstype: 3.2.3 - - baseline-browser-mapping@2.11.32: {} - - caniuse-lite@1.0.30001819: {} - - client-only@0.0.1: {} - - csstype@3.2.3: {} - - detect-libc@2.1.2: {} - - enhanced-resolve@5.26.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - graceful-fs@4.2.11: {} - - jiti@2.7.0: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - lucide-react@1.61.0(@types/react@19.3.0)(react@19.3.1): - dependencies: - react: 19.3.1 - optionalDependencies: - '@types/react': 19.3.0 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - - nanoid@3.3.20: {} - - next@16.4.2(@types/node@24.19.2)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): - dependencies: - '@next/env': 16.4.2 - '@swc/helpers': 0.5.23 - baseline-browser-mapping: 2.11.32 - caniuse-lite: 1.0.30001819 - postcss: 8.5.23 - react: 19.3.1 - react-dom: 19.3.1(react@19.3.1) - styled-jsx: 5.1.6(react@19.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 16.4.2 - '@next/swc-darwin-x64': 16.4.2 - '@next/swc-linux-arm64-gnu': 16.4.2 - '@next/swc-linux-arm64-musl': 16.4.2 - '@next/swc-linux-x64-gnu': 16.4.2 - '@next/swc-linux-x64-musl': 16.4.2 - '@next/swc-win32-arm64-msvc': 16.4.2 - '@next/swc-win32-x64-msvc': 16.4.2 - sharp: 0.35.5(@types/node@24.19.2) - transitivePeerDependencies: - - '@babel/core' - - '@types/node' - - babel-plugin-macros - - picocolors@1.1.1: {} - - postcss@8.5.23: - dependencies: - nanoid: 3.3.20 - picocolors: 1.1.1 - source-map-js: 1.2.2 - - postcss@8.5.31: - dependencies: - nanoid: 3.3.20 - picocolors: 1.1.1 - source-map-js: 1.2.2 - - react-dom@19.3.1(react@19.3.1): - dependencies: - react: 19.3.1 - scheduler: 0.28.0 - - react@19.3.1: {} - - scheduler@0.28.0: {} - - semver@7.8.5: - optional: true - - sharp@0.35.5(@types/node@24.19.2): - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.5 - '@img/sharp-darwin-x64': 0.35.5 - '@img/sharp-freebsd-wasm32': 0.35.5 - '@img/sharp-libvips-darwin-arm64': 1.3.4 - '@img/sharp-libvips-darwin-x64': 1.3.4 - '@img/sharp-libvips-linux-arm': 1.3.4 - '@img/sharp-libvips-linux-arm64': 1.3.4 - '@img/sharp-libvips-linux-ppc64': 1.3.4 - '@img/sharp-libvips-linux-riscv64': 1.3.4 - '@img/sharp-libvips-linux-s390x': 1.3.4 - '@img/sharp-libvips-linux-x64': 1.3.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 - '@img/sharp-libvips-linuxmusl-x64': 1.3.4 - '@img/sharp-linux-arm': 0.35.5 - '@img/sharp-linux-arm64': 0.35.5 - '@img/sharp-linux-ppc64': 0.35.5 - '@img/sharp-linux-riscv64': 0.35.5 - '@img/sharp-linux-s390x': 0.35.5 - '@img/sharp-linux-x64': 0.35.5 - '@img/sharp-linuxmusl-arm64': 0.35.5 - '@img/sharp-linuxmusl-x64': 0.35.5 - '@img/sharp-webcontainers-wasm32': 0.35.5 - '@img/sharp-win32-arm64': 0.35.5 - '@img/sharp-win32-ia32': 0.35.5 - '@img/sharp-win32-x64': 0.35.5 - '@types/node': 24.19.2 - optional: true - - source-map-js@1.2.2: {} - - styled-jsx@5.1.6(react@19.3.1): - dependencies: - client-only: 0.0.1 - react: 19.3.1 - - tailwindcss@4.3.3: {} - - tapable@2.3.3: {} - - tslib@2.8.1: {} - - typescript@5.9.3: {} - - undici-types@7.24.6: {} diff --git a/apps/studio/postcss.config.mjs b/apps/studio/postcss.config.mjs deleted file mode 100644 index a2dc18a..0000000 --- a/apps/studio/postcss.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export default { - plugins: { "@tailwindcss/postcss": {} }, -}; diff --git a/apps/studio/scripts/graphic-smoke.cjs b/apps/studio/scripts/graphic-smoke.cjs deleted file mode 100644 index c9a1efd..0000000 --- a/apps/studio/scripts/graphic-smoke.cjs +++ /dev/null @@ -1,24 +0,0 @@ -const {chromium}=require('playwright-core'); -const assert=require('node:assert/strict'); -(async()=>{const browser=await chromium.launch({channel:'chrome',headless:true});try{const page=await browser.newPage({viewport:{width:1440,height:1000}});const errors=[];page.on('pageerror',e=>errors.push(e.message)); - await page.route('**/api/account/session',r=>r.fulfill({json:{user:null}})); - await page.goto((process.env.STUDIO_TEST_ORIGIN||'http://8.211.154.134:18131')+'/graphic'); - await page.getByRole('button',{name:'切换图层面板',exact:true}).click(); - assert.equal(await page.getByRole('button',{name:'图层',exact:true}).isVisible(),false); - await page.getByRole('button',{name:'切换图层面板',exact:true}).click(); - await page.getByRole('button',{name:'切换属性面板',exact:true}).click(); - assert.equal(await page.getByRole('button',{name:'备份设计 JSON',exact:true}).isVisible(),false); - await page.getByRole('button',{name:'切换属性面板',exact:true}).click(); - await page.getByRole('button',{name:'添加文字',exact:true}).click(); - await page.getByRole('textbox',{name:'文字内容',exact:true}).fill('设计从这里开始'); - await page.getByRole('button',{name:'添加矩形',exact:true}).click(); - await page.getByRole('button',{name:'撤销',exact:true}).click(); - await page.getByRole('button',{name:'重做',exact:true}).click(); - await page.reload();await page.getByRole('button',{name:'文字',exact:true}).first().waitFor(); - assert.ok(await page.evaluate(()=>localStorage.getItem('studio.graphic.v1').includes('设计从这里开始'))); - const file=page.waitForEvent('download');await page.getByRole('button',{name:'导出 SVG',exact:true}).click();assert.ok((await file).suggestedFilename().endsWith('.svg')); - await page.getByRole('button',{name:'邮箱登录',exact:true}).click();await page.getByRole('dialog').waitFor();await page.keyboard.press('Escape');await page.getByRole('dialog').waitFor({state:'hidden'}); - await page.screenshot({path:process.env.STUDIO_QA_SCREENSHOT||'graphic-qa.png',fullPage:true}); - await page.setViewportSize({width:390,height:844});assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth),false); - assert.deepEqual(errors,[]);console.log('Graphic editing, history, restore, SVG download, account dialog and mobile overflow checks passed.'); - }finally{await browser.close()}})().catch(e=>{console.error(e);process.exitCode=1}); diff --git a/apps/studio/scripts/infinite-canvas-smoke.cjs b/apps/studio/scripts/infinite-canvas-smoke.cjs deleted file mode 100644 index 684e844..0000000 --- a/apps/studio/scripts/infinite-canvas-smoke.cjs +++ /dev/null @@ -1,128 +0,0 @@ -// Isolated browser context against the designated deployed server only. -const {chromium}=require('playwright-core'); -const assert=require('node:assert/strict'); -const fs=require('node:fs/promises'); -(async()=>{ - const base=process.env.BASE_URL||'http://8.211.154.134:18131'; - assert.equal(base,'http://8.211.154.134:18131'); - const browser=await chromium.launch(process.env.CHROMIUM_PATH?{executablePath:process.env.CHROMIUM_PATH,headless:true,args:['--no-sandbox','--disable-dev-shm-usage']}:{channel:'chrome',headless:true}); - let page; - try{ - const context=await browser.newContext({viewport:{width:1600,height:1100},storageState:process.env.STORAGE_STATE||undefined}); - page=await context.newPage();const errors=[];let choosers=0; - page.on('pageerror',e=>errors.push(e.message));page.on('filechooser',()=>choosers++); - const snapshot=()=>page.evaluate(()=>JSON.parse(localStorage.getItem('studio.infinite-canvas.v1'))); - const node=id=>page.locator(`[data-node-id="${id}"]`); - const content=id=>node(id).locator('[data-node-content]'); - const waitField=(id,key,value)=>page.waitForFunction(({id,key,value})=>JSON.parse(localStorage.getItem('studio.infinite-canvas.v1')).cards.find(n=>n.id===id)?.[key]===value,{id,key,value}); - const composer=page.getByRole('region',{name:'节点编辑器'}); - const capture=async name=>{if(process.env.SCREENSHOT_DIR){await fs.mkdir(process.env.SCREENSHOT_DIR,{recursive:true});await page.screenshot({path:process.env.SCREENSHOT_DIR+'/'+name+'.png'})}}; - async function unobstructed(id){const box=await node(id).boundingBox();assert.ok(box);for(const card of (await snapshot()).cards.filter(n=>n.id!==id)){const other=await node(card.id).boundingBox();assert.ok(other);assert.equal(box.xother.x&&box.yother.y,false,`${id} overlaps ${card.id}`)}} - const close=(a,b)=>assert.ok(Math.abs(a-b)<2,`${a} ≈ ${b}`); - async function drag(handle,dx,dy){const b=await handle.boundingBox();assert.ok(b);await page.mouse.move(b.x+b.width/2,b.y+b.height/2);await page.mouse.down();await page.mouse.move(b.x+b.width/2+dx,b.y+b.height/2+dy,{steps:12});await page.mouse.up()} - const canvasResponse=await page.goto(base+'/infinite_canvas',{waitUntil:'commit',timeout:60000}); - assert.ok(canvasResponse?.ok(),'Canvas document returns HTTP success'); - await page.locator('button[data-theme-ready="true"]').waitFor(); - await page.getByRole('button',{name:'从示例开始 ↗',exact:true}).click(); - await page.getByRole('button',{name:'适应画布',exact:true}).click(); - await content('brief').click({position:{x:20,y:20}}); - assert.equal(await node('brief').getAttribute('data-selected'),'true');assert.equal(await node('brief').getAttribute('data-editing'),'false'); - await page.waitForTimeout(700); - await content('brief').click({position:{x:20,y:20}}); - await node('brief').getByRole('textbox',{name:'01 / 创意简报 内容'}).fill('选择后再次点击进入编辑'); - await waitField('brief','text','选择后再次点击进入编辑'); - await node('brief').getByRole('button',{name:'加粗',exact:true}).click(); - assert.equal((await snapshot()).cards.find(n=>n.id==='brief').style.bold,true); - await node('brief').getByRole('button',{name:'斜体',exact:true}).click(); - await node('brief').getByLabel('文字颜色',{exact:true}).fill('#78905e'); - await node('brief').getByRole('combobox',{name:'文字样式',exact:true}).selectOption('24'); - await page.waitForFunction(()=>{const n=JSON.parse(localStorage.getItem('studio.infinite-canvas.v1')).cards.find(n=>n.id==='brief');return n.style.bold&&n.style.italic&&n.style.color==='#78905e'&&n.style.size===24}); - assert.deepEqual(await node('brief').getByRole('textbox',{name:'01 / 创意简报 内容'}).evaluate(el=>{const s=getComputedStyle(el);return {size:s.fontSize,weight:s.fontWeight,style:s.fontStyle,color:s.color}}),{size:'24px',weight:'700',style:'italic',color:'rgb(120, 144, 94)'}); - const compact=(await snapshot()).cards.find(n=>n.id==='brief'); - await node('brief').getByRole('button',{name:'展开文本节点',exact:true}).click(); - const expanded=(await snapshot()).cards.find(n=>n.id==='brief');assert.ok(expanded.width>(compact.width||320)&&expanded.height>(compact.height||350)); - await capture('canvas-text-format-desktop'); - await page.getByRole('button',{name:'撤销',exact:true}).click(); - assert.equal((await snapshot()).cards.find(n=>n.id==='brief').width,compact.width);assert.equal((await snapshot()).cards.find(n=>n.id==='brief').height,compact.height); - await page.keyboard.press('Escape'); - const before=(await snapshot()).cards.find(n=>n.id==='brief'); - const z=await page.locator('[class*=world]').evaluate(el=>new DOMMatrix(getComputedStyle(el).transform).a); - await drag(content('brief'),45,25); - const after=(await snapshot()).cards.find(n=>n.id==='brief');close(after.x-before.x,45/z);close(after.y-before.y,25/z); - assert.equal(await node('brief').getAttribute('data-editing'),'false'); - - await content('reference').click();assert.equal(await composer.count(),0); - await page.waitForTimeout(700);await content('reference').click();await composer.waitFor();assert.equal(choosers,0,'Entering image editing must not force a file picker'); - const panelBefore=await composer.boundingBox(),imageBefore=await node('reference').boundingBox(); - await capture('canvas-composer-desktop'); - await page.getByRole('button',{name:'缩小',exact:true}).click(); - const panelAfter=await composer.boundingBox(),imageAfter=await node('reference').boundingBox(); - close(panelBefore.width,panelAfter.width);close(panelBefore.height,panelAfter.height);assert.ok(imageAfter.width!!JSON.parse(localStorage.getItem('studio.infinite-canvas.v1')).cards.find(n=>n.id==='reference').image); - await node('reference').getByRole('button',{name:'图片高清 ↗',exact:true}).click(); - const upscale=(await snapshot()).cards.find(n=>n.operation==='upscale');assert.ok(upscale); - await unobstructed(upscale.id); - const download=page.waitForEvent('download');await composer.getByRole('button',{name:'导出任务',exact:true}).click(); - const task=JSON.parse(await fs.readFile(await (await download).path(),'utf8')); - assert.equal(task.target.id,upscale.id);assert.equal(task.references[0].id,'reference');assert.ok(task.references[0].image); - - await page.keyboard.press('Escape');await page.getByRole('button',{name:'适应画布',exact:true}).click(); - await content('brief').click({position:{x:20,y:20}}); - await node('brief').getByRole('button',{name:'引用 01 / 创意简报 生成',exact:true}).click(); - await page.getByRole('dialog',{name:'画布操作'}).getByRole('button',{name:'脚本',exact:true}).click(); - const script=(await snapshot()).cards.find(n=>n.kind==='script');assert.ok(script); - await unobstructed(script.id); - await composer.getByRole('textbox',{name:'创作要求',exact:true}).fill('基于简报写六个分镜'); - await waitField(script.id,'instruction','基于简报写六个分镜');assert.equal((await snapshot()).cards.find(n=>n.id===script.id).text,''); - await composer.getByRole('button',{name:'画布参考',exact:true}).click(); - await composer.getByRole('group',{name:'选择引用节点'}).getByRole('button',{name:'02 / 视觉参考',exact:true}).click(); - assert.ok((await snapshot()).links.some(([a,b])=>a==='reference'&&b===script.id)); - await composer.getByRole('button',{name:'移除引用 01 / 创意简报',exact:true}).click(); - assert.ok(!(await snapshot()).links.some(([a,b])=>a==='brief'&&b===script.id)); - await page.keyboard.press('Escape'); - await page.getByRole('button',{name:'适应画布',exact:true}).click(); - await content('reference').click();await node('reference').getByRole('button',{name:'02 / 视觉参考 操作',exact:true}).click(); - await page.getByRole('button',{name:'删除节点',exact:true}).click(); - assert.ok((await snapshot()).cards.some(n=>n.id===script.id));assert.ok((await snapshot()).links.every(l=>!l.includes('reference'))); - await page.getByRole('button',{name:'撤销',exact:true}).click();assert.ok((await snapshot()).links.some(([a,b])=>a==='reference'&&b===script.id)); - await page.keyboard.press('Escape'); - await content('brief').click({position:{x:20,y:20}}); - const priorWidth=(await snapshot()).cards.find(n=>n.id==='brief').width; - await drag(node('brief').getByRole('button',{name:'调整 01 / 创意简报 尺寸',exact:true}),50,30); - assert.ok((await snapshot()).cards.find(n=>n.id==='brief').width>(priorWidth||320)); - await page.getByRole('button',{name:'撤销',exact:true}).click();assert.equal((await snapshot()).cards.find(n=>n.id==='brief').width,priorWidth); - await page.getByRole('button',{name:'重做',exact:true}).click(); - await page.getByRole('button',{name:'移动画布',exact:true}).click(); - await page.mouse.move(15,300);await page.mouse.down();await page.mouse.move(75,340,{steps:8});await page.mouse.up(); - assert.equal(await page.evaluate(()=>{const path=[...document.querySelectorAll('svg path')].find(p=>p.getAttribute('d')?.startsWith('M '));const d=path.getAttribute('d').match(/-?\d+(?:\.\d+)?/g).map(Number),point=new DOMPoint(d[0],d[1]).matrixTransform(path.getScreenCTM()),card=document.querySelector('[data-node-id="brief"]').getBoundingClientRect();return Math.abs(point.x-card.right)<3&&Math.abs(point.y-(card.top+card.height/2))<3}),true); - const worldAt=()=>page.locator('[class*=world]').evaluate(el=>{const m=new DOMMatrix(getComputedStyle(el).transform);return {x:(1450-m.e)/m.a,y:(150-m.f)/m.d,z:m.a}}); - const anchorBefore=await worldAt();await page.mouse.move(1450,150);await page.keyboard.down('Control');await page.mouse.wheel(0,-80);await page.keyboard.up('Control'); - await page.waitForFunction(z=>new DOMMatrix(getComputedStyle(document.querySelector('[class*=world]')).transform).a!==z,anchorBefore.z); - const anchorAfter=await worldAt();close(anchorBefore.x,anchorAfter.x);close(anchorBefore.y,anchorAfter.y); - const saved=await snapshot();await page.reload({waitUntil:'commit',timeout:60000});await page.locator('button[data-theme-ready="true"]').waitFor();await page.waitForFunction(()=>!!localStorage.getItem('studio.infinite-canvas.v1'));assert.deepEqual(await snapshot(),saved); - const themeToggle=page.getByRole('button',{name:'切换明暗主题',exact:true}); - await page.waitForFunction(()=>document.querySelector('[data-theme-ready]')?.dataset.themeReady==='true'); - const initialMode=await page.evaluate(()=>document.documentElement.dataset.mode); - assert.ok(['light','dark'].includes(initialMode)); - for(const mode of [initialMode==='light'?'dark':'light',initialMode]){ - await themeToggle.click(); - const matchesTheme=mode=>{ - const root=document.documentElement,canvas=document.querySelector('main'); - return root.dataset.mode===mode&&root.dataset.theme===`canvas-${mode}`&&localStorage.getItem('endoleap.theme')===mode&&getComputedStyle(canvas).backgroundColor===(mode==='dark'?'rgb(20, 20, 20)':'rgb(250, 250, 250)'); - }; - await page.waitForFunction(matchesTheme,mode); - await page.reload({waitUntil:'commit'}); - await page.waitForFunction(()=>document.querySelector('[data-theme-ready]')?.dataset.themeReady==='true'); - await page.waitForFunction(matchesTheme,mode); - assert.deepEqual(await snapshot(),saved,'Theme changes must preserve canvas content'); - await capture('canvas-theme-'+mode); - } - if(process.env.SCREENSHOT_DIR){await fs.mkdir(process.env.SCREENSHOT_DIR,{recursive:true});await page.screenshot({path:process.env.SCREENSHOT_DIR+'/canvas-nodes-desktop.png'});} - await page.setViewportSize({width:390,height:844});assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)); - const viewport=await page.locator('[class*=viewport]').boundingBox();assert.equal(viewport.width,390);assert.equal(viewport.height,844); - if(process.env.SCREENSHOT_DIR)await page.screenshot({path:process.env.SCREENSHOT_DIR+'/canvas-nodes-mobile.png'}); - assert.deepEqual(errors,[]);console.log('Passed: selected-node re-click editing, drag, formatting, fixed composer, upload, references, task export, deletion/undo, reload, both persistent root themes and mobile bounds'); - }catch(error){if(process.env.SCREENSHOT_DIR&&page){await fs.mkdir(process.env.SCREENSHOT_DIR,{recursive:true});await page.screenshot({path:process.env.SCREENSHOT_DIR+'/canvas-failure.png'}).catch(()=>{})}throw error}finally{await browser.close()} -})().catch(e=>{console.error(e);process.exitCode=1}); diff --git a/apps/studio/scripts/smoke.mjs b/apps/studio/scripts/smoke.mjs deleted file mode 100644 index 729976b..0000000 --- a/apps/studio/scripts/smoke.mjs +++ /dev/null @@ -1,29 +0,0 @@ -/** CI-only isolated runtime checks, never a local preview workflow. */ -import {readFileSync} from 'node:fs'; -import {spawn} from 'node:child_process';import {once} from 'node:events';import {createServer} from 'node:net';import {createServer as createHttpServer} from 'node:http';import assert from 'node:assert/strict';import {setTimeout} from 'node:timers/promises'; -const designId='00000000-0000-4000-8000-000000000001',missingId='00000000-0000-4000-8000-000000000002'; -const standardId='00000000-0000-4000-8000-000000000003'; -const seriesId='00000000-0000-4000-8000-000000000004'; -const doc=JSON.parse(readFileSync(new URL('../fixtures/autumn-journal.json',import.meta.url))).document; -const assetId='00000000-0000-4000-8000-000000000005';doc.sources=[{assetId,author:'CI fixture',license:'test only',url:'generated',prompt:'test image',tool:'fixture'}];for(const p of doc.pages)p.parameters.images.I01.assetId=assetId; -let reads=0,mediaReads=0; -const wiki=createHttpServer((req,res)=>{assert.equal(req.headers.cookie,'session=smoke-test');if(/^\/api\/media\/[0-9a-f-]{36}$/.test(req.url)){mediaReads++;res.setHeader('Content-Type','image/png');return res.end(Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+j3ioAAAAASUVORK5CYII=','base64'))}reads++;res.setHeader('Content-Type','application/json');if(req.url.endsWith(seriesId))return res.end(JSON.stringify({design:{id:seriesId,name:'完整三页作品',layoutId:'standard-01',revision:1,url:'/studio/template-layout?id='+seriesId,parameters:doc.pages[0].parameters,document:doc}}));const standard=req.url.endsWith(standardId);if(!standard&&!req.url.endsWith(designId)){res.writeHead(404);return res.end('{}')}res.end(JSON.stringify({design:{id:standard?standardId:designId,name:'已保存的实际页面',layoutId:standard?'standard-01':'ref-01',revision:3,url:'/studio/designs/'+designId,parameters:{added:[{id:'user-smoke',kind:'text',role:'Added text',box:[10,10,20,10],direction:'horizontal',text:'新增元素已恢复',reading:'custom'}],layers:{T01:{x:12,rotation:25,opacity:.7},'user-smoke':{z:12}},texts:{T01:{value:'确定的页面文字',color:'#123456'}},images:{I01:{color:'#abcdef'}},decorations:{D01:{hidden:true}},paper:'#eeeeee'}}}))}); -wiki.listen(0,'127.0.0.1');await once(wiki,'listening'); -const socket=createServer();socket.listen(0,'127.0.0.1');await once(socket,'listening');const port=socket.address().port;await new Promise(r=>socket.close(r));const child=spawn(process.execPath,[process.env.STUDIO_STANDALONE_SERVER || '.next/standalone/apps/studio/server.js'],{env:{...process.env,PORT:String(port),HOSTNAME:'127.0.0.1',STUDIO_WIKI_ORIGIN:'http://127.0.0.1:'+wiki.address().port},stdio:'inherit'}),base='http://127.0.0.1:'+port; -try{let ready=false;for(let i=0;i<60;i++){try{if((await fetch(base+'/studio/api/health')).ok){ready=true;break}}catch{}await setTimeout(250)}assert.ok(ready);const entry=await fetch(base+'/studio',{redirect:'manual'});assert.equal(entry.status,307);assert.equal(entry.headers.get('location'),'/studio/graphic');const library=await fetch(base+'/studio/graphic').then(r=>r.text());assert.ok(library.includes('GRAPHIC'));assert.ok(!library.includes('ref-27')); const catalog=await fetch(base+'/studio/api/layouts').then(r=>r.json());assert.equal(catalog.layouts.length,28); -for(const l of catalog.layouts){const page=await fetch(base+l.url);assert.equal(page.status,200,l.id);const html=await page.text();assert.ok(html.includes(l.name));assert.ok(html.includes('data-layout-id="'+l.id+'"'));assert.ok(!/r.json());assert.equal(schema.id,l.id);for(const s of schema.elements)assert.ok(html.includes('data-element="'+s.id+'"'));if(l.family==='standard'){assert.equal((await fetch(base+l.api+'?format=svg')).status,400);continue;}const original=await fetch(base+l.api+'?format=svg',{headers:{cookie:'session=smoke-test'}}).then(r=>r.text());assert.ok(original.includes('data-layout-id="'+l.id+'"'));const text=schema.elements.find(e=>e.kind==='text');const result=await fetch(base+l.api,{method:'POST',headers:{'Content-Type':'application/json',cookie:'session=smoke-test'},body:JSON.stringify({texts:{[text.id]:{value:'API render '}}})});assert.equal(result.status,200);assert.ok((await result.text()).includes('<'));} -assert.equal(reads,0,'Default pages never load a saved design');assert.ok(mediaReads>0,'SVG exports load isolated media fixtures'); -const headers={cookie:'session=smoke-test'}; -for(const id of [designId,standardId]){ -const saved=await fetch(base+'/studio/template-layout?id='+id,{headers});assert.equal(saved.status,200);const savedHtml=await saved.text();assert.ok(savedHtml.includes('确定的页面文字'));assert.ok(savedHtml.includes('新增元素已恢复'));assert.ok(savedHtml.includes('data-element="user-smoke"'));assert.ok(savedHtml.includes('rotate(25deg)'));assert.ok(savedHtml.includes('opacity:0.7'));assert.ok(savedHtml.includes('data-design-id="'+id+'"'));assert.ok(savedHtml.includes('#123456'));assert.ok(!savedHtml.includes('data-element="D01"'));assert.ok(!savedHtml.includes('data:image'));assert.ok(!/page-tools|workbench-top|contenteditable|编辑页面/.test(savedHtml)); -} -const complete=await fetch(base+'/studio/template-layout?id='+seriesId,{headers});assert.equal(complete.status,200);const completeHtml=await complete.text(),artwork=completeHtml.match(//)[0];assert.equal((artwork.match(/data-page-key=/g)||[]).length,3);assert.ok(artwork.includes('data-style-version="autumn-journal/1"'));assert.ok(artwork.includes('把秋天'));assert.ok(artwork.includes('先看见'));assert.ok(artwork.includes('留一点白'));assert.ok(!/button|contenteditable|workbench-top/.test(artwork)); -const second=await fetch(base+'/studio/template-layout?id='+seriesId+'&page=2&export=1',{headers});assert.equal(second.status,200);const secondHtml=(await second.text()).match(//)[0];assert.equal((secondHtml.match(/data-page-key=/g)||[]).length,1);assert.ok(secondHtml.includes('data-page="2"'));assert.ok(secondHtml.includes('height:1440px'));assert.ok(secondHtml.includes('width=2160'));assert.ok(secondHtml.includes('profile=export'));assert.ok(secondHtml.includes('Noto Serif CJK SC')); -for(const suffix of ['0','4','bad'])assert.equal((await fetch(base+'/studio/template-layout?id='+seriesId+'&page='+suffix,{headers})).status,404); -for(const path of ['/studio/template-layout','/studio/template-layout?id=invalid','/studio/template-layout?id='+missingId,'/studio/template-layout?id='+designId+'&id='+standardId])assert.equal((await fetch(base+path,{headers})).status,404); -assert.equal((await fetch(base+'/studio/layouts/ref-02?id='+designId,{headers})).status,404); -assert.equal((await fetch(base+'/studio/layouts/ref-01?id='+missingId,{headers})).status,404); -const beforeInvalid=reads;assert.equal((await fetch(base+'/studio/layouts/ref-01?id=invalid',{headers})).status,404);assert.equal(reads,beforeInvalid); -const legacy=await fetch(base+'/studio/designs/'+designId,{headers,redirect:'manual'});assert.equal(legacy.status,307);assert.equal(legacy.headers.get('location'),'/studio/template-layout?id='+designId); -const old=await fetch(base+'/studio/layouts/ref-01?id='+designId,{headers,redirect:'manual'});assert.equal(old.status,307);assert.equal(old.headers.get('location'),'/studio/template-layout?id='+designId); -assert.equal((await fetch(base+'/studio/layouts/ref-28')).status,404);assert.equal((await fetch(base+'/studio/api/layouts/ref-01',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({texts:{I01:{value:'bad'}}})})).status,400);console.log('All 27 native pages, server-loaded saved IDs, legacy redirects and render APIs passed');}finally{child.kill('SIGTERM');await once(child,'exit');await new Promise(r=>wiki.close(r))} diff --git a/apps/studio/src/lib/canvas-document.ts b/apps/studio/src/lib/canvas-document.ts deleted file mode 100644 index d170267..0000000 --- a/apps/studio/src/lib/canvas-document.ts +++ /dev/null @@ -1,78 +0,0 @@ -export type NodeKind = 'text' | 'image' | 'prompt' | 'script'; -export type TextStyle = {bold?:boolean;italic?:boolean;color?:string;size?:number}; -export type CanvasNode = { - id:string;kind:NodeKind;title:string;text:string;x:number;y:number; - image?:string;width?:number;height?:number;style?:TextStyle; - instruction?:string; - operation?:'generate'|'upscale'|'script'; -}; -export type CanvasDocument = {version:1;cards:CanvasNode[];links:[string,string][]}; -const kinds:NodeKind[]=['text','image','prompt','script']; -const object=(v:unknown):v is Record=>!!v&&typeof v==='object'&&!Array.isArray(v); -const bounded=(v:unknown,min:number,max:number)=>typeof v==='number'&&Number.isFinite(v)&&v>=min&&v<=max; -const text=(v:unknown,max:number)=>typeof v==='string'&&v.length<=max; -const image=(v:unknown)=>typeof v==='string'&&v.length<3000000&&/^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/=]+$/.test(v); - -export function parseCanvasDocument(value:unknown):CanvasDocument { - const d=typeof value==='string'?JSON.parse(value):value; - if(!object(d)||d.version!==1||!Array.isArray(d.cards)||d.cards.length>80||!Array.isArray(d.links)||d.links.length>200)throw Error('画布格式不正确'); - const ids=new Set(); - const cards:CanvasNode[]=d.cards.map((n:unknown)=>{ - if(!object(n)||!text(n.id,100)||!n.id||ids.has(n.id as string)||!kinds.includes(n.kind as NodeKind)||!text(n.title,200)||!text(n.text,10000)||!bounded(n.x,-100000,100000)||!bounded(n.y,-100000,100000))throw Error('节点格式不正确'); - if(n.width!==undefined&&!bounded(n.width,180,1200)||n.height!==undefined&&!bounded(n.height,160,1200)||n.image!==undefined&&!image(n.image))throw Error('节点尺寸或图片不正确'); - if(n.instruction!==undefined&&!text(n.instruction,10000))throw Error('创作要求格式不正确'); - if(n.operation!==undefined&&!['generate','upscale','script'].includes(n.operation as string))throw Error('生成任务类型不正确'); - let style:TextStyle|undefined; - if(n.style!==undefined){ - if(!object(n.style)||n.style.bold!==undefined&&typeof n.style.bold!=='boolean'||n.style.italic!==undefined&&typeof n.style.italic!=='boolean'||n.style.color!==undefined&&(typeof n.style.color!=='string'||!/^#[a-f\d]{6}$/i.test(n.style.color))||n.style.size!==undefined&&!bounded(n.style.size,10,72))throw Error('文字样式不正确'); - style={...(n.style.bold===undefined?{}:{bold:n.style.bold as boolean}),...(n.style.italic===undefined?{}:{italic:n.style.italic as boolean}),...(n.style.color===undefined?{}:{color:n.style.color as string}),...(n.style.size===undefined?{}:{size:n.style.size as number})}; - } - ids.add(n.id as string); - return {id:n.id as string,kind:n.kind as NodeKind,title:n.title as string,text:n.text as string,x:n.x as number,y:n.y as number,...(n.width===undefined?{}:{width:n.width as number}),...(n.height===undefined?{}:{height:n.height as number}),...(n.image===undefined?{}:{image:n.image as string}),...(style?{style}:{}),...(n.instruction===undefined?{}:{instruction:n.instruction as string}),...(n.operation===undefined?{}:{operation:n.operation as CanvasNode['operation']})}; - }); - const pairs=new Set(); - const links:[string,string][]=d.links.map((l:unknown)=>{ - if(!Array.isArray(l)||l.length!==2||l[0]===l[1]||!l.every(id=>typeof id==='string'&&ids.has(id)))throw Error('引用节点不存在'); - const key=JSON.stringify(l);if(pairs.has(key))throw Error('引用重复');pairs.add(key);return [l[0],l[1]]; - }); - const outgoing=new Map(),indegree=new Map(cards.map(n=>[n.id,0])); - for(const [from,to] of links){outgoing.set(from,[...(outgoing.get(from)||[]),to]);indegree.set(to,indegree.get(to)!+1)} - const roots=cards.filter(n=>indegree.get(n.id)===0).map(n=>n.id);let visited=0; - while(roots.length){const id=roots.pop()!;visited++;for(const to of outgoing.get(id)||[]){const count=indegree.get(to)!-1;indegree.set(to,count);if(!count)roots.push(to)}} - if(visited!==cards.length)throw Error('引用不能形成循环'); - return {version:1,cards,links}; -} - -export function referencedNodes(doc:CanvasDocument,target:string):CanvasNode[]{ - return doc.links.filter(([,to])=>to===target).map(([from])=>doc.cards.find(n=>n.id===from)!).filter(Boolean); -} -export function connectNodes(doc:CanvasDocument,from:string,to:string):CanvasDocument { - if(from===to||![from,to].every(id=>doc.cards.some(n=>n.id===id)))throw Error('请选择不同的有效节点'); - if(doc.links.some(([a,b])=>a===from&&b===to))return doc; - if(doc.links.length>=200)throw Error('最多 200 条引用'); - const seen=new Set();const pending=[to]; - while(pending.length){const id=pending.pop()!;if(id===from)throw Error('引用不能形成循环');if(seen.has(id))continue;seen.add(id);pending.push(...doc.links.filter(([a])=>a===id).map(([,b])=>b));} - return {...doc,links:[...doc.links,[from,to]]}; -} -export function removeCanvasNode(doc:CanvasDocument,id:string):CanvasDocument { - return {...doc,cards:doc.cards.filter(n=>n.id!==id),links:doc.links.filter(l=>!l.includes(id))}; -} -export function deriveNode(doc:CanvasDocument,source:string,node:CanvasNode):CanvasDocument { - if(doc.cards.length>=80||doc.cards.some(n=>n.id===node.id))throw Error('节点数量或标识不正确'); - const target=parseCanvasDocument({version:1,cards:[node],links:[]}).cards[0]; - for(let attempt=0;attempt<=doc.cards.length+1;attempt++){ - const blockers=doc.cards.filter(n=>target.xn.x&&target.yn.y); - if(!blockers.length)return parseCanvasDocument(connectNodes({...doc,cards:[...doc.cards,target]},source,target.id)); - const nextY=Math.max(...blockers.map(n=>n.y+(n.height||350)+64)); - target.y=nextY>100000?-100000:nextY; - } - throw Error('请移动现有节点后再创建引用节点'); -} -export function agentTask(doc:CanvasDocument,id:string){ - const node=doc.cards.find(n=>n.id===id);if(!node)throw Error('目标节点不存在'); - const instruction=node.instruction??node.text; - if(!instruction.trim()&&node.operation!=='upscale')throw Error('请先填写创作要求'); - const references=referencedNodes(doc,id); - if(node.operation==='upscale'&&!references.some(n=>n.kind==='image'&&n.image))throw Error('请先引用一张图片'); - return {schema:'studio.canvas-task/1',target:{id:node.id,kind:node.kind,title:node.title},operation:node.operation||'generate',instruction,...(node.image?{sourceImage:node.image}:{}),references:references.map(n=>({id:n.id,kind:n.kind,title:n.title,text:n.text,...(n.image?{image:n.image}:{}),...(n.style?{style:n.style}:{})})),result:{targetId:node.id,allowedFields:node.kind==='image'?['image','text']:['text']}}; -} diff --git a/apps/studio/src/lib/layout-parameters.ts b/apps/studio/src/lib/layout-parameters.ts deleted file mode 100644 index f6125b0..0000000 --- a/apps/studio/src/lib/layout-parameters.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type Slot={id:string;kind:string;role:string;box:number[];direction:string;text:string|null;reading:string}; -export type Layout={id:string;name:string;width:number;height:number;elements:Slot[]}; -export type TextValue={value:string;color?:string;scale?:number;align?:'start'|'middle'|'end';fontFamily?:'sans'|'serif'|'mono';fontSize?:number;fontWeight?:number;lineHeight?:number;letterSpacing?:number}; -export type ImageValue={assetId?:string;color?:string;fit?:'cover'|'contain';cropX?:number;cropY?:number;zoom?:number}; -export type DecorationValue={color?:string;hidden?:boolean}; -export type LayerValue={x?:number;y?:number;width?:number;height?:number;rotation?:number;opacity?:number;radius?:number;z?:number;hidden?:boolean;locked?:boolean}; -export type AddedSlot=Slot & {shape?:'rectangle'|'ellipse'}; -export type Parameters={texts:Record;images:Record;decorations:Record;paper?:string;layers?:Record;added?:AddedSlot[]}; -export class LayoutParameterError extends Error{} -export const uuid=(v:unknown):v is string=>typeof v==='string'&&/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(v); -function fail(message:string):never{throw new LayoutParameterError(message)} -function object(v:unknown):Record{if(!v||typeof v!=='object'||Array.isArray(v))fail('参数必须为 JSON 对象');return v as Record} -function keys(v:Record,allowed:string[]){if(Object.keys(v).some(k=>!allowed.includes(k)))fail('未知参数或构件 ID')} -function number(v:unknown,min:number,max:number){if(typeof v!=='number'||!Number.isFinite(v)||vmax)fail(`数值范围 ${min}–${max}`);return v} -function color(v:unknown){if(typeof v!=='string'||!/^#[a-f0-9]{6}$/i.test(v))fail('颜色使用 #RRGGBB');return v} -export function parameters(layout:Layout,input:unknown):Parameters{ - const raw=object(input);keys(raw,['texts','images','decorations','paper','layers','added']);const result:Parameters={texts:{},images:{},decorations:{}}; - const added:AddedSlot[]=[]; - if(raw.added!==undefined){if(!Array.isArray(raw.added)||raw.added.length>100)fail('最多新增100个元素');for(const entry of raw.added){const v=object(entry);keys(v,['id','kind','role','box','direction','text','reading','shape']);if(typeof v.id!=='string'||!/^user-[a-zA-Z0-9-]{1,60}$/.test(v.id)||added.some(s=>s.id===v.id))fail('新增元素 ID 无效或重复');if(!['text','image','decoration'].includes(String(v.kind)))fail('新增元素类型无效');if(typeof v.role!=='string'||v.role.length>80)fail('图层名称最多80字');if(!Array.isArray(v.box)||v.box.length!==4)fail('box 为 x,y,width,height');const box=v.box.map((n,i)=>number(n,i<2?-10000:.1,10000));if(v.direction!=='horizontal'||v.reading!=='custom'||(v.text!==null&&(typeof v.text!=='string'||v.text.length>500)))fail('新增元素内容无效');if(v.shape!==undefined&&!['rectangle','ellipse'].includes(String(v.shape)))fail('无效形状');added.push({id:v.id,kind:String(v.kind),role:v.role,box,direction:'horizontal',text:v.text as string|null,reading:'custom',...(v.shape?{shape:v.shape as AddedSlot['shape']}:{})})}result.added=added} - const elements=[...layout.elements,...added]; - if(raw.layers!==undefined){const entries=object(raw.layers);keys(entries,elements.map(e=>e.id));result.layers={};for(const [id,entry] of Object.entries(entries)){const v=object(entry);keys(v,['x','y','width','height','rotation','opacity','radius','z','hidden','locked']);const layer:Record={};for(const [key,value] of Object.entries(v)){if(key==='hidden'||key==='locked'){if(typeof value!=='boolean')fail('图层开关必须为布尔值');layer[key]=value}else{const range:Record={x:[-10000,10000],y:[-10000,10000],width:[.1,10000],height:[.1,10000],rotation:[-360,360],opacity:[0,1],radius:[0,5000],z:[-1000,1000]};layer[key]=number(value,range[key][0],range[key][1])}}result.layers[id]=layer}} - if(raw.paper!==undefined)result.paper=color(raw.paper); - for(const [group,kind] of [['texts','text'],['images','image'],['decorations','decoration']] as const){ - const entries=object(raw[group]??{});keys(entries,elements.filter(e=>e.kind===kind).map(e=>e.id)); - for(const [id,value] of Object.entries(entries)){const v=object(value); - if(group==='texts'){keys(v,['value','color','scale','align','fontFamily','fontSize','fontWeight','lineHeight','letterSpacing']);if(typeof v.value!=='string'||Array.from(v.value).length>500||v.value.split('\n').length>8)fail('文字最多 500 字 / 8 行');const t:TextValue={value:v.value};if(v.color!==undefined)t.color=color(v.color);if(v.scale!==undefined){if(typeof v.scale!=='number'||!Number.isFinite(v.scale)||v.scale<.25||v.scale>2)fail('scale 范围 0.25–2');t.scale=v.scale}if(v.align!==undefined){if(!['start','middle','end'].includes(String(v.align)))fail('无效 align');t.align=v.align as TextValue['align']}if(v.fontFamily!==undefined){if(!['sans','serif','mono'].includes(String(v.fontFamily)))fail('无效字体');t.fontFamily=v.fontFamily as TextValue['fontFamily']}for(const [key,min,max] of [['fontSize',.1,1000],['fontWeight',100,900],['lineHeight',.5,4],['letterSpacing',-20,100]] as const)if(v[key]!==undefined)t[key]=number(v[key],min,max);result.texts[id]=t; - }else if(group==='images'){keys(v,['assetId','color','fit','cropX','cropY','zoom']);const image:ImageValue={};if(v.assetId!==undefined){if(!uuid(v.assetId))fail('assetId 必须为已上传图片 UUID');image.assetId=v.assetId}if(v.color!==undefined)image.color=color(v.color);if(v.fit!==undefined){if(v.fit!=='cover'&&v.fit!=='contain')fail('fit 使用 cover 或 contain');image.fit=v.fit}for(const [key,min,max] of [['cropX',0,100],['cropY',0,100],['zoom',1,5]] as const)if(v[key]!==undefined)image[key]=number(v[key],min,max);result.images[id]=image; - }else{keys(v,['color','hidden']);const d:DecorationValue={};if(v.color!==undefined)d.color=color(v.color);if(v.hidden!==undefined){if(typeof v.hidden!=='boolean')fail('hidden 必须为布尔值');d.hidden=v.hidden}result.decorations[id]=d} - } - } - return result; -} diff --git a/apps/studio/src/lib/series.ts b/apps/studio/src/lib/series.ts deleted file mode 100644 index 214d425..0000000 --- a/apps/studio/src/lib/series.ts +++ /dev/null @@ -1,42 +0,0 @@ -import {parameters,LayoutParameterError,type Layout,type Parameters,type TextValue,uuid} from './layout-parameters'; -export type Typography=Required>; -export type Series={version:1;brief:{topic:string;audience:string;intent:string;designIntent:string;imageStrategy:string};style:{version:string;palette:{paper:string;ink:string;muted:string;accent:string};typography:Record<'title'|'body'|'caption',Typography>;spacing:{margin:number;gap:number}};sources:{assetId:string;author:string;license:string;url:string;prompt:string;tool:string}[];pages:{key:string;role:'cover'|'body'|'ending';layoutId:string;intent:string;bindings:Record;parameters:Parameters}[]}; -function fail(message:string):never{throw new LayoutParameterError(message)} -const obj=(v:unknown)=>{if(!v||typeof v!=='object'||Array.isArray(v))return fail('作品字段必须是对象');return v as Record}; -function exact(v:unknown,allowed:string[]){const o=obj(v);if(Object.keys(o).some(k=>!allowed.includes(k)))fail('作品包含未知字段');return o} -function str(v:unknown,max=1000){if(typeof v!=='string'||!v.trim()||v.length>max)fail('作品文字缺失或超长');return v as string} -function num(v:unknown,min:number,max:number){if(typeof v!=='number'||!Number.isFinite(v)||vmax)fail('作品数值超出范围');return v as number} -/** Closed, bounded content document. The same validator runs in persistence and SSR. */ -export function series(input:unknown,layouts:Layout[]):Series{ - const raw=exact(input,['version','brief','style','sources','pages']);if(raw.version!==1)fail('不支持的作品版本'); - const brief=exact(raw.brief,['topic','audience','intent','designIntent','imageStrategy']);for(const k of ['topic','audience','intent','designIntent','imageStrategy'])str(brief[k],2000); - const style=exact(raw.style,['version','palette','typography','spacing']);str(style.version,80); - const palette=exact(style.palette,['paper','ink','muted','accent']);for(const k of ['paper','ink','muted','accent'])if(!/^#[a-f\d]{6}$/i.test(String(palette[k])))fail('作品颜色使用 #RRGGBB'); - const typography=exact(style.typography,['title','body','caption']); - for(const role of ['title','body','caption']){const t=exact(typography[role],['fontFamily','fontSize','fontWeight','lineHeight','letterSpacing']);if(!['sans','serif','mono'].includes(String(t.fontFamily)))fail('无效作品字体');num(t.fontSize,.1,40);num(t.fontWeight,100,900);num(t.lineHeight,.5,4);num(t.letterSpacing,-5,10)} - const spacing=exact(style.spacing,['margin','gap']);num(spacing.margin,4,16);num(spacing.gap,1,10);if(Number(spacing.margin)*2+Number(spacing.gap)*4>48)fail('页边距与间距组合过大'); - if(!Array.isArray(raw.sources)||raw.sources.length>100)fail('素材来源最多100条'); - const sources=raw.sources.map(v=>{const s=exact(v,['assetId','author','license','url','prompt','tool']);if(!uuid(str(s.assetId,36)))fail('来源需素材 UUID');for(const k of ['author','license','url','prompt','tool'])str(s[k],4000);if(s.url!=='generated'&&!/^https?:\/\//.test(String(s.url)))fail('来源使用 http(s) 地址或 generated');return s as Series['sources'][number]}); - if(!Array.isArray(raw.pages)||raw.pages.length<3||raw.pages.length>18)fail('作品需要3–18页'); - const seen=new Set(); - const pages=raw.pages.map((entry,index)=>{const p=exact(entry,['key','role','layoutId','intent','bindings','parameters']);const key=str(p.key,60);if(!/^[a-z0-9-]+$/.test(key)||seen.has(key))fail('页面 key 必须唯一');seen.add(key); - const role=index===0?'cover':index===(raw.pages as unknown[]).length-1?'ending':'body';if(p.role!==role)fail('页序须为封面、正文、结尾');str(p.intent,1000);const layout=layouts.find(l=>l.id===p.layoutId);if(!layout)fail('作品模板不存在');const resolvedLayout=layout!;const params=parameters(resolvedLayout,p.parameters);const bindings=obj(p.bindings);const slots=[...resolvedLayout.elements,...(params.added||[])];for(const [id,token] of Object.entries(bindings)){if(!slots.some(s=>s.id===id&&s.kind==='text')||!['title','body','caption'].includes(String(token)))fail('字体绑定无效');if(!params.texts[id])fail('绑定文字必须提供文案')} - // Every visible text is authored; template placeholder copy must never leak into a finished series. - for(const s of slots)if(s.kind==='text'&&!params.layers?.[s.id]?.hidden&&!params.texts[s.id])fail('请填写或隐藏所有文字元素'); - for(const image of Object.values(params.images))if(image.assetId&&!sources.some(s=>s.assetId===image.assetId))fail('每张图片需记录作者、许可和生成过程'); - return {...p,parameters:params,bindings} as Series['pages'][number]}); - return {version:1,brief:brief as Series['brief'],style:style as Series['style'],sources,pages}; -} -/** Style units use a 120-wide reference canvas; explicit element values always win. */ -export function pageParameters(doc:Series,page:Series['pages'][number],layout:Layout):Parameters{ - const p=page.parameters,s=doc.style,k=layout.width/120; - const result:Parameters={...p,paper:p.paper||s.palette.paper,texts:{...p.texts},images:{...p.images},decorations:{...p.decorations},layers:{...p.layers}}; - for(const [id,token] of Object.entries(page.bindings)){const t=s.typography[token];result.texts[id]={...t,fontSize:t.fontSize*k,letterSpacing:t.letterSpacing*k,color:token==='caption'?s.palette.muted:s.palette.ink,...p.texts[id]}} - for(const slot of [...layout.elements,...(p.added||[])]){if(slot.kind==='image')result.images[slot.id]={color:s.palette.accent,...p.images[slot.id]};if(slot.kind==='decoration')result.decorations[slot.id]={color:s.palette.accent,...p.decorations[slot.id]}} - if(layout.id==='standard-01'){ - const m=s.spacing.margin,g=s.spacing.gap,w=120-2*m; - const boxes:Record={T03:[m,m,w,4],T01:[m,m+4+g,w,28],I01:[m,m+32+2*g,w,50],T02:[m,m+82+3*g,w,27],D01:[m,160-m,w,.4]}; - for(const [id,[x,y,width,height]] of Object.entries(boxes))result.layers![id]={x,y,width,height,...p.layers?.[id]}; - } - return result; -} diff --git a/apps/studio/src/pages/_app.tsx b/apps/studio/src/pages/_app.tsx deleted file mode 100644 index 680063e..0000000 --- a/apps/studio/src/pages/_app.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useEffect } from 'react'; -import { useRouter } from 'next/router'; -import type { AppProps } from "next/app"; -import "../styles/globals.css"; -import "../styles/brand.css"; -export default function App({Component,pageProps}:AppProps){const router = useRouter(); - useEffect(() => { const scope = /^\/(?:graphic|infinite_canvas|layouts|template-layout)(?:\/|$)/.test(router.pathname) ? 'canvas' : 'home'; document.documentElement.dataset.theme = `${scope}-${document.documentElement.dataset.mode === 'dark' ? 'dark' : 'light'}`; }, [router.pathname]); - return ;} diff --git a/apps/studio/src/pages/_document.tsx b/apps/studio/src/pages/_document.tsx deleted file mode 100644 index 68e90da..0000000 --- a/apps/studio/src/pages/_document.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { Html, Head, Main, NextScript } from 'next/document'; -const initializeTheme = String.raw`(function(){try{var mode=localStorage.getItem('endoleap.theme')==='dark'?'dark':'light';var canvas=/^\/(?:studio\/)?(?:graphic|infinite_canvas|layouts|template-layout)(?:\/|$)/.test(location.pathname);document.documentElement.dataset.mode=mode;document.documentElement.dataset.theme=(canvas?'canvas':'home')+'-'+mode;}catch(e){}})();`; -export default function Document() { return ',x:20,y:20,w:300,h:100,fill:'#123456',size:32,hidden:false,locked:false}; -const doc={name:'作品',background:'#ffffff',items:[item]}; -test('graphic backup restores editable text and ignores unrelated properties',()=>{assert.deepEqual(parseGraphicDocument(JSON.stringify({...doc,extra:'ignored'})),doc)}); -test('graphic backup rejects corrupted layers, duplicate IDs and unbounded geometry',()=>{for(const items of [[{...item,text:null}],[item,item],[{...item,x:Infinity}],[{...item,w:-1}],[{...item,fill:'url(https://example.com)'}]])assert.throws(()=>parseGraphicDocument(JSON.stringify({...doc,items})));}); -test('graphic backup disallows active SVG and remote image loads',()=>{for(const src of ['https://example.com/image.png','javascript:alert(1)','data:image/svg+xml;base64,PHN2Zz4='])assert.throws(()=>parseGraphicDocument(JSON.stringify({...doc,items:[{...item,kind:'image',src}]})));}); diff --git a/apps/studio/tests/layout-images.test.mjs b/apps/studio/tests/layout-images.test.mjs deleted file mode 100644 index a091b3f..0000000 --- a/apps/studio/tests/layout-images.test.mjs +++ /dev/null @@ -1,27 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {embedSvgMedia} from '../src/lib/layout-images.ts'; -const id='a05dc841-240a-5a3a-b01b-d589fc21cd36'; -test('SVG export fetches each internal media once with the caller authentication',async()=>{ - const original=globalThis.fetch;let calls=0; - try{ - globalThis.fetch=async(url,options)=>{calls++;assert.equal(url,'http://waterfall-wiki:3000/api/media/'+id);assert.equal(options.headers.cookie,'session=fixture');assert.equal(options.redirect,'error');return new Response(Buffer.from('fixture'),{headers:{'content-type':'image/png'}});}; - const svg=await embedSvgMedia({headers:{cookie:'session=fixture'}},``); - assert.equal(calls,1);assert.equal((svg.match(/data:image\/png;base64,Zml4dHVyZQ==/g)||[]).length,2);assert.ok(!svg.includes('/api/media/')); - }finally{globalThis.fetch=original;} -}); -test('SVG export rejects unavailable or non-image objects',async()=>{ - const original=globalThis.fetch; - try{for(const response of [new Response('missing',{status:404}),new Response('',{headers:{'content-type':'image/svg+xml'}})]){ - globalThis.fetch=async()=>response;await assert.rejects(()=>embedSvgMedia({headers:{}},``),/模板图片不可访问/); - }}finally{globalThis.fetch=original;} -}); -test('SVG exports without managed images do not access storage',async()=>{ - assert.equal(await embedSvgMedia({headers:{}},'Title'),'Title'); -}); - -test('SVG export uses the configured isolated Wiki origin',async()=>{ - const original=globalThis.fetch,previous=process.env.STUDIO_WIKI_ORIGIN; - try{process.env.STUDIO_WIKI_ORIGIN='http://127.0.0.1:19000';globalThis.fetch=async url=>{assert.equal(url,'http://127.0.0.1:19000/api/media/'+id);return new Response('fixture',{headers:{'content-type':'image/png'}})};assert.ok((await embedSvgMedia({headers:{}},``)).includes('data:image/png;base64,'));} - finally{globalThis.fetch=original;if(previous===undefined)delete process.env.STUDIO_WIKI_ORIGIN;else process.env.STUDIO_WIKI_ORIGIN=previous;} -}); diff --git a/apps/studio/tests/layout-parameters.test.mjs b/apps/studio/tests/layout-parameters.test.mjs deleted file mode 100644 index 3185fba..0000000 --- a/apps/studio/tests/layout-parameters.test.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import {test} from 'node:test';import assert from 'node:assert/strict';import {readFileSync} from 'node:fs'; -import {parameters} from '../src/lib/layout-parameters.ts'; -const layouts=JSON.parse(readFileSync(new URL('../src/content/templates/layouts.json',import.meta.url))); -test('27 layouts preserve unique typed source slots and complete shapes',()=>{assert.equal(layouts.length,27);for(const [i,l] of layouts.entries()){assert.equal(l.id,'ref-'+String(i+1).padStart(2,'0'));assert.equal(new Set(l.elements.map(s=>s.id)).size,l.elements.length);for(const s of l.elements){assert.match(s.id,/^[TID]\d{2}$/);assert.equal(s.kind,({T:'text',I:'image',D:'decoration'})[s.id[0]]);assert.ok(s.box[0]>=0&&s.box[1]>=0&&s.box[0]+s.box[2]<=l.width&&s.box[1]+s.box[3]<=l.height);assert.match(s.raster,/^\/api\/media\/[a-f0-9-]{36}$/);if(s.kind==='image')assert.match(s.shape,/^\/api\/media\/[a-f0-9-]{36}$/)}assert.deepEqual(parameters(l,{}),{texts:{},images:{},decorations:{}})}}); -test('parameters reject mismatched slots, unsafe URLs and excessive text',()=>{const l=layouts[0];for(const input of [{texts:{I01:{value:'bad'}}},{images:{I01:{url:'https://evil.test'}}},{texts:{T01:{value:'x',scale:3}}},{texts:{T01:{value:'x'.repeat(501)}}},{paper:'url(x)'},{decorations:{D01:{hidden:'true'}}},null,[]])assert.throws(()=>parameters(l,input));const p=parameters(l,{texts:{T01:{value:'',scale:1.2,align:'middle'}},images:{I01:{color:'#abcdef',fit:'contain'}},decorations:{D01:{hidden:true}}});assert.equal(p.texts.T01.value,'');assert.equal(p.decorations.D01.hidden,true)}); -test('workspace parameters round-trip identically through Studio and Wiki validation',async()=>{const {parameters:wiki}=await import('../../waterfall-wiki/src/lib/layout-parameters.ts');for(const l of layouts){const added={id:'user-example',kind:'text',role:'Added text',box:[3,4,20,12],direction:'horizontal',text:'Hello',reading:'custom'};const input={added:[added],layers:{T01:{x:2,y:4,width:15,height:10,rotation:25,opacity:.7,radius:2,z:10,hidden:false,locked:true},'user-example':{x:8,z:20}},texts:{T01:{value:'Updated',fontFamily:'serif',fontSize:4,fontWeight:700,lineHeight:1.6,letterSpacing:.2},'user-example':{value:'Hello'}},images:{I01:{fit:'cover',cropX:15,cropY:85,zoom:2}}};assert.deepEqual(parameters(l,input),wiki(l,input));assert.deepEqual(parameters(l,parameters(l,input)),parameters(l,input))}}); -test('workspace validation rejects unbounded, mismatched and executable properties',()=>{const l=layouts[0];for(const input of [{layers:{unknown:{x:2}}},{layers:{T01:{rotation:Infinity}}},{layers:{T01:{width:0}}},{layers:{T01:{opacity:2}}},{layers:{T01:{locked:'yes'}}},{texts:{T01:{value:'x',fontFamily:'url(evil)'}}},{images:{I01:{cropX:101}}},{images:{I01:{zoom:0}}},{added:[{id:'T01'}]},{added:Array(101).fill({})},{layers:{T01:{onClick:'evil'}}}])assert.throws(()=>parameters(l,input))}); diff --git a/apps/studio/tests/native-layout.test.mjs b/apps/studio/tests/native-layout.test.mjs deleted file mode 100644 index b4e3f29..0000000 --- a/apps/studio/tests/native-layout.test.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {readFileSync} from 'node:fs'; -import {nativeLayout,defaultText,textSize,imageShape,designUrl,newElementId} from '../src/lib/native-layout.ts'; -const layouts=JSON.parse(readFileSync(new URL('../src/content/templates/layouts.json',import.meta.url))); -test('all 27 native pages are independent of source images and supply actual text',()=>{ - for(const original of layouts){const layout=nativeLayout(original),serialized=JSON.stringify(layout);assert.ok(!serialized.includes('data:image'));assert.ok(!serialized.includes('raster'));assert.ok(!serialized.includes('source'));assert.ok(serialized.length<7000); - for(const slot of layout.elements.filter(s=>s.kind==='text')){assert.ok(defaultText(layout,slot).length);for(const text of [defaultText(layout,slot),'','新的文字\n第二行','']){const size=textSize(layout,slot,text);assert.ok(Number.isFinite(size)&&size>0)}} - } -}); -test('geometric templates retain their distinct silhouettes without bitmap masks',()=>{ - assert.equal(imageShape(layouts[4],layouts[4].elements.find(s=>s.id==='I03')).borderRadius,'50%'); - assert.ok(imageShape(layouts[21],layouts[21].elements.find(s=>s.id==='I02')).clipPath.startsWith('polygon(')); - assert.notEqual(imageShape(layouts[21],layouts[21].elements.find(s=>s.id==='I02')).clipPath,imageShape(layouts[26],layouts[26].elements.find(s=>s.id==='I02')).clipPath); - assert.equal(designUrl('ref-01','abc'),'/studio/template-layout?id=abc'); - assert.equal(designUrl('standard-01','abc'),'/studio/template-layout?id=abc'); -}); - -test('element IDs work on HTTP origins without secure-context randomUUID',()=>{const saved=Object.getOwnPropertyDescriptor(globalThis,'crypto');let calls=0;Object.defineProperty(globalThis,'crypto',{configurable:true,value:{getRandomValues(array){for(let i=0;i{const d=series(input.document,[layout]);assert.equal(d.pages.length,3);for(const page of d.pages){const p=pageParameters(d,page,layout);assert.equal(p.paper,d.style.palette.paper);assert.equal(p.texts.T01.fontFamily,'serif');assert.equal(p.texts.T01.value,page.parameters.texts.T01.value);assert.equal(p.layers.T01.x,10)}const change=structuredClone(d);change.style.palette.ink='#112233';change.style.spacing.margin=8;for(const page of change.pages){const p=pageParameters(change,page,layout);assert.equal(p.texts.T02.color,'#112233');assert.equal(p.layers.T01.x,8)}change.pages[1].parameters.texts.T01.color='#ABCDEF';assert.equal(pageParameters(change,change.pages[1],layout).texts.T01.color,'#ABCDEF')}); -test('invalid page order, stale template placeholders, untracked images and unsafe fields fail',()=>{for(const mutate of [d=>d.pages.reverse(),d=>d.pages[1].key=d.pages[0].key,d=>delete d.pages[0].parameters.texts.T02,d=>d.pages[0].parameters.images.I01.assetId='f76134e7-6e4f-4261-b90c-3c523dc16307',d=>d.style.palette.ink='url(evil)',d=>d.style.spacing.gap=10,d=>d.pages[0].bindings.I01='title',d=>d.pages[0].parameters.images.I01.url='https://evil.test']){const d=structuredClone(input.document);mutate(d);assert.throws(()=>series(d,[layout]))}}); -test('persistence and renderer use byte-identical series contracts',()=>assert.equal(readFileSync(new URL('../src/lib/series.ts',import.meta.url),'utf8'),readFileSync(new URL('../../waterfall-wiki/src/lib/series.ts',import.meta.url),'utf8'))); diff --git a/apps/studio/tsconfig.json b/apps/studio/tsconfig.json deleted file mode 100644 index 6d66853..0000000 --- a/apps/studio/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react-jsx", - "incremental": true, - "baseUrl": ".", - "paths": { "@/*": ["./src/*"] } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/apps/waterfall-wiki/.env.example b/apps/waterfall-wiki/.env.example index e55e056..ad914bb 100644 --- a/apps/waterfall-wiki/.env.example +++ b/apps/waterfall-wiki/.env.example @@ -1,22 +1,18 @@ -# Replace all three placeholders before starting Docker Compose. -POSTGRES_PASSWORD=replace-with-a-long-random-password -S3_ACCESS_KEY=replace-with-a-unique-access-key -S3_SECRET_KEY=replace-with-a-long-random-secret +# Export to the web, storage bootstrap and worker processes. +DATABASE_URL=postgresql://endoleap:replace-me@127.0.0.1:5432/endoleap +S3_ENDPOINT=http://127.0.0.1:9000 +S3_ACCESS_KEY=replace-me +S3_SECRET_KEY=replace-me S3_BUCKET=prompt -WEB_BIND_ADDRESS=0.0.0.0 -WEB_PORT=18131 -PG_PORT=18132 -S3_PORT=18133 -PROMPT_IMAGE=prompt:local -RELEASE_SHA=development - -# Use HTTPS and secure cookies in production; HTTP acceptance requires explicit false. +PORT=3000 +AUTH_CONFIG_FILE=/path/to/private/auth.json AUTH_COOKIE_SECURE=true -ACCOUNT_PUBLIC_ORIGIN=http://8.211.154.134:18131 -# Enable only behind an ingress that overwrites x-forwarded-for. +ACCOUNT_PUBLIC_ORIGIN=https://endoleap.example ACCOUNT_TRUST_PROXY=false -SMTP_HOST=smtpdm.aliyun.com + +# Email verification and recovery +SMTP_HOST= SMTP_PORT=465 -SMTP_USERNAME=noreply@notice.entroia.com -SMTP_FROM=noreply@notice.entroia.com +SMTP_USERNAME= +SMTP_FROM= SMTP_PASSWORD= diff --git a/apps/waterfall-wiki/Dockerfile b/apps/waterfall-wiki/Dockerfile deleted file mode 100644 index 0d77b6a..0000000 --- a/apps/waterfall-wiki/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -FROM node:24-bookworm-slim AS base -ENV NEXT_TELEMETRY_DISABLED=1 -WORKDIR /app -RUN apt-get update \ - && apt-get install -y --no-install-recommends ffmpeg ca-certificates chromium fonts-noto-cjk \ - && rm -rf /var/lib/apt/lists/* - -FROM base AS dependencies -WORKDIR /repo/apps/waterfall-wiki -RUN corepack enable && corepack prepare pnpm@11.25.0 --activate -COPY apps/waterfall-wiki/pnpm-lock.yaml ./ -COPY pnpm-workspace.yaml /repo/pnpm-workspace.yaml -COPY packages/brand/ /repo/packages/brand/ -COPY apps/waterfall-wiki/package.json ./ -RUN pnpm --filter @prompt/waterfall-wiki install --frozen-lockfile --fetch-timeout=300000 --network-concurrency=8 - -FROM dependencies AS build -# .dockerignore excludes docs and operational notes without extra legacy COPY snapshots. -COPY apps/waterfall-wiki/ . -RUN pnpm typecheck && pnpm test && pnpm build && pnpm test:smoke - -FROM build AS integration -USER node -CMD ["npm", "run", "test:integration"] - -FROM base AS runtime -WORKDIR /app/apps/waterfall-wiki -ENV NODE_ENV=production HOSTNAME=0.0.0.0 PORT=3000 \ - FFMPEG_PATH=/usr/bin/ffmpeg FFPROBE_PATH=/usr/bin/ffprobe -COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/.next/standalone/ /app/ -COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/dist/worker.cjs /repo/apps/waterfall-wiki/dist/bootstrap.cjs /repo/apps/waterfall-wiki/dist/publish-learning-series.cjs /repo/apps/waterfall-wiki/dist/auth-cli.cjs ./ -COPY --from=build --chown=node:node /repo/apps/waterfall-wiki/db/ ./db/ -COPY --chown=node:node apps/waterfall-wiki/scripts/container-health.mjs ./ -COPY --chown=node:node apps/waterfall-wiki/scripts/import-character-packs.mjs apps/waterfall-wiki/scripts/verify-character-pack.mjs ./scripts/ -COPY --chown=node:node apps/waterfall-wiki/seed/characters/ ./seed/characters/ -COPY --chown=node:node apps/waterfall-wiki/src/content/managed-media.json ./src/content/managed-media.json -USER node -EXPOSE 3000 -CMD ["node", "server.js"] diff --git a/apps/waterfall-wiki/fixtures/autumn-journal.json b/apps/waterfall-wiki/fixtures/autumn-journal.json deleted file mode 100644 index 9d02696..0000000 --- a/apps/waterfall-wiki/fixtures/autumn-journal.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "schemaVersion": 2, - "name": "把秋天装进一页 · 银杏观察手记", - "document": { - "version": 1, - "brief": { - "topic": "银杏与秋日观察手记", - "audience": "喜欢日常摄影与视觉手帐的小红书读者", - "intent": "以封面、观察方法和行动结尾组成三页可发布图文", - "designIntent": "奶油纸底、森林墨色、银杏金点缀;宋体大标题与克制的无衬线正文;所有页面保留相同页边距和节奏", - "imageStrategy": "生成一幅原创银杏静物插画作为系列母图;每页复用素材,保留裁切配置,不把正文烘焙进图片" - }, - "style": { - "version": "autumn-journal/1", - "palette": { - "paper": "#F3EFE5", - "ink": "#253C32", - "muted": "#718074", - "accent": "#C3A050" - }, - "typography": { - "title": { - "fontFamily": "serif", - "fontSize": 8.4, - "fontWeight": 600, - "lineHeight": 1.15, - "letterSpacing": 0 - }, - "body": { - "fontFamily": "sans", - "fontSize": 3.1, - "fontWeight": 400, - "lineHeight": 1.65, - "letterSpacing": 0 - }, - "caption": { - "fontFamily": "sans", - "fontSize": 1.7, - "fontWeight": 500, - "lineHeight": 1.2, - "letterSpacing": 0 - } - }, - "spacing": { - "margin": 10, - "gap": 4 - } - }, - "sources": [], - "pages": [ - { - "key": "cover", - "role": "cover", - "layoutId": "standard-01", - "intent": "用银杏静物与两行大标题建立主题", - "bindings": { - "T01": "title", - "T02": "body", - "T03": "caption" - }, - "parameters": { - "texts": { - "T01": { - "value": "把秋天\n装进一页" - }, - "T02": { - "value": "不必走很远。\n一片银杏,就是今天的风景。" - }, - "T03": { - "value": "SEASONAL NOTES / 01" - } - }, - "images": { - "I01": { - "fit": "cover", - "cropX": 50, - "cropY": 50, - "zoom": 1 - } - }, - "decorations": {} - } - }, - { - "key": "observe", - "role": "body", - "layoutId": "standard-01", - "intent": "用三个观察动作提供可实践的方法", - "bindings": { - "T01": "title", - "T02": "body", - "T03": "caption" - }, - "parameters": { - "texts": { - "T01": { - "value": "先看见\n再记录" - }, - "T02": { - "value": "看叶脉,看光落下的方向。\n留住一片金黄,也留出一半空白。\n把感受写成短句,画面就有了呼吸。" - }, - "T03": { - "value": "FIELD NOTES / 02" - } - }, - "images": { - "I01": { - "fit": "cover", - "cropX": 65, - "cropY": 40, - "zoom": 1.25 - } - }, - "decorations": {} - } - }, - { - "key": "ending", - "role": "ending", - "layoutId": "standard-01", - "intent": "以轻量行动和提问收尾", - "bindings": { - "T01": "title", - "T02": "body", - "T03": "caption" - }, - "parameters": { - "texts": { - "T01": { - "value": "留一点白\n给下一次", - "align": "middle" - }, - "T02": { - "value": "今天,捡起一片叶子。\n用三个词记录它的颜色、纹理与心情。\n你的秋天,会从哪一页开始?", - "align": "middle" - }, - "T03": { - "value": "YOUR TURN / 03" - } - }, - "images": { - "I01": { - "fit": "cover", - "cropX": 65, - "cropY": 50, - "zoom": 1 - } - }, - "decorations": {}, - "layers": { - "I01": { - "x": 40, - "y": 55, - "width": 40, - "height": 40, - "radius": 20 - } - } - } - } - ] - } -} diff --git a/apps/waterfall-wiki/next.config.ts b/apps/waterfall-wiki/next.config.ts index 21ddc67..242011f 100644 --- a/apps/waterfall-wiki/next.config.ts +++ b/apps/waterfall-wiki/next.config.ts @@ -12,9 +12,10 @@ const nextConfig: NextConfig = { ]}, async rewrites(){return [ ...Object.entries(managedMedia).filter(([path])=>path.startsWith("apps/waterfall-wiki/public/")).map(([path,asset])=>({source:"/"+path.slice("apps/waterfall-wiki/public/".length),destination:"/api/media/"+asset.id})), - {source:'/graphic',destination:'http://prompt-studio:3000/studio/graphic'}, - {source:'/infinite_canvas',destination:'http://prompt-studio:3000/studio/infinite_canvas'}, - {source:"/studio/:path*",destination:"http://prompt-studio:3000/studio/:path*"}, + {source:"/studio/api/:path*",destination:"/api/studio-ui/:path*"}, + {source:"/studio/brand/:path*",destination:"/brand/:path*"}, + {source:"/studio/favicon.ico",destination:"/favicon.ico"}, + {source:"/studio/apple-touch-icon.png",destination:"/apple-touch-icon.png"}, {source:"/index.md",destination:"/api/agent-index"}, {source:"/skills/index.md",destination:"/api/guidance/skills"}, {source:"/skills/:path+/index.md",destination:"/api/guidance/skills/:path+"}, @@ -22,8 +23,6 @@ const nextConfig: NextConfig = { {source:"/agent/index.md",destination:"/api/agent-index"}, {source:"/skills/:skill/:doc.md",destination:"/api/skill-docs/:skill/:doc"}, ]}, - output: "standalone", outputFileTracingRoot: resolve(process.cwd(), "../.."), - outputFileTracingIncludes: {"/*":["./node_modules/playwright-core/**/*"]}, poweredByHeader: false, }; diff --git a/apps/waterfall-wiki/package.json b/apps/waterfall-wiki/package.json index 6cff118..b9c41a6 100644 --- a/apps/waterfall-wiki/package.json +++ b/apps/waterfall-wiki/package.json @@ -7,15 +7,11 @@ }, "scripts": { "dev": "pnpm brand:prepare && next dev", - "build": "pnpm brand:prepare && next build && esbuild src/worker.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/worker.cjs && esbuild scripts/bootstrap.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/bootstrap.cjs && esbuild scripts/publish-learning-series.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/publish-learning-series.cjs && esbuild scripts/auth-cli.ts --bundle --platform=node --format=cjs --external:pg-native --outfile=dist/auth-cli.cjs && node scripts/prepare-standalone.mjs", + "build": "pnpm brand:prepare && next build && esbuild src/worker.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/worker.cjs && esbuild scripts/bootstrap.ts --bundle --platform=node --format=cjs --external:pg-native --external:playwright-core --outfile=dist/bootstrap.cjs && esbuild scripts/auth-cli.ts --bundle --platform=node --format=cjs --external:pg-native --outfile=dist/auth-cli.cjs", "start": "next start", "typecheck": "next typegen && tsc --noEmit", - "test": "tsx --test tests/api-documentation.test.ts tests/health.test.ts tests/model.test.ts tests/learning-series.test.ts tests/skill-docs.test.ts tests/wiki.test.ts tests/explore.test.ts tests/preview.test.ts tests/note-markdown.test.tsx tests/api-publication.test.ts tests/discovery.test.ts tests/auth.test.ts tests/account.test.ts tests/guidance.test.ts tests/guidance-publishing.test.ts", - "test:smoke": "node scripts/smoke.mjs", "storage:prepare": "tsx scripts/bootstrap.ts", - "content:publish-learning": "tsx scripts/publish-learning-series.ts", "worker": "tsx src/worker.ts", - "test:integration": "tsx --test tests/integration.test.ts", "brand:prepare": "node ../../packages/brand/scripts/prepare-app.mjs" }, "dependencies": { diff --git a/apps/waterfall-wiki/scripts/container-health.mjs b/apps/waterfall-wiki/scripts/container-health.mjs deleted file mode 100644 index 4518ee1..0000000 --- a/apps/waterfall-wiki/scripts/container-health.mjs +++ /dev/null @@ -1,9 +0,0 @@ -try { - const port = process.env.PORT || '3000'; - const [health, ready] = await Promise.all([ - fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(5000) }), - fetch(`http://127.0.0.1:${port}/api/ready`, { signal: AbortSignal.timeout(5000) }), - ]); - const data = await health.json(); - if (!health.ok || !ready.ok || data.service !== 'prompt' || data.release !== (process.env.RELEASE_SHA || 'development')) process.exit(1); -} catch { process.exit(1); } diff --git a/apps/waterfall-wiki/scripts/import-character-packs.mjs b/apps/waterfall-wiki/scripts/import-character-packs.mjs deleted file mode 100644 index f78bced..0000000 --- a/apps/waterfall-wiki/scripts/import-character-packs.mjs +++ /dev/null @@ -1,38 +0,0 @@ -import {readFile} from 'node:fs/promises'; -import {fileURLToPath} from 'node:url'; -import {join,dirname} from 'node:path'; -import {createHash} from 'node:crypto'; -import {requiredPackImages} from './verify-character-pack.mjs'; -const root=join(dirname(fileURLToPath(import.meta.url)),'..','seed','characters'); -const base=process.env.PROMPT_IMPORT_BASE_URL||'http://127.0.0.1:3000'; -const media=JSON.parse(await readFile(join(root,'..','..','src','content','managed-media.json'),'utf8')); -const packFiles=pack=>Object.entries(media).filter(([name])=>name.startsWith(`apps/waterfall-wiki/seed/characters/${pack.directory}/`)).sort(([a],[b])=>a.localeCompare(b)); -const packs=JSON.parse(await readFile(join(root,'packs.json'),'utf8')); -async function verify(pack){ - let cursor='',post;const seen=new Set(); - do{const response=await fetch(base+'/api/feed?'+new URLSearchParams({section:'images',...(cursor?{cursor}:{})}));if(!response.ok)throw Error('Cannot verify character feed: '+response.status);const page=await response.json();post=page.posts.find(p=>p.slug===pack.slug);cursor=page.nextCursor||'';if(cursor&&seen.has(cursor))throw Error('Character feed cursor did not advance');seen.add(cursor);}while(!post&&cursor); - if(!post)throw Error('Character pack missing: '+pack.slug); - const files=packFiles(pack);if(files.length!==pack.count)throw Error('Seed file count mismatch: '+pack.slug); - const hashes=files.map(([,asset])=>asset.sha256); - const originals=requiredPackImages(hashes,post.assets.filter(a=>a.kind==='image')); - for(const image of originals){const media=await fetch(`${base}/api/media/${image.id}`,{method:'HEAD'});if(!media.ok||media.headers.get('content-type')!=='image/png')throw Error('Original PNG unavailable');} - console.log(`Verified: /p/${pack.slug} (${originals.length} original PNGs preserved; editorial covers allowed)`); -} -for(const pack of packs){ - const existing=await fetch(`${base}/p/${pack.slug}`); - if(existing.ok){console.log(`Already imported: /p/${pack.slug}`);await verify(pack);continue;} - if(existing.status!==404)throw new Error(`Cannot check pack ${pack.slug}: ${existing.status}`); - const files=packFiles(pack); - if(files.length!==pack.count)throw new Error(`Expected ${pack.count} PNGs for ${pack.slug}, got ${files.length}`); - const data=new FormData();data.set('title',pack.title);data.set('slug',pack.slug);data.set('description',pack.description); - for(const [name,asset] of files){ - const response=await fetch(`${base}/api/media/${asset.id}`);if(!response.ok)throw Error('Cannot read migrated original: '+name); - const bytes=Buffer.from(await response.arrayBuffer());if(bytes.length!==asset.bytes||createHash('sha256').update(bytes).digest('hex')!==asset.sha256)throw Error('Original checksum mismatch: '+name); - data.append('files',new Blob([bytes],{type:'image/png'}),name.split('/').pop()); - } - const response=await fetch(`${base}/api/skills/street-people`,{method:'POST',body:data}); - if(response.status!==201&&response.status!==409)throw new Error(`Import ${pack.slug}: ${response.status} ${await response.text()}`); - if(response.status===409&&!(await fetch(`${base}/p/${pack.slug}`)).ok)throw new Error(`Conflicting import ${pack.slug}`); - console.log(`Imported: /p/${pack.slug} (${files.length} transparent PNGs)`); - await verify(pack); -} diff --git a/apps/waterfall-wiki/scripts/prepare-standalone.mjs b/apps/waterfall-wiki/scripts/prepare-standalone.mjs deleted file mode 100644 index 376230b..0000000 --- a/apps/waterfall-wiki/scripts/prepare-standalone.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { cp,mkdir,access } from 'node:fs/promises'; -await mkdir('.next/standalone/apps/waterfall-wiki/.next',{recursive:true}); -await cp('.next/static','.next/standalone/apps/waterfall-wiki/.next/static',{recursive:true}); -try{await access('public');await cp('public','.next/standalone/apps/waterfall-wiki/public',{recursive:true});}catch(e){if(e.code!=='ENOENT')throw e;} diff --git a/apps/waterfall-wiki/scripts/publish-learning-series.ts b/apps/waterfall-wiki/scripts/publish-learning-series.ts deleted file mode 100644 index 580ee3a..0000000 --- a/apps/waterfall-wiki/scripts/publish-learning-series.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { db } from "../src/lib/db"; -import { publishLearningSeries } from "../src/lib/publish-learning-series"; -publishLearningSeries().then(result=>console.log(`Published learning series: ${result.published.join(", ")}; one shared cover: ${result.coverId}`)) - .catch(error=>{console.error("Learning series publication failed:",error instanceof Error?error.message:"Unknown error");process.exitCode=1;}) - .finally(()=>db().end()); diff --git a/apps/waterfall-wiki/scripts/smoke.mjs b/apps/waterfall-wiki/scripts/smoke.mjs deleted file mode 100644 index b9a73dc..0000000 --- a/apps/waterfall-wiki/scripts/smoke.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import {writeFile,unlink} from 'node:fs/promises'; -import sharp from 'sharp'; -import { spawn } from "node:child_process"; -import assert from "node:assert/strict"; -import { setTimeout } from "node:timers/promises"; -import { createServer } from "node:net"; -import { once } from "node:events"; - -async function availablePort() { - const server = createServer(); - server.listen(0, "127.0.0.1"); - await once(server, "listening"); - const port = server.address().port; - await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); - return String(port); -} - -const port = process.env.SMOKE_PORT || await availablePort(); -const release = "smoke-test"; -const previewFixture='.next/standalone/apps/waterfall-wiki/public/__smoke-preview.png'; -await writeFile(previewFixture,await sharp({create:{width:48,height:48,channels:3,background:'#eee'}}).png().toBuffer()); -const child = spawn(process.execPath, [".next/standalone/apps/waterfall-wiki/server.js"], { - // This loopback-only child checks retained resources without production credentials. - // Production keeps failing closed; the bypass requires both explicit test settings. - env: { ...process.env, AUTH_CONFIG_FILE: "", AUTH_DISABLED_FOR_TESTS: "true", PORT: port, HOSTNAME: "127.0.0.1", RELEASE_SHA: release }, - stdio: "inherit", -}); -try { - let ready = false; - for (let i = 0; i < 40; i++) { - if (child.exitCode !== null) throw new Error(`Server exited: ${child.exitCode}`); - try { - const response = await fetch(`http://127.0.0.1:${port}/api/health`); - assert.equal(response.status, 200); - assert.deepEqual(await response.json(), { status: "ok", service: "prompt", release }); - ready = true; - break; - } catch { await setTimeout(500); } - } - assert.ok(ready, "Standalone production server must be healthy"); - const response = await fetch(`http://127.0.0.1:${port}/api/health`, { method: "POST" }); - assert.equal(response.status, 405); - const indexResponse=await fetch(`http://127.0.0.1:${port}/api/wiki-index`); - assert.equal(indexResponse.status,200); - const indexText=await indexResponse.text(); - const id=indexText.match(/\/explore\/([a-f0-9]{24})/)?.[1];assert.ok(id); - const agent=await fetch(`http://127.0.0.1:${port}/explore/${id}?agent_mode=True`); - assert.equal(agent.status,200);assert.match(agent.headers.get('content-type'),/text\/plain/);const agentText=await agent.text();assert.match(agentText,/^# /);assert.doesNotMatch(agentText,/^\s*(?:r.text()); - for(const href of ['/graphic','/infinite_canvas','/account?next=/workspace'])assert.ok(home.includes(`href="${href}"`),href); - for(const [legacy,canonical] of [['/studio/graphic','/graphic'],['/studio/infinite_canvas','/infinite_canvas']]){ - const redirect=await fetch(`http://127.0.0.1:${port}${legacy}`,{redirect:'manual'}); - assert.equal(redirect.status,308);assert.equal(redirect.headers.get('location'),canonical); - } - console.log("Production smoke test passed, including landing entry points and canonical redirects"); -} finally { - await unlink(previewFixture); - if (child.exitCode === null && child.signalCode === null) { - const exited = once(child, "exit"); - child.kill("SIGTERM"); - await exited; - } -} diff --git a/apps/waterfall-wiki/scripts/verify-character-pack.mjs b/apps/waterfall-wiki/scripts/verify-character-pack.mjs deleted file mode 100644 index ab037a1..0000000 --- a/apps/waterfall-wiki/scripts/verify-character-pack.mjs +++ /dev/null @@ -1,6 +0,0 @@ -/** Extra editorial covers are allowed; every original seed image must remain attached. */ -export function requiredPackImages(hashes,images){ - const remaining=[...images],originals=[]; - for(const hash of hashes){const index=remaining.findIndex(image=>image.sha256===hash);if(index<0)throw Error('Original seed image missing from character pack');originals.push(...remaining.splice(index,1));} - return originals; -} diff --git a/apps/waterfall-wiki/src/lib/studio-design-documentation.ts b/apps/waterfall-wiki/src/lib/studio-design-documentation.ts index 71dd773..e332630 100644 --- a/apps/waterfall-wiki/src/lib/studio-design-documentation.ts +++ b/apps/waterfall-wiki/src/lib/studio-design-documentation.ts @@ -1,5 +1,4 @@ import type {NextApiRequest,NextApiResponse} from 'next'; -// Generated from content/studio-tools/layouts-api.md; keep the canonical Wiki guide identical. -export const designDocs="# Studio 排版 API · 标准图文与 27 套固定构图\n\n把本篇与图片生成文档一起交给 coding agent。agent 自行选择图片、配色、字体、构图与参数,通过 HTTP 保存渲染描述,得到可持续更新的原生 HTML/CSS 页面。数据库只保存模板关联、文字、素材引用和渲染参数,不保存截图或任意 HTML。\n\n[可选:查看模板示例](/studio) · [读取实时 API 契约](/api/studio/designs?agent_mode=True) · [照片入皮影](/explore/3c98946754c33309d73d5284) · [街头小人](/explore/4ce3283fd98bb03a8fb39a19)\n\n## agent 的交付流程\n\n1. 按下文读取模板 API,选择 layoutId,并获取该模板实际的 T/I/D 元素 ID。\n2. 上传图片取得 assetId,把图片、文字和参数提交给创建 API。\n3. 从创建响应读取 `design.id` 与 `design.url`,把完整结果链接交给用户,例如 `http://8.211.154.134:18131/studio/template-layout?id=实际返回的UUID`。\n4. 用户直接打开该链接看结果;修改内容继续调用 PATCH,同一 ID 与结果链接保持不变。\n\n不要编造 ID,也不要把模板预览链接当作最终结果。API 保存成功并返回真实 UUID 才算完成。以下文档足以通过 HTTP 完成流程,无需使用编辑器。\n\n## 多页作品:从创作 brief 到可发布文件(schemaVersion 2)\n\n多页作品由 coding agent 完成创作决策与图像生成,网站负责封闭参数验证、事务保存与原生渲染。无需模型服务、模型密钥或编辑器。先形成主题、读者、意图、逐页分镜,再生成需要的图片,最后通过 HTTP 保存整组作品。封面、正文、结尾共用一个真实 UUID、修订号和 Wiki post。\n\n### 完整内容与视觉模型\n\n创建 `POST /api/studio/series`,body 为 `{schemaVersion:2,name,document}`。读取/整体修订使用 `GET/PATCH /api/studio/series/{id}`;PATCH 额外提供最新 revision。最大512KB,未知字段会拒绝。document:\n\n- `version:1`:多页文档结构版本。\n- `brief`:topic、audience、intent、designIntent、imageStrategy,均为必填非空文字,最多2000字符。分别说明主题、读者、内容目标、完整设计描述和配图策略。\n- `style.version`:agent 指定的样式版本标识(最多80字符),如 `autumn-journal/1`。它用于记录创作版本;不锁定未来网站渲染器,精确交付请同时保留导出文件和 manifest。\n- `style.palette`:paper、ink、muted、accent,均为 #RRGGBB。\n- `style.typography`:title、body、caption 三种层级,每项包含 fontFamily(sans/serif/mono)、fontSize(0.1–40)、fontWeight(100–900)、lineHeight(0.5–4)、letterSpacing(-5–10)。字号和字距以120宽参考画板为单位,固定模板按画板宽度同比换算。\n- `style.spacing`:margin(4–16)、gap(1–10),并要求 `2*margin+4*gap<=48`。标准排版据此计算标题、图片、正文和分隔线的位置;固定模板保留原构图,用 layers 显式调整位置。\n- `sources`:最多100项,每项 assetId、author、license、url、prompt、tool 全部必填,文字最多4000字符。url 是 http(s) 来源地址或 `generated`。用户照片记录来源与授权;生成图记录完整提示词和工具,不把 AI 图写成摄影原作。所有被页面引用的图片必须有来源记录。\n- `pages`:本工作流支持3–18页(这是本服务的约束,不宣称是平台实时上限)。每页 key 唯一(小写字母/数字/连字符,最多60位)、role(首项 cover,中间 body,末项 ending)、layoutId、intent(最多1000字符)、bindings、parameters。parameters 继续使用下文封闭的单页参数契约。\n- `pages[].bindings`:把实际文字元素 ID 映射到 title/body/caption,例如 `{T01:\"title\",T02:\"body\",T03:\"caption\"}`。被绑定元素必须提供文案。共享字体、墨色和纸色会参与真实渲染;单元素显式值优先覆盖。所有可见文字必须提供或明确隐藏,避免默认占位文案泄漏。图片默认色与装饰默认色取 accent。\n\n标准排版可直接沿用 T01/T02/T03/I01/D01;固定排版先读对应模板 API,元素不能混用。agent 必须控制标题长度和正文密度,不能把“已保存”当成“已验收”。同组图片建议统一比例;导出器遇到混合比例会拒绝,agent 应改用同一画板并调整内容布局。\n\n### 一次保存,一条直接结果链接\n\n```sh\n# series.json 包含完整 document,示例文件见仓库 content/studio-examples/autumn-journal.json\ncurl -fsS -X POST \"$BASE/api/studio/series\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n --data-binary @series.json\n# 返回 {design}:schemaVersion=2、document、id、revision、url、postId 等\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/api/studio/series/$ID\"\n```\n\n创建响应的 `design.url` 仍是 `/studio/template-layout?id={UUID}`;直接展示全部页,无需选模板。`&page=2` 只显示第2页,非法页码返回404。页面是服务端原生文字与 CSS,图片走 /api/preview。创建与全部页验证、Wiki post 处于同一事务,任一页无效整组不保存。PATCH 整体替换 document,revision 冲突返回409;先重新读取并合并。单页 PATCH 接口会拒绝修改多页作品,避免只改封面导致组内不一致。可从 designs 列表发现作品,schemaVersion 区分1与2。\n\n归档 `DELETE /api/studio/series/{id}` body `{revision}`;恢复 `POST` 同一路径 body `{revision,archived:false}`。归档后结果页和导出计划返回404,API 仍能读取并恢复;参数、素材和 Wiki post 保留。更新复用原 Wiki post,手工编辑过的正文不被覆盖。\n\n### 高质量导出与验收\n\n`GET /api/studio/series/{id}/export` 返回固定顺序的导出计划:id、revision、每页 key/role/number、filename、width/height、原生渲染 url。计划本身不生成位图。示例统一为3:4、1080×1440 PNG;其他固定模板保留自己的画板比例,不拉伸。该尺寸是本流程的交付规格;发布前还须在小红书当前发布界面复核裁切及文件限制,本站不自动发布。\n\n导出 URL 带 `&page=N&export=1`,画布在1080 CSS像素下重新布局原生文字,不把860宽或手机截图放大。图片使用 `/api/preview?source=...&width=2160&profile=export`:WebP质量95,不为常规缩略图预算降低尺寸,不放大小原件。普通展示仍使用240/480/960/1280及100KB/220KB预算。\n\n在用户已确认的 **ces-control / aliyun-ces-control** 上运行仓库 `scripts/export-studio-series.cjs`,使用既有测试镜像的 playwright-core、sharp、Chromium 和中文字体。只访问 `http://8.211.154.134:18131`,认证保留进程内。设置 STUDIO_DESIGN_ID、STUDIO_EXPORT_DIR、AUTH_CONFIG_FILE(或由现有登录方式提供的 PROMPT_TOKEN),不在命令行或文件中存令牌。不得在用户本机启动编译、容器构建或预览服务器。\n\n脚本逐页检查字体和图片加载、文字溢出、图片原生分辨率与裁切缩放、相同画板比例、页序、最终PNG像素、导出前后revision一致;记录实际字体、SHA256及manifest。额外生成桌面1200宽和手机390宽整组截图供视觉检查。自动检查通过后,agent 仍须逐页看PNG,核对中文缺字、主体裁切、阅读顺序、留白和系列一致性。检查失败修订 document,重新导出;不能把失败文件当成可发布结果。\n\n最终交付:真实设计结果链接、按01/02/03排序的PNG、完整design.json、manifest.json、素材来源与提示词、简短发布文案。生成图、保存成功、导出成功、人工视觉通过、PR待合并、线上部署验收应分别如实报告。未合并代码的能力不可写成线上已可用。\n\n### 可复现三页示例\n\n仓库 `content/studio-examples/autumn-journal.json` 是完整的三页内容与样式文档:封面“把秋天装进一页”、正文“先看见再记录”、结尾“留一点白给下一次”。它不含虚构素材 UUID,空图片位仅用于契约测试。真实制作时先生成银杏母图,再上传取得assetId,在每页 I01 加入该 UUID,并在 sources 记录完整来源。不要把无图夹具当作已完成作品。\n\n推荐在 ces-control 的仓库目录直接执行 `bash scripts/deliver-studio-series.sh /absolute/path/series.json /absolute/path/ginkgo-master.png /absolute/path/provenance.json /absolute/path/output-directory`:该脚本复用已部署认证卷和匹配的CI镜像,依次保存、导出并留下证据,不启动构建或部署。成品位于 output-directory/revision-N/;已成功导出的修订会核对哈希后复用,后续修订使用新目录,避免覆盖或混入旧页。\n\n底层保存命令 `node /app/auth-cli.cjs run /delivery/scripts/save-studio-series.mjs /absolute/path/series.json /absolute/path/ginkgo-master.png /absolute/path/provenance.json /absolute/path/output-directory` 经既有 auth-cli run 在远程执行,上传素材并填写所有页面引用后,保存真实作品,写出请求、响应和状态文件。重复运行会读取已保存状态,防止在成功后重复创建;出现网络结果不明时先核对素材/设计列表,不盲目重试。脚本仅在传入文件后写入作品,不自动发布到小红书。\n\n\n## 两类排版,同一内容模型\n\n- **标准排版**:`standard-01`,120×160 单位。沿用原 Studio 图文页的标题、正文、栏目结构,增加主题图片与分隔线。T01 主标题、T02 正文、T03 栏目、I01 图片、D01 分隔线。所有内容和位置可设置,也能增加原生元素。\n- **固定排版**:`ref-01` 至 `ref-27`,保留各自画板、出血、圆形、斜切、竖题和字体构图。先读取模板,按实际 T/I/D ID 填入内容。不能假定各模板拥有相同元素。\n\n两类单页都使用 `/api/studio/designs` 的真实 CRUD。创建额外指定 layoutId;更新从 URL 确定 ID,模板关联不能修改。更换模板应创建新记录,避免把旧元素 ID 错套到新模板。调用方无需打开 `/studio` 或操作编辑器。模板由 agent 经 API 选择,图片和文字由 API 保存,用户只打开结果链接。现有独立工具箱功能保持可用。\n\n## 1. 由 agent 通过 API 发现模板\n\n以下 curl 使用指定服务器。`PROMPT_TOKEN` 由用户通过现有登录方式提供,只保存在进程环境,不放 URL 或日志。也可使用已登录浏览器的同源 Cookie;浏览器写请求需要同源 Origin。未登录 API 返回 401。\n\n```sh\nBASE=http://8.211.154.134:18131\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/studio/api/layouts\"\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/studio/api/layouts/standard-01\"\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/studio/api/layouts/ref-17\"\n```\n\n目录返回 `version, layouts, saveApi, catalogUrl, documentationUrl`。每项包含 `id,name,family,url,api`,family 为 standard/fixed。模板返回尺寸、元素 ID/类型/role/box/direction/text、默认 parameters、workspaceSchema 与页面 URL。`?agent_mode=True` 返回纯文本文档,业务请求移除此参数。\n\n## 2. 生成图片,上传一次,显示压缩衍生图\n\n先让 coding agent 按生成图片文档产出文件;Studio 不暗中调用生成模型。图片可以来自用户素材或生成结果。上传走现有素材 API:\n\n```sh\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \\\n -F kind=image -F assetOnly=true -F files=@hero.png \"$BASE/api/upload\"\n```\n\n返回 `assets` UUID 数组及对应 `files` 元数据。取 `assets[0]` 作为 assetId。上传不单独发帖;设计创建时才自动关联 Wiki post。不要把 Base64、任意远程 URL、HTML 或 SVG 塞入参数。\n\n原素材保留在素材库,**页面展示只请求压缩 WebP**。以 assetId 引用素材,不必自行再上传缩略图:\n\n```sh\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \\\n \"$BASE/api/preview?source=%2Fapi%2Fmedia%2FASSET_UUID&width=960\" -o hero-preview.webp\n```\n\nwidth 只允许 240/480/960/1280;小图预算 100KB,大图预算 220KB,复杂图片会降低尺寸/质量。原生页面按槽位显示宽度和像素密度选择 srcset 衍生图,保留 fit/cropX/cropY/zoom。目录使用原生文字与 CSS 色块,不加载参考原图;旧工具箱参考图也使用压缩预览。`/api/media/{id}` 是素材原件读取接口,不应用作展示图片的 src。\n\n## 3. 创建:POST /api/studio/designs\n\n创建和更新共用内容字段 `name,parameters`;`schemaVersion` 可省略,单页当前支持 1(多页使用上述独立 series API 的2)。创建额外传 `layoutId`,不传 revision 或 id。\n\n```sh\ncurl -fsS -X POST \"$BASE/api/studio/designs\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"schemaVersion\":1,\"layoutId\":\"standard-01\",\"name\":\"观察日常\",\"parameters\":{\"paper\":\"#edf0ef\",\"texts\":{\"T01\":{\"value\":\"观察日常\\n记录灵感\",\"fontFamily\":\"serif\",\"color\":\"#23424b\"},\"T02\":{\"value\":\"让每一页只讲一件事。\",\"lineHeight\":1.4}},\"images\":{\"I01\":{\"color\":\"#73929a\",\"fit\":\"cover\",\"cropX\":50,\"cropY\":45,\"zoom\":1}},\"decorations\":{\"D01\":{\"color\":\"#73929a\"}}}}'\n```\n\n有图片时在 I01 加 `assetId:\"上传返回的UUID\"`。固定排版只需改为所选 `layoutId`,并按 schema 选择元素 ID。例如 ref-01 支持 `texts.T01`、`texts.T02` 与 `images.I01`。\n\n201 返回 `{design}`。design 包含:\n\n| 字段 | 含义 |\n| --- | --- |\n| id / layoutId / family | 持久化 UUID、不可变模板 ID、standard 或 fixed |\n| schemaVersion / revision | 参数契约版本(1)、记录修订号(从1递增) |\n| name / parameters | 名称与规范化内容;省略的分组返回空对象 |\n| url / renderUrl | 同一个原生 HTML/CSS 页面 URL |\n| schemaUrl | 当前模板参数地址 |\n| postId / postUrl | 自动创建并在后续更新中复用的 Wiki post |\n| archived / archivedAt | 可恢复归档状态与时间 |\n| createdAt / updatedAt | 创建与最后修改时间 |\n| legacySvgUrl | 固定排版的旧 SVG 导出地址,标准排版为 null |\n\n创建设计与 Wiki post 在同一数据库事务内完成,失败整体回滚。更新同一设计复用同一 post;手工编辑过的 Wiki 正文、标题、标签、封面不会被参数更新覆盖。\n\n## 4. 读取与打开\n\n```sh\nID=创建返回的UUID\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/api/studio/designs/$ID\"\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/api/studio/designs?layoutId=standard-01&limit=20\"\n```\n\n读取返回 `{design}`,没有副作用。列表默认只列未归档记录;`archived=true` 只列归档记录;limit 为1–50,默认20;`nextCursor` 作为下一页 before。layoutId 可省略。\n\n直接打开创建响应中的 `design.url`:`/studio/template-layout?id={UUID}`。这个 UUID 对应数据库里 API 保存的模板、图片与文字,服务端按记录渲染最终 HTML/CSS。链接中无需携带 layoutId,无需打开 `/studio` 选择模板,也没有编辑按钮、双击编辑或右键编辑。`standard-01` 与 `ref-17` 是模板 ID,不是保存记录的 UUID。\n\n`/studio/designs/{UUID}` 与旧 `/studio/layouts/{layoutId}?id={UUID}` 兼容跳转到同一个结果页。缺失、非法、不存在或已归档的 ID 返回404,不会显示空模板或编辑器;旧链接模板与记录不匹配也返回404。归档设计仍能经 API 读取以恢复。\n\n## 5. 更新:PATCH /api/studio/designs/{id}\n\n先 GET,保留所有未改字段,再整体替换 parameters;不是深度合并。以下假设刚读到 revision=1:\n\n```sh\ncurl -fsS -X PATCH \"$BASE/api/studio/designs/$ID\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"name\":\"观察日常 · 修订\",\"revision\":1,\"parameters\":{\"texts\":{\"T01\":{\"value\":\"新的标题\"}},\"images\":{},\"decorations\":{}}}'\n```\n\n200 返回新 design,revision 增加。过期 revision 返回409,重新 GET 并合并后再提交;不要盲目重试。不接受 layoutId、id、url、postId 等返回字段;不能直接把整个 design 作为 body。另存副本使用 POST 创建新 UUID。\n\n## 6. 删除与恢复\n\nDELETE 采用可恢复归档,不物理删除参数、素材或 Wiki post。和更新一样必须提供最新 revision;自动同步关联 post 的归档状态。\n\n```sh\ncurl -fsS -X DELETE \"$BASE/api/studio/designs/$ID\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' -d '{\"revision\":2}'\n# 删除后读取新 revision,再恢复。此例删除后为3:\ncurl -fsS -X POST \"$BASE/api/studio/designs/$ID\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' -d '{\"revision\":3,\"archived\":false}'\n```\n\n两者200返回 `{design}`,revision 递增。归档记录 PATCH 返回409,先恢复。重复写入仍需最新 revision;409不会修改数据。独立归档 Wiki post 只影响笔记发现状态,不等于删除设计。旧设计缺 Wiki 入口可调用 `POST /api/studio/designs/{id}/post`,body `{}`,首次201、已有200;不改变 revision。\n\n## 参数清单(两类共用)\n\nparameters 顶层允许 texts/images/decorations/paper/layers/added;全部可省略,不能添加未知字段。最大请求64KB。颜色都为 `#RRGGBB`。坐标与字号使用模板单位,不是 CSS 像素。\n\n| 分组 | 字段与约束 |\n| --- | --- |\n| texts.{文字ID} | value 必填,可空,最多500字/8行;color;scale 0.25–2;align start/middle/end;fontFamily sans/serif/mono;fontSize 0.1–1000;fontWeight 100–900;lineHeight 0.5–4;letterSpacing -20–100 |\n| images.{图片ID} | assetId(已存在的图片 UUID);color;fit cover/contain;cropX/cropY 0–100(默认50);zoom 1–5(默认1) |\n| decorations.{装饰ID} | color;hidden 布尔值 |\n| paper | 纸张颜色 |\n| layers.{元素ID} | x/y -10000–10000;width/height 0.1–10000;rotation -360–360;opacity 0–1;radius 0–5000;z -1000–1000;hidden/locked 布尔值 |\n| added | 最多100项:id 为 user- 加1–60位字母/数字/连字符,唯一;kind text/image/decoration;role 最多80字;box 为 [x,y,width,height];direction 必须 horizontal;text 为文字或null;reading 必须 custom;装饰可带 shape rectangle/ellipse |\n\n新增元素先在 added 声明,然后在 texts/images/decorations/layers 用该 ID 配置。locked 控制工作区交互,不禁止 agent 更新。数据是封闭结构,无脚本执行。未来扩展新增契约版本;v1 不接受未知字段/版本。现有省略 schemaVersion 的请求继续按v1处理,旧记录无需重建,布局 ID 和页面 UUID 保持不变。默认视觉可随模板美化变化;要求固定外观时显式设置配色、字体和图层参数。\n\n错误:400 参数/版本/构件类型/图片 ID 不合法;401 未登录;404 模板或设计不存在;409 修订冲突或已归档;405 方法不支持;413 请求超限;500 服务失败。压缩预览失败可能返回503与 Retry-After。\n\n## 现有工具箱与兼容导出\n\n工具功能保留:[ref-17 同一母版四张试版](/studio/ideas/learning-skills-template)、[ref-01/07/26 三种构图试版](/studio/ideas/layout-selection)、[最初图文页](/studio/ideas/first-idea)。旧封面工具文档已合并到本篇,旧文档 URL 保留为归档说明,避免重复顶级入口。\n\n旧无状态 API:`/studio/api/export/learning-skills-template` 和 `/studio/api/export/layout-selection` 支持 GET/POST,不保存设计。前者参数 slide=0..3、heading(1–6字)、body(最多360字/3行)、color/accent、masthead(2项);后者 layoutId=ref-01/ref-07/ref-26、fields(对应T ID,1–120字/4行)、color/accent。GET 默认返回方案 JSON;GET format=svg 导出初始图;POST format=svg(默认)导出 SVG,format=json 返回参数,最大32KB。first-idea 仅支持 GET 方案/Markdown。\n\n```sh\ncurl -fsS -X POST \"$BASE/studio/api/export/learning-skills-template?format=svg\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"slide\":1,\"heading\":\"笔记复习\",\"color\":\"#7a8b7e\"}' -o cover.svg\ncurl -fsS -X POST \"$BASE/studio/api/export/layout-selection?format=svg\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"layoutId\":\"ref-01\",\"fields\":{\"T01\":\"STUDY\"},\"color\":\"#83918a\"}' -o selection.svg\n```\n\n固定排版 `/studio/api/layouts/ref-01` 的 GET format=svg、POST 仍提供旧分层 SVG;POST format=json 只验证和返回规范化参数。**旧 SVG 不反映 added、layers、扩展字体和裁切参数**,不能用来证明原生效果。standard-01 不支持 SVG。原生页面应直接打开返回 URL,;多页可按上文通过导出器生成PNG。代码可扩展渲染能力,但每次是否使用、选择哪些参数均由 coding agent 决定。\n\n## 固定排版索引\n\n读取 /studio/api/layouts 获取全部27套构图。\n"; +export const designDocs="# Studio 排版 API · 标准图文与 27 套固定构图\n\n把本篇与图片生成文档一起交给 coding agent。agent 自行选择图片、配色、字体、构图与参数,通过 HTTP 保存渲染描述,得到可持续更新的原生 HTML/CSS 页面。数据库只保存模板关联、文字、素材引用和渲染参数,不保存截图或任意 HTML。\n\n[可选:查看模板示例](/studio) · [读取实时 API 契约](/api/studio/designs?agent_mode=True) · [照片入皮影](/explore/3c98946754c33309d73d5284) · [街头小人](/explore/4ce3283fd98bb03a8fb39a19)\n\n## agent 的交付流程\n\n1. 按下文读取模板 API,选择 layoutId,并获取该模板实际的 T/I/D 元素 ID。\n2. 上传图片取得 assetId,把图片、文字和参数提交给创建 API。\n3. 从创建响应读取 `design.id` 与 `design.url`,把完整结果链接交给用户,例如 `http://8.211.154.134:18131/studio/template-layout?id=实际返回的UUID`。\n4. 用户直接打开该链接看结果;修改内容继续调用 PATCH,同一 ID 与结果链接保持不变。\n\n不要编造 ID,也不要把模板预览链接当作最终结果。API 保存成功并返回真实 UUID 才算完成。以下文档足以通过 HTTP 完成流程,无需使用编辑器。\n\n## 多页作品:从创作 brief 到可发布文件(schemaVersion 2)\n\n多页作品由 coding agent 完成创作决策与图像生成,网站负责封闭参数验证、事务保存与原生渲染。无需模型服务、模型密钥或编辑器。先形成主题、读者、意图、逐页分镜,再生成需要的图片,最后通过 HTTP 保存整组作品。封面、正文、结尾共用一个真实 UUID、修订号和 Wiki post。\n\n### 完整内容与视觉模型\n\n创建 `POST /api/studio/series`,body 为 `{schemaVersion:2,name,document}`。读取/整体修订使用 `GET/PATCH /api/studio/series/{id}`;PATCH 额外提供最新 revision。最大512KB,未知字段会拒绝。document:\n\n- `version:1`:多页文档结构版本。\n- `brief`:topic、audience、intent、designIntent、imageStrategy,均为必填非空文字,最多2000字符。分别说明主题、读者、内容目标、完整设计描述和配图策略。\n- `style.version`:agent 指定的样式版本标识(最多80字符),如 `autumn-journal/1`。它用于记录创作版本;不锁定未来网站渲染器,精确交付请同时保留导出文件和 manifest。\n- `style.palette`:paper、ink、muted、accent,均为 #RRGGBB。\n- `style.typography`:title、body、caption 三种层级,每项包含 fontFamily(sans/serif/mono)、fontSize(0.1–40)、fontWeight(100–900)、lineHeight(0.5–4)、letterSpacing(-5–10)。字号和字距以120宽参考画板为单位,固定模板按画板宽度同比换算。\n- `style.spacing`:margin(4–16)、gap(1–10),并要求 `2*margin+4*gap<=48`。标准排版据此计算标题、图片、正文和分隔线的位置;固定模板保留原构图,用 layers 显式调整位置。\n- `sources`:最多100项,每项 assetId、author、license、url、prompt、tool 全部必填,文字最多4000字符。url 是 http(s) 来源地址或 `generated`。用户照片记录来源与授权;生成图记录完整提示词和工具,不把 AI 图写成摄影原作。所有被页面引用的图片必须有来源记录。\n- `pages`:本工作流支持3–18页(这是本服务的约束,不宣称是平台实时上限)。每页 key 唯一(小写字母/数字/连字符,最多60位)、role(首项 cover,中间 body,末项 ending)、layoutId、intent(最多1000字符)、bindings、parameters。parameters 继续使用下文封闭的单页参数契约。\n- `pages[].bindings`:把实际文字元素 ID 映射到 title/body/caption,例如 `{T01:\"title\",T02:\"body\",T03:\"caption\"}`。被绑定元素必须提供文案。共享字体、墨色和纸色会参与真实渲染;单元素显式值优先覆盖。所有可见文字必须提供或明确隐藏,避免默认占位文案泄漏。图片默认色与装饰默认色取 accent。\n\n标准排版可直接沿用 T01/T02/T03/I01/D01;固定排版先读对应模板 API,元素不能混用。agent 必须控制标题长度和正文密度,不能把“已保存”当成“已验收”。同组图片建议统一比例;导出器遇到混合比例会拒绝,agent 应改用同一画板并调整内容布局。\n\n### 一次保存,一条直接结果链接\n\n```sh\n# series.json 包含完整 document,请按下文契约构造\ncurl -fsS -X POST \"$BASE/api/studio/series\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n --data-binary @series.json\n# 返回 {design}:schemaVersion=2、document、id、revision、url、postId 等\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/api/studio/series/$ID\"\n```\n\n创建响应的 `design.url` 仍是 `/studio/template-layout?id={UUID}`;直接展示全部页,无需选模板。`&page=2` 只显示第2页,非法页码返回404。页面是服务端原生文字与 CSS,图片走 /api/preview。创建与全部页验证、Wiki post 处于同一事务,任一页无效整组不保存。PATCH 整体替换 document,revision 冲突返回409;先重新读取并合并。单页 PATCH 接口会拒绝修改多页作品,避免只改封面导致组内不一致。可从 designs 列表发现作品,schemaVersion 区分1与2。\n\n归档 `DELETE /api/studio/series/{id}` body `{revision}`;恢复 `POST` 同一路径 body `{revision,archived:false}`。归档后结果页和导出计划返回404,API 仍能读取并恢复;参数、素材和 Wiki post 保留。更新复用原 Wiki post,手工编辑过的正文不被覆盖。\n\n### 高质量导出与验收\n\n`GET /api/studio/series/{id}/export` 返回固定顺序的导出计划:id、revision、每页 key/role/number、filename、width/height、原生渲染 url。计划本身不生成位图。示例统一为3:4、1080×1440 PNG;其他固定模板保留自己的画板比例,不拉伸。该尺寸是本流程的交付规格;发布前还须在小红书当前发布界面复核裁切及文件限制,本站不自动发布。\n\n导出 URL 带 `&page=N&export=1`,画布在1080 CSS像素下重新布局原生文字,不把860宽或手机截图放大。图片使用 `/api/preview?source=...&width=2160&profile=export`:WebP质量95,不为常规缩略图预算降低尺寸,不放大小原件。普通展示仍使用240/480/960/1280及100KB/220KB预算。\n\n在用户已确认的 **ces-control / aliyun-ces-control** 上运行仓库 `scripts/export-studio-series.cjs`,使用既有测试镜像的 playwright-core、sharp、Chromium 和中文字体。只访问 `http://8.211.154.134:18131`,认证保留进程内。设置 STUDIO_DESIGN_ID、STUDIO_EXPORT_DIR、AUTH_CONFIG_FILE(或由现有登录方式提供的 PROMPT_TOKEN),不在命令行或文件中存令牌。不得在用户本机启动编译、容器构建或预览服务器。\n\n脚本逐页检查字体和图片加载、文字溢出、图片原生分辨率与裁切缩放、相同画板比例、页序、最终PNG像素、导出前后revision一致;记录实际字体、SHA256及manifest。额外生成桌面1200宽和手机390宽整组截图供视觉检查。自动检查通过后,agent 仍须逐页看PNG,核对中文缺字、主体裁切、阅读顺序、留白和系列一致性。检查失败修订 document,重新导出;不能把失败文件当成可发布结果。\n\n最终交付:真实设计结果链接、按01/02/03排序的PNG、完整design.json、manifest.json、素材来源与提示词、简短发布文案。生成图、保存成功、导出成功、人工视觉通过、PR待合并、线上部署验收应分别如实报告。未合并代码的能力不可写成线上已可用。\n\n### 可复现三页示例\n\n使用真实素材时,先上传取得 assetId,再将 UUID 加入对应页的图片位,并在 sources 记录来源。\n\n推荐在 ces-control 的仓库目录直接执行 `bash scripts/deliver-studio-series.sh /absolute/path/series.json /absolute/path/ginkgo-master.png /absolute/path/provenance.json /absolute/path/output-directory`:该脚本复用已部署认证卷和匹配的CI镜像,依次保存、导出并留下证据,不启动构建或部署。成品位于 output-directory/revision-N/;已成功导出的修订会核对哈希后复用,后续修订使用新目录,避免覆盖或混入旧页。\n\n底层保存命令 `node /app/auth-cli.cjs run /delivery/scripts/save-studio-series.mjs /absolute/path/series.json /absolute/path/ginkgo-master.png /absolute/path/provenance.json /absolute/path/output-directory` 经既有 auth-cli run 在远程执行,上传素材并填写所有页面引用后,保存真实作品,写出请求、响应和状态文件。重复运行会读取已保存状态,防止在成功后重复创建;出现网络结果不明时先核对素材/设计列表,不盲目重试。脚本仅在传入文件后写入作品,不自动发布到小红书。\n\n\n## 两类排版,同一内容模型\n\n- **标准排版**:`standard-01`,120×160 单位。沿用原 Studio 图文页的标题、正文、栏目结构,增加主题图片与分隔线。T01 主标题、T02 正文、T03 栏目、I01 图片、D01 分隔线。所有内容和位置可设置,也能增加原生元素。\n- **固定排版**:`ref-01` 至 `ref-27`,保留各自画板、出血、圆形、斜切、竖题和字体构图。先读取模板,按实际 T/I/D ID 填入内容。不能假定各模板拥有相同元素。\n\n两类单页都使用 `/api/studio/designs` 的真实 CRUD。创建额外指定 layoutId;更新从 URL 确定 ID,模板关联不能修改。更换模板应创建新记录,避免把旧元素 ID 错套到新模板。调用方无需打开 `/studio` 或操作编辑器。模板由 agent 经 API 选择,图片和文字由 API 保存,用户只打开结果链接。现有独立工具箱功能保持可用。\n\n## 1. 由 agent 通过 API 发现模板\n\n以下 curl 使用指定服务器。`PROMPT_TOKEN` 由用户通过现有登录方式提供,只保存在进程环境,不放 URL 或日志。也可使用已登录浏览器的同源 Cookie;浏览器写请求需要同源 Origin。未登录 API 返回 401。\n\n```sh\nBASE=http://8.211.154.134:18131\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/studio/api/layouts\"\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/studio/api/layouts/standard-01\"\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/studio/api/layouts/ref-17\"\n```\n\n目录返回 `version, layouts, saveApi, catalogUrl, documentationUrl`。每项包含 `id,name,family,url,api`,family 为 standard/fixed。模板返回尺寸、元素 ID/类型/role/box/direction/text、默认 parameters、workspaceSchema 与页面 URL。`?agent_mode=True` 返回纯文本文档,业务请求移除此参数。\n\n## 2. 生成图片,上传一次,显示压缩衍生图\n\n先让 coding agent 按生成图片文档产出文件;Studio 不暗中调用生成模型。图片可以来自用户素材或生成结果。上传走现有素材 API:\n\n```sh\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \\\n -F kind=image -F assetOnly=true -F files=@hero.png \"$BASE/api/upload\"\n```\n\n返回 `assets` UUID 数组及对应 `files` 元数据。取 `assets[0]` 作为 assetId。上传不单独发帖;设计创建时才自动关联 Wiki post。不要把 Base64、任意远程 URL、HTML 或 SVG 塞入参数。\n\n原素材保留在素材库,**页面展示只请求压缩 WebP**。以 assetId 引用素材,不必自行再上传缩略图:\n\n```sh\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \\\n \"$BASE/api/preview?source=%2Fapi%2Fmedia%2FASSET_UUID&width=960\" -o hero-preview.webp\n```\n\nwidth 只允许 240/480/960/1280;小图预算 100KB,大图预算 220KB,复杂图片会降低尺寸/质量。原生页面按槽位显示宽度和像素密度选择 srcset 衍生图,保留 fit/cropX/cropY/zoom。目录使用原生文字与 CSS 色块,不加载参考原图;旧工具箱参考图也使用压缩预览。`/api/media/{id}` 是素材原件读取接口,不应用作展示图片的 src。\n\n## 3. 创建:POST /api/studio/designs\n\n创建和更新共用内容字段 `name,parameters`;`schemaVersion` 可省略,单页当前支持 1(多页使用上述独立 series API 的2)。创建额外传 `layoutId`,不传 revision 或 id。\n\n```sh\ncurl -fsS -X POST \"$BASE/api/studio/designs\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"schemaVersion\":1,\"layoutId\":\"standard-01\",\"name\":\"观察日常\",\"parameters\":{\"paper\":\"#edf0ef\",\"texts\":{\"T01\":{\"value\":\"观察日常\\n记录灵感\",\"fontFamily\":\"serif\",\"color\":\"#23424b\"},\"T02\":{\"value\":\"让每一页只讲一件事。\",\"lineHeight\":1.4}},\"images\":{\"I01\":{\"color\":\"#73929a\",\"fit\":\"cover\",\"cropX\":50,\"cropY\":45,\"zoom\":1}},\"decorations\":{\"D01\":{\"color\":\"#73929a\"}}}}'\n```\n\n有图片时在 I01 加 `assetId:\"上传返回的UUID\"`。固定排版只需改为所选 `layoutId`,并按 schema 选择元素 ID。例如 ref-01 支持 `texts.T01`、`texts.T02` 与 `images.I01`。\n\n201 返回 `{design}`。design 包含:\n\n| 字段 | 含义 |\n| --- | --- |\n| id / layoutId / family | 持久化 UUID、不可变模板 ID、standard 或 fixed |\n| schemaVersion / revision | 参数契约版本(1)、记录修订号(从1递增) |\n| name / parameters | 名称与规范化内容;省略的分组返回空对象 |\n| url / renderUrl | 同一个原生 HTML/CSS 页面 URL |\n| schemaUrl | 当前模板参数地址 |\n| postId / postUrl | 自动创建并在后续更新中复用的 Wiki post |\n| archived / archivedAt | 可恢复归档状态与时间 |\n| createdAt / updatedAt | 创建与最后修改时间 |\n| legacySvgUrl | 固定排版的旧 SVG 导出地址,标准排版为 null |\n\n创建设计与 Wiki post 在同一数据库事务内完成,失败整体回滚。更新同一设计复用同一 post;手工编辑过的 Wiki 正文、标题、标签、封面不会被参数更新覆盖。\n\n## 4. 读取与打开\n\n```sh\nID=创建返回的UUID\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/api/studio/designs/$ID\"\ncurl -fsS -H \"Authorization: Bearer $PROMPT_TOKEN\" \"$BASE/api/studio/designs?layoutId=standard-01&limit=20\"\n```\n\n读取返回 `{design}`,没有副作用。列表默认只列未归档记录;`archived=true` 只列归档记录;limit 为1–50,默认20;`nextCursor` 作为下一页 before。layoutId 可省略。\n\n直接打开创建响应中的 `design.url`:`/studio/template-layout?id={UUID}`。这个 UUID 对应数据库里 API 保存的模板、图片与文字,服务端按记录渲染最终 HTML/CSS。链接中无需携带 layoutId,无需打开 `/studio` 选择模板,也没有编辑按钮、双击编辑或右键编辑。`standard-01` 与 `ref-17` 是模板 ID,不是保存记录的 UUID。\n\n`/studio/designs/{UUID}` 与旧 `/studio/layouts/{layoutId}?id={UUID}` 兼容跳转到同一个结果页。缺失、非法、不存在或已归档的 ID 返回404,不会显示空模板或编辑器;旧链接模板与记录不匹配也返回404。归档设计仍能经 API 读取以恢复。\n\n## 5. 更新:PATCH /api/studio/designs/{id}\n\n先 GET,保留所有未改字段,再整体替换 parameters;不是深度合并。以下假设刚读到 revision=1:\n\n```sh\ncurl -fsS -X PATCH \"$BASE/api/studio/designs/$ID\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"name\":\"观察日常 · 修订\",\"revision\":1,\"parameters\":{\"texts\":{\"T01\":{\"value\":\"新的标题\"}},\"images\":{},\"decorations\":{}}}'\n```\n\n200 返回新 design,revision 增加。过期 revision 返回409,重新 GET 并合并后再提交;不要盲目重试。不接受 layoutId、id、url、postId 等返回字段;不能直接把整个 design 作为 body。另存副本使用 POST 创建新 UUID。\n\n## 6. 删除与恢复\n\nDELETE 采用可恢复归档,不物理删除参数、素材或 Wiki post。和更新一样必须提供最新 revision;自动同步关联 post 的归档状态。\n\n```sh\ncurl -fsS -X DELETE \"$BASE/api/studio/designs/$ID\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' -d '{\"revision\":2}'\n# 删除后读取新 revision,再恢复。此例删除后为3:\ncurl -fsS -X POST \"$BASE/api/studio/designs/$ID\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' -d '{\"revision\":3,\"archived\":false}'\n```\n\n两者200返回 `{design}`,revision 递增。归档记录 PATCH 返回409,先恢复。重复写入仍需最新 revision;409不会修改数据。独立归档 Wiki post 只影响笔记发现状态,不等于删除设计。旧设计缺 Wiki 入口可调用 `POST /api/studio/designs/{id}/post`,body `{}`,首次201、已有200;不改变 revision。\n\n## 参数清单(两类共用)\n\nparameters 顶层允许 texts/images/decorations/paper/layers/added;全部可省略,不能添加未知字段。最大请求64KB。颜色都为 `#RRGGBB`。坐标与字号使用模板单位,不是 CSS 像素。\n\n| 分组 | 字段与约束 |\n| --- | --- |\n| texts.{文字ID} | value 必填,可空,最多500字/8行;color;scale 0.25–2;align start/middle/end;fontFamily sans/serif/mono;fontSize 0.1–1000;fontWeight 100–900;lineHeight 0.5–4;letterSpacing -20–100 |\n| images.{图片ID} | assetId(已存在的图片 UUID);color;fit cover/contain;cropX/cropY 0–100(默认50);zoom 1–5(默认1) |\n| decorations.{装饰ID} | color;hidden 布尔值 |\n| paper | 纸张颜色 |\n| layers.{元素ID} | x/y -10000–10000;width/height 0.1–10000;rotation -360–360;opacity 0–1;radius 0–5000;z -1000–1000;hidden/locked 布尔值 |\n| added | 最多100项:id 为 user- 加1–60位字母/数字/连字符,唯一;kind text/image/decoration;role 最多80字;box 为 [x,y,width,height];direction 必须 horizontal;text 为文字或null;reading 必须 custom;装饰可带 shape rectangle/ellipse |\n\n新增元素先在 added 声明,然后在 texts/images/decorations/layers 用该 ID 配置。locked 控制工作区交互,不禁止 agent 更新。数据是封闭结构,无脚本执行。未来扩展新增契约版本;v1 不接受未知字段/版本。现有省略 schemaVersion 的请求继续按v1处理,旧记录无需重建,布局 ID 和页面 UUID 保持不变。默认视觉可随模板美化变化;要求固定外观时显式设置配色、字体和图层参数。\n\n错误:400 参数/版本/构件类型/图片 ID 不合法;401 未登录;404 模板或设计不存在;409 修订冲突或已归档;405 方法不支持;413 请求超限;500 服务失败。压缩预览失败可能返回503与 Retry-After。\n\n## 现有工具箱与兼容导出\n\n工具功能保留:[ref-17 同一母版四张试版](/studio/ideas/learning-skills-template)、[ref-01/07/26 三种构图试版](/studio/ideas/layout-selection)、[最初图文页](/studio/ideas/first-idea)。旧封面工具文档已合并到本篇,旧文档 URL 保留为归档说明,避免重复顶级入口。\n\n旧无状态 API:`/studio/api/export/learning-skills-template` 和 `/studio/api/export/layout-selection` 支持 GET/POST,不保存设计。前者参数 slide=0..3、heading(1–6字)、body(最多360字/3行)、color/accent、masthead(2项);后者 layoutId=ref-01/ref-07/ref-26、fields(对应T ID,1–120字/4行)、color/accent。GET 默认返回方案 JSON;GET format=svg 导出初始图;POST format=svg(默认)导出 SVG,format=json 返回参数,最大32KB。first-idea 仅支持 GET 方案/Markdown。\n\n```sh\ncurl -fsS -X POST \"$BASE/studio/api/export/learning-skills-template?format=svg\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"slide\":1,\"heading\":\"笔记复习\",\"color\":\"#7a8b7e\"}' -o cover.svg\ncurl -fsS -X POST \"$BASE/studio/api/export/layout-selection?format=svg\" \\\n -H \"Authorization: Bearer $PROMPT_TOKEN\" -H 'Content-Type: application/json' \\\n -d '{\"layoutId\":\"ref-01\",\"fields\":{\"T01\":\"STUDY\"},\"color\":\"#83918a\"}' -o selection.svg\n```\n\n固定排版 `/studio/api/layouts/ref-01` 的 GET format=svg、POST 仍提供旧分层 SVG;POST format=json 只验证和返回规范化参数。**旧 SVG 不反映 added、layers、扩展字体和裁切参数**,不能用来证明原生效果。standard-01 不支持 SVG。原生页面应直接打开返回 URL,;多页可按上文通过导出器生成PNG。代码可扩展渲染能力,但每次是否使用、选择哪些参数均由 coding agent 决定。\n\n## 固定排版索引\n\n读取 /studio/api/layouts 获取全部27套构图。\n"; export function studioDesignDocumentation(req:NextApiRequest,res:NextApiResponse){if(!['GET','HEAD'].includes(req.method||'')||String(req.query.agent_mode).toLowerCase()!=='true')return false;res.setHeader('Content-Type','text/plain; charset=utf-8');res.setHeader('Cache-Control','no-store');if(req.method==='HEAD')res.status(200).end();else res.status(200).send(designDocs);return true} diff --git a/apps/waterfall-wiki/src/pages/_app.tsx b/apps/waterfall-wiki/src/pages/_app.tsx index d213127..13be129 100644 --- a/apps/waterfall-wiki/src/pages/_app.tsx +++ b/apps/waterfall-wiki/src/pages/_app.tsx @@ -5,9 +5,11 @@ import "@/styles/globals.css"; import "@/styles/skill-docs.css"; import "@/styles/learning-series.css"; import "@/styles/brand.css"; +import "@/studio/styles/globals.css"; export default function App({ Component, pageProps }: AppProps) { const router = useRouter(); - useEffect(() => { const scope = /^\/(?:graphic|infinite_canvas|layouts|template-layout)(?:\/|$)/.test(router.pathname) ? 'canvas' : 'home'; document.documentElement.dataset.theme = `${scope}-${document.documentElement.dataset.mode === 'dark' ? 'dark' : 'light'}`; }, [router.pathname]); - return ; + useEffect(() => { const scope = /^\/(?:studio\/)?(?:graphic|infinite_canvas|layouts|template-layout)(?:\/|$)/.test(router.pathname) ? 'canvas' : 'home'; document.documentElement.dataset.theme = `${scope}-${document.documentElement.dataset.mode === 'dark' ? 'dark' : 'light'}`; }, [router.pathname]); + const studio = /^\/(?:studio(?:\/|$)|graphic(?:\/|$)|infinite_canvas(?:\/|$))/.test(router.pathname); + return studio ?
: ; } diff --git a/apps/studio/src/pages/api/export/[slug].ts b/apps/waterfall-wiki/src/pages/api/studio-ui/export/[slug].ts similarity index 82% rename from apps/studio/src/pages/api/export/[slug].ts rename to apps/waterfall-wiki/src/pages/api/studio-ui/export/[slug].ts index f24882b..45f8d02 100644 --- a/apps/studio/src/pages/api/export/[slug].ts +++ b/apps/waterfall-wiki/src/pages/api/studio-ui/export/[slug].ts @@ -1,11 +1,11 @@ -import {embedSvgMedia} from '../../../lib/layout-images'; +import {embedSvgMedia} from '../../../../studio/lib/layout-images'; import {renderToStaticMarkup} from 'react-dom/server'; -import {serveApiDocumentation} from '../../../lib/api-documentation'; +import {serveApiDocumentation} from '../../../../studio/lib/api-documentation'; import type {NextApiRequest,NextApiResponse} from 'next'; -import {ideas,ideaMarkdown} from '../../../content/ideas'; -import {selections} from '../../../components/SourceCover'; -import {renderCover} from '../../../lib/render-cover'; -import {tutorialPost} from '../../../content/tool-links'; +import {ideas,ideaMarkdown} from '../../../../studio/content/ideas'; +import {selections} from '../../../../studio/components/SourceCover'; +import {renderCover} from '../../../../studio/lib/render-cover'; +import {tutorialPost} from '../../../../studio/content/tool-links'; export default async function handler(req:NextApiRequest,res:NextApiResponse){ if(serveApiDocumentation(req,res,'/studio/api/export/{slug}'))return; if(!['GET','POST'].includes(req.method||'')){res.setHeader('Allow','GET, POST');return res.status(405).end();} diff --git a/apps/studio/src/pages/api/health.ts b/apps/waterfall-wiki/src/pages/api/studio-ui/health.ts similarity index 84% rename from apps/studio/src/pages/api/health.ts rename to apps/waterfall-wiki/src/pages/api/studio-ui/health.ts index 4d52af9..7cb2f27 100644 --- a/apps/studio/src/pages/api/health.ts +++ b/apps/waterfall-wiki/src/pages/api/studio-ui/health.ts @@ -1,3 +1,3 @@ -import {serveApiDocumentation} from '../../lib/api-documentation'; +import {serveApiDocumentation} from '../../../studio/lib/api-documentation'; import type {NextApiRequest,NextApiResponse} from "next"; export default function handler(req:NextApiRequest,res:NextApiResponse){if(serveApiDocumentation(req,res,'/studio/api/health'))return;res.setHeader("Cache-Control","no-store");if(!["GET","HEAD"].includes(req.method||""))return res.status(405).end();return req.method==="HEAD"?res.status(200).end():res.status(200).json({status:"ok",service:"prompt-studio"});} diff --git a/apps/studio/src/pages/api/index.ts b/apps/waterfall-wiki/src/pages/api/studio-ui/index.ts similarity index 92% rename from apps/studio/src/pages/api/index.ts rename to apps/waterfall-wiki/src/pages/api/studio-ui/index.ts index 3e75da6..cbc5478 100644 --- a/apps/studio/src/pages/api/index.ts +++ b/apps/waterfall-wiki/src/pages/api/studio-ui/index.ts @@ -1,3 +1,3 @@ import type {NextApiRequest,NextApiResponse} from 'next'; -import {ideas} from '../../content/ideas'; +import {ideas} from '../../../studio/content/ideas'; export default function handler(_:NextApiRequest,res:NextApiResponse){res.setHeader('Content-Type','text/markdown; charset=utf-8');res.send('# 创作站\n\n- [在线画布、引用与 Agent 任务](/api/canvases?agent_mode=True)\n- [27 套排版目录与 API](/studio/api/layouts?agent_mode=True)\n- [多页作品与统一视觉](/api/studio/series?agent_mode=True)\n- [按 ID 保存效果](/api/studio/designs?agent_mode=True)\n\n'+ideas.map(i=>`- [${i.title}](/studio/ideas/${i.slug}) · [正文](/studio/api/export/${i.slug}?format=markdown)`).join('\n'));} diff --git a/apps/studio/src/pages/api/layouts/[id].ts b/apps/waterfall-wiki/src/pages/api/studio-ui/layouts/[id].ts similarity index 92% rename from apps/studio/src/pages/api/layouts/[id].ts rename to apps/waterfall-wiki/src/pages/api/studio-ui/layouts/[id].ts index eae58e0..410181d 100644 --- a/apps/studio/src/pages/api/layouts/[id].ts +++ b/apps/waterfall-wiki/src/pages/api/studio-ui/layouts/[id].ts @@ -1,12 +1,12 @@ -import {embedSvgMedia} from '../../../lib/layout-images'; +import {embedSvgMedia} from '../../../../studio/lib/layout-images'; import type {NextApiRequest,NextApiResponse} from 'next'; -import {embedLayoutImages} from '../../../lib/layout-images'; +import {embedLayoutImages} from '../../../../studio/lib/layout-images'; import {createElement} from 'react'; import {renderToStaticMarkup} from 'react-dom/server'; -import layouts from '../../../content/templates/layouts.json'; -import standardLayout from '../../../content/standard-layout.json'; -import {LayoutCanvas} from '../../../components/LayoutCanvas'; -import {parameters,LayoutParameterError} from '../../../lib/layout-parameters'; +import layouts from '../../../../studio/content/templates/layouts.json'; +import standardLayout from '../../../../studio/content/standard-layout.json'; +import {LayoutCanvas} from '../../../../studio/components/LayoutCanvas'; +import {parameters,LayoutParameterError} from '../../../../studio/lib/layout-parameters'; const workspaceSchema={ added:'可选数组,最多100项:{id:"user-唯一ID",kind:"text|image|decoration",role:"名称",box:[x,y,width,height],direction:"horizontal",text:"文字或null",reading:"custom",shape:"rectangle|ellipse(装饰可选)"}', layers:'按原生或新增元素ID索引:x/y -10000..10000,width/height 0.1..10000,rotation -360..360,opacity 0..1,radius 0..5000,z -1000..1000,hidden/locked 布尔值;坐标和字号均为模板单位', diff --git a/apps/studio/src/pages/api/layouts/index.ts b/apps/waterfall-wiki/src/pages/api/studio-ui/layouts/index.ts similarity index 89% rename from apps/studio/src/pages/api/layouts/index.ts rename to apps/waterfall-wiki/src/pages/api/studio-ui/layouts/index.ts index cb7cd0a..e039231 100644 --- a/apps/studio/src/pages/api/layouts/index.ts +++ b/apps/waterfall-wiki/src/pages/api/studio-ui/layouts/index.ts @@ -1,6 +1,6 @@ import type {NextApiRequest,NextApiResponse} from 'next'; -import fixed from '../../../content/templates/layouts.json'; -import standard from '../../../content/standard-layout.json'; +import fixed from '../../../../studio/content/templates/layouts.json'; +import standard from '../../../../studio/content/standard-layout.json'; export default function handler(req:NextApiRequest,res:NextApiResponse){ if(!['GET','HEAD'].includes(req.method||'')){res.setHeader('Allow','GET, HEAD');return res.status(405).end()} const entries=[standard,...fixed].map(l=>({id:l.id,name:l.name,family:l.id==='standard-01'?'standard':'fixed',url:'/studio/layouts/'+l.id,api:'/studio/api/layouts/'+l.id})); diff --git a/apps/studio/src/pages/graphic/index.tsx b/apps/waterfall-wiki/src/pages/graphic/index.tsx similarity index 98% rename from apps/studio/src/pages/graphic/index.tsx rename to apps/waterfall-wiki/src/pages/graphic/index.tsx index 0a66ae1..b01fd13 100644 --- a/apps/studio/src/pages/graphic/index.tsx +++ b/apps/waterfall-wiki/src/pages/graphic/index.tsx @@ -1,11 +1,11 @@ -import { ThemeToggle } from "../../components/ThemeToggle"; -import { Brand } from "../../components/Brand"; +import { ThemeToggle } from "../../studio/components/ThemeToggle"; +import { Brand } from "../../studio/components/Brand"; import Head from 'next/head'; import {useEffect,useRef,useState,type PointerEvent} from 'react'; import {MousePointer2,Type,Square,Circle,Plus,Minus,Undo2,Redo2,Download,Layers,ImagePlus,Trash2,Copy,Eye,EyeOff,Lock,Unlock,Scan,ChevronRight,ArrowUpRight,PenTool,X,Hand,Link2,BookOpen,PanelLeft,PanelRight} from 'lucide-react'; -import {newElementId} from '../../lib/native-layout'; -import styles from '../../styles/graphic.module.css'; -import {parseGraphicDocument,type GraphicItem as Item,type GraphicDocument as Doc} from '../../lib/graphic-document'; +import {newElementId} from '../../studio/lib/native-layout'; +import styles from '../../studio/styles/graphic.module.css'; +import {parseGraphicDocument,type GraphicItem as Item,type GraphicDocument as Doc} from '../../studio/lib/graphic-document'; const blank:Doc={name:'未命名设计',background:'#ffffff',items:[]}; const storage='studio.graphic.v1'; export default function Graphic(){ diff --git a/apps/studio/src/pages/infinite_canvas/index.tsx b/apps/waterfall-wiki/src/pages/infinite_canvas/index.tsx similarity index 97% rename from apps/studio/src/pages/infinite_canvas/index.tsx rename to apps/waterfall-wiki/src/pages/infinite_canvas/index.tsx index 0b78adc..cf204ec 100644 --- a/apps/studio/src/pages/infinite_canvas/index.tsx +++ b/apps/waterfall-wiki/src/pages/infinite_canvas/index.tsx @@ -1,13 +1,13 @@ -import { ThemeToggle } from "../../components/ThemeToggle"; -import { Brand } from "../../components/Brand"; +import { ThemeToggle } from "../../studio/components/ThemeToggle"; +import { Brand } from "../../studio/components/Brand"; import Head from 'next/head'; import {useEffect,useRef,useState} from 'react'; import {ArrowLeft,Plus,MousePointer2,Hand,ImagePlus,Type,Workflow,Undo2,Redo2,Minus,Maximize,Download,Upload,Trash2,Link2,Sparkles,ChevronRight,Sun,Moon} from 'lucide-react'; -import s from '../../styles/infinite-canvas.module.css'; -import CanvasNodeView from '../../components/canvas/CanvasNodeView'; -import CanvasComposer from '../../components/canvas/CanvasComposer'; -import CanvasAgentPanel,{type CanvasAgentHandle} from '../../components/canvas/CanvasAgentPanel'; -import {parseCanvasDocument,deriveNode,connectNodes,removeCanvasNode,referencedNodes,agentTask,type CanvasNode as Card,type CanvasDocument as Doc} from '../../lib/canvas-document'; +import s from '../../studio/styles/infinite-canvas.module.css'; +import CanvasNodeView from '../../studio/components/canvas/CanvasNodeView'; +import CanvasComposer from '../../studio/components/canvas/CanvasComposer'; +import CanvasAgentPanel,{type CanvasAgentHandle} from '../../studio/components/canvas/CanvasAgentPanel'; +import {parseCanvasDocument,deriveNode,connectNodes,removeCanvasNode,referencedNodes,agentTask,type CanvasNode as Card,type CanvasDocument as Doc} from '../../studio/lib/canvas-document'; const example:Doc={version:1,cards:[{id:'brief',kind:'text',title:'01 / 创意简报',text:'把日常,拍成一场电影。\n\n一段关于城市清晨的视觉散文。低饱和色彩、自然光与细腻颗粒,让每一帧都有呼吸感。',x:70,y:140},{id:'reference',kind:'image',title:'02 / 视觉参考',text:'晨光 · 建筑 · 胶片质感\n上传你的参考图片',x:460,y:80},{id:'shot',kind:'prompt',title:'03 / 镜头构思',text:'清晨,阳光穿过窗边的植物,落在安静的房间里。缓慢推进镜头,35mm 胶片,温暖柔和的光线。',x:850,y:160}],links:[['brief','reference'],['reference','shot']]}; const initial:Doc={version:1,cards:[],links:[]}; const key='studio.infinite-canvas.v1'; diff --git a/apps/studio/src/pages/designs/[id].tsx b/apps/waterfall-wiki/src/pages/studio/designs/[id].tsx similarity index 63% rename from apps/studio/src/pages/designs/[id].tsx rename to apps/waterfall-wiki/src/pages/studio/designs/[id].tsx index 753df12..8d2dd80 100644 --- a/apps/studio/src/pages/designs/[id].tsx +++ b/apps/waterfall-wiki/src/pages/studio/designs/[id].tsx @@ -1,11 +1,11 @@ import type {GetServerSideProps} from 'next'; -import {readDesign} from '../../lib/design-server'; -import {designUrl} from '../../lib/native-layout'; +import {readDesign} from '../../../studio/lib/design-server'; +import {designUrl} from '../../../studio/lib/native-layout'; export default function DesignPage(){return null} export const getServerSideProps:GetServerSideProps=async({params,req,res})=>{ res.setHeader('Cache-Control','private, no-store'); const design=await readDesign(params?.id,req); if(!design)return {notFound:true}; - // Next adds the configured /studio basePath to server redirects. - return {redirect:{destination:designUrl(design.layoutId,design.id).slice('/studio'.length),permanent:false}}; + // The unified application has no basePath; keep the full historical URL. + return {redirect:{destination:designUrl(design.layoutId,design.id),permanent:false}}; }; diff --git a/apps/studio/src/pages/ideas/[slug].tsx b/apps/waterfall-wiki/src/pages/studio/ideas/[slug].tsx similarity index 82% rename from apps/studio/src/pages/ideas/[slug].tsx rename to apps/waterfall-wiki/src/pages/studio/ideas/[slug].tsx index 2b3174f..6888162 100644 --- a/apps/studio/src/pages/ideas/[slug].tsx +++ b/apps/waterfall-wiki/src/pages/studio/ideas/[slug].tsx @@ -1,10 +1,10 @@ import type {GetStaticPaths,GetStaticProps} from 'next'; import Head from 'next/head'; import Link from 'next/link'; -import {LayoutSelection} from '../../components/LayoutSelection'; -import {tutorialPost} from '../../content/tool-links'; -import {TemplateTrial} from '../../components/TemplateTrial'; -import {ideas,type Idea} from '../../content/ideas'; +import {LayoutSelection} from '../../../studio/components/LayoutSelection'; +import {tutorialPost} from '../../../studio/content/tool-links'; +import {TemplateTrial} from '../../../studio/components/TemplateTrial'; +import {ideas,type Idea} from '../../../studio/content/ideas'; export default function IdeaPage({idea}:{idea:Idea}){if(idea.tool==='layout-selection')return ;if(idea.template)return ;return
{idea.title} · 创作站
← 啡跃使用教程导出内容包 ↓Markdown ↓

{idea.title}

{idea.summary}

{idea.slides.map((s,i)=>
ENDOLEAP / {String(i+1).padStart(2,'0')}

{s.heading}

{s.body}

记录灵感 · 自由表达
)}
} export const getStaticPaths:GetStaticPaths=async()=>({paths:ideas.map(i=>({params:{slug:i.slug}})),fallback:false}); export const getStaticProps:GetStaticProps=async({params})=>{const idea=ideas.find(i=>i.slug===params?.slug);return idea?{props:{idea}}:{notFound:true}}; diff --git a/apps/studio/src/pages/layouts/[layoutId].tsx b/apps/waterfall-wiki/src/pages/studio/layouts/[layoutId].tsx similarity index 64% rename from apps/studio/src/pages/layouts/[layoutId].tsx rename to apps/waterfall-wiki/src/pages/studio/layouts/[layoutId].tsx index 8ce0979..b5759e4 100644 --- a/apps/studio/src/pages/layouts/[layoutId].tsx +++ b/apps/waterfall-wiki/src/pages/studio/layouts/[layoutId].tsx @@ -1,11 +1,11 @@ import type {GetServerSideProps} from 'next'; -import {NativeResult} from '../../components/NativeResult'; -import type {Layout} from '../../lib/layout-parameters'; -import fixedLayouts from '../../content/templates/layouts.json'; -import standardLayout from '../../content/standard-layout.json'; +import {NativeResult} from '../../../studio/components/NativeResult'; +import type {Layout} from '../../../studio/lib/layout-parameters'; +import fixedLayouts from '../../../studio/content/templates/layouts.json'; +import standardLayout from '../../../studio/content/standard-layout.json'; const layouts=[...fixedLayouts,standardLayout]; -import {nativeLayout,designUrl} from '../../lib/native-layout'; -import {readDesign} from '../../lib/design-server'; +import {nativeLayout,designUrl} from '../../../studio/lib/native-layout'; +import {readDesign} from '../../../studio/lib/design-server'; export default function LayoutPage({layout}:{layout:Layout}){return } export const getServerSideProps:GetServerSideProps<{layout:Layout}>=async({params,query,req,res})=>{ res.setHeader('Cache-Control','private, no-store'); diff --git a/apps/studio/src/pages/template-layout.tsx b/apps/waterfall-wiki/src/pages/studio/template-layout.tsx similarity index 65% rename from apps/studio/src/pages/template-layout.tsx rename to apps/waterfall-wiki/src/pages/studio/template-layout.tsx index 21f5e72..9b744cf 100644 --- a/apps/studio/src/pages/template-layout.tsx +++ b/apps/waterfall-wiki/src/pages/studio/template-layout.tsx @@ -1,11 +1,11 @@ import type {GetServerSideProps} from 'next'; -import {NativeSeries} from '../components/NativeSeries'; -import {NativeResult} from '../components/NativeResult'; -import fixedLayouts from '../content/templates/layouts.json'; -import standardLayout from '../content/standard-layout.json'; -import {readDesign} from '../lib/design-server'; -import {nativeLayout,type Design} from '../lib/native-layout'; -import type {Layout} from '../lib/layout-parameters'; +import {NativeSeries} from '../../studio/components/NativeSeries'; +import {NativeResult} from '../../studio/components/NativeResult'; +import fixedLayouts from '../../studio/content/templates/layouts.json'; +import standardLayout from '../../studio/content/standard-layout.json'; +import {readDesign} from '../../studio/lib/design-server'; +import {nativeLayout,type Design} from '../../studio/lib/native-layout'; +import type {Layout} from '../../studio/lib/layout-parameters'; type Props={layout:Layout;design:Design;page:number|null;quality:boolean}; export default function TemplateLayout(props:Props){return props.design.document?:} diff --git a/apps/studio/src/components/Brand.tsx b/apps/waterfall-wiki/src/studio/components/Brand.tsx similarity index 100% rename from apps/studio/src/components/Brand.tsx rename to apps/waterfall-wiki/src/studio/components/Brand.tsx diff --git a/apps/studio/src/components/LayoutCanvas.tsx b/apps/waterfall-wiki/src/studio/components/LayoutCanvas.tsx similarity index 100% rename from apps/studio/src/components/LayoutCanvas.tsx rename to apps/waterfall-wiki/src/studio/components/LayoutCanvas.tsx diff --git a/apps/studio/src/components/LayoutSelection.tsx b/apps/waterfall-wiki/src/studio/components/LayoutSelection.tsx similarity index 100% rename from apps/studio/src/components/LayoutSelection.tsx rename to apps/waterfall-wiki/src/studio/components/LayoutSelection.tsx diff --git a/apps/studio/src/components/LayoutWorkbench.tsx b/apps/waterfall-wiki/src/studio/components/LayoutWorkbench.tsx similarity index 100% rename from apps/studio/src/components/LayoutWorkbench.tsx rename to apps/waterfall-wiki/src/studio/components/LayoutWorkbench.tsx diff --git a/apps/studio/src/components/NativeCurvedCopy.tsx b/apps/waterfall-wiki/src/studio/components/NativeCurvedCopy.tsx similarity index 100% rename from apps/studio/src/components/NativeCurvedCopy.tsx rename to apps/waterfall-wiki/src/studio/components/NativeCurvedCopy.tsx diff --git a/apps/studio/src/components/NativePreview.tsx b/apps/waterfall-wiki/src/studio/components/NativePreview.tsx similarity index 100% rename from apps/studio/src/components/NativePreview.tsx rename to apps/waterfall-wiki/src/studio/components/NativePreview.tsx diff --git a/apps/studio/src/components/NativeResult.tsx b/apps/waterfall-wiki/src/studio/components/NativeResult.tsx similarity index 100% rename from apps/studio/src/components/NativeResult.tsx rename to apps/waterfall-wiki/src/studio/components/NativeResult.tsx diff --git a/apps/studio/src/components/NativeSeries.tsx b/apps/waterfall-wiki/src/studio/components/NativeSeries.tsx similarity index 100% rename from apps/studio/src/components/NativeSeries.tsx rename to apps/waterfall-wiki/src/studio/components/NativeSeries.tsx diff --git a/apps/studio/src/components/Ref17Cover.tsx b/apps/waterfall-wiki/src/studio/components/Ref17Cover.tsx similarity index 100% rename from apps/studio/src/components/Ref17Cover.tsx rename to apps/waterfall-wiki/src/studio/components/Ref17Cover.tsx diff --git a/apps/studio/src/components/SourceCover.tsx b/apps/waterfall-wiki/src/studio/components/SourceCover.tsx similarity index 100% rename from apps/studio/src/components/SourceCover.tsx rename to apps/waterfall-wiki/src/studio/components/SourceCover.tsx diff --git a/apps/studio/src/components/TemplateTrial.tsx b/apps/waterfall-wiki/src/studio/components/TemplateTrial.tsx similarity index 100% rename from apps/studio/src/components/TemplateTrial.tsx rename to apps/waterfall-wiki/src/studio/components/TemplateTrial.tsx diff --git a/apps/studio/src/components/ThemeToggle.tsx b/apps/waterfall-wiki/src/studio/components/ThemeToggle.tsx similarity index 100% rename from apps/studio/src/components/ThemeToggle.tsx rename to apps/waterfall-wiki/src/studio/components/ThemeToggle.tsx diff --git a/apps/studio/src/components/canvas/CanvasAgentPanel.tsx b/apps/waterfall-wiki/src/studio/components/canvas/CanvasAgentPanel.tsx similarity index 100% rename from apps/studio/src/components/canvas/CanvasAgentPanel.tsx rename to apps/waterfall-wiki/src/studio/components/canvas/CanvasAgentPanel.tsx diff --git a/apps/studio/src/components/canvas/CanvasComposer.tsx b/apps/waterfall-wiki/src/studio/components/canvas/CanvasComposer.tsx similarity index 100% rename from apps/studio/src/components/canvas/CanvasComposer.tsx rename to apps/waterfall-wiki/src/studio/components/canvas/CanvasComposer.tsx diff --git a/apps/studio/src/components/canvas/CanvasNodeView.tsx b/apps/waterfall-wiki/src/studio/components/canvas/CanvasNodeView.tsx similarity index 100% rename from apps/studio/src/components/canvas/CanvasNodeView.tsx rename to apps/waterfall-wiki/src/studio/components/canvas/CanvasNodeView.tsx diff --git a/apps/studio/src/content/ideas.ts b/apps/waterfall-wiki/src/studio/content/ideas.ts similarity index 100% rename from apps/studio/src/content/ideas.ts rename to apps/waterfall-wiki/src/studio/content/ideas.ts diff --git a/apps/studio/src/content/standard-layout.json b/apps/waterfall-wiki/src/studio/content/standard-layout.json similarity index 100% rename from apps/studio/src/content/standard-layout.json rename to apps/waterfall-wiki/src/studio/content/standard-layout.json diff --git a/apps/studio/src/content/templates/layouts.json b/apps/waterfall-wiki/src/studio/content/templates/layouts.json similarity index 100% rename from apps/studio/src/content/templates/layouts.json rename to apps/waterfall-wiki/src/studio/content/templates/layouts.json diff --git a/apps/studio/src/content/templates/ref-17.json b/apps/waterfall-wiki/src/studio/content/templates/ref-17.json similarity index 100% rename from apps/studio/src/content/templates/ref-17.json rename to apps/waterfall-wiki/src/studio/content/templates/ref-17.json diff --git a/apps/studio/src/content/templates/selection.json b/apps/waterfall-wiki/src/studio/content/templates/selection.json similarity index 100% rename from apps/studio/src/content/templates/selection.json rename to apps/waterfall-wiki/src/studio/content/templates/selection.json diff --git a/apps/studio/src/content/tool-links.ts b/apps/waterfall-wiki/src/studio/content/tool-links.ts similarity index 100% rename from apps/studio/src/content/tool-links.ts rename to apps/waterfall-wiki/src/studio/content/tool-links.ts diff --git a/apps/studio/src/lib/api-documentation.ts b/apps/waterfall-wiki/src/studio/lib/api-documentation.ts similarity index 100% rename from apps/studio/src/lib/api-documentation.ts rename to apps/waterfall-wiki/src/studio/lib/api-documentation.ts diff --git a/apps/waterfall-wiki/src/studio/lib/canvas-document.ts b/apps/waterfall-wiki/src/studio/lib/canvas-document.ts new file mode 100644 index 0000000..8765f32 --- /dev/null +++ b/apps/waterfall-wiki/src/studio/lib/canvas-document.ts @@ -0,0 +1 @@ +export * from '../../lib/canvas-document.ts'; diff --git a/apps/studio/src/lib/design-server.ts b/apps/waterfall-wiki/src/studio/lib/design-server.ts similarity index 93% rename from apps/studio/src/lib/design-server.ts rename to apps/waterfall-wiki/src/studio/lib/design-server.ts index 9510dd9..4b83f5f 100644 --- a/apps/studio/src/lib/design-server.ts +++ b/apps/waterfall-wiki/src/studio/lib/design-server.ts @@ -9,7 +9,7 @@ import type {Design} from './native-layout'; /** Only forward the session to our fixed private Wiki service, never a request Host. */ export async function readDesign(id:unknown,req:IncomingMessage):Promise{ if(!uuid(id))return null; - const origin=process.env.STUDIO_WIKI_ORIGIN||'http://waterfall-wiki:3000'; + const origin=process.env.STUDIO_WIKI_ORIGIN||`http://127.0.0.1:${process.env.PORT || '3000'}`; const headers:Record={}; if(req.headers.cookie)headers.cookie=req.headers.cookie; if(req.headers.authorization)headers.authorization=req.headers.authorization; diff --git a/apps/studio/src/lib/graphic-document.ts b/apps/waterfall-wiki/src/studio/lib/graphic-document.ts similarity index 100% rename from apps/studio/src/lib/graphic-document.ts rename to apps/waterfall-wiki/src/studio/lib/graphic-document.ts diff --git a/apps/studio/src/lib/layout-images-client.ts b/apps/waterfall-wiki/src/studio/lib/layout-images-client.ts similarity index 100% rename from apps/studio/src/lib/layout-images-client.ts rename to apps/waterfall-wiki/src/studio/lib/layout-images-client.ts diff --git a/apps/studio/src/lib/layout-images.ts b/apps/waterfall-wiki/src/studio/lib/layout-images.ts similarity index 65% rename from apps/studio/src/lib/layout-images.ts rename to apps/waterfall-wiki/src/studio/lib/layout-images.ts index e90e4c8..c764ba3 100644 --- a/apps/studio/src/lib/layout-images.ts +++ b/apps/waterfall-wiki/src/studio/lib/layout-images.ts @@ -4,7 +4,7 @@ import type {Parameters} from './layout-parameters'; export async function embedLayoutImages(req:NextApiRequest,params:Parameters){ const ids=[...new Set(Object.values(params.images).flatMap(v=>v.assetId?[v.assetId]:[]))],sources:Record={};let total=0; const headers:Record={};if(req.headers.authorization)headers.authorization=req.headers.authorization;if(req.headers.cookie)headers.cookie=req.headers.cookie; - for(const id of ids){const url=(process.env.STUDIO_WIKI_ORIGIN||'http://waterfall-wiki:3000')+'/api/preview?'+new URLSearchParams({source:'/api/media/'+id,width:'960'}),r=await fetch(url,{headers,signal:AbortSignal.timeout(15000),redirect:'error'});if(!r.ok||r.headers.get('content-type')?.split(';')[0]!=='image/webp')throw Error('图片预览不可访问,请检查图片 ID 与登录状态');const chunks:Buffer[]=[];const reader=r.body!.getReader();try{while(true){const {done,value}=await reader.read();if(done)break;total+=value.length;if(total>16*1024*1024)throw Error('图片预览总计超限');chunks.push(Buffer.from(value))}}finally{await reader.cancel()}sources[id]='data:image/webp;base64,'+Buffer.concat(chunks).toString('base64');} + for(const id of ids){const url=(process.env.STUDIO_WIKI_ORIGIN||`http://127.0.0.1:${process.env.PORT || '3000'}`)+'/api/preview?'+new URLSearchParams({source:'/api/media/'+id,width:'960'}),r=await fetch(url,{headers,signal:AbortSignal.timeout(15000),redirect:'error'});if(!r.ok||r.headers.get('content-type')?.split(';')[0]!=='image/webp')throw Error('图片预览不可访问,请检查图片 ID 与登录状态');const chunks:Buffer[]=[];const reader=r.body!.getReader();try{while(true){const {done,value}=await reader.read();if(done)break;total+=value.length;if(total>16*1024*1024)throw Error('图片预览总计超限');chunks.push(Buffer.from(value))}}finally{await reader.cancel()}sources[id]='data:image/webp;base64,'+Buffer.concat(chunks).toString('base64');} return sources; } @@ -14,7 +14,7 @@ export async function embedSvgMedia(req:NextApiRequest,svg:string){ const headers:Record={};if(req.headers.authorization)headers.authorization=req.headers.authorization;if(req.headers.cookie)headers.cookie=req.headers.cookie; let total=0; for(const path of paths){ - const response=await fetch((process.env.STUDIO_WIKI_ORIGIN||'http://waterfall-wiki:3000')+path,{headers,redirect:'error',signal:AbortSignal.timeout(15000)}); + const response=await fetch((process.env.STUDIO_WIKI_ORIGIN||`http://127.0.0.1:${process.env.PORT || '3000'}`)+path,{headers,redirect:'error',signal:AbortSignal.timeout(15000)}); const mime=response.headers.get('content-type')?.split(';')[0]; if(!response.ok||!mime||!['image/png','image/jpeg','image/webp'].includes(mime))throw Error('模板图片不可访问'); const chunks:Uint8Array[]=[];const reader=response.body!.getReader(); diff --git a/apps/waterfall-wiki/src/studio/lib/layout-parameters.ts b/apps/waterfall-wiki/src/studio/lib/layout-parameters.ts new file mode 100644 index 0000000..9ae0404 --- /dev/null +++ b/apps/waterfall-wiki/src/studio/lib/layout-parameters.ts @@ -0,0 +1 @@ +export * from '../../lib/layout-parameters.ts'; diff --git a/apps/studio/src/lib/native-layout.ts b/apps/waterfall-wiki/src/studio/lib/native-layout.ts similarity index 100% rename from apps/studio/src/lib/native-layout.ts rename to apps/waterfall-wiki/src/studio/lib/native-layout.ts diff --git a/apps/studio/src/lib/render-cover.ts b/apps/waterfall-wiki/src/studio/lib/render-cover.ts similarity index 100% rename from apps/studio/src/lib/render-cover.ts rename to apps/waterfall-wiki/src/studio/lib/render-cover.ts diff --git a/apps/waterfall-wiki/src/studio/lib/series.ts b/apps/waterfall-wiki/src/studio/lib/series.ts new file mode 100644 index 0000000..36adb9f --- /dev/null +++ b/apps/waterfall-wiki/src/studio/lib/series.ts @@ -0,0 +1 @@ +export * from '../../lib/series.ts'; diff --git a/apps/studio/src/styles/canvas-agent.module.css b/apps/waterfall-wiki/src/studio/styles/canvas-agent.module.css similarity index 100% rename from apps/studio/src/styles/canvas-agent.module.css rename to apps/waterfall-wiki/src/studio/styles/canvas-agent.module.css diff --git a/apps/studio/src/styles/canvas-composer.module.css b/apps/waterfall-wiki/src/studio/styles/canvas-composer.module.css similarity index 100% rename from apps/studio/src/styles/canvas-composer.module.css rename to apps/waterfall-wiki/src/studio/styles/canvas-composer.module.css diff --git a/apps/studio/src/styles/canvas-node.module.css b/apps/waterfall-wiki/src/studio/styles/canvas-node.module.css similarity index 100% rename from apps/studio/src/styles/canvas-node.module.css rename to apps/waterfall-wiki/src/studio/styles/canvas-node.module.css diff --git a/apps/studio/src/styles/globals.css b/apps/waterfall-wiki/src/studio/styles/globals.css similarity index 92% rename from apps/studio/src/styles/globals.css rename to apps/waterfall-wiki/src/studio/styles/globals.css index d8126d6..fd43ea1 100644 --- a/apps/studio/src/styles/globals.css +++ b/apps/waterfall-wiki/src/studio/styles/globals.css @@ -1,7 +1,8 @@ -@import "tailwindcss"; -@import "@endoleap/brand/themes.css"; -@import "@endoleap/brand/tailwind.css"; -*{box-sizing:border-box}body{margin:0;color:var(--foreground-strong);background:var(--selection);font-family:Arial,"PingFang SC",sans-serif}a{color:inherit;text-decoration:none}button,a{cursor:pointer}button{border:0;padding:12px 22px;border-radius:24px}.workspace{display:grid;grid-template-columns:240px 1fr;min-height:100vh}aside{padding:38px 26px;border-right:1px solid var(--border);background:var(--surface)}aside a{display:block;margin-top:30px}.logo{color:var(--primary)}section{padding:70px 6vw}h1{font-size:40px;letter-spacing:-1.5px}section>p,.hint{color:var(--muted-foreground)}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,340px));gap:24px;margin-top:45px}.card{background:var(--surface);padding:14px;border-radius:22px;border:1px solid var(--border)}.cover{background:var(--selection);aspect-ratio:4/5;border-radius:14px;padding:36px;display:flex;align-items:center;font-size:35px;font-weight:bold}.card h2{font-size:18px}.card p{line-height:1.8;color:var(--muted-foreground)}.card span{color:var(--primary)}.hint{margin-top:40px;font-size:13px}.editor{max-width:1200px;margin:auto;padding:30px}header{display:flex;align-items:center;gap:24px}header a:first-child{margin-right:auto}.slides{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:40px}.slide{aspect-ratio:3/4;padding:35px;border-radius:20px;display:flex;flex-direction:column}.slide h2{font-size:38px;margin-top:auto}.slide p{line-height:1.9}.slide footer{margin-top:auto;font-size:12px}@media(max-width:760px){.workspace{grid-template-columns:1fr}aside{border-bottom:1px solid var(--border)}aside a{display:inline-block;margin:20px 20px 0 0}section{padding:30px}.slides{grid-template-columns:1fr}header{flex-wrap:wrap}h1{font-size:30px}}@media print{header{display:none}.slides{display:block}.slide{width:180mm;height:240mm;break-after:page;print-color-adjust:exact;border-radius:0}.editor>h1,.editor>p{display:none}} +@scope (.studio-app) { + + + +*{box-sizing:border-box}:scope{margin:0;color:var(--foreground-strong);background:var(--selection);font-family:Arial,"PingFang SC",sans-serif}a{color:inherit;text-decoration:none}button,a{cursor:pointer}button{border:0;padding:12px 22px;border-radius:24px}.workspace{display:grid;grid-template-columns:240px 1fr;min-height:100vh}aside{padding:38px 26px;border-right:1px solid var(--border);background:var(--surface)}aside a{display:block;margin-top:30px}.logo{color:var(--primary)}section{padding:70px 6vw}h1{font-size:40px;letter-spacing:-1.5px}section>p,.hint{color:var(--muted-foreground)}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,340px));gap:24px;margin-top:45px}.card{background:var(--surface);padding:14px;border-radius:22px;border:1px solid var(--border)}.cover{background:var(--selection);aspect-ratio:4/5;border-radius:14px;padding:36px;display:flex;align-items:center;font-size:35px;font-weight:bold}.card h2{font-size:18px}.card p{line-height:1.8;color:var(--muted-foreground)}.card span{color:var(--primary)}.hint{margin-top:40px;font-size:13px}.editor{max-width:1200px;margin:auto;padding:30px}header{display:flex;align-items:center;gap:24px}header a:first-child{margin-right:auto}.slides{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:40px}.slide{aspect-ratio:3/4;padding:35px;border-radius:20px;display:flex;flex-direction:column}.slide h2{font-size:38px;margin-top:auto}.slide p{line-height:1.9}.slide footer{margin-top:auto;font-size:12px}@media(max-width:760px){.workspace{grid-template-columns:1fr}aside{border-bottom:1px solid var(--border)}aside a{display:inline-block;margin:20px 20px 0 0}section{padding:30px}.slides{grid-template-columns:1fr}header{flex-wrap:wrap}h1{font-size:30px}}@media print{header{display:none}.slides{display:block}.slide{width:180mm;height:240mm;break-after:page;print-color-adjust:exact;border-radius:0}.editor>h1,.editor>p{display:none}} /* The reference art stays inside its native 113:174 canvas. */ .cover-template{padding:0;border-radius:0;display:block;aspect-ratio:113/174;background:#f4f2eb} @@ -93,3 +94,5 @@ .native-series{padding-top:24px}.series-page{margin:0 auto 32px}.series-page-number{text-align:center;font-size:12px;letter-spacing:.14em;color:var(--muted-foreground);margin:14px 0 0} .native-export{padding:0;background:transparent;min-height:0}.native-export .series-page{margin:0}.native-export .native-layout{width:1080px;max-width:none;margin:0} @media print{.native-series{padding:0}.series-page{margin:0;break-after:page}.series-page:last-child{break-after:auto}.series-page-number{display:none}} + +} diff --git a/apps/studio/src/styles/graphic.module.css b/apps/waterfall-wiki/src/studio/styles/graphic.module.css similarity index 100% rename from apps/studio/src/styles/graphic.module.css rename to apps/waterfall-wiki/src/studio/styles/graphic.module.css diff --git a/apps/studio/src/styles/infinite-canvas.module.css b/apps/waterfall-wiki/src/studio/styles/infinite-canvas.module.css similarity index 100% rename from apps/studio/src/styles/infinite-canvas.module.css rename to apps/waterfall-wiki/src/studio/styles/infinite-canvas.module.css diff --git a/apps/waterfall-wiki/tests/account-integration.ts b/apps/waterfall-wiki/tests/account-integration.ts deleted file mode 100644 index ea8c971..0000000 --- a/apps/waterfall-wiki/tests/account-integration.ts +++ /dev/null @@ -1,51 +0,0 @@ -import assert from 'node:assert/strict'; -import { randomUUID } from 'node:crypto'; -import { db } from '../src/lib/db'; -import { hashPassword,newToken,tokenDigest } from '../src/lib/account-crypto'; -import { resetPassword,loginAccount,verifyAccount,currentAccount,logoutAccount,rateLimit } from '../src/lib/account'; -export async function verifyEmailAccounts(base: string) { - const id=randomUUID(), email=id+'@example.com', password='a secure test password'; - const request=(token:string)=>new Request('https://wiki.example',{headers:{cookie:'wiki_account='+token}}); - try { - await db().query('INSERT INTO account_users(id,email,password_hash) VALUES ($1,$2,$3)',[id,email,await hashPassword(password)]); - assert.equal(await loginAccount(email,password),null,'unverified accounts cannot log in'); - const verification=newToken();await db().query("INSERT INTO account_tokens VALUES ($1,$2,'verify',now()+interval '1 minute')",[tokenDigest(verification),id]); - const results=await Promise.all([verifyAccount(verification),verifyAccount(verification)]);assert.deepEqual(results.sort(),[false,true]); - assert.equal(await loginAccount(email,'incorrect password'),null); - const token=await loginAccount(email,password);assert.ok(token); - assert.equal((await currentAccount(request(token))).id,id); - await db().query("UPDATE account_tokens SET expires_at=now()-interval '1 second' WHERE digest=$1",[tokenDigest(token)]); - assert.equal(await currentAccount(request(token)),null,'expired sessions rejected'); - const fresh=await loginAccount(email,password);assert.ok(fresh);await logoutAccount(request(fresh));assert.equal(await currentAccount(request(fresh)),null,'logout revokes token'); - const oldSession=await loginAccount(email,password);assert.ok(oldSession); - const resets=[newToken(),newToken()],replacement='a new secure test password'; - for(const value of resets)await db().query("INSERT INTO account_tokens VALUES ($1,$2,'reset',now()+interval '30 minutes')",[tokenDigest(value),id]); - const resetResults=await Promise.all(resets.map(value=>resetPassword(value,replacement))); - assert.deepEqual(resetResults.sort(),[false,true],'Only one concurrent reset may succeed for one account'); - assert.equal(await currentAccount(request(oldSession)),null,'Reset revokes existing sessions'); - assert.equal(await loginAccount(email,password),null,'Original password is no longer accepted'); - assert.ok(await loginAccount(email,replacement)); - assert.equal(await resetPassword(resets[0],password),false,'Reset links are single-use, including sibling links'); - const expired=newToken();await db().query("INSERT INTO account_tokens VALUES ($1,$2,'reset',now()-interval '1 minute')",[tokenDigest(expired),id]); - assert.equal(await resetPassword(expired,password),false); - const wrongKind=newToken();await db().query("INSERT INTO account_tokens VALUES ($1,$2,'verify',now()+interval '30 minutes')",[tokenDigest(wrongKind),id]); - assert.equal(await resetPassword(wrongKind,password),false,'Email verification cannot reset a password'); - assert.equal(await resetPassword(newToken(),'short'),false); - const post=(action:string,body:object,origin=process.env.ACCOUNT_PUBLIC_ORIGIN||base)=>fetch(base+'/api/account/'+action,{method:'POST',headers:{'Content-Type':'application/json',Origin:origin},body:JSON.stringify(body)}); - assert.equal((await post('forgot',{email},'https://untrusted.invalid')).status,403,'Cross-origin recovery is rejected'); - const unknown=await post('forgot',{email:randomUUID()+'@example.com'}); - assert.equal(unknown.status,200,'Unknown email receives the same non-enumerating response without sending mail'); - assert.match((await unknown.json()).message,/若该邮箱/); - const httpToken=newToken(),httpPassword='http replacement secure password'; - const priorHttpSession=await loginAccount(email,replacement);assert.ok(priorHttpSession); - await db().query("INSERT INTO account_tokens VALUES ($1,$2,'reset',now()+interval '30 minutes')",[tokenDigest(httpToken),id]); - const changed=await post('reset',{token:httpToken,password:httpPassword}); - assert.equal(changed.status,200); - assert.match(changed.headers.get('set-cookie')||'',/Max-Age=0/); - assert.equal(await currentAccount(request(priorHttpSession)),null); - assert.ok(await loginAccount(email,httpPassword),'HTTP recovery changes the stored password'); - assert.equal((await post('reset',{token:httpToken,password:replacement})).status,400,'HTTP recovery rejects a consumed token'); - const key=randomUUID();assert.deepEqual(await Promise.all(Array.from({length:5},()=>rateLimit(key,3))).then(a=>a.sort()),[false,false,true,true,true]); - await db().query('DELETE FROM account_limits WHERE key=$1',[tokenDigest(key)]); - } finally {await db().query('DELETE FROM account_users WHERE id=$1',[id]);} -} diff --git a/apps/waterfall-wiki/tests/account.test.ts b/apps/waterfall-wiki/tests/account.test.ts deleted file mode 100644 index a95aa68..0000000 --- a/apps/waterfall-wiki/tests/account.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { normalizeEmail,validPassword,hashPassword,checkPassword,newToken,tokenDigest,accountCookie,trustedOrigin } from '../src/lib/account-crypto'; -test('canonical email and bounded passwords',()=>{assert.equal(normalizeEmail(' Test@Example.COM '),'test@example.com');assert.equal(normalizeEmail('a@b\r\nBcc:x@y.com'),null);assert.equal(validPassword('short'),false);assert.equal(validPassword('界'.repeat(100)),false);}); -test('salted passwords and constant size opaque token digests',async()=>{const a=await hashPassword('long enough password');const b=await hashPassword('long enough password');assert.notEqual(a,b);assert.equal(await checkPassword('long enough password',a),true);assert.equal(await checkPassword('wrong password',a),false);const t=newToken();assert.equal(t.length,64);assert.equal(tokenDigest(t).length,64);assert.notEqual(tokenDigest(t),t);}); -test('origin checks reject missing and cross-site origins',()=>{const r=(origin?:string,site?:string)=>new Request('https://wiki.example/api/account/login',{headers:{...(origin?{origin}:{}),...(site?{'sec-fetch-site':site}:{})}});assert.equal(trustedOrigin(r()),false);assert.equal(trustedOrigin(r('https://evil.example')),false);assert.equal(trustedOrigin(r('https://wiki.example','cross-site')),false);assert.equal(trustedOrigin(r('https://wiki.example')),true);}); -test('account cookie is distinct, HTTP-only and expires on logout',()=>{assert.match(accountCookie('abc'),/^wiki_account=abc;/);assert.match(accountCookie('abc'),/HttpOnly; SameSite=Lax/);assert.match(accountCookie('',true),/Max-Age=0/);}); diff --git a/apps/waterfall-wiki/tests/api-documentation.test.ts b/apps/waterfall-wiki/tests/api-documentation.test.ts deleted file mode 100644 index 6183ec4..0000000 --- a/apps/waterfall-wiki/tests/api-documentation.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import assert from 'node:assert/strict'; -import {test} from 'node:test'; -import type {NextApiRequest,NextApiResponse} from 'next'; -import {apiDocuments,serveApiDocumentation} from '../src/lib/api-documentation'; -import index from '../src/pages/api/index'; - -function response(){ - const state={status:0,headers:{} as Record,body:undefined as unknown}; - const res={setHeader(name:string,value:string){state.headers[name]=value;return res;},status(code:number){state.status=code;return res;},send(body:unknown){state.body=body;return res;},json(body:unknown){state.body=body;return res;},end(){return res;}}; - return {state,res:res as unknown as NextApiResponse}; -} -const request=(method:string,agent_mode:unknown)=>({method,query:{agent_mode}} as unknown as NextApiRequest); - -test('every business handler serves documentation before validating parameters or accessing dependencies',async()=>{ - const routes:Record={ - '/api/canvases/{...path}':'canvases/[[...path]]','/api/explore-feed':'explore-feed','/api/feed':'feed','/api/notes/{id}':'notes/[id]', - '/api/posts/{id}/archive':'posts/[id]/archive','/api/posts':'posts/index','/api/tags':'tags','/api/upload':'upload', - '/api/media/{id}':'media/[id]','/api/preview':'preview', - '/api/skills/street-people':'skills/street-people','/api/health':'health','/api/ready':'ready', - }; - for(const [path,route] of Object.entries(routes)){ - const {default:handler}=await import('../src/pages/api/'+route+'.ts'); - for(const method of ['GET','HEAD']){ - const {state,res}=response(); - await handler(request(method,'tRuE'),res); - assert.equal(state.status,200,path); - assert.equal(state.headers['Content-Type'],'text/plain; charset=utf-8',path); - if(method==='HEAD')assert.equal(state.body,undefined);else { - assert.ok(String(state.body).includes(path),path); - assert.ok(String(state.body).includes('参数'),path); - } - } - } - assert.deepEqual(Object.keys(routes).sort(),Object.keys(apiDocuments).sort()); -}); - -test('normal calls and mutations do not enter documentation mode',()=>{ - for(const [method,value] of [['GET',undefined],['GET','false'],['GET',['True','false']],['POST','True'],['PUT','True'],['DELETE','True']]){ - const {state,res}=response(); - assert.equal(serveApiDocumentation(request(method as string,value),res,'/api/posts'),false); - assert.equal(state.status,0); - } -}); - -test('index exposes every business endpoint and its text URL',()=>{ - const {state,res}=response();index(request('GET','True'),res); - for(const path of [...Object.keys(apiDocuments),'/studio/api/export/{slug}','/studio/api/health','/api/studio/designs','/api/studio/designs/{id}','/api/studio/designs/{id}/post'])assert.ok(String(state.body).includes(path+'?agent_mode=True')); -}); - -test('health keeps its regular JSON response',async()=>{ - const {default:handler}=await import('../src/pages/api/health'); - const {state,res}=response();handler(request('GET',undefined),res); - assert.equal(state.status,200);assert.equal((state.body as {status:string}).status,'ok'); -}); - - - -test('Studio docs describe native editing and atomic Wiki publication without accessing storage',async()=>{ - for(const route of ['studio/designs/index','studio/designs/[id]','studio/designs/[id]/post']){ - const {default:handler}=await import('../src/pages/api/'+route+'.ts'); - const {state,res}=response();await handler(request('GET','True'),res); - assert.equal(state.status,200);assert.match(String(state.body),/postUrl/);assert.match(String(state.body),/整体替换/);assert.match(String(state.body),/同一数据库事务/);assert.match(String(state.body),/原生页面/); - } -}); diff --git a/apps/waterfall-wiki/tests/api-publication.test.ts b/apps/waterfall-wiki/tests/api-publication.test.ts deleted file mode 100644 index 7d23289..0000000 --- a/apps/waterfall-wiki/tests/api-publication.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {randomUUID} from 'node:crypto'; -import {postInput} from '../src/lib/write'; -import {noteEditInput,withRevision} from '../src/lib/note-edits'; -import {publicPostId} from '../src/lib/model'; -import {entryText,type ExploreEntry} from '../src/lib/explore'; -test('generic API accepts stable public IDs while retaining UUID storage and rejecting invalid IDs',()=>{ - const publicId='0123456789abcdef01234567',stored=randomUUID(); - assert.equal(postInput.parse({kind:'article',title:'文本与图像',publicId}).publicId,publicId); - for(const id of ['bad','ABCDEF0123456789abcdef01','1'.repeat(25)])assert.equal(postInput.safeParse({kind:'article',title:'x',publicId:id}).success,false); - assert.equal(publicPostId({id:stored,public_id:publicId}),publicId);assert.equal(publicPostId({id:stored}),stored); -}); -test('media/cover edits are optional and change optimistic revisions independently of the text',()=>{ - const a=randomUUID(),b=randomUUID(); - const entry:ExploreEntry={id:'0123456789abcdef01234567',title:'笔记',description:'说明',markdown:'# 笔记\n\n正文',namespace:'Main',categories:[],imageCount:1,media:[],color:'#fff',assets:[{id:a,kind:'image',role:'content'}],coverAssetId:a}; - const before=withRevision(entry).revision; - assert.notEqual(withRevision({...entry,coverAssetId:b}).revision,before); - assert.notEqual(withRevision({...entry,assets:[{id:b,kind:'image',role:'content'}]}).revision,before); - const input={title:'笔记',description:'说明',markdown:'正文',tags:[],revision:before}; - assert.equal(noteEditInput.parse(input).assets,undefined); - assert.equal(noteEditInput.parse({...input,assets:[{id:a}],coverAssetId:null}).assets?.[0].role,'content'); -}); -test('AI text preserves JSON, code and media links as ordinary modalities',()=>{ - const id=randomUUID(),markdown='# 文本说明\n\n```json\n{"type":"text","box":[1,2,3,4]}\n```\n\n```tsx\nexport default function Example(){return
}\n```'; - const entry:ExploreEntry={id,title:'文本说明',description:'可访问',markdown,namespace:'Main',categories:[],imageCount:1,media:[{kind:'image',url:'/api/media/'+randomUUID(),title:'标注图'}],color:'#fff'}; - const text=entryText(entry);assert.ok(text.includes(markdown));assert.ok(text.includes(entry.media[0].url));assert.ok(text.includes('/explore/'+id)); -}); diff --git a/apps/waterfall-wiki/tests/auth.test.ts b/apps/waterfall-wiki/tests/auth.test.ts deleted file mode 100644 index 9c3890f..0000000 --- a/apps/waterfall-wiki/tests/auth.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import test from 'node:test'; -import assert from 'node:assert/strict'; -import {base32,totp,matchingStep,newAuthConfig,signSession,validSession,sessionCookie,safeReturn,authConfig,isLoginRedirect} from '../src/lib/auth'; -import {proxy} from '../src/proxy'; -import {NextRequest} from 'next/server'; -import {mkdtempSync,writeFileSync,rmSync} from 'node:fs'; -import {tmpdir} from 'node:os'; -import {join} from 'node:path'; -test('TOTP matches RFC 6238 SHA-1 test vectors',()=>{ - const secret=base32(Buffer.from('12345678901234567890')); - for(const [time,expected] of [[59,'94287082'],[1111111109,'07081804'],[1111111111,'14050471'],[1234567890,'89005924'],[2000000000,'69279037'],[20000000000,'65353130']] as const) assert.equal(totp(secret,Math.floor(time/30),8),expected); - assert.equal(matchingStep(secret,totp(secret,100),3000000),100); - assert.equal(matchingStep(secret,totp(secret,99),3000000),99); - assert.equal(matchingStep(secret,totp(secret,98),3000000),null); - assert.equal(matchingStep(secret,'123',3000000),null); -}); -test('JWT rejects tampering, expiry, wrong keys and malformed values',()=>{ - const config=newAuthConfig(),token=signSession(config,1000); - assert.equal(validSession(token,config,1000),true); - assert.equal(validSession(token,config,1000+604800),false); - assert.equal(validSession(token,config,900),false); - assert.equal(validSession(token,newAuthConfig(),1000),false); - assert.equal(validSession(token+'x',config,1000),false); - assert.equal(validSession('eyJhbGciOiJub25lIn0.e30.',config,1000),false); - assert.equal(validSession(undefined,config),false); -}); -test('cookies are HTTP-only and secure by default; redirects stay local',()=>{ - assert.match(sessionCookie('test'),/HttpOnly; SameSite=Lax; Max-Age=604800; Secure$/); - assert.match(sessionCookie('',true),/Max-Age=0/); - for(const path of ['https://evil.test','//evil.test','/\\evil.test','/login?next=/','/\nLocation: bad']) assert.equal(safeReturn(path),'/'); - assert.equal(safeReturn('/explore/123?q=test'),'/explore/123?q=test'); -}); -test('proxy exposes creative entry pages while protecting APIs, data and Studio documents; fails closed',()=>{ - const temp=mkdtempSync(join(tmpdir(),'prompt-auth-')); const old=process.env.AUTH_CONFIG_FILE; - try{ - const config=newAuthConfig(); process.env.AUTH_CONFIG_FILE=join(temp,'config.json'); writeFileSync(process.env.AUTH_CONFIG_FILE,JSON.stringify(config)); - for(const path of ['/library/import','/skills/example.png','/_next/data/build/library/import.json','/studio/layouts/ref-01']) assert.equal(proxy(new NextRequest('http://wiki.test'+path)).status,307,path); - for(const path of ['/api/upload','/studio/api/export','/studio/api/layouts','/api/feed']) assert.equal(proxy(new NextRequest('http://wiki.test'+path)).status,401,path); - for(const path of ['/','/_next/data/build/index.json','/llms.txt','/index.md','/agent','/agent/index.md','/api/agent-index','/api/guides','/write','/documents','/api/documents','/api/documents/preview','/guide/welcome','/protocol','/api/guides/welcome','/account','/api/account/session','/login','/brand-mark.svg','/_next/static/chunk.js','/api/health','/api/ready']) assert.equal(proxy(new NextRequest('http://wiki.test'+path)).status,200,path); - const creativeEntries=['/favicon.ico','/apple-touch-icon.png','/brand/logo-light.svg','/brand/logo-dark.svg','/brand/icon-dark.svg','/studio/favicon.ico','/studio/apple-touch-icon.png','/studio/brand/logo-light.svg','/studio/brand/logo-dark.svg','/studio/brand/icon-dark.svg','/workspace','/graphic','/infinite_canvas','/studio','/studio/graphic','/studio/infinite_canvas','/studio/api','/studio/_next/static/chunk.js']; - for(const path of creativeEntries){ - for(const method of ['GET','HEAD'])assert.equal(proxy(new NextRequest('http://wiki.test'+path,{method})).status,200,`${method} ${path}`); - assert.equal(proxy(new NextRequest('http://wiki.test'+path,{method:'POST'})).status,401,`POST ${path} must not bypass authentication`); - } - const headers={cookie:'prompt_session='+signSession(config),host:'wiki.test'}; - assert.equal(proxy(new NextRequest('http://wiki.test/api/feed',{headers})).status,200); - assert.equal(proxy(new NextRequest('http://wiki.test/api/posts',{method:'POST',headers:{...headers,origin:'http://evil.test'}})).status,403); - assert.equal(proxy(new NextRequest('http://wiki.test/api/posts',{method:'POST',headers:{...headers,origin:'http://wiki.test'}})).status,200); - assert.equal(proxy(new NextRequest('http://wiki.test/api/posts',{method:'POST',headers:{cookie:'wiki_account='+ 'a'.repeat(64)}})).status,401); - assert.equal(proxy(new NextRequest('http://wiki.test/guide/welcome',{method:'POST'})).status,401); - for(const path of ['/api/guides','/api/agent-index']) assert.equal(proxy(new NextRequest('http://wiki.test'+path,{method:'POST'})).status,401,path); - writeFileSync(process.env.AUTH_CONFIG_FILE,'{}'); assert.throws(()=>authConfig()); assert.equal(proxy(new NextRequest('http://wiki.test/api/posts')).status,503); - delete process.env.AUTH_CONFIG_FILE; - assert.equal(proxy(new NextRequest('http://wiki.test/api/posts')).status,503,'Missing admin configuration fails closed'); - }finally{if(old===undefined)delete process.env.AUTH_CONFIG_FILE;else process.env.AUTH_CONFIG_FILE=old;rmSync(temp,{recursive:true,force:true});} -}); - -test('login redirects recognize browser pages and Next.js data destinations',()=>{ - const base='http://wiki.test'; - for(const target of ['/login?next=%2F','http://wiki.test/login','/_next/data/build-123/login.json?next=%2F']) assert.equal(isLoginRedirect(target,base),true,target); - for(const target of [null,'','https://evil.test/login','//evil.test/login','/api/login.json','/_next/data/build-123/index.json']) assert.equal(isLoginRedirect(target,base),false,String(target)); -}); diff --git a/apps/waterfall-wiki/tests/brand-navigation.mjs b/apps/waterfall-wiki/tests/brand-navigation.mjs deleted file mode 100644 index b7bc03d..0000000 --- a/apps/waterfall-wiki/tests/brand-navigation.mjs +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Recover once from an idempotent document GET's transient proxy failure. - * This never retries hydration, theme assertions, or mutations, and shares one - * 60-second budget across both attempts rather than extending the deadline. - * @param {(timeout: number) => Promise<{ok(): boolean, status(): number} | null>} navigate - * @param {string} url - * @param {() => number} now - */ -export async function navigateBrandDocument(navigate, url, now = Date.now) { - const deadline = now() + 60000; - for (let attempt = 1; attempt <= 2; attempt++) { - const remaining = deadline - now(); - if (remaining <= 0) throw new Error(`Document navigation budget exhausted: ${url}`); - try { - const response = await navigate(remaining); - if (response?.ok()) return response; - const status = response?.status(); - if (attempt === 1 && [500, 502, 503, 504].includes(status ?? 0)) { - console.warn('Brand navigation proxy recovery', JSON.stringify({url, status, attempt})); - continue; - } - throw new Error(`Document navigation failed: ${url}, HTTP ${status ?? 'no response'}`); - } catch (error) { - if (attempt === 1 && /ECONNRESET|net::ERR_(?:CONNECTION_RESET|CONNECTION_CLOSED|EMPTY_RESPONSE|ABORTED)/.test(String(error))) { - console.warn('Brand navigation connection recovery', JSON.stringify({url, error: String(error), attempt})); - continue; - } - throw error; - } - } - throw new Error(`Document navigation failed: ${url}`); -} - -/** Load a document fully before relying on server-rendered text or leaving it. - * Only document GETs may recover; application assertions remain outside retry. - * @param {import('playwright-core').Page} page - * @param {string} url - * @param {boolean} reload - */ -export async function visitBrowserDocument(page, url, reload = false) { - return navigateBrandDocument(async timeout => { - const started = Date.now(); - const scripts = new Set(), failures = []; - const requested = request => { if (request.resourceType() === 'script') scripts.add(request); }; - const failed = request => { if (scripts.has(request)) failures.push(request.failure()?.errorText || 'unknown script failure'); }; - page.on('request', requested); - page.on('requestfailed', failed); - try { - const response = await (reload ? page.reload({waitUntil: 'domcontentloaded', timeout}) : page.goto(url, {waitUntil: 'domcontentloaded', timeout})); - if (response?.ok()) { - const remaining = timeout - (Date.now() - started); - if (remaining <= 0) throw new Error('Document hydration budget exhausted: ' + url); - try { - // Responsive layouts may hide the toggle. Its mounted ready marker - // proves hydration; each caller still checks the controls it needs. - // A cancelled script may be a preload duplicate. Hydration is the - // authority; recover only when cancellation actually blocks startup. - await page.locator('button[data-theme-ready="true"]').first().waitFor({state: 'attached', timeout: failures.length ? Math.min(5000, remaining) : remaining}); - } catch (error) { - if (failures.length) throw new Error('Document bootstrap script request failed: ' + failures.join('; '), {cause: error}); - throw error; - } - } - return response; - } finally { - page.off('request', requested); - page.off('requestfailed', failed); - } - }, url); -} diff --git a/apps/waterfall-wiki/tests/brand-navigation.test.mjs b/apps/waterfall-wiki/tests/brand-navigation.test.mjs deleted file mode 100644 index 2502f73..0000000 --- a/apps/waterfall-wiki/tests/brand-navigation.test.mjs +++ /dev/null @@ -1,95 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {navigateBrandDocument, visitBrowserDocument} from './brand-navigation.mjs'; -const response = status => ({ok: () => status === 200, status: () => status}); - -test('successful documents are fetched once', async () => { - let calls = 0; - await navigateBrandDocument(async () => { calls++; return response(200); }, '/canvas'); - assert.equal(calls, 1); -}); -test('one proxy failure can recover within the original deadline', async () => { - const timeouts = []; let elapsed = 0; - await navigateBrandDocument(async timeout => { - timeouts.push(timeout); elapsed = 15000; - return response(timeouts.length === 1 ? 500 : 200); - }, '/canvas', () => elapsed); - assert.deepEqual(timeouts, [60000, 45000]); -}); -test('connection reset and interrupted bootstrap scripts can recover once', async () => { - for(const failure of ['net::ERR_CONNECTION_RESET','Document bootstrap script request failed: net::ERR_ABORTED']){ - let calls = 0; - await navigateBrandDocument(async () => { - if (++calls === 1) throw new Error(failure); - return response(200); - }, '/canvas'); - assert.equal(calls, 2); - } -}); -test('persistent server failure is not hidden', async () => { - let calls = 0; - await assert.rejects(navigateBrandDocument(async () => { calls++; return response(502); }, '/canvas'), /HTTP 502/); - assert.equal(calls, 2); -}); -test('missing routes, authentication failures and ordinary timeouts are not retried', async () => { - for (const status of [401, 403, 404]) { - let calls = 0; - await assert.rejects(navigateBrandDocument(async () => { calls++; return response(status); }, '/canvas'), /HTTP/); - assert.equal(calls, 1); - } - let calls = 0; - await assert.rejects(navigateBrandDocument(async () => { calls++; throw new Error('Timeout 60000ms exceeded'); }, '/canvas'), /Timeout/); - assert.equal(calls, 1); -}); -test('recovery cannot extend the total navigation budget', async () => { - let calls = 0, elapsed = 0; - await assert.rejects(navigateBrandDocument(async () => { - calls++; elapsed = 60000; return response(503); - }, '/canvas', () => elapsed), /budget exhausted/); - assert.equal(calls, 1); -}); - -function browserFixture(failBootstrap = false, failHydration = false, blockedOnce = false, readyVisible = true) { - const listeners = new Map(), calls = []; - const page = { - on(event, fn) { if (!listeners.has(event)) listeners.set(event, new Set()); listeners.get(event).add(fn); }, - off(event, fn) { listeners.get(event)?.delete(fn); }, - async goto(url, options) { - calls.push(options.waitUntil); - const request = {resourceType: () => 'script', failure: () => ({errorText: 'net::ERR_ABORTED'})}; - for (const fn of listeners.get('request') || []) fn(request); - if (failBootstrap && calls.length === 1) for (const fn of listeners.get('requestfailed') || []) fn(request); - return response(200); - }, - locator() { return {first: () => ({waitFor: async options => { calls.push('hydrated'); if (!readyVisible && options.state !== 'attached') throw Error('Ready marker is hidden by responsive CSS'); if (failHydration || (blockedOnce && calls.length === 2)) throw Error('Hydration timeout'); }})}; }, - }; - return {page, calls, listeners}; -} -test('document visit completes deferred scripts before checking hydration and removes listeners', async () => { - const {page, calls, listeners} = browserFixture(); - await visitBrowserDocument(page, '/account'); - assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); - assert.ok([...listeners.values()].every(set => set.size === 0)); -}); -test('interrupted bootstrap retries only document loading before hydration', async () => { - const {page, calls} = browserFixture(true, false, true); - await visitBrowserDocument(page, '/account'); - assert.deepEqual(calls, ['domcontentloaded', 'hydrated', 'domcontentloaded', 'hydrated']); -}); -test('hydration failure is surfaced without retrying the application', async () => { - const {page, calls} = browserFixture(false, true); - await assert.rejects(visitBrowserDocument(page, '/account'), /Hydration timeout/); - assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); -}); - -test('cancelled script does not reject an already hydrated document', async () => { - const {page, calls} = browserFixture(true); - await visitBrowserDocument(page, '/account'); - assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); -}); - -test('hydrated pages are ready even when responsive CSS hides the marker', async () => { - const {page, calls} = browserFixture(false, false, false, false); - await visitBrowserDocument(page, '/graphic'); - assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); -}); diff --git a/apps/waterfall-wiki/tests/brand-ui-integration.ts b/apps/waterfall-wiki/tests/brand-ui-integration.ts deleted file mode 100644 index 0a1ec12..0000000 --- a/apps/waterfall-wiki/tests/brand-ui-integration.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** CI-only checks against the disposable stack, never against personal documents. */ -import assert from 'node:assert/strict'; -import { chromium, type Request } from 'playwright-core'; -import { visitBrowserDocument } from './brand-navigation.mjs'; -export async function verifyBrandUI(base: string) { - for (const prefix of process.env.VERIFY_CANVAS_UI === '1' ? ['', '/studio'] : ['']) { - const icon = await fetch(`${base}${prefix}/favicon.ico`); - assert.equal(icon.status, 200); - const bytes = new Uint8Array(await icon.arrayBuffer()); - assert.deepEqual([...bytes.slice(0, 4)], [0, 0, 1, 0]); - assert.equal(bytes[4], 7, 'favicon includes seven resolutions'); - for (const file of ['logo-light.svg', 'logo-dark.svg', 'icon-dark.svg']) { - const image = await fetch(`${base}${prefix}/brand/${file}`); - assert.equal(image.status, 200, prefix + '/' + file); - assert.match(await image.text(), /(); - page.on('request', request => { if(request.resourceType()==='script') scriptStarted.set(request,Date.now()); }); - page.on('response', response => { if(response.request().resourceType()==='script') { scripts.push({path:new URL(response.url()).pathname,status:response.status(),ms:Date.now()-(scriptStarted.get(response.request())??Date.now())}); if(scripts.length>24)scripts.shift(); } }); - page.on('pageerror', error => failures.push(error.message)); - page.on('requestfailed', request => { failures.push(new URL(request.url()).pathname + ': ' + request.failure()?.errorText); if(request.resourceType()==='script') scripts.push({path:new URL(request.url()).pathname,ms:Date.now()-(scriptStarted.get(request)??Date.now()),error:request.failure()?.errorText}); }); - const visit = async (path: string, theme: string) => { - const started = Date.now(); - try { - await visitBrowserDocument(page, base + path); - await page.waitForFunction(expected => document.documentElement.dataset.theme === expected, theme); - console.log('Brand UI route ready:', path, theme, Date.now() - started); - } catch (error) { - console.error('Brand UI navigation diagnostics', JSON.stringify({path, url:page.url(), elapsedMs:Date.now()-started, failures:failures.slice(-20), scripts:scripts.slice(-24), controls:await page.locator('[data-theme-ready]').evaluateAll(elements=>elements.map(el=>({ready:el.getAttribute('data-theme-ready'),display:getComputedStyle(el).display}))), body:(await page.locator('body').innerText({timeout:3000}).catch(()=>'unavailable')).slice(0,1200)})); - throw error; - } - }; - const background = (selector: string) => page.locator(selector).first().evaluate(el => getComputedStyle(el).backgroundColor); - await visit('/', 'home-light'); - assert.equal(await background('body'), 'rgb(255, 246, 239)'); - assert.ok((await page.title()).includes('啡跃')); - assert.ok((await page.locator('link[rel="icon"]').all()).length >= 2); - assert.equal(await page.locator('.endoleap-mark-light').first().isVisible(), true); - await page.getByRole('button', { name: '切换明暗主题', exact: true }).first().click(); - await page.waitForFunction(() => document.documentElement.dataset.theme === 'home-dark'); - assert.equal(await background('body'), 'rgb(28, 24, 22)'); - assert.equal(await page.locator('.endoleap-mark-dark').first().isVisible(), true); - await visit('/account', 'home-dark'); - if (process.env.VERIFY_CANVAS_UI === '1') { - await visit('/graphic', 'canvas-dark'); - assert.equal(await background('body'), 'rgb(20, 20, 20)'); - assert.equal(await page.locator('[class*="boardWrap"] > div').first().evaluate(el => getComputedStyle(el).backgroundColor), 'rgb(255, 255, 255)', 'artwork stays white in dark chrome'); - await page.getByRole('button', { name: '切换明暗主题', exact: true }).first().click(); - await page.waitForFunction(() => document.documentElement.dataset.theme === 'canvas-light'); - await visit('/infinite_canvas', 'canvas-light'); - assert.equal(await background('[class*="viewport"]'), 'rgb(250, 250, 250)'); - } else { - await page.getByRole('button', { name: '切换明暗主题', exact: true }).first().click(); - } - await visit('/', 'home-light'); - await page.setViewportSize({ width: 390, height: 844 }); - assert.equal(await page.locator('.endoleap-mark-light').first().isVisible(), true); - assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'mobile home has no horizontal overflow'); - for (const width of [390, 320]) { - await page.setViewportSize({ width, height: 844 }); - assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'Editorial home fits narrow screens'); - const entry = await page.getByRole('navigation', {name: '主导航'}).getByRole('link', {name: /进入工作台/}).boundingBox(); - assert.ok(entry && entry.x >= 0 && entry.x + entry.width <= width, 'Workspace entry is visible, not clipped'); - assert.equal(await page.getByRole('link', {name: /去知识空间逛逛/}).getAttribute('href'), '/documents'); - assert.equal(await page.getByRole('link', {name: '开始创作', exact: true}).getAttribute('href'), '/account?next=/workspace'); - } - console.log('Endoleap logos, both favicon paths, persisted home/canvas themes and artwork color isolation passed.'); - } finally { await browser.close(); } -} diff --git a/apps/waterfall-wiki/tests/canvas-integration.ts b/apps/waterfall-wiki/tests/canvas-integration.ts deleted file mode 100644 index 352a937..0000000 --- a/apps/waterfall-wiki/tests/canvas-integration.ts +++ /dev/null @@ -1,87 +0,0 @@ -import assert from 'node:assert/strict'; -import {randomUUID} from 'node:crypto'; -import {db} from '../src/lib/db'; -import {newToken,tokenDigest,ACCOUNT_COOKIE} from '../src/lib/account-crypto'; -import type {CanvasDocument} from '../src/lib/canvas-document'; -export async function verifyCanvasWorkspace(base:string){ - const identities=await Promise.all([0,1].map(async()=>{const id=randomUUID(),token=newToken();await db().query("INSERT INTO account_users(id,email,password_hash,verified_at) VALUES($1,$2,'test',now())",[id,id+'@example.com']);await db().query("INSERT INTO account_tokens(digest,user_id,kind,expires_at) VALUES($1,$2,'session',now()+interval '1 hour')",[tokenDigest(token),id]);return {[`cookie`]:ACCOUNT_COOKIE+'='+token}})); - const origin=process.env.ACCOUNT_PUBLIC_ORIGIN||base; - const call=(path:string,method='GET',body?:unknown,headers:Record={})=>fetch(base+'/api/canvases'+path,{method,headers:{'Content-Type':'application/json',origin,...headers},...(body===undefined?{}:{body:JSON.stringify(body)})}); - const doc={version:1,cards:[{id:'note',kind:'text',title:'简报',text:'自然光与城市清晨',x:0,y:0},{id:'script',kind:'script',title:'脚本',text:'',instruction:'写六个分镜',operation:'script',x:500,y:0}],links:[['note','script']]}; - assert.equal((await call('','POST',{document:doc})).status,401); - const create=await call('','POST',{document:doc},identities[0]);assert.equal(create.status,201,await create.clone().text());const canvas=await create.json();assert.equal(canvas.revision,1); - const root='/'+canvas.id; - assert.equal((await call(root,'GET',undefined,identities[1])).status,404); - assert.equal((await call(root,'PUT',{revision:1,document:doc},{...identities[0],origin:'https://evil.example'})).status,403); - assert.equal((await call(root,'PUT',{revision:1,document:doc},{...identities[0],authorization:'Bearer invalid',origin:'https://evil.example'})).status,404,'A bearer request cannot fall back to an owner cookie'); - assert.equal((await call(root,'PUT',{revision:1,document:{...doc,links:[['note','script'],['script','note']]}},identities[0])).status,400,'Imported cyclic graphs are rejected'); - const linked=await call(root+'/link','POST',{},identities[0]);assert.equal(linked.status,201);const credentials=await linked.json();const agent={authorization:'Bearer '+credentials.token}; - assert.equal((await call('','GET',undefined,agent)).status,401); - const other=await (await call('','POST',{document:doc},identities[0])).json();assert.equal((await call('/'+other.id,'GET',undefined,agent)).status,404,'A link cannot read another canvas'); - assert.equal((await call(root,'PUT',{revision:1,document:doc},identities[0])).status,200); - assert.equal((await call(root,'PUT',{revision:1,document:doc},identities[0])).status,409); - async function queue(rev:number){const r=await call(root+'/tasks','POST',{revision:rev,targetId:'script'},identities[0]);assert.equal(r.status,201,await r.clone().text());return (await r.json()).id as string} - const concurrent=await Promise.all([0,1].map(()=>call('/'+other.id,'PUT',{revision:1,document:doc},identities[0]))); - assert.deepEqual(concurrent.map(r=>r.status).sort(),[200,409],'Concurrent saves cannot lose an update'); - const first=await queue(2); - assert.equal((await call(root+'/tasks','POST',{revision:2,targetId:'script'},identities[0])).status,409,'Only one live task per target'); - const recipe=await (await call(root+'/tasks/'+first,'GET',undefined,agent)).json();assert.equal(recipe.request.references[0].text,doc.cards[0].text);assert.equal(recipe.request.instruction,'写六个分镜'); - assert.equal((await call(root+'/tasks/'+first,'GET',undefined,identities[1])).status,404); - const result={status:'succeeded',result:{text:'第一镜:城市醒来。'}}; - assert.equal((await call(root+'/tasks/'+first,'PATCH',result,agent)).status,409,'Result cannot arrive before claim'); - assert.equal((await call(root+'/tasks/'+first,'PATCH',{status:'running'},agent)).status,200); - assert.equal((await call(root,'PUT',{revision:2,document:{...doc,cards:doc.cards.map(n=>({...n,x:n.x+10}))}},identities[0])).status,200); - assert.equal((await call(root+'/tasks/'+first,'PATCH',result,agent)).status,409,'Stale result must not overwrite newer user edits'); - assert.equal((await call(root+'/tasks/'+first,'PATCH',{status:'failed',error:'依据新版本重新派发'},identities[0])).status,200); - const second=await queue(3);assert.equal((await call(root+'/tasks/'+second,'PATCH',{status:'running'},agent)).status,200); - assert.equal((await call(root+'/tasks/'+second,'PATCH',{status:'succeeded',result:{text:{invalid:true}}},agent)).status,400,'Malformed results do not mutate the canvas'); - assert.equal((await call(root+'/tasks/'+second,'PATCH',{status:'succeeded',result:{image:'data:image/png;base64,AAAA'}},agent)).status,400,'A script node cannot accept an image result'); - assert.equal((await call(root+'/tasks/'+second,'PATCH',result,agent)).status,200); - assert.equal((await call(root+'/tasks/'+second,'PATCH',result,agent)).status,200,'Identical completion is idempotent'); - assert.equal((await call(root+'/tasks/'+second,'PATCH',{status:'succeeded',result:{text:'覆盖'}},agent)).status,409); - const final=await (await call(root,'GET',undefined,identities[0])).json();assert.equal(final.revision,4);assert.equal(final.document.cards.find((n:{id:string})=>n.id==='script').text,'第一镜:城市醒来。');assert.deepEqual(final.document.links,doc.links); - const png='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='; - const imageDocument:CanvasDocument={version:1,cards:[ - {id:'source',kind:'image',title:'参考图',text:'保留原图',image:png,x:0,y:0}, - {id:'generated',kind:'image',title:'图生图',text:'',instruction:'保留构图,改为雪夜',operation:'generate',x:500,y:0}, - {id:'upscaled',kind:'image',title:'图片高清',text:'',operation:'upscale',x:1000,y:0}, - ],links:[['source','generated'],['generated','upscaled']]}; - const imageCreate=await call('','POST',{document:imageDocument},identities[0]);assert.equal(imageCreate.status,201); - const imageCanvas=await imageCreate.json(),imageRoot='/'+imageCanvas.id; - const imageLink=await call(imageRoot+'/link','POST',{},identities[0]);assert.equal(imageLink.status,201); - const imageAgent={authorization:'Bearer '+(await imageLink.json()).token}; - for(const [index,targetId] of ['generated','upscaled'].entries()){ - const queued=await call(imageRoot+'/tasks','POST',{revision:index+1,targetId},identities[0]);assert.equal(queued.status,201,await queued.clone().text()); - const taskId=(await queued.json()).id,taskPath=imageRoot+'/tasks/'+taskId; - const snapshotResponse=await call(taskPath,'GET',undefined,imageAgent);assert.equal(snapshotResponse.status,200); - const snapshot=await snapshotResponse.json();assert.equal(snapshot.request.operation,index===0?'generate':'upscale'); - assert.equal(snapshot.request.references[0].id,index===0?'source':'generated'); - assert.equal(snapshot.request.references[0].image,png,'Image tasks carry the referenced image bytes'); - assert.deepEqual(snapshot.request.result.allowedFields,['image','text']); - assert.equal((await call(taskPath,'PATCH',{status:'running'},imageAgent)).status,200); - const completion={status:'succeeded',result:{image:png,text:index===0?'雪夜图片结果':'高清图片结果'}}; - const completed=await call(taskPath,'PATCH',completion,imageAgent);assert.equal(completed.status,200,await completed.clone().text()); - assert.equal((await call(taskPath,'PATCH',completion,imageAgent)).status,200,'Image completion retries are idempotent'); - const stateResponse=await call(imageRoot,'GET',undefined,identities[0]);assert.equal(stateResponse.status,200); - const state=await stateResponse.json();assert.equal(state.revision,index+2); - const cards=(state.document as CanvasDocument).cards; - assert.equal(cards.find(n=>n.id===targetId)?.image,png);assert.equal(cards.find(n=>n.id===targetId)?.text,completion.result.text); - assert.deepEqual(cards.find(n=>n.id==='source'),imageDocument.cards[0],'Generation preserves the reference node'); - assert.deepEqual(state.document.links,imageDocument.links,'Image results preserve the reference graph'); - if(index===0)assert.deepEqual(cards.find(n=>n.id==='upscaled'),imageDocument.cards[2],'Generation only updates its target'); - else assert.equal(cards.find(n=>n.id==='generated')?.text,'雪夜图片结果','Upscaling preserves the previous generated image'); - } - assert.equal((await call(imageRoot+'/link','DELETE',{},identities[0])).status,200); - assert.equal((await call(imageRoot,'GET',undefined,imageAgent)).status,404); - assert.equal((await call(root+'/link','POST',{},agent)).status,403,'A delegated agent cannot mint links'); - assert.equal((await call(root+'/link','DELETE',{},identities[0])).status,200); - assert.equal((await call(root,'GET',undefined,agent)).status,404,'Revocation takes effect on the next request'); - const rotated=await (await call(root+'/link','POST',{},identities[0])).json(); - const expired={authorization:'Bearer '+rotated.token}; - await db().query("UPDATE canvas_agent_links SET expires_at=now()-interval '1 second' WHERE canvas_id=$1",[canvas.id]); - assert.equal((await call(root,'GET',undefined,expired)).status,404,'Expired capabilities are rejected'); - const rotations=await Promise.all([call(root+'/link','POST',{},identities[0]),call(root+'/link','POST',{},identities[0])]); - assert.ok(rotations.every(r=>r.status===201)); - assert.equal((await db().query('SELECT count(*)::int AS count FROM canvas_agent_links WHERE canvas_id=$1',[canvas.id])).rows[0].count,1,'Concurrent rotation leaves one active capability'); - const docs=await call(root+'?agent_mode=True');assert.equal(docs.status,200);assert.match(await docs.text(),/Authorization/); -} diff --git a/apps/waterfall-wiki/tests/canvas-ui-integration.ts b/apps/waterfall-wiki/tests/canvas-ui-integration.ts deleted file mode 100644 index ce8434c..0000000 --- a/apps/waterfall-wiki/tests/canvas-ui-integration.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { visitBrowserDocument } from "./brand-navigation.mjs"; -import assert from 'node:assert/strict'; -import {randomUUID} from 'node:crypto'; -import {chromium,type Request} from 'playwright-core'; -import {db} from '../src/lib/db'; -import {hashPassword,ACCOUNT_COOKIE} from '../src/lib/account-crypto'; - -export async function verifyCanvasAgentUI(base:string){ - assert.equal(process.env.PROMPT_INTEGRATION_TEST,'1'); - const owner=randomUUID(),email=owner+'@example.com',password='Canvas integration example1!'; - await db().query("INSERT INTO account_users(id,email,password_hash,verified_at) VALUES($1,$2,$3,now())",[owner,email,await hashPassword(password)]); - const browser=await chromium.launch({executablePath:process.env.CHROMIUM_PATH||'/usr/bin/chromium',headless:true,args:['--no-sandbox','--disable-dev-shm-usage']}); - const context=await browser.newContext({viewport:{width:1440,height:1000}}),errors:string[]=[]; - const page=await context.newPage(),canvasRequests:string[]=[],pending=new Set(),network:string[]=[]; - const started=Date.now(),navigation:{ms:number;event:string;path:string}[]=[]; - const mark=(event:string)=>{navigation.push({ms:Date.now()-started,event,path:new URL(page.url()).pathname});if(navigation.length>60)navigation.shift()}; - page.on('framenavigated',frame=>{if(frame===page.mainFrame())mark('framenavigated')}); - page.on('domcontentloaded',()=>mark('domcontentloaded'));page.on('load',()=>mark('load')); - const requestName=(request:{method:()=>string;url:()=>string})=>request.method()+' '+new URL(request.url()).pathname; - page.on('request',request=>pending.add(request)); - page.on('requestfinished',request=>pending.delete(request)); - page.on('requestfailed',request=>{pending.delete(request);network.push(requestName(request)+': '+request.failure()?.errorText)}); - page.on('response',response=>{const url=new URL(response.url());if(url.pathname.startsWith('/api/canvases'))canvasRequests.push(`${response.request().method()} ${url.pathname} ${response.status()}`)}); - try{ - page.setDefaultTimeout(60000);page.setDefaultNavigationTimeout(60000);page.on('pageerror',e=>errors.push(e.message)); - mark('open landing');await visitBrowserDocument(page,base+'/'); - await page.getByRole('heading',{name:/想法不该停在对话里/,level:1}).waitFor(); - const accountSession=page.waitForResponse(response=>new URL(response.url()).pathname==='/api/account/session'&&response.request().method()==='GET'); - const [,sessionResponse]=await Promise.all([page.getByRole('link',{name:'开始创作',exact:true}).click(),accountSession]); - assert.equal(sessionResponse.status(),200);assert.equal((await sessionResponse.json()).user,null); - await page.getByRole('heading',{name:'欢迎回来',exact:true,level:2}).waitFor(); - await page.locator('button[data-theme-ready="true"]').first().waitFor(); - mark('login form ready'); - await page.getByLabel('邮箱地址',{exact:true}).fill(email); - await page.getByLabel('密码',{exact:true}).fill(password); - mark('before login click');await page.locator('form').getByRole('button',{name:'登录',exact:true}).click(); - mark('after login click'); - await page.waitForURL(url=>url.pathname==='/workspace',{waitUntil:'commit'}); - mark('workspace URL accepted'); - await page.getByRole('heading',{name:'今天,让哪个想法成形?',exact:true}).waitFor(); - await page.locator('button[data-theme-ready="true"]').first().waitFor(); - mark('workspace rendered'); - const session=(await context.cookies()).find(c=>c.name===ACCOUNT_COOKIE);assert.ok(session?.httpOnly,'The actual login response establishes an HTTP-only account session'); - const ownerHeaders={cookie:ACCOUNT_COOKIE+'='+session.value}; - const canvasLink=page.getByRole('link',{name:'无限画布',exact:true}); - assert.equal(await canvasLink.getAttribute('href'),'/infinite_canvas'); - await visitBrowserDocument(page,base+'/infinite_canvas'); - await page.getByRole('link',{name:email,exact:true}).waitFor({state:'attached'}); - await page.getByRole('button',{name:'从示例开始 ↗',exact:true}).click(); - const shot=page.locator('[data-node-id="shot"] [data-node-content]'); - await shot.click();await shot.click(); - const composer=page.getByRole('region',{name:'节点编辑器',exact:true}); - await composer.getByRole('textbox',{name:'创作要求',exact:true}).fill('根据参考资料写六个分镜'); - await composer.getByRole('button',{name:'交给 Agent',exact:true}).click(); - const panel=page.getByRole('region',{name:'Agent 连接',exact:true}); - await panel.getByText('任务已创建,等待你的 coding agent 领取',{exact:true}).waitFor(); - const list=await fetch(base+'/api/canvases',{headers:ownerHeaders}).then(r=>r.json());assert.equal(list.canvases.length,1); - const root='/api/canvases/'+list.canvases[0].id; - const state=await fetch(base+root,{headers:ownerHeaders}).then(r=>r.json());assert.equal(state.tasks.length,1);assert.equal(state.tasks[0].status,'queued'); - await panel.getByRole('button',{name:'生成连接凭据',exact:true}).click(); - const credentials=panel.getByRole('textbox',{name:'Agent 连接凭据',exact:true});await credentials.waitFor(); - await panel.getByRole('button',{name:'复制给 coding agent',exact:true}).click(); - await panel.getByRole('status').filter({hasText:/凭据已复制|已选中凭据/}).waitFor(); - const first=JSON.parse(await credentials.inputValue());assert.equal(first.canvasUrl,base+root); - const agentHeaders={'Content-Type':'application/json',authorization:first.authorization}; - const task=root+'/tasks/'+state.tasks[0].id; - const recipe=await fetch(base+task,{headers:agentHeaders}).then(r=>r.json());assert.equal(recipe.request.instruction,'根据参考资料写六个分镜');assert.equal(recipe.request.references[0].id,'reference'); - const patch=async(body:unknown)=>fetch(base+task,{method:'PATCH',headers:agentHeaders,body:JSON.stringify(body)}); - assert.equal((await patch({status:'running'})).status,200); - await panel.getByText(/Agent 正在处理/).waitFor(); - // This fixture verifies the external-client protocol and UI delivery, not model quality. - let generated='第一镜:晨光穿过窗边的植物。'; - assert.equal((await patch({status:'succeeded',result:{text:generated}})).status,200); - await page.locator('[data-node-id="shot"] [data-node-content]').getByText(generated,{exact:true}).waitFor(); - const updated=await fetch(base+root,{headers:ownerHeaders}).then(r=>r.json()); - assert.equal(updated.document.cards.find((n:{id:string})=>n.id==='shot').instruction,'根据参考资料写六个分镜'); - await panel.getByRole('button',{name:'关闭 Agent 连接',exact:true}).click(); - await shot.click();await shot.click(); - await composer.getByRole('textbox',{name:'创作要求',exact:true}).fill('再写一个新镜头'); - const queued=page.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname===root+'/tasks'); - await composer.getByRole('button',{name:'交给 Agent',exact:true}).click(); - const queuedResponse=await queued;assert.equal(queuedResponse.status(),201);const secondTask=await queuedResponse.json(); - await panel.getByRole('button',{name:'关闭 Agent 连接',exact:true}).click(); - const brief=page.locator('[data-node-id="brief"] [data-node-content]');await brief.click();await brief.click(); - const localText=page.getByRole('textbox',{name:'01 / 创意简报 内容',exact:true});await localText.fill('用户尚未同步的本机修改'); - const nextTask=root+'/tasks/'+secondTask.id; - const nextPatch=(body:unknown)=>fetch(base+nextTask,{method:'PATCH',headers:agentHeaders,body:JSON.stringify(body)}); - assert.equal((await nextPatch({status:'running'})).status,200); - generated='第二镜:城市在雨后醒来。'; - assert.equal((await nextPatch({status:'succeeded',result:{text:generated}})).status,200); - await page.getByRole('button',{name:'Agent · 在线画布',exact:true}).click(); - await panel.getByRole('button',{name:'载入在线版本',exact:true}).waitFor(); - assert.equal(await localText.inputValue(),'用户尚未同步的本机修改','Polling must not replace unsaved local work'); - await panel.getByRole('button',{name:'载入在线版本',exact:true}).click(); - await page.locator('[data-node-id="shot"] [data-node-content]').getByText(generated,{exact:true}).waitFor(); - assert.equal(await page.locator('[data-node-id="brief"] [data-node-content]').textContent(),state.document.cards.find((n:{id:string})=>n.id==='brief').text); - await panel.getByRole('button',{name:'生成连接凭据',exact:true}).click(); - await page.waitForFunction(old=>{const field=document.querySelector('textarea[aria-label="Agent 连接凭据"]');return !!field&&JSON.parse(field.value).authorization!==old},first.authorization); - const second=JSON.parse(await credentials.inputValue());assert.equal((await fetch(base+root,{headers:agentHeaders})).status,404); - await panel.getByRole('button',{name:'撤销连接',exact:true}).click(); - await panel.getByText('Agent 访问已撤销',{exact:true}).waitFor(); - assert.equal((await fetch(base+root,{headers:{authorization:second.authorization}})).status,404); - await page.setViewportSize({width:390,height:844}); - const bounds=await panel.boundingBox();assert.ok(bounds&&bounds.x>=0&&bounds.x+bounds.width<=390,'Agent panel stays within the mobile viewport'); - await visitBrowserDocument(page,page.url(),true); - await page.getByRole('link',{name:email,exact:true,includeHidden:true}).waitFor({state:'attached'}); - await page.getByRole('button',{name:'Agent',exact:true}).click(); - const reopened=page.getByRole('region',{name:'Agent 连接',exact:true}); - await reopened.getByRole('combobox',{name:'打开在线画布',exact:true}).selectOption(list.canvases[0].id); - await reopened.getByRole('button',{name:'载入在线版本',exact:true}).click(); - await page.locator('[data-node-id="shot"] [data-node-content]').getByText(generated,{exact:true}).waitFor(); - assert.deepEqual(errors,[]); - console.log('Canvas navigation timeline',JSON.stringify(navigation)); - console.log('Canvas UI dispatched a real stored task, received an external-client fixture result, rotated/revoked access and restored its online document'); - }catch(error){ - const statuses=await page.getByRole('status').allTextContents().catch(()=>[]); - const headings=await page.getByRole('heading').allTextContents().catch(()=>[]); - const documentState=await page.evaluate(()=>({readyState:document.readyState,navigation:performance.getEntriesByType('navigation').map(entry=>{const timing=entry as PerformanceNavigationTiming;return {type:timing.type,domContentLoadedEventEnd:timing.domContentLoadedEventEnd,loadEventEnd:timing.loadEventEnd}})})).catch(()=>null); - console.error('Canvas agent UI diagnostics',JSON.stringify({url:page.url(),browserConnected:browser.isConnected(),headings,statuses,documentState,navigation,pending:[...pending].map(requestName),network:network.slice(-20),canvasRequests:canvasRequests.slice(-20),errors})); - throw error; - }finally{await context.close();await browser.close()} -} diff --git a/apps/waterfall-wiki/tests/character-pack.test.mjs b/apps/waterfall-wiki/tests/character-pack.test.mjs deleted file mode 100644 index 8f349c7..0000000 --- a/apps/waterfall-wiki/tests/character-pack.test.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import {test} from 'node:test';import assert from 'node:assert/strict';import {requiredPackImages} from '../scripts/verify-character-pack.mjs'; -test('editorial covers do not invalidate preserved original packs',()=>{const originals=[{id:'a',sha256:'hash-a'},{id:'b',sha256:'hash-b'}];assert.deepEqual(requiredPackImages(['hash-a','hash-b'],[{id:'cover',sha256:'cover'},...originals]),originals)}); -test('extra images cannot disguise missing or replaced originals',()=>{assert.throws(()=>requiredPackImages(['a','b'],[{sha256:'a'},{sha256:'x'},{sha256:'y'}]));assert.throws(()=>requiredPackImages(['a','a'],[{sha256:'a'}]));assert.equal(requiredPackImages(['a','a'],[{sha256:'a'},{sha256:'a'}]).length,2)}); diff --git a/apps/waterfall-wiki/tests/discovery.test.ts b/apps/waterfall-wiki/tests/discovery.test.ts deleted file mode 100644 index 138c998..0000000 --- a/apps/waterfall-wiki/tests/discovery.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {backlinksFor,documentLinks,rankDocuments,discoveryPage,InvalidDiscoveryCursor,type SearchDocument} from '../src/lib/discovery'; -const doc=(id:string,patch:Partial={}):SearchDocument=>({id,title:'普通笔记 '+id,description:'',markdown:'',categories:[],namespace:'Main',createdAt:'2026-01-01',...patch}); -test('backlinks count distinct documents, resolve UUID aliases and ignore self/external/image/code links',()=>{ - const a=doc('a',{aliases:['aaaa-1111'],markdown:'[self](/explore/a)'}),b=doc('b',{markdown:'[inline](/explore/a) [again](http://8.211.154.134:18131/explore/aaaa-1111?agent_mode=True#x)\n\n[reference][target]\n\n[target]: /api/notes/a\n\n![image](/explore/c)\n\n`[code](/explore/c)`\n\n```md\n[fenced](/explore/c)\n```\n[external](https://example.com/explore/c)'}),c=doc('c',{markdown:'[a](/explore/a)'}); - const refs=backlinksFor([a,b,c]);assert.deepEqual(refs.get('a')?.map(r=>r.id),['b','c']);assert.equal(refs.get('c')?.length,0);assert.equal(documentLinks(b.markdown).length,4); -}); -test('search includes body, tags and descriptions; matches all terms, casefolds and ranks title above body',()=>{ - const documents=[doc('a',{markdown:'正文专属关键词 React 实践',categories:['课程']}),doc('b',{title:'REACT 实践',description:'正文专属关键词'}),doc('c',{description:'React 介绍'})]; - assert.deepEqual(rankDocuments(documents,'react 实践').map(r=>r.doc.id),['b','a']); - assert.deepEqual(rankDocuments(documents,'课程 正文专属关键词').map(r=>r.doc.id),['a']); - assert.deepEqual(rankDocuments(documents,'"react 实践"').map(r=>r.doc.id),['b','a']);assert.equal(rankDocuments(documents,'不存在').length,0); -}); -test('default order favors inbound links and filters the entire corpus before paginating',()=>{ - const documents=Array.from({length:52},(_,i)=>doc(i.toString(16).padStart(3,'0'),{categories:i%2?['wanted']:[],markdown:i?'[target](/explore/000)':''})); - const ranked=rankDocuments(documents);assert.equal(ranked[0].doc.id,'000');assert.equal(ranked[0].backlinkCount,51); - const filtered=rankDocuments(documents,'','wanted'),first=discoveryPage(filtered,'','wanted'),second=discoveryPage(filtered,'','wanted',first.nextCursor!); - assert.equal(first.total,26);assert.equal(first.rows.length,24);assert.equal(second.rows.length,2);assert.equal(second.nextCursor,null);assert.equal(new Set([...first.rows,...second.rows].map(r=>r.doc.id)).size,26); - assert.throws(()=>discoveryPage(filtered,'different','wanted',first.nextCursor!),InvalidDiscoveryCursor);assert.throws(()=>discoveryPage(filtered,'','wanted','bad'),InvalidDiscoveryCursor); -}); - -test('pasted full URLs count but disguised link labels and inline code do not',()=>{ - const source=doc('b',{markdown:'http://8.211.154.134:18131/explore/a。\n\n[http://8.211.154.134:18131/explore/c](https://example.com)\n\n`http://8.211.154.134:18131/explore/c`'}); - const refs=backlinksFor([doc('a'),source,doc('c')]);assert.equal(refs.get('a')?.length,1);assert.equal(refs.get('c')?.length,0); -}); - -test('system categories filter independently of ordinary tags and are searchable',()=>{ - const ranked=rankDocuments([doc('a',{categories:['中国风'],systemTags:['文档入口','document-entry']}),doc('b',{categories:['中国风']})],'','document-entry'); - assert.deepEqual(ranked.map(r=>r.doc.id),['a']);assert.equal(rankDocuments([doc('a',{categories:['中国风'],systemTags:['文档入口']})],'中国风 文档入口').length,1); -}); diff --git a/apps/waterfall-wiki/tests/explore.test.ts b/apps/waterfall-wiki/tests/explore.test.ts deleted file mode 100644 index f827ef0..0000000 --- a/apps/waterfall-wiki/tests/explore.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {staticEntries,getEntry,entryText} from '../src/lib/explore'; -import {pageId,previewUrl} from '../src/content/page-links'; -import {guidanceDocuments} from '../src/content/skills/guidance'; -import {getServerSideProps} from '../src/pages/explore/[id]'; -test('every public document has a distinct persisted ID and canonical entry',async()=>{assert.equal(new Set(staticEntries.map(e=>e.id)).size,staticEntries.length);for(const e of staticEntries){assert.match(e.id,/^[a-f0-9]{24}$/);assert.equal((await getEntry(e.id))?.title,e.title);}for(const doc of guidanceDocuments)assert.ok(pageId('guidance:'+doc.path));assert.equal(await getEntry('unknown'),null);}); -test('agent_mode on the canonical URL emits text with media links and no HTML',async()=>{const entry=staticEntries.find(e=>e.media.length)!;let body='';const headers:Record={};const response={setHeader:(k:string,v:string)=>headers[k]=v,end:(value:string)=>{body=value}};await getServerSideProps({params:{id:entry.id},query:{agent_mode:'True'},res:response,req:{method:'GET'}} as never);assert.match(headers['Content-Type'],/text\/plain/);assert.ok(body.includes(entry.title));assert.ok(body.includes(entry.media[0].url));assert.ok(body.startsWith('# '+entry.title));assert.ok(!body.includes('!['));assert.equal(body,entryText((await getEntry(entry.id))!));}); -test('preview URLs always use bounded image endpoints',()=>{const url=new URL(previewUrl('/api/media/id?variant=poster',480),'http://test');assert.equal(url.pathname,'/api/preview');assert.equal(url.searchParams.get('source'),'/api/media/id?variant=poster');assert.equal(url.searchParams.get('width'),'480');}); - -test('independent notes expose their own description, tags and complete image counts',async()=>{ - for(const entry of staticEntries){assert.ok(entry.description.trim());assert.equal(entry.imageCount,entry.media.filter(m=>m.kind==='image').length);assert.equal(new Set(entry.media.map(m=>m.url)).size,entry.media.length);} - const note=(await getEntry(pageId('guidance:shadow-puppet')))!; - assert.equal(note.imageCount,2);assert.ok(note.categories.includes('照片入皮影'));assert.ok(entryText(note).includes('Description: '+note.description)); - const workflow=(await getEntry(pageId('guidance:shadow-puppet/workflow')))!; - assert.notEqual(workflow.description,note.description);assert.notEqual(workflow.id,note.id); -}); diff --git a/apps/waterfall-wiki/tests/flat-note-migration.ts b/apps/waterfall-wiki/tests/flat-note-migration.ts deleted file mode 100644 index b347516..0000000 --- a/apps/waterfall-wiki/tests/flat-note-migration.ts +++ /dev/null @@ -1,38 +0,0 @@ -import assert from 'node:assert/strict'; -import {readFile} from 'node:fs/promises'; -import {randomUUID} from 'node:crypto'; -import {db} from '../src/lib/db'; -export async function verifyFlatNoteMigration(){ - const client=await db().connect();const schema='migration_'+randomUUID().replaceAll('-',''); - try{ - await client.query(`CREATE SCHEMA ${schema}`);await client.query(`SET search_path TO ${schema}`); - for(const name of ['001_library','002_page_snapshots','003_note_descriptions'])await client.query(await readFile(`db/${name}.sql`,'utf8')); - const [video,picture,post,tag,duplicate,parent]=Array.from({length:6},()=>randomUUID()); - await client.query(`INSERT INTO assets(id,kind,storage_key,mime_type,byte_size) VALUES($1,'video','original','video/mp4',100),($2,'image','snapshot','image/png',50)`,[video,picture]); - await client.query(`INSERT INTO posts(id,slug,kind,title,page_key) VALUES($1,'old-note','article','保留的笔记','old-code')`,[post]); - await client.query(`INSERT INTO post_assets(post_id,asset_id,position) VALUES($1,$2,0)`,[post,video]); - await client.query(`INSERT INTO page_snapshots(post_id,page_index,asset_id,revision) VALUES($1,1,$2,'old-revision')`,[post,picture]); - await client.query(`INSERT INTO render_jobs(post_id,status,revision) VALUES($1,'pending','old-revision')`,[post]); - await client.query(`INSERT INTO asset_variants(asset_id,role,storage_key,mime_type,byte_size) VALUES($1,'poster','old-poster','image/jpeg',20)`,[video]); - await client.query(`INSERT INTO media_jobs(id,asset_id) VALUES($1,$2)`,[randomUUID(),video]); - await client.query(`INSERT INTO tags(id,slug,name) VALUES($1,'parent','上级'),($2,'same','重复')`,[parent,tag]); - await client.query(`INSERT INTO tags(id,parent_id,slug,name) VALUES($1,$2,'same','重复')`,[duplicate,parent]); - await client.query(`INSERT INTO post_tags(post_id,tag_id) VALUES($1,$2),($1,$3)`,[post,tag,duplicate]); - await client.query(`INSERT INTO asset_tags(asset_id,tag_id) VALUES($1,$2)`,[video,duplicate]); - await client.query(await readFile('db/004_flat_notes.sql','utf8')); - assert.equal((await client.query('SELECT count(*)::integer n FROM post_assets WHERE post_id=$1 AND asset_id=$2',[post,picture])).rows[0].n,1,'Previously generated image remains linked'); - const processed=(await client.query(`SELECT a.storage_key,pa.role FROM post_assets pa JOIN assets a ON a.id=pa.asset_id WHERE pa.post_id=$1 AND a.derivation='poster'`,[post])).rows; - assert.deepEqual(processed,[{storage_key:'old-poster',role:'poster'}],'Processed file uses its existing object and a normal resource link'); - assert.equal((await client.query('SELECT count(*)::integer n FROM post_tags WHERE post_id=$1',[post])).rows[0].n,1); - assert.equal((await client.query('SELECT count(*)::integer n FROM asset_tags WHERE asset_id=$1',[video])).rows[0].n,1,'Merging equal tags retains asset associations'); - assert.equal((await client.query('SELECT count(*)::integer n FROM media_jobs')).rows[0].n,1,'Video processing queue survives'); - assert.equal((await client.query(`SELECT count(*)::integer n FROM information_schema.columns WHERE table_schema=$1 AND column_name IN ('parent_id','page_key')`,[schema])).rows[0].n,0); - await client.query(await readFile('db/008_system_tags.sql','utf8')); - assert.equal((await client.query("SELECT count(*)::integer n FROM tags WHERE kind='system'")).rows[0].n,2); - assert.ok((await client.query("SELECT 1 FROM tags WHERE kind='normal'")).rowCount); - assert.equal((await client.query('SELECT count(*)::integer n FROM post_tags WHERE post_id=$1',[post])).rows[0].n,1,'System tag migration retains ordinary associations'); - await assert.rejects(client.query("INSERT INTO tags(id,slug,name,kind) VALUES($1,'invalid-system','Invalid system','system')",[randomUUID()])); - }finally{ - await client.query('ROLLBACK');await client.query('SET search_path TO public');await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`);client.release(); - } -} diff --git a/apps/waterfall-wiki/tests/guidance-integration.ts b/apps/waterfall-wiki/tests/guidance-integration.ts deleted file mode 100644 index 4c49835..0000000 --- a/apps/waterfall-wiki/tests/guidance-integration.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { visitBrowserDocument } from "./brand-navigation.mjs"; -import assert from 'node:assert/strict'; -import {verifyGuidanceWorkspaceUI} from './guidance-workspace-ui'; -import {chromium} from 'playwright-core'; -import {randomUUID} from 'node:crypto'; -import {newToken,tokenDigest,ACCOUNT_COOKIE} from '../src/lib/account-crypto'; -import {guidanceXml,type Guidance} from '../src/content/guides/catalog'; -import {db} from '../src/lib/db'; -import {publishDocument,resolveDocument,withdrawDocument,discoverDocuments} from '../src/lib/guidance-store'; -export async function verifyGuidancePublishing(base:string){ - const owner=randomUUID(),other=randomUUID(),id='user-'+owner+'-document'; - for(const uid of [owner,other])await db().query("INSERT INTO account_users(id,email,password_hash,verified_at) VALUES($1,$2,'test',now())",[uid,uid+'@example.com']); - const doc:Guidance={id,revision:1,kind:'context',title:'实际数据库文档',summary:'摘要',applies:'条件',body:'正文',steps:[],checks:[],links:[],tags:[]}; - await assert.rejects(()=>publishDocument(other,doc),/INVALID_ID/); - await publishDocument(other,{...doc,id:'user-'+other+'-document'}); - await publishDocument(owner,doc);assert.equal((await resolveDocument(id,1))?.title,doc.title); - await assert.rejects(()=>publishDocument(other,{...doc,revision:2}),/INVALID_ID/); - const concurrent=await Promise.allSettled([publishDocument(owner,{...doc,revision:2,body:'新正文'}),publishDocument(owner,{...doc,revision:2})]);assert.equal(concurrent.filter(r=>r.status==='fulfilled').length,1); - assert.equal((await resolveDocument(id,1))?.body,'正文');assert.equal(await resolveDocument(id,999),null); - await assert.rejects(()=>db().query('UPDATE guidance_revisions SET content=$2 WHERE document_id=$1',[id,{}]),/immutable/); - assert.equal(await withdrawDocument(other,id),false);assert.equal(await withdrawDocument(owner,id),true);assert.equal(await resolveDocument(id),null);assert.ok(await resolveDocument(id,1));assert.ok(!(await discoverDocuments()).some(d=>d.id===id)); - const token=newToken();await db().query("INSERT INTO account_tokens(digest,user_id,kind,expires_at) VALUES($1,$2,'session',now()+interval '1 hour')",[tokenDigest(token),owner]); - const headers={'Content-Type':'application/json',origin:process.env.ACCOUNT_PUBLIC_ORIGIN||base,cookie:ACCOUNT_COOKIE+'='+token}; - const httpDoc={...doc,id:'user-'+owner+'-http',tags:['往返标签']}; - const request=(path:string,method:string,body?:unknown,extra:Record={})=>fetch(base+'/api/documents'+path,{method,headers:{...headers,...extra},...(body?{body:typeof body==='string'?body:JSON.stringify(body)}:{})}); - assert.equal((await request('','POST',httpDoc,{cookie:''})).status,401); - assert.equal((await request('','POST',httpDoc,{origin:'https://untrusted.example'})).status,403); - const preview=await request('/preview','POST',httpDoc);assert.equal(preview.status,200,await preview.clone().text());assert.equal(await resolveDocument(httpDoc.id,1),null); - const created=await request('','POST',guidanceXml(httpDoc),{'Content-Type':'application/xml'});assert.equal(created.status,201,await created.clone().text()); - assert.equal((await request('','POST',httpDoc)).status,409); - assert.equal((await request('','POST',{...httpDoc,id:'user-'+other+'-http'})).status,400); - const own=await fetch(base+'/api/documents?author='+owner).then(r=>r.json());assert.ok(own.documents.some((d:{id:string})=>d.id===httpDoc.id));assert.ok(own.documents.every((d:{author:string})=>d.author===owner));assert.equal((await fetch(base+'/api/documents?author=invalid')).status,400); - const xml=await fetch(base+'/api/documents/'+httpDoc.id+'?revision=1&format=xml');assert.equal(xml.status,200);assert.match(await xml.text(),/往返标签<\/tag>/); - const page=await fetch(base+'/documents/'+httpDoc.id+'?revision=1');assert.equal(page.status,200);assert.ok((await page.text()).includes('实际数据库文档')); - const directory=await fetch(base+'/api/documents');assert.equal(directory.status,200);assert.ok((await directory.json()).documents.some((d:{id:string})=>d.id===httpDoc.id),'Published document must appear in the public API'); - // The document discovery list hydrates from the real API; the homepage is the product landing page. - const browser=await chromium.launch({executablePath:process.env.CHROMIUM_PATH||'/usr/bin/chromium',headless:true,args:['--no-sandbox','--disable-dev-shm-usage']}); - try{ - const page=await browser.newPage();page.setDefaultTimeout(60000);page.setDefaultNavigationTimeout(60000); - const navigationDiagnostics:string[]=[],inflight=new Set(); - const record=(value:string)=>{navigationDiagnostics.push(value.slice(0,1800));if(navigationDiagnostics.length>24)navigationDiagnostics.shift();}; - page.on('pageerror',error=>record('pageerror: '+error.message)); - page.on('console',message=>{if(message.type()==='error')record('console: '+message.text());}); - page.on('request',request=>inflight.add(request.url())); - page.on('requestfinished',request=>inflight.delete(request.url())); - page.on('requestfailed',request=>{inflight.delete(request.url());record('requestfailed: '+new URL(request.url()).pathname+' '+request.failure()?.errorText)}); - page.on('response',response=>{const path=new URL(response.url()).pathname;if(path.startsWith('/api/')||path.includes('/_next/data/'))void response.text().then(body=>record(`data ${response.status()} ${path}: ${body.slice(0,1400)}`)).catch(()=>{});else if(response.status()>=400||response.request().resourceType()==='script')record(`resource ${response.status()} ${path}`);}); - try{ - const link=`a[href="/documents/${httpDoc.id}?revision=1"]`; - const navigation=await visitBrowserDocument(page,base+'/documents');record('directory HTTP '+navigation?.status()); - await page.locator(link).waitFor({state:'visible'});assert.equal(await page.locator(link).getByRole('heading').textContent(),httpDoc.title); - // Published matches must not inherit the examples' empty state; submitted filters survive reload. - const search=page.getByRole('textbox',{name:'搜索文档'}); - await search.fill(httpDoc.title);await search.press('Enter'); - await page.waitForURL(url=>url.searchParams.get('q')===httpDoc.title,{waitUntil:'commit'}); - await page.getByRole('status').filter({hasText:'找到 2 篇文档'}).waitFor(); - assert.equal(await page.getByText('没有匹配文档',{exact:false}).count(),0); - await visitBrowserDocument(page,page.url(),true); - await page.getByRole('status').filter({hasText:'找到 2 篇文档'}).waitFor(); - // The matching count can also describe the unfiltered list before router hydration. - await page.waitForFunction(title=>document.querySelector('input[aria-label="搜索文档"]')?.value===title,httpDoc.title); - assert.equal(await search.inputValue(),httpDoc.title); - await page.getByRole('tab',{name:/事实依据/}).click(); - await page.waitForURL(url=>url.searchParams.get('kind')==='evidence',{waitUntil:'commit'}); - await page.getByRole('heading',{name:'没有匹配文档',exact:true}).waitFor(); - await page.getByRole('button',{name:'清除所有筛选'}).click(); - await page.waitForURL(url=>url.search==='',{waitUntil:'commit'}); - await page.locator(link).waitFor({state:'visible'}); - await page.waitForFunction(()=>document.querySelector('input[aria-label="搜索文档"]')?.value===''); - assert.equal(await search.inputValue(),''); - await page.getByRole('navigation',{name:'主导航',exact:true}).getByRole('link',{name:'社区文档',exact:true}).click();await page.locator(link).waitFor({state:'visible'}); - await page.locator(link).click(); - await page.waitForURL(url=>url.pathname==='/documents/'+httpDoc.id&&url.searchParams.get('revision')==='1',{waitUntil:'commit'}); - await page.getByRole('heading',{name:httpDoc.title,exact:true,level:1}).waitFor(); - assert.equal(await page.locator('h1').textContent(),httpDoc.title); - await visitBrowserDocument(page,base+'/write');await page.getByRole('heading',{name:'发布独立文档',exact:true,level:1}).waitFor();assert.equal(await page.locator('h1').textContent(),'发布独立文档'); - }catch(error){console.error('Document navigation diagnostics',JSON.stringify({url:page.url(),headings:await page.locator('h1').allTextContents(),body:(await page.locator('body').innerText()).slice(0,2200),inflight:[...inflight].map(url=>new URL(url).pathname),events:navigationDiagnostics}));throw error;} - await page.close(); - await verifyGuidanceWorkspaceUI(base,browser); - console.log('Workspace desktop/mobile, filters, reading, account and publishing assertions passed in a fresh browser context'); - }finally{await browser.close();} - assert.equal((await request('/'+httpDoc.id,'DELETE',undefined,{cookie:''})).status,401); - assert.equal((await request('/'+httpDoc.id,'DELETE')).status,200); - assert.equal((await fetch(base+'/api/documents/'+httpDoc.id)).status,404); - assert.equal((await fetch(base+'/api/documents/'+httpDoc.id+'?revision=1')).status,200); - -} diff --git a/apps/waterfall-wiki/tests/guidance-publishing.test.ts b/apps/waterfall-wiki/tests/guidance-publishing.test.ts deleted file mode 100644 index 6661976..0000000 --- a/apps/waterfall-wiki/tests/guidance-publishing.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {parseDocumentXml,documentInput} from '../src/lib/guidance-input'; -import {guidanceXml} from '../src/content/guides/catalog'; -const doc={id:'user-test',revision:1,kind:'guide' as const,title:'A & B',summary:'摘要',applies:'适用',body:'<正文>',steps:['一步'],checks:[],links:[],tags:[]}; -test('XML canonical round trip and strict rejection',()=>{const xml=guidanceXml(doc);assert.deepEqual(parseDocumentXml(xml),doc);for(const bad of [xml.replace('',''),xml.replace('</body>','</body><title>duplicate'),xml.replace('urn:linked-guidance:0.1','urn:linked-guidance:0.2'),xml.replace('&','&unknown;'),xml.replace('','\n\n[unsafe](javascript:alert)\n\n![图片说明](/api/media/example)'); - assert.ok(!html.includes('`; -const common='默认模式使用已分类的原字形与图形 PNG,重组后保留原像素;它不是字体或矢量轮廓的自动恢复。替换文字时才渲染 DOM 文本,字体变化须重新验收。微字内容未辨识时保持 null,不猜写。参考图中的灰绿区域是图片占位/色块,没有可辨认的真实照片。'; -const sourceNote='原始拼图仅 736×980,每张约 112×173;放大不恢复缺失细节。图片与构件均通过本文的 /api/media 链接读取,JSON 和源码直接作为文本提供。'; -const routerMarkdown=`这是一份以文本和图像为载体的 27 张原图排版路由文档。左侧说明栏不计入;从上到下、每行从左到右编号,前三行各 5 张、后两行各 6 张。每张设计有独立 post、源坐标、构件清单与代码示例。\n\n## 27 张固定对应索引\n\n| ID | 原图位置(含左侧说明栏所在列) | 设计 |\n| --- | --- | --- |\n${layouts.map(l=>`| ${link(l.id)} | 第 ${l.source.row} 行、第 ${l.source.column} 列 | ${l.name} |`).join('\n')}\n\n## 路由规则\n\n输入必须给出 ref-01 至 ref-27 中的一个 ID;不能用近似风格名随意替换构图。读取目标 post 的原图、分类图与 JSON,再按 source.box 和每个构件 box 定位。对外使用完整构件键,例如 ref-01/T01;不得混入 ref-02 的素材。\n\n## 三类构件\n\nT 是文字:标题、品牌、日期、正文、图注。I 是图片占位或色块。D 是线框、圆环、节点和引线。纸底单独保存。相同单词的残字应合并,ref-01 的品牌字标整体为 T01、左下说明为 T02,没有重复的 T03。ref-21 的 03 日期与旋转英文保持独立。\n\n## 识别与复核\n\n先按背景差分和四邻域连通域检测 27 张外框;按 y 聚行、按 x 排序。使用本机 Vision 提取 OCR 候选,再人工核对文字区与图形区;图位按局部颜色种子和连通形状提取。残字按基线、间距和字高合并。默认图层实际合成覆盖 527,155 个像素,与原裁片 RGB 一致;这只证明重组保真,不代替语义复核。\n\n${common}\n\n${sourceNote}\n\n## 调用与交付\n\n1. 明确 layoutId。\n2. 阅读对应 post 的文字、图像与 JSON。\n3. 检查各构件 id、类型、坐标与方向。\n4. 根据 HTML、React 或 Next.js 示例创建你自己的作品。\n5. 替换内容后检查溢出、字体、竖排、裁切和透明边缘。\n\n所有本系列资料均是普通 Wiki post 与媒体资产;不需要独立演示页、检查器、JSON 文件端点或下载页面。获取纯文本用同一个 post 地址加 ?agent_mode=True。\n\n## 共用 React / TSX 渲染器\n\n将下面代码保存到你自己的项目中,配合子文档的 JSON;这些是创作示例,不是 Wiki 站的页面。跨站使用前可先通过 API 下载素材到自己的项目,并按需替换资源路径。\n\n${fence('tsx',renderer)}\n`; -const posts=[{key:'router',publicId:ids.router,slug:'layout-reference-router',title:'高级感排版 · 27 张原图 Router',description:'27 张实际原图的编号索引,按文字、图位和装饰分层调用;文档与图片均可直接读取。',markdown:routerMarkdown,main:['router-cover.png','numbered-source.png'],extra:['source.png']}]; -for(const l of layouts){ - const text=l.elements.filter(e=>e.kind==='text'),images=l.elements.filter(e=>e.kind==='image'),decor=l.elements.filter(e=>e.kind==='decoration'); - const markdown=`${l.id} 只对应原图第 ${l.source.row} 行、第 ${l.source.column} 列的“${l.name}”。原裁片 source.box = [${l.source.box.join(', ')}]。\n\n## 图像阅读顺序\n\n第 1 张是原裁片;第 2 张是构件标注;第 3 张为蓝色文字掩膜;第 4 张为橙色图位掩膜;第 5 张为绿色装饰掩膜。文字 ${text.length} 组、图片占位/色块 ${images.length} 组、装饰 ${decor.length} 组。T 和 I 可以叠放,但不能混成同一构件。\n\n## 构件逐项对应\n\n| 完整键 | 类型 | 用途 | 坐标 x,y,w,h | 内容或状态 |\n| --- | --- | --- | --- | --- |\n${l.elements.map(e=>`| ${l.id}/${e.id} | ${kindName(e.kind)} | ${e.role} | ${e.box.join(', ')} | ${e.kind==='text'?(e.text??'待核对,保留原字形'):'非文字'} |`).join('\n')}\n\n## 制作步骤\n\n1. 使用 ${l.width}×${l.height} 的源坐标建立画板,保持比例。\n2. 按 I 构件放置 ${images.map(e=>e.id+' '+e.role).join('、')},采用本构件 mask。\n3. 分别放置 ${text.map(e=>e.id+' '+e.role).join('、')}。\n4. 还原 ${decor.map(e=>e.id+' '+e.role).join('、')||'原有细节'}。\n5. 与原裁片叠加比较,检查同词拆框、文字遮挡与边缘裁切。\n\n## 文字方向\n\n${text.map(e=>'- '+e.id+':'+({vertical:'竖排',rotated:'整体旋转',angled:'倾斜排字',curved:'沿曲线排字',horizontal:'横排'}[e.direction]||e.direction)+';'+(e.text?'已核对“'+e.text+'”':'无法可靠辨认,保留独立原字形')+'。').join('\n')}\n\n${common}\n\n${sourceNote}\n\n## 机器可读构件清单\n\n以下 JSON 即本设计清单,所有资源都指向标准媒体 API。将它另存到创作项目中即可使用,无需访问其他页面。\n\n${fence('json',JSON.stringify(l,null,2))}\n\n## HTML / CSS 示例\n\n${fence('html',htmlExample(l))}\n\n## React / TSX 示例\n\n${fence('tsx',reactExample(l))}\n\n## 共用渲染器源码\n\n${fence('tsx',renderer)}\n\n## Next.js App Router 示例\n\n${fence('tsx',nextExample(l))}\n\n## 替换与验收\n\n只允许本设计完整键,如 ${l.id}/${text[0].id};图位使用 I 键,不能作为文字键。原样模式保留原图;替换模式会改变内容和字体,需重新验收。弧形文字须沿该设计专用 SVG 路径排字,通用渲染器拒绝直接改成横排。网页长文另提供正常字号正文,不把低分辨率海报小字当作阅读版。\n\n${link('router','返回 27 张 Router')}\n`; - const extra=new Set(['source.png',`${l.id}/background.png`]);for(const e of raw.find(x=>x.id===l.id).elements)for(const key of ['asset','mask','underlay'])if(e[key])extra.add(e[key].slice('asset:'.length)); - posts.push({key:l.id,publicId:ids[l.id],slug:'layout-reference-'+l.id,title:l.id+' · '+l.name,description:`${l.id} 原图一一对应:${text.length} 组文字、${images.length} 组图位、${decor.length} 组装饰;含图像、坐标及 HTML/React/Next.js 示例。`,markdown,main:[`${l.id}/source.png`,`${l.id}/annotated.png`,`${l.id}/text-map.png`,`${l.id}/image-map.png`,`${l.id}/decoration-map.png`],extra:[...extra]}); -} -for(const p of posts){p.markdown=p.markdown.trim();if(p.markdown.length>100000)throw Error('正文超长 '+p.key);if(p.markdown.includes('/layout-reference/'))throw Error('正文仍有专用静态路径 '+p.key)} -if(dryRun){console.log(JSON.stringify({posts:posts.length,uniqueFiles:assetNames.size,uniqueHashes:new Set(Object.values(byFile).map(a=>a.hash)).size,maxText:Math.max(...posts.map(p=>p.markdown.length)),cover:'router-cover.png'}));process.exit(0)} -const tag=await api('/api/tags',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:'高级感排版',slug:'premium-layout'})}); -for(const p of [...posts.slice(1),posts[0]]){ - const seen=new Set(),assets=[];for(const[files,role]of [[p.main,'content'],[p.extra,'caption']])for(const name of files){const id=state.assets[byFile[name].hash];if(!seen.has(id)){assets.push({id,role});seen.add(id)}} - const coverAssetId=state.assets[byFile[p.main[0]].hash]; - let existing=await api('/api/notes/'+p.publicId); - if(existing){if(existing.entry.postSlug!==p.slug)throw Error('ID 已被其他内容占用:'+p.publicId);const e=existing.entry;const same=e.title===p.title&&e.description===p.description&&e.editableMarkdown===p.markdown&&e.coverAssetId===coverAssetId&&JSON.stringify(e.assets.map(a=>({id:a.id,role:a.role})))===JSON.stringify(assets)&&e.categories.includes(tag.name);if(!same)existing=await api('/api/notes/'+p.publicId,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:p.title,description:p.description,markdown:p.markdown,tags:[tag.id],revision:e.revision,assets,coverAssetId})});} - else await api('/api/posts',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({kind:'article',title:p.title,publicId:p.publicId,slug:p.slug,description:p.description,text:p.markdown,tags:[tag.id],coverAssetId,assets})}); - state.posts[p.key]={publicId:p.publicId,slug:p.slug,coverAssetId,assets:assets.length};await save();console.log('Published '+p.key+' /explore/'+p.publicId); -} -console.log(`Published ${posts.length} ordinary Wiki posts using text and image APIs only.`); diff --git a/scripts/publish-layout-studio-tutorial.mjs b/scripts/publish-layout-studio-tutorial.mjs deleted file mode 100644 index 261a8dd..0000000 --- a/scripts/publish-layout-studio-tutorial.mjs +++ /dev/null @@ -1,14 +0,0 @@ -/** Run only inside the Wiki container via auth-cli.cjs run. API-only editorial update. */ -import {readFile,writeFile} from 'node:fs/promises'; -const base='http://127.0.0.1:3000',id='8eb8c76500580f7a4b56ba3c',dir='/tmp/prompt-layout-studio'; -const layouts=JSON.parse(await readFile(dir+'/catalog.json','utf8')); -const stateFile='/tmp/prompt-layout-studio-state.json';let state;try{state=JSON.parse(await readFile(stateFile,'utf8'))}catch{state={designs:{}}} -for(const l of layouts){if(state.designs[l.id])continue;const r=await fetch(base+'/api/studio/designs',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({layoutId:l.id,name:l.id+' · 初始排版',parameters:{}})});if(!r.ok)throw Error('Design save failed: '+r.status);state.designs[l.id]=(await r.json()).design;await writeFile(stateFile,JSON.stringify(state),{mode:0o600});} -let markdown=await readFile(dir+'/tutorial.md','utf8'); -markdown=markdown.replace('{{LAYOUTS}}',layouts.map(l=>`- **[${l.id} · ${l.name}](/studio/layouts/${l.id})** · [已保存初始效果](${state.designs[l.id].url}) · [构件文档](${l.tutorialUrl}) · [API 参数](/studio/api/layouts/${l.id}?agent_mode=True)`).join('\n')); -const response=await fetch(base+'/api/notes/'+id);if(!response.ok)throw Error('Router unavailable');const {entry}=await response.json(); -try{await readFile('/tmp/prompt-layout-router-before.json')}catch{await writeFile('/tmp/prompt-layout-router-before.json',JSON.stringify(entry),{mode:0o600})} -const tags=await fetch(base+'/api/tags').then(r=>r.json()),selected=tags.filter(t=>entry.categories.includes(t.name)||(entry.systemTags||[]).some(s=>s.id===t.id)).map(t=>t.id); -const result=await fetch(base+'/api/notes/'+id,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:'27 套排版 · Studio 参数 API 与保存教程',description:'27 个原图对应效果页,按 T/I/D 构件调整参数;API 渲染、保存独立 ID 与再次编辑,和 Wiki 共用数据库。',markdown,tags:selected,assets:entry.assets,coverAssetId:entry.coverAssetId,revision:entry.revision})}); -if(!result.ok)throw Error('Router update failed: '+result.status); -console.log(JSON.stringify({routerUpdated:true,studioPages:layouts.length,savedDesigns:Object.keys(state.designs).length,contentExported:false})); diff --git a/scripts/publish-shadow-covers.mjs b/scripts/publish-shadow-covers.mjs deleted file mode 100644 index 099b79c..0000000 --- a/scripts/publish-shadow-covers.mjs +++ /dev/null @@ -1,46 +0,0 @@ -/** Run with the production auth CLI after staging reviewed covers and manifest. Never stores auth tokens. */ -import {readFile,writeFile} from 'node:fs/promises'; -import {join,resolve} from 'node:path'; -import {createHash} from 'node:crypto'; -const root=resolve(process.env.SHADOW_COVERS_DIR||'/tmp/shadow-covers'); -const base=process.env.AUTH_CHECK_BASE_URL||'http://127.0.0.1:3000'; -const manifest=JSON.parse(await readFile(join(root,'manifest.json'),'utf8')); -const backup=JSON.parse(await readFile(join(root,'notes-before.json'),'utf8')); -let state;try{state=JSON.parse(await readFile(join(root,'publication.json'),'utf8'))}catch{state={covers:{},archived:[],beforeArchive:[]}} -const persist=()=>writeFile(join(root,'publication.json'),JSON.stringify(state,null,2)); -async function api(path,body,method=body?'POST':'GET'){const r=await fetch(base+path,{method,...(body?{headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}:{})});const data=await r.json();if(!r.ok)throw Error(path+' '+r.status+' '+JSON.stringify(data));return data} -async function upload(key,file){if(state.covers[key]?.assetId)return state.covers[key].assetId;const bytes=await readFile(join(root,file)),form=new FormData();form.set('kind','image');form.set('assetOnly','true');form.set('files',new Blob([bytes],{type:'image/png'}),file);const r=await fetch(base+'/api/upload',{method:'POST',body:form});const data=await r.json();if(!r.ok)throw Error('Upload '+r.status+JSON.stringify(data));state.covers[key]={assetId:data.assets[0],sha256:createHash('sha256').update(bytes).digest('hex')};await persist();return data.assets[0]} -for(const item of manifest){ - const assetId=await upload(item.key,item.outputFile),{entry:e,tags}=await api('/api/notes/'+item.postId); - const marker=''; - if(e.coverAssetId!==assetId){ - const ids=[...(e.systemTags||[]).map(t=>t.id),...e.categories.map(name=>{const t=tags.find(t=>t.kind==='normal'&&t.name===name);if(!t)throw Error('Missing tag '+name);return t.id})]; - const attribution=`\n\n${marker}\n## 本篇封面:照片入皮影\n\n摄影/馆藏来源:[原始照片](${item.source});作者:${item.author}。\n\n封面图许可:[${item.license}](${item.licenseUrl})(仅适用于本封面及其改编,不改变正文原有许可)。照片经裁切,皮影由 AI 根据原照生成;上下分区经代码精确排版为 1:1。${item.key==='lion'?'上半部亦使用 AI 对原照扩图。':''}\n`; - const markdown=e.editableMarkdown.includes(marker)?e.editableMarkdown:e.editableMarkdown+attribution; - const assets=(e.assets||[]).map(({id,role})=>({id,role})); - if(!e.postSlug){ - // Retain the original skill's two style examples, already stored by its companion notes. - assets.push({id:'102a1975-5343-4da9-91aa-c6914db50d5e',role:'content'},{id:'0a7a631d-0a4a-4a66-bae9-71d824469c50',role:'content'}); - } - if(!assets.some(a=>a.id===assetId))assets.push({id:assetId,role:'content'}); - const input={title:e.title,description:e.description,tags:[...new Set(ids)],assets,coverAssetId:assetId}; - if(e.postSlug)await api('/api/notes/'+e.id,{...input,markdown,revision:e.revision},'PUT'); - else await api('/api/posts',{...input,kind:'article',publicId:e.id,slug:'guide-shadow-puppet',text:markdown}); - } - const after=(await api('/api/notes/'+e.id)).entry;if(after.coverAssetId!==assetId)throw Error('Cover mismatch'); - const media=await fetch(base+'/api/media/'+assetId);if(!media.ok||createHash('sha256').update(Buffer.from(await media.arrayBuffer())).digest('hex')!==state.covers[item.key].sha256)throw Error('Media checksum mismatch'); - state.covers[item.key].postId=e.id;state.covers[item.key].verified=true;await persist();console.log(JSON.stringify({cover:item.key,postId:e.id,verified:true})); -} -if(backup.oldRefs.length!==27)throw Error('Expected exact 27-entry archive allowlist'); -for(const item of backup.oldRefs){ - const e=(await api('/api/notes/'+item.id)).entry; - if(e.title!==item.title||!/^ref-(0[1-9]|1[0-9]|2[0-7]) · /.test(e.title)||!e.postSlug)throw Error('Archive allowlist mismatch '+item.id); - if(!state.beforeArchive.some(n=>n.id===e.id)){state.beforeArchive.push(e);await persist()} - await api('/api/posts/'+item.id+'/archive',{archived:true}); - const after=(await api('/api/notes/'+item.id)).entry; - if(!after.archived||after.editableMarkdown!==e.editableMarkdown||JSON.stringify(after.assets)!==JSON.stringify(e.assets))throw Error('Archive preservation failed'); - if(!state.archived.includes(e.id))state.archived.push(e.id);await persist(); -} -for(const category of ['照片入皮影','高级感排版']){let cursor=null,entries=[];do{const d=await api('/api/explore-feed?category='+encodeURIComponent(category)+(cursor?'&cursor='+encodeURIComponent(cursor):''));entries.push(...d.entries);cursor=d.nextCursor}while(cursor);if(category==='照片入皮影'&&!manifest.every(m=>entries.some(e=>e.id===m.postId)))throw Error('Missing cover post');if(entries.some(e=>state.archived.includes(e.id)))throw Error('Archived article still discoverable');state[category]=entries.map(e=>({id:e.id,title:e.title}));} -const saved=(await api('/api/notes/38a7a8ea-6b3b-46fd-902f-431d2f351cf4')).entry;if(saved.archived)throw Error('User design incorrectly archived'); -state.completedAt=new Date().toISOString();await persist();console.log(JSON.stringify({covers:manifest.length,archived:state.archived.length,userDesignPreserved:true})); diff --git a/scripts/publish-studio-guide.sh b/scripts/publish-studio-guide.sh deleted file mode 100644 index 3ece921..0000000 --- a/scripts/publish-studio-guide.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# Run on ces-control only after both services have deployed this revision. -set -Eeuo pipefail -ROOT=$(git rev-parse --show-toplevel) -IMAGE=$(docker inspect --format '{{.Config.Image}}' prompt-waterfall-wiki-1) -[[ "$IMAGE" =~ ^waterfall-wiki:[0-9a-f]{40}$ ]] || exit 1 -OUT="$ROOT/artifacts/studio-guide" -mkdir -p "$OUT" -chown 1000:1000 "$OUT" -docker run --rm --volumes-from prompt-waterfall-wiki-1:ro \ - --mount "type=bind,src=$ROOT,dst=/delivery,readonly" \ - --mount "type=bind,src=$OUT,dst=/tmp/studio-library" \ - -e AUTH_CONFIG_FILE=/auth/config.json -e AUTH_CHECK_BASE_URL=http://8.211.154.134:18131 \ - --entrypoint node "waterfall-wiki-tests:${IMAGE#waterfall-wiki:}" dist/auth-cli.cjs run /delivery/scripts/publish-studio-library.mjs diff --git a/scripts/publish-studio-library.mjs b/scripts/publish-studio-library.mjs deleted file mode 100644 index e76c4b3..0000000 --- a/scripts/publish-studio-library.mjs +++ /dev/null @@ -1,41 +0,0 @@ -/** Execute via production auth-cli run; token stays in memory. Uses only the designated origin. */ -import {readFile,writeFile,mkdir} from 'node:fs/promises'; -const base=process.env.AUTH_CHECK_BASE_URL||'http://8.211.154.134:18131'; -if(base!=='http://8.211.154.134:18131')throw Error('Use the designated acceptance origin'); -const dir=process.env.STUDIO_LIBRARY_DIR||'/tmp/studio-library'; -await mkdir(dir,{recursive:true}); -async function api(path,body,method=body?'POST':'GET'){ - const response=await fetch(base+path,{method,...(body?{headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}:{})}); - if(!response.ok)throw Error(path+' status '+response.status); - return response.json(); -} -const capability=await fetch(base+'/api/studio/series?agent_mode=True');if(!capability.ok||!(await capability.text()).includes('schemaVersion 2'))throw Error('Series API is not deployed; refusing to publish future documentation'); -const studioCapability=await api('/studio/api/layouts');if(studioCapability.seriesDocumentVersion!==1)throw Error('Studio renderer is not deployed; refusing to publish future documentation'); -const canonical='8eb8c76500580f7a4b56ba3c',legacy='9ebd8f78d54b4b9db741b4e8'; -const {entry:current,tags}=await api('/api/notes/'+canonical),{entry:old}=await api('/api/notes/'+legacy); -// Snapshot only non-secret editorial state. Retain the first backup across repeat publication. -for(const [id,entry] of [[canonical,current],[legacy,old]]){try{await readFile(dir+'/'+id+'-before.json')}catch{await writeFile(dir+'/'+id+'-before.json',JSON.stringify(entry),{mode:0o600})}} -const catalogue=await api('/studio/api/layouts'); -let markdown=(await readFile(new URL('../content/studio-tools/layouts-api.md',import.meta.url),'utf8')).replace('{{LAYOUTS}}',catalogue.layouts.filter(l=>l.family==='fixed').map(l=>`- [${l.id} · ${l.name}](${l.url}) · [参数](${l.api})`).join('\n')); -const appendixMarker='## 兼容工具原说明与代码示例'; -const historicalNotice='> 以下保留历史工具示例,不代表当前 API 保存与打开流程。历史文字中关于首页、编辑器和旧结果地址的描述已过时:当前由 API 创建 ID,直接打开 `/studio/template-layout?id={UUID}`,无需选版或编辑器。当前契约以上文为准。'; -if(current.editableMarkdown.includes(appendixMarker)){ - const appendix=current.editableMarkdown.slice(current.editableMarkdown.indexOf(appendixMarker)); - markdown+='\n\n'+(appendix.includes(historicalNotice)?appendix:appendix.replace(appendixMarker,appendixMarker+'\n\n'+historicalNotice)); -}else{ - if(old.archived)throw Error('Original toolbox appendix is missing; refusing to replace it with the archived redirect'); - const legacyOriginal=JSON.parse(await readFile(dir+'/'+legacy+'-before.json','utf8')).editableMarkdown; - markdown+='\n\n'+appendixMarker+'\n\n'+historicalNotice+'\n\n以下保留原工具的说明与 HTML/React/Next.js 调用示例;它们是无状态兼容接口,持久化页面请使用上文 CRUD。\n\n'+legacyOriginal; -} -const tagIds=entry=>[...new Set([...(entry.systemTags||[]).map(t=>t.id),...entry.categories.map(name=>tags.find(t=>t.name===name)?.id).filter(Boolean)])]; -const body=(entry,text,title=entry.title)=>({title,description:entry.description,markdown:text,tags:tagIds(entry),assets:entry.assets,coverAssetId:entry.coverAssetId,revision:entry.revision}); -await api('/api/notes/'+canonical,{...body(current,markdown,'Studio 图文 API · 多页作品与统一视觉设计'),description:'coding agent 制定主题分镜与统一视觉,生成配图,经 HTTP 保存可修订多页作品;凭稳定 ID 直接打开原生结果并在远程导出高清图片。'},'PUT'); -const redirectText=`# Studio 文档已合并\n\n[前往 Studio 排版 API 统一入口](/explore/${canonical})\n\n[可视化 API 库](/studio) · [原 ref-17 工具](/studio/ideas/learning-skills-template) · [原三种构图工具](/studio/ideas/layout-selection)\n\n完整原说明与代码示例已保留在统一入口的兼容工具章节。此记录仅可恢复归档,未删除工具、素材或设计。`; -await api('/api/notes/'+legacy,body(old,redirectText),'PUT'); -await api('/api/posts/'+legacy+'/archive',{archived:true}); -const entries=await api('/api/explore-feed?category='+encodeURIComponent('文档入口')); -const expected=['3c98946754c33309d73d5284','4ce3283fd98bb03a8fb39a19',canonical].sort(); -if(entries.total!==3||entries.nextCursor||JSON.stringify(entries.entries.map(e=>e.id).sort())!==JSON.stringify(expected))throw Error('Document-entry category does not contain exactly the intended three posts'); -const after=(await api('/api/notes/'+legacy)).entry; -if(!after.archived||!after.editableMarkdown.includes(canonical))throw Error('Legacy compatibility link missing'); -console.log(JSON.stringify({canonical:base+'/explore/'+canonical,legacyArchived:true,documentEntryCount:entries.total})); diff --git a/scripts/publish-studio-tools.mjs b/scripts/publish-studio-tools.mjs deleted file mode 100644 index 4e6af33..0000000 --- a/scripts/publish-studio-tools.mjs +++ /dev/null @@ -1,18 +0,0 @@ -/** Publish a normal Wiki tutorial using only generic post, tag and media APIs. */ -import {readFile,writeFile,mkdir} from 'node:fs/promises'; -const base=(process.env.WIKI_BASE||'http://8.211.154.134:18131').replace(/\/$/,''),out=process.env.STUDIO_PUBLISH_OUTPUT||'../output/studio-tools',publicId='9ebd8f78d54b4b9db741b4e8'; -await mkdir(out,{recursive:true}); -async function api(path,options={}){const r=await fetch(base+path,{...options,signal:AbortSignal.timeout(60000)});if(r.status===404)return null;if(!r.ok)throw Error(path+': '+r.status+' '+(await r.text()).slice(0,300));return r.json()} -const post=(path,body,method='POST')=>api(path,{method,headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}); -let state;try{state=JSON.parse(await readFile(out+'/published.json','utf8'))}catch{state={base,assets:{}}}if(state.base!==base)throw Error('Wrong publish target'); -for(const name of ['cover.png','ref17-master.png','ref17-notes.png','ref17-teaching.png','ref17-english.png','ref-01.png','ref-07.png','ref-26.png']){ - if(state.assets[name])continue;const form=new FormData();form.set('kind','image');form.set('assetOnly','true');form.append('files',new Blob([await readFile(out+'/'+name)],{type:'image/png'}),name); - const data=await api('/api/upload',{method:'POST',body:form});state.assets[name]=data.assets[0];await writeFile(out+'/published.json',JSON.stringify(state,null,2)); -} -const ids=JSON.parse(await readFile('content/layout-reference/post-ids.json','utf8')); -const gallery=Object.entries(state.assets).filter(([name])=>name!=='cover.png').map(([name,id])=>`![${name.replace('.png','')} 模板试版](/api/media/${id})`).join('\n\n'); -const text=(await readFile('content/studio-tools/tutorial.md','utf8')).replace('{{GALLERY}}',gallery).replace('{{REF26}}',`[ref-26](/explore/${ids['ref-26']})`); -const tag=await post('/api/tags',{name:'Studio工具',slug:'studio-tools'}),title='Studio 封面工具 · 同一母版与三种构图',description='从 PROMPT 进入两件 Studio 工具:ref-17 四张同母版试版,以及 ref-01/07/26 三种构图。含可访问图像、GET/POST API、HTML/React/Next.js 示例与构件验收。'; -const assets=Object.values(state.assets).map(id=>({id,role:'content'})),coverAssetId=state.assets['cover.png'],old=await api('/api/notes/'+publicId); -state.post=old?await post('/api/notes/'+publicId,{title,description,markdown:text,tags:[tag.id],assets,coverAssetId,revision:old.entry.revision},'PUT'):await post('/api/posts',{publicId,slug:'studio-cover-tools',kind:'article',title,description,text,tags:[tag.id],assets,coverAssetId}); -await writeFile(out+'/published.json',JSON.stringify(state,null,2));console.log(base+'/explore/'+publicId); diff --git a/scripts/recognize-layout-text.swift b/scripts/recognize-layout-text.swift deleted file mode 100644 index ed42ce3..0000000 --- a/scripts/recognize-layout-text.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation -import Vision -import AppKit -let folder=URL(fileURLWithPath:CommandLine.arguments[1]) -var result:[[String:Any]]=[] -for url in try FileManager.default.contentsOfDirectory(at:folder,includingPropertiesForKeys:nil).filter({$0.pathExtension=="png"}).sorted(by:{$0.lastPathComponent<$1.lastPathComponent}) { - let request=VNRecognizeTextRequest() - request.recognitionLevel = .accurate - request.recognitionLanguages = ["zh-Hans","en-US"] - request.usesLanguageCorrection = false - request.minimumTextHeight = 0.004 - try VNImageRequestHandler(url:url).perform([request]) - let lines=(request.results ?? []).compactMap { observation -> [String:Any]? in - guard let candidate=observation.topCandidates(1).first else{return nil} - let b=observation.boundingBox - return ["text":candidate.string,"confidence":candidate.confidence,"box":[b.minX,1-b.maxY,b.width,b.height]] - } - result.append(["file":url.lastPathComponent,"lines":lines]) -} -let data=try JSONSerialization.data(withJSONObject:result,options:[.prettyPrinted,.sortedKeys]) -try data.write(to:URL(fileURLWithPath:CommandLine.arguments[2])) -print("OCR processed \(result.count) panels") diff --git a/scripts/save-studio-series.mjs b/scripts/save-studio-series.mjs deleted file mode 100644 index ef0897a..0000000 --- a/scripts/save-studio-series.mjs +++ /dev/null @@ -1,17 +0,0 @@ -/** Invoke via the deployed auth-cli run on ces-control; authorization stays in memory. */ -import {readFile,writeFile,mkdir} from 'node:fs/promises'; -import {resolve} from 'node:path'; -const [input,image,provenance,out]=process.argv.slice(4); // auth-cli.cjs run SCRIPT ARGS -if(!input||!image||!provenance||!out)throw Error('Supply input.json image.png provenance.json output-directory'); -const dir=resolve(out);await mkdir(dir,{recursive:true}); -const statePath=dir+'/save-state.json';let state={};try{state=JSON.parse(await readFile(statePath,'utf8'))}catch(e){if(e.code!=='ENOENT')throw e} -const base='http://8.211.154.134:18131'; -if(state.designId){const r=await fetch(base+'/api/studio/series/'+state.designId);if(!r.ok)throw Error('Saved state cannot be verified');const result=await r.json();await writeFile(dir+'/design.json',JSON.stringify(result.design,null,2));console.log(JSON.stringify({existing:true,id:state.designId,url:base+result.design.url}));process.exit(0)} -// An in-flight operation is deliberately not retried: reconcile uncertain outcomes through the API. -if(state.pending)throw Error('Previous request outcome unknown; reconcile '+state.pending+' before retrying'); -const save=()=>writeFile(statePath,JSON.stringify(state,null,2)); -if(!state.assetId){state.pending='upload';await save();const form=new FormData();form.set('kind','image');form.set('assetOnly','true');form.append('files',new Blob([await readFile(image)],{type:'image/png'}),'ginkgo-master.png');const r=await fetch(base+'/api/upload',{method:'POST',body:form});if(!r.ok)throw Error('Upload failed: '+r.status);const uploaded=await r.json();state.assetId=uploaded.assets[0];delete state.pending;await save()} -const body=JSON.parse(await readFile(input,'utf8')),source=JSON.parse(await readFile(provenance,'utf8')); -body.document.sources=[{...source,assetId:state.assetId}];for(const page of body.document.pages)page.parameters.images.I01={...page.parameters.images.I01,assetId:state.assetId}; -await writeFile(dir+'/series-request.json',JSON.stringify(body,null,2));state.pending='create';await save(); -const r=await fetch(base+'/api/studio/series',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});if(!r.ok)throw Error('Create failed: '+r.status+' '+await r.text());const {design}=await r.json();state.designId=design.id;delete state.pending;await save();await writeFile(dir+'/design.json',JSON.stringify(design,null,2));console.log(JSON.stringify({id:design.id,url:base+design.url,revision:design.revision,pages:design.document.pages.length})); diff --git a/scripts/segment-layout-reference.ts b/scripts/segment-layout-reference.ts deleted file mode 100644 index 9eb997d..0000000 --- a/scripts/segment-layout-reference.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** Deterministic segmentation + reviewed semantic regions; no generated imagery. */ -import sharp from 'sharp'; -import {readFile,writeFile,mkdir,rm} from 'node:fs/promises'; -import path from 'node:path'; -import {createHash} from 'node:crypto'; -import annotations from '../contentasset:annotations.json'; -import {textMergeTarget,unionBoxes} from '../contentasset:group-text'; -import ocr from '../contentasset:ocr-evidence.json'; -const root=path.resolve(import.meta.dirname,'..'),target=path.resolve(process.env.MEDIA_AUTHORING_ROOT||'/tmp/prompt-media-authoring'); -if(target===root||target.startsWith(root+path.sep))throw Error('MEDIA_AUTHORING_ROOT must be outside repository'); -type Box=[number,number,number,number]; -type Element={id:string;kind:'text'|'image'|'decoration';role:string;box:Box;text:string|null;direction:string;reading:'verified'|'unreadable'|'not-text';asset:string;mask?:string;underlay?:string;pixels:number}; -function components(mask:Uint8Array,w:number,h:number){ - const seen=new Uint8Array(mask.length),items:{box:Box;pixels:number[]}[]=[]; - for(let i=0;i=0&&kMath.abs(data[i*3+k]-c)))>19?1:0; - const found=components(mask,W,H).filter(({box:[,,w,h]})=>w>85&&w<125&&h>145&&h<190).map(({box:[x,y,w,h]})=>({x,y,w,h})); - if(found.length!==27)throw Error(`Expected 27 panels; found ${found.length}`); - const rows:number[]=[],cols:number[]=[]; - for(const p of [...found].sort((a,b)=>a.y-b.y))if(!rows.some(y=>Math.abs(y-p.y)<8))rows.push(p.y); - for(const p of [...found].sort((a,b)=>a.x-b.x))if(!cols.some(x=>Math.abs(x-p.x)<8))cols.push(p.x); - const panels=found.map(p=>({...p,row:rows.findIndex(y=>Math.abs(y-p.y)<8)+1,column:cols.findIndex(x=>Math.abs(x-p.x)<8)+1})).sort((a,b)=>a.row-b.row||a.x-b.x); - const manifests=[];let totalPixels=0; - for(const [index,p]of panels.entries()){ - const id='ref-'+String(index+1).padStart(2,'0'),spec=annotations[index],dir=path.join(target,id);await rm(dir,{recursive:true,force:true});await mkdir(dir,{recursive:true}); - const crop=await sharp(source).extract({left:p.x,top:p.y,width:p.w,height:p.h}).removeAlpha().raw().toBuffer(),w=p.w,h=p.h; - await sharp(crop,{raw:{width:w,height:h,channels:3}}).png().toFile(path.join(dir,'source.png')); - const owner=new Int16Array(w*h).fill(-1),elements:Element[]=[]; - function entry(kind:Element['kind'],role:string,box:number[],text:string|null=null,direction='horizontal'){ - const prefix={text:'T',image:'I',decoration:'D'}[kind],n=elements.filter(e=>e.kind===kind).length+1; - let[x,y,bw,bh]=box.map(Math.round);x=Math.max(0,Math.min(w-1,x));y=Math.max(0,Math.min(h-1,y)); - const e:Element={id:prefix+String(n).padStart(2,'0'),kind,role,box:[x,y,Math.max(1,Math.min(w-x,bw)),Math.max(1,Math.min(h-y,bh))],text,direction,reading:kind==='text'?(text===null?'unreadable':'verified'):'not-text',asset:'',pixels:0};elements.push(e);return e; - } - function region(box:Box,fn:(i:number)=>boolean){const[x,y,bw,bh]=box;for(let yy=y;yy(); - for(let y=Math.max(1,sy-7);y220||Math.min(...rgb)<65)continue;const key=rgb.map(v=>Math.round(v/8)).join(','),c=hist.get(key);if(c)c.count++;else hist.set(key,{rgb,count:1})} - const color=[...hist.values()].sort((a,b)=>b.count-a.count)[0]?.rgb;if(!color)throw Error(id+': no fill color');return color; - } - const imageColors=spec.images.map(image=>fillColor(image.seed)); - for(const d of spec.decor.filter(d=>d.box[3]<=2&&d.box[2]>8)){const e=entry('decoration',d.role,d.box);region(e.box,i=>Math.max(...crop.subarray(i*3,i*3+3))<225)} - for(const t of spec.texts){const e=entry('text',t.role,t.box,t.text,t.direction); - const[x,y,bw,bh]=e.box,hist=new Map();for(let yy=y;yyb[1]-a[1])[0][0]; - region(e.box,i=>{ - const rgb=[...crop.subarray(i*3,i*3+3)],lum=(rgb[0]+rgb[1]+rgb[2])/3; - if(t.tone==='light'||t.tone==='muted-light')return lum>mode+7; - if(t.tone==='muted-dark')return lum{const[a,b,c,d]=spec.images[n].box,x=i%w,y=Math.floor(i/w);return x>=a&&x=b&&yrgb[k]-v))<16}); - }) - } - - for(const[n,image]of spec.images.entries()){ - const e=entry('image',image.role,image.box),color=imageColors[n],candidate=new Uint8Array(w*h),[a,b,c,d]=image.box; - for(let y=Math.max(1,b-12);ycrop[i*3+k]-v))<=36)candidate[i]=1} - const[sx,sy]=image.seed,options=components(candidate,w,h).filter(c=>c.pixels.length>=12).sort((u,v)=>{ - const distance=(part:{box:Box})=>{const[x,y,bw,bh]=part.box;return Math.max(x-sx,sx-x-bw,0)**2+Math.max(y-sy,sy-y-bh,0)**2};return distance(u)-distance(v)||v.pixels.length-u.pixels.length; - }); - const selected=options[0];if(!selected)throw Error(id+': no connected image region '+image.role); - for(const i of selected.pixels)if(owner[i]===-1)owner[i]=elements.length-1; - } - - for(const d of spec.decor.filter(d=>!(d.box[3]<=2&&d.box[2]>8))){const e=entry('decoration',d.role,d.box);region(e.box,i=>id==='ref-04'?Math.min(...crop.subarray(i*3,i*3+3))>218:Math.max(...crop.subarray(i*3,i*3+3))<240)} - const residual=new Uint8Array(w*h);for(let y=4;y=7&&bh<=2)||(bh>=7&&bw<=2)){entry('decoration','自动识别细线',c.box);for(const i of c.pixels)owner[i]=elements.length-1}else tiny.push(...c.pixels)} - if(tiny.length){ - const joined=new Uint8Array(w*h);for(const i of tiny){const x=i%w,y=Math.floor(i/w);for(let dy=-1;dy<=1;dy++)for(let dx=-2;dx<=2;dx++)if(x+dx>=4&&x+dx=4&&y+dyset.has(i));if(!ink.length)continue;const candidates=elements.filter(e=>e.kind==='text'),match=textMergeTarget(c.box,candidates);if(match>=0){const existing=candidates[match],n=elements.indexOf(existing);existing.box=unionBoxes(existing.box,c.box);for(const i of ink)owner[i]=n}else{entry('text','未辨识细字(内容待核对)',c.box);for(const i of ink)owner[i]=elements.length-1}} - } - for(const[n,e]of elements.entries()){ - let x0=w,y0=h,x1=-1,y1=-1;for(let i=0;i=0)e.box=[x0,y0,x1-x0+1,y1-y0+1]; - } - const result=Buffer.alloc(w*h*3),background=Buffer.alloc(w*h*4);let assigned=0; - for(let i=0;i=w||yy+dy<0||yy+dy>=h)continue; - const k=(yy+dy)*w+xx+dx;if(elements[owner[k]]?.kind==='text')continue;samples.push([...crop.subarray(k*3,k*3+3)]); - } - for(let c=0;c<3;c++)underlay[j+c]=samples.length?Math.round(samples.reduce((sum,rgb)=>sum+rgb[c],0)/samples.length):245;underlay[j+3]=255;underlay.copy(background,i*4,j,j+4); - } - e.underlay=`asset:${id}/${e.id}-underlay.png`; - await sharp(underlay,{raw:{width:bw,height:bh,channels:4}}).png().toFile(path.join(dir,e.id+'-underlay.png')); - } - if(e.kind==='image'){ - const outside=new Uint8Array(binary.length),q:number[]=[];for(let i=0;i=bw*(bh-1)||i%bw===0||i%bw===bw-1)){q.push(i);outside[i]=1} - for(let c=0;c=0&&je.pixels>0),colors={text:'#2c64d8',image:'#e66d22',decoration:'#08875d'}; - const overlay=`${kept.map(e=>{const[x,y,bw,bh]=e.box;return `${e.id}`}).join('')}`; - await sharp(crop,{raw:{width:w,height:h,channels:3}}).resize(w*6,h*6,{kernel:'nearest'}).composite([{input:Buffer.from(overlay)}]).png().toFile(path.join(dir,'annotated.png')); - const manifest={id,name:spec.name,source:{url:'asset:source.png',sha256:createHash('sha256').update(source).digest('hex'),box:[p.x,p.y,w,h],row:p.row,column:p.column},width:w,height:h,reference:`asset:${id}/source.png`,annotated:`asset:${id}/annotated.png`,background:`asset:${id}/background.png`,elements:kept,ocr:ocr.find(r=>r.file===id+'.png')?.lines||[],verification:{pixelIdentical:true,pixels:w*h,unreadableText:kept.filter(e=>e.reading==='unreadable').length}}; - await writeFile(path.join(dir,'manifest.json'),JSON.stringify(manifest,null,2)+'\n');manifests.push(manifest);totalPixels+=w*h; - } - const overlay=`${manifests.map(m=>{const[x,y,w,h]=m.source.box;return `${m.id}`}).join('')}`; - await sharp(source).composite([{input:Buffer.from(overlay)}]).png().toFile(path.join(target,'numbered-source.png')); - await writeFile(path.join(root,'contentasset:manifest.json'),JSON.stringify(manifests,null,2)+'\n'); - await writeFile(path.join(target,'index.json'),JSON.stringify({version:2,sourceSha256:manifests[0].source.sha256,numbering:'row-major; exclude left title banner',layouts:manifests.map(m=>({id:m.id,name:m.name,source:m.source,manifest:`asset:${m.id}/manifest.json`}))},null,2)+'\n'); - console.log(`Detected ${panels.length} panels; ${manifests.reduce((n,m)=>n+m.elements.length,0)} typed components; ${totalPixels} pixels reconstructed exactly.`); -} -main().catch(e=>{console.error(e);process.exitCode=1}); diff --git a/scripts/test-integration-cleanup.py b/scripts/test-integration-cleanup.py deleted file mode 100644 index 4964431..0000000 --- a/scripts/test-integration-cleanup.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise cleanup ownership checks without a Docker daemon or build.""" -import json -import os -from pathlib import Path -import subprocess -import tempfile -import unittest - -SCRIPT = Path(__file__).with_name('cleanup-integration.sh') -FAKE_DOCKER = '''#!/usr/bin/env python3 -import json, os, sys -args = sys.argv[1:] -with open(os.environ['DOCKER_CALLS'], 'a') as output: - output.write(json.dumps(args) + '\\n') -if os.environ.get('EMPTY_INVENTORY'): - sys.exit(0) -if args[0] == 'ps': - if os.environ.get('FAIL_LIST'): - sys.exit(1) - print('test-container') -elif args[:2] == ['volume', 'ls']: - print('test-volume') -elif args[:2] == ['network', 'ls']: - print('test-network') -elif 'inspect' in args: - print(os.environ.get('RESOURCE_OWNER', 'prompt-ci-605')) -''' - - -class CleanupTests(unittest.TestCase): - def run_cleanup(self, run_id='605', **extra): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - docker = root / 'docker' - docker.write_text(FAKE_DOCKER) - docker.chmod(0o700) - calls = root / 'calls' - env = {**os.environ, 'PATH': directory + os.pathsep + os.environ['PATH'], - 'GITEA_RUN_ID': run_id, 'DOCKER_CALLS': str(calls), **extra} - result = subprocess.run(['bash', str(SCRIPT)], env=env, capture_output=True) - return result.returncode, [json.loads(line) for line in calls.read_text().splitlines()] if calls.exists() else [] - - def test_removes_only_run_owned_resources_in_dependency_order(self): - code, calls = self.run_cleanup() - self.assertEqual(code, 0) - self.assertEqual([args for args in calls if 'rm' in args], [ - ['rm', '-f', 'test-container'], ['volume', 'rm', 'test-volume'], - ['network', 'rm', 'test-network']]) - for args in calls: - if '--filter' in args: - self.assertEqual(args[-1], 'label=com.docker.compose.project=prompt-ci-605') - self.assertEqual(sum('inspect' in args for args in calls), 3) - - def test_already_clean_stack_is_a_successful_noop(self): - code, calls = self.run_cleanup(EMPTY_INVENTORY='1') - self.assertEqual(code, 0) - self.assertEqual(len(calls), 3) - self.assertFalse(any('rm' in args for args in calls)) - - def test_invalid_or_production_project_never_contacts_docker(self): - for value in ['', 'prompt', '0', '-1', '605*', '605/../prompt']: - with self.subTest(value=value): - code, calls = self.run_cleanup(value) - self.assertNotEqual(code, 0) - self.assertEqual(calls, []) - - def test_foreign_label_prevents_removal(self): - code, calls = self.run_cleanup(RESOURCE_OWNER='prompt') - self.assertNotEqual(code, 0) - self.assertFalse(any('rm' in args for args in calls)) - - def test_inventory_failure_does_not_continue_with_deletion(self): - code, calls = self.run_cleanup(FAIL_LIST='1') - self.assertNotEqual(code, 0) - self.assertEqual(len(calls), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/scripts/test-review-image-cleanup.py b/scripts/test-review-image-cleanup.py deleted file mode 100644 index c2cb74f..0000000 --- a/scripts/test-review-image-cleanup.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -"""Check review cleanup scope without Docker or build output.""" -import json -import os -from pathlib import Path -import subprocess -import tempfile -import unittest - -SCRIPT = Path(__file__).with_name('cleanup-review-images.sh') -SHA = 'a' * 40 -FAKE_DOCKER = '''#!/usr/bin/env python3 -import json, os, sys -args=sys.argv[1:] -with open(os.environ['DOCKER_CALLS'], 'a') as output: - output.write(json.dumps(args)+'\\n') -if args[0]=='ps': - if os.environ.get('FAIL_INVENTORY'): sys.exit(1) - if os.environ.get('REFERENCED'): print('container-id') -elif args[0]=='inspect': - print('sha256:review-image') -elif args[:2]==['image','inspect']: - if os.environ.get('MISSING_IMAGE'): sys.exit(1) - print('sha256:review-image') -''' - - -class ReviewCleanupTests(unittest.TestCase): - def run_cleanup(self, sha=SHA, **extra): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - docker = root / 'docker' - docker.write_text(FAKE_DOCKER) - docker.chmod(0o700) - calls = root / 'calls' - result = subprocess.run(['bash', str(SCRIPT)], capture_output=True, env={ - **os.environ, 'PATH': directory + os.pathsep + os.environ['PATH'], - 'DOCKER_CALLS': str(calls), 'REVIEW_SHA': sha, **extra}) - return result.returncode, [json.loads(line) for line in calls.read_text().splitlines()] if calls.exists() else [] - - def test_removes_only_exact_review_tags_without_force_and_keeps_parent_cache(self): - code, calls = self.run_cleanup() - self.assertEqual(code, 0) - self.assertEqual([args for args in calls if args[:2] == ['image', 'rm']], [ - ['image', 'rm', '--no-prune', name + ':' + SHA] for name in - ['creative-studio-review', 'creative-wiki-tests', 'creative-wiki-review']]) - self.assertFalse(any('-f' in args or 'prune' in args or 'volume' in args for args in calls)) - - def test_invalid_sha_never_contacts_docker(self): - for value in ['', 'main', 'a' * 39, 'a' * 41, 'A' * 40, '*' * 40]: - with self.subTest(value=value): - code, calls = self.run_cleanup(value) - self.assertNotEqual(code, 0) - self.assertEqual(calls, []) - - def test_container_referenced_images_are_kept(self): - code, calls = self.run_cleanup(REFERENCED='1') - self.assertEqual(code, 0) - self.assertFalse(any(args[:2] == ['image', 'rm'] for args in calls)) - - def test_missing_tags_are_a_successful_noop(self): - code, calls = self.run_cleanup(MISSING_IMAGE='1') - self.assertEqual(code, 0) - self.assertFalse(any(args[:2] == ['image', 'rm'] for args in calls)) - - def test_inventory_failure_prevents_removal(self): - code, calls = self.run_cleanup(FAIL_INVENTORY='1') - self.assertNotEqual(code, 0) - self.assertEqual(len(calls), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/scripts/verify-studio-library.mjs b/scripts/verify-studio-library.mjs deleted file mode 100644 index 1f4e0b9..0000000 --- a/scripts/verify-studio-library.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import assert from 'node:assert/strict'; -import {writeFile} from 'node:fs/promises'; -const base='http://8.211.154.134:18131'; -async function api(path,body,method=body?'POST':'GET',status=200){const r=await fetch(base+path,{method,...(body?{headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}:{})});assert.equal(r.status,status,path);return r.json()} -if(process.argv.includes('--finalize')){ - const {results}=JSON.parse(await (await import('node:fs/promises')).readFile('/tmp/studio-library/verification.json','utf8')); - const canonical='8eb8c76500580f7a4b56ba3c',legacy='9ebd8f78d54b4b9db741b4e8'; - let documentFailure;let category; - try{ - const entry=(await api('/api/notes/'+canonical)).entry; - for(const needle of ['standard-01','DELETE','revision','cropX','兼容工具原说明'])assert.ok(entry.editableMarkdown.includes(needle),needle); - for(const id of [canonical,legacy])assert.equal((await fetch(base+'/explore/'+id)).status,200); - const old=(await api('/api/notes/'+legacy)).entry;assert.ok(old.archived&&old.editableMarkdown.includes(canonical)); - category=await api('/api/explore-feed?category='+encodeURIComponent('文档入口')); - assert.equal(category.total,3);assert.equal(category.nextCursor,null); - assert.deepEqual(category.entries.map(e=>e.id).sort(),['3c98946754c33309d73d5284','4ce3283fd98bb03a8fb39a19',canonical].sort()); - }catch(error){documentFailure=error} - for(const item of results){const d=(await api('/api/studio/designs/'+item.id)).design;assert.ok(d.name.startsWith('API 验收'));assert.equal(d.postId,item.postId);if(!d.archived)await api('/api/studio/designs/'+d.id,{revision:d.revision},'DELETE');assert.equal((await api('/api/studio/designs/'+d.id)).design.archived,true)} - const final={canonical:base+'/explore/'+canonical,documentEntries:category?.total??null,legacyCompatible:!documentFailure,testRecordsArchived:results.length}; - await writeFile('/tmp/studio-library/final-verification.json',JSON.stringify(final,null,2));console.log(JSON.stringify(final));if(documentFailure)throw documentFailure; -}else{ -for(const path of ['/studio/template-layout','/studio/template-layout?id=invalid'])assert.equal((await fetch(base+path)).status,404); -const catalog=await api('/studio/api/layouts');assert.equal(catalog.layouts.length,28); -const note=(await api('/api/notes/8eb8c76500580f7a4b56ba3c')).entry; -const asset=note.coverAssetId||note.assets.find(a=>a.kind==='image').id; -const preview=await fetch(base+'/api/preview?'+new URLSearchParams({source:'/api/media/'+asset,width:'960'}));assert.equal(preview.status,200);assert.equal(preview.headers.get('content-type'),'image/webp');const bytes=(await preview.arrayBuffer()).byteLength;assert.ok(bytes<=220000); -const results=[]; -for(const layoutId of ['standard-01','ref-17']){ - const schema=await api('/studio/api/layouts/'+layoutId);assert.ok(schema.elements.some(s=>s.id==='T01')); - const p={texts:{T01:{value:'API 验收 · 原生页面',fontFamily:'serif',fontWeight:600}},images:{I01:{assetId:asset,fit:'cover',cropX:35,cropY:65,zoom:1.2}},added:[{id:'user-api-check',kind:'text',role:'扩展正文',box:[10,120,60,9],direction:'horizontal',text:'通过 HTTP 保存并恢复',reading:'custom'}],layers:{'user-api-check':{z:20,rotation:2}}}; - let d=(await api('/api/studio/designs',{schemaVersion:1,layoutId,name:'API 验收 '+layoutId,parameters:p},'POST',201)).design; - const id=d.id,postId=d.postId; - assert.equal(d.url,'/studio/template-layout?id='+id);assert.equal(d.renderUrl,d.url); - results.push({layoutId,id,url:d.url,revision:d.revision,postId,parameters:p}); - await writeFile('/tmp/studio-library/verification.json',JSON.stringify({results,preview:{asset,bytes,type:'image/webp'},pages:0},null,2)); - assert.deepEqual((await api('/api/studio/designs/'+id)).design.parameters,d.parameters); - const html=await fetch(base+d.url).then(r=>r.text());assert.ok(html.includes('API 验收'));assert.ok(html.includes('user-api-check'));assert.ok(html.includes('/api/preview?'));assert.ok(!html.includes('src="/api/media/'));assert.ok(!/page-tools|workbench-top|contenteditable|编辑页面/.test(html)); - d=(await api('/api/studio/designs/'+id,{name:'API 验收修订 '+layoutId,parameters:p,revision:d.revision},'PATCH')).design; - assert.equal(d.postId,postId);assert.equal(d.revision,2); - await api('/api/studio/designs/'+id,{name:'过期写入',parameters:{},revision:1},'PATCH',409); - d=(await api('/api/studio/designs/'+id,{revision:2},'DELETE')).design;assert.equal(d.archived,true); - assert.equal((await fetch(base+d.url)).status,404);assert.equal((await api('/api/notes/'+postId)).entry.archived,true); - assert.deepEqual((await api('/api/studio/designs/'+id)).design.parameters,d.parameters); - assert.ok((await api('/api/studio/designs?archived=true&layoutId='+layoutId)).designs.some(v=>v.id===id)); - d=(await api('/api/studio/designs/'+id,{revision:3,archived:false},'POST')).design;assert.equal(d.revision,4);assert.equal(d.archived,false);assert.equal((await fetch(base+d.url)).status,200); - Object.assign(results[results.length-1],{url:d.url,revision:d.revision}); - // Restore tested URL for visual inspection, then archive only these clearly named test records later. -} -for(const l of catalog.layouts){const r=await fetch(base+l.url);assert.equal(r.status,200,l.id);const html=await r.text();assert.ok(html.includes('data-layout-id="'+l.id+'"'));assert.ok(!/r)})); - -} diff --git a/scripts/verify-studio-production.sh b/scripts/verify-studio-production.sh deleted file mode 100644 index 7c803de..0000000 --- a/scripts/verify-studio-production.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# Run on ces-control after deployment; never starts a preview web server. -set -Eeuo pipefail -# The shared release job deploys Wiki before Studio; a missing route is an error, -# not a reason to occupy this runner while another queued job tries to deploy it. -if ! curl -fsS http://8.211.154.134:18131/ | grep -q '想法不该停在对话里'; then - echo 'The shared release has not deployed the Wiki landing and canonical routes' - exit 1 -fi -REPO_ROOT=$(git rev-parse --show-toplevel) -RESULT_DIR="$REPO_ROOT/artifacts/studio-acceptance" -MODE=${1:-verify} -# Legacy template visual checks are opt-in; their API compatibility checks remain below. -[[ "$MODE" == verify || "$MODE" == --diagnose || "$MODE" == --legacy-layouts ]] || exit 2 -WIKI_IMAGE=$(docker inspect --format '{{.Config.Image}}' prompt-waterfall-wiki-1) -[[ "$WIKI_IMAGE" =~ ^waterfall-wiki:[0-9a-f]{40}$ ]] || { echo 'Unexpected Wiki image'; exit 1; } -TEST_IMAGE="waterfall-wiki-tests:${WIKI_IMAGE#waterfall-wiki:}" -docker image inspect "$TEST_IMAGE" >/dev/null -mkdir -p "$RESULT_DIR" -chown 1000:1000 "$RESULT_DIR" -run_api(){ - local result_root=$1 script=$2; shift 2 - docker run --rm --volumes-from prompt-waterfall-wiki-1:ro \ - --mount "type=bind,src=$REPO_ROOT,dst=/delivery,readonly" \ - --mount "type=bind,src=$result_root,dst=/tmp/studio-library" \ - -e AUTH_CONFIG_FILE=/auth/config.json -e AUTH_CHECK_BASE_URL=http://8.211.154.134:18131 \ - --entrypoint node "$TEST_IMAGE" dist/auth-cli.cjs run "/delivery/scripts/$script" "$@" -} -# Result files are uploaded by the workflow and removed after successful upload. -printf '{"temporaryOutputManagedByCI":true}\n' > "$RESULT_DIR/cleanup.json" -# Always archive only the UUIDs created by this run, including when visual checks fail. -finish(){ - local status=$? - trap - EXIT - if [[ -f "$RESULT_DIR/verification.json" ]]; then - if ! run_api "$RESULT_DIR" verify-studio-library.mjs --finalize; then status=1; fi - fi - exit "$status" -} -trap finish EXIT -run_api "$RESULT_DIR" verify-studio-library.mjs -docker run --rm --volumes-from prompt-waterfall-wiki-1:ro \ - --mount "type=bind,src=$REPO_ROOT,dst=/delivery,readonly" \ - --mount "type=bind,src=$RESULT_DIR,dst=/tmp/studio-library" \ - -e AUTH_CONFIG_FILE=/auth/config.json -e NODE_PATH=/repo/apps/waterfall-wiki/node_modules \ - --entrypoint node "$TEST_IMAGE" /delivery/scripts/verify-studio-visual.cjs "$MODE" -docker run --rm \ - --mount "type=bind,src=$REPO_ROOT,dst=/delivery,readonly" \ - --mount "type=bind,src=$RESULT_DIR,dst=/tmp/studio-library" \ - -e NODE_PATH=/repo/apps/waterfall-wiki/node_modules -e CHROMIUM_PATH=/usr/bin/chromium \ - -e SCREENSHOT_DIR=/tmp/studio-library/visual \ - --entrypoint node "$TEST_IMAGE" /delivery/apps/studio/scripts/infinite-canvas-smoke.cjs diff --git a/scripts/verify-studio-visual.cjs b/scripts/verify-studio-visual.cjs deleted file mode 100644 index f5a631b..0000000 --- a/scripts/verify-studio-visual.cjs +++ /dev/null @@ -1,107 +0,0 @@ -const {readFile,mkdir,writeFile}=require('node:fs/promises'); -const {createHmac,randomBytes}=require('node:crypto'); -const {chromium}=require('playwright-core'); -const assert=require('node:assert/strict'); -(async()=>{ - const base='http://8.211.154.134:18131',origin=new URL(base).origin,dir='/tmp/studio-library/visual';await mkdir(dir,{recursive:true}); - const config=JSON.parse(await readFile(process.env.AUTH_CONFIG_FILE,'utf8')); - const now=Math.floor(Date.now()/1000),part=o=>Buffer.from(JSON.stringify(o)).toString('base64url'); - const content=part({alg:'HS256',typ:'JWT'})+'.'+part({iss:'prompt',aud:'prompt-web',sub:'shared',iat:now,exp:now+1200,jti:randomBytes(16).toString('hex')}); - const token=content+'.'+createHmac('sha256',Buffer.from(config.jwtKey,'hex')).update(content).digest('base64url'); - const {navigateBrandDocument,visitBrowserDocument}=await import('../apps/waterfall-wiki/tests/brand-navigation.mjs'); - const browser=await chromium.launch({executablePath:'/usr/bin/chromium',headless:true,args:['--no-sandbox','--disable-dev-shm-usage']}); - const context=await browser.newContext({viewport:{width:1100,height:1000},deviceScaleFactor:1}); - await context.route('**/*',async route=>{const u=new URL(route.request().url());if(u.origin===origin)return route.continue({headers:{...route.request().headers(),Authorization:'Bearer '+token}});if(['data:','blob:'].includes(u.protocol))return route.continue();return route.abort()}); - const page=await context.newPage(),errors=[],images=[]; - page.setDefaultTimeout(60000); - page.on('pageerror',error=>errors.push({url:page.url(),message:error.message,stack:error.stack})); - page.on('response',async response=>{if(response.request().resourceType()==='image'){const url=new URL(response.url());images.push({path:url.pathname+url.search,status:response.status(),type:response.headers()['content-type'],bytes:(await response.body().catch(()=>Buffer.alloc(0))).length})}}); - // Wait for the actual document and application, not an idle background network. - // Shared navigation recovery is limited to safe GETs; assertions are never retried. - const visitDocument=async(url,interactive=false,reload=false)=>{ - if(interactive)await visitBrowserDocument(page,url,reload); - else await navigateBrandDocument(timeout=>page.goto(url,{waitUntil:'domcontentloaded',timeout}),url); - await page.waitForFunction(()=>Array.from(document.images).every(img=>img.complete&&img.naturalWidth>0)); - console.log('Production document ready:',new URL(url).pathname); - }; - // The retired catalogue is retained for existing API consumers, not a release UI gate. - // Explicit legacy/diagnostic runs still exercise its full overflow assertions. - const checkLegacy=process.argv.includes('--legacy-layouts')||process.argv.includes('--diagnose'); - const metrics=[]; - for(let i=1;i<=27&&checkLegacy;i++){ - const id='ref-'+String(i).padStart(2,'0');await visitDocument(base+'/studio/layouts/'+id);await page.locator('article.native-layout').screenshot({path:dir+'/'+id+'.png',style:'.page-tools{visibility:hidden}'}); - const m=await page.locator('article.native-layout').evaluate(el=>{ - const overflow=[...el.querySelectorAll('.native-copy')].filter(e=>e.scrollHeight>e.clientHeight+2||e.scrollWidth>e.clientWidth+2); - return {id:el.dataset.layoutId,elements:el.querySelectorAll('[data-element]').length,width:el.clientWidth,height:el.clientHeight,overflow:overflow.map(e=>e.closest('[data-element]').dataset.element),overflowDetails:overflow.map(e=>{const style=getComputedStyle(e);return {id:e.closest('[data-element]').dataset.element,text:e.textContent,clientWidth:e.clientWidth,scrollWidth:e.scrollWidth,clientHeight:e.clientHeight,scrollHeight:e.scrollHeight,fontSize:style.fontSize,lineHeight:style.lineHeight,letterSpacing:style.letterSpacing,fontFamily:style.fontFamily,fontWeight:style.fontWeight,writingMode:style.writingMode}})}; - });metrics.push(m);await writeFile(dir+'/metrics.json',JSON.stringify({metrics,images,errors},null,2)); - } - if(checkLegacy&&!process.argv.includes('--diagnose'))assert.deepEqual(metrics.filter(m=>m.overflow.length),[],'Default copy must fit every native slot'); - await visitDocument(base+'/',true);await page.getByRole('heading',{name:/想法不该停在对话里/}).waitFor();await page.screenshot({path:dir+'/landing-desktop.png',fullPage:true}); - // Capture the complete editorial home at both narrow breakpoints on the deployed server. - await page.getByRole('heading',{name:/给好奇心一点空间/}).waitFor(); - for(const [width,name] of [[390,'landing-mobile'],[320,'landing-mobile-compact']]){ - await page.setViewportSize({width,height:844}); - assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1),'Landing fits narrow screens'); - const entry=await page.getByRole('navigation',{name:'主导航'}).getByRole('link',{name:/进入工作台/}).boundingBox(); - assert.ok(entry&&entry.x>=0&&entry.x+entry.width<=width,'Landing workspace entry remains inside the viewport'); - await page.screenshot({path:dir+'/'+name+'.png',fullPage:true}); - } - // Verify the public account flow in this fresh context, without submitting credentials. - await page.setViewportSize({width:1128,height:959}); - await visitDocument(base+'/account?next=/workspace',true); - await page.locator('button[data-theme-ready="true"]').waitFor(); - await page.getByLabel('邮箱地址',{exact:true}).waitFor(); - assert.equal(await page.locator('aside').count(),0,'Authentication must not include a workspace sidebar'); - assert.equal(await page.getByRole('link',{name:'创建文档',exact:true}).count(),0); - assert.equal(await page.locator('.endoleap-brand').count(),1,'Only one brand belongs on the account page'); - const accountSections=await page.locator('#account-content > div > section').evaluateAll(elements=>elements.map(el=>{const r=el.getBoundingClientRect();return {x:r.x,right:r.right,width:r.width}})); - assert.equal(accountSections.length,2); - assert.ok(accountSections[0].right<=accountSections[1].x,'Brand panel and credentials occupy separate columns'); - const accountFits=async()=>assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1&&document.documentElement.scrollHeight<=innerHeight+1),'Account document fits both viewport axes'); - const accountControlsFit=async()=>assert.ok(await page.locator('#account-content input, #account-content button[type="submit"]').evaluateAll(elements=>elements.every(el=>{const r=el.getBoundingClientRect();return r.left>=0&&r.right<=innerWidth&&r.top>=0&&r.bottom<=innerHeight})),'Account inputs and submit button remain visible, not merely clipped'); - await accountFits();await accountControlsFit(); - await page.screenshot({path:dir+'/account-desktop.png',fullPage:true}); - await page.getByRole('button',{name:'创建账户',exact:true}).click(); - await page.getByRole('heading',{name:'创建你的账户',exact:true}).waitFor(); - await page.waitForFunction(()=>document.querySelector('#account-content').getAnimations({subtree:true}).every(animation=>animation.playState!=='running')); - await page.screenshot({path:dir+'/account-register.png',fullPage:true}); - await page.getByRole('button',{name:'登录',exact:true}).click(); - await page.waitForFunction(()=>document.querySelector('#account-content').getAnimations({subtree:true}).every(animation=>animation.playState!=='running')); - await page.setViewportSize({width:390,height:844}); - await accountFits();await accountControlsFit(); - await page.screenshot({path:dir+'/account-mobile.png',fullPage:true}); - await page.setViewportSize({width:320,height:568});await accountFits();await accountControlsFit(); - await page.screenshot({path:dir+'/account-mobile-compact.png',fullPage:true}); - await page.setViewportSize({width:1100,height:1000}); - await visitDocument(base+'/graphic',true); - assert.equal(new URL(page.url()).pathname,'/graphic'); - await page.getByRole('button',{name:'添加文字',exact:true}).waitFor({state:'visible'}); - assert.equal(await page.locator('.library-item').count(),0,'Old catalog is retired from the entry page'); - await page.getByRole('button',{name:'添加文字',exact:true}).click(); - await page.getByRole('textbox',{name:'文字内容',exact:true}).fill('Studio 线上验收'); - await visitDocument(page.url(),true,true); - assert.ok(await page.evaluate(()=>JSON.parse(localStorage.getItem('studio.graphic.v1')).items.some(i=>i.text==='Studio 线上验收'))); - await page.screenshot({path:dir+'/graphic-desktop.png',fullPage:true}); - await visitDocument(base+'/infinite_canvas',true); - assert.equal(new URL(page.url()).pathname,'/infinite_canvas'); - await page.getByRole('button',{name:'添加笔记',exact:true}).waitFor({state:'visible'}); - await page.screenshot({path:dir+'/infinite-canvas-desktop.png',fullPage:true}); - const verify=JSON.parse(await readFile('/tmp/studio-library/verification.json','utf8')); - for(const d of verify.results){await visitDocument(base+d.url);assert.equal(await page.locator('img').count(),1);const current=await page.locator('img').evaluate(img=>({src:img.currentSrc,complete:img.complete,width:img.naturalWidth}));assert.ok(current.complete&¤t.width>0);assert.equal(new URL(current.src).pathname,'/api/preview');await page.locator('article.native-layout').screenshot({path:dir+'/'+d.layoutId+'-saved.png',style:'.page-tools{visibility:hidden}'}); - assert.equal(new URL(page.url()).pathname,'/studio/template-layout'); - assert.equal(await page.locator('button,input,textarea,select,[contenteditable],.page-tools,.workbench-top').count(),0); - await page.locator('.native-copy').first().dblclick();await page.locator('.native-copy').first().dispatchEvent('contextmenu'); - assert.equal(await page.locator('button,input,textarea,select,[contenteditable],.workbench-top').count(),0); - const plain=await browser.newContext({viewport:{width:1100,height:1000},javaScriptEnabled:false}); - await plain.route('**/*',async route=>{const u=new URL(route.request().url());if(u.origin===origin)return route.continue({headers:{...route.request().headers(),Authorization:'Bearer '+token}});return route.abort()}); - const direct=await plain.newPage();await navigateBrandDocument(timeout=>direct.goto(base+d.url,{waitUntil:'domcontentloaded',timeout}),base+d.url);assert.equal(await direct.locator('article[data-design-id="'+d.id+'"]').count(),1);assert.ok((await direct.locator('article').innerText()).includes('API 验收'));await plain.close(); - } - for(const slug of ['learning-skills-template','layout-selection']){await visitDocument(base+'/studio/ideas/'+slug);await page.screenshot({path:dir+'/tool-'+slug+'.png',fullPage:true})} - await page.setViewportSize({width:390,height:844});await visitDocument(base+'/graphic',true);assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.screenshot({path:dir+'/graphic-mobile.png',fullPage:true}); - await visitDocument(base+verify.results[0].url);await page.screenshot({path:dir+'/standard-mobile.png',fullPage:true}); - await visitDocument(base+'/categories?category='+encodeURIComponent('文档入口'),true);await page.getByRole('navigation',{name:'系统标签分类',exact:true}).waitFor({state:'visible'});assert.equal(await page.locator('.discovery-card').count(),3);await page.waitForFunction(()=>Array.from(document.querySelectorAll('.discovery-card img')).every(img=>img.complete&&img.naturalWidth>0),null,{timeout:60000});await page.screenshot({path:dir+'/document-entries.png',fullPage:true}); - await writeFile(dir+'/metrics.json',JSON.stringify({metrics,images,errors},null,2)); - assert.equal(images.filter(r=>new URL(r.path,base).pathname.startsWith('/api/media/')).length,0,'Display must never fetch originals');if(!process.argv.includes('--diagnose'))assert.equal(errors.length,0,JSON.stringify(errors));for(const image of images){if(/^\/(?:studio\/)?brand\/(?:logo-light|logo-dark|icon-dark)\.svg$/.test(new URL(image.path,base).pathname)){assert.equal(image.status,200);assert.match(image.type,/image\/svg\+xml/);continue;}assert.equal(new URL(image.path,base).pathname,'/api/preview');assert.equal(image.status,200);assert.equal(image.type,'image/webp');assert.ok(image.bytes>0&&image.bytes<=220000)} - await writeFile(dir+'/metrics.json',JSON.stringify({metrics,images,errors},null,2));await browser.close(); - console.log(JSON.stringify({diagnostic:process.argv.includes('--diagnose'),legacyLayoutsChecked:checkLegacy,legacyPages:metrics.length,errors:errors.length,displayImages:images.length,originalImageRequests:0,overflow:metrics.filter(m=>m.overflow.length)})); -})().catch(e=>{console.error(e.stack||e.message);process.exit(1)}); diff --git a/scripts/wiki-image-lifecycle.py b/scripts/wiki-image-lifecycle.py deleted file mode 100644 index f181e95..0000000 --- a/scripts/wiki-image-lifecycle.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python3 -"""Wiki-only image retention. Preview is the default; never prune the daemon.""" -import argparse -from contextlib import contextmanager -from datetime import datetime, timezone -import fcntl -import json -import os -from pathlib import Path -import re -import subprocess -import sys - -SHA = re.compile(r"^[0-9a-f]{40}$") -PROJECT = "io.entroia.project" -ROOT = Path("/opt/prompt") -TTL = 3 * 86400 - - -def run(args, **kwargs): - return subprocess.run(args, check=True, text=True, **kwargs) - - -def output(args): - return run(args, stdout=subprocess.PIPE).stdout - - -def timestamp(value): - return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() - - -@contextmanager -def lock(path): - # Fail rather than wait behind a build and consume the workflow timeout. - with open(path, "a") as handle: - try: - fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) - except BlockingIOError: - raise RuntimeError(f"Busy: {path}; no cleanup performed") - yield - - -def history(root): - path = root / "successful-releases" - values = path.read_text().splitlines() if path.exists() else [] - if any(not SHA.fullmatch(value) for value in values): - raise RuntimeError("Invalid successful-releases ledger; refusing cleanup") - return list(dict.fromkeys(values)) - - -def current_sha(root): - path = root / "current" / "release.env" - if not path.is_file(): - raise RuntimeError("Missing current/release.env; refusing cleanup") - values = [line.split("=", 1)[1] for line in path.read_text().splitlines() - if line.startswith("RELEASE_SHA=")] - if len(values) != 1 or not SHA.fullmatch(values[0]): - raise RuntimeError("Invalid current release SHA; refusing cleanup") - return values[0] - - -def record(root, sha): - if current_sha(root) != sha: - raise RuntimeError("Only the verified current release can be recorded") - values = [sha] + [item for item in history(root) if item != sha] - temp = root / ".successful-releases.next" - temp.write_text("\n".join(values) + "\n") - os.replace(temp, root / "successful-releases") - - -def decision(image, referenced, retained, ready, now, completed_test=None): - """Return keep/delete with a reviewable reason; all tags must be in scope.""" - tags = image.get("RepoTags") or [] - labels = image.get("Config", {}).get("Labels") or {} - if image["Id"] in referenced: - return "keep", "container reference (including stopped containers)" - parsed = [] - for tag in tags: - repo, _, sha = tag.rpartition(":") - if repo not in ("waterfall-wiki", "waterfall-wiki-tests") or not SHA.fullmatch(sha): - return "keep", "foreign or non-release tag" - parsed.append((repo, sha)) - if any(digest.partition("@")[0] not in ("waterfall-wiki", "waterfall-wiki-tests") - for digest in image.get("RepoDigests") or []): - return "keep", "foreign registry digest retained conservatively" - if any(sha in retained for _, sha in parsed): - return "keep", "retained release or its operational test image" - if any(repo == "waterfall-wiki" for repo, _ in parsed) and not ready: - return "keep", "bootstrap: fewer than three successful releases recorded" - if parsed and all(repo == "waterfall-wiki-tests" and sha == completed_test - for repo, sha in parsed): - return "delete", "this job's tests completed successfully" - if now - timestamp(image["Created"]) < TTL: - return "keep", "three-day build/debug window" - if parsed: - return "delete", "expired project release/test image" - if labels.get(PROJECT) == "waterfall-wiki": - return "delete", "expired project-labelled intermediate image" - return "keep", "unlabelled image: ownership unknown" - - -def container_expired(container, now): - labels = container.get("Config", {}).get("Labels") or {} - return (labels.get(PROJECT) == "waterfall-wiki" - and labels.get("io.entroia.wiki.build") == "true" - and container["State"]["Status"] == "exited" - and not container.get("Mounts") - and now - timestamp(container["Created"]) >= TTL) - - -def containers(): - ids = output(["docker", "ps", "-aq"]).split() - return json.loads(output(["docker", "inspect", *ids])) if ids else [] - - -def image_ids(): - # Include labelled build intermediates, but never enumerate foreign image metadata. - ids = set() - for name in ("waterfall-wiki", "waterfall-wiki-tests"): - ids.update(output(["docker", "image", "ls", "-aq", "--no-trunc", name]).split()) - ids.update(output(["docker", "image", "ls", "-aq", "--no-trunc", "--filter", - f"label={PROJECT}=waterfall-wiki"]).split()) - return sorted(ids) - - -def cleanup(root, apply=False, completed_test=None): - # CI holds the image lock throughout build, integration, deployment and cleanup. - # Taking the deployment lock also protects direct/manual deployment invocations. - with lock(root / ".deploy.lock"): - successes = history(root) - retained = set(successes[:3]) | {current_sha(root)} - ready = len(successes) >= 3 - now = datetime.now(timezone.utc).timestamp() - print("APPLY" if apply else "PREVIEW", "retained releases:", sorted(retained), flush=True) - for container in containers(): - if container_expired(container, now): - print("delete-container", container["Id"], "expired, project-labelled, no mounts", flush=True) - if apply: - fresh = json.loads(output(["docker", "inspect", container["Id"]]))[0] - if container_expired(fresh, now): - run(["docker", "container", "rm", container["Id"]]) - referenced = {item["Image"] for item in containers()} - ids = image_ids() - images = json.loads(output(["docker", "image", "inspect", *ids])) if ids else [] - for image in images: - image_id = image["Id"] - action, reason = decision(image, referenced, retained, ready, now, completed_test) - print(action, image_id, json.dumps(image.get("RepoTags") or []), reason, flush=True) - if apply and action == "delete": - # Recheck references and tags immediately before removal. No -f or volume operations. - fresh = json.loads(output(["docker", "image", "inspect", image_id]))[0] - refs = {item["Image"] for item in containers()} - if decision(fresh, refs, retained, ready, now, completed_test)[0] != "delete": - continue - for target in fresh.get("RepoTags") or [image_id]: - result = subprocess.run(["docker", "image", "rm", target], text=True) - if result.returncode: - print("skip", target, "Docker refused removal; retained", flush=True) - break - - -def ci(root, sha): - """One lock spans the whole job; failed build images get a three-day grace.""" - env = dict(os.environ, WATERFALL_WIKI_IMAGE=f"waterfall-wiki:{sha}", - WATERFALL_WIKI_TEST_IMAGE=f"waterfall-wiki-tests:{sha}") - code = 1 - try: - run(["docker", "compose", "--env-file", "apps/waterfall-wiki/.env.example", "config", "--quiet"]) - run(["docker", "build", "--target", "integration", "-t", env["WATERFALL_WIKI_TEST_IMAGE"], "."]) - run(["docker", "run", "--rm", "--entrypoint", "node", env["WATERFALL_WIKI_TEST_IMAGE"], - "--test", "tests/character-pack.test.mjs"]) - run(["docker", "build", "--target", "runtime", "-t", env["WATERFALL_WIKI_IMAGE"], "."]) - run(["bash", "scripts/integration.sh"], env=env) - run(["bash", "scripts/deploy.sh", sha], env=env) - code = 0 - except subprocess.CalledProcessError as error: - code = error.returncode - finally: - try: - cleanup(root, apply=True, completed_test=sha if code == 0 else None) - except Exception as error: - # Housekeeping must not mask the build/deployment result or roll back healthy services. - print(f"Cleanup failed; images retained for later retry: {error}", file=sys.stderr) - return code - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("command", nargs="?", choices=("preview", "apply", "record-success", "ci"), default="preview") - parser.add_argument("--sha") - args = parser.parse_args() - if args.command in ("ci", "record-success") and not SHA.fullmatch(args.sha or ""): - parser.error("--sha must be a full lowercase Git SHA") - if args.command == "record-success": - # Called at the end of deploy.sh while its deployment lock is still held. - record(ROOT, args.sha) - return 0 - with lock(ROOT / ".image-lifecycle.lock"): - if args.command == "ci": - return ci(ROOT, args.sha) - cleanup(ROOT, apply=args.command == "apply") - return 0 - - -if __name__ == "__main__": - try: - sys.exit(main()) - except Exception as error: - print(str(error), file=sys.stderr) - sys.exit(1) diff --git a/tests/managed-media.test.mjs b/tests/managed-media.test.mjs deleted file mode 100644 index 9268ae3..0000000 --- a/tests/managed-media.test.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import {test} from 'node:test'; -import assert from 'node:assert/strict'; -import {readFile,mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises'; -import {tmpdir} from 'node:os'; -import path from 'node:path'; -import {managedMedia,readRepositoryMedia} from '../scripts/managed-media.mjs'; -test('media registry uses immutable objects and consistent identities',()=>{ - const byKey=new Map(); - assert.equal(Object.keys(managedMedia).length,603); - for(const [name,asset] of Object.entries(managedMedia)){ - assert.ok(!name.includes('..')&&!name.startsWith('/')); - assert.match(asset.sha256,/^[a-f0-9]{64}$/); - assert.equal(asset.key,`repository-media/sha256/${asset.sha256}.png`); - assert.match(asset.id,/^[a-f0-9-]{36}$/); - assert.ok(asset.bytes>0&&asset.width>0&&asset.height>0); - if(byKey.has(asset.key))assert.deepEqual(byKey.get(asset.key),asset); - byKey.set(asset.key,asset); - } - assert.equal(byKey.size,572); -}); -test('external cache preserves all bytes and detects corruption',async()=>{ - if(!process.env.MEDIA_SOURCE_ROOT)return; - for(const name of Object.keys(managedMedia))assert.equal((await readRepositoryMedia(name)).length,managedMedia[name].bytes); - await assert.rejects(()=>readRepositoryMedia('../unregistered.png'),/Unregistered/); - const previous=process.env.MEDIA_SOURCE_ROOT;const temporary=await mkdtemp(path.join(tmpdir(),'media-corruption-')); - try{ - const name=Object.keys(managedMedia)[0];const file=path.join(temporary,name);await mkdir(path.dirname(file),{recursive:true});await writeFile(file,'corrupt'); - process.env.MEDIA_SOURCE_ROOT=temporary;await assert.rejects(()=>readRepositoryMedia(name),/checksum mismatch/); - }finally{process.env.MEDIA_SOURCE_ROOT=previous;await rm(temporary,{recursive:true,force:true});} -}); -test('every public raster path has an S3 mapping and runtime importer has no directory reads',async()=>{ - const publicImages=Object.keys(managedMedia).filter(name=>name.startsWith('apps/waterfall-wiki/public/')); - assert.equal(publicImages.length,5); - const importer=await readFile(new URL('../apps/waterfall-wiki/scripts/import-character-packs.mjs',import.meta.url),'utf8'); - assert.ok(importer.includes('/api/media/'));assert.ok(!importer.includes('readdir')); - for(const pack of JSON.parse(await readFile(new URL('../apps/waterfall-wiki/seed/characters/packs.json',import.meta.url)))){ - assert.equal(Object.keys(managedMedia).filter(name=>name.startsWith(`apps/waterfall-wiki/seed/characters/${pack.directory}/`)).length,pack.count); - } -}); diff --git a/tests/test_wiki_image_lifecycle.py b/tests/test_wiki_image_lifecycle.py deleted file mode 100644 index 8f760bb..0000000 --- a/tests/test_wiki_image_lifecycle.py +++ /dev/null @@ -1,168 +0,0 @@ -import importlib.util -import json -from pathlib import Path -import tempfile -import unittest -from unittest.mock import patch - -spec = importlib.util.spec_from_file_location( - "lifecycle", Path(__file__).resolve().parents[1] / "scripts/wiki-image-lifecycle.py") -m = importlib.util.module_from_spec(spec) -spec.loader.exec_module(m) -NOW = m.timestamp("2026-10-29T12:00:00Z") -A, B, C, D = [letter * 40 for letter in "abcd"] - - -def image(tags=None, labels=None, created="2026-10-20T12:00:00Z"): - return {"Id": "sha256:example", "RepoTags": tags or [], "RepoDigests": [], - "Created": created, "Config": {"Labels": labels or {}}} - - -class RetentionTests(unittest.TestCase): - def decide(self, item, referenced=(), retained=(A, B, C), ready=True, completed=None): - return m.decision(item, set(referenced), set(retained), ready, NOW, completed)[0] - - def test_current_and_two_previous_survive(self): - for sha in (A, B, C): - self.assertEqual(self.decide(image([f"waterfall-wiki:{sha}"])), "keep") - self.assertEqual(self.decide(image([f"waterfall-wiki:{D}"])), "delete") - - def test_retained_release_test_images_remain_available_to_studio_tools(self): - for sha in (A, B, C): - self.assertEqual(self.decide(image([f"waterfall-wiki-tests:{sha}"]), completed=sha), "keep") - - def test_stopped_container_reference_survives(self): - self.assertEqual(self.decide(image([f"waterfall-wiki:{D}"]), - referenced=("sha256:example",)), "keep") - - def test_all_tags_of_shared_retained_image_survive(self): - self.assertEqual(self.decide(image([f"waterfall-wiki:{A}", f"waterfall-wiki:{D}"])), "keep") - - def test_foreign_and_legacy_tags_survive(self): - for tag in (f"prompt:{D}", f"other-app:{D}", "waterfall-wiki:local"): - self.assertEqual(self.decide(image([tag, f"waterfall-wiki:{D}"])), "keep") - - def test_bootstrap_never_deletes_runtime(self): - self.assertEqual(self.decide(image([f"waterfall-wiki:{D}"]), ready=False), "keep") - - def test_failed_job_debug_window(self): - for repo in ("waterfall-wiki", "waterfall-wiki-tests"): - self.assertEqual(self.decide(image([f"{repo}:{D}"], created="2026-10-28T12:00:00Z")), "keep") - - def test_successful_job_test_image_can_be_removed_immediately(self): - item = image([f"waterfall-wiki-tests:{D}"], created="2026-10-29T11:00:00Z") - self.assertEqual(self.decide(item, completed=D), "delete") - self.assertEqual(self.decide(item), "keep") - - def test_expired_tests_dont_require_release_ledger(self): - self.assertEqual(self.decide(image([f"waterfall-wiki-tests:{D}"]), ready=False), "delete") - - def test_only_owned_dangling_images_are_candidates(self): - self.assertEqual(self.decide(image()), "keep") - self.assertEqual(self.decide(image(labels={m.PROJECT: "other"})), "keep") - self.assertEqual(self.decide(image(labels={m.PROJECT: "waterfall-wiki"})), "delete") - - def test_registry_digest_is_conservatively_retained(self): - item = image([f"waterfall-wiki:{D}"]) - item["RepoDigests"] = ["registry.example/wiki@sha256:abc"] - self.assertEqual(self.decide(item), "keep") - - def test_local_containerd_digest_does_not_disable_expiry(self): - item = image([f"waterfall-wiki-tests:{D}"]) - item["RepoDigests"] = ["waterfall-wiki-tests@sha256:example"] - self.assertEqual(self.decide(item), "delete") - - def test_failed_container_requires_ownership_age_and_no_mounts(self): - item = {"Config": {"Labels": {m.PROJECT: "waterfall-wiki", "io.entroia.wiki.build": "true"}}, - "State": {"Status": "exited"}, "Mounts": [], "Created": "2026-10-20T12:00:00Z"} - self.assertTrue(m.container_expired(item, NOW)) - for key, value in (("Mounts", [{"Type": "volume"}]), - ("State", {"Status": "running"}), - ("Created", "2026-10-29T11:00:00Z"), - ("Config", {"Labels": {}})): - self.assertFalse(m.container_expired(dict(item, **{key: value}), NOW)) - - def test_failed_ci_preserves_original_exit_and_debug_images(self): - error = m.subprocess.CalledProcessError(7, ["docker", "build"]) - with patch.object(m, "run", side_effect=error), patch.object(m, "cleanup") as cleanup: - self.assertEqual(m.ci(Path("/unused"), D), 7) - cleanup.assert_called_once_with(Path("/unused"), apply=True, completed_test=None) - - def test_cleanup_error_does_not_turn_failed_ci_into_success(self): - error = m.subprocess.CalledProcessError(7, ["docker", "build"]) - with patch.object(m, "run", side_effect=error), \ - patch.object(m, "cleanup", side_effect=RuntimeError("busy")): - self.assertEqual(m.ci(Path("/unused"), D), 7) - - def test_success_only_cleans_tests_after_deployment(self): - events = [] - with patch.object(m, "run", side_effect=lambda args, **kw: events.append(args)), \ - patch.object(m, "cleanup", side_effect=lambda *a, **kw: events.append(kw)): - self.assertEqual(m.ci(Path("/unused"), D), 0) - self.assertEqual(events[-2], ["bash", "scripts/deploy.sh", D]) - self.assertEqual(events[-1], {"apply": True, "completed_test": D}) - - def test_busy_lock_refuses_work(self): - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "lock" - with m.lock(path): - with self.assertRaisesRegex(RuntimeError, "Busy"): - with m.lock(path): - self.fail("Second holder entered lock") - - def test_only_current_verified_sha_can_be_recorded_and_order_is_unique(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - (root / "current").mkdir() - (root / "current/release.env").write_text(f"RELEASE_SHA={A}\n") - (root / "successful-releases").write_text(f"{B}\n{A}\n{C}\n") - m.record(root, A) - self.assertEqual(m.history(root), [A, B, C]) - with self.assertRaises(RuntimeError): - m.record(root, D) - (root / "successful-releases").write_text("bad-ledger\n") - with self.assertRaises(RuntimeError): - m.history(root) - - def test_preview_has_no_docker_mutations(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - item = image([f"waterfall-wiki-tests:{D}"]) - with patch.object(m, "history", return_value=[A, B, C]), \ - patch.object(m, "current_sha", return_value=A), \ - patch.object(m, "containers", return_value=[]), \ - patch.object(m, "image_ids", return_value=[item["Id"]]), \ - patch.object(m, "output", return_value=json.dumps([item])), \ - patch.object(m, "run") as run, patch.object(m.subprocess, "run") as mutate: - m.cleanup(root) - run.assert_not_called() - mutate.assert_not_called() - - def test_cleanup_rechecks_container_reference_before_removal(self): - with tempfile.TemporaryDirectory() as directory: - item = image([f"waterfall-wiki-tests:{D}"]) - with patch.object(m, "history", return_value=[A, B, C]), \ - patch.object(m, "current_sha", return_value=A), \ - patch.object(m, "containers", side_effect=[[], [], [{"Image": item["Id"]}]]), \ - patch.object(m, "image_ids", return_value=[item["Id"]]), \ - patch.object(m, "output", return_value=json.dumps([item])), \ - patch.object(m.subprocess, "run") as mutate: - m.cleanup(Path(directory), apply=True) - mutate.assert_not_called() - - def test_cleanup_never_forces_or_removes_volumes(self): - with tempfile.TemporaryDirectory() as directory: - item = image([f"waterfall-wiki-tests:{D}"]) - with patch.object(m, "history", return_value=[A, B, C]), \ - patch.object(m, "current_sha", return_value=A), \ - patch.object(m, "containers", return_value=[]), \ - patch.object(m, "image_ids", return_value=[item["Id"]]), \ - patch.object(m, "output", return_value=json.dumps([item])), \ - patch.object(m.subprocess, "run") as mutate: - mutate.return_value.returncode = 0 - m.cleanup(Path(directory), apply=True) - mutate.assert_called_once_with(["docker", "image", "rm", f"waterfall-wiki-tests:{D}"], text=True) - - -if __name__ == "__main__": - unittest.main() From 51347669a45db0d486e7f1d2bafad16e5b225d5c Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Wed, 4 Nov 2026 22:49:09 +0800 Subject: [PATCH 2/3] Adopt Turborepo in the existing pnpm workspace --- .gitignore | 2 + AGENTS.md | 3 +- README.md | 14 +- apps/waterfall-wiki/pnpm-lock.yaml | 3180 --------------------------- package.json | 10 +- packages/brand/pnpm-lock.yaml | 440 ---- pnpm-lock.yaml | 3275 +++++++++++++++++++++++++++- pnpm-workspace.yaml | 3 +- turbo.json | 17 + 9 files changed, 3315 insertions(+), 3629 deletions(-) delete mode 100644 apps/waterfall-wiki/pnpm-lock.yaml delete mode 100644 packages/brand/pnpm-lock.yaml create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore index fbd17f3..ef1a4d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +.pnpm-store/ +.turbo/ .next/ coverage/ .env* diff --git a/AGENTS.md b/AGENTS.md index 8bb360c..3265de6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,6 @@ This block is written and re-added by `next dev` — verify at `node_modules/nex -## 构建与维护 +## 项目维护 -- 构建和网页运行使用用户确认的远程环境。本地可执行差异审查和无需构建的轻量检查。 - Wiki 和 Studio 共用 `apps/waterfall-wiki` 的 Pages Router、登录保护和品牌包。保留已发布的路由、媒体标识和数据库迁移。 diff --git a/README.md b/README.md index 013ef80..7ab0530 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## 运行 -在已确认的远程环境准备 Node.js 24、pnpm 11.25.0、PostgreSQL、S3 兼容对象存储和 FFmpeg。参考 [环境变量示例](apps/waterfall-wiki/.env.example),将变量注入 Web、初始化和媒体 worker 进程。邮箱注册与找回密码需要可用的 SMTP 服务。 +准备 Node.js 24、pnpm 11.25.0、PostgreSQL、S3 兼容对象存储和 FFmpeg。参考 [环境变量示例](apps/waterfall-wiki/.env.example),将变量注入 Web、初始化和媒体 worker 进程。邮箱注册与找回密码需要可用的 SMTP 服务。 在仓库根目录运行 `pnpm install --frozen-lockfile` 和 `pnpm build`。然后在 `apps/waterfall-wiki` 目录准备数据库和存储,初始化管理员认证并启动 Web。 @@ -24,3 +24,15 @@ pnpm start ``` 媒体 worker 在同一目录作为独立进程运行 `node dist/worker.cjs`。绑定管理员验证器时,在远程交互终端执行 `node dist/auth-cli.cjs qr`。已有实例复用原数据库、对象存储和私有认证文件。 + +## Monorepo 与 Turborepo + +仓库在现有 pnpm workspace 上接入 Turborepo,无需运行面向新项目的 `npx create-turbo@latest`。应用仍位于 `apps/waterfall-wiki`,源码品牌包仍位于 `packages/brand`;子包脚本和应用产物路径保持原样。依赖统一锁定在根目录 `pnpm-lock.yaml`,安装从仓库根目录执行。 + +根目录命令由 Turbo 调度:`pnpm dev` 启动开发任务,`pnpm dev:waterfall-wiki` 仅启动 Wiki,`pnpm build` 构建应用及 worker/CLI,`pnpm typecheck` 生成 Next.js 类型并检查 TypeScript。`pnpm start` 继续直接启动现有应用。也可以在子包目录使用原有命令。 + +锁文件合并保留应用原有依赖解析。唯一重叠差异是 `@tailwindcss/postcss` 的间接 PostCSS 依赖:品牌包原为 8.5.32,现在沿用应用的 8.5.31;品牌包直接声明的 PostCSS 8.5.6 不变。 + +迁移阶段所有任务禁用缓存,根命令使用 `--env-mode=loose` 透传既有环境变量,保留品牌资源准备步骤的每次执行。后续启用缓存前,需明确环境变量输入、跨包资源和所有输出。当前不配置 CI/CD,也不自动发布。 + +仅检查任务计划、不执行编译或生成应用产物时,可运行 `pnpm build --dry=json` 和 `pnpm typecheck --dry=json`。完整构建与运行验证另行执行。 diff --git a/apps/waterfall-wiki/pnpm-lock.yaml b/apps/waterfall-wiki/pnpm-lock.yaml deleted file mode 100644 index 946ad48..0000000 --- a/apps/waterfall-wiki/pnpm-lock.yaml +++ /dev/null @@ -1,3180 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@aws-sdk/client-s3': - specifier: 3.1155.0 - version: 3.1155.0 - '@endoleap/brand': - specifier: workspace:* - version: link:../../packages/brand - formidable: - specifier: 3.5.4 - version: 3.5.4 - lucide-react: - specifier: 1.61.0 - version: 1.61.0(@types/react@19.3.0)(react@19.3.1) - mdast-util-from-markdown: - specifier: 2.1.0 - version: 2.1.0 - next: - specifier: 16.4.2 - version: 16.4.2(@types/node@24.19.2)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) - nodemailer: - specifier: 10.2.1 - version: 10.2.1 - pg: - specifier: 8.23.1 - version: 8.23.1 - playwright-core: - specifier: 1.56.1 - version: 1.56.1 - qrcode: - specifier: 1.5.4 - version: 1.5.4 - react: - specifier: 19.3.1 - version: 19.3.1 - react-dom: - specifier: 19.3.1 - version: 19.3.1(react@19.3.1) - react-markdown: - specifier: 10.1.0 - version: 10.1.0(@types/react@19.3.0)(react@19.3.1) - remark-gfm: - specifier: 4.0.1 - version: 4.0.1 - sharp: - specifier: 0.35.5 - version: 0.35.5(@types/node@24.19.2) - zod: - specifier: 4.6.7 - version: 4.6.7 - devDependencies: - '@tailwindcss/postcss': - specifier: 4.3.3 - version: 4.3.3 - '@types/formidable': - specifier: 3.5.1 - version: 3.5.1 - '@types/node': - specifier: 24.19.2 - version: 24.19.2 - '@types/nodemailer': - specifier: 7.0.4 - version: 7.0.4 - '@types/pg': - specifier: 8.23.1 - version: 8.23.1 - '@types/qrcode': - specifier: 1.5.6 - version: 1.5.6 - '@types/react': - specifier: 19.3.0 - version: 19.3.0 - '@types/react-dom': - specifier: 19.3.0 - version: 19.3.0(@types/react@19.3.0) - esbuild: - specifier: 0.28.2 - version: 0.28.2 - tailwindcss: - specifier: 4.3.3 - version: 4.3.3 - tsx: - specifier: 4.23.15 - version: 4.23.15 - typescript: - specifier: 5.9.3 - version: 5.9.3 - -packages: - - '@alloc/quick-lru@5.3.0': - resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==} - engines: {node: '>=10'} - - '@aws-sdk/checksums@3.1001.5': - resolution: {integrity: sha512-6erto9nK2OZO/woEgmFIaPh5gChgdNd7nL6jgAf8TAXz3/QHC8Pan2TFe9GYp3IxOIKCCGWPylSjtOHKfNzQlw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/client-s3@3.1155.0': - resolution: {integrity: sha512-l3uMeIlzWVgj/RTJTh5sKYKtQz1la3cvWLUPn1cwFH4UZpcqTH66SgJHCeB2FOE9bEAjOdbmCyPKt9aw867Fng==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/client-sesv2@3.1158.0': - resolution: {integrity: sha512-CEhnnEhIUeyfVSEB/Q8Gm2YOu/vF6ONrQ09e9t3imkvTTBVbpMNDhVyFBlBfHxNDD63kgJ1p39DPicx5Er+Sxw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/core@3.978.5': - resolution: {integrity: sha512-ruI0AWqcrfL20XDS6SwZIJFPOBZ6N0ADa9nLzWlBhxk1aoUjbUX3JkTR9vEmk7o35XpAHi68ahZ4T6RE2U9XyA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/core@3.978.6': - resolution: {integrity: sha512-ykF/MiYoM15dgZwF/WLgjJXVbAK2wU6vjPigeEnOHwyUJ0rdlraO5uekecKNVPkvOnz+oju/5zljViQs1nI1FQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-env@3.972.76': - resolution: {integrity: sha512-YxmL1Ar7bV9ZkJvtv+lj+MF6le76NnZnbmgySyZuxpZaCv0/1lZ3KxKy6RIj1h9ui2hWnVjZxbmTMeO0y/NrQA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-env@3.972.77': - resolution: {integrity: sha512-CN6dQCDo32a8n2gvm2HBeqGV5BNI9noG96nTj5bGO2HWtt0Rucu/A8pcnJIqkzILOHSuilnCk0Do8/7h6SOXhQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-http@3.972.78': - resolution: {integrity: sha512-3VHknDRngoebsaqyT1iR75b5cBT2zfEtODK6yYmYltljkK1HEAQLs2FEd1IpWqAeOYdMGaG9wikngWLTufY7RA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-http@3.972.79': - resolution: {integrity: sha512-Netsu0nnmvP2HiN1ZFQBeUu+JehQIuubqz+j2kHDgeSE4HDzniCpIZ7RzoWABq+LTcbCi9W03hywifUagoVIWQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-ini@3.973.21': - resolution: {integrity: sha512-aLx9hbCX6mGAyv4ZZd8UagJ4RASHVJw6BFDSIyuKmLkqNL6H47EjBPK6qaJ4B5TDV6mc+vMa0SJFYyo4mkvVQg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-ini@3.973.22': - resolution: {integrity: sha512-x7/5h58cqOoZR0RWUZC7v/cJOADQR42iSIQkvU+00HVHR4jh8ZpqEgRPGf2zei59iH5qP40lL3bkq49PuWl71g==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-login@3.972.83': - resolution: {integrity: sha512-9bJU6FjRacF4FK/QHf8Dyxhur7B1grRj8C1Yh4IkpoFW2exQflPdCv0VrbemLW9ZctLNle+AE7TETs9KNvuLYg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-login@3.972.84': - resolution: {integrity: sha512-G9Ty1uIV0laEvEp2KiMbEhsuBAjgaD3KYrwVjAM5VUxpcIuPBqQI5riA7/qzrnSgYiAKQbubbgkwcqCICKCIxg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-node@3.972.88': - resolution: {integrity: sha512-AG0KID5qhQPzrFkh5b0Cws1Amytvzo0L0Xj6hMgJXEwD6ZXAqninDhSNAWJiN5zU4YVVZnuidnqwPwYJ04IamA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-node@3.972.89': - resolution: {integrity: sha512-O8xj7/sanrnXfFHu/LUujVBiiwn5mknYenU7xt1w6+yvhR1QoDZWXLFh1gLITaFVPtb1jWgEirC+kFNw+//tyw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-process@3.972.76': - resolution: {integrity: sha512-pdhKFTXv1zPYhh92MTlp0jVbLWvUEV9v+rziuEkoskGGQ2iJskxHTO38nA8UAbT/N4IM3n8OZJ9rIY5IBO15/A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-process@3.972.77': - resolution: {integrity: sha512-sWA8Jm+ASzkTxoqLizHWRbt8Gj3yZCmZ0hYdvg0uIxuLzWseYcfrDFzaSnkRWlEWSJV38rAWFimdrN0J4NR7Pg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-sso@3.973.20': - resolution: {integrity: sha512-DlxrAB3MnjNLx/da7b89W+3DyJQYso/q7Ns15MsST2IV2nkAYQOYTUCaffNSjgrpMP99Dm2mV2jxzcbyF94ZfA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-sso@3.973.21': - resolution: {integrity: sha512-KV2LM8fbYUhDzGEBnPwb+ISNfNlhmnpLS+y6A6VsJpg1EI9aUJ0+dRTxh2GrGdneFH0T4TiuboHxz8i6+M38JA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.972.82': - resolution: {integrity: sha512-LUo46IqPTJ4xKBsrLuoaxiv/D2GTBCcFDMD1y3jKIqPR/p7Rw14RJFZ0iRWftqZtyLPQX9JMGnSi3mBu4y/pNA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.972.83': - resolution: {integrity: sha512-iekYilmo2Re51fC7gMkEfZ7jFDXwehSCmXjDESaYQsdgYhsLQFG7rTYr3GBvRIyj+Fkda2NxbZX7hAZDaFD0JQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-sdk-s3@3.972.81': - resolution: {integrity: sha512-jfanv1pV0sJFZw6/DPiNjvK7B478god0zOum5yIWo+/+1Gy+CSXWpGBWx+3nBJyDvjcu/2kUMr4ADUp9BHH34Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/nested-clients@3.997.50': - resolution: {integrity: sha512-nKb8qu4QsnpZTq/6JIXc72mzuzfh7BK4sUQkpQznPec2z4W7Ob2xYkcM4i5e1yGTBnbJVeFv/0sf3jcz85hclA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/nested-clients@3.997.51': - resolution: {integrity: sha512-DJK4iGIHWU8Jh8ODMvqFYcX9yGBMXV4tSZAvqlHF5EDd8hbmF3eFO9KIWOnA7wcQjPzEkyMMW1/FgZ9RU8oimQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.996.49': - resolution: {integrity: sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.1155.0': - resolution: {integrity: sha512-iIcQnYLhWuq9qOLQbIWFrxF3PF0svVeZvHnUS4DUnO4zUFmFHIyGyjqrntXG+e/5PUgfavV0pYfDIKHxTzN2LA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.1157.0': - resolution: {integrity: sha512-AeAzcwWRP2uANO2TjFpKjRdvOCzgfIu6YQl9Ceibd6E0gnq5dfOzujFnq2aeqgbLg4wRtXbUZE4gekCCPrz/OQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/types@3.975.0': - resolution: {integrity: sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/xml-builder@3.972.41': - resolution: {integrity: sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==} - engines: {node: '>=20.0.0'} - - '@aws/lambda-invoke-store@0.3.0': - resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} - engines: {node: '>=18.0.0'} - - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - - '@esbuild/aix-ppc64@0.28.2': - resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.2': - resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.2': - resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.2': - resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.2': - resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.2': - resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.2': - resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.2': - resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.2': - resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.2': - resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.2': - resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.2': - resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.2': - resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.2': - resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.2': - resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.2': - resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.2': - resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.2': - resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.2': - resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.2': - resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.2': - resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.2': - resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.2': - resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.2': - resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.2': - resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.2': - resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.35.5': - resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.35.5': - resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [darwin] - - '@img/sharp-freebsd-wasm32@0.35.5': - resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} - engines: {node: '>=20.9.0'} - os: [freebsd] - - '@img/sharp-libvips-darwin-arm64@1.3.4': - resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.3.4': - resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.3.4': - resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.3.4': - resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.3.4': - resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.3.4': - resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.3.4': - resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.3.4': - resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.3.4': - resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.3.4': - resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.35.5': - resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.35.5': - resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} - engines: {node: '>=20.9.0'} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.35.5': - resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} - engines: {node: '>=20.9.0'} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.35.5': - resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} - engines: {node: '>=20.9.0'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.35.5': - resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} - engines: {node: '>=20.9.0'} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.35.5': - resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.35.5': - resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.35.5': - resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.35.5': - resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} - engines: {node: '>=20.9.0'} - - '@img/sharp-webcontainers-wasm32@0.35.5': - resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} - engines: {node: '>=20.9.0'} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.35.5': - resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} - engines: {node: '>=20.9.0'} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.35.5': - resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} - engines: {node: ^20.9.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.35.5': - resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} - engines: {node: '>=20.9.0'} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.6.0': - resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@next/env@16.4.2': - resolution: {integrity: sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==} - - '@next/swc-darwin-arm64@16.4.2': - resolution: {integrity: sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@16.4.2': - resolution: {integrity: sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@16.4.2': - resolution: {integrity: sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@16.4.2': - resolution: {integrity: sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@16.4.2': - resolution: {integrity: sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@16.4.2': - resolution: {integrity: sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@16.4.2': - resolution: {integrity: sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.4.2': - resolution: {integrity: sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@paralleldrive/cuid2@2.3.1': - resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} - - '@smithy/core@3.36.2': - resolution: {integrity: sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==} - engines: {node: '>=18.0.0'} - - '@smithy/core@3.37.0': - resolution: {integrity: sha512-97imTcCyiPd8k0e9+ts/Sp0W65Dn9Ti357J7M09Ov4tI8Q1JDJVFSoAJtjEVKqfHEr3Pdo2jfolqKCaaiszH0g==} - engines: {node: '>=18.0.0'} - - '@smithy/credential-provider-imds@4.5.2': - resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} - engines: {node: '>=18.0.0'} - - '@smithy/fetch-http-handler@5.8.0': - resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} - engines: {node: '>=18.0.0'} - - '@smithy/node-http-handler@4.12.1': - resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} - engines: {node: '>=18.0.0'} - - '@smithy/node-http-handler@4.13.0': - resolution: {integrity: sha512-/GGcPoNJ2nzGytdAanpbx7mpp9Coa8LP+/Rpmbktr8YeLuIXs9B0SEz+X3AeonHdyzOIFJdKbN7jOZ+Tj+pMGw==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.7.4': - resolution: {integrity: sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==} - engines: {node: '>=18.0.0'} - - '@smithy/types@4.19.0': - resolution: {integrity: sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==} - engines: {node: '>=18.0.0'} - - '@swc/helpers@0.5.23': - resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} - - '@tailwindcss/node@4.3.3': - resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - - '@tailwindcss/oxide-android-arm64@4.3.3': - resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.3.3': - resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.3.3': - resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.3.3': - resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - - '@types/debug@4.1.13': - resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/formidable@3.5.1': - resolution: {integrity: sha512-aFQijSGbD8JCeEST2LEbwR7faHynbt43lojLIcTM/QhB2U06h41ZVRFVEql+Z1xdL+aKGIzm69V/P/uSW9N6XA==} - - '@types/hast@3.0.5': - resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@24.19.2': - resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} - - '@types/nodemailer@7.0.4': - resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} - - '@types/pg@8.23.1': - resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} - - '@types/qrcode@1.5.6': - resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} - - '@types/react-dom@19.3.0': - resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} - peerDependencies: - '@types/react': ^19.3.0 - - '@types/react@19.3.0': - resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@ungap/structured-clone@1.4.0': - resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - baseline-browser-mapping@2.11.32: - resolution: {integrity: sha512-UvuxFU9P3hM29LrQdwu0cD6w6/+SFr+UlB9s/knI7+O2rK9xFxKUwSDWxGdbbqQ9/V7BJ5s0NI5nQAE4uJp3gQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - bowser@2.14.1: - resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - caniuse-lite@1.0.30001819: - resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - dezalgo@1.0.4: - resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} - - dijkstrajs@1.0.3: - resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - enhanced-resolve@5.26.0: - resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} - engines: {node: '>=10.13.0'} - - esbuild@0.28.2: - resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} - engines: {node: '>=18'} - hasBin: true - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - formidable@3.5.4: - resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} - engines: {node: '>=14.0.0'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hast-util-to-jsx-runtime@2.3.6: - resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - html-url-attributes@3.0.1: - resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - - inline-style-parser@0.2.7: - resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - lucide-react@1.61.0: - resolution: {integrity: sha512-v2SQPPPNjnr+BlhL9Spuzmo5yR+hLkftfuE4E3jaxdNPgSH0hJKPcLJBP7yAta87jQNPsSERS59PJjCJ/CorBA==} - peerDependencies: - '@types/react': '*' - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - markdown-table@3.0.4: - resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - - mdast-util-find-and-replace@3.0.4: - resolution: {integrity: sha512-z4B5c6jrnJ1ltOQemhWYPBSAo6y42Z8Gt2yEgfgNTMcLSU59B8bfj8Z8AnAlJrBGLnqkdAUZ7jdDEqQw3KuipQ==} - - mdast-util-from-markdown@2.1.0: - resolution: {integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==} - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - - mdast-util-gfm-footnote@2.1.0: - resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - - mdast-util-gfm-strikethrough@2.0.1: - resolution: {integrity: sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==} - - mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - - mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - - mdast-util-gfm@3.1.0: - resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} - - mdast-util-mdx-expression@2.0.1: - resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - - mdast-util-mdx-jsx@3.2.0: - resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} - - mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - mdast-util-to-markdown@2.2.0: - resolution: {integrity: sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - micromark-core-commonmark@2.0.4: - resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - - micromark-extension-gfm-footnote@2.1.0: - resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - - micromark-extension-gfm-table@2.1.2: - resolution: {integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==} - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - - micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-space@2.1.0: - resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-edit-map@1.0.0: - resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.3: - resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} - - micromark@4.0.3: - resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.20: - resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - next@16.4.2: - resolution: {integrity: sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==} - engines: {node: '>=20.9.0'} - hasBin: true - 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 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - - nodemailer@10.2.1: - resolution: {integrity: sha512-ZrDNsPFoDMwefNs7sJwrYiEJ/CG4ClJWDZv05iNJsEBWa8GRDry1QJHTJxsuTAY9eDDKjX4k+q3SxslXJOao7w==} - engines: {node: '>=20.0.0'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - pg-cloudflare@1.4.1: - resolution: {integrity: sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==} - - pg-connection-string@2.14.1: - resolution: {integrity: sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.14.0: - resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.16.1: - resolution: {integrity: sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.23.1: - resolution: {integrity: sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==} - engines: {node: '>= 16.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - playwright-core@1.56.1: - resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} - engines: {node: '>=18'} - hasBin: true - - pngjs@5.0.0: - resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} - engines: {node: '>=10.13.0'} - - postcss@8.5.23: - resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.31: - resolution: {integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==} - engines: {node: ^10 || ^12 || >=14} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.1: - resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - property-information@7.2.0: - resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} - - qrcode@1.5.4: - resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} - engines: {node: '>=10.13.0'} - hasBin: true - - react-dom@19.3.1: - resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} - peerDependencies: - react: ^19.3.1 - - react-markdown@10.1.0: - resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' - - react@19.3.1: - resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} - engines: {node: '>=0.10.0'} - - remark-gfm@4.0.1: - resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} - - remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - - remark-rehype@11.1.2: - resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - - remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - - scheduler@0.28.0: - resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - - sharp@0.35.5: - resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} - engines: {node: '>=20.9.0'} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - source-map-js@1.2.2: - resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} - engines: {node: '>=0.10.0'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - style-to-js@1.1.21: - resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - - style-to-object@1.0.14: - resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - - tailwindcss@4.3.3: - resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.23.15: - resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.1.0: - resolution: {integrity: sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w==} - - unist-util-visit@5.2.0: - resolution: {integrity: sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - - yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - - yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - - zod@4.6.7: - resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@alloc/quick-lru@5.3.0': {} - - '@aws-sdk/checksums@3.1001.5': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/client-s3@3.1155.0': - dependencies: - '@aws-sdk/checksums': 3.1001.5 - '@aws-sdk/core': 3.978.5 - '@aws-sdk/credential-provider-node': 3.972.88 - '@aws-sdk/middleware-sdk-s3': 3.972.81 - '@aws-sdk/signature-v4-multi-region': 3.996.49 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.12.1 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/client-sesv2@3.1158.0': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/credential-provider-node': 3.972.89 - '@aws-sdk/signature-v4-multi-region': 3.996.49 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.13.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/core@3.978.5': - dependencies: - '@aws-sdk/types': 3.975.0 - '@aws-sdk/xml-builder': 3.972.41 - '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.36.2 - '@smithy/signature-v4': 5.7.4 - '@smithy/types': 4.19.0 - bowser: 2.14.1 - tslib: 2.8.1 - - '@aws-sdk/core@3.978.6': - dependencies: - '@aws-sdk/types': 3.975.0 - '@aws-sdk/xml-builder': 3.972.41 - '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.37.0 - '@smithy/signature-v4': 5.7.4 - '@smithy/types': 4.19.0 - bowser: 2.14.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.972.76': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.972.77': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.972.78': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.12.1 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.972.79': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.13.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.973.21': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/credential-provider-env': 3.972.76 - '@aws-sdk/credential-provider-http': 3.972.78 - '@aws-sdk/credential-provider-login': 3.972.83 - '@aws-sdk/credential-provider-process': 3.972.76 - '@aws-sdk/credential-provider-sso': 3.973.20 - '@aws-sdk/credential-provider-web-identity': 3.972.82 - '@aws-sdk/nested-clients': 3.997.50 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/credential-provider-imds': 4.5.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.973.22': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/credential-provider-env': 3.972.77 - '@aws-sdk/credential-provider-http': 3.972.79 - '@aws-sdk/credential-provider-login': 3.972.84 - '@aws-sdk/credential-provider-process': 3.972.77 - '@aws-sdk/credential-provider-sso': 3.973.21 - '@aws-sdk/credential-provider-web-identity': 3.972.83 - '@aws-sdk/nested-clients': 3.997.51 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/credential-provider-imds': 4.5.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-login@3.972.83': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/nested-clients': 3.997.50 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-login@3.972.84': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/nested-clients': 3.997.51 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-node@3.972.88': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.76 - '@aws-sdk/credential-provider-http': 3.972.78 - '@aws-sdk/credential-provider-ini': 3.973.21 - '@aws-sdk/credential-provider-process': 3.972.76 - '@aws-sdk/credential-provider-sso': 3.973.20 - '@aws-sdk/credential-provider-web-identity': 3.972.82 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/credential-provider-imds': 4.5.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-node@3.972.89': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.77 - '@aws-sdk/credential-provider-http': 3.972.79 - '@aws-sdk/credential-provider-ini': 3.973.22 - '@aws-sdk/credential-provider-process': 3.972.77 - '@aws-sdk/credential-provider-sso': 3.973.21 - '@aws-sdk/credential-provider-web-identity': 3.972.83 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/credential-provider-imds': 4.5.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-process@3.972.76': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-process@3.972.77': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-sso@3.973.20': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/nested-clients': 3.997.50 - '@aws-sdk/token-providers': 3.1155.0 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-sso@3.973.21': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/nested-clients': 3.997.51 - '@aws-sdk/token-providers': 3.1157.0 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-web-identity@3.972.82': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/nested-clients': 3.997.50 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-web-identity@3.972.83': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/nested-clients': 3.997.51 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-s3@3.972.81': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/signature-v4-multi-region': 3.996.49 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/nested-clients@3.997.50': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/signature-v4-multi-region': 3.996.49 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.12.1 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/nested-clients@3.997.51': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/signature-v4-multi-region': 3.996.49 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/fetch-http-handler': 5.8.0 - '@smithy/node-http-handler': 4.13.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.996.49': - dependencies: - '@aws-sdk/types': 3.975.0 - '@smithy/signature-v4': 5.7.4 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.1155.0': - dependencies: - '@aws-sdk/core': 3.978.5 - '@aws-sdk/nested-clients': 3.997.50 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.1157.0': - dependencies: - '@aws-sdk/core': 3.978.6 - '@aws-sdk/nested-clients': 3.997.51 - '@aws-sdk/types': 3.975.0 - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/types@3.975.0': - dependencies: - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws-sdk/xml-builder@3.972.41': - dependencies: - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@aws/lambda-invoke-store@0.3.0': {} - - '@emnapi/runtime@1.11.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.28.2': - optional: true - - '@esbuild/android-arm64@0.28.2': - optional: true - - '@esbuild/android-arm@0.28.2': - optional: true - - '@esbuild/android-x64@0.28.2': - optional: true - - '@esbuild/darwin-arm64@0.28.2': - optional: true - - '@esbuild/darwin-x64@0.28.2': - optional: true - - '@esbuild/freebsd-arm64@0.28.2': - optional: true - - '@esbuild/freebsd-x64@0.28.2': - optional: true - - '@esbuild/linux-arm64@0.28.2': - optional: true - - '@esbuild/linux-arm@0.28.2': - optional: true - - '@esbuild/linux-ia32@0.28.2': - optional: true - - '@esbuild/linux-loong64@0.28.2': - optional: true - - '@esbuild/linux-mips64el@0.28.2': - optional: true - - '@esbuild/linux-ppc64@0.28.2': - optional: true - - '@esbuild/linux-riscv64@0.28.2': - optional: true - - '@esbuild/linux-s390x@0.28.2': - optional: true - - '@esbuild/linux-x64@0.28.2': - optional: true - - '@esbuild/netbsd-arm64@0.28.2': - optional: true - - '@esbuild/netbsd-x64@0.28.2': - optional: true - - '@esbuild/openbsd-arm64@0.28.2': - optional: true - - '@esbuild/openbsd-x64@0.28.2': - optional: true - - '@esbuild/openharmony-arm64@0.28.2': - optional: true - - '@esbuild/sunos-x64@0.28.2': - optional: true - - '@esbuild/win32-arm64@0.28.2': - optional: true - - '@esbuild/win32-ia32@0.28.2': - optional: true - - '@esbuild/win32-x64@0.28.2': - optional: true - - '@img/colour@1.1.0': {} - - '@img/sharp-darwin-arm64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.4 - optional: true - - '@img/sharp-darwin-x64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.4 - optional: true - - '@img/sharp-freebsd-wasm32@0.35.5': - dependencies: - '@img/sharp-wasm32': 0.35.5 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.3.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.3.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.3.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.3.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.3.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.3.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.3.4': - optional: true - - '@img/sharp-linux-arm64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.4 - optional: true - - '@img/sharp-linux-arm@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.4 - optional: true - - '@img/sharp-linux-ppc64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.4 - optional: true - - '@img/sharp-linux-riscv64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.4 - optional: true - - '@img/sharp-linux-s390x@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.4 - optional: true - - '@img/sharp-linux-x64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.35.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.4 - optional: true - - '@img/sharp-wasm32@0.35.5': - dependencies: - '@emnapi/runtime': 1.11.3 - optional: true - - '@img/sharp-webcontainers-wasm32@0.35.5': - dependencies: - '@img/sharp-wasm32': 0.35.5 - optional: true - - '@img/sharp-win32-arm64@0.35.5': - optional: true - - '@img/sharp-win32-ia32@0.35.5': - optional: true - - '@img/sharp-win32-x64@0.35.5': - optional: true - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.6.0': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.6.0 - - '@next/env@16.4.2': {} - - '@next/swc-darwin-arm64@16.4.2': - optional: true - - '@next/swc-darwin-x64@16.4.2': - optional: true - - '@next/swc-linux-arm64-gnu@16.4.2': - optional: true - - '@next/swc-linux-arm64-musl@16.4.2': - optional: true - - '@next/swc-linux-x64-gnu@16.4.2': - optional: true - - '@next/swc-linux-x64-musl@16.4.2': - optional: true - - '@next/swc-win32-arm64-msvc@16.4.2': - optional: true - - '@next/swc-win32-x64-msvc@16.4.2': - optional: true - - '@noble/hashes@1.8.0': {} - - '@paralleldrive/cuid2@2.3.1': - dependencies: - '@noble/hashes': 1.8.0 - - '@smithy/core@3.36.2': - dependencies: - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/core@3.37.0': - dependencies: - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/credential-provider-imds@4.5.2': - dependencies: - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@5.8.0': - dependencies: - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/node-http-handler@4.12.1': - dependencies: - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/node-http-handler@4.13.0': - dependencies: - '@smithy/core': 3.37.0 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/signature-v4@5.7.4': - dependencies: - '@smithy/core': 3.36.2 - '@smithy/types': 4.19.0 - tslib: 2.8.1 - - '@smithy/types@4.19.0': - dependencies: - tslib: 2.8.1 - - '@swc/helpers@0.5.23': - dependencies: - tslib: 2.8.1 - - '@tailwindcss/node@4.3.3': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.26.0 - jiti: 2.7.0 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.2 - tailwindcss: 4.3.3 - - '@tailwindcss/oxide-android-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide@4.3.3': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-x64': 4.3.3 - '@tailwindcss/oxide-freebsd-x64': 4.3.3 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-x64-musl': 4.3.3 - '@tailwindcss/oxide-wasm32-wasi': 4.3.3 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - - '@tailwindcss/postcss@4.3.3': - dependencies: - '@alloc/quick-lru': 5.3.0 - '@tailwindcss/node': 4.3.3 - '@tailwindcss/oxide': 4.3.3 - postcss: 8.5.31 - tailwindcss: 4.3.3 - - '@types/debug@4.1.13': - dependencies: - '@types/ms': 2.1.0 - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.9 - - '@types/estree@1.0.9': {} - - '@types/formidable@3.5.1': - dependencies: - '@types/node': 24.19.2 - - '@types/hast@3.0.5': - dependencies: - '@types/unist': 3.0.3 - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/ms@2.1.0': {} - - '@types/node@24.19.2': - dependencies: - undici-types: 7.24.6 - - '@types/nodemailer@7.0.4': - dependencies: - '@aws-sdk/client-sesv2': 3.1158.0 - '@types/node': 24.19.2 - - '@types/pg@8.23.1': - dependencies: - '@types/node': 24.19.2 - pg-protocol: 1.16.1 - pg-types: 2.2.0 - - '@types/qrcode@1.5.6': - dependencies: - '@types/node': 24.19.2 - - '@types/react-dom@19.3.0(@types/react@19.3.0)': - dependencies: - '@types/react': 19.3.0 - - '@types/react@19.3.0': - dependencies: - csstype: 3.2.3 - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@ungap/structured-clone@1.4.0': {} - - ansi-regex@5.0.1: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - asap@2.0.6: {} - - bail@2.0.2: {} - - baseline-browser-mapping@2.11.32: {} - - bowser@2.14.1: {} - - camelcase@5.3.1: {} - - caniuse-lite@1.0.30001819: {} - - ccount@2.0.1: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - client-only@0.0.1: {} - - cliui@6.0.0: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - comma-separated-tokens@2.0.3: {} - - csstype@3.2.3: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decamelize@1.2.0: {} - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - dequal@2.0.3: {} - - detect-libc@2.1.2: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - dezalgo@1.0.4: - dependencies: - asap: 2.0.6 - wrappy: 1.0.2 - - dijkstrajs@1.0.3: {} - - emoji-regex@8.0.0: {} - - enhanced-resolve@5.26.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - esbuild@0.28.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.2 - '@esbuild/android-arm': 0.28.2 - '@esbuild/android-arm64': 0.28.2 - '@esbuild/android-x64': 0.28.2 - '@esbuild/darwin-arm64': 0.28.2 - '@esbuild/darwin-x64': 0.28.2 - '@esbuild/freebsd-arm64': 0.28.2 - '@esbuild/freebsd-x64': 0.28.2 - '@esbuild/linux-arm': 0.28.2 - '@esbuild/linux-arm64': 0.28.2 - '@esbuild/linux-ia32': 0.28.2 - '@esbuild/linux-loong64': 0.28.2 - '@esbuild/linux-mips64el': 0.28.2 - '@esbuild/linux-ppc64': 0.28.2 - '@esbuild/linux-riscv64': 0.28.2 - '@esbuild/linux-s390x': 0.28.2 - '@esbuild/linux-x64': 0.28.2 - '@esbuild/netbsd-arm64': 0.28.2 - '@esbuild/netbsd-x64': 0.28.2 - '@esbuild/openbsd-arm64': 0.28.2 - '@esbuild/openbsd-x64': 0.28.2 - '@esbuild/openharmony-arm64': 0.28.2 - '@esbuild/sunos-x64': 0.28.2 - '@esbuild/win32-arm64': 0.28.2 - '@esbuild/win32-ia32': 0.28.2 - '@esbuild/win32-x64': 0.28.2 - - escape-string-regexp@5.0.0: {} - - estree-util-is-identifier-name@3.0.0: {} - - extend@3.0.2: {} - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - formidable@3.5.4: - dependencies: - '@paralleldrive/cuid2': 2.3.1 - dezalgo: 1.0.4 - once: 1.4.0 - - fsevents@2.3.3: - optional: true - - get-caller-file@2.0.5: {} - - graceful-fs@4.2.11: {} - - hast-util-to-jsx-runtime@2.3.6: - dependencies: - '@types/estree': 1.0.9 - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.21 - unist-util-position: 5.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - html-url-attributes@3.0.1: {} - - inline-style-parser@0.2.7: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-decimal@2.0.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-hexadecimal@2.0.1: {} - - is-plain-obj@4.1.0: {} - - jiti@2.7.0: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - longest-streak@3.1.0: {} - - lucide-react@1.61.0(@types/react@19.3.0)(react@19.3.1): - dependencies: - react: 19.3.1 - optionalDependencies: - '@types/react': 19.3.0 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - - markdown-table@3.0.4: {} - - mdast-util-find-and-replace@3.0.4: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.1.0 - - mdast-util-from-markdown@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.3 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.4 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.1.0 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.1 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.2.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-expression@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@3.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.1.0 - mdast-util-to-markdown: 2.2.0 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.4.0 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.2.0 - vfile: 6.0.3 - - mdast-util-to-markdown@2.2.0: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-character: 2.1.1 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - unist-util-visit: 5.2.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - micromark-core-commonmark@2.0.4: - dependencies: - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.1.0 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-edit-map: 1.0.0 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.4 - micromark-factory-space: 2.1.0 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-extension-gfm-table@2.1.2: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.3 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.2 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-factory-space@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.3 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-edit-map@1.0.0: - dependencies: - micromark-util-types: 2.0.3 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.3 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.3: {} - - micromark@4.0.3: - dependencies: - '@types/debug': 4.1.13 - debug: 4.4.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.4 - micromark-factory-space: 2.1.0 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-edit-map: 1.0.0 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.3 - transitivePeerDependencies: - - supports-color - - ms@2.1.3: {} - - nanoid@3.3.20: {} - - next@16.4.2(@types/node@24.19.2)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): - dependencies: - '@next/env': 16.4.2 - '@swc/helpers': 0.5.23 - baseline-browser-mapping: 2.11.32 - caniuse-lite: 1.0.30001819 - postcss: 8.5.23 - react: 19.3.1 - react-dom: 19.3.1(react@19.3.1) - styled-jsx: 5.1.6(react@19.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 16.4.2 - '@next/swc-darwin-x64': 16.4.2 - '@next/swc-linux-arm64-gnu': 16.4.2 - '@next/swc-linux-arm64-musl': 16.4.2 - '@next/swc-linux-x64-gnu': 16.4.2 - '@next/swc-linux-x64-musl': 16.4.2 - '@next/swc-win32-arm64-msvc': 16.4.2 - '@next/swc-win32-x64-msvc': 16.4.2 - sharp: 0.35.5(@types/node@24.19.2) - transitivePeerDependencies: - - '@babel/core' - - '@types/node' - - babel-plugin-macros - - nodemailer@10.2.1: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-try@2.2.0: {} - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - path-exists@4.0.0: {} - - pg-cloudflare@1.4.1: - optional: true - - pg-connection-string@2.14.1: {} - - pg-int8@1.0.1: {} - - pg-pool@3.14.0(pg@8.23.1): - dependencies: - pg: 8.23.1 - - pg-protocol@1.16.1: {} - - pg-types@2.2.0: - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.1 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - - pg@8.23.1: - dependencies: - pg-connection-string: 2.14.1 - pg-pool: 3.14.0(pg@8.23.1) - pg-protocol: 1.16.1 - pg-types: 2.2.0 - pgpass: 1.0.5 - optionalDependencies: - pg-cloudflare: 1.4.1 - - pgpass@1.0.5: - dependencies: - split2: 4.2.0 - - picocolors@1.1.1: {} - - playwright-core@1.56.1: {} - - pngjs@5.0.0: {} - - postcss@8.5.23: - dependencies: - nanoid: 3.3.20 - picocolors: 1.1.1 - source-map-js: 1.2.2 - - postcss@8.5.31: - dependencies: - nanoid: 3.3.20 - picocolors: 1.1.1 - source-map-js: 1.2.2 - - postgres-array@2.0.0: {} - - postgres-bytea@1.0.1: {} - - postgres-date@1.0.7: {} - - postgres-interval@1.2.0: - dependencies: - xtend: 4.0.2 - - property-information@7.2.0: {} - - qrcode@1.5.4: - dependencies: - dijkstrajs: 1.0.3 - pngjs: 5.0.0 - yargs: 15.4.1 - - react-dom@19.3.1(react@19.3.1): - dependencies: - react: 19.3.1 - scheduler: 0.28.0 - - react-markdown@10.1.0(@types/react@19.3.0)(react@19.3.1): - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/react': 19.3.0 - devlop: 1.1.0 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.1 - react: 19.3.1 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - unified: 11.0.5 - unist-util-visit: 5.2.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - react@19.3.1: {} - - remark-gfm@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-gfm: 3.1.0 - micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0 - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.1.0 - micromark-util-types: 2.0.3 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-rehype@11.1.2: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.1 - unified: 11.0.5 - vfile: 6.0.3 - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.2.0 - unified: 11.0.5 - - require-directory@2.1.1: {} - - require-main-filename@2.0.0: {} - - scheduler@0.28.0: {} - - semver@7.8.5: {} - - set-blocking@2.0.0: {} - - sharp@0.35.5(@types/node@24.19.2): - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.5 - '@img/sharp-darwin-x64': 0.35.5 - '@img/sharp-freebsd-wasm32': 0.35.5 - '@img/sharp-libvips-darwin-arm64': 1.3.4 - '@img/sharp-libvips-darwin-x64': 1.3.4 - '@img/sharp-libvips-linux-arm': 1.3.4 - '@img/sharp-libvips-linux-arm64': 1.3.4 - '@img/sharp-libvips-linux-ppc64': 1.3.4 - '@img/sharp-libvips-linux-riscv64': 1.3.4 - '@img/sharp-libvips-linux-s390x': 1.3.4 - '@img/sharp-libvips-linux-x64': 1.3.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 - '@img/sharp-libvips-linuxmusl-x64': 1.3.4 - '@img/sharp-linux-arm': 0.35.5 - '@img/sharp-linux-arm64': 0.35.5 - '@img/sharp-linux-ppc64': 0.35.5 - '@img/sharp-linux-riscv64': 0.35.5 - '@img/sharp-linux-s390x': 0.35.5 - '@img/sharp-linux-x64': 0.35.5 - '@img/sharp-linuxmusl-arm64': 0.35.5 - '@img/sharp-linuxmusl-x64': 0.35.5 - '@img/sharp-webcontainers-wasm32': 0.35.5 - '@img/sharp-win32-arm64': 0.35.5 - '@img/sharp-win32-ia32': 0.35.5 - '@img/sharp-win32-x64': 0.35.5 - '@types/node': 24.19.2 - - source-map-js@1.2.2: {} - - space-separated-tokens@2.0.2: {} - - split2@4.2.0: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - style-to-js@1.1.21: - dependencies: - style-to-object: 1.0.14 - - style-to-object@1.0.14: - dependencies: - inline-style-parser: 0.2.7 - - styled-jsx@5.1.6(react@19.3.1): - dependencies: - client-only: 0.0.1 - react: 19.3.1 - - tailwindcss@4.3.3: {} - - tapable@2.3.3: {} - - trim-lines@3.0.1: {} - - trough@2.2.0: {} - - tslib@2.8.1: {} - - tsx@4.23.15: - dependencies: - esbuild: 0.28.2 - optionalDependencies: - fsevents: 2.3.3 - - typescript@5.9.3: {} - - undici-types@7.24.6: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.2.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.1.0 - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - which-module@2.0.1: {} - - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrappy@1.0.2: {} - - xtend@4.0.2: {} - - y18n@4.0.3: {} - - yargs-parser@18.1.3: - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - - yargs@15.4.1: - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - - zod@4.6.7: {} - - zwitch@2.0.4: {} diff --git a/package.json b/package.json index d531dce..8b21b97 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,13 @@ "private": true, "packageManager": "pnpm@11.25.0", "scripts": { - "dev:waterfall-wiki": "pnpm --filter @prompt/waterfall-wiki dev", - "build": "pnpm --filter @prompt/waterfall-wiki build", - "typecheck": "pnpm -r typecheck", + "dev": "turbo run dev --env-mode=loose", + "dev:waterfall-wiki": "turbo run dev --filter=@prompt/waterfall-wiki --env-mode=loose", + "build": "turbo run build --env-mode=loose", + "typecheck": "turbo run typecheck --env-mode=loose", "start": "pnpm --filter @prompt/waterfall-wiki start" + }, + "devDependencies": { + "turbo": "2.11.10" } } diff --git a/packages/brand/pnpm-lock.yaml b/packages/brand/pnpm-lock.yaml deleted file mode 100644 index 26695f8..0000000 --- a/packages/brand/pnpm-lock.yaml +++ /dev/null @@ -1,440 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@tailwindcss/postcss': - specifier: 4.3.3 - version: 4.3.3 - postcss: - specifier: 8.5.6 - version: 8.5.6 - tailwindcss: - specifier: 4.3.3 - version: 4.3.3 - typescript: - specifier: 5.9.3 - version: 5.9.3 - -packages: - - '@alloc/quick-lru@5.3.0': - resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==} - engines: {node: '>=10'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.6.0': - resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@tailwindcss/node@4.3.3': - resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - - '@tailwindcss/oxide-android-arm64@4.3.3': - resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.3.3': - resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.3.3': - resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.3.3': - resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - enhanced-resolve@5.26.0: - resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} - engines: {node: '>=10.13.0'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - nanoid@3.3.20: - resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - postcss@8.5.32: - resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - source-map-js@1.2.2: - resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} - engines: {node: '>=0.10.0'} - - tailwindcss@4.3.3: - resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - -snapshots: - - '@alloc/quick-lru@5.3.0': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.6.0': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.6.0 - - '@tailwindcss/node@4.3.3': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.26.0 - jiti: 2.7.0 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.2 - tailwindcss: 4.3.3 - - '@tailwindcss/oxide-android-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide@4.3.3': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-x64': 4.3.3 - '@tailwindcss/oxide-freebsd-x64': 4.3.3 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-x64-musl': 4.3.3 - '@tailwindcss/oxide-wasm32-wasi': 4.3.3 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - - '@tailwindcss/postcss@4.3.3': - dependencies: - '@alloc/quick-lru': 5.3.0 - '@tailwindcss/node': 4.3.3 - '@tailwindcss/oxide': 4.3.3 - postcss: 8.5.32 - tailwindcss: 4.3.3 - - detect-libc@2.1.2: {} - - enhanced-resolve@5.26.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - graceful-fs@4.2.11: {} - - jiti@2.7.0: {} - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.6.0 - - nanoid@3.3.20: {} - - picocolors@1.1.1: {} - - postcss@8.5.32: - dependencies: - nanoid: 3.3.20 - picocolors: 1.1.1 - source-map-js: 1.2.2 - - postcss@8.5.6: - dependencies: - nanoid: 3.3.20 - picocolors: 1.1.1 - source-map-js: 1.2.2 - - source-map-js@1.2.2: {} - - tailwindcss@4.3.3: {} - - tapable@2.3.3: {} - - typescript@5.9.3: {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b60ae1..1bcec2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,4 +6,3277 @@ settings: importers: - .: {} + .: + devDependencies: + turbo: + specifier: 2.11.10 + version: 2.11.10 + + apps/waterfall-wiki: + dependencies: + '@aws-sdk/client-s3': + specifier: 3.1155.0 + version: 3.1155.0 + '@endoleap/brand': + specifier: workspace:* + version: link:../../packages/brand + formidable: + specifier: 3.5.4 + version: 3.5.4 + lucide-react: + specifier: 1.61.0 + version: 1.61.0(@types/react@19.3.0)(react@19.3.1) + mdast-util-from-markdown: + specifier: 2.1.0 + version: 2.1.0 + next: + specifier: 16.4.2 + version: 16.4.2(@types/node@24.19.2)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + nodemailer: + specifier: 10.2.1 + version: 10.2.1 + pg: + specifier: 8.23.1 + version: 8.23.1 + playwright-core: + specifier: 1.56.1 + version: 1.56.1 + qrcode: + specifier: 1.5.4 + version: 1.5.4 + react: + specifier: 19.3.1 + version: 19.3.1 + react-dom: + specifier: 19.3.1 + version: 19.3.1(react@19.3.1) + react-markdown: + specifier: 10.1.0 + version: 10.1.0(@types/react@19.3.0)(react@19.3.1) + remark-gfm: + specifier: 4.0.1 + version: 4.0.1 + sharp: + specifier: 0.35.5 + version: 0.35.5(@types/node@24.19.2) + zod: + specifier: 4.6.7 + version: 4.6.7 + devDependencies: + '@tailwindcss/postcss': + specifier: 4.3.3 + version: 4.3.3 + '@types/formidable': + specifier: 3.5.1 + version: 3.5.1 + '@types/node': + specifier: 24.19.2 + version: 24.19.2 + '@types/nodemailer': + specifier: 7.0.4 + version: 7.0.4 + '@types/pg': + specifier: 8.23.1 + version: 8.23.1 + '@types/qrcode': + specifier: 1.5.6 + version: 1.5.6 + '@types/react': + specifier: 19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: 19.3.0 + version: 19.3.0(@types/react@19.3.0) + esbuild: + specifier: 0.28.2 + version: 0.28.2 + tailwindcss: + specifier: 4.3.3 + version: 4.3.3 + tsx: + specifier: 4.23.15 + version: 4.23.15 + typescript: + specifier: 5.9.3 + version: 5.9.3 + + packages/brand: + devDependencies: + '@tailwindcss/postcss': + specifier: 4.3.3 + version: 4.3.3 + postcss: + specifier: 8.5.6 + version: 8.5.6 + tailwindcss: + specifier: 4.3.3 + version: 4.3.3 + typescript: + specifier: 5.9.3 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.3.0': + resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==} + engines: {node: '>=10'} + + '@aws-sdk/checksums@3.1001.5': + resolution: {integrity: sha512-6erto9nK2OZO/woEgmFIaPh5gChgdNd7nL6jgAf8TAXz3/QHC8Pan2TFe9GYp3IxOIKCCGWPylSjtOHKfNzQlw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-s3@3.1155.0': + resolution: {integrity: sha512-l3uMeIlzWVgj/RTJTh5sKYKtQz1la3cvWLUPn1cwFH4UZpcqTH66SgJHCeB2FOE9bEAjOdbmCyPKt9aw867Fng==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-sesv2@3.1158.0': + resolution: {integrity: sha512-CEhnnEhIUeyfVSEB/Q8Gm2YOu/vF6ONrQ09e9t3imkvTTBVbpMNDhVyFBlBfHxNDD63kgJ1p39DPicx5Er+Sxw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.978.5': + resolution: {integrity: sha512-ruI0AWqcrfL20XDS6SwZIJFPOBZ6N0ADa9nLzWlBhxk1aoUjbUX3JkTR9vEmk7o35XpAHi68ahZ4T6RE2U9XyA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.978.6': + resolution: {integrity: sha512-ykF/MiYoM15dgZwF/WLgjJXVbAK2wU6vjPigeEnOHwyUJ0rdlraO5uekecKNVPkvOnz+oju/5zljViQs1nI1FQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.76': + resolution: {integrity: sha512-YxmL1Ar7bV9ZkJvtv+lj+MF6le76NnZnbmgySyZuxpZaCv0/1lZ3KxKy6RIj1h9ui2hWnVjZxbmTMeO0y/NrQA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.77': + resolution: {integrity: sha512-CN6dQCDo32a8n2gvm2HBeqGV5BNI9noG96nTj5bGO2HWtt0Rucu/A8pcnJIqkzILOHSuilnCk0Do8/7h6SOXhQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.78': + resolution: {integrity: sha512-3VHknDRngoebsaqyT1iR75b5cBT2zfEtODK6yYmYltljkK1HEAQLs2FEd1IpWqAeOYdMGaG9wikngWLTufY7RA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.79': + resolution: {integrity: sha512-Netsu0nnmvP2HiN1ZFQBeUu+JehQIuubqz+j2kHDgeSE4HDzniCpIZ7RzoWABq+LTcbCi9W03hywifUagoVIWQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.973.21': + resolution: {integrity: sha512-aLx9hbCX6mGAyv4ZZd8UagJ4RASHVJw6BFDSIyuKmLkqNL6H47EjBPK6qaJ4B5TDV6mc+vMa0SJFYyo4mkvVQg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.973.22': + resolution: {integrity: sha512-x7/5h58cqOoZR0RWUZC7v/cJOADQR42iSIQkvU+00HVHR4jh8ZpqEgRPGf2zei59iH5qP40lL3bkq49PuWl71g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.83': + resolution: {integrity: sha512-9bJU6FjRacF4FK/QHf8Dyxhur7B1grRj8C1Yh4IkpoFW2exQflPdCv0VrbemLW9ZctLNle+AE7TETs9KNvuLYg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.84': + resolution: {integrity: sha512-G9Ty1uIV0laEvEp2KiMbEhsuBAjgaD3KYrwVjAM5VUxpcIuPBqQI5riA7/qzrnSgYiAKQbubbgkwcqCICKCIxg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.88': + resolution: {integrity: sha512-AG0KID5qhQPzrFkh5b0Cws1Amytvzo0L0Xj6hMgJXEwD6ZXAqninDhSNAWJiN5zU4YVVZnuidnqwPwYJ04IamA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.89': + resolution: {integrity: sha512-O8xj7/sanrnXfFHu/LUujVBiiwn5mknYenU7xt1w6+yvhR1QoDZWXLFh1gLITaFVPtb1jWgEirC+kFNw+//tyw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.76': + resolution: {integrity: sha512-pdhKFTXv1zPYhh92MTlp0jVbLWvUEV9v+rziuEkoskGGQ2iJskxHTO38nA8UAbT/N4IM3n8OZJ9rIY5IBO15/A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.77': + resolution: {integrity: sha512-sWA8Jm+ASzkTxoqLizHWRbt8Gj3yZCmZ0hYdvg0uIxuLzWseYcfrDFzaSnkRWlEWSJV38rAWFimdrN0J4NR7Pg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.973.20': + resolution: {integrity: sha512-DlxrAB3MnjNLx/da7b89W+3DyJQYso/q7Ns15MsST2IV2nkAYQOYTUCaffNSjgrpMP99Dm2mV2jxzcbyF94ZfA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.973.21': + resolution: {integrity: sha512-KV2LM8fbYUhDzGEBnPwb+ISNfNlhmnpLS+y6A6VsJpg1EI9aUJ0+dRTxh2GrGdneFH0T4TiuboHxz8i6+M38JA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.82': + resolution: {integrity: sha512-LUo46IqPTJ4xKBsrLuoaxiv/D2GTBCcFDMD1y3jKIqPR/p7Rw14RJFZ0iRWftqZtyLPQX9JMGnSi3mBu4y/pNA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.83': + resolution: {integrity: sha512-iekYilmo2Re51fC7gMkEfZ7jFDXwehSCmXjDESaYQsdgYhsLQFG7rTYr3GBvRIyj+Fkda2NxbZX7hAZDaFD0JQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.81': + resolution: {integrity: sha512-jfanv1pV0sJFZw6/DPiNjvK7B478god0zOum5yIWo+/+1Gy+CSXWpGBWx+3nBJyDvjcu/2kUMr4ADUp9BHH34Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.50': + resolution: {integrity: sha512-nKb8qu4QsnpZTq/6JIXc72mzuzfh7BK4sUQkpQznPec2z4W7Ob2xYkcM4i5e1yGTBnbJVeFv/0sf3jcz85hclA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.997.51': + resolution: {integrity: sha512-DJK4iGIHWU8Jh8ODMvqFYcX9yGBMXV4tSZAvqlHF5EDd8hbmF3eFO9KIWOnA7wcQjPzEkyMMW1/FgZ9RU8oimQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.996.49': + resolution: {integrity: sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1155.0': + resolution: {integrity: sha512-iIcQnYLhWuq9qOLQbIWFrxF3PF0svVeZvHnUS4DUnO4zUFmFHIyGyjqrntXG+e/5PUgfavV0pYfDIKHxTzN2LA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.1157.0': + resolution: {integrity: sha512-AeAzcwWRP2uANO2TjFpKjRdvOCzgfIu6YQl9Ceibd6E0gnq5dfOzujFnq2aeqgbLg4wRtXbUZE4gekCCPrz/OQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.975.0': + resolution: {integrity: sha512-WQlhs6juByzWOzj5fwxOx0mPJaEqMcG/sDpl3Id61B3XhKE63xY+0QWcnG+sWR/sCfprmreyUo1lM7IKZmHMzw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.41': + resolution: {integrity: sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} + engines: {node: '>=18.0.0'} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.5': + resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.5': + resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.5': + resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.4': + resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.4': + resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.4': + resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.4': + resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.4': + resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.4': + resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.4': + resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.4': + resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.5': + resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.5': + resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.5': + resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.5': + resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.5': + resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.5': + resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.5': + resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.5': + resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.5': + resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.5': + resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.5': + resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.5': + resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.5': + resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@next/env@16.4.2': + resolution: {integrity: sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==} + + '@next/swc-darwin-arm64@16.4.2': + resolution: {integrity: sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.4.2': + resolution: {integrity: sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.4.2': + resolution: {integrity: sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@16.4.2': + resolution: {integrity: sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@16.4.2': + resolution: {integrity: sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@16.4.2': + resolution: {integrity: sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@16.4.2': + resolution: {integrity: sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.4.2': + resolution: {integrity: sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} + + '@smithy/core@3.36.2': + resolution: {integrity: sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.37.0': + resolution: {integrity: sha512-97imTcCyiPd8k0e9+ts/Sp0W65Dn9Ti357J7M09Ov4tI8Q1JDJVFSoAJtjEVKqfHEr3Pdo2jfolqKCaaiszH0g==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.5.2': + resolution: {integrity: sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.8.0': + resolution: {integrity: sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.12.1': + resolution: {integrity: sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.13.0': + resolution: {integrity: sha512-/GGcPoNJ2nzGytdAanpbx7mpp9Coa8LP+/Rpmbktr8YeLuIXs9B0SEz+X3AeonHdyzOIFJdKbN7jOZ+Tj+pMGw==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.7.4': + resolution: {integrity: sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.19.0': + resolution: {integrity: sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==} + engines: {node: '>=18.0.0'} + + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} + + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} + + '@tailwindcss/postcss@4.3.3': + resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} + + '@turbo/darwin-64@2.11.10': + resolution: {integrity: sha512-By4idRap5+nLnMuRzLB4qkOPRlvNdyY1ne4VJYDyUoUB11H94k7uif9mbe3JT8JSNXs1D68uMcBj6hJ/jMQRvA==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.11.10': + resolution: {integrity: sha512-pyEYlVcuexOPa92p0Wlwl9YT1Is5D60lxN3ItWm3g30YLvFaUq86TF4BBWg5ZAc0uMTnrOzCvuCsOBTdzUWnRQ==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.11.10': + resolution: {integrity: sha512-1tYeR/IDZ5HEdsb0jNq2L2xam5nMQ+g80tlV2BZmv4OFw+pMcUM0mMmUcfc5wXPPNHVNPuDMjh9Y8G8HgXPLZA==} + cpu: [x64] + os: [android, linux] + + '@turbo/linux-arm64@2.11.10': + resolution: {integrity: sha512-HuekQxvVGhUd46g1Im/DKdOtglHr4/gguZAvzEpPUgtxNkztSa8lWhQEGQ+EmPQ0n7w19qjThFU+OfEEd0uvlw==} + cpu: [arm64] + os: [android, linux] + + '@turbo/windows-64@2.11.10': + resolution: {integrity: sha512-UJOP3BkbreY3/JlMgJR6ZiimxYIBn+c+B4tnBIxD0E9c3Estmk+XoVNVBNBottZLNRXe3/ScgOU4+/k8tIWysg==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.11.10': + resolution: {integrity: sha512-xOkEjF3ws/b/YNlc+bvJHMYBn+vL+QbEISZFVi0qS+ImmhA1X3/gmNmBrjjIG3dg6aeN+n6gQitzSkXQ286qJQ==} + cpu: [arm64] + os: [win32] + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/formidable@3.5.1': + resolution: {integrity: sha512-aFQijSGbD8JCeEST2LEbwR7faHynbt43lojLIcTM/QhB2U06h41ZVRFVEql+Z1xdL+aKGIzm69V/P/uSW9N6XA==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.19.2': + resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} + + '@types/nodemailer@7.0.4': + resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} + + '@types/pg@8.23.1': + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} + peerDependencies: + '@types/react': ^19.3.0 + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + baseline-browser-mapping@2.11.32: + resolution: {integrity: sha512-UvuxFU9P3hM29LrQdwu0cD6w6/+SFr+UlB9s/knI7+O2rK9xFxKUwSDWxGdbbqQ9/V7BJ5s0NI5nQAE4uJp3gQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bowser@2.14.1: + resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001819: + resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} + engines: {node: '>=10.13.0'} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lucide-react@1.61.0: + resolution: {integrity: sha512-v2SQPPPNjnr+BlhL9Spuzmo5yR+hLkftfuE4E3jaxdNPgSH0hJKPcLJBP7yAta87jQNPsSERS59PJjCJ/CorBA==} + peerDependencies: + '@types/react': '*' + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-find-and-replace@3.0.4: + resolution: {integrity: sha512-z4B5c6jrnJ1ltOQemhWYPBSAo6y42Z8Gt2yEgfgNTMcLSU59B8bfj8Z8AnAlJrBGLnqkdAUZ7jdDEqQw3KuipQ==} + + mdast-util-from-markdown@2.1.0: + resolution: {integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.1: + resolution: {integrity: sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.2.0: + resolution: {integrity: sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.4: + resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.2: + resolution: {integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.1.0: + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-edit-map@1.0.0: + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.3: + resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} + + micromark@4.0.3: + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next@16.4.2: + resolution: {integrity: sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==} + engines: {node: '>=20.9.0'} + hasBin: true + 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 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + nodemailer@10.2.1: + resolution: {integrity: sha512-ZrDNsPFoDMwefNs7sJwrYiEJ/CG4ClJWDZv05iNJsEBWa8GRDry1QJHTJxsuTAY9eDDKjX4k+q3SxslXJOao7w==} + engines: {node: '>=20.0.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + pg-cloudflare@1.4.1: + resolution: {integrity: sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==} + + pg-connection-string@2.14.1: + resolution: {integrity: sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.1: + resolution: {integrity: sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.23.1: + resolution: {integrity: sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + playwright-core@1.56.1: + resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} + engines: {node: '>=18'} + hasBin: true + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.31: + resolution: {integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + react-dom@19.3.1: + resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} + peerDependencies: + react: ^19.3.1 + + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react@19.3.1: + resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} + engines: {node: '>=0.10.0'} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + sharp@0.35.5: + resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} + engines: {node: '>=18.0.0'} + hasBin: true + + turbo@2.11.10: + resolution: {integrity: sha512-3GJYwig8ygEI89oaOZdduJX8/aw6O9eRPYHoIt6T3FZl9hkKjiyIspufJoVgqXihPoJu8M5CSV0DlZvzfohBCw==} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.1.0: + resolution: {integrity: sha512-vKXxz6O38sKDj1mtx/k2cWvkkFBuYGx6uGrlzBkVeWCRF/F8sdI0j3Wbkz/5rVNRRuVc8UrgRdbCxNhmYXgP3w==} + + unist-util-visit@5.2.0: + resolution: {integrity: sha512-fpsYs/VVoZxfGY9/j3FQpZmN8wdLJaV1Pt38AaJNZu9uKIHcFU3halCdBveEq1HGKO1OEfJY65w1zvoFV1b3qQ==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + zod@4.6.7: + resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.3.0': {} + + '@aws-sdk/checksums@3.1001.5': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.1155.0': + dependencies: + '@aws-sdk/checksums': 3.1001.5 + '@aws-sdk/core': 3.978.5 + '@aws-sdk/credential-provider-node': 3.972.88 + '@aws-sdk/middleware-sdk-s3': 3.972.81 + '@aws-sdk/signature-v4-multi-region': 3.996.49 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/client-sesv2@3.1158.0': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-node': 3.972.89 + '@aws-sdk/signature-v4-multi-region': 3.996.49 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.13.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/core@3.978.5': + dependencies: + '@aws-sdk/types': 3.975.0 + '@aws-sdk/xml-builder': 3.972.41 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.36.2 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/core@3.978.6': + dependencies: + '@aws-sdk/types': 3.975.0 + '@aws-sdk/xml-builder': 3.972.41 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.37.0 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 + bowser: 2.14.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.76': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.972.77': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.78': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.972.79': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.13.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.21': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/credential-provider-env': 3.972.76 + '@aws-sdk/credential-provider-http': 3.972.78 + '@aws-sdk/credential-provider-login': 3.972.83 + '@aws-sdk/credential-provider-process': 3.972.76 + '@aws-sdk/credential-provider-sso': 3.973.20 + '@aws-sdk/credential-provider-web-identity': 3.972.82 + '@aws-sdk/nested-clients': 3.997.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.973.22': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/credential-provider-env': 3.972.77 + '@aws-sdk/credential-provider-http': 3.972.79 + '@aws-sdk/credential-provider-login': 3.972.84 + '@aws-sdk/credential-provider-process': 3.972.77 + '@aws-sdk/credential-provider-sso': 3.973.21 + '@aws-sdk/credential-provider-web-identity': 3.972.83 + '@aws-sdk/nested-clients': 3.997.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.83': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/nested-clients': 3.997.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-login@3.972.84': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.88': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.76 + '@aws-sdk/credential-provider-http': 3.972.78 + '@aws-sdk/credential-provider-ini': 3.973.21 + '@aws-sdk/credential-provider-process': 3.972.76 + '@aws-sdk/credential-provider-sso': 3.973.20 + '@aws-sdk/credential-provider-web-identity': 3.972.82 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.89': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.77 + '@aws-sdk/credential-provider-http': 3.972.79 + '@aws-sdk/credential-provider-ini': 3.973.22 + '@aws-sdk/credential-provider-process': 3.972.77 + '@aws-sdk/credential-provider-sso': 3.973.21 + '@aws-sdk/credential-provider-web-identity': 3.972.83 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.76': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.77': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.20': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/nested-clients': 3.997.50 + '@aws-sdk/token-providers': 3.1155.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.21': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 + '@aws-sdk/token-providers': 3.1157.0 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.82': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/nested-clients': 3.997.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.83': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.81': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/signature-v4-multi-region': 3.996.49 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.50': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/signature-v4-multi-region': 3.996.49 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.51': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/signature-v4-multi-region': 3.996.49 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.13.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.49': + dependencies: + '@aws-sdk/types': 3.975.0 + '@smithy/signature-v4': 5.7.4 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1155.0': + dependencies: + '@aws-sdk/core': 3.978.5 + '@aws-sdk/nested-clients': 3.997.50 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1157.0': + dependencies: + '@aws-sdk/core': 3.978.6 + '@aws-sdk/nested-clients': 3.997.51 + '@aws-sdk/types': 3.975.0 + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/types@3.975.0': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.41': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.3.0': {} + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.4 + optional: true + + '@img/sharp-darwin-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.4 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.4': + optional: true + + '@img/sharp-linux-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.4 + optional: true + + '@img/sharp-linux-arm@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.4 + optional: true + + '@img/sharp-linux-ppc64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.4 + optional: true + + '@img/sharp-linux-riscv64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.4 + optional: true + + '@img/sharp-linux-s390x@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.4 + optional: true + + '@img/sharp-linux-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + optional: true + + '@img/sharp-wasm32@0.35.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.5': + dependencies: + '@img/sharp-wasm32': 0.35.5 + optional: true + + '@img/sharp-win32-arm64@0.35.5': + optional: true + + '@img/sharp-win32-ia32@0.35.5': + optional: true + + '@img/sharp-win32-x64@0.35.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@next/env@16.4.2': {} + + '@next/swc-darwin-arm64@16.4.2': + optional: true + + '@next/swc-darwin-x64@16.4.2': + optional: true + + '@next/swc-linux-arm64-gnu@16.4.2': + optional: true + + '@next/swc-linux-arm64-musl@16.4.2': + optional: true + + '@next/swc-linux-x64-gnu@16.4.2': + optional: true + + '@next/swc-linux-x64-musl@16.4.2': + optional: true + + '@next/swc-win32-arm64-msvc@16.4.2': + optional: true + + '@next/swc-win32-x64-msvc@16.4.2': + optional: true + + '@noble/hashes@1.8.0': {} + + '@paralleldrive/cuid2@2.3.1': + dependencies: + '@noble/hashes': 1.8.0 + + '@smithy/core@3.36.2': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/core@3.37.0': + dependencies: + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.5.2': + dependencies: + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.8.0': + dependencies: + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.12.1': + dependencies: + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.13.0': + dependencies: + '@smithy/core': 3.37.0 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.7.4': + dependencies: + '@smithy/core': 3.36.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + + '@smithy/types@4.19.0': + dependencies: + tslib: 2.8.1 + + '@swc/helpers@0.5.23': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.3.3': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.26.0 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.2 + tailwindcss: 4.3.3 + + '@tailwindcss/oxide-android-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.3.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + optional: true + + '@tailwindcss/oxide@4.3.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/postcss@4.3.3': + dependencies: + '@alloc/quick-lru': 5.3.0 + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + postcss: 8.5.31 + tailwindcss: 4.3.3 + + '@turbo/darwin-64@2.11.10': + optional: true + + '@turbo/darwin-arm64@2.11.10': + optional: true + + '@turbo/linux-64@2.11.10': + optional: true + + '@turbo/linux-arm64@2.11.10': + optional: true + + '@turbo/windows-64@2.11.10': + optional: true + + '@turbo/windows-arm64@2.11.10': + optional: true + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/formidable@3.5.1': + dependencies: + '@types/node': 24.19.2 + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@24.19.2': + dependencies: + undici-types: 7.24.6 + + '@types/nodemailer@7.0.4': + dependencies: + '@aws-sdk/client-sesv2': 3.1158.0 + '@types/node': 24.19.2 + + '@types/pg@8.23.1': + dependencies: + '@types/node': 24.19.2 + pg-protocol: 1.16.1 + pg-types: 2.2.0 + + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 24.19.2 + + '@types/react-dom@19.3.0(@types/react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.4.0': {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + asap@2.0.6: {} + + bail@2.0.2: {} + + baseline-browser-mapping@2.11.32: {} + + bowser@2.14.1: {} + + camelcase@5.3.1: {} + + caniuse-lite@1.0.30001819: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + client-only@0.0.1: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + dijkstrajs@1.0.3: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.26.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escape-string-regexp@5.0.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + extend@3.0.2: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + graceful-fs@4.2.11: {} + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + html-url-attributes@3.0.1: {} + + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + + jiti@2.7.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + longest-streak@3.1.0: {} + + lucide-react@1.61.0(@types/react@19.3.0)(react@19.3.1): + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.4: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.1.0 + + mdast-util-from-markdown@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.3 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.4 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.1.0 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.1 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.2.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.2.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.2.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-character: 2.1.1 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + unist-util-visit: 5.2.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.4: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.1.0 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-edit-map: 1.0.0 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-table@2.1.2: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.3 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.2 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-space@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.3 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-edit-map@1.0.0: + dependencies: + micromark-util-types: 2.0.3 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.3 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.3: {} + + micromark@4.0.3: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-edit-map: 1.0.0 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + transitivePeerDependencies: + - supports-color + + ms@2.1.3: {} + + nanoid@3.3.20: {} + + next@16.4.2(@types/node@24.19.2)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@next/env': 16.4.2 + '@swc/helpers': 0.5.23 + baseline-browser-mapping: 2.11.32 + caniuse-lite: 1.0.30001819 + postcss: 8.5.23 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + styled-jsx: 5.1.6(react@19.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 16.4.2 + '@next/swc-darwin-x64': 16.4.2 + '@next/swc-linux-arm64-gnu': 16.4.2 + '@next/swc-linux-arm64-musl': 16.4.2 + '@next/swc-linux-x64-gnu': 16.4.2 + '@next/swc-linux-x64-musl': 16.4.2 + '@next/swc-win32-arm64-msvc': 16.4.2 + '@next/swc-win32-x64-msvc': 16.4.2 + sharp: 0.35.5(@types/node@24.19.2) + transitivePeerDependencies: + - '@babel/core' + - '@types/node' + - babel-plugin-macros + + nodemailer@10.2.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + path-exists@4.0.0: {} + + pg-cloudflare@1.4.1: + optional: true + + pg-connection-string@2.14.1: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.23.1): + dependencies: + pg: 8.23.1 + + pg-protocol@1.16.1: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.23.1: + dependencies: + pg-connection-string: 2.14.1 + pg-pool: 3.14.0(pg@8.23.1) + pg-protocol: 1.16.1 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picocolors@1.1.1: {} + + playwright-core@1.56.1: {} + + pngjs@5.0.0: {} + + postcss@8.5.23: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + postcss@8.5.31: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + property-information@7.2.0: {} + + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + + react-dom@19.3.1(react@19.3.1): + dependencies: + react: 19.3.1 + scheduler: 0.28.0 + + react-markdown@10.1.0(@types/react@19.3.0)(react@19.3.1): + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@types/react': 19.3.0 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.2.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react@19.3.1: {} + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.1.0 + micromark-util-types: 2.0.3 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.2.0 + unified: 11.0.5 + + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + + scheduler@0.28.0: {} + + semver@7.8.5: {} + + set-blocking@2.0.0: {} + + sharp@0.35.5(@types/node@24.19.2): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.5 + '@img/sharp-darwin-x64': 0.35.5 + '@img/sharp-freebsd-wasm32': 0.35.5 + '@img/sharp-libvips-darwin-arm64': 1.3.4 + '@img/sharp-libvips-darwin-x64': 1.3.4 + '@img/sharp-libvips-linux-arm': 1.3.4 + '@img/sharp-libvips-linux-arm64': 1.3.4 + '@img/sharp-libvips-linux-ppc64': 1.3.4 + '@img/sharp-libvips-linux-riscv64': 1.3.4 + '@img/sharp-libvips-linux-s390x': 1.3.4 + '@img/sharp-libvips-linux-x64': 1.3.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.4 + '@img/sharp-libvips-linuxmusl-x64': 1.3.4 + '@img/sharp-linux-arm': 0.35.5 + '@img/sharp-linux-arm64': 0.35.5 + '@img/sharp-linux-ppc64': 0.35.5 + '@img/sharp-linux-riscv64': 0.35.5 + '@img/sharp-linux-s390x': 0.35.5 + '@img/sharp-linux-x64': 0.35.5 + '@img/sharp-linuxmusl-arm64': 0.35.5 + '@img/sharp-linuxmusl-x64': 0.35.5 + '@img/sharp-webcontainers-wasm32': 0.35.5 + '@img/sharp-win32-arm64': 0.35.5 + '@img/sharp-win32-ia32': 0.35.5 + '@img/sharp-win32-x64': 0.35.5 + '@types/node': 24.19.2 + + source-map-js@1.2.2: {} + + space-separated-tokens@2.0.2: {} + + split2@4.2.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + styled-jsx@5.1.6(react@19.3.1): + dependencies: + client-only: 0.0.1 + react: 19.3.1 + + tailwindcss@4.3.3: {} + + tapable@2.3.3: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tslib@2.8.1: {} + + tsx@4.23.15: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + + turbo@2.11.10: + optionalDependencies: + '@turbo/darwin-64': 2.11.10 + '@turbo/darwin-arm64': 2.11.10 + '@turbo/linux-64': 2.11.10 + '@turbo/linux-arm64': 2.11.10 + '@turbo/windows-64': 2.11.10 + '@turbo/windows-arm64': 2.11.10 + + typescript@5.9.3: {} + + undici-types@7.24.6: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.2.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.1.0 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + which-module@2.0.1: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + xtend@4.0.2: {} + + y18n@4.0.3: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + zod@4.6.7: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0d9a5e7..4db738f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,8 +1,7 @@ packages: - apps/waterfall-wiki - packages/brand -# Each application owns its lockfile, so releases stay independent. -sharedWorkspaceLockfile: false +sharedWorkspaceLockfile: true allowBuilds: sharp: true esbuild: true diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..77c9888 --- /dev/null +++ b/turbo.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://turborepo.dev/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "cache": false + }, + "typecheck": { + "dependsOn": ["^typecheck"], + "cache": false + }, + "dev": { + "cache": false, + "persistent": true + } + } +} From 61d80f352f13840f360e4dc42f955dc3597e97c7 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Wed, 4 Nov 2026 23:01:32 +0800 Subject: [PATCH 3/3] Rename the unified application to dashboard --- .gitignore | 6 +-- AGENTS.md | 2 +- README.md | 10 ++-- .../.env.example | 0 apps/{waterfall-wiki => dashboard}/ACCOUNT.md | 0 .../db/001_library.sql | 0 .../db/002_page_snapshots.sql | 0 .../db/003_note_descriptions.sql | 0 .../db/004_flat_notes.sql | 0 .../db/005_note_editing.sql | 0 .../db/006_post_public_ids.sql | 0 .../db/007_dynamic_code_auth.sql | 0 .../db/008_system_tags.sql | 0 .../db/009_studio_designs.sql | 0 .../db/010_studio_design_posts.sql | 0 .../db/011_post_archiving.sql | 0 .../db/012_studio_library.sql | 0 .../db/013_studio_series.sql | 0 .../db/014_email_accounts.sql | 0 .../db/015_guidance_documents.sql | 0 .../db/016_canvas_workspace.sql | 0 .../db/017_password_reset.sql | 0 .../next-env.d.ts | 0 .../next.config.ts | 2 +- .../package.json | 2 +- .../postcss.config.mjs | 0 .../public/brand-mark.svg | 0 .../public/learning-skills/cover.svg | 0 .../public/llms.txt | 0 .../scripts/auth-cli.ts | 0 .../scripts/bootstrap.ts | 0 .../seed/characters/extraction.json | 0 .../seed/characters/packs.json | 0 .../seed/characters/prompts.json | 0 .../src/components/Brand.tsx | 0 .../src/components/DocumentDirectory.tsx | 0 .../src/components/ExploreDetail.tsx | 0 .../src/components/ExploreFeed.tsx | 0 .../src/components/Feed.tsx | 0 .../src/components/GuidanceIndex.tsx | 0 .../src/components/GuidanceReader.tsx | 0 .../src/components/GuidanceShell.tsx | 0 .../src/components/GuidanceUI.tsx | 0 .../src/components/LandingSketches.tsx | 0 .../src/components/NoteEditor.tsx | 0 .../src/components/NoteMarkdown.tsx | 0 .../src/components/QuickTags.tsx | 0 .../src/components/Shell.tsx | 0 .../src/components/SkillCard.tsx | 0 .../src/components/SkillDocs.tsx | 0 .../src/components/TagInput.tsx | 0 .../src/components/ThemeToggle.tsx | 0 .../src/content/guides/catalog.ts | 0 .../src/content/learning-skills/Series.tsx | 0 .../src/content/learning-skills/cover.ts | 0 .../src/content/learning-skills/data.ts | 0 .../src/content/managed-media.json | 48 +++++++++---------- .../src/content/note-descriptions.ts | 0 .../src/content/page-ids.json | 0 .../src/content/page-links.ts | 0 .../src/content/skills/catalog.ts | 0 .../src/content/skills/guidance.ts | 0 .../src/content/skills/shadow-puppet.ts | 0 .../src/content/standard-layout.json | 0 .../src/content/studio-layouts.json | 0 .../src/content/wiki.ts | 0 .../src/legacy/WaterfallHome.tsx | 0 .../src/lib/account-crypto.ts | 0 .../src/lib/account-handler.ts | 0 .../src/lib/account.ts | 0 .../src/lib/api-documentation.ts | 0 .../src/lib/api.ts | 0 .../src/lib/asset-links.ts | 0 .../src/lib/auth-store.ts | 0 .../src/lib/auth.ts | 0 .../src/lib/canvas-document.ts | 0 .../src/lib/canvas-store.ts | 0 .../src/lib/character-image.ts | 0 .../src/lib/db.ts | 0 .../src/lib/discovery.ts | 0 .../src/lib/explore.ts | 0 .../src/lib/guidance-discovery.ts | 0 .../src/lib/guidance-input.ts | 0 .../src/lib/guidance-store.ts | 0 .../src/lib/health.ts | 0 .../src/lib/layout-parameters.ts | 0 .../src/lib/library.ts | 0 .../src/lib/markdown-response.ts | 0 .../src/lib/model.ts | 0 .../src/lib/note-edits.ts | 0 .../src/lib/post-archive.ts | 0 .../src/lib/preview.ts | 2 +- .../src/lib/probe.ts | 0 .../src/lib/publish-learning-series.ts | 2 +- .../src/lib/series.ts | 0 .../src/lib/storage.ts | 0 .../src/lib/street-people.ts | 0 .../src/lib/studio-design-documentation.ts | 0 .../src/lib/studio-design-posts.ts | 0 .../src/lib/studio-designs.ts | 0 .../src/lib/studio-series.ts | 0 .../src/lib/system-tags.ts | 0 .../src/lib/write.ts | 0 .../src/pages/[section]/[[...taxonomy]].tsx | 0 .../src/pages/_app.tsx | 0 .../src/pages/_document.tsx | 0 .../src/pages/account.tsx | 0 .../src/pages/api/account/[action].ts | 0 .../src/pages/api/agent-index.ts | 0 .../src/pages/api/auth/login.ts | 0 .../src/pages/api/auth/logout.ts | 0 .../src/pages/api/canvases/[[...path]].ts | 0 .../src/pages/api/documents/[[...path]].ts | 0 .../src/pages/api/explore-feed.ts | 0 .../src/pages/api/feed.ts | 0 .../src/pages/api/guidance/[[...path]].ts | 0 .../src/pages/api/guides/[id].ts | 0 .../src/pages/api/guides/index.ts | 0 .../src/pages/api/health.ts | 0 .../src/pages/api/index.ts | 0 .../src/pages/api/media/[id].ts | 0 .../src/pages/api/notes/[id].ts | 0 .../src/pages/api/posts/[id]/archive.ts | 0 .../src/pages/api/posts/index.ts | 0 .../src/pages/api/preview.ts | 0 .../src/pages/api/ready.ts | 0 .../src/pages/api/skill-docs/[skill]/[doc].ts | 0 .../api/skill-docs/street-people/[doc].ts | 0 .../src/pages/api/skills/street-people.ts | 0 .../src/pages/api/studio-ui/export/[slug].ts | 0 .../src/pages/api/studio-ui/health.ts | 0 .../src/pages/api/studio-ui/index.ts | 0 .../src/pages/api/studio-ui/layouts/[id].ts | 0 .../src/pages/api/studio-ui/layouts/index.ts | 0 .../src/pages/api/studio/designs/[id].ts | 0 .../src/pages/api/studio/designs/[id]/post.ts | 0 .../src/pages/api/studio/designs/index.ts | 0 .../src/pages/api/studio/series/[id].ts | 0 .../pages/api/studio/series/[id]/export.ts | 0 .../src/pages/api/studio/series/index.ts | 0 .../src/pages/api/tags.ts | 0 .../src/pages/api/upload.ts | 0 .../src/pages/api/wiki-index.ts | 0 .../src/pages/api/wiki-post/[slug].ts | 0 .../src/pages/api/wiki/[key].ts | 0 .../src/pages/categories.tsx | 0 .../src/pages/documents/[id].tsx | 0 .../src/pages/documents/index.tsx | 0 .../src/pages/explore/[id].tsx | 0 .../src/pages/graphic/index.tsx | 0 .../src/pages/guide/[id].tsx | 0 .../src/pages/index.tsx | 0 .../src/pages/infinite_canvas/index.tsx | 0 .../src/pages/library/import.tsx | 0 .../src/pages/login.tsx | 0 .../src/pages/p/[slug].tsx | 0 .../src/pages/protocol.tsx | 0 .../src/pages/skills/[[...path]].tsx | 0 .../src/pages/studio/designs/[id].tsx | 0 .../src/pages/studio/ideas/[slug].tsx | 0 .../src/pages/studio/layouts/[layoutId].tsx | 0 .../src/pages/studio/template-layout.tsx | 0 .../src/pages/tools/doodle.tsx | 0 .../pages/tools/skills/[skill]/[[...doc]].tsx | 0 .../src/pages/tools/skills/index.tsx | 0 .../src/pages/tools/skills/street-people.tsx | 0 .../tools/skills/street-people/[doc].tsx | 0 .../src/pages/wiki/[key].tsx | 0 .../src/pages/workspace.tsx | 0 .../src/pages/write.tsx | 0 .../src/proxy.ts | 0 .../src/studio/components/Brand.tsx | 0 .../src/studio/components/LayoutCanvas.tsx | 0 .../src/studio/components/LayoutSelection.tsx | 0 .../src/studio/components/LayoutWorkbench.tsx | 0 .../studio/components/NativeCurvedCopy.tsx | 0 .../src/studio/components/NativePreview.tsx | 0 .../src/studio/components/NativeResult.tsx | 0 .../src/studio/components/NativeSeries.tsx | 0 .../src/studio/components/Ref17Cover.tsx | 0 .../src/studio/components/SourceCover.tsx | 0 .../src/studio/components/TemplateTrial.tsx | 0 .../src/studio/components/ThemeToggle.tsx | 0 .../components/canvas/CanvasAgentPanel.tsx | 0 .../components/canvas/CanvasComposer.tsx | 0 .../components/canvas/CanvasNodeView.tsx | 0 .../src/studio/content/ideas.ts | 0 .../src/studio/content/standard-layout.json | 0 .../src/studio/content/templates/layouts.json | 0 .../src/studio/content/templates/ref-17.json | 0 .../studio/content/templates/selection.json | 0 .../src/studio/content/tool-links.ts | 0 .../src/studio/lib/api-documentation.ts | 0 .../src/studio/lib/canvas-document.ts | 0 .../src/studio/lib/design-server.ts | 0 .../src/studio/lib/graphic-document.ts | 0 .../src/studio/lib/layout-images-client.ts | 0 .../src/studio/lib/layout-images.ts | 0 .../src/studio/lib/layout-parameters.ts | 0 .../src/studio/lib/native-layout.ts | 0 .../src/studio/lib/render-cover.ts | 0 .../src/studio/lib/series.ts | 0 .../src/studio/styles/canvas-agent.module.css | 0 .../studio/styles/canvas-composer.module.css | 0 .../src/studio/styles/canvas-node.module.css | 0 .../src/studio/styles/globals.css | 0 .../src/studio/styles/graphic.module.css | 0 .../studio/styles/infinite-canvas.module.css | 0 .../src/styles/account.module.css | 0 .../src/styles/brand.css | 0 .../src/styles/documents.module.css | 0 .../src/styles/globals.css | 0 .../src/styles/guidance.module.css | 0 .../src/styles/landing.module.css | 0 .../src/styles/learning-series.css | 0 .../src/styles/skill-docs.css | 0 .../src/styles/workspace.module.css | 0 .../src/worker.ts | 0 .../tsconfig.json | 0 package.json | 4 +- packages/brand/scripts/prepare-app.mjs | 2 +- pnpm-lock.yaml | 2 +- pnpm-workspace.yaml | 2 +- 223 files changed, 42 insertions(+), 42 deletions(-) rename apps/{waterfall-wiki => dashboard}/.env.example (100%) rename apps/{waterfall-wiki => dashboard}/ACCOUNT.md (100%) rename apps/{waterfall-wiki => dashboard}/db/001_library.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/002_page_snapshots.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/003_note_descriptions.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/004_flat_notes.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/005_note_editing.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/006_post_public_ids.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/007_dynamic_code_auth.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/008_system_tags.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/009_studio_designs.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/010_studio_design_posts.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/011_post_archiving.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/012_studio_library.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/013_studio_series.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/014_email_accounts.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/015_guidance_documents.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/016_canvas_workspace.sql (100%) rename apps/{waterfall-wiki => dashboard}/db/017_password_reset.sql (100%) rename apps/{waterfall-wiki => dashboard}/next-env.d.ts (100%) rename apps/{waterfall-wiki => dashboard}/next.config.ts (89%) rename apps/{waterfall-wiki => dashboard}/package.json (97%) rename apps/{waterfall-wiki => dashboard}/postcss.config.mjs (100%) rename apps/{waterfall-wiki => dashboard}/public/brand-mark.svg (100%) rename apps/{waterfall-wiki => dashboard}/public/learning-skills/cover.svg (100%) rename apps/{waterfall-wiki => dashboard}/public/llms.txt (100%) rename apps/{waterfall-wiki => dashboard}/scripts/auth-cli.ts (100%) rename apps/{waterfall-wiki => dashboard}/scripts/bootstrap.ts (100%) rename apps/{waterfall-wiki => dashboard}/seed/characters/extraction.json (100%) rename apps/{waterfall-wiki => dashboard}/seed/characters/packs.json (100%) rename apps/{waterfall-wiki => dashboard}/seed/characters/prompts.json (100%) rename apps/{waterfall-wiki => dashboard}/src/components/Brand.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/DocumentDirectory.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/ExploreDetail.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/ExploreFeed.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/Feed.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/GuidanceIndex.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/GuidanceReader.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/GuidanceShell.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/GuidanceUI.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/LandingSketches.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/NoteEditor.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/NoteMarkdown.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/QuickTags.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/Shell.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/SkillCard.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/SkillDocs.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/TagInput.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/components/ThemeToggle.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/content/guides/catalog.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/learning-skills/Series.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/content/learning-skills/cover.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/learning-skills/data.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/managed-media.json (99%) rename apps/{waterfall-wiki => dashboard}/src/content/note-descriptions.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/page-ids.json (100%) rename apps/{waterfall-wiki => dashboard}/src/content/page-links.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/skills/catalog.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/skills/guidance.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/skills/shadow-puppet.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/content/standard-layout.json (100%) rename apps/{waterfall-wiki => dashboard}/src/content/studio-layouts.json (100%) rename apps/{waterfall-wiki => dashboard}/src/content/wiki.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/legacy/WaterfallHome.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/account-crypto.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/account-handler.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/account.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/api-documentation.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/api.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/asset-links.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/auth-store.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/auth.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/canvas-document.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/canvas-store.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/character-image.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/db.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/discovery.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/explore.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/guidance-discovery.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/guidance-input.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/guidance-store.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/health.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/layout-parameters.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/library.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/markdown-response.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/model.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/note-edits.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/post-archive.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/preview.ts (98%) rename apps/{waterfall-wiki => dashboard}/src/lib/probe.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/publish-learning-series.ts (98%) rename apps/{waterfall-wiki => dashboard}/src/lib/series.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/storage.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/street-people.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/studio-design-documentation.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/studio-design-posts.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/studio-designs.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/studio-series.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/system-tags.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/lib/write.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/[section]/[[...taxonomy]].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/_app.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/_document.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/account.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/account/[action].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/agent-index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/auth/login.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/auth/logout.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/canvases/[[...path]].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/documents/[[...path]].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/explore-feed.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/feed.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/guidance/[[...path]].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/guides/[id].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/guides/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/health.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/media/[id].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/notes/[id].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/posts/[id]/archive.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/posts/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/preview.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/ready.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/skill-docs/[skill]/[doc].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/skill-docs/street-people/[doc].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/skills/street-people.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio-ui/export/[slug].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio-ui/health.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio-ui/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio-ui/layouts/[id].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio-ui/layouts/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio/designs/[id].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio/designs/[id]/post.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio/designs/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio/series/[id].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio/series/[id]/export.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/studio/series/index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/tags.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/upload.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/wiki-index.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/wiki-post/[slug].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/api/wiki/[key].ts (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/categories.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/documents/[id].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/documents/index.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/explore/[id].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/graphic/index.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/guide/[id].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/index.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/infinite_canvas/index.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/library/import.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/login.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/p/[slug].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/protocol.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/skills/[[...path]].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/studio/designs/[id].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/studio/ideas/[slug].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/studio/layouts/[layoutId].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/studio/template-layout.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/tools/doodle.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/tools/skills/[skill]/[[...doc]].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/tools/skills/index.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/tools/skills/street-people.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/tools/skills/street-people/[doc].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/wiki/[key].tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/workspace.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/pages/write.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/proxy.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/Brand.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/LayoutCanvas.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/LayoutSelection.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/LayoutWorkbench.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/NativeCurvedCopy.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/NativePreview.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/NativeResult.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/NativeSeries.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/Ref17Cover.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/SourceCover.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/TemplateTrial.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/ThemeToggle.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/canvas/CanvasAgentPanel.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/canvas/CanvasComposer.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/components/canvas/CanvasNodeView.tsx (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/content/ideas.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/content/standard-layout.json (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/content/templates/layouts.json (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/content/templates/ref-17.json (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/content/templates/selection.json (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/content/tool-links.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/api-documentation.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/canvas-document.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/design-server.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/graphic-document.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/layout-images-client.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/layout-images.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/layout-parameters.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/native-layout.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/render-cover.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/lib/series.ts (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/styles/canvas-agent.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/styles/canvas-composer.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/styles/canvas-node.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/styles/globals.css (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/styles/graphic.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/studio/styles/infinite-canvas.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/account.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/brand.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/documents.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/globals.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/guidance.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/landing.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/learning-series.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/skill-docs.css (100%) rename apps/{waterfall-wiki => dashboard}/src/styles/workspace.module.css (100%) rename apps/{waterfall-wiki => dashboard}/src/worker.ts (100%) rename apps/{waterfall-wiki => dashboard}/tsconfig.json (100%) diff --git a/.gitignore b/.gitignore index ef1a4d2..b1429b9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,6 @@ __pycache__/ /apps/studio/public/brand/ /apps/studio/public/favicon.ico /apps/studio/public/apple-touch-icon.png -/apps/waterfall-wiki/public/brand/ -/apps/waterfall-wiki/public/favicon.ico -/apps/waterfall-wiki/public/apple-touch-icon.png +/apps/dashboard/public/brand/ +/apps/dashboard/public/favicon.ico +/apps/dashboard/public/apple-touch-icon.png diff --git a/AGENTS.md b/AGENTS.md index 3265de6..7ca203e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,4 +10,4 @@ This block is written and re-added by `next dev` — verify at `node_modules/nex ## 项目维护 -- Wiki 和 Studio 共用 `apps/waterfall-wiki` 的 Pages Router、登录保护和品牌包。保留已发布的路由、媒体标识和数据库迁移。 +- Wiki 和 Studio 共用 `apps/dashboard` 的 Pages Router、登录保护和品牌包。保留已发布的路由、媒体标识和数据库迁移。 diff --git a/README.md b/README.md index 7ab0530..475e09c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 啡跃 Endoleap -啡跃把笔记、参考素材和创作工作台放在同一个 Next.js 应用里。应用位于 `apps/waterfall-wiki`,共享主题与品牌资源位于 `packages/brand`。 +啡跃把笔记、参考素材和创作工作台放在同一个 Next.js 应用里。应用位于 `apps/dashboard`,共享主题与品牌资源位于 `packages/brand`。 | 入口 | 用途 | | --- | --- | @@ -13,9 +13,9 @@ ## 运行 -准备 Node.js 24、pnpm 11.25.0、PostgreSQL、S3 兼容对象存储和 FFmpeg。参考 [环境变量示例](apps/waterfall-wiki/.env.example),将变量注入 Web、初始化和媒体 worker 进程。邮箱注册与找回密码需要可用的 SMTP 服务。 +准备 Node.js 24、pnpm 11.25.0、PostgreSQL、S3 兼容对象存储和 FFmpeg。参考 [环境变量示例](apps/dashboard/.env.example),将变量注入 Web、初始化和媒体 worker 进程。邮箱注册与找回密码需要可用的 SMTP 服务。 -在仓库根目录运行 `pnpm install --frozen-lockfile` 和 `pnpm build`。然后在 `apps/waterfall-wiki` 目录准备数据库和存储,初始化管理员认证并启动 Web。 +在仓库根目录运行 `pnpm install --frozen-lockfile` 和 `pnpm build`。然后在 `apps/dashboard` 目录准备数据库和存储,初始化管理员认证并启动 Web。 ```sh node dist/bootstrap.cjs @@ -27,9 +27,9 @@ pnpm start ## Monorepo 与 Turborepo -仓库在现有 pnpm workspace 上接入 Turborepo,无需运行面向新项目的 `npx create-turbo@latest`。应用仍位于 `apps/waterfall-wiki`,源码品牌包仍位于 `packages/brand`;子包脚本和应用产物路径保持原样。依赖统一锁定在根目录 `pnpm-lock.yaml`,安装从仓库根目录执行。 +仓库在现有 pnpm workspace 上接入 Turborepo,无需运行面向新项目的 `npx create-turbo@latest`。应用仍位于 `apps/dashboard`,源码品牌包仍位于 `packages/brand`;子包脚本和应用产物路径保持原样。依赖统一锁定在根目录 `pnpm-lock.yaml`,安装从仓库根目录执行。 -根目录命令由 Turbo 调度:`pnpm dev` 启动开发任务,`pnpm dev:waterfall-wiki` 仅启动 Wiki,`pnpm build` 构建应用及 worker/CLI,`pnpm typecheck` 生成 Next.js 类型并检查 TypeScript。`pnpm start` 继续直接启动现有应用。也可以在子包目录使用原有命令。 +根目录命令由 Turbo 调度:`pnpm dev` 启动开发任务,`pnpm dev:dashboard` 仅启动 Wiki,`pnpm build` 构建应用及 worker/CLI,`pnpm typecheck` 生成 Next.js 类型并检查 TypeScript。`pnpm start` 继续直接启动现有应用。也可以在子包目录使用原有命令。 锁文件合并保留应用原有依赖解析。唯一重叠差异是 `@tailwindcss/postcss` 的间接 PostCSS 依赖:品牌包原为 8.5.32,现在沿用应用的 8.5.31;品牌包直接声明的 PostCSS 8.5.6 不变。 diff --git a/apps/waterfall-wiki/.env.example b/apps/dashboard/.env.example similarity index 100% rename from apps/waterfall-wiki/.env.example rename to apps/dashboard/.env.example diff --git a/apps/waterfall-wiki/ACCOUNT.md b/apps/dashboard/ACCOUNT.md similarity index 100% rename from apps/waterfall-wiki/ACCOUNT.md rename to apps/dashboard/ACCOUNT.md diff --git a/apps/waterfall-wiki/db/001_library.sql b/apps/dashboard/db/001_library.sql similarity index 100% rename from apps/waterfall-wiki/db/001_library.sql rename to apps/dashboard/db/001_library.sql diff --git a/apps/waterfall-wiki/db/002_page_snapshots.sql b/apps/dashboard/db/002_page_snapshots.sql similarity index 100% rename from apps/waterfall-wiki/db/002_page_snapshots.sql rename to apps/dashboard/db/002_page_snapshots.sql diff --git a/apps/waterfall-wiki/db/003_note_descriptions.sql b/apps/dashboard/db/003_note_descriptions.sql similarity index 100% rename from apps/waterfall-wiki/db/003_note_descriptions.sql rename to apps/dashboard/db/003_note_descriptions.sql diff --git a/apps/waterfall-wiki/db/004_flat_notes.sql b/apps/dashboard/db/004_flat_notes.sql similarity index 100% rename from apps/waterfall-wiki/db/004_flat_notes.sql rename to apps/dashboard/db/004_flat_notes.sql diff --git a/apps/waterfall-wiki/db/005_note_editing.sql b/apps/dashboard/db/005_note_editing.sql similarity index 100% rename from apps/waterfall-wiki/db/005_note_editing.sql rename to apps/dashboard/db/005_note_editing.sql diff --git a/apps/waterfall-wiki/db/006_post_public_ids.sql b/apps/dashboard/db/006_post_public_ids.sql similarity index 100% rename from apps/waterfall-wiki/db/006_post_public_ids.sql rename to apps/dashboard/db/006_post_public_ids.sql diff --git a/apps/waterfall-wiki/db/007_dynamic_code_auth.sql b/apps/dashboard/db/007_dynamic_code_auth.sql similarity index 100% rename from apps/waterfall-wiki/db/007_dynamic_code_auth.sql rename to apps/dashboard/db/007_dynamic_code_auth.sql diff --git a/apps/waterfall-wiki/db/008_system_tags.sql b/apps/dashboard/db/008_system_tags.sql similarity index 100% rename from apps/waterfall-wiki/db/008_system_tags.sql rename to apps/dashboard/db/008_system_tags.sql diff --git a/apps/waterfall-wiki/db/009_studio_designs.sql b/apps/dashboard/db/009_studio_designs.sql similarity index 100% rename from apps/waterfall-wiki/db/009_studio_designs.sql rename to apps/dashboard/db/009_studio_designs.sql diff --git a/apps/waterfall-wiki/db/010_studio_design_posts.sql b/apps/dashboard/db/010_studio_design_posts.sql similarity index 100% rename from apps/waterfall-wiki/db/010_studio_design_posts.sql rename to apps/dashboard/db/010_studio_design_posts.sql diff --git a/apps/waterfall-wiki/db/011_post_archiving.sql b/apps/dashboard/db/011_post_archiving.sql similarity index 100% rename from apps/waterfall-wiki/db/011_post_archiving.sql rename to apps/dashboard/db/011_post_archiving.sql diff --git a/apps/waterfall-wiki/db/012_studio_library.sql b/apps/dashboard/db/012_studio_library.sql similarity index 100% rename from apps/waterfall-wiki/db/012_studio_library.sql rename to apps/dashboard/db/012_studio_library.sql diff --git a/apps/waterfall-wiki/db/013_studio_series.sql b/apps/dashboard/db/013_studio_series.sql similarity index 100% rename from apps/waterfall-wiki/db/013_studio_series.sql rename to apps/dashboard/db/013_studio_series.sql diff --git a/apps/waterfall-wiki/db/014_email_accounts.sql b/apps/dashboard/db/014_email_accounts.sql similarity index 100% rename from apps/waterfall-wiki/db/014_email_accounts.sql rename to apps/dashboard/db/014_email_accounts.sql diff --git a/apps/waterfall-wiki/db/015_guidance_documents.sql b/apps/dashboard/db/015_guidance_documents.sql similarity index 100% rename from apps/waterfall-wiki/db/015_guidance_documents.sql rename to apps/dashboard/db/015_guidance_documents.sql diff --git a/apps/waterfall-wiki/db/016_canvas_workspace.sql b/apps/dashboard/db/016_canvas_workspace.sql similarity index 100% rename from apps/waterfall-wiki/db/016_canvas_workspace.sql rename to apps/dashboard/db/016_canvas_workspace.sql diff --git a/apps/waterfall-wiki/db/017_password_reset.sql b/apps/dashboard/db/017_password_reset.sql similarity index 100% rename from apps/waterfall-wiki/db/017_password_reset.sql rename to apps/dashboard/db/017_password_reset.sql diff --git a/apps/waterfall-wiki/next-env.d.ts b/apps/dashboard/next-env.d.ts similarity index 100% rename from apps/waterfall-wiki/next-env.d.ts rename to apps/dashboard/next-env.d.ts diff --git a/apps/waterfall-wiki/next.config.ts b/apps/dashboard/next.config.ts similarity index 89% rename from apps/waterfall-wiki/next.config.ts rename to apps/dashboard/next.config.ts index 242011f..0175b93 100644 --- a/apps/waterfall-wiki/next.config.ts +++ b/apps/dashboard/next.config.ts @@ -11,7 +11,7 @@ const nextConfig: NextConfig = { {source:'/studio',destination:'/workspace',permanent:false}, ]}, async rewrites(){return [ - ...Object.entries(managedMedia).filter(([path])=>path.startsWith("apps/waterfall-wiki/public/")).map(([path,asset])=>({source:"/"+path.slice("apps/waterfall-wiki/public/".length),destination:"/api/media/"+asset.id})), + ...Object.entries(managedMedia).filter(([path])=>path.startsWith("apps/dashboard/public/")).map(([path,asset])=>({source:"/"+path.slice("apps/dashboard/public/".length),destination:"/api/media/"+asset.id})), {source:"/studio/api/:path*",destination:"/api/studio-ui/:path*"}, {source:"/studio/brand/:path*",destination:"/brand/:path*"}, {source:"/studio/favicon.ico",destination:"/favicon.ico"}, diff --git a/apps/waterfall-wiki/package.json b/apps/dashboard/package.json similarity index 97% rename from apps/waterfall-wiki/package.json rename to apps/dashboard/package.json index b9c41a6..594d322 100644 --- a/apps/waterfall-wiki/package.json +++ b/apps/dashboard/package.json @@ -1,5 +1,5 @@ { - "name": "@prompt/waterfall-wiki", + "name": "@prompt/dashboard", "version": "0.1.0", "private": true, "engines": { diff --git a/apps/waterfall-wiki/postcss.config.mjs b/apps/dashboard/postcss.config.mjs similarity index 100% rename from apps/waterfall-wiki/postcss.config.mjs rename to apps/dashboard/postcss.config.mjs diff --git a/apps/waterfall-wiki/public/brand-mark.svg b/apps/dashboard/public/brand-mark.svg similarity index 100% rename from apps/waterfall-wiki/public/brand-mark.svg rename to apps/dashboard/public/brand-mark.svg diff --git a/apps/waterfall-wiki/public/learning-skills/cover.svg b/apps/dashboard/public/learning-skills/cover.svg similarity index 100% rename from apps/waterfall-wiki/public/learning-skills/cover.svg rename to apps/dashboard/public/learning-skills/cover.svg diff --git a/apps/waterfall-wiki/public/llms.txt b/apps/dashboard/public/llms.txt similarity index 100% rename from apps/waterfall-wiki/public/llms.txt rename to apps/dashboard/public/llms.txt diff --git a/apps/waterfall-wiki/scripts/auth-cli.ts b/apps/dashboard/scripts/auth-cli.ts similarity index 100% rename from apps/waterfall-wiki/scripts/auth-cli.ts rename to apps/dashboard/scripts/auth-cli.ts diff --git a/apps/waterfall-wiki/scripts/bootstrap.ts b/apps/dashboard/scripts/bootstrap.ts similarity index 100% rename from apps/waterfall-wiki/scripts/bootstrap.ts rename to apps/dashboard/scripts/bootstrap.ts diff --git a/apps/waterfall-wiki/seed/characters/extraction.json b/apps/dashboard/seed/characters/extraction.json similarity index 100% rename from apps/waterfall-wiki/seed/characters/extraction.json rename to apps/dashboard/seed/characters/extraction.json diff --git a/apps/waterfall-wiki/seed/characters/packs.json b/apps/dashboard/seed/characters/packs.json similarity index 100% rename from apps/waterfall-wiki/seed/characters/packs.json rename to apps/dashboard/seed/characters/packs.json diff --git a/apps/waterfall-wiki/seed/characters/prompts.json b/apps/dashboard/seed/characters/prompts.json similarity index 100% rename from apps/waterfall-wiki/seed/characters/prompts.json rename to apps/dashboard/seed/characters/prompts.json diff --git a/apps/waterfall-wiki/src/components/Brand.tsx b/apps/dashboard/src/components/Brand.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/Brand.tsx rename to apps/dashboard/src/components/Brand.tsx diff --git a/apps/waterfall-wiki/src/components/DocumentDirectory.tsx b/apps/dashboard/src/components/DocumentDirectory.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/DocumentDirectory.tsx rename to apps/dashboard/src/components/DocumentDirectory.tsx diff --git a/apps/waterfall-wiki/src/components/ExploreDetail.tsx b/apps/dashboard/src/components/ExploreDetail.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/ExploreDetail.tsx rename to apps/dashboard/src/components/ExploreDetail.tsx diff --git a/apps/waterfall-wiki/src/components/ExploreFeed.tsx b/apps/dashboard/src/components/ExploreFeed.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/ExploreFeed.tsx rename to apps/dashboard/src/components/ExploreFeed.tsx diff --git a/apps/waterfall-wiki/src/components/Feed.tsx b/apps/dashboard/src/components/Feed.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/Feed.tsx rename to apps/dashboard/src/components/Feed.tsx diff --git a/apps/waterfall-wiki/src/components/GuidanceIndex.tsx b/apps/dashboard/src/components/GuidanceIndex.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/GuidanceIndex.tsx rename to apps/dashboard/src/components/GuidanceIndex.tsx diff --git a/apps/waterfall-wiki/src/components/GuidanceReader.tsx b/apps/dashboard/src/components/GuidanceReader.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/GuidanceReader.tsx rename to apps/dashboard/src/components/GuidanceReader.tsx diff --git a/apps/waterfall-wiki/src/components/GuidanceShell.tsx b/apps/dashboard/src/components/GuidanceShell.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/GuidanceShell.tsx rename to apps/dashboard/src/components/GuidanceShell.tsx diff --git a/apps/waterfall-wiki/src/components/GuidanceUI.tsx b/apps/dashboard/src/components/GuidanceUI.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/GuidanceUI.tsx rename to apps/dashboard/src/components/GuidanceUI.tsx diff --git a/apps/waterfall-wiki/src/components/LandingSketches.tsx b/apps/dashboard/src/components/LandingSketches.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/LandingSketches.tsx rename to apps/dashboard/src/components/LandingSketches.tsx diff --git a/apps/waterfall-wiki/src/components/NoteEditor.tsx b/apps/dashboard/src/components/NoteEditor.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/NoteEditor.tsx rename to apps/dashboard/src/components/NoteEditor.tsx diff --git a/apps/waterfall-wiki/src/components/NoteMarkdown.tsx b/apps/dashboard/src/components/NoteMarkdown.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/NoteMarkdown.tsx rename to apps/dashboard/src/components/NoteMarkdown.tsx diff --git a/apps/waterfall-wiki/src/components/QuickTags.tsx b/apps/dashboard/src/components/QuickTags.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/QuickTags.tsx rename to apps/dashboard/src/components/QuickTags.tsx diff --git a/apps/waterfall-wiki/src/components/Shell.tsx b/apps/dashboard/src/components/Shell.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/Shell.tsx rename to apps/dashboard/src/components/Shell.tsx diff --git a/apps/waterfall-wiki/src/components/SkillCard.tsx b/apps/dashboard/src/components/SkillCard.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/SkillCard.tsx rename to apps/dashboard/src/components/SkillCard.tsx diff --git a/apps/waterfall-wiki/src/components/SkillDocs.tsx b/apps/dashboard/src/components/SkillDocs.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/SkillDocs.tsx rename to apps/dashboard/src/components/SkillDocs.tsx diff --git a/apps/waterfall-wiki/src/components/TagInput.tsx b/apps/dashboard/src/components/TagInput.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/TagInput.tsx rename to apps/dashboard/src/components/TagInput.tsx diff --git a/apps/waterfall-wiki/src/components/ThemeToggle.tsx b/apps/dashboard/src/components/ThemeToggle.tsx similarity index 100% rename from apps/waterfall-wiki/src/components/ThemeToggle.tsx rename to apps/dashboard/src/components/ThemeToggle.tsx diff --git a/apps/waterfall-wiki/src/content/guides/catalog.ts b/apps/dashboard/src/content/guides/catalog.ts similarity index 100% rename from apps/waterfall-wiki/src/content/guides/catalog.ts rename to apps/dashboard/src/content/guides/catalog.ts diff --git a/apps/waterfall-wiki/src/content/learning-skills/Series.tsx b/apps/dashboard/src/content/learning-skills/Series.tsx similarity index 100% rename from apps/waterfall-wiki/src/content/learning-skills/Series.tsx rename to apps/dashboard/src/content/learning-skills/Series.tsx diff --git a/apps/waterfall-wiki/src/content/learning-skills/cover.ts b/apps/dashboard/src/content/learning-skills/cover.ts similarity index 100% rename from apps/waterfall-wiki/src/content/learning-skills/cover.ts rename to apps/dashboard/src/content/learning-skills/cover.ts diff --git a/apps/waterfall-wiki/src/content/learning-skills/data.ts b/apps/dashboard/src/content/learning-skills/data.ts similarity index 100% rename from apps/waterfall-wiki/src/content/learning-skills/data.ts rename to apps/dashboard/src/content/learning-skills/data.ts diff --git a/apps/waterfall-wiki/src/content/managed-media.json b/apps/dashboard/src/content/managed-media.json similarity index 99% rename from apps/waterfall-wiki/src/content/managed-media.json rename to apps/dashboard/src/content/managed-media.json index 20bbfc6..ae83043 100644 --- a/apps/waterfall-wiki/src/content/managed-media.json +++ b/apps/dashboard/src/content/managed-media.json @@ -1,5 +1,5 @@ { - "apps/waterfall-wiki/public/learning-skills/cover.png": { + "apps/dashboard/public/learning-skills/cover.png": { "id": "a9cafd80-8207-567b-aaa4-ebd250edaab5", "key": "repository-media/sha256/8049b5704e44b2ebf3b97cef2495a85628af9a16d933ede2f7e790bc69b5fab1.png", "sha256": "8049b5704e44b2ebf3b97cef2495a85628af9a16d933ede2f7e790bc69b5fab1", @@ -8,7 +8,7 @@ "width": 1080, "height": 1440 }, - "apps/waterfall-wiki/public/skills/shadow-puppet/dancer-example.png": { + "apps/dashboard/public/skills/shadow-puppet/dancer-example.png": { "id": "6514a180-bb28-558d-ac9e-0dd49279a058", "key": "repository-media/sha256/94803894dcb06dae84d3744f963f55e077326d34f103a4fce05e32c2f4ab6895.png", "sha256": "94803894dcb06dae84d3744f963f55e077326d34f103a4fce05e32c2f4ab6895", @@ -17,7 +17,7 @@ "width": 680, "height": 900 }, - "apps/waterfall-wiki/public/skills/shadow-puppet/egret-example.png": { + "apps/dashboard/public/skills/shadow-puppet/egret-example.png": { "id": "63aefa65-5431-5a2e-a20c-4293a010410e", "key": "repository-media/sha256/2dd294282bf6c0dda86902cb2be265ba5b89c160a4d5e01ec50f79dac7ac6b82.png", "sha256": "2dd294282bf6c0dda86902cb2be265ba5b89c160a4d5e01ec50f79dac7ac6b82", @@ -26,7 +26,7 @@ "width": 1043, "height": 1380 }, - "apps/waterfall-wiki/public/skills/street-people/reference.png": { + "apps/dashboard/public/skills/street-people/reference.png": { "id": "26cdf219-7264-5b51-b1bd-b13eaedcb946", "key": "repository-media/sha256/e488a52338ac3496e6e51ea4843d99b8ca9cfe3bec4fc8d1ade3985bbb4f4c86.png", "sha256": "e488a52338ac3496e6e51ea4843d99b8ca9cfe3bec4fc8d1ade3985bbb4f4c86", @@ -35,7 +35,7 @@ "width": 1200, "height": 2133 }, - "apps/waterfall-wiki/public/skills/street-people/sample.png": { + "apps/dashboard/public/skills/street-people/sample.png": { "id": "c1d98b87-a103-5cf6-829a-3f46321db72f", "key": "repository-media/sha256/2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88.png", "sha256": "2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88", @@ -44,7 +44,7 @@ "width": 1024, "height": 1536 }, - "apps/waterfall-wiki/seed/characters/original/01-florist.png": { + "apps/dashboard/seed/characters/original/01-florist.png": { "id": "c1d98b87-a103-5cf6-829a-3f46321db72f", "key": "repository-media/sha256/2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88.png", "sha256": "2db53d7593010e6c48dab8f212bc3004b02fc278944ea1686f74d6178a700b88", @@ -53,7 +53,7 @@ "width": 1024, "height": 1536 }, - "apps/waterfall-wiki/seed/characters/original/02-reader.png": { + "apps/dashboard/seed/characters/original/02-reader.png": { "id": "85c30364-9644-5d4a-9e83-96e982860bbe", "key": "repository-media/sha256/669c85e689e7e88f8eff9f4bdaf9b4b7816df88c1956e817d82f8fd39bcdf278.png", "sha256": "669c85e689e7e88f8eff9f4bdaf9b4b7816df88c1956e817d82f8fd39bcdf278", @@ -62,7 +62,7 @@ "width": 1024, "height": 1536 }, - "apps/waterfall-wiki/seed/characters/original/03-cyclist.png": { + "apps/dashboard/seed/characters/original/03-cyclist.png": { "id": "0200f54c-a0a9-50bd-bf04-6512059da0c5", "key": "repository-media/sha256/6dedf97b96fafe9f70a16822b0fe9156a073d9149bad5eeab1450323b67ce575.png", "sha256": "6dedf97b96fafe9f70a16822b0fe9156a073d9149bad5eeab1450323b67ce575", @@ -71,7 +71,7 @@ "width": 1347, "height": 1167 }, - "apps/waterfall-wiki/seed/characters/original/04-coffee.png": { + "apps/dashboard/seed/characters/original/04-coffee.png": { "id": "ebd511bf-559e-588d-886a-095b785216bd", "key": "repository-media/sha256/ab337b3a2e5c05406c03aa4a69018191624f18de70081accf740e79993a1835e.png", "sha256": "ab337b3a2e5c05406c03aa4a69018191624f18de70081accf740e79993a1835e", @@ -80,7 +80,7 @@ "width": 1024, "height": 1536 }, - "apps/waterfall-wiki/seed/characters/reference/01-basket.png": { + "apps/dashboard/seed/characters/reference/01-basket.png": { "id": "48ff5ce0-2763-5fa5-86cb-86c654bf4e24", "key": "repository-media/sha256/62e6785bb373676cb5f24d44b9fa71a8ec8f413a4d518419aa29006ae62b7af8.png", "sha256": "62e6785bb373676cb5f24d44b9fa71a8ec8f413a4d518419aa29006ae62b7af8", @@ -89,7 +89,7 @@ "width": 1106, "height": 1422 }, - "apps/waterfall-wiki/seed/characters/reference/02-phone.png": { + "apps/dashboard/seed/characters/reference/02-phone.png": { "id": "d21179b3-29aa-57c4-bc50-79f2459b5ee8", "key": "repository-media/sha256/f27657c347e73786cc84df290f689f11284bb57822415a651c4c7567c15d034e.png", "sha256": "f27657c347e73786cc84df290f689f11284bb57822415a651c4c7567c15d034e", @@ -98,7 +98,7 @@ "width": 1161, "height": 1354 }, - "apps/waterfall-wiki/seed/characters/reference/03-coffee.png": { + "apps/dashboard/seed/characters/reference/03-coffee.png": { "id": "10e3d787-2575-52f5-8403-cb62072cd1f0", "key": "repository-media/sha256/cd53d3d1771c014cb4c5c1b7ed4d1846f72ec996d48a889514268461ed9c5f7c.png", "sha256": "cd53d3d1771c014cb4c5c1b7ed4d1846f72ec996d48a889514268461ed9c5f7c", @@ -107,7 +107,7 @@ "width": 1191, "height": 1320 }, - "apps/waterfall-wiki/seed/characters/reference/04-red-coat.png": { + "apps/dashboard/seed/characters/reference/04-red-coat.png": { "id": "3fe8aac2-160f-5069-9b67-6615732fe54f", "key": "repository-media/sha256/39249450ccd7f0a207f191742e2c27117f8404d35dfbe092ccdaa54ae59e218d.png", "sha256": "39249450ccd7f0a207f191742e2c27117f8404d35dfbe092ccdaa54ae59e218d", @@ -116,7 +116,7 @@ "width": 1024, "height": 1536 }, - "apps/waterfall-wiki/seed/characters/reference/05-blue-dogs.png": { + "apps/dashboard/seed/characters/reference/05-blue-dogs.png": { "id": "8db94ff7-cee3-526d-a594-282a757317cf", "key": "repository-media/sha256/d6c7ce1c8e3375f8aaa40012a91d11925113a7776b0fe383c74c734d91edfc37.png", "sha256": "d6c7ce1c8e3375f8aaa40012a91d11925113a7776b0fe383c74c734d91edfc37", @@ -125,7 +125,7 @@ "width": 1536, "height": 1024 }, - "apps/waterfall-wiki/seed/characters/reference/06-dark-dog.png": { + "apps/dashboard/seed/characters/reference/06-dark-dog.png": { "id": "a3a00127-0f67-5c8f-9ae9-259e9c390be0", "key": "repository-media/sha256/9e19ac9d04c30e5d0a552c7d59a8365ffca8be5df1b7f9ec12fb35bef7aa444b.png", "sha256": "9e19ac9d04c30e5d0a552c7d59a8365ffca8be5df1b7f9ec12fb35bef7aa444b", @@ -134,7 +134,7 @@ "width": 1372, "height": 1146 }, - "apps/waterfall-wiki/seed/characters/reference/07-pockets.png": { + "apps/dashboard/seed/characters/reference/07-pockets.png": { "id": "468c8637-c841-5819-8afb-24d246b77821", "key": "repository-media/sha256/158c1f3a492a7a3223bf4ed56d3750d192c696a054dc66d2c3ae65ce9e0a8dad.png", "sha256": "158c1f3a492a7a3223bf4ed56d3750d192c696a054dc66d2c3ae65ce9e0a8dad", @@ -143,7 +143,7 @@ "width": 1024, "height": 1536 }, - "apps/waterfall-wiki/seed/characters/reference/08-blue-board.png": { + "apps/dashboard/seed/characters/reference/08-blue-board.png": { "id": "1731746a-2a59-5b8b-b91c-8f862567f7ef", "key": "repository-media/sha256/fa8f0dd989cb8133ad5565c19dc9f798a9c2bd216e3c90342d1a4e45329e8cd9.png", "sha256": "fa8f0dd989cb8133ad5565c19dc9f798a9c2bd216e3c90342d1a4e45329e8cd9", @@ -152,7 +152,7 @@ "width": 1130, "height": 1392 }, - "apps/waterfall-wiki/seed/characters/reference/09-scarf-dog.png": { + "apps/dashboard/seed/characters/reference/09-scarf-dog.png": { "id": "7e99a693-1c95-5544-af4c-ae2ce12824c8", "key": "repository-media/sha256/8ee2ea052e4985a28eef1ba415190bdbea70e4ca52eb3bdcd5206210a4285bc7.png", "sha256": "8ee2ea052e4985a28eef1ba415190bdbea70e4ca52eb3bdcd5206210a4285bc7", @@ -161,7 +161,7 @@ "width": 1220, "height": 1289 }, - "apps/waterfall-wiki/seed/characters/reference/10-elder.png": { + "apps/dashboard/seed/characters/reference/10-elder.png": { "id": "be125dda-5a86-5a7b-9498-a8dba66df31e", "key": "repository-media/sha256/45bfe1e35f88e52adfc7f97b0834a5dbeea7dbcf283ebdabb9adf4236eb93eab.png", "sha256": "45bfe1e35f88e52adfc7f97b0834a5dbeea7dbcf283ebdabb9adf4236eb93eab", @@ -170,7 +170,7 @@ "width": 1195, "height": 1316 }, - "apps/waterfall-wiki/seed/characters/reference/11-long-coat.png": { + "apps/dashboard/seed/characters/reference/11-long-coat.png": { "id": "5bb056a0-735e-5f24-bfd1-1036ebb208e8", "key": "repository-media/sha256/76f00ba08e5b8e7caf4fe2801368cfc5f7cf3c445ae2a80b496a4f431272d053.png", "sha256": "76f00ba08e5b8e7caf4fe2801368cfc5f7cf3c445ae2a80b496a4f431272d053", @@ -179,7 +179,7 @@ "width": 1148, "height": 1371 }, - "apps/waterfall-wiki/seed/characters/reference/12-trolley.png": { + "apps/dashboard/seed/characters/reference/12-trolley.png": { "id": "11da89bb-daa9-59b3-b22f-997f1fba46fe", "key": "repository-media/sha256/8235f95071abb54f0618b61d66c20e63633e6d929a8ec4d43d4cef121226e078.png", "sha256": "8235f95071abb54f0618b61d66c20e63633e6d929a8ec4d43d4cef121226e078", @@ -188,7 +188,7 @@ "width": 1241, "height": 1267 }, - "apps/waterfall-wiki/seed/characters/reference/13-bucket.png": { + "apps/dashboard/seed/characters/reference/13-bucket.png": { "id": "3f203043-3d69-52c4-91fa-c898daf61eb6", "key": "repository-media/sha256/2a4b34afdb45d439f2588095abc89378f7772fd842bdc38518e4a03756b15b82.png", "sha256": "2a4b34afdb45d439f2588095abc89378f7772fd842bdc38518e4a03756b15b82", @@ -197,7 +197,7 @@ "width": 1140, "height": 1380 }, - "apps/waterfall-wiki/seed/characters/reference/14-curly.png": { + "apps/dashboard/seed/characters/reference/14-curly.png": { "id": "5cfb45ab-542f-5bd8-89f5-549072e4caf3", "key": "repository-media/sha256/f10faf8145e56830c9a179b6a52cfdd688de3f460fa33ded82fa2b3fe0678b3e.png", "sha256": "f10faf8145e56830c9a179b6a52cfdd688de3f460fa33ded82fa2b3fe0678b3e", @@ -206,7 +206,7 @@ "width": 1141, "height": 1379 }, - "apps/waterfall-wiki/seed/characters/reference/15-red-walk.png": { + "apps/dashboard/seed/characters/reference/15-red-walk.png": { "id": "41e92c94-adca-54b4-9fe9-db192a266937", "key": "repository-media/sha256/238eae73ac2faf53e6ac02591b00759115378233fbcbfdcf8e67ce93a804d26e.png", "sha256": "238eae73ac2faf53e6ac02591b00759115378233fbcbfdcf8e67ce93a804d26e", diff --git a/apps/waterfall-wiki/src/content/note-descriptions.ts b/apps/dashboard/src/content/note-descriptions.ts similarity index 100% rename from apps/waterfall-wiki/src/content/note-descriptions.ts rename to apps/dashboard/src/content/note-descriptions.ts diff --git a/apps/waterfall-wiki/src/content/page-ids.json b/apps/dashboard/src/content/page-ids.json similarity index 100% rename from apps/waterfall-wiki/src/content/page-ids.json rename to apps/dashboard/src/content/page-ids.json diff --git a/apps/waterfall-wiki/src/content/page-links.ts b/apps/dashboard/src/content/page-links.ts similarity index 100% rename from apps/waterfall-wiki/src/content/page-links.ts rename to apps/dashboard/src/content/page-links.ts diff --git a/apps/waterfall-wiki/src/content/skills/catalog.ts b/apps/dashboard/src/content/skills/catalog.ts similarity index 100% rename from apps/waterfall-wiki/src/content/skills/catalog.ts rename to apps/dashboard/src/content/skills/catalog.ts diff --git a/apps/waterfall-wiki/src/content/skills/guidance.ts b/apps/dashboard/src/content/skills/guidance.ts similarity index 100% rename from apps/waterfall-wiki/src/content/skills/guidance.ts rename to apps/dashboard/src/content/skills/guidance.ts diff --git a/apps/waterfall-wiki/src/content/skills/shadow-puppet.ts b/apps/dashboard/src/content/skills/shadow-puppet.ts similarity index 100% rename from apps/waterfall-wiki/src/content/skills/shadow-puppet.ts rename to apps/dashboard/src/content/skills/shadow-puppet.ts diff --git a/apps/waterfall-wiki/src/content/standard-layout.json b/apps/dashboard/src/content/standard-layout.json similarity index 100% rename from apps/waterfall-wiki/src/content/standard-layout.json rename to apps/dashboard/src/content/standard-layout.json diff --git a/apps/waterfall-wiki/src/content/studio-layouts.json b/apps/dashboard/src/content/studio-layouts.json similarity index 100% rename from apps/waterfall-wiki/src/content/studio-layouts.json rename to apps/dashboard/src/content/studio-layouts.json diff --git a/apps/waterfall-wiki/src/content/wiki.ts b/apps/dashboard/src/content/wiki.ts similarity index 100% rename from apps/waterfall-wiki/src/content/wiki.ts rename to apps/dashboard/src/content/wiki.ts diff --git a/apps/waterfall-wiki/src/legacy/WaterfallHome.tsx b/apps/dashboard/src/legacy/WaterfallHome.tsx similarity index 100% rename from apps/waterfall-wiki/src/legacy/WaterfallHome.tsx rename to apps/dashboard/src/legacy/WaterfallHome.tsx diff --git a/apps/waterfall-wiki/src/lib/account-crypto.ts b/apps/dashboard/src/lib/account-crypto.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/account-crypto.ts rename to apps/dashboard/src/lib/account-crypto.ts diff --git a/apps/waterfall-wiki/src/lib/account-handler.ts b/apps/dashboard/src/lib/account-handler.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/account-handler.ts rename to apps/dashboard/src/lib/account-handler.ts diff --git a/apps/waterfall-wiki/src/lib/account.ts b/apps/dashboard/src/lib/account.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/account.ts rename to apps/dashboard/src/lib/account.ts diff --git a/apps/waterfall-wiki/src/lib/api-documentation.ts b/apps/dashboard/src/lib/api-documentation.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/api-documentation.ts rename to apps/dashboard/src/lib/api-documentation.ts diff --git a/apps/waterfall-wiki/src/lib/api.ts b/apps/dashboard/src/lib/api.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/api.ts rename to apps/dashboard/src/lib/api.ts diff --git a/apps/waterfall-wiki/src/lib/asset-links.ts b/apps/dashboard/src/lib/asset-links.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/asset-links.ts rename to apps/dashboard/src/lib/asset-links.ts diff --git a/apps/waterfall-wiki/src/lib/auth-store.ts b/apps/dashboard/src/lib/auth-store.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/auth-store.ts rename to apps/dashboard/src/lib/auth-store.ts diff --git a/apps/waterfall-wiki/src/lib/auth.ts b/apps/dashboard/src/lib/auth.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/auth.ts rename to apps/dashboard/src/lib/auth.ts diff --git a/apps/waterfall-wiki/src/lib/canvas-document.ts b/apps/dashboard/src/lib/canvas-document.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/canvas-document.ts rename to apps/dashboard/src/lib/canvas-document.ts diff --git a/apps/waterfall-wiki/src/lib/canvas-store.ts b/apps/dashboard/src/lib/canvas-store.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/canvas-store.ts rename to apps/dashboard/src/lib/canvas-store.ts diff --git a/apps/waterfall-wiki/src/lib/character-image.ts b/apps/dashboard/src/lib/character-image.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/character-image.ts rename to apps/dashboard/src/lib/character-image.ts diff --git a/apps/waterfall-wiki/src/lib/db.ts b/apps/dashboard/src/lib/db.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/db.ts rename to apps/dashboard/src/lib/db.ts diff --git a/apps/waterfall-wiki/src/lib/discovery.ts b/apps/dashboard/src/lib/discovery.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/discovery.ts rename to apps/dashboard/src/lib/discovery.ts diff --git a/apps/waterfall-wiki/src/lib/explore.ts b/apps/dashboard/src/lib/explore.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/explore.ts rename to apps/dashboard/src/lib/explore.ts diff --git a/apps/waterfall-wiki/src/lib/guidance-discovery.ts b/apps/dashboard/src/lib/guidance-discovery.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/guidance-discovery.ts rename to apps/dashboard/src/lib/guidance-discovery.ts diff --git a/apps/waterfall-wiki/src/lib/guidance-input.ts b/apps/dashboard/src/lib/guidance-input.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/guidance-input.ts rename to apps/dashboard/src/lib/guidance-input.ts diff --git a/apps/waterfall-wiki/src/lib/guidance-store.ts b/apps/dashboard/src/lib/guidance-store.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/guidance-store.ts rename to apps/dashboard/src/lib/guidance-store.ts diff --git a/apps/waterfall-wiki/src/lib/health.ts b/apps/dashboard/src/lib/health.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/health.ts rename to apps/dashboard/src/lib/health.ts diff --git a/apps/waterfall-wiki/src/lib/layout-parameters.ts b/apps/dashboard/src/lib/layout-parameters.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/layout-parameters.ts rename to apps/dashboard/src/lib/layout-parameters.ts diff --git a/apps/waterfall-wiki/src/lib/library.ts b/apps/dashboard/src/lib/library.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/library.ts rename to apps/dashboard/src/lib/library.ts diff --git a/apps/waterfall-wiki/src/lib/markdown-response.ts b/apps/dashboard/src/lib/markdown-response.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/markdown-response.ts rename to apps/dashboard/src/lib/markdown-response.ts diff --git a/apps/waterfall-wiki/src/lib/model.ts b/apps/dashboard/src/lib/model.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/model.ts rename to apps/dashboard/src/lib/model.ts diff --git a/apps/waterfall-wiki/src/lib/note-edits.ts b/apps/dashboard/src/lib/note-edits.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/note-edits.ts rename to apps/dashboard/src/lib/note-edits.ts diff --git a/apps/waterfall-wiki/src/lib/post-archive.ts b/apps/dashboard/src/lib/post-archive.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/post-archive.ts rename to apps/dashboard/src/lib/post-archive.ts diff --git a/apps/waterfall-wiki/src/lib/preview.ts b/apps/dashboard/src/lib/preview.ts similarity index 98% rename from apps/waterfall-wiki/src/lib/preview.ts rename to apps/dashboard/src/lib/preview.ts index dbc1539..1cdb8a0 100644 --- a/apps/waterfall-wiki/src/lib/preview.ts +++ b/apps/dashboard/src/lib/preview.ts @@ -14,6 +14,6 @@ const pending=new Map>();const cache=join(tmpdir(),'promp export async function compressPreview(input:Buffer,width:number){const max=width<=480?100_000:220_000;let size=width,quality=72;let output:Buffer;do{output=await sharp(input,{limitInputPixels:40_000_000,animated:false}).rotate().resize({width:size,withoutEnlargement:true}).webp({quality,effort:3}).toBuffer();if(output.length<=max)return output;size=Math.max(240,Math.floor(size*.8));quality=Math.max(30,quality-10);}while(size>240||quality>30);return sharp(input,{limitInputPixels:40_000_000}).rotate().resize({width:240,withoutEnlargement:true}).webp({quality:30}).toBuffer();} export async function cachedPreview(identity:string,width:number,load:()=>Promise,quality=false){const key=createHash('sha256').update(identity+':'+width+(quality?':export-v1':':webp-v1')).digest('hex');const file=join(cache,key+'.webp');try{return {bytes:await readFile(file),etag:'"'+key+'"'};}catch{/* cache miss */}let task=pending.get(key);if(!task){task=limited(async()=>{await mkdir(cache,{recursive:true});const input=await load();const bytes=quality?await sharp(input,{limitInputPixels:40_000_000,animated:false}).rotate().resize({width,withoutEnlargement:true}).webp({quality:95,effort:4}).toBuffer():await compressPreview(input,width);const temp=file+'.'+process.pid+'.tmp';await writeFile(temp,bytes);await rename(temp,file);if(++writes%32===0){const files=await readdir(cache);if(files.length>1024){const dates=await Promise.all(files.map(async name=>({name,time:(await stat(join(cache,name)).catch(()=>({mtimeMs:0}))).mtimeMs})));await Promise.all(dates.sort((a,b)=>a.time-b.time).slice(0,files.length-1024).map(f=>unlink(join(cache,f.name)).catch(()=>{})));}}return bytes;});pending.set(key,task);}try{return {bytes:await task,etag:'"'+key+'"'};}finally{pending.delete(key);}} export async function loadStaticPreview(source:string){ - const asset=(managedMedia as Record)["apps/waterfall-wiki/public"+source]; + const asset=(managedMedia as Record)["apps/dashboard/public"+source]; if(asset){return {identity:asset.key,load:async()=>{const object=await getObject(asset.key);if(asset.bytes>64*1024*1024)throw Error("Image too large");const bytes=Buffer.from(await object.Body!.transformToByteArray());if(bytes.length!==asset.bytes||createHash("sha256").update(bytes).digest("hex")!==asset.sha256)throw Error("Stored image checksum mismatch");return bytes;}};} const root=await realpath(join(process.cwd(),'public'));const file=await realpath(staticPreviewPath(source,root));if(!file.startsWith(root+sep))throw Error('Invalid source');const info=await stat(file);if(info.size>64*1024*1024)throw Error('Image too large');return {identity:file+':'+info.mtimeMs,load:()=>readFile(file)};} diff --git a/apps/waterfall-wiki/src/lib/probe.ts b/apps/dashboard/src/lib/probe.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/probe.ts rename to apps/dashboard/src/lib/probe.ts diff --git a/apps/waterfall-wiki/src/lib/publish-learning-series.ts b/apps/dashboard/src/lib/publish-learning-series.ts similarity index 98% rename from apps/waterfall-wiki/src/lib/publish-learning-series.ts rename to apps/dashboard/src/lib/publish-learning-series.ts index a8ee4f0..591679f 100644 --- a/apps/waterfall-wiki/src/lib/publish-learning-series.ts +++ b/apps/dashboard/src/lib/publish-learning-series.ts @@ -13,7 +13,7 @@ function stableId(name: string) { } // Explicit editorial publication, never part of the generic storage bootstrap. // Re-running updates these three authored posts without duplicating posts or media. -export async function publishLearningSeries(asset=managedMedia["apps/waterfall-wiki/public/learning-skills/cover.png"]) { +export async function publishLearningSeries(asset=managedMedia["apps/dashboard/public/learning-skills/cover.png"]) { const object=await getObject(asset.key); const cover=Buffer.from(await object.Body!.transformToByteArray()); const digest=createHash("sha256").update(cover).digest("hex"); diff --git a/apps/waterfall-wiki/src/lib/series.ts b/apps/dashboard/src/lib/series.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/series.ts rename to apps/dashboard/src/lib/series.ts diff --git a/apps/waterfall-wiki/src/lib/storage.ts b/apps/dashboard/src/lib/storage.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/storage.ts rename to apps/dashboard/src/lib/storage.ts diff --git a/apps/waterfall-wiki/src/lib/street-people.ts b/apps/dashboard/src/lib/street-people.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/street-people.ts rename to apps/dashboard/src/lib/street-people.ts diff --git a/apps/waterfall-wiki/src/lib/studio-design-documentation.ts b/apps/dashboard/src/lib/studio-design-documentation.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/studio-design-documentation.ts rename to apps/dashboard/src/lib/studio-design-documentation.ts diff --git a/apps/waterfall-wiki/src/lib/studio-design-posts.ts b/apps/dashboard/src/lib/studio-design-posts.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/studio-design-posts.ts rename to apps/dashboard/src/lib/studio-design-posts.ts diff --git a/apps/waterfall-wiki/src/lib/studio-designs.ts b/apps/dashboard/src/lib/studio-designs.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/studio-designs.ts rename to apps/dashboard/src/lib/studio-designs.ts diff --git a/apps/waterfall-wiki/src/lib/studio-series.ts b/apps/dashboard/src/lib/studio-series.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/studio-series.ts rename to apps/dashboard/src/lib/studio-series.ts diff --git a/apps/waterfall-wiki/src/lib/system-tags.ts b/apps/dashboard/src/lib/system-tags.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/system-tags.ts rename to apps/dashboard/src/lib/system-tags.ts diff --git a/apps/waterfall-wiki/src/lib/write.ts b/apps/dashboard/src/lib/write.ts similarity index 100% rename from apps/waterfall-wiki/src/lib/write.ts rename to apps/dashboard/src/lib/write.ts diff --git a/apps/waterfall-wiki/src/pages/[section]/[[...taxonomy]].tsx b/apps/dashboard/src/pages/[section]/[[...taxonomy]].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/[section]/[[...taxonomy]].tsx rename to apps/dashboard/src/pages/[section]/[[...taxonomy]].tsx diff --git a/apps/waterfall-wiki/src/pages/_app.tsx b/apps/dashboard/src/pages/_app.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/_app.tsx rename to apps/dashboard/src/pages/_app.tsx diff --git a/apps/waterfall-wiki/src/pages/_document.tsx b/apps/dashboard/src/pages/_document.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/_document.tsx rename to apps/dashboard/src/pages/_document.tsx diff --git a/apps/waterfall-wiki/src/pages/account.tsx b/apps/dashboard/src/pages/account.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/account.tsx rename to apps/dashboard/src/pages/account.tsx diff --git a/apps/waterfall-wiki/src/pages/api/account/[action].ts b/apps/dashboard/src/pages/api/account/[action].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/account/[action].ts rename to apps/dashboard/src/pages/api/account/[action].ts diff --git a/apps/waterfall-wiki/src/pages/api/agent-index.ts b/apps/dashboard/src/pages/api/agent-index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/agent-index.ts rename to apps/dashboard/src/pages/api/agent-index.ts diff --git a/apps/waterfall-wiki/src/pages/api/auth/login.ts b/apps/dashboard/src/pages/api/auth/login.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/auth/login.ts rename to apps/dashboard/src/pages/api/auth/login.ts diff --git a/apps/waterfall-wiki/src/pages/api/auth/logout.ts b/apps/dashboard/src/pages/api/auth/logout.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/auth/logout.ts rename to apps/dashboard/src/pages/api/auth/logout.ts diff --git a/apps/waterfall-wiki/src/pages/api/canvases/[[...path]].ts b/apps/dashboard/src/pages/api/canvases/[[...path]].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/canvases/[[...path]].ts rename to apps/dashboard/src/pages/api/canvases/[[...path]].ts diff --git a/apps/waterfall-wiki/src/pages/api/documents/[[...path]].ts b/apps/dashboard/src/pages/api/documents/[[...path]].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/documents/[[...path]].ts rename to apps/dashboard/src/pages/api/documents/[[...path]].ts diff --git a/apps/waterfall-wiki/src/pages/api/explore-feed.ts b/apps/dashboard/src/pages/api/explore-feed.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/explore-feed.ts rename to apps/dashboard/src/pages/api/explore-feed.ts diff --git a/apps/waterfall-wiki/src/pages/api/feed.ts b/apps/dashboard/src/pages/api/feed.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/feed.ts rename to apps/dashboard/src/pages/api/feed.ts diff --git a/apps/waterfall-wiki/src/pages/api/guidance/[[...path]].ts b/apps/dashboard/src/pages/api/guidance/[[...path]].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/guidance/[[...path]].ts rename to apps/dashboard/src/pages/api/guidance/[[...path]].ts diff --git a/apps/waterfall-wiki/src/pages/api/guides/[id].ts b/apps/dashboard/src/pages/api/guides/[id].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/guides/[id].ts rename to apps/dashboard/src/pages/api/guides/[id].ts diff --git a/apps/waterfall-wiki/src/pages/api/guides/index.ts b/apps/dashboard/src/pages/api/guides/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/guides/index.ts rename to apps/dashboard/src/pages/api/guides/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/health.ts b/apps/dashboard/src/pages/api/health.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/health.ts rename to apps/dashboard/src/pages/api/health.ts diff --git a/apps/waterfall-wiki/src/pages/api/index.ts b/apps/dashboard/src/pages/api/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/index.ts rename to apps/dashboard/src/pages/api/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/media/[id].ts b/apps/dashboard/src/pages/api/media/[id].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/media/[id].ts rename to apps/dashboard/src/pages/api/media/[id].ts diff --git a/apps/waterfall-wiki/src/pages/api/notes/[id].ts b/apps/dashboard/src/pages/api/notes/[id].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/notes/[id].ts rename to apps/dashboard/src/pages/api/notes/[id].ts diff --git a/apps/waterfall-wiki/src/pages/api/posts/[id]/archive.ts b/apps/dashboard/src/pages/api/posts/[id]/archive.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/posts/[id]/archive.ts rename to apps/dashboard/src/pages/api/posts/[id]/archive.ts diff --git a/apps/waterfall-wiki/src/pages/api/posts/index.ts b/apps/dashboard/src/pages/api/posts/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/posts/index.ts rename to apps/dashboard/src/pages/api/posts/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/preview.ts b/apps/dashboard/src/pages/api/preview.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/preview.ts rename to apps/dashboard/src/pages/api/preview.ts diff --git a/apps/waterfall-wiki/src/pages/api/ready.ts b/apps/dashboard/src/pages/api/ready.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/ready.ts rename to apps/dashboard/src/pages/api/ready.ts diff --git a/apps/waterfall-wiki/src/pages/api/skill-docs/[skill]/[doc].ts b/apps/dashboard/src/pages/api/skill-docs/[skill]/[doc].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/skill-docs/[skill]/[doc].ts rename to apps/dashboard/src/pages/api/skill-docs/[skill]/[doc].ts diff --git a/apps/waterfall-wiki/src/pages/api/skill-docs/street-people/[doc].ts b/apps/dashboard/src/pages/api/skill-docs/street-people/[doc].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/skill-docs/street-people/[doc].ts rename to apps/dashboard/src/pages/api/skill-docs/street-people/[doc].ts diff --git a/apps/waterfall-wiki/src/pages/api/skills/street-people.ts b/apps/dashboard/src/pages/api/skills/street-people.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/skills/street-people.ts rename to apps/dashboard/src/pages/api/skills/street-people.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio-ui/export/[slug].ts b/apps/dashboard/src/pages/api/studio-ui/export/[slug].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio-ui/export/[slug].ts rename to apps/dashboard/src/pages/api/studio-ui/export/[slug].ts diff --git a/apps/waterfall-wiki/src/pages/api/studio-ui/health.ts b/apps/dashboard/src/pages/api/studio-ui/health.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio-ui/health.ts rename to apps/dashboard/src/pages/api/studio-ui/health.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio-ui/index.ts b/apps/dashboard/src/pages/api/studio-ui/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio-ui/index.ts rename to apps/dashboard/src/pages/api/studio-ui/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio-ui/layouts/[id].ts b/apps/dashboard/src/pages/api/studio-ui/layouts/[id].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio-ui/layouts/[id].ts rename to apps/dashboard/src/pages/api/studio-ui/layouts/[id].ts diff --git a/apps/waterfall-wiki/src/pages/api/studio-ui/layouts/index.ts b/apps/dashboard/src/pages/api/studio-ui/layouts/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio-ui/layouts/index.ts rename to apps/dashboard/src/pages/api/studio-ui/layouts/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio/designs/[id].ts b/apps/dashboard/src/pages/api/studio/designs/[id].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio/designs/[id].ts rename to apps/dashboard/src/pages/api/studio/designs/[id].ts diff --git a/apps/waterfall-wiki/src/pages/api/studio/designs/[id]/post.ts b/apps/dashboard/src/pages/api/studio/designs/[id]/post.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio/designs/[id]/post.ts rename to apps/dashboard/src/pages/api/studio/designs/[id]/post.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio/designs/index.ts b/apps/dashboard/src/pages/api/studio/designs/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio/designs/index.ts rename to apps/dashboard/src/pages/api/studio/designs/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio/series/[id].ts b/apps/dashboard/src/pages/api/studio/series/[id].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio/series/[id].ts rename to apps/dashboard/src/pages/api/studio/series/[id].ts diff --git a/apps/waterfall-wiki/src/pages/api/studio/series/[id]/export.ts b/apps/dashboard/src/pages/api/studio/series/[id]/export.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio/series/[id]/export.ts rename to apps/dashboard/src/pages/api/studio/series/[id]/export.ts diff --git a/apps/waterfall-wiki/src/pages/api/studio/series/index.ts b/apps/dashboard/src/pages/api/studio/series/index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/studio/series/index.ts rename to apps/dashboard/src/pages/api/studio/series/index.ts diff --git a/apps/waterfall-wiki/src/pages/api/tags.ts b/apps/dashboard/src/pages/api/tags.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/tags.ts rename to apps/dashboard/src/pages/api/tags.ts diff --git a/apps/waterfall-wiki/src/pages/api/upload.ts b/apps/dashboard/src/pages/api/upload.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/upload.ts rename to apps/dashboard/src/pages/api/upload.ts diff --git a/apps/waterfall-wiki/src/pages/api/wiki-index.ts b/apps/dashboard/src/pages/api/wiki-index.ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/wiki-index.ts rename to apps/dashboard/src/pages/api/wiki-index.ts diff --git a/apps/waterfall-wiki/src/pages/api/wiki-post/[slug].ts b/apps/dashboard/src/pages/api/wiki-post/[slug].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/wiki-post/[slug].ts rename to apps/dashboard/src/pages/api/wiki-post/[slug].ts diff --git a/apps/waterfall-wiki/src/pages/api/wiki/[key].ts b/apps/dashboard/src/pages/api/wiki/[key].ts similarity index 100% rename from apps/waterfall-wiki/src/pages/api/wiki/[key].ts rename to apps/dashboard/src/pages/api/wiki/[key].ts diff --git a/apps/waterfall-wiki/src/pages/categories.tsx b/apps/dashboard/src/pages/categories.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/categories.tsx rename to apps/dashboard/src/pages/categories.tsx diff --git a/apps/waterfall-wiki/src/pages/documents/[id].tsx b/apps/dashboard/src/pages/documents/[id].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/documents/[id].tsx rename to apps/dashboard/src/pages/documents/[id].tsx diff --git a/apps/waterfall-wiki/src/pages/documents/index.tsx b/apps/dashboard/src/pages/documents/index.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/documents/index.tsx rename to apps/dashboard/src/pages/documents/index.tsx diff --git a/apps/waterfall-wiki/src/pages/explore/[id].tsx b/apps/dashboard/src/pages/explore/[id].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/explore/[id].tsx rename to apps/dashboard/src/pages/explore/[id].tsx diff --git a/apps/waterfall-wiki/src/pages/graphic/index.tsx b/apps/dashboard/src/pages/graphic/index.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/graphic/index.tsx rename to apps/dashboard/src/pages/graphic/index.tsx diff --git a/apps/waterfall-wiki/src/pages/guide/[id].tsx b/apps/dashboard/src/pages/guide/[id].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/guide/[id].tsx rename to apps/dashboard/src/pages/guide/[id].tsx diff --git a/apps/waterfall-wiki/src/pages/index.tsx b/apps/dashboard/src/pages/index.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/index.tsx rename to apps/dashboard/src/pages/index.tsx diff --git a/apps/waterfall-wiki/src/pages/infinite_canvas/index.tsx b/apps/dashboard/src/pages/infinite_canvas/index.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/infinite_canvas/index.tsx rename to apps/dashboard/src/pages/infinite_canvas/index.tsx diff --git a/apps/waterfall-wiki/src/pages/library/import.tsx b/apps/dashboard/src/pages/library/import.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/library/import.tsx rename to apps/dashboard/src/pages/library/import.tsx diff --git a/apps/waterfall-wiki/src/pages/login.tsx b/apps/dashboard/src/pages/login.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/login.tsx rename to apps/dashboard/src/pages/login.tsx diff --git a/apps/waterfall-wiki/src/pages/p/[slug].tsx b/apps/dashboard/src/pages/p/[slug].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/p/[slug].tsx rename to apps/dashboard/src/pages/p/[slug].tsx diff --git a/apps/waterfall-wiki/src/pages/protocol.tsx b/apps/dashboard/src/pages/protocol.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/protocol.tsx rename to apps/dashboard/src/pages/protocol.tsx diff --git a/apps/waterfall-wiki/src/pages/skills/[[...path]].tsx b/apps/dashboard/src/pages/skills/[[...path]].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/skills/[[...path]].tsx rename to apps/dashboard/src/pages/skills/[[...path]].tsx diff --git a/apps/waterfall-wiki/src/pages/studio/designs/[id].tsx b/apps/dashboard/src/pages/studio/designs/[id].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/studio/designs/[id].tsx rename to apps/dashboard/src/pages/studio/designs/[id].tsx diff --git a/apps/waterfall-wiki/src/pages/studio/ideas/[slug].tsx b/apps/dashboard/src/pages/studio/ideas/[slug].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/studio/ideas/[slug].tsx rename to apps/dashboard/src/pages/studio/ideas/[slug].tsx diff --git a/apps/waterfall-wiki/src/pages/studio/layouts/[layoutId].tsx b/apps/dashboard/src/pages/studio/layouts/[layoutId].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/studio/layouts/[layoutId].tsx rename to apps/dashboard/src/pages/studio/layouts/[layoutId].tsx diff --git a/apps/waterfall-wiki/src/pages/studio/template-layout.tsx b/apps/dashboard/src/pages/studio/template-layout.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/studio/template-layout.tsx rename to apps/dashboard/src/pages/studio/template-layout.tsx diff --git a/apps/waterfall-wiki/src/pages/tools/doodle.tsx b/apps/dashboard/src/pages/tools/doodle.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/tools/doodle.tsx rename to apps/dashboard/src/pages/tools/doodle.tsx diff --git a/apps/waterfall-wiki/src/pages/tools/skills/[skill]/[[...doc]].tsx b/apps/dashboard/src/pages/tools/skills/[skill]/[[...doc]].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/tools/skills/[skill]/[[...doc]].tsx rename to apps/dashboard/src/pages/tools/skills/[skill]/[[...doc]].tsx diff --git a/apps/waterfall-wiki/src/pages/tools/skills/index.tsx b/apps/dashboard/src/pages/tools/skills/index.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/tools/skills/index.tsx rename to apps/dashboard/src/pages/tools/skills/index.tsx diff --git a/apps/waterfall-wiki/src/pages/tools/skills/street-people.tsx b/apps/dashboard/src/pages/tools/skills/street-people.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/tools/skills/street-people.tsx rename to apps/dashboard/src/pages/tools/skills/street-people.tsx diff --git a/apps/waterfall-wiki/src/pages/tools/skills/street-people/[doc].tsx b/apps/dashboard/src/pages/tools/skills/street-people/[doc].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/tools/skills/street-people/[doc].tsx rename to apps/dashboard/src/pages/tools/skills/street-people/[doc].tsx diff --git a/apps/waterfall-wiki/src/pages/wiki/[key].tsx b/apps/dashboard/src/pages/wiki/[key].tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/wiki/[key].tsx rename to apps/dashboard/src/pages/wiki/[key].tsx diff --git a/apps/waterfall-wiki/src/pages/workspace.tsx b/apps/dashboard/src/pages/workspace.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/workspace.tsx rename to apps/dashboard/src/pages/workspace.tsx diff --git a/apps/waterfall-wiki/src/pages/write.tsx b/apps/dashboard/src/pages/write.tsx similarity index 100% rename from apps/waterfall-wiki/src/pages/write.tsx rename to apps/dashboard/src/pages/write.tsx diff --git a/apps/waterfall-wiki/src/proxy.ts b/apps/dashboard/src/proxy.ts similarity index 100% rename from apps/waterfall-wiki/src/proxy.ts rename to apps/dashboard/src/proxy.ts diff --git a/apps/waterfall-wiki/src/studio/components/Brand.tsx b/apps/dashboard/src/studio/components/Brand.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/Brand.tsx rename to apps/dashboard/src/studio/components/Brand.tsx diff --git a/apps/waterfall-wiki/src/studio/components/LayoutCanvas.tsx b/apps/dashboard/src/studio/components/LayoutCanvas.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/LayoutCanvas.tsx rename to apps/dashboard/src/studio/components/LayoutCanvas.tsx diff --git a/apps/waterfall-wiki/src/studio/components/LayoutSelection.tsx b/apps/dashboard/src/studio/components/LayoutSelection.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/LayoutSelection.tsx rename to apps/dashboard/src/studio/components/LayoutSelection.tsx diff --git a/apps/waterfall-wiki/src/studio/components/LayoutWorkbench.tsx b/apps/dashboard/src/studio/components/LayoutWorkbench.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/LayoutWorkbench.tsx rename to apps/dashboard/src/studio/components/LayoutWorkbench.tsx diff --git a/apps/waterfall-wiki/src/studio/components/NativeCurvedCopy.tsx b/apps/dashboard/src/studio/components/NativeCurvedCopy.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/NativeCurvedCopy.tsx rename to apps/dashboard/src/studio/components/NativeCurvedCopy.tsx diff --git a/apps/waterfall-wiki/src/studio/components/NativePreview.tsx b/apps/dashboard/src/studio/components/NativePreview.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/NativePreview.tsx rename to apps/dashboard/src/studio/components/NativePreview.tsx diff --git a/apps/waterfall-wiki/src/studio/components/NativeResult.tsx b/apps/dashboard/src/studio/components/NativeResult.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/NativeResult.tsx rename to apps/dashboard/src/studio/components/NativeResult.tsx diff --git a/apps/waterfall-wiki/src/studio/components/NativeSeries.tsx b/apps/dashboard/src/studio/components/NativeSeries.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/NativeSeries.tsx rename to apps/dashboard/src/studio/components/NativeSeries.tsx diff --git a/apps/waterfall-wiki/src/studio/components/Ref17Cover.tsx b/apps/dashboard/src/studio/components/Ref17Cover.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/Ref17Cover.tsx rename to apps/dashboard/src/studio/components/Ref17Cover.tsx diff --git a/apps/waterfall-wiki/src/studio/components/SourceCover.tsx b/apps/dashboard/src/studio/components/SourceCover.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/SourceCover.tsx rename to apps/dashboard/src/studio/components/SourceCover.tsx diff --git a/apps/waterfall-wiki/src/studio/components/TemplateTrial.tsx b/apps/dashboard/src/studio/components/TemplateTrial.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/TemplateTrial.tsx rename to apps/dashboard/src/studio/components/TemplateTrial.tsx diff --git a/apps/waterfall-wiki/src/studio/components/ThemeToggle.tsx b/apps/dashboard/src/studio/components/ThemeToggle.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/ThemeToggle.tsx rename to apps/dashboard/src/studio/components/ThemeToggle.tsx diff --git a/apps/waterfall-wiki/src/studio/components/canvas/CanvasAgentPanel.tsx b/apps/dashboard/src/studio/components/canvas/CanvasAgentPanel.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/canvas/CanvasAgentPanel.tsx rename to apps/dashboard/src/studio/components/canvas/CanvasAgentPanel.tsx diff --git a/apps/waterfall-wiki/src/studio/components/canvas/CanvasComposer.tsx b/apps/dashboard/src/studio/components/canvas/CanvasComposer.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/canvas/CanvasComposer.tsx rename to apps/dashboard/src/studio/components/canvas/CanvasComposer.tsx diff --git a/apps/waterfall-wiki/src/studio/components/canvas/CanvasNodeView.tsx b/apps/dashboard/src/studio/components/canvas/CanvasNodeView.tsx similarity index 100% rename from apps/waterfall-wiki/src/studio/components/canvas/CanvasNodeView.tsx rename to apps/dashboard/src/studio/components/canvas/CanvasNodeView.tsx diff --git a/apps/waterfall-wiki/src/studio/content/ideas.ts b/apps/dashboard/src/studio/content/ideas.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/content/ideas.ts rename to apps/dashboard/src/studio/content/ideas.ts diff --git a/apps/waterfall-wiki/src/studio/content/standard-layout.json b/apps/dashboard/src/studio/content/standard-layout.json similarity index 100% rename from apps/waterfall-wiki/src/studio/content/standard-layout.json rename to apps/dashboard/src/studio/content/standard-layout.json diff --git a/apps/waterfall-wiki/src/studio/content/templates/layouts.json b/apps/dashboard/src/studio/content/templates/layouts.json similarity index 100% rename from apps/waterfall-wiki/src/studio/content/templates/layouts.json rename to apps/dashboard/src/studio/content/templates/layouts.json diff --git a/apps/waterfall-wiki/src/studio/content/templates/ref-17.json b/apps/dashboard/src/studio/content/templates/ref-17.json similarity index 100% rename from apps/waterfall-wiki/src/studio/content/templates/ref-17.json rename to apps/dashboard/src/studio/content/templates/ref-17.json diff --git a/apps/waterfall-wiki/src/studio/content/templates/selection.json b/apps/dashboard/src/studio/content/templates/selection.json similarity index 100% rename from apps/waterfall-wiki/src/studio/content/templates/selection.json rename to apps/dashboard/src/studio/content/templates/selection.json diff --git a/apps/waterfall-wiki/src/studio/content/tool-links.ts b/apps/dashboard/src/studio/content/tool-links.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/content/tool-links.ts rename to apps/dashboard/src/studio/content/tool-links.ts diff --git a/apps/waterfall-wiki/src/studio/lib/api-documentation.ts b/apps/dashboard/src/studio/lib/api-documentation.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/api-documentation.ts rename to apps/dashboard/src/studio/lib/api-documentation.ts diff --git a/apps/waterfall-wiki/src/studio/lib/canvas-document.ts b/apps/dashboard/src/studio/lib/canvas-document.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/canvas-document.ts rename to apps/dashboard/src/studio/lib/canvas-document.ts diff --git a/apps/waterfall-wiki/src/studio/lib/design-server.ts b/apps/dashboard/src/studio/lib/design-server.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/design-server.ts rename to apps/dashboard/src/studio/lib/design-server.ts diff --git a/apps/waterfall-wiki/src/studio/lib/graphic-document.ts b/apps/dashboard/src/studio/lib/graphic-document.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/graphic-document.ts rename to apps/dashboard/src/studio/lib/graphic-document.ts diff --git a/apps/waterfall-wiki/src/studio/lib/layout-images-client.ts b/apps/dashboard/src/studio/lib/layout-images-client.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/layout-images-client.ts rename to apps/dashboard/src/studio/lib/layout-images-client.ts diff --git a/apps/waterfall-wiki/src/studio/lib/layout-images.ts b/apps/dashboard/src/studio/lib/layout-images.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/layout-images.ts rename to apps/dashboard/src/studio/lib/layout-images.ts diff --git a/apps/waterfall-wiki/src/studio/lib/layout-parameters.ts b/apps/dashboard/src/studio/lib/layout-parameters.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/layout-parameters.ts rename to apps/dashboard/src/studio/lib/layout-parameters.ts diff --git a/apps/waterfall-wiki/src/studio/lib/native-layout.ts b/apps/dashboard/src/studio/lib/native-layout.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/native-layout.ts rename to apps/dashboard/src/studio/lib/native-layout.ts diff --git a/apps/waterfall-wiki/src/studio/lib/render-cover.ts b/apps/dashboard/src/studio/lib/render-cover.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/render-cover.ts rename to apps/dashboard/src/studio/lib/render-cover.ts diff --git a/apps/waterfall-wiki/src/studio/lib/series.ts b/apps/dashboard/src/studio/lib/series.ts similarity index 100% rename from apps/waterfall-wiki/src/studio/lib/series.ts rename to apps/dashboard/src/studio/lib/series.ts diff --git a/apps/waterfall-wiki/src/studio/styles/canvas-agent.module.css b/apps/dashboard/src/studio/styles/canvas-agent.module.css similarity index 100% rename from apps/waterfall-wiki/src/studio/styles/canvas-agent.module.css rename to apps/dashboard/src/studio/styles/canvas-agent.module.css diff --git a/apps/waterfall-wiki/src/studio/styles/canvas-composer.module.css b/apps/dashboard/src/studio/styles/canvas-composer.module.css similarity index 100% rename from apps/waterfall-wiki/src/studio/styles/canvas-composer.module.css rename to apps/dashboard/src/studio/styles/canvas-composer.module.css diff --git a/apps/waterfall-wiki/src/studio/styles/canvas-node.module.css b/apps/dashboard/src/studio/styles/canvas-node.module.css similarity index 100% rename from apps/waterfall-wiki/src/studio/styles/canvas-node.module.css rename to apps/dashboard/src/studio/styles/canvas-node.module.css diff --git a/apps/waterfall-wiki/src/studio/styles/globals.css b/apps/dashboard/src/studio/styles/globals.css similarity index 100% rename from apps/waterfall-wiki/src/studio/styles/globals.css rename to apps/dashboard/src/studio/styles/globals.css diff --git a/apps/waterfall-wiki/src/studio/styles/graphic.module.css b/apps/dashboard/src/studio/styles/graphic.module.css similarity index 100% rename from apps/waterfall-wiki/src/studio/styles/graphic.module.css rename to apps/dashboard/src/studio/styles/graphic.module.css diff --git a/apps/waterfall-wiki/src/studio/styles/infinite-canvas.module.css b/apps/dashboard/src/studio/styles/infinite-canvas.module.css similarity index 100% rename from apps/waterfall-wiki/src/studio/styles/infinite-canvas.module.css rename to apps/dashboard/src/studio/styles/infinite-canvas.module.css diff --git a/apps/waterfall-wiki/src/styles/account.module.css b/apps/dashboard/src/styles/account.module.css similarity index 100% rename from apps/waterfall-wiki/src/styles/account.module.css rename to apps/dashboard/src/styles/account.module.css diff --git a/apps/waterfall-wiki/src/styles/brand.css b/apps/dashboard/src/styles/brand.css similarity index 100% rename from apps/waterfall-wiki/src/styles/brand.css rename to apps/dashboard/src/styles/brand.css diff --git a/apps/waterfall-wiki/src/styles/documents.module.css b/apps/dashboard/src/styles/documents.module.css similarity index 100% rename from apps/waterfall-wiki/src/styles/documents.module.css rename to apps/dashboard/src/styles/documents.module.css diff --git a/apps/waterfall-wiki/src/styles/globals.css b/apps/dashboard/src/styles/globals.css similarity index 100% rename from apps/waterfall-wiki/src/styles/globals.css rename to apps/dashboard/src/styles/globals.css diff --git a/apps/waterfall-wiki/src/styles/guidance.module.css b/apps/dashboard/src/styles/guidance.module.css similarity index 100% rename from apps/waterfall-wiki/src/styles/guidance.module.css rename to apps/dashboard/src/styles/guidance.module.css diff --git a/apps/waterfall-wiki/src/styles/landing.module.css b/apps/dashboard/src/styles/landing.module.css similarity index 100% rename from apps/waterfall-wiki/src/styles/landing.module.css rename to apps/dashboard/src/styles/landing.module.css diff --git a/apps/waterfall-wiki/src/styles/learning-series.css b/apps/dashboard/src/styles/learning-series.css similarity index 100% rename from apps/waterfall-wiki/src/styles/learning-series.css rename to apps/dashboard/src/styles/learning-series.css diff --git a/apps/waterfall-wiki/src/styles/skill-docs.css b/apps/dashboard/src/styles/skill-docs.css similarity index 100% rename from apps/waterfall-wiki/src/styles/skill-docs.css rename to apps/dashboard/src/styles/skill-docs.css diff --git a/apps/waterfall-wiki/src/styles/workspace.module.css b/apps/dashboard/src/styles/workspace.module.css similarity index 100% rename from apps/waterfall-wiki/src/styles/workspace.module.css rename to apps/dashboard/src/styles/workspace.module.css diff --git a/apps/waterfall-wiki/src/worker.ts b/apps/dashboard/src/worker.ts similarity index 100% rename from apps/waterfall-wiki/src/worker.ts rename to apps/dashboard/src/worker.ts diff --git a/apps/waterfall-wiki/tsconfig.json b/apps/dashboard/tsconfig.json similarity index 100% rename from apps/waterfall-wiki/tsconfig.json rename to apps/dashboard/tsconfig.json diff --git a/package.json b/package.json index 8b21b97..d2d166a 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "packageManager": "pnpm@11.25.0", "scripts": { "dev": "turbo run dev --env-mode=loose", - "dev:waterfall-wiki": "turbo run dev --filter=@prompt/waterfall-wiki --env-mode=loose", + "dev:dashboard": "turbo run dev --filter=@prompt/dashboard --env-mode=loose", "build": "turbo run build --env-mode=loose", "typecheck": "turbo run typecheck --env-mode=loose", - "start": "pnpm --filter @prompt/waterfall-wiki start" + "start": "pnpm --filter @prompt/dashboard start" }, "devDependencies": { "turbo": "2.11.10" diff --git a/packages/brand/scripts/prepare-app.mjs b/packages/brand/scripts/prepare-app.mjs index 6879063..33eb835 100644 --- a/packages/brand/scripts/prepare-app.mjs +++ b/packages/brand/scripts/prepare-app.mjs @@ -2,7 +2,7 @@ import { copyFile, mkdir, readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; const app = JSON.parse(await readFile('package.json', 'utf8')); -if (app.name !== '@prompt/waterfall-wiki') throw Error('Run from a consuming application directory'); +if (app.name !== '@prompt/dashboard') throw Error('Run from a consuming application directory'); const source = new URL('../assets/', import.meta.url); await mkdir(resolve('public/brand'), {recursive:true}); for (const name of ['logo-light.svg','logo-dark.svg','icon-dark.svg']) await copyFile(new URL(`svg/${name}`, source), resolve(`public/brand/${name}`)); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bcec2b..c077bbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: specifier: 2.11.10 version: 2.11.10 - apps/waterfall-wiki: + apps/dashboard: dependencies: '@aws-sdk/client-s3': specifier: 3.1155.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4db738f..98647db 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,5 @@ packages: - - apps/waterfall-wiki + - apps/dashboard - packages/brand sharedWorkspaceLockfile: true allowBuilds: