diff --git a/.changeset/dark-mode-toggle-mobile.md b/.changeset/dark-mode-toggle-mobile.md new file mode 100644 index 00000000..f6aa0b22 --- /dev/null +++ b/.changeset/dark-mode-toggle-mobile.md @@ -0,0 +1,11 @@ +--- +"@stackwright/core": patch +--- + +fix(top-app-bar): always show color mode toggle on mobile + +The `colorModeToggle` was previously hidden on narrow screens when nav +menu items were present. It is now always visible β€” rendering to the +left of the hamburger icon on mobile (`[πŸŒ™] [☰]`) and to the right of +nav links on desktop. Replaces one compound conditional with three +intent-revealing render sites. diff --git a/examples/stackwright-docs/build-manifest.json b/examples/stackwright-docs/build-manifest.json index e9065519..3e30af0b 100644 --- a/examples/stackwright-docs/build-manifest.json +++ b/examples/stackwright-docs/build-manifest.json @@ -1,10 +1,10 @@ { "format": "stackwright-build-manifest", "version": "1.0.0", - "generated": "2026-05-15T14:03:57.119Z", + "generated": "2026-05-18T16:22:20.092Z", "project": { "name": "stackwright-docs", - "version": "0.1.6-alpha.3", + "version": "0.1.6", "root": "/home/charles/git/peraspera/stackwright/examples/stackwright-docs", "isMonorepo": false }, @@ -137,11 +137,11 @@ }, { "name": "tailwind-merge", - "version": "3.5.0", + "version": "3.6.0", "type": "direct", "category": "external", - "purl": "pkg:npm/tailwind-merge@3.5.0", - "integrity": "039d2e92d475e4268b1babe22545c8cc507d46e9fc09aacaa75ee5925b885a10", + "purl": "pkg:npm/tailwind-merge@3.6.0", + "integrity": "084c7514e672a9283e7a4bf1c162bf248d481e0aac75ffe397d8763082a57852", "depth": 0 } ], diff --git a/examples/stackwright-docs/cyclonedx.json b/examples/stackwright-docs/cyclonedx.json index 00af6ac7..71152b07 100644 --- a/examples/stackwright-docs/cyclonedx.json +++ b/examples/stackwright-docs/cyclonedx.json @@ -3,7 +3,7 @@ "specVersion": "1.5", "version": 1, "metadata": { - "timestamp": "2026-05-15T14:03:57.118Z", + "timestamp": "2026-05-18T16:22:20.092Z", "tools": [ { "vendor": "Stackwright", @@ -14,8 +14,8 @@ "component": { "type": "application", "name": "stackwright-docs", - "version": "0.1.6-alpha.3", - "purl": "pkg:npm/stackwright-docs@0.1.6-alpha.3" + "version": "0.1.6", + "purl": "pkg:npm/stackwright-docs@0.1.6" } }, "components": [ @@ -288,13 +288,13 @@ { "type": "library", "name": "tailwind-merge", - "version": "3.5.0", - "purl": "pkg:npm/tailwind-merge@3.5.0", + "version": "3.6.0", + "purl": "pkg:npm/tailwind-merge@3.6.0", "scope": "required", "hashes": [ { "alg": "SHA-256", - "content": "039d2e92d475e4268b1babe22545c8cc507d46e9fc09aacaa75ee5925b885a10" + "content": "084c7514e672a9283e7a4bf1c162bf248d481e0aac75ffe397d8763082a57852" } ], "externalReferences": [ @@ -307,7 +307,7 @@ ], "dependencies": [ { - "ref": "pkg:npm/stackwright-docs@0.1.6-alpha.3", + "ref": "pkg:npm/stackwright-docs@0.1.6", "dependsOn": [ "@radix-ui/react-accordion", "@radix-ui/react-slot", diff --git a/examples/stackwright-docs/pages/acknowledgements/content.yml b/examples/stackwright-docs/pages/acknowledgements/content.yml index b06e27e9..68f6731e 100644 --- a/examples/stackwright-docs/pages/acknowledgements/content.yml +++ b/examples/stackwright-docs/pages/acknowledgements/content.yml @@ -1,7 +1,7 @@ content: meta: - title: "Acknowledgements | Stackwright" - description: "Thank you to the humans and AI agents who built Stackwright." + title: "Acknowledgements | StackWright" + description: "Thank you to the humans and AI agents who built StackWright." content_items: # ───────────────────────────────────────────────────────── @@ -13,7 +13,7 @@ content: text: "Acknowledgements πŸ™" textSize: h1 textBlocks: - - text: "Stackwright is built by humans, powered by AI, and shaped by open source." + - text: "StackWright is built by humans, powered by AI, and shaped by open source." textSize: h3 - text: "This page thanks the contributors β€” silicon and carbon alike β€” who made it possible." textSize: body1 @@ -28,7 +28,7 @@ content: text: "Code Puppy 🐢" textSize: h2 textBlocks: - - text: "The AI coding agent that built Stackwright. Every package, every component, every test β€” code-puppy wrote the code that writes your code." + - text: "The AI coding agent that built StackWright. Every package, every component, every test β€” code-puppy wrote the code that writes your code." textSize: body1 background: background @@ -36,7 +36,7 @@ content: label: code-puppy-origin variant: note title: "A Sassy Little Puppy" - body: "Code Puppy was created by Marcus Paffenberger in response to AI coding tools removing access to models and raising prices. It's privacy-first, sarcastic, and thoroughly unimpressed with enterprise software. It coded Stackwright out of spite for expensive IDEs." + body: "Code Puppy was created by Michael Pfaffenberger (https://github.com/mpfaffenberger) in response to AI coding tools removing access to models and raising prices. It's privacy-first, sarcastic, and thoroughly unimpressed with expensive IDEs." background: surface - type: feature_list @@ -106,7 +106,7 @@ content: code: | # Try Code Puppy yourself uvx code-puppy - + # Or install permanently pip install code-puppy @@ -130,26 +130,11 @@ content: href: "https://code-puppy.dev" background: background - # ───────────────────────────────────────────────────────── - # CLAUDE SECTION - # ───────────────────────────────────────────────────────── - - type: text_block - label: claude-heading - heading: - text: "Claude (Anthropic) πŸ€–" - textSize: h2 - textBlocks: - - text: "Code Puppy is powered by Claude. The reasoning, generation, and architectural decisions that make Stackwright work β€” all catalyzed by Anthropic's model." - textSize: body1 - - text: "We chose Claude for its ability to understand complex codebases, maintain context across long conversations, and produce production-ready code β€” not rough drafts." - textSize: body1 - background: background - - type: alert label: anthropic-note variant: info title: "Privacy Matters" - body: "Both Stackwright and Code Puppy are committed to privacy. Your prompts are never logged, your code is never shared, and you can run everything locally if you prefer." + body: "Both StackWright and Code Puppy are committed to privacy. Your prompts are never logged, your code is never shared, and you can run everything locally if you prefer." background: surface # ───────────────────────────────────────────────────────── @@ -184,7 +169,7 @@ content: ], "tools": [ "list_files", - "read_file", + "read_file", "create_file", "agent_share_your_reasoning" ] @@ -206,7 +191,7 @@ content: text: "Open Source Dependencies" textSize: h2 textBlocks: - - text: "Stackwright stands on the shoulders of giants:" + - text: "StackWright stands on the shoulders of giants:" textSize: body1 background: background @@ -269,7 +254,7 @@ content: label: gratitude variant: success title: "Thank You" - body: "To every maintainer, contributor, and user of these projects β€” thank you for making the open source ecosystem what it is. Stackwright is just one small flower in a vast garden." + body: "To every maintainer, contributor, and user of these projects β€” thank you for making the open source ecosystem what it is. StackWright is just one small flower in a vast garden." background: surface # ───────────────────────────────────────────────────────── @@ -281,9 +266,9 @@ content: text: "Per Aspera Sapientia" textSize: h2 textBlocks: - - text: "\"Through hardships to wisdom.\" β€” The motto of Per Aspera LLC." + - text: "\"Through hardships to wisdom.\" β€” The motto of Per Aspera Sapientia LLC." textSize: body1 - - text: "Stackwright was built because the founder got tired of enterprise software that was expensive, opaque, and disrespectful of users. We built something that respects your time, your privacy, and your intelligence." + - text: "StackWright encodes software architecture and security experiences earned the hard way into a framework." textSize: body1 - text: "The AI coding tools existed. We just made them work together. The hard part wasn't the code β€” it was the vision of what could be." textSize: body1 @@ -298,7 +283,7 @@ content: text: "Thank you for reading" textSize: h3 textBlocks: - - text: "Built with ❀️ by Per Aspera LLC. Powered by code-puppy. Made possible by open source." + - text: "Built with ❀️ by Per Aspera Sapientia LLC. Powered by code-puppy. Made possible by open source." textSize: body1 buttons: - text: "Back to Home" diff --git a/examples/stackwright-docs/pages/architecture/content.yml b/examples/stackwright-docs/pages/architecture/content.yml index 1587edf6..812f8271 100644 --- a/examples/stackwright-docs/pages/architecture/content.yml +++ b/examples/stackwright-docs/pages/architecture/content.yml @@ -1,38 +1,7 @@ content: - navSidebar: - navigation: - - label: "πŸ“– Documentation" - href: /getting-started - children: - - label: "πŸš€ Getting Started" - href: /getting-started - - label: "πŸ“¦ Content Types" - href: /content-types - - label: "πŸ—οΈ Architecture" - href: /architecture - - label: "⌨️ CLI Reference" - href: /cli - - label: "πŸ€– AI Agents" - href: /otter-raft - children: - - label: "πŸ€– Otter Raft" - href: /otter-raft - - label: "πŸ” Pro" - href: /pro - - label: "πŸ“œ Legal" - href: /acknowledgements - children: - - label: "πŸ™ Acknowledgements" - href: /acknowledgements - - label: "πŸ”’ Privacy Policy" - href: /privacy-policy - - label: "πŸ“ Terms of Service" - href: /terms-of-service - - label: "πŸ§ͺ Showcase" - href: /showcase meta: - title: Architecture | Stackwright - description: How Stackwright compiles YAML into production Next.js apps. Technical deep-dive. + title: Architecture | StackWright + description: How StackWright compiles YAML into production Next.js apps. Technical deep-dive. content_items: - type: main label: hero @@ -40,7 +9,7 @@ content: text: Architecture πŸ—οΈ textSize: h1 textBlocks: - - text: Stackwright is a typed DSL that compiles YAML into production-ready Next.js applications. + - text: StackWright is a typed DSL that compiles YAML into production-ready Next.js applications. textSize: h3 - text: Visual rendering + constrained grammar + AI iteration = non-technical people building enterprise apps that are safe by construction. textSize: body1 @@ -51,21 +20,21 @@ content: text: The Big Picture textSize: h2 textBlocks: - - text: 'Stackwright transforms your intent into a working app through a predictable pipeline:' + - text: 'StackWright transforms your intent into a working app through a predictable pipeline:' textSize: body1 background: background - type: code_block label: big-picture-diagram language: text lineNumbers: false - code: "β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\nβ”‚ STACKWRIGHT PIPELINE β”‚\nβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜\nβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\nβ”‚ YOUR β”‚ β”‚PREBUILD β”‚ β”‚ JSON β”‚ β”‚ NEXT.js β”‚\nβ”‚ YAMl β”‚ ───▢ β”‚ β”‚ ───▢ β”‚ DATA β”‚ ───▢ β”‚ β”‚\nβ”‚ FILES β”‚ β”‚ compiles β”‚ β”‚ (static)β”‚ β”‚ SSG β”‚\nβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜\n β”‚\n β–Ό\n β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n β”‚ OUTPUT β”‚\n β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚\n β”‚ β”‚ Static β”‚ β”‚ Theme β”‚ β”‚ Typed β”‚ β”‚\n β”‚ β”‚ HTML β”‚ + β”‚ CSS β”‚ + β”‚ React β”‚ = \U0001F680 Fast β”‚\n β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Web Apps β”‚\n β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜" + code: "β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\nβ”‚ StackWright PIPELINE β”‚\nβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜\nβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\nβ”‚ YOUR β”‚ β”‚PREBUILD β”‚ β”‚ JSON β”‚ β”‚ NEXT.js β”‚\nβ”‚ YAMl β”‚ ───▢ β”‚ β”‚ ───▢ β”‚ DATA β”‚ ───▢ β”‚ β”‚\nβ”‚ FILES β”‚ β”‚ compiles β”‚ β”‚ (static)β”‚ β”‚ SSG β”‚\nβ””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜\n β”‚\n β–Ό\n β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n β”‚ OUTPUT β”‚\n β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚\n β”‚ β”‚ Static β”‚ β”‚ Theme β”‚ β”‚ Typed β”‚ β”‚\n β”‚ β”‚ HTML β”‚ + β”‚ CSS β”‚ + β”‚ React β”‚ = \U0001F680 Fast β”‚\n β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Web Apps β”‚\n β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜" - type: text_block label: flow-heading heading: text: Data Flow textSize: h2 textBlocks: - - text: 'Here''s what happens when you build a Stackwright site:' + - text: 'Here''s what happens when you build a StackWright site:' textSize: body1 background: background - type: code_block @@ -137,7 +106,7 @@ content: label: no-runtime-fs variant: info title: No Runtime Filesystem - body: The JSON is pre-generated at build time. Next.js serves static files β€” no fs.readFile at runtime. This makes Stackwright apps compatible with edge functions, CDN hosting, and serverless environments. + body: The JSON is pre-generated at build time. Next.js serves static files β€” no fs.readFile at runtime. This makes StackWright apps compatible with edge functions, CDN hosting, and serverless environments. background: surface - type: text_block label: packages-heading @@ -145,7 +114,7 @@ content: text: Package Architecture textSize: h2 textBlocks: - - text: 'Stackwright is a pnpm monorepo with 10 packages. Here''s how they relate:' + - text: 'StackWright is a pnpm monorepo with 10 packages. Here''s how they relate:' textSize: body1 background: background - type: code_block @@ -166,7 +135,7 @@ content: label: file-structure-tree language: text lineNumbers: false - code: "stackwright/\nβ”œβ”€β”€ packages/\nβ”‚ β”œβ”€β”€ core/ # YAMLβ†’React engine\nβ”‚ β”‚ └── src/\nβ”‚ β”‚ β”œβ”€β”€ components/ # React components\nβ”‚ β”‚ β”‚ β”œβ”€β”€ base/ # Content types\nβ”‚ β”‚ β”‚ β”œβ”€β”€ structural/ # Layout (PageLayout, NavSidebar)\nβ”‚ β”‚ β”‚ β”œβ”€β”€ narrative/ # Carousel, Timeline\nβ”‚ β”‚ β”‚ └── content/ # Map, etc.\nβ”‚ β”‚ β”œβ”€β”€ config/ # Default config\nβ”‚ β”‚ └── utils/ # Registries, renderers\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ types/ # Zod schemas, TypeScript types\nβ”‚ β”‚ └── src/\nβ”‚ β”‚ β”œβ”€β”€ types/ # Content schemas\nβ”‚ β”‚ └── schemas/ # JSON schemas\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ nextjs/ # Next.js adapter\nβ”‚ β”‚ └── src/components/ # NextImage, NextLink, etc.\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ themes/ # Theme system\nβ”‚ β”‚ └── src/ThemesProvider.tsx\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ build-scripts/ # Prebuild pipeline\nβ”‚ β”‚ └── src/\nβ”‚ β”‚ β”œβ”€β”€ prebuild.ts # Main entry\nβ”‚ β”‚ └── ... # Transformers\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ cli/ # Developer CLI\nβ”‚ β”‚ └── src/commands/ # init, validate, etc.\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ mcp/ # MCP server\nβ”‚ β”‚ └── src/tools/ # AI authoring tools\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ icons/ # Icon registry\nβ”‚ β”‚ └── src/registry.ts\nβ”‚ β”‚\nβ”‚ └── ui-shadcn/ # Radix + Tailwind\nβ”‚ └── src/components/\nβ”‚\n└── examples/\n └── stackwright-docs/ # This documentation site\n β”œβ”€β”€ pages/ # YAML page files\n β”‚ β”œβ”€β”€ index/\n β”‚ β”œβ”€β”€ getting-started/\n β”‚ β”œβ”€β”€ content-types/\n β”‚ └── ...\n β”œβ”€β”€ stackwright.yml # Theme config\n └── public/\n └── images/ # Co-located images" + code: "StackWright/\nβ”œβ”€β”€ packages/\nβ”‚ β”œβ”€β”€ core/ # YAMLβ†’React engine\nβ”‚ β”‚ └── src/\nβ”‚ β”‚ β”œβ”€β”€ components/ # React components\nβ”‚ β”‚ β”‚ β”œβ”€β”€ base/ # Content types\nβ”‚ β”‚ β”‚ β”œβ”€β”€ structural/ # Layout (PageLayout, NavSidebar)\nβ”‚ β”‚ β”‚ β”œβ”€β”€ narrative/ # Carousel, Timeline\nβ”‚ β”‚ β”‚ └── content/ # Map, etc.\nβ”‚ β”‚ β”œβ”€β”€ config/ # Default config\nβ”‚ β”‚ └── utils/ # Registries, renderers\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ types/ # Zod schemas, TypeScript types\nβ”‚ β”‚ └── src/\nβ”‚ β”‚ β”œβ”€β”€ types/ # Content schemas\nβ”‚ β”‚ └── schemas/ # JSON schemas\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ nextjs/ # Next.js adapter\nβ”‚ β”‚ └── src/components/ # NextImage, NextLink, etc.\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ themes/ # Theme system\nβ”‚ β”‚ └── src/ThemesProvider.tsx\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ build-scripts/ # Prebuild pipeline\nβ”‚ β”‚ └── src/\nβ”‚ β”‚ β”œβ”€β”€ prebuild.ts # Main entry\nβ”‚ β”‚ └── ... # Transformers\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ cli/ # Developer CLI\nβ”‚ β”‚ └── src/commands/ # init, validate, etc.\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ mcp/ # MCP server\nβ”‚ β”‚ └── src/tools/ # AI authoring tools\nβ”‚ β”‚\nβ”‚ β”œβ”€β”€ icons/ # Icon registry\nβ”‚ β”‚ └── src/registry.ts\nβ”‚ β”‚\nβ”‚ └── ui-shadcn/ # Radix + Tailwind\nβ”‚ └── src/components/\nβ”‚\n└── examples/\n └── StackWright-docs/ # This documentation site\n β”œβ”€β”€ pages/ # YAML page files\n β”‚ β”œβ”€β”€ index/\n β”‚ β”œβ”€β”€ getting-started/\n β”‚ β”œβ”€β”€ content-types/\n β”‚ └── ...\n β”œβ”€β”€ StackWright.yml # Theme config\n └── public/\n └── images/ # Co-located images" - type: text_block label: package-details-heading heading: @@ -187,7 +156,7 @@ content: height: 40 label: core-icon heading: '@stackwright/core' - description: '**The engine.** YAMLβ†’React compilation, component registry, content rendering, hooks, utilities. Exports: `renderContent()`, `stackwrightRegistry`, `registerComponent()`.' + description: '**The engine.** YAMLβ†’React compilation, component registry, content rendering, hooks, utilities. Exports: `renderContent()`, `StackWrightRegistry`, `registerComponent()`.' - icon: type: icon src: Layers @@ -201,14 +170,14 @@ content: height: 40 label: nextjs-icon heading: '@stackwright/nextjs' - description: '**The adapter.** Next.js-specific components: `StackwrightImage`, `StackwrightLink`, `createStackwrightNextConfig()`. Handles App Router and Pages Router.' + description: '**The adapter.** Next.js-specific components: `StackWrightImage`, `StackWrightLink`, `createStackWrightNextConfig()`. Handles App Router and Pages Router.' - icon: type: icon src: Palette height: 40 label: themes-icon heading: '@stackwright/themes' - description: '**The skin.** CSS custom properties theming, `ThemeProvider`, `ColorModeScript` for dark mode. Your stackwright.yml colors become CSS variables.' + description: '**The skin.** CSS custom properties theming, `ThemeProvider`, `ColorModeScript` for dark mode. Your StackWright.yml colors become CSS variables.' - icon: type: icon src: Hammer @@ -222,7 +191,7 @@ content: height: 40 label: cli-icon heading: '@stackwright/cli' - description: '**The CLI.** `npx launch-stackwright`, `stackwright validate`, `stackwright preview`. Developer interface for scaffolding and testing.' + description: '**The CLI.** `npx launch-StackWright`, `StackWright validate`, `StackWright preview`. Developer interface for scaffolding and testing.' - icon: type: icon src: Bot @@ -270,7 +239,7 @@ content: text: Safe by Construction textSize: h2 textBlocks: - - text: 'Stackwright''s constrained grammar creates a fundamentally different security posture:' + - text: 'StackWright''s constrained grammar creates a fundamentally different security posture:' textSize: body1 background: background - type: feature_list @@ -298,7 +267,7 @@ content: height: 40 label: auditable-icon heading: Auditable Surface Area - description: Security review reduces to reviewing the component library β€” a fixed, bounded codebase. Not thousands of YAML files. SBOMs are generated automatically on every build in SPDX 2.3, CycloneDX 1.5, and Stackwright Build Manifest formats. + description: Security review reduces to reviewing the component library β€” a fixed, bounded codebase. Not thousands of YAML files. SBOMs are generated automatically on every build in SPDX 2.3, CycloneDX 1.5, and StackWright Build Manifest formats. - icon: type: icon src: Bot @@ -312,7 +281,7 @@ content: text: Common Extension Points textSize: h2 textBlocks: - - text: 'Stackwright apps are standard Next.js apps. Extend freely:' + - text: 'StackWright apps are standard Next.js apps. Extend freely:' textSize: body1 background: background - type: grid diff --git a/examples/stackwright-docs/pages/cli/content.yml b/examples/stackwright-docs/pages/cli/content.yml index 4e4127e2..d27b1ecb 100644 --- a/examples/stackwright-docs/pages/cli/content.yml +++ b/examples/stackwright-docs/pages/cli/content.yml @@ -1,7 +1,7 @@ content: meta: - title: "CLI Reference | Stackwright" - description: "All Stackwright CLI commands β€” scaffolding, page management, validation, rendering, and builds." + title: "CLI Reference | StackWright" + description: "All StackWright CLI commands β€” scaffolding, page management, validation, rendering, and builds." content_items: # Hero - type: main @@ -21,7 +21,7 @@ content: text: "Installation" textSize: h2 textBlocks: - - text: "Stackwright CLI comes bundled with @stackwright/cli. If you scaffolded with launch-stackwright, it's already installed." + - text: "StackWright CLI comes bundled with @StackWright/cli. If you scaffolded with launch-StackWright, it's already installed." textSize: body1 background: surface @@ -30,11 +30,11 @@ content: language: bash lineNumbers: false code: | - # Already installed via launch-stackwright - npx launch-stackwright my-site + # Already installed via launch-StackWright + npx launch-StackWright my-site # Or add to an existing project - pnpm add @stackwright/cli + pnpm add @StackWright/cli background: surface # Page Commands @@ -44,7 +44,7 @@ content: text: "Page Commands" textSize: h2 textBlocks: - - text: "Create, list, and manage pages in your Stackwright site." + - text: "Create, list, and manage pages in your StackWright site." textSize: body1 background: background @@ -54,13 +54,13 @@ content: lineNumbers: false code: | # Add a new page - pnpm stackwright page add about --heading "About Us" + pnpm StackWright page add about --heading "About Us" # List all pages - pnpm stackwright page list + pnpm StackWright page list # Remove a page - pnpm stackwright page remove about + pnpm StackWright page remove about background: background # Validate Commands @@ -80,10 +80,10 @@ content: lineNumbers: false code: | # Validate all pages - pnpm stackwright validate + pnpm StackWright validate # Validate a specific page - pnpm stackwright validate pages/about/content.yml + pnpm StackWright validate pages/about/content.yml background: surface # Theme Commands @@ -93,7 +93,7 @@ content: text: "Theme Management" textSize: h2 textBlocks: - - text: "Preview and switch between themes. Custom themes are defined in stackwright.yml." + - text: "Preview and switch between themes. Custom themes are defined in StackWright.yml." textSize: body1 background: background @@ -103,10 +103,10 @@ content: lineNumbers: false code: | # List available themes - pnpm stackwright theme list + pnpm StackWright theme list # Preview a theme - pnpm stackwright theme preview my-theme + pnpm StackWright theme preview my-theme background: background # Render Commands @@ -126,10 +126,10 @@ content: lineNumbers: false code: | # Render a specific page - pnpm stackwright render /about --width 1280 --height 720 + pnpm StackWright render /about --width 1280 --height 720 # Render all pages - pnpm stackwright render --all + pnpm StackWright render --all background: surface # Build Commands @@ -163,7 +163,7 @@ content: label: mcp-note variant: tip title: "MCP Server" - body: "The Stackwright MCP server exposes CLI commands as tools. AI agents call validate, render, and page-add through the protocol β€” no terminal needed." + body: "The StackWright MCP server exposes CLI commands as tools. AI agents call validate, render, and page-add through the protocol β€” no terminal needed." background: surface # Quick Reference diff --git a/examples/stackwright-docs/pages/content-types/content.yml b/examples/stackwright-docs/pages/content-types/content.yml index 16107d93..97125c83 100644 --- a/examples/stackwright-docs/pages/content-types/content.yml +++ b/examples/stackwright-docs/pages/content-types/content.yml @@ -1,6 +1,6 @@ content: meta: - title: "Content Types | Stackwright" + title: "Content Types | StackWright" description: "18 content types for building pages. Each is a YAML block with required and optional fields." content_items: - type: main diff --git a/examples/stackwright-docs/pages/content.yml b/examples/stackwright-docs/pages/content.yml index 02d5cd8e..5575efee 100644 --- a/examples/stackwright-docs/pages/content.yml +++ b/examples/stackwright-docs/pages/content.yml @@ -1,7 +1,7 @@ content: meta: - title: "Stackwright β€” YAML is the syntax. The schema is the safety net. AI writes the code." - description: "From API spec to working Next.js app in hours. Stackwright is a typed DSL where AI agents write YAML, the schema enforces safety, and the output is a production-ready React app you own." + title: "StackWright β€” YAML is the syntax. The schema is the safety net. AI writes the code." + description: "From API spec to working Next.js app in hours. StackWright is a typed DSL where AI agents write YAML, the schema enforces safety, and the output is a production-ready React app you own." content_items: @@ -12,7 +12,7 @@ content: text: "YAML is the syntax. The schema is the safety net. AI writes the code." textSize: h1 textBlocks: - - text: "From API spec to working Next.js app in hours. Stackwright is a typed DSL where AI agents write YAML, the schema enforces safety, and the output is a production-ready React app you own." + - text: "From API spec to working Next.js app in hours. StackWright is a typed DSL where AI agents write YAML, the schema enforces safety, and the output is a production-ready React app you own." textSize: body1 buttons: - text: "Get Started" @@ -23,11 +23,11 @@ content: - text: "View on GitHub" textSize: button variant: outlined - href: https://github.com/Per-Aspera-LLC/stackwright + href: https://github.com/Per-Aspera-LLC/StackWright media: type: image src: ./stackwright-digital.png - alt: Stackwright Logo + alt: StackWright Logo label: hero-logo height: 280 width: 280 @@ -35,11 +35,11 @@ content: background: background color: text - # 2. Features Grid β€” "Why Stackwright" + # 2. Features Grid β€” "Why StackWright" - type: feature_list label: features heading: - text: "Why Stackwright" + text: "Why StackWright" textSize: h2 columns: 3 items: @@ -104,7 +104,7 @@ content: text: "Get Started in 60 Seconds" textSize: h2 textBlocks: - - text: "Install Stackwright and launch your first site. The Otter Raft is ready to build." + - text: "Install StackWright and launch your first site. The Otter Raft is ready to build." textSize: body1 background: background color: text @@ -151,7 +151,7 @@ content: text: "Hello World" textSize: h1 textBlocks: - - text: "My first Stackwright page." + - text: "My first StackWright page." textSize: body1 buttons: - text: "Learn More" @@ -168,7 +168,7 @@ content: text: "Ready to Build?" textSize: h2 textBlocks: - - text: "Stackwright turns structured YAML into production-ready Next.js apps. Start with the CLI, bring your team, and ship something real." + - text: "StackWright turns structured YAML into production-ready Next.js apps. Start with the CLI, bring your team, and ship something real." textSize: body1 buttons: - text: "Get Started" diff --git a/examples/stackwright-docs/pages/getting-started/content.yml b/examples/stackwright-docs/pages/getting-started/content.yml index 7e2f4a8d..c861ba77 100644 --- a/examples/stackwright-docs/pages/getting-started/content.yml +++ b/examples/stackwright-docs/pages/getting-started/content.yml @@ -1,7 +1,7 @@ content: meta: - title: "Getting Started | Stackwright" - description: "From zero to running Stackwright site in 5 minutes. No prior experience required. Install, run, and ship your first page." + title: "Getting Started | StackWright" + description: "From zero to running StackWright site in 5 minutes. No prior experience required. Install, run, and ship your first page." content_items: # Hero @@ -11,7 +11,7 @@ content: text: "Getting Started" textSize: h1 textBlocks: - - text: "From zero to running Stackwright site in 5 minutes. No prior experience required." + - text: "From zero to running StackWright site in 5 minutes. No prior experience required." textSize: body1 background: background @@ -77,7 +77,7 @@ content: text: "My Custom Heading" textSize: h1 textBlocks: - - text: "This is my first Stackwright page." + - text: "This is my first StackWright page." textSize: body1 lineNumbers: true background: surface @@ -160,11 +160,3 @@ content: description: "Integrate with AI tools via Model Context Protocol." columns: 2 background: background - - # Hackathon Tip - - type: alert - label: hackathon-tip - variant: tip - title: "Hackathon Teams" - body: "Running Stackwright at a hackathon? You can go from API spec to working demo in hours. The Otter Raft agents handle brand, theme, and content β€” just describe what you need." - background: surface diff --git a/examples/stackwright-docs/pages/otter-raft/content.yml b/examples/stackwright-docs/pages/otter-raft/content.yml index 29bbfdd3..8448e537 100644 --- a/examples/stackwright-docs/pages/otter-raft/content.yml +++ b/examples/stackwright-docs/pages/otter-raft/content.yml @@ -1,7 +1,7 @@ content: meta: - title: "The Otter Raft | Stackwright" - description: "Four AI agents that build Stackwright sites through conversation. Watch them build their own documentation site!" + title: "The Otter Raft | StackWright" + description: "Four AI agents that build StackWright sites through conversation. Watch them build their own documentation site!" content_items: # ───────────────────────────────────────────────────────── @@ -13,7 +13,7 @@ content: text: "The Otter Raft 🦦" textSize: h1 textBlocks: - - text: "Four AI agents that build Stackwright sites through conversation." + - text: "Four AI agents that build StackWright sites through conversation." textSize: h3 - text: "Brand β†’ Theme β†’ Pages β€” each otter handles one step. The Foreman coordinates the pipeline." textSize: body1 @@ -25,7 +25,7 @@ content: - type: video label: demo-video src: "./otter-raft-demo.mp4" - alt: "Watch the Otter Raft build the stackwright-docs site from conversation" + alt: "Watch the Otter Raft build the StackWright-docs site from conversation" height: 480 width: "100%" style: contained @@ -39,7 +39,7 @@ content: label: video-note variant: note title: "🎬 Watch the Otters Build Their Own Docs" - body: "The video above shows the Otter Raft working on the stackwright-docs site, which was built from scratch. Brand discovery β†’ Theme design β†’ Page generation β†’ Deploy. No YAML expertise required." + body: "The video above shows the Otter Raft working on the StackWright-docs site, which was built from scratch. Brand discovery β†’ Theme design β†’ Page generation β†’ Deploy. No YAML expertise required." background: surface # ───────────────────────────────────────────────────────── @@ -71,7 +71,7 @@ content: height: 48 label: theme-icon heading: "🦦🌈 Theme Otter" - description: "Translates brand briefs into Stackwright themes. Colors, typography, spacing, dark mode. Writes stackwright.yml configuration." + description: "Translates brand briefs into StackWright themes. Colors, typography, spacing, dark mode. Writes StackWright.yml configuration." - icon: type: icon src: FileText @@ -126,7 +126,7 @@ content: language: yaml lineNumbers: false code: | - # stackwright.yml (Theme Otter) + # StackWright.yml (Theme Otter) customTheme: colors: @@ -166,7 +166,7 @@ content: label: success-story variant: success title: "🦦 Built Their Own Docs" - body: "The stackwright-docs site you're reading right now was built by the Otter Raft. Brand discovery β†’ Theme design β†’ 7 pages β†’ Deployed. Total time: 4 hours of AI conversation. The YAML files are in the repo β€” you can read exactly what the otters wrote." + body: "The StackWright-docs site you're reading right now was built by the Otter Raft. Brand discovery β†’ Theme design β†’ 7 pages β†’ Deployed. Total time: 4 hours of AI conversation. The YAML files are in the repo β€” you can read exactly what the otters wrote." background: surface # ───────────────────────────────────────────────────────── @@ -190,7 +190,7 @@ content: - year: "Step 2" event: "Brand Otter asks questions. Voice? Tone? Primary CTA? Emotional target? Writes BRAND_BRIEF.md." - year: "Step 3" - event: "Theme Otter reads the brand brief. Picks colors, typography, spacing. Writes stackwright.yml." + event: "Theme Otter reads the brand brief. Picks colors, typography, spacing. Writes StackWright.yml." - year: "Step 4" event: "Page Otter reads the brand brief and theme. Writes page YAML files. Validates and renders screenshots." - year: "Step 5" @@ -206,7 +206,7 @@ content: text: "How to Use" textSize: h2 textBlocks: - - text: "The Otter Raft is built into launch-stackwright. After scaffolding, the agent configs are in .stackwright/otters/." + - text: "The Otter Raft is built into launch-StackWright. After scaffolding, the agent configs are in .StackWright/otters/." textSize: body1 - text: "Use with Code Puppy or any MCP-compatible AI client." textSize: body1 @@ -218,15 +218,15 @@ content: lineNumbers: false code: | # Scaffold a new project (includes Otter Raft) - npx launch-stackwright my-site + npx launch-StackWright my-site cd my-site # The Otter Raft configs are in: - .stackwright/otters/ - β”œβ”€β”€ stackwright-brand-otter.json - β”œβ”€β”€ stackwright-theme-otter.json - β”œβ”€β”€ stackwright-page-otter.json - └── stackwright-foreman-otter.json + .StackWright/otters/ + β”œβ”€β”€ StackWright-brand-otter.json + β”œβ”€β”€ StackWright-theme-otter.json + β”œβ”€β”€ StackWright-page-otter.json + └── StackWright-foreman-otter.json # Use with Code Puppy: # The otter configs are auto-loaded @@ -235,7 +235,7 @@ content: label: mcp-tip variant: tip title: "MCP Integration" - body: "The Otter Raft works best with Code Puppy or Claude Code. Each otter is an agent config that gets loaded into your AI assistant. They use the Stackwright MCP server for validation, rendering, and file operations." + body: "The Otter Raft works best with Code Puppy or Claude Code. Each otter is an agent config that gets loaded into your AI assistant. They use the StackWright MCP server for validation, rendering, and file operations." background: background # ───────────────────────────────────────────────────────── @@ -276,7 +276,7 @@ content: text: "Traditional Way" textSize: h3 textBlocks: - - text: "❌ Read Stackwright docs" + - text: "❌ Read StackWright docs" textSize: body2 - text: "❌ Learn YAML syntax" textSize: body2 @@ -322,7 +322,7 @@ content: text: "Ready to build?" textSize: h2 textBlocks: - - text: "Install Stackwright and the Otter Raft will guide you through brand discovery, theme generation, and page composition." + - text: "Install StackWright and the Otter Raft will guide you through brand discovery, theme generation, and page composition." textSize: body1 buttons: - text: "Get Started" diff --git a/examples/stackwright-docs/pages/privacy-policy/content.yml b/examples/stackwright-docs/pages/privacy-policy/content.yml index ee209e69..6cac0cf3 100644 --- a/examples/stackwright-docs/pages/privacy-policy/content.yml +++ b/examples/stackwright-docs/pages/privacy-policy/content.yml @@ -14,7 +14,7 @@ content: text: Overview textSize: h2 textBlocks: - - text: Stackwright ("we", "us", or "our") is committed to protecting your privacy. This policy describes what information + - text: StackWright ("we", "us", or "our") is committed to protecting your privacy. This policy describes what information we collect, how we use it, and your rights with respect to it. textSize: body1 - type: main @@ -65,4 +65,4 @@ content: - text: Open an Issue on GitHub textSize: body1 variant: outlined - href: https://github.com/Per-Aspera-LLC/stackwright/issues + href: https://github.com/Per-Aspera-LLC/StackWright/issues diff --git a/examples/stackwright-docs/pages/pro/content.yml b/examples/stackwright-docs/pages/pro/content.yml index 75e5acb5..fdbadab8 100644 --- a/examples/stackwright-docs/pages/pro/content.yml +++ b/examples/stackwright-docs/pages/pro/content.yml @@ -1,16 +1,16 @@ content: meta: - title: "Pro | Stackwright" - description: "OpenAPI integration, CMS backends, and enterprise features for Stackwright." + title: "Pro | StackWright" + description: "OpenAPI integration, CMS backends, and enterprise features for StackWright." content_items: - type: main label: hero heading: - text: "Stackwright Pro πŸ”" + text: "StackWright Pro πŸ”" textSize: h1 textBlocks: - - text: "Pro packages extend Stackwright into paid territory β€” third-party integrations, OpenAPI tooling, and enterprise features." + - text: "Pro packages extend StackWright into paid territory β€” third-party integrations, OpenAPI tooling, and enterprise features." textSize: h3 - text: "The OSS monorepo defines interfaces; Pro ships implementations. Clean separation by design." textSize: body1 @@ -29,13 +29,6 @@ content: label: openapi-icon heading: "OpenAPI Integration" description: "Turn any OpenAPI spec into typed Zod schemas, CollectionProviders, and API clients. 'Here is an API spec' β†’ 'Here is a working app' in hours." - - icon: - type: icon - src: Database - height: 48 - label: cms-icon - heading: "CMS Backends" - description: "Contentful, Sanity, Shopify, Airtable, Notion β€” each as a CollectionProvider. Switching backends is a one-line change. No YAML changes needed." - icon: type: icon src: Shield @@ -59,11 +52,11 @@ content: text: "OSS + Pro: Clean Separation" textSize: h2 textBlocks: - - text: "**Stackwright OSS** defines the interfaces, rendering engine, and file-backed implementations. It's the grammar β€” what you write." + - text: "**StackWright OSS** defines the interfaces, rendering engine, and file-backed implementations. It's the grammar β€” what you write." textSize: body1 - - text: "**Stackwright Pro** ships implementations too specialized, too maintenance-intensive, or too commercially sensitive for open source. It's the dictionary β€” what things mean." + - text: "**StackWright Pro** ships implementations too specialized, too maintenance-intensive, or too commercially sensitive for open source. It's the dictionary β€” what things mean." textSize: body1 - - text: "Your app never takes a hard dependency on Pro. Register a Pro provider the same way you register any Stackwright provider. Removing Pro degrades gracefully to OSS fallbacks." + - text: "Your app never takes a hard dependency on Pro. Register a Pro provider the same way you register any StackWright provider. Removing Pro degrades gracefully to OSS fallbacks." textSize: body1 background: background @@ -73,20 +66,20 @@ content: lineNumbers: false code: | # One command to scaffold Pro app - npx launch-stackwright-pro my-app --spec ./api.yaml --yes + npx launch-StackWright-pro my-app --spec ./api.yaml --yes # Or add Pro to existing project - pnpm add @stackwright-pro/openapi @stackwright-pro/auth + pnpm add @StackWright-pro/openapi @StackWright-pro/auth # Register in your app - import { registerOpenAPIProvider } from '@stackwright-pro/openapi'; + import { registerOpenAPIProvider } from '@StackWright-pro/openapi'; registerOpenAPIProvider({ spec: './api.yaml' }); - type: alert label: hackathon-tip variant: tip title: "Hackathon Teams" - body: "Stackwright Pro is built for your use case. Bring an OpenAPI spec, leave with a working app with RBAC, type-safe API clients, and mock auth for dev. See the Pro repo QUICKSTART.md for step-by-step." + body: "StackWright Pro is built for your use case. Bring an OpenAPI spec, leave with a working app with RBAC, type-safe API clients, and mock auth for dev. See the Pro repo QUICKSTART.md for step-by-step." background: surface - type: main @@ -101,10 +94,10 @@ content: - text: "View Pro Repo" textSize: button variant: contained - href: "https://github.com/Per-Aspera-LLC/stackwright-pro" + href: "https://github.com/Per-Aspera-LLC/StackWright-pro" bgColor: accent - text: "Read the Vision" textSize: button variant: outlined - href: "https://github.com/Per-Aspera-LLC/stackwright-pro/blob/main/VISION.md" + href: "https://github.com/Per-Aspera-LLC/StackWright-pro/blob/main/VISION.md" background: background diff --git a/examples/stackwright-docs/pages/showcase/content.yml b/examples/stackwright-docs/pages/showcase/content.yml index 9f4f3705..09a98d19 100644 --- a/examples/stackwright-docs/pages/showcase/content.yml +++ b/examples/stackwright-docs/pages/showcase/content.yml @@ -1,7 +1,7 @@ content: meta: - title: "Component Showcase | Stackwright" - description: "Every Stackwright content type rendered on the dark-mode-first docs theme." + title: "Component Showcase | StackWright" + description: "Every StackWright content type rendered on the dark-mode-first docs theme." content_items: # 1. Main (hero-style, graphic right) - type: main @@ -10,13 +10,13 @@ content: text: "Component Showcase" textSize: h1 textBlocks: - - text: "Every content type in the Stackwright schema, rendered on the docs theme. Used for visual regression and accessibility testing." + - text: "Every content type in the StackWright schema, rendered on the docs theme. Used for visual regression and accessibility testing." textSize: body1 buttons: - text: "View Source" textSize: button variant: outlined - href: https://github.com/Per-Aspera-LLC/stackwright + href: https://github.com/Per-Aspera-LLC/StackWright background: background color: text @@ -111,7 +111,7 @@ content: - type: text_block label: "Overview" textBlocks: - - text: "Stackwright uses YAML as the authoring format. Content is validated by Zod schemas and rendered as React components." + - text: "StackWright uses YAML as the authoring format. Content is validated by Zod schemas and rendered as React components." textSize: body1 - type: text_block label: "Architecture" @@ -139,7 +139,7 @@ content: text: "Hello World" textSize: h1 textBlocks: - - text: "My first Stackwright page." + - text: "My first StackWright page." textSize: body1 background: surface @@ -182,7 +182,7 @@ content: textSize: h2 columns: 3 items: - - quote: "Stackwright turned our hackathon into a shipping machine." + - quote: "StackWright turned our hackathon into a shipping machine." name: "Demo Author A" role: "Developer" - quote: "The Otter Raft built our entire site through conversation." @@ -200,7 +200,7 @@ content: text: "FAQ Demo" textSize: h2 items: - - question: "What is Stackwright?" + - question: "What is StackWright?" answer: "A YAML-driven React application framework that enables rapid development of professional websites." - question: "Do I need to know React?" answer: "No. You write YAML, the framework handles rendering. But you can extend with custom React components." @@ -299,9 +299,9 @@ content: heading: text: "Contact Form Demo" textSize: h2 - description: "Get in touch with the Stackwright team." - email: "hello@stackwright.dev" - email_subject: "Stackwright Inquiry" + description: "Get in touch with the StackWright team." + email: "hello@email.ai" + email_subject: "StackWright Inquiry" phone: "+1 (555) 123-4567" address: "123 Framework Lane, Open Source City" button_text: "Send Email" diff --git a/examples/stackwright-docs/pages/terms-of-service/content.yml b/examples/stackwright-docs/pages/terms-of-service/content.yml index 2f242919..c4ea4173 100644 --- a/examples/stackwright-docs/pages/terms-of-service/content.yml +++ b/examples/stackwright-docs/pages/terms-of-service/content.yml @@ -14,7 +14,7 @@ content: text: Acceptance of Terms textSize: h2 textBlocks: - - text: By accessing or using Stackwright, you agree to be bound by these Terms of Service. If you do not agree, please + - text: By accessing or using StackWright, you agree to be bound by these Terms of Service. If you do not agree, please do not use this software or site. textSize: body1 - type: main @@ -23,7 +23,7 @@ content: text: License textSize: h2 textBlocks: - - text: Stackwright is open-source software. Your use of the framework source code is governed by the license found in + - text: StackWright is open-source software. Your use of the framework source code is governed by the license found in the project repository. These terms apply to your use of this website and any hosted services. textSize: body1 - type: main @@ -43,7 +43,7 @@ content: text: Disclaimer of Warranties textSize: h2 textBlocks: - - text: Stackwright is provided "as is" without warranty of any kind, express or implied. We make no guarantees regarding + - text: StackWright is provided "as is" without warranty of any kind, express or implied. We make no guarantees regarding availability, accuracy, or fitness for a particular purpose. textSize: body1 - type: main @@ -52,8 +52,8 @@ content: text: Limitation of Liability textSize: h2 textBlocks: - - text: To the fullest extent permitted by law, Per Aspera LLC shall not be liable for any indirect, incidental, special, - or consequential damages arising from your use of Stackwright. + - text: To the fullest extent permitted by law, Per Aspera Sapientia LLC shall not be liable for any indirect, incidental, special, + or consequential damages arising from your use of StackWright. textSize: body1 - type: main label: tos-changes @@ -76,4 +76,4 @@ content: - text: Open an Issue on GitHub textSize: body1 variant: outlined - href: https://github.com/Per-Aspera-LLC/stackwright/issues + href: https://github.com/Per-Aspera-LLC/StackWright/issues diff --git a/examples/stackwright-docs/public/images/config/stackwright-alt.png b/examples/stackwright-docs/public/images/config/stackwright-alt.png new file mode 100644 index 00000000..ed37c9e5 Binary files /dev/null and b/examples/stackwright-docs/public/images/config/stackwright-alt.png differ diff --git a/examples/stackwright-docs/spdx.json b/examples/stackwright-docs/spdx.json index d0939667..c05d6dbd 100644 --- a/examples/stackwright-docs/spdx.json +++ b/examples/stackwright-docs/spdx.json @@ -1,11 +1,11 @@ { "spdxVersion": "SPDX-2.3", "dataLicense": "CC0-1.0", - "SPDXID": "SPDXRef-DOCUMENT-4daafef0", - "name": "stackwright-docs@0.1.6-alpha.3", - "documentNamespace": "https://stackwright.dev/spdx/stackwright-docs/2026-05-15T14:03:57.117Z", + "SPDXID": "SPDXRef-DOCUMENT-2772c13f", + "name": "stackwright-docs@0.1.6", + "documentNamespace": "https://stackwright.dev/spdx/stackwright-docs/2026-05-18T16:22:20.091Z", "creationInfo": { - "created": "2026-05-15T14:03:57.117Z", + "created": "2026-05-18T16:22:20.091Z", "creators": [ "Tool: @stackwright/sbom-generator", "Tool-Version: 0.0.0" @@ -337,14 +337,14 @@ { "SPDXID": "SPDXRef-Package-tailwind-merge", "name": "tailwind-merge", - "versionInfo": "3.5.0", + "versionInfo": "3.6.0", "supplier": "NOASSERTION", "downloadLocation": "NOASSERTION", "filesAnalyzed": false, "checksums": [ { "algorithm": "SHA256", - "value": "039d2e92d475e4268b1babe22545c8cc507d46e9fc09aacaa75ee5925b885a10" + "value": "084c7514e672a9283e7a4bf1c162bf248d481e0aac75ffe397d8763082a57852" } ], "licenseConcluded": "NOASSERTION", @@ -353,14 +353,14 @@ { "referenceCategory": "PACKAGE-MANAGER", "referenceType": "purl", - "referenceLocator": "pkg:npm/tailwind-merge@3.5.0" + "referenceLocator": "pkg:npm/tailwind-merge@3.6.0" } ] } ], "relationships": [ { - "spdxElementId": "SPDXRef-DOCUMENT-4daafef0", + "spdxElementId": "SPDXRef-DOCUMENT-2772c13f", "relationshipType": "DESCRIBES", "relatedSpdxElement": "SPDXRef-Package-clsx" }, diff --git a/examples/stackwright-docs/spdx.spdx b/examples/stackwright-docs/spdx.spdx index 41bfa76a..3e031b04 100644 --- a/examples/stackwright-docs/spdx.spdx +++ b/examples/stackwright-docs/spdx.spdx @@ -1,11 +1,11 @@ SPDXVersion: SPDX-2.3 DataLicense: CC0-1.0 -SPDXID: SPDXRef-DOCUMENT-4daafef0 -DocumentName: stackwright-docs@0.1.6-alpha.3 -DocumentNamespace: https://stackwright.dev/spdx/stackwright-docs/2026-05-15T14:03:57.117Z +SPDXID: SPDXRef-DOCUMENT-2772c13f +DocumentName: stackwright-docs@0.1.6 +DocumentNamespace: https://stackwright.dev/spdx/stackwright-docs/2026-05-18T16:22:20.091Z CreationInfo: - Created: 2026-05-15T14:03:57.117Z + Created: 2026-05-18T16:22:20.091Z Creator: Tool: @stackwright/sbom-generator Creator: Tool-Version: 0.0.0 @@ -123,13 +123,13 @@ ExternalRef: PACKAGE-MANAGER purl pkg:npm/react-dom@19.2.4 PackageName: tailwind-merge SPDXID: SPDXRef-Package-tailwind-merge -PackageVersion: 3.5.0 +PackageVersion: 3.6.0 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION -PackageChecksum: SHA256: 039d2e92d475e4268b1babe22545c8cc507d46e9fc09aacaa75ee5925b885a10 -ExternalRef: PACKAGE-MANAGER purl pkg:npm/tailwind-merge@3.5.0 +PackageChecksum: SHA256: 084c7514e672a9283e7a4bf1c162bf248d481e0aac75ffe397d8763082a57852 +ExternalRef: PACKAGE-MANAGER purl pkg:npm/tailwind-merge@3.6.0 -Relationship: SPDXRef-DOCUMENT-4daafef0 DESCRIBES SPDXRef-Package-clsx +Relationship: SPDXRef-DOCUMENT-2772c13f DESCRIBES SPDXRef-Package-clsx Relationship: SPDXRef-Package-clsx CONTAINS SPDXRef-Package--radix-ui-react-accordion Relationship: SPDXRef-Package-clsx CONTAINS SPDXRef-Package--radix-ui-react-slot Relationship: SPDXRef-Package-clsx CONTAINS SPDXRef-Package--radix-ui-react-tabs diff --git a/examples/stackwright-docs/stackwright.yml b/examples/stackwright-docs/stackwright.yml index de7accf0..8dfb1d22 100644 --- a/examples/stackwright-docs/stackwright.yml +++ b/examples/stackwright-docs/stackwright.yml @@ -10,17 +10,17 @@ appBar: logo: type: image src: ./stackwright-alt.png - height: 40 - width: 90 + height: 60 + width: 180 navigation: - - label: "🦦 Home" + - label: "Home" href: "/" - - label: "πŸš€ Getting Started" + - label: "Getting Started" href: "/getting-started" - - label: "⌨️ CLI" - href: "/cli" - - label: "πŸ€– Otter Raft" +# - label: "⌨️ CLI" +# href: "/cli" + - label: "🦦 Raft" href: "/otter-raft" # Search configuration (Cmd+K to open) @@ -56,13 +56,16 @@ sidebar: footer: backgroundColor: "primary" - textColor: "text" - copyright: "Built with Stackwright 🦦" + textColor: "#1A1A2E" links: - - label: "GitHub" + - label: "🦦 Built with StackWright 🦦" href: "https://github.com/Per-Aspera-LLC/stackwright" - label: "Source for this Site" href: "https://github.com/Per-Aspera-LLC/stackwright/tree/main/examples/stackwright-docs" + - label: "Terms" + href: "/terms-and-conditions" + - label: "Privacy Policy" + href: "/privacy-policy" customTheme: id: "stackwright-docs" @@ -77,7 +80,7 @@ customTheme: text: "#FFFFFF" textSecondary: "#B0BEC5" darkColors: - primary: "#92400e" # Amber 800 β€” WCAG AA compliant on light dark-mode backgrounds (~5.8:1 on #F5F5F5) + primary: "#FCC03E" # Safety Yellow secondary: "#0288D1" accent: "#F59E0B" # Amber 500 background: "#FDFDFD" diff --git a/packages/core/src/components/structural/BottomAppBar.tsx b/packages/core/src/components/structural/BottomAppBar.tsx index a23d5dff..daace05e 100644 --- a/packages/core/src/components/structural/BottomAppBar.tsx +++ b/packages/core/src/components/structural/BottomAppBar.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import { FooterConfig } from '@stackwright/types'; import { useSafeTheme } from '../../hooks/useSafeTheme'; -import { getBetterTextColor, resolveColor } from '../../utils/colorUtils'; -import { ThemedButton } from '../base/ThemedButton'; +import { useBreakpoints } from '../../hooks/useBreakpoints'; +import { getHighContrastTextColor, resolveColor } from '../../utils/colorUtils'; interface BottomAppBarProps { footer?: FooterConfig; @@ -10,75 +9,134 @@ interface BottomAppBarProps { export default function BottomAppBar({ footer }: BottomAppBarProps) { const theme = useSafeTheme(); + const { isSmDown } = useBreakpoints(); const currentYear = new Date().getFullYear(); const backgroundColor = footer?.backgroundColor - ? resolveColor(footer?.backgroundColor, theme.colors) + ? resolveColor(footer.backgroundColor, theme.colors) : theme.colors.primary; + const textColor = footer?.textColor ? resolveColor(footer.textColor, theme.colors) - : getBetterTextColor(theme.colors.text, theme.colors.textSecondary, backgroundColor); + : getHighContrastTextColor(backgroundColor, [ + theme.colors.text, + theme.colors.textSecondary, + '#ffffff', + '#000000', + ]); + + const hasLinks = footer?.links && footer.links.length > 0; + const hasSocialLinks = footer?.socialLinks && footer.socialLinks.length > 0; + const hasTopContent = hasLinks || hasSocialLinks; + + // Only chunk into columns when itemsPerColumn is explicitly configured. + // Without it, links render in a horizontal row (the natural footer pattern). + const useColumns = !!footer?.itemsPerColumn; + const linkColumns: NonNullable[] = []; + if (hasLinks && useColumns) { + const chunkSize = footer!.itemsPerColumn!; + for (let i = 0; i < footer!.links!.length; i += chunkSize) { + linkColumns.push(footer!.links!.slice(i, i + chunkSize)); + } + } return ( ); } diff --git a/packages/core/src/components/structural/NavSidebar.tsx b/packages/core/src/components/structural/NavSidebar.tsx index b325f7bf..446340d8 100644 --- a/packages/core/src/components/structural/NavSidebar.tsx +++ b/packages/core/src/components/structural/NavSidebar.tsx @@ -37,6 +37,7 @@ export interface NavSidebarProps { backgroundColor?: string; textColor?: string; onCollapseToggle?: (collapsed: boolean) => void; + topOffset?: number; } interface NavItemProps { @@ -261,6 +262,7 @@ export default function NavSidebar({ backgroundColor, textColor, onCollapseToggle, + topOffset, }: NavSidebarProps) { const theme = useSafeTheme(); const [internalCollapsed, setInternalCollapsed] = useState(false); @@ -396,9 +398,9 @@ export default function NavSidebar({ aria-label="Main navigation" style={{ position: isMobile ? 'fixed' : 'sticky', - top: 0, + top: isMobile ? 0 : (topOffset ?? 0), left: 0, - height: '100vh', + height: isMobile ? '100vh' : `calc(100vh - ${topOffset ?? 0}px)`, width: isMobile ? 280 : effectiveWidth, backgroundColor: bgColor, boxShadow: getThemeShadow(theme, 'md'), diff --git a/packages/core/src/components/structural/PageLayout.tsx b/packages/core/src/components/structural/PageLayout.tsx index 248890aa..3deadbfe 100644 --- a/packages/core/src/components/structural/PageLayout.tsx +++ b/packages/core/src/components/structural/PageLayout.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import TopAppBar from './TopAppBar'; import { PageContent, SiteConfig, PageSidebar } from '@stackwright/types'; import BottomAppBar from './BottomAppBar'; @@ -71,11 +71,26 @@ export default function PageLayout({ pageContent, siteConfig }: PageLayoutProps) const config = siteConfig || defaultSiteConfig; const backgroundColor = theme.colors.background; + const layoutRef = useRef(null); + const [topBarHeight, setTopBarHeight] = useState(0); + + useEffect(() => { + if (typeof window === 'undefined' || !layoutRef.current) return; + const header = layoutRef.current.querySelector('header'); + if (!header) return; + const ro = new ResizeObserver(([entry]) => { + setTopBarHeight(entry.contentRect.height); + }); + ro.observe(header); + return () => ro.disconnect(); + }, []); + // Resolve sidebar: page-level override > site-level default const resolvedSidebar = resolveSidebarConfig(pageContent.content.navSidebar, config.sidebar); return (
)} -
- {renderContent(pageContent, { contentItemsOnly: true })} -
-
+ {/* Content column: main grows to fill space, footer sits at the bottom */} +
+
+ {renderContent(pageContent, { contentItemsOnly: true })} +
- + +
+ {/* Search Modal - Cmd+K to open */} diff --git a/packages/core/src/components/structural/TopAppBar.tsx b/packages/core/src/components/structural/TopAppBar.tsx index 6e3504c0..79aea591 100644 --- a/packages/core/src/components/structural/TopAppBar.tsx +++ b/packages/core/src/components/structural/TopAppBar.tsx @@ -199,6 +199,9 @@ export default function TopAppBar({
+ {/* Mobile: toggle appears LEFT of hamburger */} + {colorModeToggle && isSmDown && } + {menuItems && menuItems.length > 0 && (isSmDown ? ( @@ -231,8 +234,10 @@ export default function TopAppBar({
))} - {/* Show standalone toggle only when: no menu items exist, OR on desktop */} - {colorModeToggle && (!menuItems || menuItems.length === 0 || !isSmDown) && ( + {/* Desktop: toggle appears RIGHT of nav links (original position) */} + {colorModeToggle && !isSmDown && } + + {colorModeToggle && (!menuItems || menuItems.length === 0) && ( )} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f7acd241..9242c63a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -58,6 +58,19 @@ export { getCollectionProvider, } from './utils/collectionProviderRegistry'; +// Color utilities +export { + hexToRgb, + getLuminance, + getContrastRatio, + isReadable, + getBetterTextColor, + getHoverColor, + hexToRgba, + resolveColor, + getHighContrastTextColor, +} from './utils/colorUtils'; + // Cookie & consent utilities export { getCookie, setCookie, removeCookie } from './utils/cookies'; export type { CookieOptions } from './utils/cookies'; diff --git a/packages/core/src/utils/colorUtils.ts b/packages/core/src/utils/colorUtils.ts index c13c1d88..24f13871 100644 --- a/packages/core/src/utils/colorUtils.ts +++ b/packages/core/src/utils/colorUtils.ts @@ -111,3 +111,27 @@ export function resolveColor(colorValue: string, themeColors: Record bestRatio) { + bestRatio = ratio; + best = candidate; + } + } + return best; +} diff --git a/packages/core/test/components/nav-sidebar.test.tsx b/packages/core/test/components/nav-sidebar.test.tsx index 47c7dd9a..ebf0681d 100644 --- a/packages/core/test/components/nav-sidebar.test.tsx +++ b/packages/core/test/components/nav-sidebar.test.tsx @@ -255,6 +255,20 @@ describe('NavSidebar', () => { const nav = screen.getByRole('navigation'); expect(nav).toHaveStyle({ width: '300px' }); }); + + it('applies topOffset to sticky top position', () => { + render(); + + const nav = screen.getByRole('navigation'); + expect(nav).toHaveStyle({ top: '64px' }); + }); + + it('defaults top to 0 when topOffset is not provided', () => { + render(); + + const nav = screen.getByRole('navigation'); + expect(nav).toHaveStyle({ top: '0px' }); + }); }); describe('Accessibility', () => { diff --git a/packages/core/test/setup.ts b/packages/core/test/setup.ts index 61d9bc39..7c75896f 100644 --- a/packages/core/test/setup.ts +++ b/packages/core/test/setup.ts @@ -3,6 +3,15 @@ import { stackwrightRegistry } from '../src/utils/stackwrightComponentRegistry'; import { DefaultStackwrightImage } from '../src/components/stackwright/DefaultStackwrightComponents'; import '@testing-library/jest-dom/vitest'; +// jsdom doesn't implement ResizeObserver β€” stub it for PageLayout's +// top-bar height measurement. Use a class (not an arrow function) so +// `new ResizeObserver(...)` doesn't throw "not a constructor". +global.ResizeObserver = class ResizeObserverStub { + observe() {} + unobserve() {} + disconnect() {} +} as unknown as typeof ResizeObserver; + // jsdom doesn't implement matchMedia β€” stub it for ThemeProvider's // prefers-color-scheme detection. Object.defineProperty(window, 'matchMedia', { diff --git a/packages/core/test/utils/colorUtils.test.ts b/packages/core/test/utils/colorUtils.test.ts index 3d2ae44b..feb2bca2 100644 --- a/packages/core/test/utils/colorUtils.test.ts +++ b/packages/core/test/utils/colorUtils.test.ts @@ -5,6 +5,7 @@ import { getBetterTextColor, getHoverColor, resolveColor, + getHighContrastTextColor, } from '../../src/utils/colorUtils'; describe('getContrastRatio', () => { @@ -155,3 +156,46 @@ describe('resolveColor', () => { consoleSpy.mockRestore(); }); }); + +describe('getHighContrastTextColor', () => { + it('returns black over mid-grey on a light background', () => { + // #000000 has much higher contrast on white than #888888 + const result = getHighContrastTextColor('#ffffff', ['#888888', '#000000']); + expect(result).toBe('#000000'); + }); + + it('returns white over mid-grey on a dark background', () => { + // #ffffff has much higher contrast on near-black than #888888 + const result = getHighContrastTextColor('#1a1a2e', ['#888888', '#ffffff']); + expect(result).toBe('#ffffff'); + }); + + it('picks the best from four candidates including theme colors', () => { + // On a mid-blue background, black and white both have good contrast; + // white should win as it has higher contrast on #1565c0 + const result = getHighContrastTextColor('#1565c0', [ + '#1f2937', // dark theme text + '#6b7280', // secondary theme text + '#ffffff', + '#000000', + ]); + // white (#ffffff) should have higher contrast on a dark blue than black + expect(result).toBe('#ffffff'); + }); + + it('returns the single candidate when only one is provided', () => { + const result = getHighContrastTextColor('#ffffff', ['#ff0000']); + expect(result).toBe('#ff0000'); + }); + + it('returns #000000 fallback for an empty candidates array', () => { + const result = getHighContrastTextColor('#ffffff', []); + expect(result).toBe('#000000'); + }); + + it('returns the best valid candidate even when some are invalid hex', () => { + // 'notacolor' will return getContrastRatio of 1 (invalid), so '#000000' wins + const result = getHighContrastTextColor('#ffffff', ['notacolor', '#000000']); + expect(result).toBe('#000000'); + }); +}); diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/carousel-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/carousel-mobile.png index a94406b3..cf3fbcbf 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/carousel-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/carousel-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/contact-form-stub-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/contact-form-stub-mobile.png index 189e19d4..2cd12266 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/contact-form-stub-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/contact-form-stub-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/feature-list-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/feature-list-mobile.png index 44269c56..5bfe2841 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/feature-list-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/feature-list-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/grid-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/grid-mobile.png index bd079327..fa5fb300 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/grid-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/grid-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-desktop.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-desktop.png index d92370bb..9a6825f8 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-desktop.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-desktop.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-mobile.png index 8332a15b..cb9ced26 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/home-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/pricing-table-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/pricing-table-mobile.png index d63378c5..096d4b9d 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/pricing-table-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/pricing-table-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/testimonial-grid-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/testimonial-grid-mobile.png index 7da34cd5..9756809e 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/testimonial-grid-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/testimonial-grid-mobile.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-desktop.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-desktop.png index 0e44feb3..93204b33 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-desktop.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-desktop.png differ diff --git a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-mobile.png b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-mobile.png index df8ed930..d2f1457e 100644 Binary files a/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-mobile.png and b/packages/e2e/tests/__screenshots__/stackwright-docs/visual.spec.ts/timeline-mobile.png differ diff --git a/packages/e2e/tests/e2e/user-journeys.spec.ts b/packages/e2e/tests/e2e/user-journeys.spec.ts index a1c59188..34dfe5e6 100644 --- a/packages/e2e/tests/e2e/user-journeys.spec.ts +++ b/packages/e2e/tests/e2e/user-journeys.spec.ts @@ -31,15 +31,15 @@ async function navigateAndWait(page: Page, path: string) { */ async function clickNavLink(page: Page, linkPattern: string | RegExp, expectedPath: string) { const initialUrl = page.url(); - + // Find and click the nav link (could be in header, mobile menu, etc.) const link = page.locator('header a, nav a').filter({ hasText: linkPattern }); await expect(link.first()).toBeVisible(); await link.first().click(); - + // Wait for navigation await page.waitForURL(`**${expectedPath}`, { timeout: 5000 }); - + // Verify we actually navigated const newUrl = page.url(); expect(newUrl).not.toBe(initialUrl); @@ -49,20 +49,20 @@ async function clickNavLink(page: Page, linkPattern: string | RegExp, expectedPa test.describe('User Journey: Complete Site Navigation', () => { test('Journey 1: Navigate through main site pages', async ({ page }) => { await navigateAndWait(page, '/'); - await expect(page.locator('h1').first()).toBeVisible(); - + await expect(page.locator('h1:not(:empty), h2, h3').first()).toBeVisible(); + // Navigate through available pages (skip home since we start there) const navEntries = Object.values(NAV_PATTERNS).slice(1, 4); for (const entry of navEntries) { if (!entry) continue; const link = page.locator('header a, nav a').filter({ hasText: entry.text }); - if (await link.count() > 0) { + if ((await link.count()) > 0) { await link.first().click(); await page.waitForURL(`**${entry.path}`, { timeout: 5000 }); - await expect(page.locator('h1, h2').first()).toBeVisible(); + await expect(page.locator('h1:not(:empty), h2').first()).toBeVisible(); } } - + // Return home const homeNav = NAV_PATTERNS.home; if (!homeNav) return; @@ -77,38 +77,38 @@ test.describe('User Journey: Complete Site Navigation', () => { test.skip(); return; } - + // Start at home await navigateAndWait(page, '/'); - + // Go to blog listing await clickNavLink(page, blogNav.text, blogNav.path); - + // Verify blog posts are listed (collection_list generates different href patterns) const postLinks = page.locator('a[href*="/posts/"]'); const postCount = await postLinks.count(); - + // Blog page might not have posts or uses different routing - skip if no posts if (postCount === 0) { test.skip(); return; } - + expect(postCount).toBeGreaterThan(0); - + // Click the first blog post const firstPostHref = await postLinks.first().getAttribute('href'); expect(firstPostHref).toBeTruthy(); await postLinks.first().click(); - + // Wait for article page to load await page.waitForLoadState('networkidle'); await expect(page.locator('h1, h2').first()).toBeVisible(); - + // Go back to blog await page.goBack(); await page.waitForLoadState('networkidle'); - + // Verify we're back at the blog listing expect(page.url()).toContain('/blog'); await expect(postLinks.first()).toBeVisible(); @@ -116,12 +116,12 @@ test.describe('User Journey: Complete Site Navigation', () => { test('Journey 3: All navigation links are functional', async ({ page }) => { await navigateAndWait(page, '/'); - + // Find all navigation links in header/nav const navLinks = page.locator('header a[href], nav a[href]'); const count = await navLinks.count(); expect(count).toBeGreaterThan(0); - + // Collect all internal link hrefs const hrefs: string[] = []; for (let i = 0; i < count; i++) { @@ -130,18 +130,19 @@ test.describe('User Journey: Complete Site Navigation', () => { hrefs.push(href); } } - + // Test each unique nav link const uniqueHrefs = [...new Set(hrefs)]; for (const href of uniqueHrefs) { await navigateAndWait(page, href); - + // Each page should have content const bodyText = await page.locator('body').innerText(); expect(bodyText.trim().length).toBeGreaterThan(0); - + // Each page should have at least one heading - await expect(page.locator('h1, h2, h3').first()).toBeVisible(); + // h1:not(:empty) skips the intentionally-empty TopAppBar h1 (wordmark mode) + await expect(page.locator('h1:not(:empty), h2, h3').first()).toBeVisible(); } }); @@ -150,46 +151,49 @@ test.describe('User Journey: Complete Site Navigation', () => { test.skip(); return; } - + await navigateAndWait(page, '/'); - + // Find theme toggle button const themeToggle = page.locator( 'button[aria-label*="theme" i], button[aria-label*="dark" i], button[aria-label*="light" i], ' + - 'button[title*="theme" i], button[title*="dark" i], button[title*="light" i]' + 'button[title*="theme" i], button[title*="dark" i], button[title*="light" i]' ); - + // Theme toggle might not exist in all implementations - const toggleExists = await themeToggle.count() > 0; + const toggleExists = (await themeToggle.count()) > 0; if (!toggleExists) { test.skip(); return; } - + await expect(themeToggle.first()).toBeVisible(); - + // Get initial color-mode cookie const initialCookies = await context.cookies(); - const initialMode = initialCookies.find(c => c.name === 'sw-color-mode')?.value; - + const initialMode = initialCookies.find((c) => c.name === 'sw-color-mode')?.value; + // Toggle theme await themeToggle.first().click(); await page.waitForTimeout(500); - + // Verify theme changed via cookie const afterToggleCookies = await context.cookies(); - const afterToggleMode = afterToggleCookies.find(c => c.name === 'sw-color-mode')?.value; + const afterToggleMode = afterToggleCookies.find((c) => c.name === 'sw-color-mode')?.value; expect(afterToggleMode).toBeTruthy(); expect(afterToggleMode).not.toBe(initialMode); - + // Navigate to another page const gettingStarted = NAV_PATTERNS.gettingStarted; - if (!gettingStarted) { test.skip(); return; } + if (!gettingStarted) { + test.skip(); + return; + } await clickNavLink(page, gettingStarted.text, gettingStarted.path); - + // Verify theme persisted (cookie should survive navigation) const afterNavCookies = await context.cookies(); - const afterNavMode = afterNavCookies.find(c => c.name === 'sw-color-mode')?.value; + const afterNavMode = afterNavCookies.find((c) => c.name === 'sw-color-mode')?.value; expect(afterNavMode).toBe(afterToggleMode); }); }); @@ -199,27 +203,29 @@ test.describe('User Journey: Mobile Navigation', () => { test('Mobile menu opens and navigation works', async ({ page }) => { await navigateAndWait(page, '/'); - + // Look for mobile menu button (hamburger icon) - const menuButton = page.locator('button[aria-label*="menu" i], button[aria-label*="navigation" i]').first(); - + const menuButton = page + .locator('button[aria-label*="menu" i], button[aria-label*="navigation" i]') + .first(); + // If mobile menu exists, test it if (await menuButton.isVisible()) { await menuButton.click(); - + // Wait for menu to open await page.waitForTimeout(300); - + // Mobile nav should now be visible const mobileNav = page.locator('nav, [role="navigation"]'); await expect(mobileNav.first()).toBeVisible(); - + // Find a link in the mobile nav const gettingStarted = NAV_PATTERNS.gettingStarted; if (!gettingStarted) return; const navLink = page.locator('a').filter({ hasText: gettingStarted.text }); await expect(navLink.first()).toBeVisible(); - + // Click it and verify navigation await navLink.first().click(); await page.waitForURL(`**${gettingStarted.path}`, { timeout: 5000 }); @@ -229,16 +235,16 @@ test.describe('User Journey: Mobile Navigation', () => { test('Responsive layout works on mobile viewport', async ({ page }) => { await navigateAndWait(page, '/'); - + // Content should be visible (not cut off) const main = page.locator('main, [role="main"]'); await expect(main.first()).toBeVisible(); - + // Check that grids stack properly (width should be close to viewport) const body = page.locator('body'); const bodyWidth = await body.evaluate((el) => el.offsetWidth); expect(bodyWidth).toBeLessThanOrEqual(MOBILE_VIEWPORT.width); - + // No horizontal scroll should be needed const hasHorizontalScroll = await page.evaluate(() => { return document.documentElement.scrollWidth > document.documentElement.clientWidth; @@ -250,39 +256,45 @@ test.describe('User Journey: Mobile Navigation', () => { test.describe('User Journey: Cross-Page State', () => { test('Cookies persist across page navigation', async ({ page, context }) => { await navigateAndWait(page, '/'); - + // Get initial cookies const initialCookies = await context.cookies(); - + // Navigate to another page const gettingStarted = NAV_PATTERNS.gettingStarted; - if (!gettingStarted) { test.skip(); return; } + if (!gettingStarted) { + test.skip(); + return; + } await clickNavLink(page, gettingStarted.text, gettingStarted.path); - + // Cookies should still be present const afterNavCookies = await context.cookies(); - + // Should have same or more cookies (framework may set some) expect(afterNavCookies.length).toBeGreaterThanOrEqual(initialCookies.length); }); test('Local storage persists across navigation', async ({ page }) => { await navigateAndWait(page, '/'); - + // Set a test value in localStorage await page.evaluate(() => { localStorage.setItem('test-persistence', 'stackwright-rocks'); }); - + // Navigate away const gsNav = NAV_PATTERNS.gettingStarted; - if (!gsNav) { test.skip(); return; } + if (!gsNav) { + test.skip(); + return; + } await clickNavLink(page, gsNav.text, gsNav.path); - + // Check localStorage still has our value const value = await page.evaluate(() => localStorage.getItem('test-persistence')); expect(value).toBe('stackwright-rocks'); - + // Clean up await page.evaluate(() => localStorage.removeItem('test-persistence')); }); @@ -294,31 +306,31 @@ test.describe('User Journey: Content Discovery', () => { test.skip(); return; } - + await navigateAndWait(page, '/blog'); - + // Should show blog posts (collection_list might use different patterns) const posts = page.locator('a[href*="/posts/"]'); const count = await posts.count(); - + // Blog might not have posts or uses different routing - skip if no posts if (count === 0) { test.skip(); return; } - + expect(count).toBeGreaterThan(0); - + // Each post should have a title/label for (let i = 0; i < Math.min(count, 3); i++) { const postText = await posts.nth(i).innerText(); expect(postText.trim().length).toBeGreaterThan(0); } - + // Posts should be clickable const firstPost = posts.first(); await expect(firstPost).toBeVisible(); - + const href = await firstPost.getAttribute('href'); expect(href).toBeTruthy(); expect(href).toMatch(/\/(posts?|blog)\//); // Flexible matching @@ -326,20 +338,23 @@ test.describe('User Journey: Content Discovery', () => { test('Showcase page displays all content types', async ({ page }) => { await navigateAndWait(page, SHOWCASE_PAGE); - + // Should have multiple content sections const headings = page.locator('h2, h3'); const count = await headings.count(); expect(count).toBeGreaterThan(5); // Showcase has many sections - + // Spot check a few content types exist const contentTypes = ['carousel', 'timeline', 'feature_list', 'faq']; - + for (const type of contentTypes) { // Look for label that matches the content type const section = page.locator(`text="${type}"`).or(page.locator(`[data-label*="${type}"]`)); // At least one should be visible - const visible = await section.first().isVisible().catch(() => false); + const visible = await section + .first() + .isVisible() + .catch(() => false); // This is a soft check - content might be labeled differently expect(visible || true).toBeTruthy(); } @@ -351,10 +366,10 @@ test.describe('User Journey: Error Handling', () => { const response = await page.goto('/this-page-definitely-does-not-exist', { waitUntil: 'networkidle', }); - + // Should get 404 expect(response?.status()).toBe(404); - + // Page should still render (not blank) const bodyText = await page.locator('body').innerText(); expect(bodyText.trim().length).toBeGreaterThan(0); @@ -362,7 +377,7 @@ test.describe('User Journey: Error Handling', () => { test('Navigation handles invalid links gracefully', async ({ page }) => { await navigateAndWait(page, '/'); - + // Try to navigate to a broken link await page.evaluate(() => { const link = document.createElement('a'); @@ -371,10 +386,10 @@ test.describe('User Journey: Error Handling', () => { link.id = 'test-broken-link'; document.body.appendChild(link); }); - + await page.locator('#test-broken-link').click(); await page.waitForLoadState('networkidle'); - + // Should get 404 but app shouldn't crash const bodyText = await page.locator('body').innerText(); expect(bodyText.trim().length).toBeGreaterThan(0); diff --git a/packages/e2e/tests/smoke.spec.ts b/packages/e2e/tests/smoke.spec.ts index 63a67002..9ceaac9f 100644 --- a/packages/e2e/tests/smoke.spec.ts +++ b/packages/e2e/tests/smoke.spec.ts @@ -36,7 +36,8 @@ for (const { path: pagePath, name } of PAGES) { expect(body.trim().length).toBeGreaterThan(0); // At least one heading should be present on every page - const headings = page.locator('h1, h2, h3'); + // h1:not(:empty) skips the intentionally-empty TopAppBar h1 (wordmark mode) + const headings = page.locator('h1:not(:empty), h2, h3'); await expect(headings.first()).toBeVisible(); });