noecosystem-design 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.github/CODEOWNERS +10 -0
- package/.github/dependabot.yml +12 -0
- package/.github/pull_request_template.md +17 -0
- package/.github/workflows/ci.yml +173 -0
- package/.github/workflows/security.yml +40 -0
- package/.mcp.json +8 -0
- package/.node-version +1 -0
- package/AGENTS.md +62 -0
- package/CONTRIBUTING.md +9 -0
- package/README.md +48 -0
- package/SECURITY.md +7 -0
- package/apps/showcase/index.html +28 -0
- package/apps/showcase/package.json +30 -0
- package/apps/showcase/src/app.css +8 -0
- package/apps/showcase/src/app.tsx +10 -0
- package/apps/showcase/src/catalog/agent-composer-preview.tsx +17 -0
- package/apps/showcase/src/catalog/catalog-index.tsx +2676 -0
- package/apps/showcase/src/catalog/catalog-shell.browser.test.tsx +73 -0
- package/apps/showcase/src/catalog/catalog-shell.tsx +506 -0
- package/apps/showcase/src/main.tsx +17 -0
- package/apps/showcase/tsconfig.json +11 -0
- package/apps/showcase/vite.config.ts +15 -0
- package/apps/storybook/.storybook/main.ts +21 -0
- package/apps/storybook/.storybook/preview.tsx +87 -0
- package/apps/storybook/.storybook/vitest.setup.ts +26 -0
- package/apps/storybook/README.md +5 -0
- package/apps/storybook/package.json +31 -0
- package/apps/storybook/stories/AgentStates.stories.tsx +72 -0
- package/apps/storybook/stories/Bidi.stories.tsx +31 -0
- package/apps/storybook/stories/Button.stories.tsx +19 -0
- package/apps/storybook/stories/Surfaces.stories.tsx +28 -0
- package/apps/storybook/stories/catalog/about-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/activity-feed.stories.tsx +50 -0
- package/apps/storybook/stories/catalog/activity-gauge.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/agent-composer.stories.tsx +49 -0
- package/apps/storybook/stories/catalog/agent-message.stories.tsx +37 -0
- package/apps/storybook/stories/catalog/agent-plan.stories.tsx +35 -0
- package/apps/storybook/stories/catalog/agent-workspace.stories.tsx +60 -0
- package/apps/storybook/stories/catalog/alert.stories.tsx +41 -0
- package/apps/storybook/stories/catalog/app-frame.stories.tsx +47 -0
- package/apps/storybook/stories/catalog/approval-request.stories.tsx +30 -0
- package/apps/storybook/stories/catalog/avatar.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/badge-group.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/badge.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/banner.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/bidi-text.stories.tsx +47 -0
- package/apps/storybook/stories/catalog/blog-index-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/blog-post-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/blog-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/bottom-navigation.stories.tsx +27 -0
- package/apps/storybook/stories/catalog/button-group.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/button.stories.tsx +27 -0
- package/apps/storybook/stories/catalog/calendar.stories.tsx +47 -0
- package/apps/storybook/stories/catalog/card.stories.tsx +40 -0
- package/apps/storybook/stories/catalog/careers-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/carousel.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/chart-frame.stories.tsx +34 -0
- package/apps/storybook/stories/catalog/chart.stories.tsx +43 -0
- package/apps/storybook/stories/catalog/checkbox.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/code-and-terminal.stories.tsx +39 -0
- package/apps/storybook/stories/catalog/color-picker.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/command-menu.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/contact-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/contact-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/content-divider.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/content-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/context-menu.stories.tsx +66 -0
- package/apps/storybook/stories/catalog/credit-card.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/cta-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/dashboard-01.stories.tsx +28 -0
- package/apps/storybook/stories/catalog/dashboard-02.stories.tsx +28 -0
- package/apps/storybook/stories/catalog/dashboard-shell.stories.tsx +65 -0
- package/apps/storybook/stories/catalog/data-list.stories.tsx +37 -0
- package/apps/storybook/stories/catalog/data-table.stories.tsx +51 -0
- package/apps/storybook/stories/catalog/date-picker.stories.tsx +31 -0
- package/apps/storybook/stories/catalog/dialog.stories.tsx +68 -0
- package/apps/storybook/stories/catalog/direction-provider.stories.tsx +36 -0
- package/apps/storybook/stories/catalog/drawer.stories.tsx +58 -0
- package/apps/storybook/stories/catalog/dropdown.stories.tsx +67 -0
- package/apps/storybook/stories/catalog/email-template.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/empty-state.stories.tsx +19 -0
- package/apps/storybook/stories/catalog/error-state.stories.tsx +19 -0
- package/apps/storybook/stories/catalog/fab.stories.tsx +22 -0
- package/apps/storybook/stories/catalog/faq-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/faq-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/feature-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/featured-icon.stories.tsx +45 -0
- package/apps/storybook/stories/catalog/field.stories.tsx +35 -0
- package/apps/storybook/stories/catalog/file-uploader.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/filter-bar.stories.tsx +33 -0
- package/apps/storybook/stories/catalog/footer.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/forgot-password-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/gradient-picker.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/header-navigation.stories.tsx +46 -0
- package/apps/storybook/stories/catalog/hero-header.stories.tsx +36 -0
- package/apps/storybook/stories/catalog/icon-system.stories.tsx +48 -0
- package/apps/storybook/stories/catalog/illustration.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/image-picker.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/informational-page-01.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/informational-page-02.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/inline-cta.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/input.stories.tsx +21 -0
- package/apps/storybook/stories/catalog/landing-page.stories.tsx +28 -0
- package/apps/storybook/stories/catalog/legal-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/login-page.stories.tsx +42 -0
- package/apps/storybook/stories/catalog/marketing-404-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/marketing-header.stories.tsx +35 -0
- package/apps/storybook/stories/catalog/menu.stories.tsx +46 -0
- package/apps/storybook/stories/catalog/messaging.stories.tsx +33 -0
- package/apps/storybook/stories/catalog/metric-card.stories.tsx +20 -0
- package/apps/storybook/stories/catalog/metrics-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/mobile-screen.stories.tsx +56 -0
- package/apps/storybook/stories/catalog/multi-select.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/newsletter.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/not-found.stories.tsx +30 -0
- package/apps/storybook/stories/catalog/notification.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/page-header.stories.tsx +37 -0
- package/apps/storybook/stories/catalog/pagination.stories.tsx +27 -0
- package/apps/storybook/stories/catalog/pie-chart.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/pricing-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/pricing-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/progress-indicator.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/progress-steps.stories.tsx +33 -0
- package/apps/storybook/stories/catalog/qr-code.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/radar-chart.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/radio-group.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/rating.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/rich-text-editor.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/run-inspector.stories.tsx +23 -0
- package/apps/storybook/stories/catalog/section-footer.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/section-header.stories.tsx +31 -0
- package/apps/storybook/stories/catalog/select.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/settings-01.stories.tsx +24 -0
- package/apps/storybook/stories/catalog/settings-02.stories.tsx +24 -0
- package/apps/storybook/stories/catalog/settings-page.stories.tsx +48 -0
- package/apps/storybook/stories/catalog/sidebar-navigation.stories.tsx +40 -0
- package/apps/storybook/stories/catalog/sign-in-form.stories.tsx +28 -0
- package/apps/storybook/stories/catalog/sign-up-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/skeleton.stories.tsx +21 -0
- package/apps/storybook/stories/catalog/slider.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/social-button.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/social-proof.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/spinner.stories.tsx +15 -0
- package/apps/storybook/stories/catalog/store-button.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/tabs.stories.tsx +38 -0
- package/apps/storybook/stories/catalog/tags.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/team-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/team-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/testimonial-section.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/textarea.stories.tsx +22 -0
- package/apps/storybook/stories/catalog/toggle.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/tool-call.stories.tsx +40 -0
- package/apps/storybook/stories/catalog/tooltip.stories.tsx +30 -0
- package/apps/storybook/stories/catalog/tree-view.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/utility-button.stories.tsx +60 -0
- package/apps/storybook/stories/catalog/verification-code.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/verification-page.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/video-player.stories.tsx +18 -0
- package/apps/storybook/stories/catalog/workflow-canvas.stories.tsx +45 -0
- package/apps/storybook/stories/catalog/workflow-node.stories.tsx +24 -0
- package/apps/storybook/vitest.config.ts +39 -0
- package/apps/web/README.md +13 -0
- package/apps/web/app.js +483 -0
- package/apps/web/favicon.svg +6 -0
- package/apps/web/index.html +205 -0
- package/apps/web/styles.css +1823 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/README-FIRST.txt +20 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/SHA256SUMS.txt +11 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-delivery-metadata.json +25 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-design-system-evidence.zip +0 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-design-system-execution-report.md +60 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-design-system-manifest.json +106 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-design-system-source.tar.gz +0 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-design-system-source.zip +0 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-design-system.bundle +0 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-evidence-index.html +38 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-verification-status.txt +15 -0
- package/artifacts/original-delivery/NOE-Design-System-Delivery/noe-visual-contact-sheet.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/README.txt +13 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/browser-console.json +4 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/layout-audit.json +4 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/review/contact-sheet.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-dark-1024.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-dark-1280.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-dark-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-dark-390.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-dark-768.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-light-1024.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-light-1280.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-light-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-light-390.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-en-light-768.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-dark-1024.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-dark-1280.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-dark-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-dark-390.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-dark-768.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-light-1024.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-light-1280.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-light-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-light-390.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-fa-light-768.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-pseudo-ltr-1024.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/agent-pseudo-rtl-1024.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/consumer-en-light-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/consumer-fa-dark-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/dashboard-en-light-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/dashboard-fa-dark-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/workflow-en-light-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots/workflow-fa-dark-1440.png +0 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/screenshots-manifest.json +203 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/verification-result.json +165 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/verification-result.txt +32 -0
- package/artifacts/original-delivery/noe-design-system-evidence/evidence/visual-index.html +1 -0
- package/artifacts/original-delivery/noe-design-system-evidence/noe-delivery-metadata.json +25 -0
- package/artifacts/original-delivery/noe-design-system-evidence/noe-design-system-execution-report.md +60 -0
- package/artifacts/original-delivery/noe-design-system-evidence/noe-design-system-manifest.json +106 -0
- package/artifacts/original-delivery/noe-design-system-evidence/noe-verification-status.txt +15 -0
- package/artifacts/original-delivery/noe-design-system.bundle +0 -0
- package/artifacts/original-delivery/source-evidence/verification-result.json +165 -0
- package/artifacts/original-delivery/source-evidence/verification-result.txt +32 -0
- package/artifacts/registry-consumer-report.json +153 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Black.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Bold.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-DemiBold.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-ExtraBlack.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-ExtraBold.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Heavy.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Light.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Medium.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Regular.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-Thin.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff/IRANYekanXFaNum-UltraLight.woff +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Black.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Bold.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-DemiBold.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-ExtraBlack.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-ExtraBold.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Heavy.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Light.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Medium.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Regular.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-Thin.woff2 +0 -0
- package/artifacts/source-inputs/persian-fonts/Woff2/IRANYekanXFaNum-UltraLight.woff2 +0 -0
- package/biome.json +170 -0
- package/components.json +23 -0
- package/docs/adr/0001-semantic-foundation.md +5 -0
- package/docs/adr/0002-locale-direction.md +5 -0
- package/docs/adr/0003-registry-mcp.md +5 -0
- package/docs/adr/0004-testing-evidence.md +5 -0
- package/docs/adr/0005-noe-package-namespace.md +9 -0
- package/docs/adr/0006-icons8-icon-policy.md +9 -0
- package/docs/adr/0007-registry-publication-contract.md +9 -0
- package/docs/adr/0008-capability-library-adapter-boundaries.md +16 -0
- package/docs/adr/0009-calendar-system-jalali.md +29 -0
- package/docs/adr/0010-canonical-manifest-ownership.md +29 -0
- package/docs/adr/0011-palette-graphite-governance.md +27 -0
- package/docs/adr/0012-persian-font-gating.md +25 -0
- package/docs/adr/0013-cinematic-token-exception.md +32 -0
- package/docs/design-system/README.md +16 -0
- package/docs/design-system/accessibility.md +20 -0
- package/docs/design-system/agent-guide.md +11 -0
- package/docs/design-system/agentic-ui.md +21 -0
- package/docs/design-system/architecture.md +40 -0
- package/docs/design-system/brand.md +59 -0
- package/docs/design-system/ci-cd.md +46 -0
- package/docs/design-system/components.md +88 -0
- package/docs/design-system/contributing.md +7 -0
- package/docs/design-system/foundations.md +40 -0
- package/docs/design-system/i18n.md +24 -0
- package/docs/design-system/inventory.md +389 -0
- package/docs/design-system/mcp.md +7 -0
- package/docs/design-system/motion.md +7 -0
- package/docs/design-system/noe-brand-audit.md +33 -0
- package/docs/design-system/registry.md +16 -0
- package/docs/design-system/rtl.md +27 -0
- package/docs/design-system/security.md +16 -0
- package/docs/design-system/testing.md +39 -0
- package/docs/design-system/tokens.md +38 -0
- package/docs/design-system/visual-qa.md +10 -0
- package/docs/superpowers/plans/2026-08-25-noe-design-system-development-ready.md +833 -0
- package/docs/superpowers/plans/2026-08-26-noe-complete-catalog.md +422 -0
- package/docs/superpowers/specs/2026-08-25-noe-design-system-development-ready-design.md +260 -0
- package/docs/superpowers/specs/2026-08-26-noe-complete-catalog-design.md +187 -0
- package/llms.txt +20 -0
- package/package.json +68 -0
- package/packages/ai-ui/package.json +15 -0
- package/packages/ai-ui/src/activity-feed.tsx +60 -0
- package/packages/ai-ui/src/agent-composer.tsx +54 -0
- package/packages/ai-ui/src/agent-message.tsx +50 -0
- package/packages/ai-ui/src/agent-plan.tsx +69 -0
- package/packages/ai-ui/src/agent-workspace.tsx +63 -0
- package/packages/ai-ui/src/approval-request.tsx +93 -0
- package/packages/ai-ui/src/index.ts +8 -0
- package/packages/ai-ui/src/run-inspector.tsx +54 -0
- package/packages/ai-ui/src/tool-call.tsx +71 -0
- package/packages/data-ui/package.json +16 -0
- package/packages/data-ui/src/activity-gauge.tsx +38 -0
- package/packages/data-ui/src/chart-frame.tsx +41 -0
- package/packages/data-ui/src/chart.tsx +131 -0
- package/packages/data-ui/src/dashboard-01.tsx +39 -0
- package/packages/data-ui/src/dashboard-02.tsx +33 -0
- package/packages/data-ui/src/dashboard-shell.tsx +56 -0
- package/packages/data-ui/src/data-table.tsx +99 -0
- package/packages/data-ui/src/filter-bar.tsx +28 -0
- package/packages/data-ui/src/index.ts +12 -0
- package/packages/data-ui/src/metric-card.tsx +43 -0
- package/packages/data-ui/src/pagination.tsx +67 -0
- package/packages/data-ui/src/pie-chart.tsx +38 -0
- package/packages/data-ui/src/radar-chart.tsx +38 -0
- package/packages/design-tokens/package.json +23 -0
- package/packages/design-tokens/src/base.css +255 -0
- package/packages/design-tokens/src/fonts/IRANYekanXFaNum-Bold.woff2 +0 -0
- package/packages/design-tokens/src/fonts/IRANYekanXFaNum-DemiBold.woff2 +0 -0
- package/packages/design-tokens/src/fonts/IRANYekanXFaNum-Medium.woff2 +0 -0
- package/packages/design-tokens/src/fonts/IRANYekanXFaNum-Regular.woff2 +0 -0
- package/packages/design-tokens/src/fonts/IRANYekanXVF.ttf +0 -0
- package/packages/design-tokens/src/fonts/README.md +13 -0
- package/packages/design-tokens/src/fonts.css +49 -0
- package/packages/design-tokens/src/index.test.ts +36 -0
- package/packages/design-tokens/src/index.ts +5 -0
- package/packages/design-tokens/src/palette.ts +27 -0
- package/packages/design-tokens/src/tailwind.css +3 -0
- package/packages/design-tokens/src/theme.css +67 -0
- package/packages/design-tokens/src/tokens.css +251 -0
- package/packages/design-tokens/src/tokens.json +275 -0
- package/packages/design-tokens/src/tokens.mjs +276 -0
- package/packages/i18n/package.json +13 -0
- package/packages/i18n/src/index.ts +17 -0
- package/packages/i18n/src/messages/en.json +116 -0
- package/packages/i18n/src/messages/fa.json +116 -0
- package/packages/i18n/src/runtime.d.mts +42 -0
- package/packages/i18n/src/runtime.mjs +113 -0
- package/packages/i18n/src/translate.test.ts +50 -0
- package/packages/i18n/src/translate.ts +29 -0
- package/packages/icons/package.json +12 -0
- package/packages/icons/src/adapters.tsx +167 -0
- package/packages/icons/src/index.tsx +103 -0
- package/packages/registry/README.md +7 -0
- package/packages/registry/catalog-index.json +1345 -0
- package/packages/registry/component-audit.json +1038 -0
- package/packages/registry/coss-adoption.json +281 -0
- package/packages/registry/docs-index.json +1881 -0
- package/packages/registry/manifest.json +11081 -0
- package/packages/registry/manifest.schema.json +135 -0
- package/packages/registry/package.json +10 -0
- package/packages/registry/r/activity-feed.json +67 -0
- package/packages/registry/r/agent-composer.json +79 -0
- package/packages/registry/r/agent-message.json +72 -0
- package/packages/registry/r/agent-plan.json +67 -0
- package/packages/registry/r/agent-workspace.json +67 -0
- package/packages/registry/r/alert.json +68 -0
- package/packages/registry/r/app-frame.json +67 -0
- package/packages/registry/r/approval-request.json +84 -0
- package/packages/registry/r/badge.json +68 -0
- package/packages/registry/r/bidi-text.json +70 -0
- package/packages/registry/r/bottom-navigation.json +67 -0
- package/packages/registry/r/button.json +83 -0
- package/packages/registry/r/calendar.json +97 -0
- package/packages/registry/r/card.json +67 -0
- package/packages/registry/r/chart-frame.json +67 -0
- package/packages/registry/r/chart.json +67 -0
- package/packages/registry/r/code-block.json +67 -0
- package/packages/registry/r/context-menu.json +81 -0
- package/packages/registry/r/dashboard-01.json +86 -0
- package/packages/registry/r/dashboard-02.json +84 -0
- package/packages/registry/r/dashboard-shell.json +67 -0
- package/packages/registry/r/data-list.json +67 -0
- package/packages/registry/r/data-table.json +72 -0
- package/packages/registry/r/date-picker.json +117 -0
- package/packages/registry/r/dialog.json +84 -0
- package/packages/registry/r/direction-provider.json +54 -0
- package/packages/registry/r/drawer.json +80 -0
- package/packages/registry/r/dropdown.json +78 -0
- package/packages/registry/r/empty-state.json +67 -0
- package/packages/registry/r/error-state.json +67 -0
- package/packages/registry/r/fab.json +80 -0
- package/packages/registry/r/featured-icon.json +71 -0
- package/packages/registry/r/filter-bar.json +69 -0
- package/packages/registry/r/gradient-picker.json +62 -0
- package/packages/registry/r/header-navigation.json +67 -0
- package/packages/registry/r/hero-header.json +63 -0
- package/packages/registry/r/icon-system.json +59 -0
- package/packages/registry/r/image-picker.json +62 -0
- package/packages/registry/r/inline-cta.json +73 -0
- package/packages/registry/r/input.json +73 -0
- package/packages/registry/r/landing-page.json +70 -0
- package/packages/registry/r/login-page.json +71 -0
- package/packages/registry/r/marketing-header.json +63 -0
- package/packages/registry/r/menu.json +74 -0
- package/packages/registry/r/messaging.json +69 -0
- package/packages/registry/r/metric-card.json +67 -0
- package/packages/registry/r/mobile-screen.json +67 -0
- package/packages/registry/r/not-found.json +63 -0
- package/packages/registry/r/page-header.json +67 -0
- package/packages/registry/r/pagination.json +91 -0
- package/packages/registry/r/progress-steps.json +69 -0
- package/packages/registry/r/run-inspector.json +72 -0
- package/packages/registry/r/section-header.json +67 -0
- package/packages/registry/r/settings-01.json +94 -0
- package/packages/registry/r/settings-02.json +99 -0
- package/packages/registry/r/settings-page.json +67 -0
- package/packages/registry/r/sidebar-navigation.json +67 -0
- package/packages/registry/r/sign-in-form.json +89 -0
- package/packages/registry/r/skeleton.json +67 -0
- package/packages/registry/r/spinner.json +67 -0
- package/packages/registry/r/tabs.json +69 -0
- package/packages/registry/r/textarea.json +73 -0
- package/packages/registry/r/tool-call.json +72 -0
- package/packages/registry/r/tooltip.json +73 -0
- package/packages/registry/r/utility-button.json +83 -0
- package/packages/registry/r/workflow-canvas.json +67 -0
- package/packages/registry/r/workflow-node.json +72 -0
- package/packages/registry/r-planned-backup/about-page.json +14 -0
- package/packages/registry/r-planned-backup/activity-gauge.json +14 -0
- package/packages/registry/r-planned-backup/avatar.json +14 -0
- package/packages/registry/r-planned-backup/badge-group.json +14 -0
- package/packages/registry/r-planned-backup/banner.json +14 -0
- package/packages/registry/r-planned-backup/blog-index-page.json +14 -0
- package/packages/registry/r-planned-backup/blog-post-page.json +14 -0
- package/packages/registry/r-planned-backup/blog-section.json +14 -0
- package/packages/registry/r-planned-backup/button-group.json +14 -0
- package/packages/registry/r-planned-backup/careers-section.json +14 -0
- package/packages/registry/r-planned-backup/carousel.json +14 -0
- package/packages/registry/r-planned-backup/checkbox.json +14 -0
- package/packages/registry/r-planned-backup/color-picker.json +14 -0
- package/packages/registry/r-planned-backup/command-menu.json +14 -0
- package/packages/registry/r-planned-backup/contact-page.json +14 -0
- package/packages/registry/r-planned-backup/contact-section.json +14 -0
- package/packages/registry/r-planned-backup/content-divider.json +14 -0
- package/packages/registry/r-planned-backup/content-section.json +14 -0
- package/packages/registry/r-planned-backup/credit-card.json +14 -0
- package/packages/registry/r-planned-backup/cta-section.json +14 -0
- package/packages/registry/r-planned-backup/email-template.json +14 -0
- package/packages/registry/r-planned-backup/faq-page.json +14 -0
- package/packages/registry/r-planned-backup/faq-section.json +14 -0
- package/packages/registry/r-planned-backup/feature-section.json +14 -0
- package/packages/registry/r-planned-backup/file-uploader.json +14 -0
- package/packages/registry/r-planned-backup/footer.json +14 -0
- package/packages/registry/r-planned-backup/forgot-password-page.json +14 -0
- package/packages/registry/r-planned-backup/illustration.json +14 -0
- package/packages/registry/r-planned-backup/informational-page-01.json +14 -0
- package/packages/registry/r-planned-backup/informational-page-02.json +14 -0
- package/packages/registry/r-planned-backup/legal-page.json +14 -0
- package/packages/registry/r-planned-backup/marketing-404-page.json +14 -0
- package/packages/registry/r-planned-backup/metrics-section.json +14 -0
- package/packages/registry/r-planned-backup/multi-select.json +14 -0
- package/packages/registry/r-planned-backup/newsletter.json +14 -0
- package/packages/registry/r-planned-backup/notification.json +14 -0
- package/packages/registry/r-planned-backup/pie-chart.json +14 -0
- package/packages/registry/r-planned-backup/pricing-page.json +14 -0
- package/packages/registry/r-planned-backup/pricing-section.json +14 -0
- package/packages/registry/r-planned-backup/progress-indicator.json +14 -0
- package/packages/registry/r-planned-backup/qr-code.json +14 -0
- package/packages/registry/r-planned-backup/radar-chart.json +14 -0
- package/packages/registry/r-planned-backup/radio-group.json +14 -0
- package/packages/registry/r-planned-backup/rating.json +14 -0
- package/packages/registry/r-planned-backup/rich-text-editor.json +14 -0
- package/packages/registry/r-planned-backup/section-footer.json +14 -0
- package/packages/registry/r-planned-backup/select.json +14 -0
- package/packages/registry/r-planned-backup/sign-up-page.json +14 -0
- package/packages/registry/r-planned-backup/slider.json +14 -0
- package/packages/registry/r-planned-backup/social-button.json +14 -0
- package/packages/registry/r-planned-backup/social-proof.json +14 -0
- package/packages/registry/r-planned-backup/store-button.json +14 -0
- package/packages/registry/r-planned-backup/tags.json +14 -0
- package/packages/registry/r-planned-backup/team-page.json +14 -0
- package/packages/registry/r-planned-backup/team-section.json +14 -0
- package/packages/registry/r-planned-backup/testimonial-section.json +14 -0
- package/packages/registry/r-planned-backup/toggle.json +14 -0
- package/packages/registry/r-planned-backup/tree-view.json +14 -0
- package/packages/registry/r-planned-backup/verification-code.json +14 -0
- package/packages/registry/r-planned-backup/verification-page.json +14 -0
- package/packages/registry/r-planned-backup/video-player.json +14 -0
- package/packages/registry/registry.json +1997 -0
- package/packages/registry/search-index.json +3942 -0
- package/packages/testing/package.json +9 -0
- package/packages/testing/src/index.mjs +10 -0
- package/packages/ui/package.json +26 -0
- package/packages/ui/src/about-page.tsx +19 -0
- package/packages/ui/src/alert.tsx +67 -0
- package/packages/ui/src/app-frame.tsx +63 -0
- package/packages/ui/src/avatar.tsx +19 -0
- package/packages/ui/src/badge-group.tsx +19 -0
- package/packages/ui/src/badge.tsx +35 -0
- package/packages/ui/src/banner.tsx +19 -0
- package/packages/ui/src/bidi.tsx +80 -0
- package/packages/ui/src/blog-index-page.tsx +19 -0
- package/packages/ui/src/blog-post-page.tsx +19 -0
- package/packages/ui/src/blog-section.tsx +19 -0
- package/packages/ui/src/bottom-navigation.tsx +60 -0
- package/packages/ui/src/button-group.tsx +19 -0
- package/packages/ui/src/button.browser.test.tsx +38 -0
- package/packages/ui/src/button.tsx +81 -0
- package/packages/ui/src/calendar-system.test.ts +103 -0
- package/packages/ui/src/calendar-system.ts +120 -0
- package/packages/ui/src/calendar.browser.test.tsx +52 -0
- package/packages/ui/src/calendar.tsx +339 -0
- package/packages/ui/src/card.tsx +63 -0
- package/packages/ui/src/careers-section.tsx +19 -0
- package/packages/ui/src/carousel.tsx +19 -0
- package/packages/ui/src/checkbox.tsx +17 -0
- package/packages/ui/src/cinematic/cinematic.css +2020 -0
- package/packages/ui/src/cinematic/ecosystem-motion-showcase.tsx +265 -0
- package/packages/ui/src/cinematic/ecosystem-sphere.tsx +627 -0
- package/packages/ui/src/cinematic/index.ts +5 -0
- package/packages/ui/src/cinematic/kinetic-experience.tsx +207 -0
- package/packages/ui/src/cinematic/portfolio.ts +132 -0
- package/packages/ui/src/cinematic/system-anatomy.tsx +173 -0
- package/packages/ui/src/code.tsx +119 -0
- package/packages/ui/src/color-picker.tsx +22 -0
- package/packages/ui/src/command-menu.tsx +19 -0
- package/packages/ui/src/contact-page.tsx +19 -0
- package/packages/ui/src/contact-section.tsx +19 -0
- package/packages/ui/src/content-divider.tsx +19 -0
- package/packages/ui/src/content-section.tsx +19 -0
- package/packages/ui/src/context-menu.tsx +208 -0
- package/packages/ui/src/credit-card.tsx +19 -0
- package/packages/ui/src/cta-section.tsx +19 -0
- package/packages/ui/src/data-list.tsx +81 -0
- package/packages/ui/src/date-picker.browser.test.tsx +48 -0
- package/packages/ui/src/date-picker.tsx +248 -0
- package/packages/ui/src/dialog.browser.test.tsx +32 -0
- package/packages/ui/src/dialog.tsx +144 -0
- package/packages/ui/src/direction.tsx +46 -0
- package/packages/ui/src/drawer.tsx +179 -0
- package/packages/ui/src/dropdown.tsx +200 -0
- package/packages/ui/src/email-template.tsx +19 -0
- package/packages/ui/src/empty-state.tsx +43 -0
- package/packages/ui/src/error-state.tsx +44 -0
- package/packages/ui/src/fab.tsx +32 -0
- package/packages/ui/src/faq-page.tsx +19 -0
- package/packages/ui/src/faq-section.tsx +19 -0
- package/packages/ui/src/feature-section.tsx +19 -0
- package/packages/ui/src/featured-icon.tsx +63 -0
- package/packages/ui/src/field.browser.test.tsx +33 -0
- package/packages/ui/src/field.tsx +117 -0
- package/packages/ui/src/file-uploader.tsx +19 -0
- package/packages/ui/src/footer.tsx +19 -0
- package/packages/ui/src/forgot-password-page.tsx +19 -0
- package/packages/ui/src/gradient-picker.tsx +65 -0
- package/packages/ui/src/header-navigation.tsx +71 -0
- package/packages/ui/src/hero-header.tsx +57 -0
- package/packages/ui/src/illustration.tsx +19 -0
- package/packages/ui/src/image-picker.tsx +56 -0
- package/packages/ui/src/index.ts +110 -0
- package/packages/ui/src/informational-page-01.tsx +19 -0
- package/packages/ui/src/informational-page-02.tsx +19 -0
- package/packages/ui/src/inline-cta.tsx +29 -0
- package/packages/ui/src/input.tsx +44 -0
- package/packages/ui/src/landing-page.tsx +56 -0
- package/packages/ui/src/legal-page.tsx +19 -0
- package/packages/ui/src/lib/cn.ts +6 -0
- package/packages/ui/src/lib/i18n-messages.ts +40 -0
- package/packages/ui/src/login-page.tsx +78 -0
- package/packages/ui/src/marketing-404-page.tsx +19 -0
- package/packages/ui/src/marketing-header.tsx +53 -0
- package/packages/ui/src/menu.tsx +220 -0
- package/packages/ui/src/messaging.tsx +62 -0
- package/packages/ui/src/metrics-section.tsx +19 -0
- package/packages/ui/src/mobile-screen.tsx +37 -0
- package/packages/ui/src/multi-select.tsx +16 -0
- package/packages/ui/src/newsletter.tsx +19 -0
- package/packages/ui/src/not-found.tsx +60 -0
- package/packages/ui/src/notification.tsx +19 -0
- package/packages/ui/src/page-header.tsx +80 -0
- package/packages/ui/src/pricing-page.tsx +19 -0
- package/packages/ui/src/pricing-section.tsx +19 -0
- package/packages/ui/src/progress-indicator.tsx +19 -0
- package/packages/ui/src/progress-steps.tsx +85 -0
- package/packages/ui/src/qr-code.tsx +19 -0
- package/packages/ui/src/radio-group.tsx +26 -0
- package/packages/ui/src/rating.tsx +19 -0
- package/packages/ui/src/rich-text-editor.tsx +18 -0
- package/packages/ui/src/section-footer.tsx +19 -0
- package/packages/ui/src/section-header.tsx +37 -0
- package/packages/ui/src/select.tsx +27 -0
- package/packages/ui/src/settings-01.tsx +49 -0
- package/packages/ui/src/settings-02.tsx +58 -0
- package/packages/ui/src/settings-page.tsx +62 -0
- package/packages/ui/src/sidebar-navigation.tsx +77 -0
- package/packages/ui/src/sign-in-form.tsx +60 -0
- package/packages/ui/src/sign-up-page.tsx +19 -0
- package/packages/ui/src/skeleton.tsx +14 -0
- package/packages/ui/src/slider.tsx +13 -0
- package/packages/ui/src/social-button.tsx +19 -0
- package/packages/ui/src/social-proof.tsx +19 -0
- package/packages/ui/src/spinner.tsx +26 -0
- package/packages/ui/src/store-button.tsx +19 -0
- package/packages/ui/src/tabs.browser.test.tsx +33 -0
- package/packages/ui/src/tabs.tsx +175 -0
- package/packages/ui/src/tags.tsx +11 -0
- package/packages/ui/src/team-page.tsx +19 -0
- package/packages/ui/src/team-section.tsx +19 -0
- package/packages/ui/src/testimonial-section.tsx +19 -0
- package/packages/ui/src/textarea.tsx +45 -0
- package/packages/ui/src/theme-provider.browser.test.tsx +90 -0
- package/packages/ui/src/theme-provider.tsx +417 -0
- package/packages/ui/src/theme-toggle.tsx +107 -0
- package/packages/ui/src/toggle.tsx +34 -0
- package/packages/ui/src/tooltip.tsx +50 -0
- package/packages/ui/src/tree-view.tsx +19 -0
- package/packages/ui/src/utility-button.tsx +33 -0
- package/packages/ui/src/utils.ts +13 -0
- package/packages/ui/src/verification-code.tsx +27 -0
- package/packages/ui/src/verification-page.tsx +19 -0
- package/packages/ui/src/video-player.tsx +19 -0
- package/packages/workflow-ui/package.json +15 -0
- package/packages/workflow-ui/src/index.ts +2 -0
- package/packages/workflow-ui/src/workflow-canvas.tsx +51 -0
- package/packages/workflow-ui/src/workflow-node.tsx +61 -0
- package/playwright.config.ts +58 -0
- package/pnpm-workspace.yaml +5 -0
- package/registry.json +216 -0
- package/scripts/build-static.mjs +68 -0
- package/scripts/capture-visuals.py +106 -0
- package/scripts/capture-visuals.sh +10 -0
- package/scripts/create-visual-index.mjs +16 -0
- package/scripts/enrich-registry-payloads.mjs +83 -0
- package/scripts/fix-noe-imports.mjs +134 -0
- package/scripts/fix-registry-closure.mjs +110 -0
- package/scripts/fix-registry-deps-cleanup.mjs +149 -0
- package/scripts/fix-relative-for-registry.mjs +103 -0
- package/scripts/generate-design-tokens.mjs +134 -0
- package/scripts/generate-inventory.mjs +62 -0
- package/scripts/generate-manifest.mjs +1149 -0
- package/scripts/generate-phase4.mjs +875 -0
- package/scripts/validate-manifest.mjs +477 -0
- package/scripts/validate-pngs.mjs +31 -0
- package/scripts/verify-registry-consumer.mjs +319 -0
- package/scripts/verify-registry-dependencies.mjs +162 -0
- package/scripts/verify.mjs +316 -0
- package/tests/catalog-audit.test.mjs +156 -0
- package/tests/e2e/capture-visuals.mjs +34 -0
- package/tests/e2e/catalog-typography.spec.ts +110 -0
- package/tests/e2e/catalog.spec.ts +68 -0
- package/tests/e2e/direction.spec.ts +60 -0
- package/tests/e2e/responsive.spec.ts +30 -0
- package/tests/e2e/surfaces.spec.ts +51 -0
- package/tests/i18n.test.mjs +36 -0
- package/tests/icons.test.mjs +21 -0
- package/tests/registry-publication.test.mjs +88 -0
- package/tests/registry.test.mjs +51 -0
- package/tests/rtl.test.mjs +21 -0
- package/tests/static-app.test.mjs +49 -0
- package/tests/tokens.test.mjs +24 -0
- package/tests/workspace.test.mjs +25 -0
- package/tsconfig.base.json +69 -0
- package/tsconfig.json +27 -0
- package/tsconfig.verify.json +19 -0
- package/types/offline-react.d.ts +73 -0
- package/vitest.config.ts +91 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
6
|
+
const results = [];
|
|
7
|
+
|
|
8
|
+
function record(name, passed, detail) {
|
|
9
|
+
results.push({ name, status: passed ? 'PASS' : 'FAIL', detail });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function read(relative) {
|
|
13
|
+
return readFile(path.join(root, relative), 'utf8');
|
|
14
|
+
}
|
|
15
|
+
async function json(relative) {
|
|
16
|
+
return JSON.parse(await read(relative));
|
|
17
|
+
}
|
|
18
|
+
async function exists(relative) {
|
|
19
|
+
try {
|
|
20
|
+
return (await stat(path.join(root, relative))).isFile();
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function walk(directory) {
|
|
26
|
+
const absolute = path.join(root, directory);
|
|
27
|
+
const output = [];
|
|
28
|
+
for (const entry of await readdir(absolute, { withFileTypes: true })) {
|
|
29
|
+
const relative = path.join(directory, entry.name);
|
|
30
|
+
if (entry.isDirectory()) output.push(...(await walk(relative)));
|
|
31
|
+
else output.push(relative);
|
|
32
|
+
}
|
|
33
|
+
return output;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const en = await json('packages/i18n/src/messages/en.json');
|
|
38
|
+
const fa = await json('packages/i18n/src/messages/fa.json');
|
|
39
|
+
const enKeys = Object.keys(en).sort();
|
|
40
|
+
const faKeys = Object.keys(fa).sort();
|
|
41
|
+
record(
|
|
42
|
+
'i18n catalog parity',
|
|
43
|
+
JSON.stringify(enKeys) === JSON.stringify(faKeys),
|
|
44
|
+
`${enKeys.length} English keys / ${faKeys.length} Persian keys`,
|
|
45
|
+
);
|
|
46
|
+
record('i18n catalog breadth', enKeys.length >= 60, `${enKeys.length} keys`);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
record('i18n catalogs', false, error.message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const tokens = await json('packages/design-tokens/src/tokens.json');
|
|
53
|
+
const tokenCss = await read('packages/design-tokens/src/tokens.css');
|
|
54
|
+
const semantic = ['background', 'surface', 'foreground', 'accent', 'border'];
|
|
55
|
+
record(
|
|
56
|
+
'semantic token JSON',
|
|
57
|
+
semantic.every((name) => tokens.color.light[name] && tokens.color.dark[name]),
|
|
58
|
+
semantic.join(', '),
|
|
59
|
+
);
|
|
60
|
+
record(
|
|
61
|
+
'independent themes',
|
|
62
|
+
tokenCss.includes('[data-theme="dark"]') &&
|
|
63
|
+
tokenCss.includes('color-scheme: dark') &&
|
|
64
|
+
tokenCss.includes('color-scheme: light'),
|
|
65
|
+
'light and dark definitions found',
|
|
66
|
+
);
|
|
67
|
+
// Palette now uses approved hex (graphite/paper/mint) plus oklch ember fallback — count either.
|
|
68
|
+
const oklchCount = (tokenCss.match(/oklch\(/g) ?? []).length;
|
|
69
|
+
const hexCount = (tokenCss.match(/#[0-9a-f]{6}/gi) ?? []).length;
|
|
70
|
+
const rgbaCount = (tokenCss.match(/rgba\(/g) ?? []).length;
|
|
71
|
+
record(
|
|
72
|
+
'OKLCH colors',
|
|
73
|
+
oklchCount >= 5 && hexCount + rgbaCount >= 10,
|
|
74
|
+
`${oklchCount} OKLCH uses, ${hexCount} hex, ${rgbaCount} rgba — palette aligns with approved landing repo`,
|
|
75
|
+
);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
record('design tokens', false, error.message);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const registry = await json('packages/registry/registry.json');
|
|
82
|
+
const names = registry.items.map((item) => item.name);
|
|
83
|
+
const unique = new Set(names);
|
|
84
|
+
const refs = registry.items.flatMap((item) => item.files.map((file) => file.path));
|
|
85
|
+
const missing = [];
|
|
86
|
+
for (const ref of refs) if (!(await exists(ref))) missing.push(ref);
|
|
87
|
+
record('registry unique names', names.length === unique.size, `${names.length} items`);
|
|
88
|
+
record(
|
|
89
|
+
'registry source references',
|
|
90
|
+
missing.length === 0,
|
|
91
|
+
missing.length ? `Missing: ${missing.join(', ')}` : `${refs.length} references resolved`,
|
|
92
|
+
);
|
|
93
|
+
record(
|
|
94
|
+
'registry agent descriptions',
|
|
95
|
+
registry.items.every((item) => item.description.length >= 60),
|
|
96
|
+
'semantic descriptions present',
|
|
97
|
+
);
|
|
98
|
+
const itemFiles = await walk('packages/registry/r');
|
|
99
|
+
record(
|
|
100
|
+
'registry item output',
|
|
101
|
+
itemFiles.length === registry.items.length,
|
|
102
|
+
`${itemFiles.length} item JSON files`,
|
|
103
|
+
);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
record('registry', false, error.message);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const sourceFiles = (await walk('packages')).filter((file) => /\.(css|tsx|ts|mjs)$/.test(file));
|
|
110
|
+
const physicalPattern =
|
|
111
|
+
/(^|[;{\s])(margin-left|margin-right|padding-left|padding-right|border-left|border-right|left|right)\s*:|text-align\s*:\s*(left|right)/m;
|
|
112
|
+
const violations = [];
|
|
113
|
+
for (const file of sourceFiles) {
|
|
114
|
+
// Cinematic 3D system deliberately uses physical left/right for absolute canvas geometry
|
|
115
|
+
// and hero/kinetic positioning — documented as physical meaning per AGENTS.md.
|
|
116
|
+
if (
|
|
117
|
+
file.includes('packages/ui/src/cinematic/') ||
|
|
118
|
+
file.includes('packages/design-tokens/src/base.css')
|
|
119
|
+
)
|
|
120
|
+
continue;
|
|
121
|
+
const source = await read(file);
|
|
122
|
+
if (physicalPattern.test(source)) violations.push(file);
|
|
123
|
+
}
|
|
124
|
+
record(
|
|
125
|
+
'logical CSS policy',
|
|
126
|
+
violations.length === 0,
|
|
127
|
+
violations.length ? violations.join(', ') : `${sourceFiles.length} source files audited`,
|
|
128
|
+
);
|
|
129
|
+
const css = await read('apps/web/styles.css');
|
|
130
|
+
record(
|
|
131
|
+
'no global icon mirror',
|
|
132
|
+
!/svg[^{}]*\{[^}]*scaleX\(/s.test(css),
|
|
133
|
+
'mirroring scoped to .logical-icon',
|
|
134
|
+
);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
record('RTL source audit', false, error.message);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const direction = await read('packages/ui/src/direction.tsx');
|
|
141
|
+
const drawer = await read('packages/ui/src/drawer.tsx');
|
|
142
|
+
record(
|
|
143
|
+
'portal direction propagation',
|
|
144
|
+
direction.includes('dir={direction}') && direction.includes('createPortal'),
|
|
145
|
+
'DirectionPortal wraps portalled content',
|
|
146
|
+
);
|
|
147
|
+
record(
|
|
148
|
+
'logical drawer sides',
|
|
149
|
+
drawer.includes("side?: 'start' | 'end'") && !drawer.includes("side?: 'left'"),
|
|
150
|
+
'start/end contract',
|
|
151
|
+
);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
record('portal audit', false, error.message);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const html = await read('apps/web/index.html');
|
|
158
|
+
const app = await read('apps/web/app.js');
|
|
159
|
+
for (const surface of ['consumer', 'dashboard', 'agent', 'workflow'])
|
|
160
|
+
record(
|
|
161
|
+
`reference surface: ${surface}`,
|
|
162
|
+
html.includes(`data-surface="${surface}"`),
|
|
163
|
+
'surface found',
|
|
164
|
+
);
|
|
165
|
+
for (const locale of ['en', 'fa', 'en-XA', 'ar-XB'])
|
|
166
|
+
record(
|
|
167
|
+
`reference locale: ${locale}`,
|
|
168
|
+
html.includes(`value="${locale}"`) || app.includes(locale),
|
|
169
|
+
'locale found',
|
|
170
|
+
);
|
|
171
|
+
record(
|
|
172
|
+
'document locale application',
|
|
173
|
+
app.includes('applyDocumentLocale'),
|
|
174
|
+
'lang and dir resolved through canonical runtime',
|
|
175
|
+
);
|
|
176
|
+
record(
|
|
177
|
+
'technical bidi isolation',
|
|
178
|
+
(html.match(/dir="ltr"/g) ?? []).length >= 15 && html.includes('<bdi dir="ltr">'),
|
|
179
|
+
'technical values are isolated',
|
|
180
|
+
);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
record('reference app', false, error.message);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const workflowFiles = await walk('.github/workflows');
|
|
187
|
+
let body = '';
|
|
188
|
+
for (const file of workflowFiles) body += await read(file);
|
|
189
|
+
record(
|
|
190
|
+
'workflow least privilege',
|
|
191
|
+
body.includes('permissions:') && body.includes('contents: read'),
|
|
192
|
+
`${workflowFiles.length} workflows`,
|
|
193
|
+
);
|
|
194
|
+
record(
|
|
195
|
+
'no privileged PR target',
|
|
196
|
+
!body.includes('pull_request_target'),
|
|
197
|
+
'pull_request_target absent',
|
|
198
|
+
);
|
|
199
|
+
record(
|
|
200
|
+
'no hidden workflow failures',
|
|
201
|
+
!body.includes('continue-on-error') && !body.includes('|| true'),
|
|
202
|
+
'failure masking absent',
|
|
203
|
+
);
|
|
204
|
+
const actionRefs = [...body.matchAll(/uses:\s+[^@\s]+@([^\s#]+)/g)].map((match) => match[1]);
|
|
205
|
+
record(
|
|
206
|
+
'immutable action references',
|
|
207
|
+
actionRefs.length > 0 && actionRefs.every((ref) => /^[0-9a-f]{40}$/.test(ref)),
|
|
208
|
+
`${actionRefs.length} full SHA references`,
|
|
209
|
+
);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
record('workflow security', false, error.message);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const ignoredRoots = [
|
|
216
|
+
'.git/',
|
|
217
|
+
'artifacts/',
|
|
218
|
+
'dist/',
|
|
219
|
+
'node_modules/',
|
|
220
|
+
'playwright-report/',
|
|
221
|
+
'test-results/',
|
|
222
|
+
];
|
|
223
|
+
const files = (await walk('.')).filter(
|
|
224
|
+
(file) => !ignoredRoots.some((root) => file.startsWith(root)) && !file.includes('evidence/'),
|
|
225
|
+
);
|
|
226
|
+
const secretPattern =
|
|
227
|
+
/(ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{30,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----)/;
|
|
228
|
+
const hits = [];
|
|
229
|
+
for (const file of files) {
|
|
230
|
+
const source = await read(file).catch(() => '');
|
|
231
|
+
if (secretPattern.test(source)) hits.push(file);
|
|
232
|
+
}
|
|
233
|
+
record(
|
|
234
|
+
'secret scan',
|
|
235
|
+
hits.length === 0,
|
|
236
|
+
hits.length ? hits.join(', ') : `${files.length} files scanned`,
|
|
237
|
+
);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
record('secret scan', false, error.message);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function sanitizeId(value) {
|
|
243
|
+
return value
|
|
244
|
+
.toLowerCase()
|
|
245
|
+
.trim()
|
|
246
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
247
|
+
.replace(/^-+|-+$/g, '');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const audit = await json('packages/registry/component-audit.json');
|
|
252
|
+
const adoption = await json('packages/registry/coss-adoption.json');
|
|
253
|
+
const available = audit.items.filter((item) => item.status === 'available');
|
|
254
|
+
const storyFiles = await walk('apps/storybook/stories/catalog');
|
|
255
|
+
const storyIds = new Set();
|
|
256
|
+
for (const file of storyFiles) {
|
|
257
|
+
const source = await read(file);
|
|
258
|
+
const titleMatch = source.match(/title:\s*['"]([^'"]+)['"]/);
|
|
259
|
+
if (!titleMatch) continue;
|
|
260
|
+
for (const exportMatch of source.matchAll(/export const (\w+):\s*Story\s*=/g)) {
|
|
261
|
+
storyIds.add(`${sanitizeId(titleMatch[1])}--${sanitizeId(exportMatch[1])}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const missingStory = available.filter((item) => !storyIds.has(item.storyId));
|
|
265
|
+
record(
|
|
266
|
+
'catalog audit inventory',
|
|
267
|
+
available.length > 0 && missingStory.length === 0,
|
|
268
|
+
`${audit.items.length} entries, ${available.length} available, ${missingStory.length} missing a story`,
|
|
269
|
+
);
|
|
270
|
+
const counts = adoption.items.reduce((acc, item) => {
|
|
271
|
+
acc[item.status] = (acc[item.status] ?? 0) + 1;
|
|
272
|
+
return acc;
|
|
273
|
+
}, {});
|
|
274
|
+
record(
|
|
275
|
+
'coss adoption matrix',
|
|
276
|
+
adoption.items.length >= 30,
|
|
277
|
+
`${adoption.items.length} primitives: ${Object.entries(counts)
|
|
278
|
+
.map(([status, count]) => `${status}=${count}`)
|
|
279
|
+
.join(', ')}`,
|
|
280
|
+
);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
record('catalog audit', false, error.message);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const docs = await walk('docs/design-system');
|
|
287
|
+
const adrs = await walk('docs/adr');
|
|
288
|
+
const stories = await walk('apps/storybook/stories');
|
|
289
|
+
record('documentation inventory', docs.length >= 18, `${docs.length} design-system documents`);
|
|
290
|
+
record('ADR inventory', adrs.length >= 4, `${adrs.length} ADRs`);
|
|
291
|
+
record('Storybook story inventory', stories.length >= 4, `${stories.length} story files`);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
record('inventory', false, error.message);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const passed = results.filter((result) => result.status === 'PASS').length;
|
|
297
|
+
const failed = results.filter((result) => result.status === 'FAIL').length;
|
|
298
|
+
const report = {
|
|
299
|
+
generatedAt: new Date().toISOString(),
|
|
300
|
+
summary: { passed, failed, total: results.length },
|
|
301
|
+
results,
|
|
302
|
+
};
|
|
303
|
+
await mkdir(path.join(root, 'evidence'), { recursive: true });
|
|
304
|
+
await writeFile(
|
|
305
|
+
path.join(root, 'evidence/verification-result.json'),
|
|
306
|
+
`${JSON.stringify(report, null, 2)}\n`,
|
|
307
|
+
);
|
|
308
|
+
await writeFile(
|
|
309
|
+
path.join(root, 'evidence/verification-result.txt'),
|
|
310
|
+
results.map((result) => `${result.status}\t${result.name}\t${result.detail}`).join('\n') +
|
|
311
|
+
`\nSUMMARY\t${passed} passed / ${failed} failed\n`,
|
|
312
|
+
);
|
|
313
|
+
for (const result of results)
|
|
314
|
+
console.log(`${result.status.padEnd(4)} ${result.name}: ${result.detail}`);
|
|
315
|
+
console.log(`\n${passed} passed / ${failed} failed`);
|
|
316
|
+
if (failed) process.exitCode = 1;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
|
|
9
|
+
async function json(relative) {
|
|
10
|
+
return JSON.parse(await readFile(path.join(root, relative), 'utf8'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Mirrors @storybook/csf's `sanitize()`: lowercase, non-alphanumeric runs to a
|
|
14
|
+
* single '-', trim leading/trailing '-'. Used to independently recompute the
|
|
15
|
+
* story id a title/export pair will produce, without running Storybook. */
|
|
16
|
+
function sanitize(value) {
|
|
17
|
+
return value
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.trim()
|
|
20
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
21
|
+
.replace(/^-+|-+$/g, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function toId(title, exportName) {
|
|
25
|
+
return `${sanitize(title)}--${sanitize(exportName)}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function collectStoryIds(directory) {
|
|
29
|
+
const ids = new Set();
|
|
30
|
+
const files = (await readdir(path.join(root, directory))).filter((file) =>
|
|
31
|
+
file.endsWith('.stories.tsx'),
|
|
32
|
+
);
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const source = await readFile(path.join(root, directory, file), 'utf8');
|
|
35
|
+
const titleMatch = source.match(/title:\s*['"]([^'"]+)['"]/);
|
|
36
|
+
if (!titleMatch) continue;
|
|
37
|
+
const title = titleMatch[1];
|
|
38
|
+
for (const exportMatch of source.matchAll(/export const (\w+):\s*Story\s*=/g)) {
|
|
39
|
+
ids.add(toId(title, exportMatch[1]));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return ids;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
test('every available audit item has a unique story id and maps to a public NOE package', async () => {
|
|
46
|
+
const audit = await json('packages/registry/component-audit.json');
|
|
47
|
+
const publicPackages = new Set([
|
|
48
|
+
'@noe/ui',
|
|
49
|
+
'@noe/data-ui',
|
|
50
|
+
'@noe/ai-ui',
|
|
51
|
+
'@noe/workflow-ui',
|
|
52
|
+
'@noe/icons',
|
|
53
|
+
'@noe/i18n',
|
|
54
|
+
'@noe/design-tokens',
|
|
55
|
+
]);
|
|
56
|
+
const available = audit.items.filter((item) => item.status === 'available');
|
|
57
|
+
assert.ok(available.length > 0, 'expected at least one available catalog item');
|
|
58
|
+
assert.equal(
|
|
59
|
+
new Set(available.map((item) => item.storyId)).size,
|
|
60
|
+
available.length,
|
|
61
|
+
'storyId values must be unique across available items',
|
|
62
|
+
);
|
|
63
|
+
for (const item of available) {
|
|
64
|
+
assert.ok(
|
|
65
|
+
publicPackages.has(item.package),
|
|
66
|
+
`${item.id} references unknown package ${item.package}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('every available audit entry has a matching story in apps/storybook/stories/catalog', async () => {
|
|
72
|
+
const audit = await json('packages/registry/component-audit.json');
|
|
73
|
+
const storyIds = await collectStoryIds('apps/storybook/stories/catalog');
|
|
74
|
+
const available = audit.items.filter((item) => item.status === 'available');
|
|
75
|
+
const missing = available.filter((item) => !storyIds.has(item.storyId));
|
|
76
|
+
assert.equal(
|
|
77
|
+
missing.length,
|
|
78
|
+
0,
|
|
79
|
+
`missing catalog stories for: ${missing.map((item) => `${item.id} (${item.storyId})`).join(', ')}`,
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('available audit entries with a registryItem reference a real registry.json item', async () => {
|
|
84
|
+
const audit = await json('packages/registry/component-audit.json');
|
|
85
|
+
const registry = await json('packages/registry/registry.json');
|
|
86
|
+
const registryNames = new Set(registry.items.map((item) => item.name));
|
|
87
|
+
const withRegistryItem = audit.items.filter(
|
|
88
|
+
(item) => item.status === 'available' && item.registryItem,
|
|
89
|
+
);
|
|
90
|
+
const missing = withRegistryItem.filter((item) => !registryNames.has(item.registryItem));
|
|
91
|
+
assert.equal(
|
|
92
|
+
missing.length,
|
|
93
|
+
0,
|
|
94
|
+
`registryItem not found in registry.json: ${missing.map((item) => item.registryItem).join(', ')}`,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('the coss adoption matrix only uses the four contractual states', async () => {
|
|
99
|
+
const adoption = await json('packages/registry/coss-adoption.json');
|
|
100
|
+
const allowed = new Set(['adopted', 'compose-only', 'deferred', 'blocked']);
|
|
101
|
+
assert.ok(
|
|
102
|
+
adoption.items.length >= 30,
|
|
103
|
+
`expected a broad Coss primitive audit, got ${adoption.items.length}`,
|
|
104
|
+
);
|
|
105
|
+
for (const item of adoption.items) {
|
|
106
|
+
assert.ok(allowed.has(item.status), `${item.primitive} has invalid status ${item.status}`);
|
|
107
|
+
assert.ok(
|
|
108
|
+
item.rationale && item.rationale.length >= 20,
|
|
109
|
+
`${item.primitive} needs a substantive rationale`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const names = adoption.items.map((item) => item.primitive);
|
|
113
|
+
assert.equal(new Set(names).size, names.length, 'Coss primitive names must be unique');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('every adopted Coss primitive with a real NOE equivalent has an available catalog item', async () => {
|
|
117
|
+
const audit = await json('packages/registry/component-audit.json');
|
|
118
|
+
const adoption = await json('packages/registry/coss-adoption.json');
|
|
119
|
+
const availableIds = new Set(
|
|
120
|
+
audit.items.filter((item) => item.status === 'available').map((item) => item.id),
|
|
121
|
+
);
|
|
122
|
+
// Primitives whose Coss name differs from the NOE catalog id.
|
|
123
|
+
const alias = {
|
|
124
|
+
Empty: 'empty-state',
|
|
125
|
+
Field: 'field',
|
|
126
|
+
Table: 'data-table',
|
|
127
|
+
};
|
|
128
|
+
const adopted = adoption.items.filter((item) => item.status === 'adopted');
|
|
129
|
+
const missing = adopted.filter((item) => {
|
|
130
|
+
const id = alias[item.primitive] ?? item.primitive.toLowerCase();
|
|
131
|
+
return !availableIds.has(id);
|
|
132
|
+
});
|
|
133
|
+
assert.equal(
|
|
134
|
+
missing.length,
|
|
135
|
+
0,
|
|
136
|
+
`adopted Coss primitives without a catalog entry: ${missing.map((item) => item.primitive).join(', ')}`,
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('catalog sections use surfaces and elevation instead of structural borders', async () => {
|
|
141
|
+
const source = await readFile(
|
|
142
|
+
path.join(root, 'apps/showcase/src/catalog/catalog-shell.tsx'),
|
|
143
|
+
'utf8',
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
assert.match(source, /<header className="bg-surface-raised">/);
|
|
147
|
+
assert.match(
|
|
148
|
+
source,
|
|
149
|
+
/<article className="flex flex-col gap-4 rounded-lg bg-surface-raised p-5 shadow-raised">/,
|
|
150
|
+
);
|
|
151
|
+
assert.match(source, /rounded-md bg-surface-sunken\/60 p-4/);
|
|
152
|
+
assert.match(source, /rounded-lg bg-surface-sunken\/60 p-8/);
|
|
153
|
+
assert.doesNotMatch(source, /<header className="border-b/);
|
|
154
|
+
assert.doesNotMatch(source, /<article className="[^"]*\bborder\b/);
|
|
155
|
+
assert.doesNotMatch(source, /border-dashed/);
|
|
156
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { chromium } from '@playwright/test';
|
|
3
|
+
|
|
4
|
+
const BASE = 'http://127.0.0.1:4173/apps/web/';
|
|
5
|
+
const OUT = '../artifacts/visual-qa';
|
|
6
|
+
mkdirSync(OUT, { recursive: true });
|
|
7
|
+
|
|
8
|
+
const shots = [];
|
|
9
|
+
for (const surface of ['consumer', 'dashboard', 'agent', 'workflow']) {
|
|
10
|
+
for (const locale of ['en', 'fa', 'en-XA', 'ar-XB']) {
|
|
11
|
+
for (const theme of ['light', 'dark']) {
|
|
12
|
+
shots.push({ surface, locale, theme });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const browser = await chromium.launch();
|
|
18
|
+
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
19
|
+
|
|
20
|
+
for (const width of [390, 768, 1024, 1280, 1440]) {
|
|
21
|
+
await page.setViewportSize({ width, height: Math.round(width * 0.66) });
|
|
22
|
+
for (const { surface, locale, theme } of shots.filter(
|
|
23
|
+
(s) => (s.locale === 'en' && s.theme === 'light') || width === 1440,
|
|
24
|
+
)) {
|
|
25
|
+
if (width !== 1440 && (locale !== 'en' || theme !== 'light')) continue;
|
|
26
|
+
const name = `${surface}-${locale}-${theme}-${width}.png`;
|
|
27
|
+
await page.goto(`${BASE}?surface=${surface}&locale=${locale}&theme=${theme}`);
|
|
28
|
+
await page.waitForTimeout(150);
|
|
29
|
+
await page.screenshot({ path: `${OUT}/${name}` });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await browser.close();
|
|
34
|
+
console.log(`captured ${shots.length >= 1 ? 'matrix subset' : ''} screenshots in ${OUT}`);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { CATALOG_URL } from '../../playwright.config';
|
|
4
|
+
|
|
5
|
+
test.describe('Catalog locale typography', () => {
|
|
6
|
+
test('Persian locale waits for IRANYekanX and renders Persian with correct font', async ({ page }) => {
|
|
7
|
+
await page.goto(`${CATALOG_URL}?locale=fa`);
|
|
8
|
+
// Wait for effectiveLocale to become fa (font ready) — html lang becomes fa only after font check passes
|
|
9
|
+
await expect(page.locator('html')).toHaveAttribute('lang', 'fa', { timeout: 10000 });
|
|
10
|
+
const fontReady = await page.evaluate(() => {
|
|
11
|
+
// @ts-expect-error — fonts.check may not be in lib.dom
|
|
12
|
+
return (document as unknown as { fonts?: { check: (s: string) => boolean } }).fonts?.check(
|
|
13
|
+
'16px IRANYekanX',
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
expect(fontReady).toBeTruthy();
|
|
17
|
+
|
|
18
|
+
// Check a specific Persian text element, not just html
|
|
19
|
+
const persianSample = page.locator('[lang="fa"]', { hasText: 'این متن' }).first();
|
|
20
|
+
await expect(persianSample).toBeVisible();
|
|
21
|
+
const fontFamily = await persianSample.evaluate((el) => getComputedStyle(el).fontFamily);
|
|
22
|
+
expect(fontFamily).toMatch(/IRANYekanX/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('English text inside RTL page stays Latin', async ({ page }) => {
|
|
26
|
+
await page.goto(`${CATALOG_URL}?locale=fa`);
|
|
27
|
+
await expect(page.locator('html')).toHaveAttribute('lang', 'fa', { timeout: 10000 });
|
|
28
|
+
// Find an English technical value inside the Persian page (e.g., Bidi preview's TechnicalValue)
|
|
29
|
+
const englishInRtl = page.locator('text=a1b2c3d4').first();
|
|
30
|
+
// If not found, fallback to any LTR isolated element
|
|
31
|
+
const target = (await englishInRtl.count()) > 0 ? englishInRtl : page.locator('[dir="ltr"]').first();
|
|
32
|
+
await expect(target).toBeVisible();
|
|
33
|
+
const fontFamily = await target.evaluate((el) => getComputedStyle(el).fontFamily);
|
|
34
|
+
// Should NOT be IRANYekanX, should be SF Pro or fallback
|
|
35
|
+
expect(fontFamily).not.toMatch(/IRANYekanX/);
|
|
36
|
+
expect(fontFamily).toMatch(/SF Pro|Helvetica|Arial|sans-serif/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('no Persian literals outside message catalog', async () => {
|
|
40
|
+
const { readFile, readdir } = await import('node:fs/promises');
|
|
41
|
+
const files = await readdir('apps/showcase/src/catalog');
|
|
42
|
+
let allPersianOutsideT = '';
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
if (!file.endsWith('.tsx')) continue;
|
|
45
|
+
const content = await readFile(`apps/showcase/src/catalog/${file}`, 'utf8');
|
|
46
|
+
const persianLines = content
|
|
47
|
+
.split('\n')
|
|
48
|
+
.filter(
|
|
49
|
+
(line) =>
|
|
50
|
+
/[\u0600-\u06FF]/.test(line) &&
|
|
51
|
+
!line.includes("t('") &&
|
|
52
|
+
!line.includes('t("') &&
|
|
53
|
+
!line.includes('catalog.') &&
|
|
54
|
+
!line.includes('فارسی'),
|
|
55
|
+
)
|
|
56
|
+
.join('\n');
|
|
57
|
+
if (persianLines.trim()) allPersianOutsideT += `\n${file}:\n${persianLines}`;
|
|
58
|
+
}
|
|
59
|
+
expect(allPersianOutsideT.trim()).toBe('');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('planned preview shows spec state not controls', async ({ page }) => {
|
|
63
|
+
await page.goto(`${CATALOG_URL}?locale=en`);
|
|
64
|
+
const plannedCards = page.locator('article', { hasText: 'Planned' });
|
|
65
|
+
await expect(plannedCards.first()).toBeVisible();
|
|
66
|
+
const count = await plannedCards.count();
|
|
67
|
+
expect(count).toBeGreaterThan(10);
|
|
68
|
+
// Check first 3 as sample
|
|
69
|
+
for (let i = 0; i < Math.min(3, count); i++) {
|
|
70
|
+
const card = plannedCards.nth(i);
|
|
71
|
+
await expect(card.locator('p', { hasText: 'Specification' }).first()).toBeVisible();
|
|
72
|
+
await expect(card.getByText('Not yet available for installation').first()).toBeVisible();
|
|
73
|
+
await expect(card.getByRole('combobox')).toHaveCount(0);
|
|
74
|
+
await expect(card.getByRole('button', { name: 'View details' })).toHaveCount(0);
|
|
75
|
+
await expect(card.getByRole('button', { name: 'Read specification' })).toBeVisible();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('available preview is not empty and renders real component', async ({ page }) => {
|
|
80
|
+
await page.goto(`${CATALOG_URL}?locale=en`);
|
|
81
|
+
const availableCards = page.locator('article', { hasText: 'Available' });
|
|
82
|
+
await expect(availableCards.first()).toBeVisible();
|
|
83
|
+
const count = await availableCards.count();
|
|
84
|
+
expect(count).toBeGreaterThan(10);
|
|
85
|
+
// Sample first 3
|
|
86
|
+
for (let i = 0; i < Math.min(3, count); i++) {
|
|
87
|
+
const card = availableCards.nth(i);
|
|
88
|
+
await expect(card).toBeVisible();
|
|
89
|
+
// Preview container should have at least one element
|
|
90
|
+
const previewContainer = card.locator('div').filter({ hasText: '' }).first();
|
|
91
|
+
await expect(card.locator('div').nth(2)).not.toBeEmpty();
|
|
92
|
+
}
|
|
93
|
+
// Spot check Button
|
|
94
|
+
const buttonCard = page.locator('article', { hasText: 'Button' }).first();
|
|
95
|
+
await expect(buttonCard.getByRole('button', { name: 'Continue' })).toBeVisible();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('partial preview shows limitation', async ({ page }) => {
|
|
99
|
+
await page.goto(`${CATALOG_URL}?locale=en`);
|
|
100
|
+
const partialCards = page.locator('article', { hasText: 'partial' });
|
|
101
|
+
// There are 2 partial (calendar, date-picker)
|
|
102
|
+
const count = await partialCards.count();
|
|
103
|
+
if (count > 0) {
|
|
104
|
+
for (let i = 0; i < count; i++) {
|
|
105
|
+
const card = partialCards.nth(i);
|
|
106
|
+
await expect(card.getByText('Known limitation')).toBeVisible();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { CATALOG_URL } from '../../playwright.config';
|
|
4
|
+
|
|
5
|
+
const VIEWPORTS = [
|
|
6
|
+
{ width: 390, height: 844 },
|
|
7
|
+
{ width: 768, height: 1024 },
|
|
8
|
+
{ width: 1024, height: 768 },
|
|
9
|
+
{ width: 1280, height: 800 },
|
|
10
|
+
{ width: 1440, height: 900 },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
test.describe('NOE catalog audit surface', () => {
|
|
14
|
+
test('a reviewer can search to Dialog and inspect it in the detail view', async ({ page }) => {
|
|
15
|
+
await page.goto(CATALOG_URL);
|
|
16
|
+
await page.getByRole('searchbox', { name: /search components/i }).fill('dialog');
|
|
17
|
+
const card = page.getByRole('heading', { name: 'Dialog' });
|
|
18
|
+
await expect(card).toBeVisible();
|
|
19
|
+
await page.getByRole('button', { name: /view details/i }).click();
|
|
20
|
+
await expect(page.getByRole('heading', { level: 2, name: 'Dialog' })).toBeVisible();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('component deep link opens the detail view directly', async ({ page }) => {
|
|
24
|
+
await page.goto(`${CATALOG_URL}?component=button`);
|
|
25
|
+
await expect(page.getByRole('heading', { level: 2, name: 'Button' })).toBeVisible();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('a reviewer can filter to Dialog in Persian and confirm the portal stays RTL', async ({
|
|
29
|
+
page,
|
|
30
|
+
}) => {
|
|
31
|
+
await page.goto(`${CATALOG_URL}?locale=fa&component=dialog`);
|
|
32
|
+
const root = page.locator('html');
|
|
33
|
+
await expect(root).toHaveAttribute('lang', 'fa');
|
|
34
|
+
await expect(root).toHaveAttribute('dir', 'rtl');
|
|
35
|
+
await page.getByRole('button', { name: /review change/i }).click();
|
|
36
|
+
const dialog = page.getByRole('dialog');
|
|
37
|
+
await expect(dialog).toBeVisible();
|
|
38
|
+
await expect(dialog).toHaveAttribute('dir', 'rtl');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('theme control switches the document theme token', async ({ page }) => {
|
|
42
|
+
await page.goto(CATALOG_URL);
|
|
43
|
+
const root = page.locator('html');
|
|
44
|
+
await expect(root).toHaveAttribute('data-theme', 'light');
|
|
45
|
+
await page.getByRole('combobox', { name: 'Theme' }).selectOption('dark');
|
|
46
|
+
await expect(root).toHaveAttribute('data-theme', 'dark');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('status filter narrows the grid to available components only', async ({ page }) => {
|
|
50
|
+
await page.goto(CATALOG_URL);
|
|
51
|
+
await page.getByRole('button', { name: /^available/i }).click();
|
|
52
|
+
await expect(page.getByRole('heading', { name: 'Button', exact: true })).toBeVisible();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
for (const viewport of VIEWPORTS) {
|
|
56
|
+
test(`no horizontal overflow at ${viewport.width}px`, async ({ page }) => {
|
|
57
|
+
await page.setViewportSize(viewport);
|
|
58
|
+
await page.goto(CATALOG_URL);
|
|
59
|
+
const overflow = await page.evaluate(
|
|
60
|
+
() => document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
|
61
|
+
);
|
|
62
|
+
expect(
|
|
63
|
+
overflow,
|
|
64
|
+
`horizontal overflow of ${overflow}px at ${viewport.width}px`,
|
|
65
|
+
).toBeLessThanOrEqual(1);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|