diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d55fec..9349152 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,8 @@ jobs: compat_critical: ${{ steps.filter.outputs.schemas == 'true' || steps.filter.outputs.compat_script == 'true' || steps.filter.outputs.services == 'true' }} # Tier 2: Visual tests web_terminal: ${{ steps.filter.outputs.web_terminal }} + # Tier 2b: Docs viewer tests + docs: ${{ steps.filter.outputs.docs }} # Tier 3: Governance-only (does NOT trigger integration gate) contracts_docs: ${{ steps.filter.outputs.contracts_docs }} governance: ${{ steps.filter.outputs.governance }} @@ -57,6 +59,8 @@ jobs: - 'scripts/*.py' - 'scripts/*.ps1' - '.github/workflows/**' + docs: + - 'docs/**' # Explicit filter-failed detection with CI annotation - name: Check filter health @@ -583,6 +587,50 @@ jobs: if: always() run: docker compose -f docker-compose.integration.yml down -v + # ============================================================ + # Docs Viewer Tests (Playwright) + # Only runs when /docs changes - isolated from main test suite + # ============================================================ + docs-tests: + name: Docs Viewer Tests + runs-on: ubuntu-latest + needs: [paths-filter] + # Run ONLY when docs files change + if: needs.paths-filter.outputs.docs == 'true' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: | + cd docs + npm ci + + - name: Install Playwright browsers + run: | + cd docs + npx playwright install chromium webkit --with-deps + + - name: Run docs viewer tests + run: | + cd docs + npx playwright test + timeout-minutes: 5 + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: docs-playwright-report + path: docs/playwright-report/ + retention-days: 7 + # ============================================================ # Distribution Audit (naming, version, artifact consistency) # ============================================================ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05c088a..b5a1d81 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -325,7 +325,7 @@ jobs: sha256sum -c SHA256SUMS --ignore-missing ``` - See [Verifying Releases](./docs/VERIFYING_RELEASES.md) for full verification instructions. + See [Verifying Releases](./docs/agents/VERIFYING_RELEASES.md) for full verification instructions. # Publish npm package npm-publish: diff --git a/README.md b/README.md index 6b0eefe..5478ec5 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,12 @@ A self-contained, local-first demonstration platform showcasing modern, producti ![Demo](screenshots/3.x/demo.gif) -[Download Demo (MP4)](https://github.com/oddessentials/odd-demonstration/raw/main/screenshots/3.x/demo.mp4) +πŸŽ₯ +Click here to watch the dashboard demo on YouTube + +πŸ’Ύ [Click here to download the dasbhoard demo (MP4)](https://github.com/oddessentials/odd-demonstration/raw/main/screenshots/3.x/demo.mp4) + + --- @@ -32,8 +37,8 @@ A self-contained, local-first demonstration platform showcasing modern, producti **Authoritative Resources** - πŸ—ΊοΈ [Blueprints & Design](contracts/blueprint.md) -- πŸ“ [Invariants](docs/INVARIANTS.md) -- βœ… [Feature Coverage](docs/FEATURES.md) +- πŸ“ [Invariants](docs/agents/INVARIANTS.md) +- βœ… [Feature Coverage](docs/agents/FEATURES.md) **Diagrams** @@ -65,7 +70,7 @@ A self-contained, local-first demonstration platform showcasing modern, producti ## πŸ“¦ Installation Details > **Note:** currently releases are unsigned bootstrap builds. -> See [Verifying Releases](./docs/VERIFYING_RELEASES.md) for checksums. +> See [Verifying Releases](./docs/agents/VERIFYING_RELEASES.md) for checksums. ### Verify installation @@ -87,7 +92,7 @@ odd-dashboard doctor | Linux | ARM64 | `odd-dashboard-linux-arm64` | **System Requirements:** 8GB RAM minimum (16GB recommended), 4+ CPU cores, 15GB disk. -See [Support Matrix](./docs/SUPPORT_MATRIX.md) for full hardware requirements and Docker Desktop configuration. +See [Support Matrix](./docs/agents/SUPPORT_MATRIX.md) for full hardware requirements and Docker Desktop configuration. --- @@ -372,11 +377,84 @@ kind delete cluster --name task-observatory --- +## πŸ”¬ Experiment + +Here are the results of the experiment associated with this repository. + +[![Experiment Results](screenshots/3.x/assessment-meta-data-2025-12-27.png)](https://oddessentials.github.io/odd-demonstration/) + +View the full experiment β†’ + +--- + +## πŸ” Audit (raw details) + +This project includes comprehensive audit documentation capturing the implementation journey across 31+ phases: + +| Document | Description | +| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| [πŸ“‹ session-summary.md](./audit/session-summary.md) | High-level project overview with technology stack, key features, and quick access points | +| [βœ… task.md](./audit/task.md) | Phase-by-phase implementation checklist tracking all completed work from foundation to hardening | +| [πŸ“– walkthrough.md](./audit/walkthrough.md) | Detailed implementation walkthrough covering core services, observability, automation, and verification | +| [πŸ“‘ complete-session-audit.md](./audit/complete-session-audit.md) | Comprehensive technical audit with executive summary, phase details, and architecture decisions | +| [πŸ“¦ conversations.zip](./audit/conversations.zip) | \*Archived conversation logs from the development sessions. \*.pb files require some priorietery unlock. | + +### 🎬 Audit Video + +[![Watch the Audit Video](https://img.youtube.com/vi/Z3iev0YyYCw/maxresdefault.jpg)](https://youtu.be/Z3iev0YyYCw) + +_\* Because the converations.zip doesn't seem accessible, I've recorded the Google Anti-Gravity conversations that made up the vast majority of this development effort (from start to finish) in this video._ + +--- + ## πŸ“š Documentation +### Guides + - [Beginner Setup Guide](./README_beginner.md) - Step-by-step with prerequisites - [Contributing](./CONTRIBUTING.md) - Development guidelines -- [Audit](./audit/) - Implementation details and walkthroughs + +### Agent Documentation (docs/agents/) + +Authoritative reference documentation for builders and autonomous agents: + +| Document | Description | +| --------------------------------------------------------------- | ------------------------------------------------------ | +| [πŸ“ INVARIANTS.md](./docs/agents/INVARIANTS.md) | System invariants and CI enforcement map | +| [βœ… FEATURES.md](./docs/agents/FEATURES.md) | Feature coverage and implementation status | +| [πŸ§ͺ TESTING.md](./docs/agents/TESTING.md) | Testing strategy, harnesses, and determinism contracts | +| [πŸ“¦ RELEASE_CHECKLIST.md](./docs/agents/RELEASE_CHECKLIST.md) | Release preparation and verification steps | +| [πŸ” SECRET_MANAGEMENT.md](./docs/agents/SECRET_MANAGEMENT.md) | Secrets handling and rotation procedures | +| [πŸ“‹ SUPPORT_MATRIX.md](./docs/agents/SUPPORT_MATRIX.md) | Platform support and hardware requirements | +| [βœ”οΈ VERIFYING_RELEASES.md](./docs/agents/VERIFYING_RELEASES.md) | Release verification and checksum validation | + +### πŸ“– Further Reading & Background + +The following articles document the motivation and evolution of this repository. +They are **not required reading**, but provide additional context for interested readers. + +- **From Puppeteer to Conductor (Part 3 of 3)** + _Designing autonomous systems without sacrificing safety or determinism_ + https://medium.com/@pete.palles/from-puppeteer-to-conductor-520c8f18e37f + +- **The Renaissance Engineers (Part 2 of 3)** + _Dark Magic, Dog Food, Determinism, and the Humans in the Loop_ + https://medium.com/@pete.palles/the-renaissance-engineers-e3c1efa15572 + +- **The Future of Software Engineering (Part 1 of 3)** + _Supercolonies: Where the Most Skilled Engineers Command Hives and Swarms_ + https://medium.com/@pete.palles/the-future-of-software-engineering-51de53d2e45a + +--- + +## πŸ‘€ Author + +Pete Palles + +**Pete Palles** +πŸ”— LinkedIn: https://www.linkedin.com/in/petepalles + +Peter is a Software Engineering Manager at a large enterprise healthcare organization, where he leads a team of highly skilled software engineers. He is also the Founder and CEO of Odd Essentials, LLC. With more than 20 years of experience spanning full-stack development, systems engineering, and applied AI, Peter has architected, designed, and delivered large-scale software systems end-to-end. At the ripe age of 41, Pete is currently completing his MBA at the University of Pittsburgh’s Katz Graduate School of Business. --- diff --git a/audit/session-summary.md b/audit/session-summary.md index a559004..2dcfd5a 100644 --- a/audit/session-summary.md +++ b/audit/session-summary.md @@ -1,7 +1,7 @@ # Distributed Task Observatory - Session Summary -**Last Updated:** 2025-12-27 -**Phases Completed:** 0-31.6 (Phase 31.6: CI Docker Build Context Fix) +**Last Updated:** 2025-12-28 +**Phases Completed:** 0-32 (Phase 32: Docs Directory Refactor) ## Objective @@ -78,6 +78,9 @@ Implement a complete, production-grade distributed task processing system demons - **PTY Server Coverage** - 81% (47 unit tests) - **Tiered Visual Test Strategy** (Phase 31.5) - Nightly workflow, server-side failure injection, deterministic fallback testing - **CI Docker Build Context Fix** (Phase 31.6) - Fixed CI contexts, added `validate-dockerfile-context.py` prevention script +- **Mermaid Animator Expansion** (Phase 31.7) - Multi-tokenizer architecture for 20+ Mermaid types, 110+ passing unit tests +- **Docs Viewer & E2E Tests** (Phase 31.8) - GitHub Pages experiment viewer with comparison mode, Playwright E2E tests +- **Docs Directory Refactor** (Phase 32) - Moved 7 .md files to `docs/agents/`, enhanced README documentation section ## Quick Start diff --git a/audit/task.md b/audit/task.md index ce4f56f..2198cfd 100644 --- a/audit/task.md +++ b/audit/task.md @@ -104,7 +104,7 @@ - [x] Update BUILD.bazel for TypeScript tests ## Phase 17: Testing Optimizations & CI Hardening [x] -- [x] Create `docs/INVARIANTS.md` with cross-platform and contract guarantees +- [x] Create `docs/agents/INVARIANTS.md` with cross-platform and contract guarantees - [x] Add unified coverage enforcement via `coverage-config.json` and `check-coverage.py` - [x] Parallelize Go tests with pwsh < 7 fallback - [x] Add `dorny/paths-filter` for reliable change detection @@ -118,3 +118,23 @@ - [x] Add validation to CI workflow - [x] Update INVARIANTS.md with accurate B1-B4 documentation +## Phase 31.7: Mermaid Animator Expansion [x] +- [x] Implement Phase 1 tokenizers (Flowchart, State, Sequence) +- [x] Implement Phase 2 tokenizers (Sankey, Pie, Radar, Gantt, Mindmap, GitGraph, Class) +- [x] Create type detection router (`src/type-router.js`) +- [x] Add animation strategies (stream-flow, reveal, trace, cascade, branch-grow) +- [x] 110+ passing unit tests across 20+ suites + +## Phase 31.8: Docs Viewer & E2E Tests [x] +- [x] Create GitHub Pages experiment viewer (`docs/index.html`, `docs/app.js`) +- [x] Implement side-by-side comparison mode for assessments +- [x] Add responsive mobile stacking for compare mode +- [x] Add Playwright E2E tests (`docs/tests/docs-viewer.spec.js`) +- [x] Integrate with CI via path-filtered triggers + +## Phase 32: Docs Directory Refactor [x] +- [x] Move 7 .md files from `docs/` to `docs/agents/` +- [x] Update all functional code references (README.md, scripts, doctor.rs) +- [x] Update internal cross-references in moved files +- [x] Enhance README Documentation section with docs/agents table +- [x] Update audit session data with new phases diff --git a/docs/FEATURES.md b/docs/agents/FEATURES.md similarity index 100% rename from docs/FEATURES.md rename to docs/agents/FEATURES.md diff --git a/docs/INVARIANTS.md b/docs/agents/INVARIANTS.md similarity index 100% rename from docs/INVARIANTS.md rename to docs/agents/INVARIANTS.md diff --git a/docs/RELEASE_CHECKLIST.md b/docs/agents/RELEASE_CHECKLIST.md similarity index 100% rename from docs/RELEASE_CHECKLIST.md rename to docs/agents/RELEASE_CHECKLIST.md diff --git a/docs/SECRET_MANAGEMENT.md b/docs/agents/SECRET_MANAGEMENT.md similarity index 95% rename from docs/SECRET_MANAGEMENT.md rename to docs/agents/SECRET_MANAGEMENT.md index 5f7a70f..0932ded 100644 --- a/docs/SECRET_MANAGEMENT.md +++ b/docs/agents/SECRET_MANAGEMENT.md @@ -66,7 +66,7 @@ Secrets are defined at the environment level, NOT repository level. 2. Export private key: `gpg --export-secret-keys --armor KEY_ID` 3. Update `GPG_PRIVATE_KEY` and `GPG_PASSPHRASE` 4. Export and publish public key to `keys/release-signing.pub` -5. Update key fingerprint in `docs/VERIFYING_RELEASES.md` +5. Update key fingerprint in `VERIFYING_RELEASES.md` 6. Update GitHub user GPG keys ## Emergency Revocation diff --git a/docs/SUPPORT_MATRIX.md b/docs/agents/SUPPORT_MATRIX.md similarity index 95% rename from docs/SUPPORT_MATRIX.md rename to docs/agents/SUPPORT_MATRIX.md index e2281a4..5327e11 100644 --- a/docs/SUPPORT_MATRIX.md +++ b/docs/agents/SUPPORT_MATRIX.md @@ -104,7 +104,7 @@ The following platforms are **not supported**: Running on unsupported platforms will result in: ``` ERROR: Unsupported platform: {os}-{arch} -See supported configurations: https://github.com/oddessentials/odd-demonstration/blob/main/docs/SUPPORT_MATRIX.md +See supported configurations: https://github.com/oddessentials/odd-demonstration/blob/main/docs/agents/SUPPORT_MATRIX.md ``` ## Checking Platform Support diff --git a/docs/TESTING.md b/docs/agents/TESTING.md similarity index 100% rename from docs/TESTING.md rename to docs/agents/TESTING.md diff --git a/docs/VERIFYING_RELEASES.md b/docs/agents/VERIFYING_RELEASES.md similarity index 100% rename from docs/VERIFYING_RELEASES.md rename to docs/agents/VERIFYING_RELEASES.md diff --git a/docs/app.js b/docs/app.js index 9de127e..4060321 100644 --- a/docs/app.js +++ b/docs/app.js @@ -4,247 +4,396 @@ */ const ExperimentViewer = { - // Base path for content files (relative to /docs) - basePath: 'experiment/', - - // File tree matching actual directory structure under /docs/experiment - fileTree: [ + // Base path for content files (relative to /docs) + basePath: "experiment/", + + // File tree matching actual directory structure under /docs/experiment + fileTree: [ + { + name: "control-groups", + type: "dir", + path: "experiment/control-groups", + children: [ { - name: 'control-groups', - type: 'dir', - path: 'experiment/control-groups', - children: [ - { - name: 'dapr', - type: 'dir', - path: 'experiment/control-groups/dapr', - children: [ - { name: 'dapr-claude-opus-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-claude-opus-assessment-2025-12-27.md' }, - { name: 'dapr-claude-sonnet-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-claude-sonnet-assessment-2025-12-27.md' }, - { name: 'dapr-gemini-flash-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-gemini-flash-assessment-2025-12-27.md' }, - { name: 'dapr-gemini-high-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-gemini-high-assessment-2025-12-27.md' }, - { name: 'dapr-gpt-oss-120b-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-gpt-oss-120b-assessment-2025-12-27.md' }, - { name: 'dapr-gpt5.2-browser-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-gpt5.2-browser-assessment-2025-12-27.md' }, - { name: 'dapr-gpt5.2-browser-assessment-2025-12-27.pdf', type: 'file', path: 'experiment/control-groups/dapr/dapr-gpt5.2-browser-assessment-2025-12-27.pdf' }, - { name: 'dapr-supergrok-browser-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/dapr/dapr-supergrok-browser-assessment-2025-12-27.md' }, - ] - }, - { - name: 'google-microservices-demo', - type: 'dir', - path: 'experiment/control-groups/google-microservices-demo', - children: [ - { name: 'gm-claude-opus-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-claude-opus-assessment-2025-12-27.md' }, - { name: 'gm-claude-sonnet-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-claude-sonnet-assessment-2025-12-27.md' }, - { name: 'gm-gemini-flash-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-gemini-flash-assessment-2025-12-27.md' }, - { name: 'gm-gemini-high-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-gemini-high-assessment-2025-12-27.md' }, - { name: 'gm-gpt-oss-120b-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-gpt-oss-120b-assessment-2025-12-27.md' }, - { name: 'gm-gpt5.2-browser-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-gpt5.2-browser-assessment-2025-12-27.md' }, - { name: 'gm-gpt5.2-browser-assessment-2025-12-27.pdf', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-gpt5.2-browser-assessment-2025-12-27.pdf' }, - { name: 'gm-supergrok-browser-assessment-2025-12-27.md', type: 'file', path: 'experiment/control-groups/google-microservices-demo/gm-supergrok-browser-assessment-2025-12-27.md' }, - ] - } - ] + name: "dapr", + type: "dir", + path: "experiment/control-groups/dapr", + children: [ + { + name: "dapr-claude-opus-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-claude-opus-assessment-2025-12-27.md", + }, + { + name: "dapr-claude-sonnet-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-claude-sonnet-assessment-2025-12-27.md", + }, + { + name: "dapr-gemini-flash-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-gemini-flash-assessment-2025-12-27.md", + }, + { + name: "dapr-gemini-high-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-gemini-high-assessment-2025-12-27.md", + }, + { + name: "dapr-gpt-oss-120b-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-gpt-oss-120b-assessment-2025-12-27.md", + }, + { + name: "dapr-gpt5.2-browser-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-gpt5.2-browser-assessment-2025-12-27.md", + }, + { + name: "dapr-gpt5.2-browser-assessment-2025-12-27.pdf", + type: "file", + path: "experiment/control-groups/dapr/dapr-gpt5.2-browser-assessment-2025-12-27.pdf", + }, + { + name: "dapr-supergrok-browser-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/dapr/dapr-supergrok-browser-assessment-2025-12-27.md", + }, + ], }, { - name: 'experiment-group', - type: 'dir', - path: 'experiment/experiment-group', - children: [ - { name: 'oed-claude-opus-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-claude-opus-assessment-2025-12-27.md' }, - { name: 'oed-claude-sonnet-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-claude-sonnet-assessment-2025-12-27.md' }, - { name: 'oed-gemini-flash-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-gemini-flash-assessment-2025-12-27.md' }, - { name: 'oed-gemini-high-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-gemini-high-assessment-2025-12-27.md' }, - { name: 'oed-gpt-codex-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-gpt-codex-assessment-2025-12-27.md' }, - { name: 'oed-gpt-oss-120b-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-gpt-oss-120b-assessment-2025-12-27.md' }, - { name: 'oed-gpt5.2-browser-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-gpt5.2-browser-assessment-2025-12-27.md' }, - { name: 'oed-gpt5.2-browser-assessment-2025-12-27.pdf', type: 'file', path: 'experiment/experiment-group/oed-gpt5.2-browser-assessment-2025-12-27.pdf' }, - { name: 'oed-supergrok-browser-assessment-2025-12-27.md', type: 'file', path: 'experiment/experiment-group/oed-supergrok-browser-assessment-2025-12-27.md' }, - ] + name: "google-microservices-demo", + type: "dir", + path: "experiment/control-groups/google-microservices-demo", + children: [ + { + name: "gm-claude-opus-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-claude-opus-assessment-2025-12-27.md", + }, + { + name: "gm-claude-sonnet-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-claude-sonnet-assessment-2025-12-27.md", + }, + { + name: "gm-gemini-flash-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-gemini-flash-assessment-2025-12-27.md", + }, + { + name: "gm-gemini-high-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-gemini-high-assessment-2025-12-27.md", + }, + { + name: "gm-gpt-oss-120b-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-gpt-oss-120b-assessment-2025-12-27.md", + }, + { + name: "gm-gpt5.2-browser-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-gpt5.2-browser-assessment-2025-12-27.md", + }, + { + name: "gm-gpt5.2-browser-assessment-2025-12-27.pdf", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-gpt5.2-browser-assessment-2025-12-27.pdf", + }, + { + name: "gm-supergrok-browser-assessment-2025-12-27.md", + type: "file", + path: "experiment/control-groups/google-microservices-demo/gm-supergrok-browser-assessment-2025-12-27.md", + }, + ], }, - { name: 'experiment.md', type: 'file', path: 'experiment/experiment.md' }, - { name: 'experiment.pdf', type: 'file', path: 'experiment/experiment.pdf' }, - ], - - // Application state - state: { - currentFile: null, - compareFile: null, - compareMode: false, - }, - - // Icons - icons: { - folder: ``, - folderOpen: ``, - markdown: ``, - pdf: ``, - compare: ``, - close: ``, + ], }, - - // Build file tree recursively - buildTree(items, parentUl, paneId = 'primary') { - items.sort((a, b) => { - if (a.type === b.type) return a.name.localeCompare(b.name); - return a.type === 'dir' ? -1 : 1; - }); - - for (const item of items) { - const li = document.createElement('li'); - - if (item.type === 'dir') { - const span = document.createElement('span'); - span.className = 'folder'; - span.innerHTML = `${this.icons.folder}${item.name}`; - li.appendChild(span); - - const ul = document.createElement('ul'); - ul.className = 'collapsed'; - li.appendChild(ul); - - if (item.children) { - this.buildTree(item.children, ul, paneId); - } - - span.onclick = () => { - const isExpanded = !ul.classList.contains('collapsed'); - ul.classList.toggle('collapsed'); - span.innerHTML = `${isExpanded ? this.icons.folder : this.icons.folderOpen}${item.name}`; - }; - } else { - const isPdf = item.name.endsWith('.pdf'); - const a = document.createElement('a'); - a.href = '#'; - a.className = 'file-link'; - a.innerHTML = `${isPdf ? this.icons.pdf : this.icons.markdown}${item.name}`; - a.onclick = (e) => { - e.preventDefault(); - this.loadFile(item.path, isPdf, paneId); - }; - li.appendChild(a); - } - - parentUl.appendChild(li); - } + { + name: "experiment-group", + type: "dir", + path: "experiment/experiment-group", + children: [ + { + name: "oed-claude-opus-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-claude-opus-assessment-2025-12-27.md", + }, + { + name: "oed-claude-sonnet-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-claude-sonnet-assessment-2025-12-27.md", + }, + { + name: "oed-gemini-flash-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-gemini-flash-assessment-2025-12-27.md", + }, + { + name: "oed-gemini-high-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-gemini-high-assessment-2025-12-27.md", + }, + { + name: "oed-gpt-codex-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-gpt-codex-assessment-2025-12-27.md", + }, + { + name: "oed-gpt-oss-120b-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-gpt-oss-120b-assessment-2025-12-27.md", + }, + { + name: "oed-gpt5.2-browser-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-gpt5.2-browser-assessment-2025-12-27.md", + }, + { + name: "oed-gpt5.2-browser-assessment-2025-12-27.pdf", + type: "file", + path: "experiment/experiment-group/oed-gpt5.2-browser-assessment-2025-12-27.pdf", + }, + { + name: "oed-supergrok-browser-assessment-2025-12-27.md", + type: "file", + path: "experiment/experiment-group/oed-supergrok-browser-assessment-2025-12-27.md", + }, + ], }, - - // Load file content - async loadFile(path, isPdf, paneId = 'primary') { - const contentDiv = document.getElementById(paneId === 'primary' ? 'content-primary' : 'content-secondary'); - const headerDiv = contentDiv.querySelector('.content-header'); - const bodyDiv = contentDiv.querySelector('.content-body'); - - // Update state - if (paneId === 'primary') { - this.state.currentFile = path; - } else { - this.state.compareFile = path; + { name: "experiment.md", type: "file", path: "experiment/experiment.md" }, + { name: "experiment.pdf", type: "file", path: "experiment/experiment.pdf" }, + ], + + // Application state + state: { + currentFile: null, + compareFile: null, + compareMode: false, + }, + + // Cache for loaded content (path -> {html: string, isPdf: bool}) + contentCache: new Map(), + + // Icons + icons: { + folder: ``, + folderOpen: ``, + markdown: ``, + pdf: ``, + compare: ``, + close: ``, + }, + + // Build file tree recursively + buildTree(items, parentUl, paneId = "primary") { + items.sort((a, b) => { + if (a.type === b.type) return a.name.localeCompare(b.name); + return a.type === "dir" ? -1 : 1; + }); + + for (const item of items) { + const li = document.createElement("li"); + + if (item.type === "dir") { + const span = document.createElement("span"); + span.className = "folder"; + span.innerHTML = `${this.icons.folder}${item.name}`; + li.appendChild(span); + + const ul = document.createElement("ul"); + ul.className = "collapsed"; + li.appendChild(ul); + + if (item.children) { + this.buildTree(item.children, ul, paneId); } - // Update header (show path without 'experiment/' prefix for cleaner display) - const displayPath = path.replace(/^experiment\//, ''); - headerDiv.innerHTML = `${displayPath}`; - - // Update URL hash - this.updateHash(); - - // Highlight active file in tree - this.highlightActiveFile(path, paneId); - - if (isPdf) { - bodyDiv.innerHTML = ``; - } else { - try { - bodyDiv.innerHTML = '
Loading...
'; - const response = await fetch(path); - if (!response.ok) throw new Error('Failed to load file'); - const md = await response.text(); - bodyDiv.innerHTML = `
${marked.parse(md)}
`; - } catch (error) { - console.error(error); - bodyDiv.innerHTML = '
Error loading content.
'; - } - } - }, - - // Highlight active file in sidebar - highlightActiveFile(path, paneId) { - const treeId = paneId === 'primary' ? 'tree-primary' : 'tree-secondary'; - const tree = document.getElementById(treeId); - if (!tree) return; - - tree.querySelectorAll('.file-link').forEach(link => { - link.classList.remove('active'); - }); - - tree.querySelectorAll('.file-link').forEach(link => { - if (link.textContent.includes(path.split('/').pop())) { - link.classList.add('active'); - } - }); - }, + span.onclick = () => { + const isExpanded = !ul.classList.contains("collapsed"); + ul.classList.toggle("collapsed"); + span.innerHTML = `${ + isExpanded ? this.icons.folder : this.icons.folderOpen + }${item.name}`; + }; + } else { + const isPdf = item.name.endsWith(".pdf"); + const a = document.createElement("a"); + a.href = "#"; + a.className = "file-link"; + a.innerHTML = `${ + isPdf ? this.icons.pdf : this.icons.markdown + }${item.name}`; + a.onclick = (e) => { + e.preventDefault(); + this.loadFile(item.path, isPdf, paneId); + }; + li.appendChild(a); + } - // Toggle compare mode - toggleCompareMode() { - this.state.compareMode = !this.state.compareMode; - const viewer = document.getElementById('viewer'); - const btn = document.getElementById('compare-btn'); - - if (this.state.compareMode) { - viewer.classList.add('compare-mode'); - btn.classList.add('active'); - btn.innerHTML = `${this.icons.close}Exit Compare`; - - // Build secondary tree if not exists - const secondaryTree = document.getElementById('tree-secondary'); - if (secondaryTree && secondaryTree.children.length === 0) { - this.buildTree(this.fileTree, secondaryTree, 'secondary'); - } - } else { - viewer.classList.remove('compare-mode'); - btn.classList.remove('active'); - btn.innerHTML = `${this.icons.compare}Compare`; - this.state.compareFile = null; - } + parentUl.appendChild(li); + } + }, + + // Load file content with caching + async loadFile(path, isPdf, paneId = "primary") { + const contentDiv = document.getElementById( + paneId === "primary" ? "content-primary" : "content-secondary" + ); + const headerDiv = contentDiv.querySelector(".content-header"); + const bodyDiv = contentDiv.querySelector(".content-body"); + + // Update state + if (paneId === "primary") { + this.state.currentFile = path; + } else { + this.state.compareFile = path; + } - this.updateHash(); - }, + // Update header (show path without 'experiment/' prefix for cleaner display) + const displayPath = path.replace(/^experiment\//, ""); + const filePathSlot = headerDiv.querySelector(".file-path-slot"); + if (filePathSlot) { + filePathSlot.textContent = displayPath; + } else { + // Fallback for when slot doesn't exist + headerDiv.innerHTML = `${displayPath}`; + } - // Update URL hash for deep linking - updateHash() { - let hash = ''; - if (this.state.currentFile) { - hash = this.state.currentFile; - if (this.state.compareMode && this.state.compareFile) { - hash += '|' + this.state.compareFile; - } - } - if (hash) { - window.location.hash = hash; - } - }, + // Update URL hash only if changed + const oldHash = window.location.hash.slice(1); + const newHash = this.getHashString(); + if (newHash !== oldHash) { + window.location.hash = newHash; + } - // Parse URL hash - parseHash() { - const hash = window.location.hash.slice(1); - if (!hash) return null; + // Highlight active file in tree + this.highlightActiveFile(path, paneId); - const parts = hash.split('|'); - return { - primary: parts[0] || null, - secondary: parts[1] || null, - }; - }, + const cacheKey = path; + const cached = this.contentCache.get(cacheKey); - // Show intro content - showIntro() { - const contentDiv = document.getElementById('content-primary'); - const headerDiv = contentDiv.querySelector('.content-header'); - const bodyDiv = contentDiv.querySelector('.content-body'); + if (cached) { + bodyDiv.innerHTML = cached.html; + return; + } - headerDiv.innerHTML = 'Welcome'; + if (isPdf) { + const html = ``; + bodyDiv.innerHTML = html; + this.contentCache.set(cacheKey, { html, isPdf: true }); + } else { + bodyDiv.innerHTML = '
Loading...
'; + try { + const response = await fetch(path); + if (!response.ok) throw new Error("Failed to load file"); + const md = await response.text(); + const parsed = marked.parse(md); + const html = `
${parsed}
`; + bodyDiv.innerHTML = html; + this.contentCache.set(cacheKey, { html, isPdf: false }); + } catch (error) { + console.error(error); bodyDiv.innerHTML = ` +
+ Error loading content. + +
`; + } + } + }, + + // Retry file load (uses cache if available) + retryLoad(path, isPdf, paneId) { + this.loadFile(path, isPdf, paneId); + }, + + // Highlight active file in sidebar + highlightActiveFile(path, paneId) { + const treeId = paneId === "primary" ? "tree-primary" : "tree-secondary"; + const tree = document.getElementById(treeId); + if (!tree) return; + + tree.querySelectorAll(".file-link").forEach((link) => { + link.classList.remove("active"); + }); + + tree.querySelectorAll(".file-link").forEach((link) => { + if (link.textContent.includes(path.split("/").pop())) { + link.classList.add("active"); + } + }); + }, + + // Toggle compare mode + toggleCompareMode() { + this.state.compareMode = !this.state.compareMode; + const viewer = document.getElementById("viewer"); + const btn = document.getElementById("compare-btn"); + + if (this.state.compareMode) { + viewer.classList.add("compare-mode"); + btn.classList.add("active"); + btn.innerHTML = `${this.icons.close}Exit Compare`; + + // Lazy build secondary tree + const secondaryTree = document.getElementById("tree-secondary"); + if (secondaryTree && secondaryTree.children.length === 0) { + this.buildTree(this.fileTree, secondaryTree, "secondary"); + } + } else { + viewer.classList.remove("compare-mode"); + btn.classList.remove("active"); + btn.innerHTML = `${this.icons.compare}Compare`; + this.state.compareFile = null; + } + + // Update hash only if changed + const oldHash = window.location.hash.slice(1); + const newHash = this.getHashString(); + if (newHash !== oldHash) { + window.location.hash = newHash; + } + }, + + // Get current hash string + getHashString() { + let hash = ""; + if (this.state.currentFile) { + hash = this.state.currentFile; + if (this.state.compareMode && this.state.compareFile) { + hash += "|" + this.state.compareFile; + } + } + return hash; + }, + + // Parse URL hash + parseHash() { + const hash = window.location.hash.slice(1); + if (!hash) return null; + + const parts = hash.split("|"); + return { + primary: parts[0] || null, + secondary: parts[1] || null, + }; + }, + + // Show intro content + showIntro() { + const contentDiv = document.getElementById("content-primary"); + const headerDiv = contentDiv.querySelector(".content-header"); + const bodyDiv = contentDiv.querySelector(".content-body"); + + const filePathSlot = headerDiv.querySelector(".file-path-slot"); + if (filePathSlot) { + filePathSlot.textContent = "Welcome"; + } else { + headerDiv.innerHTML = 'Welcome'; + } + bodyDiv.innerHTML = `

AI Model Assessment Experiment

@@ -273,44 +422,193 @@ const ExperimentViewer = {

`; - }, + }, + + // Initialize application + init() { + // Build primary tree + const primaryTree = document.getElementById("tree-primary"); + this.buildTree(this.fileTree, primaryTree, "primary"); + + // Setup compare button + const compareBtn = document.getElementById("compare-btn"); + compareBtn.onclick = () => this.toggleCompareMode(); + + // Setup mobile menu + this.setupMobileMenu(); + + // Setup per-pane nav buttons for mobile compare mode + this.setupPaneNavButtons(); + + // Check for hash on load + const hashState = this.parseHash(); + if (hashState && hashState.primary) { + const isPdf = hashState.primary.endsWith(".pdf"); + this.loadFile(hashState.primary, isPdf, "primary"); + + if (hashState.secondary) { + this.toggleCompareMode(); + const isPdfSecondary = hashState.secondary.endsWith(".pdf"); + this.loadFile(hashState.secondary, isPdfSecondary, "secondary"); + } + } else { + // Show intro by default + this.showIntro(); + } - // Initialize application - init() { - // Build primary tree - const primaryTree = document.getElementById('tree-primary'); - this.buildTree(this.fileTree, primaryTree, 'primary'); - - // Setup compare button - const compareBtn = document.getElementById('compare-btn'); - compareBtn.onclick = () => this.toggleCompareMode(); - - // Check for hash on load - const hashState = this.parseHash(); - if (hashState && hashState.primary) { - const isPdf = hashState.primary.endsWith('.pdf'); - this.loadFile(hashState.primary, isPdf, 'primary'); - - if (hashState.secondary) { - this.toggleCompareMode(); - const isPdfSecondary = hashState.secondary.endsWith('.pdf'); - this.loadFile(hashState.secondary, isPdfSecondary, 'secondary'); - } - } else { - // Show intro by default - this.showIntro(); + // Handle hash changes (with light debounce for safety) + let hashTimeout; + window.onhashchange = () => { + clearTimeout(hashTimeout); + hashTimeout = setTimeout(() => { + const state = this.parseHash(); + if (state && state.primary) { + const isPdf = state.primary.endsWith(".pdf"); + this.loadFile(state.primary, isPdf, "primary"); + } + }, 50); + }; + }, + + // Setup mobile menu functionality + setupMobileMenu() { + const menuBtn = document.getElementById("menu-btn"); + const primarySidebar = document.getElementById("sidebar-primary"); + const secondarySidebar = document.getElementById("sidebar-secondary"); + const overlay = document.getElementById("sidebar-overlay"); + + if (!menuBtn || !primarySidebar || !overlay) return; + + // Toggle sidebar on menu button click + menuBtn.onclick = () => { + this.toggleMobileMenu(); + }; + + // Close sidebar on overlay click + overlay.onclick = () => { + this.closeMobileMenu(); + }; + + // Close sidebar when a file is selected on mobile (for primary sidebar) + primarySidebar.addEventListener("click", (e) => { + if (e.target.closest(".file-link") && window.innerWidth <= 768) { + this.closeMobileMenu(); + } + }); + + // Close sidebar when a file is selected on mobile (for secondary sidebar) + if (secondarySidebar) { + secondarySidebar.addEventListener("click", (e) => { + if (e.target.closest(".file-link") && window.innerWidth <= 768) { + this.closeMobileMenu(); } + }); + } - // Handle hash changes - window.onhashchange = () => { - const state = this.parseHash(); - if (state && state.primary) { - const isPdf = state.primary.endsWith('.pdf'); - this.loadFile(state.primary, isPdf, 'primary'); - } - }; + // Handle window resize - close mobile menu when resizing to desktop + window.addEventListener("resize", () => { + if (window.innerWidth > 768) { + this.closeMobileMenu(); + } + }); + }, + + // Track which sidebar is currently open on mobile + activeMobileSidebar: null, + + // Toggle mobile menu - in compare mode, cycle through both sidebars + toggleMobileMenu() { + const primarySidebar = document.getElementById("sidebar-primary"); + const secondarySidebar = document.getElementById("sidebar-secondary"); + const overlay = document.getElementById("sidebar-overlay"); + + if (!primarySidebar || !overlay) return; + + // In compare mode, cycle: closed -> primary -> secondary -> closed + if (this.state.compareMode && secondarySidebar) { + if (this.activeMobileSidebar === null) { + // Open primary sidebar + primarySidebar.classList.add("open"); + overlay.classList.add("visible"); + this.activeMobileSidebar = "primary"; + } else if (this.activeMobileSidebar === "primary") { + // Switch to secondary sidebar + primarySidebar.classList.remove("open"); + secondarySidebar.classList.add("open"); + this.activeMobileSidebar = "secondary"; + } else { + // Close all + this.closeMobileMenu(); + } + } else { + // Normal mode - just toggle primary + const isOpen = primarySidebar.classList.contains("open"); + if (isOpen) { + this.closeMobileMenu(); + } else { + primarySidebar.classList.add("open"); + overlay.classList.add("visible"); + this.activeMobileSidebar = "primary"; + } + } + }, + + // Close mobile menu + closeMobileMenu() { + const primarySidebar = document.getElementById("sidebar-primary"); + const secondarySidebar = document.getElementById("sidebar-secondary"); + const overlay = document.getElementById("sidebar-overlay"); + + if (primarySidebar) primarySidebar.classList.remove("open"); + if (secondarySidebar) secondarySidebar.classList.remove("open"); + if (overlay) overlay.classList.remove("visible"); + this.activeMobileSidebar = null; + }, + + // Setup per-pane navigation buttons for mobile compare mode + setupPaneNavButtons() { + const primaryNavBtn = document.getElementById("nav-btn-primary"); + const secondaryNavBtn = document.getElementById("nav-btn-secondary"); + + if (primaryNavBtn) { + primaryNavBtn.onclick = () => { + this.openSidebar("primary"); + }; + } + + if (secondaryNavBtn) { + secondaryNavBtn.onclick = () => { + // Lazy build secondary tree on first mobile access + const secondaryTree = document.getElementById("tree-secondary"); + if (secondaryTree && secondaryTree.children.length === 0) { + this.buildTree(this.fileTree, secondaryTree, "secondary"); + } + this.openSidebar("secondary"); + }; + } + }, + + // Open a specific sidebar directly + openSidebar(sidebarId) { + const primarySidebar = document.getElementById("sidebar-primary"); + const secondarySidebar = document.getElementById("sidebar-secondary"); + const overlay = document.getElementById("sidebar-overlay"); + + // Close any open sidebar first + this.closeMobileMenu(); + + // Open the requested sidebar + if (sidebarId === "primary" && primarySidebar) { + primarySidebar.classList.add("open"); + if (overlay) overlay.classList.add("visible"); + this.activeMobileSidebar = "primary"; + } else if (sidebarId === "secondary" && secondarySidebar) { + secondarySidebar.classList.add("open"); + if (overlay) overlay.classList.add("visible"); + this.activeMobileSidebar = "secondary"; } + }, }; // Initialize on DOM ready -document.addEventListener('DOMContentLoaded', () => ExperimentViewer.init()); +document.addEventListener("DOMContentLoaded", () => ExperimentViewer.init()); diff --git a/docs/img/pete-palles-512.jpg b/docs/img/pete-palles-512.jpg new file mode 100644 index 0000000..cb1cf36 Binary files /dev/null and b/docs/img/pete-palles-512.jpg differ diff --git a/docs/index.html b/docs/index.html index 63862c2..1f7be8d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,28 +3,62 @@ - AI Model Assessment Experiment - - + πŸ”¬ AI Model Assessment Experiment + + - - + + - + +
+ + +
-
-
diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..9e430cb --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,1124 @@ +{ + "name": "docs-viewer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docs-viewer", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.40.0", + "serve": "^14.2.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serve": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz", + "integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.8.1", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.6", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..e6ca2b6 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,15 @@ +{ + "name": "docs-viewer", + "version": "1.0.0", + "description": "AI Assessment Experiment Viewer - E2E tests", + "private": true, + "scripts": { + "test": "playwright test", + "test:headed": "playwright test --headed", + "test:debug": "playwright test --debug" + }, + "devDependencies": { + "@playwright/test": "^1.40.0", + "serve": "^14.2.0" + } +} \ No newline at end of file diff --git a/docs/playwright.config.js b/docs/playwright.config.js new file mode 100644 index 0000000..762ba35 --- /dev/null +++ b/docs/playwright.config.js @@ -0,0 +1,43 @@ +// @ts-check +const { defineConfig, devices } = require('@playwright/test'); + +/** + * Playwright configuration for Docs Viewer E2E tests. + * Uses a local static server to serve the docs directory. + */ +module.exports = defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html', { outputFolder: 'playwright-report' }], + ['list'] + ], + + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'mobile-safari', + use: { ...devices['iPhone 12'] }, + }, + ], + + /* Run local static server before starting tests */ + webServer: { + command: 'npx serve -l 3000 .', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + timeout: 30000, + }, +}); diff --git a/docs/styles.css b/docs/styles.css index 8978e69..38baf35 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -1,40 +1,56 @@ /* ============================================ - Experiment Viewer - Dark Glassmorphic Theme + Experiment Viewer - Professional Dark Theme ============================================ */ /* CSS Custom Properties */ +/* Changes: + - Updated accent colors to a more professional blue palette for consistency and sophistication. + - Removed gradient accents to avoid visual clutter; using solid colors for a cleaner look. + - Adjusted text colors slightly for better readability and contrast. + - Kept glassmorphic effects but toned down opacities for a subtler appearance. + - Unified miscellaneous color references (e.g., #0284c7) to match new accents. +*/ :root { --bg-primary: #0a0a0f; --bg-secondary: #12121a; --bg-tertiary: #1a1a25; - --glass-bg: rgba(255, 255, 255, 0.03); - --glass-border: rgba(255, 255, 255, 0.08); - --glass-hover: rgba(255, 255, 255, 0.06); - - --text-primary: #e8e8ed; - --text-secondary: #a0a0b0; - --text-muted: #606075; - - --accent-primary: #8b5cf6; - --accent-secondary: #06b6d4; - --accent-gradient: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary)); - + --glass-bg: rgba(255, + 255, + 255, + 0.02); + /* Slightly reduced opacity for subtlety */ + --glass-border: rgba(255, 255, 255, 0.06); + --glass-hover: rgba(255, 255, 255, 0.04); + + --text-primary: #f0f0f5; + /* Slightly brighter for better contrast */ + --text-secondary: #b0b0c0; + --text-muted: #707085; + + --accent-primary: #3b82f6; + /* Professional blue */ + --accent-secondary: #60a5fa; + /* Lighter blue for secondary elements */ + --sidebar-width: 320px; --header-height: 56px; --transition-fast: 150ms ease; --transition-normal: 250ms ease; - + --radius-sm: 6px; --radius-md: 10px; --radius-lg: 16px; } /* Reset & Base */ -*, *::before, *::after { +*, +*::before, +*::after { box-sizing: border-box; } -html, body { +html, +body { margin: 0; padding: 0; height: 100%; @@ -42,7 +58,8 @@ html, body { } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; @@ -71,10 +88,22 @@ body { .logo { font-size: 1.1rem; font-weight: 600; - background: var(--accent-gradient); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--accent-primary); +} + +.logo-link { + text-decoration: none; + transition: opacity var(--transition-fast); +} + +.logo-link:hover { + opacity: 0.8; +} + +.header-left { + display: flex; + align-items: center; + gap: 12px; } .toolbar { @@ -103,7 +132,7 @@ body { } .toolbar-btn.active { - background: var(--accent-gradient); + background: var(--accent-primary); border-color: transparent; color: white; } @@ -166,6 +195,37 @@ body { padding: 12px; } +.sidebar-footer { + padding: 12px; + border-top: 1px solid var(--glass-border); + display: flex; + flex-direction: column; + gap: 4px; +} + +.external-link { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border-radius: var(--radius-sm); + color: var(--text-secondary); + text-decoration: none; + font-size: 0.875rem; + transition: all var(--transition-fast); +} + +.external-link:hover { + background: var(--glass-hover); + color: var(--text-primary); +} + +.external-link .icon { + width: 18px; + height: 18px; + flex-shrink: 0; +} + /* Custom Scrollbar */ .sidebar-content::-webkit-scrollbar, .content-body::-webkit-scrollbar { @@ -214,7 +274,8 @@ body { margin: 2px 0; } -.folder, .file-link { +.folder, +.file-link { display: flex; align-items: center; gap: 10px; @@ -227,7 +288,8 @@ body { font-size: 0.875rem; } -.folder:hover, .file-link:hover { +.folder:hover, +.file-link:hover { background: var(--glass-hover); color: var(--text-primary); } @@ -238,7 +300,7 @@ body { } .file-link.active { - background: var(--accent-gradient); + background: var(--accent-primary); color: white; } @@ -282,15 +344,59 @@ body { padding: 0 24px; display: flex; align-items: center; + gap: 12px; border-bottom: 1px solid var(--glass-border); background: var(--glass-bg); flex-shrink: 0; } -.file-path { +.file-path, +.file-path-slot { font-size: 0.875rem; color: var(--text-muted); - font-family: 'SF Mono', 'Fira Code', monospace; + font-family: "SF Mono", "Fira Code", monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Per-pane navigation button - hidden by default, shown on mobile compare mode */ +.pane-nav-btn { + display: none; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: var(--radius-sm); + color: var(--text-secondary); + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + transition: all var(--transition-fast); + flex-shrink: 0; + -webkit-tap-highlight-color: transparent; +} + +.pane-nav-btn:hover, +.pane-nav-btn:active { + background: var(--glass-hover); + color: var(--text-primary); + border-color: var(--accent-primary); +} + +.pane-nav-btn .icon { + width: 14px; + height: 14px; + color: var(--accent-primary); +} + +.pane.primary .pane-nav-btn .icon { + color: var(--accent-primary); +} + +.pane.secondary .pane-nav-btn .icon { + color: var(--accent-secondary); } .content-body { @@ -305,9 +411,9 @@ body { color: var(--text-primary); } -.markdown-body h1, -.markdown-body h2, -.markdown-body h3, +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, .markdown-body h4 { color: var(--text-primary); margin-top: 2em; @@ -315,9 +421,20 @@ body { font-weight: 600; } -.markdown-body h1 { font-size: 2rem; margin-top: 0; } -.markdown-body h2 { font-size: 1.5rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 0.3em; } -.markdown-body h3 { font-size: 1.25rem; } +.markdown-body h1 { + font-size: 2rem; + margin-top: 0; +} + +.markdown-body h2 { + font-size: 1.5rem; + border-bottom: 1px solid var(--glass-border); + padding-bottom: 0.3em; +} + +.markdown-body h3 { + font-size: 1.25rem; +} .markdown-body p { margin: 1em 0; @@ -337,7 +454,7 @@ body { background: var(--bg-tertiary); padding: 2px 6px; border-radius: 4px; - font-family: 'SF Mono', 'Fira Code', monospace; + font-family: "SF Mono", "Fira Code", monospace; font-size: 0.9em; color: var(--accent-primary); } @@ -365,7 +482,8 @@ body { color: var(--text-secondary); } -.markdown-body ul, .markdown-body ol { +.markdown-body ul, +.markdown-body ol { padding-left: 24px; color: var(--text-secondary); } @@ -380,7 +498,8 @@ body { margin: 1em 0; } -.markdown-body th, .markdown-body td { +.markdown-body th, +.markdown-body td { padding: 10px 14px; border: 1px solid var(--glass-border); text-align: left; @@ -415,10 +534,8 @@ body { .intro h1 { font-size: 2.5rem; font-weight: 700; - background: var(--accent-gradient); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--accent-primary); + /* Updated to match new accent */ margin: 0 0 24px 0; } @@ -449,10 +566,8 @@ body { .stat-value { font-size: 2.5rem; font-weight: 700; - background: var(--accent-gradient); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + color: var(--accent-primary); + /* Updated to match new accent */ } .stat-label { @@ -463,7 +578,8 @@ body { } /* Loading & Error States */ -.loading, .error { +.loading, +.error { display: flex; align-items: center; justify-content: center; @@ -482,16 +598,273 @@ body { } } -@media (max-width: 900px) { +/* Mobile Styles */ +@media (max-width: 768px) { + :root { + --sidebar-width: 85vw; + --header-height: 52px; + } + + /* Header adjustments */ + #header { + padding: 0 16px; + } + + .logo { + font-size: 1rem; + } + + /* Hamburger Menu Button */ + .menu-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + padding: 0; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + border-radius: var(--radius-sm); + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + -webkit-tap-highlight-color: transparent; + } + + .menu-btn:hover, + .menu-btn:active { + background: var(--glass-hover); + color: var(--text-primary); + border-color: var(--accent-primary); + } + + .menu-btn .icon { + width: 20px; + height: 20px; + pointer-events: none; + } + + /* Toolbar adjustments */ + .toolbar { + gap: 8px; + } + + .toolbar-btn { + padding: 10px 12px; + min-height: 40px; + } + + .toolbar-btn span { + display: none; + } + + /* Sidebar as slide-out drawer */ .sidebar { + position: fixed; + top: var(--header-height); + left: 0; + bottom: 0; + width: var(--sidebar-width); + max-width: 320px; + z-index: 100; + transform: translateX(-100%); + transition: transform var(--transition-normal); + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.5); + } + + .sidebar.open { + transform: translateX(0); + } + + /* Sidebar overlay */ + .sidebar-overlay { display: none; + position: fixed; + top: var(--header-height); + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + z-index: 99; + -webkit-tap-highlight-color: transparent; + } + + .sidebar-overlay.visible { + display: block; + } + + /* Improve touch targets in sidebar */ + .folder, + .file-link, + .external-link { + padding: 12px 16px; + min-height: 44px; } - + + /* Content body adjustments */ + .content-body { + padding: 20px 16px; + } + + .content-header { + padding: 0 16px; + height: 44px; + } + + .file-path { + font-size: 0.8rem; + } + + /* Markdown adjustments for mobile */ + .markdown-body { + max-width: 100%; + } + + .markdown-body h1 { + font-size: 1.5rem; + } + + .markdown-body h2 { + font-size: 1.25rem; + } + + .markdown-body h3 { + font-size: 1.1rem; + } + + .markdown-body pre { + padding: 12px; + font-size: 0.85rem; + } + + .markdown-body table { + display: block; + overflow-x: auto; + white-space: nowrap; + } + + /* Intro section mobile */ + .intro { + padding: 24px 0; + } + + .intro h1 { + font-size: 1.75rem; + } + + .intro p { + font-size: 1rem; + } + + .stats { + flex-wrap: wrap; + gap: 24px; + margin-top: 24px; + padding-top: 24px; + } + + .stat-value { + font-size: 2rem; + } + + /* Compare mode on mobile - stack vertically */ + #viewer.compare-mode { + flex-direction: column; + } + + #viewer.compare-mode .pane { + flex: none; + height: 50vh; + min-height: 300px; + } + #viewer.compare-mode .pane.secondary { + display: flex; + border-left: none; + border-top: 2px solid var(--accent-primary); + } + + /* In compare mode, both sidebars become slide-out drawers */ + #viewer.compare-mode .sidebar { + position: fixed; + top: var(--header-height); + left: 0; + bottom: 0; + width: var(--sidebar-width); + max-width: 320px; + z-index: 100; + transform: translateX(-100%); + transition: transform var(--transition-normal); + box-shadow: 4px 0 24px rgba(0, 0, 0, 0.5); + /* Safari fix: ensure proper overflow containment */ + overflow: hidden; + display: flex; + flex-direction: column; + } + + #viewer.compare-mode .sidebar.open { + transform: translateX(0); + } + + /* Safari fix: ensure sidebar-content scrolls properly within fixed container */ + #viewer.compare-mode .sidebar-content { + flex: 1; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + /* Show per-pane nav buttons in mobile compare mode */ + #viewer.compare-mode .pane-nav-btn { + display: flex; + } + + /* Hide main hamburger in mobile compare mode - per-pane buttons replace it */ + #viewer.compare-mode~#header .menu-btn, + body:has(#viewer.compare-mode) .menu-btn { display: none; } - - .content-body { - padding: 24px; + + /* Adjust content-header for mobile compare */ + #viewer.compare-mode .content-header { + padding: 0 12px; + gap: 8px; } } + +/* Hide hamburger on desktop */ +@media (min-width: 769px) { + .menu-btn { + display: none; + } + + .sidebar-overlay { + display: none !important; + } +} + +/* Extra small devices */ +@media (max-width: 480px) { + .logo { + font-size: 0.9rem; + } + + .content-body { + padding: 16px 12px; + } + + .intro h1 { + font-size: 1.5rem; + } + + .stats { + gap: 16px; + } + + .stat-value { + font-size: 1.75rem; + } + + .stat-label { + font-size: 0.75rem; + } +} \ No newline at end of file diff --git a/docs/test-results/.last-run.json b/docs/test-results/.last-run.json new file mode 100644 index 0000000..cbcc1fb --- /dev/null +++ b/docs/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/docs/tests/docs-viewer.spec.js b/docs/tests/docs-viewer.spec.js new file mode 100644 index 0000000..c84e406 --- /dev/null +++ b/docs/tests/docs-viewer.spec.js @@ -0,0 +1,172 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +/** + * Docs Viewer E2E Tests + * + * Tests key functionality of the AI Assessment Experiment Viewer: + * A. Open article - markdown file loads and renders + * B. Comparison mode - toggle compare and dual panes work + * C. Comparison mobile - vertical stacking on small viewports + * D. URL hash navigation - deep linking works + */ + +test.describe('Docs Viewer', () => { + + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); + + // ============================================================ + // Test A: Open Article + // ============================================================ + test('A: should load and render a markdown article', async ({ page }) => { + // Navigate directly to a markdown file (more reliable than UI navigation) + const testFile = 'experiment/control-groups/dapr/dapr-claude-opus-assessment-2025-12-27.md'; + await page.goto(`/#${testFile}`); + + // Wait for content to load + const contentBody = page.locator('#content-primary .content-body'); + await expect(contentBody.locator('.markdown-body')).toBeVisible({ timeout: 10000 }); + + // Verify file path is shown in header + const filePath = page.locator('#content-primary .file-path-slot'); + await expect(filePath).toContainText('dapr-claude-opus-assessment'); + + // Verify markdown was actually parsed (has headings or paragraphs) + await expect(contentBody.locator('.markdown-body h1, .markdown-body h2, .markdown-body p').first()).toBeVisible(); + }); + + // ============================================================ + // Test B: Comparison Mode + // ============================================================ + test('B: should toggle comparison mode and show dual panes', async ({ page }) => { + // Initially, secondary pane should not be visible + const secondaryPane = page.locator('.pane.secondary'); + await expect(secondaryPane).not.toBeVisible(); + + // Click Compare button + const compareBtn = page.locator('#compare-btn'); + await compareBtn.click(); + + // Secondary pane should now be visible + await expect(secondaryPane).toBeVisible(); + + // Compare button should be active + await expect(compareBtn).toHaveClass(/active/); + + // Secondary tree should be populated + await expect(page.locator('#tree-secondary')).not.toBeEmpty(); + + // Click Compare button again to exit + await compareBtn.click(); + + // Secondary pane should be hidden again + await expect(secondaryPane).not.toBeVisible(); + }); + + // ============================================================ + // Test C: Comparison Mobile + // ============================================================ + test('C: should stack panes vertically on mobile in compare mode', async ({ page }) => { + // Set mobile viewport + await page.setViewportSize({ width: 375, height: 667 }); + + // Enter compare mode + const compareBtn = page.locator('#compare-btn'); + await compareBtn.click(); + + // Both panes should be visible + const primaryPane = page.locator('.pane.primary'); + const secondaryPane = page.locator('.pane.secondary'); + await expect(primaryPane).toBeVisible(); + await expect(secondaryPane).toBeVisible(); + + // Viewer should have compare-mode class + const viewer = page.locator('#viewer'); + await expect(viewer).toHaveClass(/compare-mode/); + + // Per-pane nav buttons should be visible on mobile compare mode + const primaryNavBtn = page.locator('#nav-btn-primary'); + const secondaryNavBtn = page.locator('#nav-btn-secondary'); + await expect(primaryNavBtn).toBeVisible(); + await expect(secondaryNavBtn).toBeVisible(); + + // Check that panes are stacked (secondary pane should be below primary) + const primaryBox = await primaryPane.boundingBox(); + const secondaryBox = await secondaryPane.boundingBox(); + + expect(primaryBox).not.toBeNull(); + expect(secondaryBox).not.toBeNull(); + + if (primaryBox && secondaryBox) { + // Secondary pane top should be at or after primary pane bottom (stacked) + expect(secondaryBox.y).toBeGreaterThanOrEqual(primaryBox.y + primaryBox.height - 5); + } + }); + + // ============================================================ + // Test D: URL Hash Navigation + // ============================================================ + test('D: should load file from URL hash (deep linking)', async ({ page }) => { + // Navigate directly to a specific file via hash + const testFilePath = 'experiment/experiment.md'; + await page.goto(`/#${testFilePath}`); + + // Wait for content to load + const contentBody = page.locator('#content-primary .content-body'); + await expect(contentBody.locator('.markdown-body')).toBeVisible({ timeout: 10000 }); + + // Verify file path is shown correctly + const filePath = page.locator('#content-primary .file-path-slot'); + await expect(filePath).toContainText('experiment.md'); + + // Verify URL hash is preserved + const url = page.url(); + expect(url).toContain(testFilePath); + }); + + // ============================================================ + // Test D2: URL Hash with Compare Mode + // ============================================================ + test('D2: should load compare mode from URL hash', async ({ page }) => { + // First, navigate to primary file and enable compare mode via UI + const primaryFile = 'experiment/experiment.md'; + await page.goto(`/#${primaryFile}`); + + // Wait for primary to load + await expect(page.locator('#content-primary .markdown-body')).toBeVisible({ timeout: 10000 }); + + // Enable compare mode via button click + const compareBtn = page.locator('#compare-btn'); + await compareBtn.click(); + + // Verify compare mode is active + const viewer = page.locator('#viewer'); + await expect(viewer).toHaveClass(/compare-mode/); + + // Secondary tree should be populated + const secondaryTree = page.locator('#tree-secondary'); + await expect(secondaryTree).not.toBeEmpty(); + + // On mobile, the secondary sidebar is a drawer that needs to be opened first + const secondaryNavBtn = page.locator('#nav-btn-secondary'); + if (await secondaryNavBtn.isVisible()) { + await secondaryNavBtn.click(); + // Wait for sidebar to slide in + await expect(page.locator('#sidebar-secondary')).toHaveClass(/open/, { timeout: 5000 }); + } + + // Click on 'experiment.pdf' which is a top-level file (always visible) + const secondaryFile = secondaryTree.locator('.file-link', { hasText: 'experiment.pdf' }); + await expect(secondaryFile).toBeVisible({ timeout: 5000 }); + await secondaryFile.click(); + + // Wait for secondary pane to have PDF content + await expect(page.locator('#content-secondary .pdf-viewer')).toBeVisible({ timeout: 10000 }); + + // Both panes should have content + await expect(page.locator('#content-primary .markdown-body')).toBeVisible(); + }); + +}); diff --git a/screenshots/3.x/assessment-meta-data-2025-12-27.png b/screenshots/3.x/assessment-meta-data-2025-12-27.png new file mode 100644 index 0000000..338f346 Binary files /dev/null and b/screenshots/3.x/assessment-meta-data-2025-12-27.png differ diff --git a/screenshots/3.x/demo.gif b/screenshots/3.x/demo.gif index 07281e5..1083625 100644 Binary files a/screenshots/3.x/demo.gif and b/screenshots/3.x/demo.gif differ diff --git a/scripts/sync-coverage-docs.py b/scripts/sync-coverage-docs.py index cd415c2..247b241 100644 --- a/scripts/sync-coverage-docs.py +++ b/scripts/sync-coverage-docs.py @@ -5,7 +5,7 @@ This script reads coverage thresholds from coverage-config.json (single source of truth) and updates: 1. README.md - Coverage badges showing minimum thresholds -2. docs/INVARIANTS.md - Coverage Invariants table +2. docs/agents/INVARIANTS.md - Coverage Invariants table Usage: python sync-coverage-docs.py # Update README.md and INVARIANTS.md @@ -178,7 +178,7 @@ def update_invariants(project_root: Path, config: dict, check_only: bool = False Returns: (changed: bool, message: str) """ - invariants_path = project_root / "docs" / "INVARIANTS.md" + invariants_path = project_root / "docs" / "agents" / "INVARIANTS.md" content = invariants_path.read_text(encoding='utf-8') # Pattern to match the coverage table diff --git a/scripts/validate-compose-k8s-parity.ps1 b/scripts/validate-compose-k8s-parity.ps1 index de57cff..e43b760 100644 --- a/scripts/validate-compose-k8s-parity.ps1 +++ b/scripts/validate-compose-k8s-parity.ps1 @@ -10,7 +10,7 @@ $ErrorActionPreference = "Stop" Write-Host "=== Docker Compose / K8s Parity Check ===" -ForegroundColor Cyan # Critical services that MUST exist in both k8s and docker-compose -# This is enforced as invariant I7 in docs/INVARIANTS.md +# This is enforced as invariant I7 in docs/agents/INVARIANTS.md $criticalServices = @( "postgres", "mongodb", diff --git a/scripts/validate-dockerfile-context.py b/scripts/validate-dockerfile-context.py index a51707e..8e049b3 100644 --- a/scripts/validate-dockerfile-context.py +++ b/scripts/validate-dockerfile-context.py @@ -2,7 +2,7 @@ """ Validate CI Docker build contexts match Dockerfile path assumptions. -ASSUMPTIONS (documented in docs/INVARIANTS.md B1): +ASSUMPTIONS (documented in docs/agents/INVARIANTS.md B1): 1. Dockerfiles with COPY paths starting with 'src/' or 'contracts/' require repo root as build context (context: '.') 2. Dockerfiles with local-relative COPY paths (e.g., 'package.json', 'go.mod') diff --git a/src/interfaces/tui/src/doctor.rs b/src/interfaces/tui/src/doctor.rs index 98ec9fa..d0c90bd 100644 --- a/src/interfaces/tui/src/doctor.rs +++ b/src/interfaces/tui/src/doctor.rs @@ -21,7 +21,7 @@ pub const SUPPORT_MATRIX: &[(&str, &str)] = &[ ("linux", "aarch64"), ]; -pub const SUPPORT_MATRIX_URL: &str = "https://github.com/oddessentials/odd-demonstration/blob/main/docs/SUPPORT_MATRIX.md"; +pub const SUPPORT_MATRIX_URL: &str = "https://github.com/oddessentials/odd-demonstration/blob/main/docs/agents/SUPPORT_MATRIX.md"; /// Check if current platform is supported (no I/O, pure computation) pub fn check_platform_support() -> Result<(), String> {