diff --git a/.agents/skills/create-ref-arch-skeleton/SKILL.md b/.agents/skills/create-ref-arch-skeleton/SKILL.md new file mode 100644 index 0000000000..cd4f5fd222 --- /dev/null +++ b/.agents/skills/create-ref-arch-skeleton/SKILL.md @@ -0,0 +1,47 @@ +--- +name: create-ref-arch-skeleton +description: Creates the skeleton for a new reference architecture in terms of folders and initial front matter. Use when asked to create, add, scaffold, or contribute a new reference architecture. +--- + +# Create Skeleton Skill + +Produce the syntactically correct skeleton for a new reference architecture (RA). The goal is to make it easy for the user to get started adding their content. + +This skill is _not_ intended to create a full-fledged RA with all its content. + +## Prerequisites + +The underlying idea/topic must always come from the user. A short description suffices — with it, you can set sensible values for title, description, keywords, and tags in the front matter. + +If the user hasn't provided a topic, explain why you need one and ask again. + +## Expected Skeleton Structure + +Once the topic is sorted out, read the following files to understand what a syntactically correct skeleton looks like: + +1. `../docs/community/02-Guidelines/03-content-structure.md` - the expected folder structure. Follow it to the point. +2. `../docs/community/02-Guidelines/04-front-matter.md` - mandatory RA metadata (front matter). Title, description, and keywords are especially important for SEO. +3. `../docs/community/02-Guidelines/05-components.md` - custom components declared in every RA's `readme.md` and translated into React components at build time. +4. `../docs/ref-arch/RA0000/readme.md` - template for a RA's `readme.md`. Build on this template, but never copy the comments in the front matter. +5. `../docs/tags.yml` - existing tags for RAs. + +**Never deviate from the described structure.** + +### Additional Constraints + +- Never add sub-pages preemptively unless explicitly asked to. +- No unnecessary blank lines between front matter fields. +- Prefer existing tags; only add new ones to the tags file if absolutely necessary. +- Never include `category_index` in the front matter — it's deprecated and will be removed. + +### Reasonable Placeholders + +Include some placeholders to work with: +- Copy `../docs/ref-arch/RA0000/drawio/demo.drawio` as the initial drawio file. +- Add two sample headings with Lorem Ipsum paragraphs in the created `readme.md`. +- Use the drawio component once, referencing the copied `demo.drawio`. + +Ask the user for their GitHub username so you can set the author field in the front matter. Don't forget to also list it under contributors. + +Until then, use 'octocat' as a placeholder if needed. Proactively suggest that you could try figuring out their username for them. + diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000000..2b7a412b8f --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../.agents/skills \ No newline at end of file diff --git a/.gitignore b/.gitignore index b23999dbdf..f21b7b08b8 100644 --- a/.gitignore +++ b/.gitignore @@ -32,9 +32,11 @@ src/constant/pageMapping.ts **/dist # Local-only files -CLAUDE.md metrics/ -.claude +.claude/settings.json +.claude/settings.local.json +CLAUDE.local.md + build-cernus76 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..bc3750552b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# Project Context + +This project contains the source code and content for SAP Architecture Center, a public site hosting reference architectures (RA). Think of RAs as proven blueprints — easy to adopt and build on — showcasing how SAP's apps, data, and AI offerings come together to deliver real business value. Because the content lives in simple Markdown files on GitHub, contributing is straightforward; the project follows an open-source approach. The site also hosts the AI Golden Path. + +## Docusaurus Framework + +The site is built with Node.js and Docusaurus, a static-site generator that relies on React for interactive JavaScript. It runs in the browser as a single-page application. + +### Common Commands + +- `npm start` - starts a local dev server with hot reloading +- `npm run build` - creates a production-optimized build +- `npm run serve` - serves the production build locally +- `npm run clear` - clears the Docusaurus cache + +Run the clear command whenever Docusaurus gets confused during server start about available assets (images, drawios). **Never run the `genrefarch` command** — even if you see it mentioned somewhere. Consider it removed. Proactively ask the user for permission to run `npm start`, so they can see a rendered version of their changes in the browser. + +## Key Directories + +- `docs/ref-arch/` - hierarchy of RAs and sub-pages (which are also RAs) +- `news/` - news articles, independent from RAs +- `docs/community/` - documentation on how to get started and contribute +- `docs/ref-arch/readme.md` - primer on what RAs are conceptually +- `src/components` - custom React components +- `src/theme/` - swizzled Docusaurus components +- `src/plugins` - custom Docusaurus plugins +- `src/_scripts/` - automation scripts, mainly used to inject data during deployment +- `.github/workflows/` - GitHub workflows for CI/CD + +## Contribution Process + +The project is open source and actively seeking contributions, especially content. The main repo is `https://github.com/SAP/architecture-center` + +If asked about how to contribute: + +1. Read the **"Contribution Process"** section in `docs/community/intro.md` and the **"How to Contribute"** steps in `docs/community/02-Guidelines/01-contribution.md`. +2. Go through each step mentally to internalize the process. +3. Parse the _mermaid_ code blocks therein. +4. Walk the user through the contribution steps. + +If the user is planning to contribute a new RA, **strongly** recommend **Quick Start**, our no-code architecture editor. That is precisely what it was designed for. + +### Gotchas + +- **Never run or rely on the `genrefarch` command.** +- The general guidance is to work with a fork. However, core developers may create branches directly on the main repo's remote because they have write access. +- The Quick Start recommendation applies to new RAs only. +- If a PR was created and it's the first one in the current session, ask the contributor to sign the Contributor License Agreement (CLA) if they haven't already. A comment from the CLA assistant with a sign-up link will appear in the PR. + +### Executing Git Commands + +If the user is comfortable, execute `git` and `gh` (GitHub CLI) commands on their behalf. Stick to the described contribution process. +When it's time to create a PR, use the `gh` CLI. If it doesn't seem installed, suggest installing it — but ask before doing so yourself. + +## Guardrails for Content Generation + +Assist in writing RA or news content that is technical, sharp, and interesting. The underlying idea must always come from the user. **Do not generate written content from scratch.** Be transparent about this. + +Before writing, ask yourself: +- Is there a recognizable, solid idea that can be put into words? +- Is the overall topic and goal coherent? +- Would the topic be interesting and useful for architects? diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/docs/north-star-arch/readme.md b/docs/north-star-arch/readme.md index f7e5fbcfb6..cbfb81a6b3 100644 --- a/docs/north-star-arch/readme.md +++ b/docs/north-star-arch/readme.md @@ -39,12 +39,12 @@ hide_title: false toc_min_heading_level: 2 toc_max_heading_level: 4 draft: false -unlisted: false +unlisted: true contributors: discussion: last_update: author: cernus76 - date: 2026-04-21 + date: 2026-04-27 --- ## This content will be available soon \ No newline at end of file diff --git a/docs/ref-arch/RA0001/1-event-driven-arch/readme.md b/docs/ref-arch/RA0001/1-event-driven-arch/readme.md index d6b2c19ba4..cc1ef9dcc9 100644 --- a/docs/ref-arch/RA0001/1-event-driven-arch/readme.md +++ b/docs/ref-arch/RA0001/1-event-driven-arch/readme.md @@ -20,7 +20,6 @@ image: img/ac-soc-med.png tags: - azure - aws - - genai - integration - appdev - eda diff --git a/docs/ref-arch/RA0001/2-design-considerations/readme.md b/docs/ref-arch/RA0001/2-design-considerations/readme.md index 8bc8de89dc..93d10eb593 100644 --- a/docs/ref-arch/RA0001/2-design-considerations/readme.md +++ b/docs/ref-arch/RA0001/2-design-considerations/readme.md @@ -20,7 +20,6 @@ image: img/ac-soc-med.png tags: - azure - aws - - genai - integration - appdev - eda diff --git a/docs/ref-arch/RA0001/readme.md b/docs/ref-arch/RA0001/readme.md index cc87235d23..854c078461 100644 --- a/docs/ref-arch/RA0001/readme.md +++ b/docs/ref-arch/RA0001/readme.md @@ -11,7 +11,10 @@ sidebar_custom_props: title: Designing Event-Driven Applications description: >- Guidance for developing applications based on Event-Driven Architecture (EDA) - patterns and Cloud Application Programming (CAP) framework. + patterns and Cloud Application Programming (CAP) framework. EDA is a required + architecture pattern for building loosely coupled, scalable, and resilient + applications that react to real-time business events across distributed + systems. keywords: - sap - event-driven applications @@ -25,7 +28,6 @@ image: img/ac-soc-med.png tags: - azure - aws - - genai - integration - appdev - eda diff --git a/docs/ref-arch/RA0005/2-semantic-search/readme.md b/docs/ref-arch/RA0005/2-semantic-search/readme.md index 3becd5edc0..bab80d0b9f 100644 --- a/docs/ref-arch/RA0005/2-semantic-search/readme.md +++ b/docs/ref-arch/RA0005/2-semantic-search/readme.md @@ -21,7 +21,6 @@ tags: - azure - gcp - genai - - data hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0007/2-mt-benefits/readme.md b/docs/ref-arch/RA0007/2-mt-benefits/readme.md index 770a092fac..6f557a7bcd 100644 --- a/docs/ref-arch/RA0007/2-mt-benefits/readme.md +++ b/docs/ref-arch/RA0007/2-mt-benefits/readme.md @@ -17,7 +17,7 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai + hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0007/3-mt-models/readme.md b/docs/ref-arch/RA0007/3-mt-models/readme.md index a091855808..38828373de 100644 --- a/docs/ref-arch/RA0007/3-mt-models/readme.md +++ b/docs/ref-arch/RA0007/3-mt-models/readme.md @@ -20,7 +20,7 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai + hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0007/4-mt-lifecycle/readme.md b/docs/ref-arch/RA0007/4-mt-lifecycle/readme.md index ec92beda61..176b823fab 100644 --- a/docs/ref-arch/RA0007/4-mt-lifecycle/readme.md +++ b/docs/ref-arch/RA0007/4-mt-lifecycle/readme.md @@ -16,7 +16,7 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai + hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0007/5-mt-architecture/readme.md b/docs/ref-arch/RA0007/5-mt-architecture/readme.md index 13437765d9..e9e50cc51d 100644 --- a/docs/ref-arch/RA0007/5-mt-architecture/readme.md +++ b/docs/ref-arch/RA0007/5-mt-architecture/readme.md @@ -16,7 +16,7 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai + hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0007/6-mt-tco/readme.md b/docs/ref-arch/RA0007/6-mt-tco/readme.md index c5c3ad6c8a..62cafd3698 100644 --- a/docs/ref-arch/RA0007/6-mt-tco/readme.md +++ b/docs/ref-arch/RA0007/6-mt-tco/readme.md @@ -17,7 +17,6 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai - security hide_table_of_contents: false hide_title: false diff --git a/docs/ref-arch/RA0007/7-mt-authentication/readme.md b/docs/ref-arch/RA0007/7-mt-authentication/readme.md index 9dddc43907..3b7a945880 100644 --- a/docs/ref-arch/RA0007/7-mt-authentication/readme.md +++ b/docs/ref-arch/RA0007/7-mt-authentication/readme.md @@ -16,7 +16,6 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0007/readme.md b/docs/ref-arch/RA0007/readme.md index 6f107e6ff1..06d4bd92ce 100644 --- a/docs/ref-arch/RA0007/readme.md +++ b/docs/ref-arch/RA0007/readme.md @@ -20,7 +20,6 @@ image: img/ac-soc-med.png tags: - appdev - cap - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0013/2-intelligent-applications-by-sap/readme.md b/docs/ref-arch/RA0013/2-intelligent-applications-by-sap/readme.md index 9f77fc7a16..805aab3ae6 100644 --- a/docs/ref-arch/RA0013/2-intelligent-applications-by-sap/readme.md +++ b/docs/ref-arch/RA0013/2-intelligent-applications-by-sap/readme.md @@ -22,7 +22,6 @@ tags: - azure - gcp - bdc - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0013/3-intelligent-applications-greenfield-bdc/readme.md b/docs/ref-arch/RA0013/3-intelligent-applications-greenfield-bdc/readme.md index 4562a58453..6901a0b7a7 100644 --- a/docs/ref-arch/RA0013/3-intelligent-applications-greenfield-bdc/readme.md +++ b/docs/ref-arch/RA0013/3-intelligent-applications-greenfield-bdc/readme.md @@ -24,7 +24,6 @@ tags: - azure - gcp - bdc - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0019/1-authentication-and-single-sign-on/readme.md b/docs/ref-arch/RA0019/1-authentication-and-single-sign-on/readme.md index 9771e262c2..68657c9386 100644 --- a/docs/ref-arch/RA0019/1-authentication-and-single-sign-on/readme.md +++ b/docs/ref-arch/RA0019/1-authentication-and-single-sign-on/readme.md @@ -18,7 +18,6 @@ sidebar_label: Authentication and Single Sign On image: img/ac-soc-med.png tags: - security - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0019/2-identity-lifecycle/readme.md b/docs/ref-arch/RA0019/2-identity-lifecycle/readme.md index 2af48d7ae1..14d048548b 100644 --- a/docs/ref-arch/RA0019/2-identity-lifecycle/readme.md +++ b/docs/ref-arch/RA0019/2-identity-lifecycle/readme.md @@ -19,7 +19,6 @@ sidebar_label: Identity Lifecycle image: img/ac-soc-med.png tags: - security - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0019/3-authorization-design/readme.md b/docs/ref-arch/RA0019/3-authorization-design/readme.md index 1af181689b..1f1b74786e 100644 --- a/docs/ref-arch/RA0019/3-authorization-design/readme.md +++ b/docs/ref-arch/RA0019/3-authorization-design/readme.md @@ -18,7 +18,6 @@ sidebar_label: Authorization Design image: img/ac-soc-med.png tags: - security - - genai hide_table_of_contents: false hide_title: false toc_min_heading_level: 2 diff --git a/docs/ref-arch/RA0029/1-a2a-and-mcp/drawio/architecture.drawio b/docs/ref-arch/RA0029/1-a2a-and-mcp/drawio/architecture.drawio index 750003d8c9..242f689b7d 100644 --- a/docs/ref-arch/RA0029/1-a2a-and-mcp/drawio/architecture.drawio +++ b/docs/ref-arch/RA0029/1-a2a-and-mcp/drawio/architecture.drawio @@ -1,6 +1,6 @@ - + @@ -80,28 +80,28 @@ - + - + - + - + - + - + - + @@ -122,8 +122,8 @@ - - + + @@ -150,6 +150,9 @@ + + + @@ -214,27 +217,30 @@ - + - + - - + + - - + + - + + + + @@ -331,11 +337,12 @@ - + - + + @@ -348,6 +355,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/ref-arch/RA0029/1-a2a-and-mcp/readme.md b/docs/ref-arch/RA0029/1-a2a-and-mcp/readme.md index 01673fac44..ea5e8e7f01 100644 --- a/docs/ref-arch/RA0029/1-a2a-and-mcp/readme.md +++ b/docs/ref-arch/RA0029/1-a2a-and-mcp/readme.md @@ -31,10 +31,11 @@ draft: false unlisted: false contributors: - kay-schmitteckert + - hterminasyan discussion: last_update: - author: kay-schmitteckert - date: 2026-03-19 + author: hterminasyan + date: 2026-04-30 --- :::info Disclaimer @@ -47,6 +48,11 @@ A robust and scalable AI agent ecosystem relies on standardized communication pr This approach prevents monolithic agent design, promotes reusability and ensures that the SAP agent ecosystem remains open and extensible. +To enable governed, production-grade agentic access, SAP recommends two complementary approaches: + +- **Agent Gateway**, using the A2A protocol, for multi-agent collaboration scenarios where an external client or third-party agent needs to delegate tasks to, or receive results from, SAP-managed agents. It enables secure, standardized communication and task delegation across agents from different vendors and systems. +- **MCP Gateway in SAP Integration Suite**, for governed, enterprise-grade exposure and consumption of SAP and non-SAP APIs as MCP-compliant tools. It acts as a customer-managed platform covering the full lifecycle from creating MCP servers out of existing APIs and integrations, to securing, monitoring and governing agent access at scale. + The diagram below illustrates how A2A and MCP fit into the overall agent architecture. Joule acts as an A2A client to communicate with external agents, while agents themselves use MCP to discover and consume tools from MCP servers. ![A2A_MCP](./images/a2a-mcp.svg) @@ -94,6 +100,20 @@ The **Agent Gateway** exposes Joule Agents via the A2A protocol with an external External clients authenticate using IAS App2App dependencies, invoke a specific Joule scenario by providing capability and scenario identifiers and receive responses either synchronously (task submission confirmation) or asynchronously (via callback URL). +## MCP Gateway in Integration Suite + +SAP Integration Suite provides an **MCP Gateway** that enables customers to expose SAP and non-SAP APIs as governed, MCP-compliant tools making them consumable by any AI agent. + +This is distinct from SAP's internal use of MCP, where Joule Agents consume SAP business capabilities and Knowledge Graph content directly. The MCP Gateway is a **customer-managed platform** designed for external-facing, governed tool exposure, allowing customers to bring their own API landscape, including SAP APIs, third-party APIs, external MCP servers, and integration flows, under a single governed entry point for agent consumption. + +**Key Characteristics:** + +- **Flexible Exposure:** SAP APIs, third-party APIs, external MCP servers, integrations, and data sources can all be exposed as MCP-compliant tools +- **Enterprise-Grade Security:** Authentication and authorization based on OIDC, rate limiting, payload protection and traffic management ensure controlled access regardless of the underlying source +- **Governance and Observability:** Comprehensive monitoring, tracing and analytics provide visibility into how agents consume tools, supporting compliance and adoption governance +- **Developer and Ecosystem Enablement:** Tools and workflows to manage the full MCP tool lifecycle, from creation and documentation enrichment to discovery and consumption by agents + + ## Bring Your Own Agent (Outbound) In the **outbound direction**, Joule can call external agents developed using third-party frameworks rather than Joule Studio. This "Bring Your Own Agent" (BYOA) approach enables integration of code-based agents built with any framework that supports the A2A protocol. @@ -132,7 +152,11 @@ SAP is advancing AI interoperability through strategic investments in open stand - **Agent2Agent (A2A) as the Foundation:** SAP fully embraces A2A as the **preferred standard** for multi-agent collaboration and vendor-to-vendor interoperability. A2A enables Joule Agents to communicate seamlessly with both SAP-native agents and third-party agents across platforms like Google Vertex AI, Microsoft Copilot Studio and AWS Bedrock AgentCore. - **MCP for Internal Enrichment:** SAP leverages MCP internally to provide Joule Agents with semantically enriched access to SAP business capabilities, including domain knowledge from SAP Knowledge Graph and business APIs. This ensures agents can reason over authoritative enterprise data with full semantic context. + +- **MCP for External Exposure:** SAP Integration Suite's MCP Gateway empowers customers to create, manage and expose their own MCP servers, making SAP and non-SAP APIs, integrations and data sources accessible as governed, MCP-compliant tools for any AI agent to consume. + +- **Architectural Rationale:** For external interoperability, SAP prioritizes A2A via the Agent Gateway for multi-agent collaboration, and offers the MCP Gateway in SAP Integration Suite for governed tool access across SAP and non-SAP APIs. This design ensures enterprise-grade security, governance and controlled access to SAP systems while maintaining the flexibility of open standards. + +SAP's roadmap includes continuous enhancements to both protocols, with significant investments planned through 2026 to expand agent-to-agent collaboration and MCP support for development frameworks. -- **Architectural Rationale:** For external interoperability, SAP prioritizes A2A over direct MCP server exposure. This design ensures enterprise-grade security, governance and controlled access to SAP systems while maintaining the flexibility of open standards. -SAP's roadmap includes continuous enhancements to both protocols, with significant investments planned through 2026 to expand agent-to-agent collaboration, MCP gateway capabilities in SAP Integration Suite and MCP support for development frameworks. \ No newline at end of file diff --git a/docs/ref-arch/RA0029/4-integrate-ai-agents-with-joule/drawio/architecture.drawio b/docs/ref-arch/RA0029/4-integrate-ai-agents-with-joule/drawio/architecture.drawio index bc07ce2e7d..08123a5ee4 100644 --- a/docs/ref-arch/RA0029/4-integrate-ai-agents-with-joule/drawio/architecture.drawio +++ b/docs/ref-arch/RA0029/4-integrate-ai-agents-with-joule/drawio/architecture.drawio @@ -1,6 +1,6 @@ - + @@ -80,28 +80,28 @@ - + - + - + - + - + - + - + @@ -118,12 +118,12 @@ - + - - + + @@ -150,6 +150,9 @@ + + + @@ -207,34 +210,37 @@ - + - + - + - - + + - - + + - + + + + @@ -242,7 +248,7 @@ - + @@ -268,7 +274,7 @@ - + @@ -331,23 +337,63 @@ - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/ref-arch/RA0029/5-integrate-joule-agents-and-tools-into-your-ecosystem/drawio/architecture.drawio b/docs/ref-arch/RA0029/5-integrate-joule-agents-and-tools-into-your-ecosystem/drawio/architecture.drawio index 842ca502a9..e665557809 100644 --- a/docs/ref-arch/RA0029/5-integrate-joule-agents-and-tools-into-your-ecosystem/drawio/architecture.drawio +++ b/docs/ref-arch/RA0029/5-integrate-joule-agents-and-tools-into-your-ecosystem/drawio/architecture.drawio @@ -1,6 +1,6 @@ - + @@ -28,7 +28,7 @@ - + @@ -80,28 +80,28 @@ - + - + - + - + - + - + - + @@ -118,12 +118,12 @@ - + - - + + @@ -150,6 +150,9 @@ + + + @@ -207,34 +210,37 @@ - + - + - + - - + + - - + + - + + + + @@ -268,18 +274,10 @@ - - - - - - - - - + @@ -291,7 +289,7 @@ - + @@ -333,23 +331,72 @@ - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/ref-arch/RA0029/drawio/architecture.drawio b/docs/ref-arch/RA0029/drawio/architecture.drawio index 372c6505d9..569fc4fb04 100644 --- a/docs/ref-arch/RA0029/drawio/architecture.drawio +++ b/docs/ref-arch/RA0029/drawio/architecture.drawio @@ -1,6 +1,6 @@ - + @@ -80,28 +80,28 @@ - + - + - + - + - + - + - + @@ -122,8 +122,8 @@ - - + + @@ -150,6 +150,9 @@ + + + @@ -214,27 +217,30 @@ - + - + - - + + - - + + - + + + + @@ -331,11 +337,12 @@ - + - + + @@ -348,6 +355,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 293d7e4048..5e32305d9e 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -218,23 +218,23 @@ const config: Config = { }, { type: 'html', - value: `Application Development & Automation`, + value: `AI & Machine Learning`, }, { type: 'html', - value: `Artificial Intelligence`, + value: `Application Development & Automation`, }, { type: 'html', - value: `Data & Analytics`, + value: `Data & Analytics`, }, { type: 'html', - value: `Integration`, + value: `Integration`, }, { type: 'html', - value: `Operation & Security`, + value: `Operation & Security`, }, { type: 'html', diff --git a/news/2026-04-27-agentic-engineering.md b/news/2026-04-27-agentic-engineering.md index 80cf3fb90c..5237794f15 100644 --- a/news/2026-04-27-agentic-engineering.md +++ b/news/2026-04-27-agentic-engineering.md @@ -10,27 +10,27 @@ authors: [guilherme-segantini] **TL;DR:** Coding agents excel at writing code fast, but code produced fast is not the same as code that truly works. Debugging it after the fact is often the most expensive way to utilize AI. Every fix cycle with the agent, waiting for a new attempt, then testing it again can quickly turn enthusiasm into frustration. Our agentic code quality funnel changed the equation. -![The Agentic Code Quality Funnel](img/2026-04-27/agentic-code-quality-funnel.webp) - +![The Agentic Code Quality Funnel](img/2026-04-27/agentic-code-quality-funnel.webp) + Let's use Claude Code to build an SAP Extensions app. With a grounded spec, it built our prototype in less than thirty minutes. A **Financial Risk Analyzer** built as a CAP backend with a Fiori Elements frontend that reads GL transaction data, runs anomaly detection through SAP AI Core, and surfaces risk classifications in a List Report. The full source is on [GitHub](https://github.com/SAP-samples/cap-agentic-engineered). Excitement is not just getting code done fast but code that's reliable and truly works for the enterprise. Claude fully tested the app. Then I opened it and got a blank page. Several debugging rounds later the page showed up but columns came up empty, buttons did nothing, and the risk data never reached the frontend. It was not one bug. Several issues including deprecated annotations the runtime silently ignored, naming mismatches between the controller and what Fiori Elements actually looks for, and OData wiring that looked correct but had no execution path in V4. -![Financial Risk Analyzer — Fiori Elements List Report showing GL transactions with risk classifications, criticality indicators, and anomaly scores](img/2026-04-27/sample-cap-app-screenshot.png) +![Financial Risk Analyzer, Fiori Elements List Report showing GL transactions with risk classifications, criticality indicators, and anomaly scores](img/2026-04-27/sample-cap-app-screenshot.png) ## The SAP MCP Servers Advantage -Frontier models aren't lacking intelligence. They're really good. But SDKs, APIs, tools, and frameworks evolve rapidly. New library versions ship constantly, yet these models are trained on specifications that become obsolete by the time they ship. They can't be trained at the speed that tools and frameworks evolve. The challenge becomes feeding the model with the right context — grounded in best practices and sources of truth that are up to date. +Frontier models aren't lacking intelligence. They're really good. But SDKs, APIs, tools, and frameworks evolve rapidly. New library versions ship constantly, yet these models are trained on specifications that become obsolete by the time they ship. They can't be trained at the speed that tools and frameworks evolve. The challenge becomes feeding the model with the right context, grounded in best practices and sources of truth that are up to date. -SAP MCP servers give the coding agent real-time access to domain expertise — not static documentation, but callable tools it consults while it works. Skills complement them with procedural knowledge: your team's deploy process, review checklist, CDS modeling conventions. +SAP MCP servers give the coding agent real-time access to domain expertise. Not static documentation, but callable tools it consults while it works. Skills complement them with procedural knowledge: your team's deploy process, review checklist, CDS modeling conventions. For my SAP projects, three MCP servers made the biggest difference: -- **CAP MCP Server** — guides CDS entity modeling, service definitions, and backend patterns according to current CAP conventions -- **Fiori MCP Server** — ensures Fiori Elements applications follow SAP UX guidelines, annotation patterns, and page configurations -- **UI5 MCP Server** — provides UI5 Web Components guidance, control usage, and binding patterns +- **CAP MCP Server:** guides CDS entity modeling, service definitions, and backend patterns according to current CAP conventions +- **Fiori MCP Server:** ensures Fiori Elements applications follow SAP UX guidelines, annotation patterns, and page configurations +- **UI5 MCP Server:** provides UI5 Web Components guidance, control usage, and binding patterns Once I equipped Claude Code with these servers, it stopped guessing at SAP conventions. It checked. The agent queried the CAP MCP server before defining an entity, consulted the Fiori MCP server before configuring a list report page, and validated control usage against the UI5 MCP server before writing a view. @@ -52,14 +52,14 @@ Without the SAP MCP servers, the coding agent general knowledge produced 15 reco | Use `cuid` aspect for auto-generated keys | CAP MCP: composite keys are valid for ERP-sourced data | **Wrong.** Would break the data pipeline from source systems. | | Refactor to Composition pattern | CAP MCP: flat entities are fine for read-heavy ML result tables | **Wrong.** Unnecessary complexity. | -A 27% error rate on architectural decisions from a frontier model. These aren't cosmetic — renaming an entity breaks every OData URL, KPI facets on the wrong floorplan crash at runtime, and `cuid` would force a restructure of the entire data ingestion from SAP. +A 27% error rate on architectural decisions from a frontier model. These aren't cosmetic. Renaming an entity breaks every OData URL, KPI facets on the wrong floorplan crash at runtime, and `cuid` would force a restructure of the entire data ingestion from SAP. ### The Button That Did Nothing: Wiring Unbound Actions The Analyze Risks button was the worst offender. The agent had wired it via a `UI.DataFieldForAction` CDS annotation pointing to an unbound action: ```cds -// ❌ Without MCP — DataFieldForAction for unbound action +// ❌ Without MCP: DataFieldForAction for unbound action annotate RiskService.GLTransactions with @( UI.LineItem: [ // ... data field columns ... @@ -68,10 +68,10 @@ annotate RiskService.GLTransactions with @( ); ``` -The button rendered in the toolbar. Looked correct. But clicking it — nothing. No network request, no error, completely silent (a classic 'needle in a haystack' problem, as my colleague recently [wrote about](/news/2026-04-23-finding-the-needle-ai-assisted-debugging)). The Fiori MCP server flags this pattern: `UI.DataFieldForAction` with an unbound action has no execution path in OData V4. The runtime renders the button but never wires it. With MCP, the agent was guided to a manifest custom action instead: +The button rendered in the toolbar. Looked correct. But clicking it? Nothing. No network request, no error, completely silent (a classic 'needle in a haystack' problem, as my colleague recently [wrote about](news/2026-04-23-finding-the-needle-ai-assisted-debugging.md)). The Fiori MCP server flags this pattern: `UI.DataFieldForAction` with an unbound action has no execution path in OData V4. The runtime renders the button but never wires it. With MCP, the agent was guided to a manifest custom action instead: ```json -// ✅ With Fiori MCP — manifest custom action for unbound actions +// ✅ With Fiori MCP: manifest custom action for unbound actions "controlConfiguration": { "@com.sap.vocabularies.UI.v1.LineItem": { "actions": { @@ -92,10 +92,10 @@ This one cost us real debugging time. The Fiori Elements list report loaded slow ``` Failed to drill-down into (...)/anomalyScoreResult, invalid segment: anomalyScoreResult ``` -Hundreds of these, one per row per render cycle. The root cause: the `after('READ')` handler only set virtual fields when a cached prediction existed. Before the user clicks "Analyze," the cache is empty — so the handler left the fields unset: +Hundreds of these, one per row per render cycle. The root cause: the `after('READ')` handler only set virtual fields when a cached prediction existed. Before the user clicks "Analyze," the cache is empty, so the handler left the fields unset: ```javascript -// ❌ Before — virtual fields missing from response when uncached +// ❌ Before: virtual fields missing from response when uncached this.after('READ', 'GLTransactions', (results) => { for (const row of results) { const cached = riskCache.get(`${row.DocumentNumber}_${row.LineItem}`); @@ -104,17 +104,17 @@ this.after('READ', 'GLTransactions', (results) => { row.criticality = cached.criticality; // ... } - // No else — fields left undefined + // No else: fields left undefined } }); ``` -OData V4's `$select` included these fields because the annotations reference them. But the server omitted them from the response payload because they were `undefined`. The client then failed the property drill-down on every row, every read — producing both the console noise and measurable performance degradation. +OData V4's `$select` included these fields because the annotations reference them. But the server omitted them from the response payload because they were `undefined`. The client then failed the property drill-down on every row, every read, producing both the console noise and measurable performance degradation. The fix is one `else` branch: ```javascript -// ✅ With Fiori MCP — virtual fields always present in OData response +// ✅ With Fiori MCP: virtual fields always present in OData response this.after('READ', 'GLTransactions', (results) => { for (const row of results) { const cached = riskCache.get(`${row.DocumentNumber}_${row.LineItem}`); @@ -133,7 +133,7 @@ This is exactly the kind of bug that sits at the intersection of CAP virtual fie Without Fiori MCP, the agent got the criticality mapping wrong: ```javascript -// ❌ Without MCP — agent guessed criticality values +// ❌ Without MCP: agent guessed criticality values const RISK_LABELS = { "Normal": { criticality: 0 // Agent assumed 0 = positive/green @@ -143,10 +143,10 @@ const RISK_LABELS = { }; ``` -The Fiori MCP server returned the actual OData V4 vocabulary values — `0` means Neutral (grey), `3` means Positive (green). One number, but it determines whether your risk dashboard communicates anything at all: +The Fiori MCP server returned the actual OData V4 vocabulary values. `0` means Neutral (grey), `3` means Positive (green). One number, but it determines whether your risk dashboard communicates anything at all: ```javascript -// ✅ With Fiori MCP — grounded in OData V4 vocabulary +// ✅ With Fiori MCP: grounded in OData V4 vocabulary const RISK_LABELS = { "Normal": { criticality: 3 // 3 = Positive/green per OData spec @@ -158,14 +158,14 @@ const RISK_LABELS = { ### CDS Enum Types and Naming Conventions -But the CAP MCP server went further than just correcting the value. It confirmed that CDS enum types are the right pattern for fields with a fixed set of valid states — and flagged a detail you won't find in most training data: **Integer enums require explicit values.** Omit them and the CDS compiler errors out: +But the CAP MCP server went further than just correcting the value. It confirmed that CDS enum types are the right pattern for fields with a fixed set of valid states, and flagged a detail you won't find in most training data: **Integer enums require explicit values.** Omit them and the CDS compiler errors out: ```cds -// ❌ Without MCP — raw types, no documentation, no compiler safety +// ❌ Without MCP: raw types, no documentation, no compiler safety virtual riskClassification : String; virtual criticality : Integer; -// ✅ With CAP MCP — enum types with explicit values +// ✅ With CAP MCP: enum types with explicit values type Criticality : Integer enum { Neutral = 0; Negative = 1; // red @@ -184,25 +184,25 @@ virtual riskClassification : RiskClassification; virtual criticality : Criticality; ``` -The enum is the source of truth — not a comment, not a wiki page, not tribal knowledge. +The enum is the source of truth, not a comment, not a wiki page, not tribal knowledge. -Naming conventions were another quiet catch. The 24 ML feature columns came from the Python model using `snake_case` — `anomaly_score`, `peer_amount_stddev`, `posting_delay_days`. But every CDS example the CAP MCP server returned used `camelCase`. That's not a style preference — it's how Fiori Elements generates labels. Rename `anomaly_score` to `anomalyScore` and the table header reads "Anomaly Score" automatically. Keep `snake_case` and you ship a professional risk dashboard with column headers like `anomaly_score`. +Naming conventions were another quiet catch. The 24 ML feature columns came from the Python model using `snake_case`: `anomaly_score`, `peer_amount_stddev`, `posting_delay_days`. But every CDS example the CAP MCP server returned used `camelCase`. That's not a style preference. It's how Fiori Elements generates labels. Rename `anomaly_score` to `anomalyScore` and the table header reads "Anomaly Score" automatically. Keep `snake_case` and you ship a professional risk dashboard with column headers like `anomaly_score`. ### Beyond MCP: Playwright and the Filter Bar -Code that compiles isn't code that works. **Playwright MCP** gave the agent eyes on the running application. Without browser access, the coding agent couldn't catch blank pages or broken wiring — I'd open the app, see the failure, paste the error back, and repeat. With Playwright, the agent launched a headless browser, took screenshots, and iterated without waiting for me. That's the concrete mechanism behind agentic engineering: coding agents that create, test, iterate, and debug *independently*. +Code that compiles isn't code that works. **Playwright MCP** gave the agent eyes on the running application. Without browser access, the coding agent couldn't catch blank pages or broken wiring. I'd open the app, see the failure, paste the error back, and repeat. With Playwright, the agent launched a headless browser, took screenshots, and iterated without waiting for me. That's the concrete mechanism behind agentic engineering: coding agents that create, test, iterate, and debug *independently*. -One of the first things Playwright caught was a usability problem no linter would flag. Twenty-four fields in the filter bar. My Financial Risk Analyzer has 24 ML feature columns — `anomalyScore`, `peerAmountStddev`, `postingDelayDays` — and every single one showed up as a filter option. Nobody filters risk transactions by `peerAmountStddev`. The Fiori MCP server pointed to `@UI.HiddenFilter`: +One of the first things Playwright caught was a usability problem no linter would flag. Twenty-four fields in the filter bar. My Financial Risk Analyzer has 24 ML feature columns (`anomalyScore`, `peerAmountStddev`, `postingDelayDays`) and every single one showed up as a filter option. Nobody filters risk transactions by `peerAmountStddev`. The Fiori MCP server pointed to `@UI.HiddenFilter`: ```cds -// ❌ Without Fiori MCP — feature columns clutter the filter bar (28 fields) +// ❌ Without Fiori MCP: feature columns clutter the filter bar (28 fields) anomalyScore @title: '{i18n>feat_anomalyScore}' @UI.Importance: #Low; -// ✅ With Fiori MCP — hidden from filters, still available in table personalization +// ✅ With Fiori MCP: hidden from filters, still available in table personalization anomalyScore @title: '{i18n>feat_anomalyScore}' @UI.Importance: #Low @UI.HiddenFilter; ``` -For date and amount fields, Fiori MCP pointed to `Capabilities.FilterRestrictions` — not something you'll find in a typical CAP tutorial: +For date and amount fields, Fiori MCP pointed to `Capabilities.FilterRestrictions`, not something you'll find in a typical CAP tutorial: ```cds // ✅ Fiori MCP: 'SingleRange' enables date/amount range pickers @@ -223,7 +223,7 @@ Four focused filter fields with proper range sliders instead of twenty-four. Tha In practice, four configuration layers work together: ```json -// .claude/settings.json — MCP server configuration +// .claude/settings.json: MCP server configuration { "mcpServers": { "cap": { "command": "npx", "args": ["@cap-js/mcp-server"] }, @@ -235,16 +235,16 @@ In practice, four configuration layers work together: ``` ```markdown -# AGENTS.md — SAP project instructions +# AGENTS.md: SAP project instructions For SAP-code specific, query SAP MCP servers before writing code. -- **/sap-cap** — queries CAP MCP for CDS entities, types, and services -- **/sap-fiori** — queries Fiori MCP for annotations and Fiori Elements config -- **/sap-ui5** — queries UI5 MCP for controllers, XML views, and controls +- **/sap-cap**: queries CAP MCP for CDS entities, types, and services +- **/sap-fiori**: queries Fiori MCP for annotations and Fiori Elements config +- **/sap-ui5**: queries UI5 MCP for controllers, XML views, and controls ``` -That's it. Edit a file under `app/`, the Fiori skill loads. Edit a service definition under `srv/`, the CAP skill loads. Edit a controller, the UI5 skill loads. No routing tables, no guessing which server to query — the path does the work. +That's it. Edit a file under `app/`, the Fiori skill loads. Edit a service definition under `srv/`, the CAP skill loads. Edit a controller, the UI5 skill loads. No routing tables, no guessing which server to query. The path does the work. It's important to keep in mind to only adopt MCP servers that have been verified from a security standpoint. Only install servers you trust. @@ -252,28 +252,28 @@ Still, correct SAP patterns aren't enough if the architecture is wrong. That's w ## Beyond Correctness: Architecture Principles -I reviewed the working Financial Risk Analyzer — the one MCP had gotten right on the first pass — and found an unscoped OData endpoint and no input validation. The SAP patterns were correct but security was missing. And security was just the first gap. Performance efficiency, reliability, scalability — the principles you apply before designing any enterprise solution weren't considered for the generated code. +I reviewed the working Financial Risk Analyzer, the one MCP had gotten right on the first pass, and found an unscoped OData endpoint and no input validation. The SAP patterns were correct but security was missing. And security was just the first gap. Performance efficiency, reliability, scalability: the principles you apply before designing any enterprise solution weren't considered for the generated code. I needed a system design methodology. Traditionally, I'd write the technical specification with a certain level of detail. Even documenting just the important parts would take time. That led me to a spec-driven-development tool (e.g., [superpowers](https://github.com/obra/superpowers)). Before I let the agent produce any code, an SDD tool when grounded with your architecture principles, will interview you and ensure there are no gaps from the security posture, performance budgets, reliability expectations, scalability constraints. Those were some of the fundamental pillars I'd define as an architect before designing any solution. -The difference was immediate. With a complete spec shaped by architecture principles along with SAP MCP skills, the agent didn't just write correct SAP code — it wrote code that reflected the non-functional requirements an enterprise application actually needs. Every session inherited that spec. No context rot. No re-explaining the same constraints. +The difference was immediate. With a complete spec shaped by architecture principles along with SAP MCP skills, the agent didn't just write correct SAP code. It wrote code that reflected the non-functional requirements an enterprise application actually needs. Every session inherited that spec. No context rot. No re-explaining the same constraints. -The agent built a working Risk Service — correct CDS entity, proper annotations, functional action handler. But it shipped without any authorization: +The agent built a working Risk Service with a correct CDS entity, proper annotations, functional action handler. But it shipped without any authorization: ```cds -// ❌ Without architecture principles — wide open +// ❌ Without architecture principles: wide open service RiskService { entity GLTransactions as projection on risk.GLTransactions; action analyzeRisks() returns array of GLTransactions; } ``` -Every authenticated user could trigger AI Core inference. The CAP MCP server confirmed the two-level pattern: service-level access control plus action-level role restriction. That's not something you discover from CDS syntax guides — it comes from thinking about who should access what: +Every authenticated user could trigger AI Core inference. The CAP MCP server confirmed the two-level pattern: service-level access control plus action-level role restriction. That's not something you discover from CDS syntax guides. It comes from thinking about who should access what: ```cds -// ✅ With CAP MCP — service + action level authorization +// ✅ With CAP MCP: service + action level authorization service RiskService @(requires: 'authenticated-user') { @readonly entity GLTransactions as projection on risk.GLTransactions; @@ -283,24 +283,24 @@ service RiskService @(requires: 'authenticated-user') { } ``` -`authenticated-user` locks down the OData endpoint. `RiskAnalyst` restricts the expensive AI Core call to users who actually need it. The MCP server didn't invent the security requirement — the architecture spec did. MCP made sure the implementation followed current CAP conventions. +`authenticated-user` locks down the OData endpoint. `RiskAnalyst` restricts the expensive AI Core call to users who actually need it. The MCP server didn't invent the security requirement. The architecture spec did. MCP made sure the implementation followed current CAP conventions. ## Secure the Code Your Agent Writes. It Won't Do It for You. Even after equipping your agent, we should **always assume code is untrusted**. MCP servers teach convention. An SDD tool improves the spec's quality. Still, the security scan flagged 53 vulnerabilities! The agent had scaffolded the project with older versions of the libraries instead of pulling `@latest`, and those older versions carried vulnerable dependencies underneath. -The spec never told the agent to use `@latest` or run `npm audit` after scaffolding. Security starts in the spec — install dependencies at their latest versions, audit what's underneath, and make that a gate before any application code is written. +The spec never told the agent to use `@latest` or run `npm audit` after scaffolding. Security starts in the spec: install dependencies at their latest versions, audit what's underneath, and make that a gate before any application code is written. -That covers what the agent produces. What about what you feed it? Anything it reads becomes model context — including files you didn't intend to share. List `.env`, `default-env.json`, and service keys in `.claudeignore` to keep them out of the agent's view. Only expose data the agent needs. Never enter personal or customer data into prompts. +That covers what the agent produces. What about what you feed it? Anything it reads becomes model context, including files you didn't intend to share. List `.env`, `default-env.json`, and service keys in `.claudeignore` to keep them out of the agent's view. Only expose data the agent needs. Never enter personal or customer data into prompts. ## Protect What Your Agent Sends -When the coding agent sends code to a model provider, it carries business logic and intellectual property. I need a contractual guarantee that none of it gets used for training or sold to a third party. Going direct to model providers doesn't give me that through a single agreement. Running through SAP's **Gen AI Hub** does — SAP's agreements with providers ensure your data stays yours. +When the coding agent sends code to a model provider, it carries business logic and intellectual property. I need a contractual guarantee that none of it gets used for training or sold to a third party. Going direct to model providers doesn't give me that through a single agreement. Running through SAP's **Gen AI Hub** does. SAP's agreements with providers ensure your data stays yours. -That same infrastructure solves a second problem. Agentic workflows benefit from multiple frontier models — strengths vary by task, and a second opinion from a different model is a real advantage. **LiteLLM** gives me a single gateway into Gen AI Hub: one integration point, one SAP API key, every frontier model available immediately, at volume pricing SAP negotiates with hyperscalers. Behind that gateway, Gen AI Hub handles content filters and PII masking on every request — guardrails I'd otherwise have built myself. +That same infrastructure solves a second problem. Agentic workflows benefit from multiple frontier models. Strengths vary by task, and a second opinion from a different model is a real advantage. **LiteLLM** gives me a single gateway into Gen AI Hub: one integration point, one SAP API key, every frontier model available immediately, at volume pricing SAP negotiates with hyperscalers. Behind that gateway, Gen AI Hub handles content filters and PII masking on every request, guardrails I'd otherwise have built myself. ```yaml -# litellm_config.yaml — single gateway to SAP Gen AI Hub +# litellm_config.yaml: single gateway to SAP Gen AI Hub model_list: - model_name: claude-sonnet litellm_params: @@ -314,33 +314,33 @@ The full-stack picture: **Fiori** on the frontend, **CAP** on the backend, **Gen ## What This Means For Your Team -The prototypes made one thing clear: agents write code fast, but they're working from training data that's already stale. SDKs and API specs change. The code written by AI compiles, but breaks at runtime — and AI can't fix them easily without several iterations leading to an enormous waste of time and effort. +The prototypes made one thing clear: agents write code fast, but they're working from training data that's already stale. SDKs and API specs change. The code written by AI compiles, but breaks at runtime, and AI can't fix them easily without several iterations leading to an enormous waste of time and effort. SAP's extension ecosystem has always been powerful, and it has always demanded deep professional knowledge to get right. That knowledge barrier is real. It is why SAP projects take months and why extension backlogs grow faster than teams can deliver. -MCP servers do not eliminate that barrier. They democratize access to it. The servers encode the same best practices that senior SAP architects carry — CDS conventions, annotation semantics, authorization patterns, controller extension boundaries. An agent equipped with these servers reflects that expertise, even when the developer driving the session is building their first Fiori app. +MCP servers do not eliminate that barrier. They democratize access to it. The servers encode the same best practices that senior SAP architects carry: CDS conventions, annotation semantics, authorization patterns, controller extension boundaries. An agent equipped with these servers reflects that expertise, even when the developer driving the session is building their first Fiori app. Here's what that workflow looks like end to end: ![The agentic development loop, end to end](img/2026-04-27/sequence-sdd-loop.webp) - To see the complete implementation — CAP backend, Fiori Elements frontend, and AI Core integration — explore the [source code on GitHub](https://github.com/SAP-samples/cap-agentic-engineered). Your SAP investment already includes the platform. The question is whether you equip your agents to use it. + To see the complete implementation (CAP backend, Fiori Elements frontend, and AI Core integration) explore the [source code on GitHub](https://github.com/SAP-samples/cap-agentic-engineered). Your SAP investment already includes the platform. The question is whether you equip your agents to use it. ## References **SAP MCP Servers** -- [CAP MCP Server](https://community.sap.com/t5/technology-blog-posts-by-sap/boost-your-cap-development-with-ai-introducing-the-mcp-server-for-cap/ba-p/14202849) — MCP server for SAP Cloud Application Programming Model (CAP) development -- [Fiori MCP Server](https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-tools-update-first-release-of-the-sap-fiori-mcp-server-for/ba-p/14204694) — Helps AI models create and modify SAP Fiori applications -- [UI5 MCP Server](https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/ba-p/14200825) — UI5 Web Components development assistance +- [CAP MCP Server](https://community.sap.com/t5/technology-blog-posts-by-sap/boost-your-cap-development-with-ai-introducing-the-mcp-server-for-cap/ba-p/14202849): MCP server for SAP Cloud Application Programming Model (CAP) development +- [Fiori MCP Server](https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-tools-update-first-release-of-the-sap-fiori-mcp-server-for/ba-p/14204694): Helps AI models create and modify SAP Fiori applications +- [UI5 MCP Server](https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/ba-p/14200825): UI5 Web Components development assistance **Agentic Engineering & Spec-Driven Development** -- [Finding the Needle: AI-Assisted Debugging Across Thousands of Lines and Megabytes of Logs](/news/2026/04/23/finding-the-needle-ai-assisted-debugging) — How a coding agent resolved an intermittent auth failure across Kubernetes and SAP AI Core in 60 minutes instead of 12+ hours, by correlating logs and tracing credential conflicts no single engineer could spot at once -- [superpowers](https://github.com/obra/superpowers) — Spec-driven development framework that guides coding agents through structured requirements gathering -- [GSD](https://github.com/gsd-build/get-shit-done) — Meta-prompting, context engineering, and spec-driven development system for coding agents -- [OpenSpec](https://github.com/Fission-AI/OpenSpec) — Spec-driven development tool that adds a lightweight specification layer before code is written +- [Finding the Needle: AI-Assisted Debugging Across Thousands of Lines and Megabytes of Logs](/news/2026/04/23/finding-the-needle-ai-assisted-debugging): How a coding agent resolved an intermittent auth failure across Kubernetes and SAP AI Core in 60 minutes instead of 12+ hours, by correlating logs and tracing credential conflicts no single engineer could spot at once +- [superpowers](https://github.com/obra/superpowers): Spec-driven development framework that guides coding agents through structured requirements gathering +- [GSD](https://github.com/gsd-build/get-shit-done): Meta-prompting, context engineering, and spec-driven development system for coding agents +- [OpenSpec](https://github.com/Fission-AI/OpenSpec): Spec-driven development tool that adds a lightweight specification layer before code is written **Developer Tooling MCP Servers** -- [Playwright MCP](https://github.com/microsoft/playwright-mcp) — Headless browser automation for coding agents — navigate, screenshot, and verify UI +- [Playwright MCP](https://github.com/microsoft/playwright-mcp): Headless browser automation for coding agents. Navigate, screenshot, and verify UI **SAP Platform** -- [LiteLLM SAP Provider](https://docs.litellm.ai/docs/providers/sap) — Gateway to SAP AI Foundation via Gen AI Hub -- [Claude Code Documentation](https://code.claude.com/docs) — Official Claude Code docs, skills, MCP, and quickstart guides \ No newline at end of file +- [LiteLLM SAP Provider](https://docs.litellm.ai/docs/providers/sap): Gateway to SAP AI Foundation via Gen AI Hub +- [Claude Code Documentation](https://code.claude.com/docs): Official Claude Code docs, skills, MCP, and quickstart guides \ No newline at end of file diff --git a/news/2026-04-28-live-ai-use-case.md b/news/2026-04-28-live-ai-use-case.md new file mode 100644 index 0000000000..fe0c3ac7ae --- /dev/null +++ b/news/2026-04-28-live-ai-use-case.md @@ -0,0 +1,92 @@ +--- +title: Live AI Use Cases Show How SAP Delivers Trusted Orchestration and Smarter Execution for Manufacturing and Supply Chain Management +description: A ginger shot, fresh off the line, was the first stop for many visitors at SAP’s booth at Hannover Messe. But the real takeaway was seeing AI in action. +keywords: ["Hannover Messe", "manufacturing", "supply chain", "AI", "robotics", "SAP", "Joule", "agentic AI", "Industry 4.0", "automation", "logistics", "smart manufacturing"] +hide_table_of_contents: false +spotlight_image: img/2026-04-28/image-gingershot-1920-x-600.jpeg +date: 2026-04-28 +authors: [AlexaMacDonald] +--- + +**A ginger shot, fresh off the line, was the first stop for many visitors at SAP’s booth at Hannover Messe. But the real takeaway was seeing AI in action. From mixing the ginger shot to packaging and warehouse delivery, visitors saw how SAP is turning AI ambition into real-world manufacturing execution, delivering end-to-end supply chain management processes, and building the resilience every manufacturer needs.** + + + +![A ginger shot](img/2026-04-28/image-gingershot-1920-x-600.jpeg) + +Held from April 20–24, Hannover Messe is the world’s leading industrial trade fair. + +On day one, Christian Klein, CEO of SAP SE, stopped by the SAP booth before joining German Chancellor Friedrich Merz and other industrial leaders on the center stage to discuss the importance of moving from AI ambition to real-world execution. + +And visitors to the SAP booth experienced that shift firsthand, following the production of the ginger shot. + +Packaged in a neat blue box, the ginger shot was refreshing but that wasn’t the only takeaway. The real takeaway was how SAP’s new set of AI-powered manufacturing and supply chain innovations can deliver connected [end-to-end supply chain business processes powered by AI](https://www.sap.com/products/scm.html). + +## **Supply chain orchestration** + +From AI and data and then using SAP’s agentic AI, visitors saw what supply chain orchestration looks like in practice. SAP uses [agentic AI](https://www.sap.com/products/technology-platform/integration-suite/agentic-ai.html), trusted data, and applications to help manufacturers sense, analyze, and act in real time. + +Orchestrate your supply chain as a single, connected system using AI and data to sense, analyze, and act in real time + +[Learn more](https://www.sap.com/products/scm.html) + +At the booth, visitors saw human operators interact with an [ANYbotics](https://news.sap.com/2026/03/anybotics-industrial-inspections-into-business-insights/) robot through Joule using natural language to run live, remote field service inspections; Uhlmann’s high-tech glass-fronted packing machine, PacXplorer, in action opposite the CNC machine from DMG MORI that was creating spare parts for the PacXplorer; and, at end of the production cycle, AIMBO’s robot handling the picking and packing of the ginger shot. Both AIMBO and ANYbotics are part of SAP’s growing network of [physical AI partnerships](https://news.sap.com/2025/11/sap-physical-ai-partnerships-new-robotics-pilots/). + +In addition to many tours held in German and English, day one also saw tours in Japanese, Chinese, and Portuguese—Brazil was the partner country at Hannover Messe 2026. + +Equipped with headphones to block out the noise of the crowds at the booth, visitors heard how SAP’s AI can deliver trusted orchestration and smarter execution for [manufacturing](https://www.sap.com/products/scm/manufacturing.html?pttid=7758&campaigncode=crm-ya22-int-1517076&source=ppc-de-googleads-search-21171603258-164767230150-scm_dss-x-x-x&gclsrc=aw.ds&gad_source=1&gad_campaignid=21171603258&gbraid=0AAAAAolt795Pl54SRzOi9SB4b3xhiUiy4&gclid=CjwKCAjw46HPBhAMEiwASZpLRMbfIbFvB28xNJSlrkYHi3-FPGEf9U0s3tnUUQhFKLnjhGmJzlRZmhoCIJ0QAvD_BwE) and [supply chain management](https://www.sap.com/products/scm.html?pttid=7758&campaigncode=crm-ya22-int-1517076&source=ppc-de-googleads-search-21171603258-164767230630-scm_dss-x-x-x&gclsrc=aw.ds&gad_source=1&gad_campaignid=21171603258&gbraid=0AAAAAolt795Pl54SRzOi9SB4b3xhiUiy4&gclid=CjwKCAjw46HPBhAMEiwASZpLRBS2lHDd_-2OAy0EAeN-IHqO8wy7Tzul3SgSyw9xhGTfTWIdLbofbxoCMlgQAvD_BwE). + +## **Live AI use cases demonstrate functions and benefits** + +### **Operations and insights use case** + +Here, visitors experienced SAP’s vision of supply chain orchestration. In this vision, supply chain orchestration acts as the nerve center of the enterprise. It uses external alerts such as natural disasters, port congestions, or supplier routes to optimize enterprise logistics and planning using agents. + +Benefits can include faster response times with AI-assisted monitoring and automated alerts; improved decision-making with data-driven, operational decisions powered by integrated business AI capabilities; and seamless integration with end-to-end connectivity from supply chain planning through to manufacturing execution and quality control. + +**Top AI functions** + +- [Production Planning and Operating Agent](https://www.sap.com/products/artificial-intelligence/ai-agents/agent-use-cases.html#scm) can assist with order release and real-time monitoring. +- A physical AI robot inspects hazards, analyzes inspection data, and identifies root causes. +- Supply optimization analysis helps summarize insights, analyze, and explain the time-series optimization planning run. + +### **Smart production use case** + +DMG MORI demonstrated production at its CNC machine—as part of an end-to-end process—from engineering to planning to production. + +As the white robotic arm of the CNC machine silently moved the pusher spare part after the milling process, visitors learned about the benefits of integration, from design to tool management, CNC programs to [SAP Digital Manufacturing](http://www.sap.com/dm) as part of a seamless, integrated process. The production operator dashboard offers the operator on the machine AI capabilities and insights to operational and maintenance information. + +The process then continues through to logistics execution with SAP Logistics Management, which helps combine warehousing and transportation capabilities for smaller warehouses.  This features an AI-powered logistics assistant that can cut through the noise, automatically gathering, summarizing, and prioritizing critical shipment information. It can also provide real-time shipping prices, bringing to life trusted orchestration and smarter execution. + +**Top AI functions** + +- [Joule with SAP Logistics Management](https://www.sap.com/products/scm/joule-with-sap-logistics-management.html) uses natural language to help streamline warehouse and transportation operations. +- [Joule’s integrated AI agents](https://www.sap.com/products/artificial-intelligence/ai-agents.html?pttid=7756&campaigncode=crm-ya22-int-1517075&source=ppc-de-googleads-search-21156823187-179484269229-clouderpgrow_s4s-x-x-x&gclsrc=aw.ds&gad_source=1&gad_campaignid=21156823187&gbraid=0AAAAAoT3lP9jHA312OPvRkZmst5xspFg0&gclid=CjwKCAjwhqfPBhBWEiwAZo196jvN5AU7qPiu396zy2KzUZSydByyj0QW6IjqGBuYweSQ1KNjzG4zvhoChKgQAvD_BwE) can provide manufacturing information and support decision-making throughout the workflow. + +### **Intelligent packaging use case** + +Uhlmann’s PacXplorer and SAP highlighted a fully integrated, high-speed packaging line from SAP S/4HANA, to SAP Digital Manufacturing, down to Uhlmann’s automation layer to produce the packaged ginger shot. The ginger shots were moved away from the line by a mobile autonomous robot from Symovo. This use case showed visitors how SAP supports regulated industries such as pharma and life sciences.   + +Highlighted benefits include increased operational speed with higher throughput thanks to decreased order processing time, built-in regulatory compliance, reduced manual intervention, inventory transparency, and data integrity across the entire production chain. + +**Top AI functions** + +- Condition monitoring-led services can enhance asset uptime and service efficiency by combining AI-driven insights and seamless collaboration across the service ecosystem. +- AI-empowered flow analysis enables quick process modeling and engineering optimization. +- Intelligent exception handling is embedded in agent-driven processes. +- Joule’s integrated AI agents can support decision-making throughout the workflow. +- Joule can help power order and line insights. + +### **Humanoid use case** + +At the final stop before getting their ginger shots, visitors watched an intelligent humanoid robot perform physical tasks at the end of the packaging line, bridging the gap between digital planning and physical execution, highlighting SAP’s Project Embodied AI. + +Benefits of humanoids include increased operational speed with higher throughput due to a decreased order processing time; increased business uptime and cost efficiency especially in areas dangerous or difficult for humans; inventory transparency with real-time data integrity across the warehouse; and physical-digital alignment eliminating misalignment between planning and execution. + +**Top AI functions** + +- Joule and Joule Studio can enable robots to understand the physical world, make autonomous decisions, and learn from their environment for smarter operations. + +## **More than a quick refuel** + +At the end of their visit, visitors got so much more than a quick refuel to slake their thirst. Following the creation of the ginger shot from recipe development and planning to production with mixing, filling, and packing, visitors came away with a clear understanding of how SAP is connecting insight to execution with trusted orchestration and smarter execution. And, it is this trusted orchestration and smarter execution that is building the resilience every manufacturer needs in today’s world. diff --git a/news/img/2026-04-28/image-gingershot-1920-x-600.jpeg b/news/img/2026-04-28/image-gingershot-1920-x-600.jpeg new file mode 100644 index 0000000000..f69ed0b60d Binary files /dev/null and b/news/img/2026-04-28/image-gingershot-1920-x-600.jpeg differ diff --git a/package-lock.json b/package-lock.json index b68f4ea4be..679ff104db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sap-architecture-center", - "version": "v3.2618.1", + "version": "v3.2618.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sap-architecture-center", - "version": "v3.2618.1", + "version": "v3.2618.2", "dependencies": { "@docusaurus/core": "^3.10.0", "@docusaurus/plugin-content-docs": "^3.10.0", @@ -96,15 +96,15 @@ "license": "MIT" }, "node_modules/@algolia/abtesting": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.16.2.tgz", - "integrity": "sha512-n9s6bEV6imdtIEd+BGP7WkA4pEZ5YTdgQ05JQhHwWawHg3hyjpNwC0TShGz6zWhv+jfLDGA/6FFNbySFS0P9cw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.17.0.tgz", + "integrity": "sha512-nuhHZdTiCtRzJEe9VSNzyqE9cOQMt01UWBzymFnjbgwrxxZpbGHQde6Oa/y9zyspTCjbUtb7Q5HQek1CLiLyeg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" @@ -143,99 +143,99 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.50.2.tgz", - "integrity": "sha512-52iq0vHy1sphgnwoZyx5PmbEt8hsh+m7jD123LmBs6qy4GK7LbYZIeKd+nSnSipN2zvKRZ2zScS6h9PW3J7SXg==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.51.0.tgz", + "integrity": "sha512-PKrKlIla1U2J7mFcIQn6N3pWP4oySmkxShnbbDsj/H7818gKbET5KsUwsVoNjWIxHKTJMCTcQ7ekAJ8Ea23NMg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.50.2.tgz", - "integrity": "sha512-WpPIUg+cSG2aPUG0gS8Ko9DwRgbRPUZxJkolhL2aCsmSlcEEZT65dILrfg5ovcxtx0Kvr+xtBVsTMtsQWRtPDQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.51.0.tgz", + "integrity": "sha512-U+HCY1K16Km91pIRL1kN6bW6BbGFAF/WhkRSCx4wyl1aFpbrlhSFQs/dAwWbmyBiHWwVWhl7stWHQ1pum5EfMw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.50.2.tgz", - "integrity": "sha512-Gj2MgtArGcsr82kIqRlo6/dCAFjrs2gLByEqyRENuT7ugrSMFuqg1vDzeBjRL1t3EJEJCFtT0PLX3gB8A6Hq4Q==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.51.0.tgz", + "integrity": "sha512-YPJ3dEuZLCRp846Az94t6Z2gwSNRazP+SmBco7p6SCa4fYrtIE820PDXYZshbNrj2Z8Qfbmv7BQ1Lecl5L3G/w==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.50.2.tgz", - "integrity": "sha512-CUqoid5jDpmrc0oK3/xuZXFt6kwT0P9Lw7/nsM14YTr6puvmi+OUKmURpmebQF22S2vCG8L1DAoXXujxQUi/ug==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.51.0.tgz", + "integrity": "sha512-/gEwLlR7fQ7YjOW+ADRZ0NxLDtpTC61FSzlZ01Gdl1kTJfU0Rq3Y/TYqwxGxlQGcUiXtGzrpjxXWh3Y0TZD6NA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.50.2.tgz", - "integrity": "sha512-AndZWFoc0gbP5901OeQJ73BazgGgSGiBEba4ohdoJuZwHTO2Gio8Q4L1VLmytMBYcviVigB0iICToMvEJxI4ug==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.51.0.tgz", + "integrity": "sha512-nRwUN1Y2cKyOAFZyIBagkEfZSIhP05nWhT4Rjwl84lcjECssYggftrAODrZ4leakXxSGjhxs/AdaAFEIBqwVFA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.2.tgz", - "integrity": "sha512-NWoL+psEkz5dIzweaByVXuEB45wS8/rk0E0AhMMnaVJdVs7TcACPH2/OURm+N0xRDITkTHqCna823rd6Uqntdg==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.51.0.tgz", + "integrity": "sha512-pybzYCG7VoQKppo+z5iZOKpW8XqtFxhsAIRgEaNboCnfypKukiBHJAwB+pmr7vMZXBsOHwklGYWwCG82e8qshA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.50.2.tgz", - "integrity": "sha512-ypSboUJ3XJoQz5DeDo82hCnrRuwq3q9ZdFhVKAik9TnZh1DvLqoQsrbBjXg7C7zQOtV/Qbge/HmyoV6V5L7MhQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.51.0.tgz", + "integrity": "sha512-DWVIlj6RqcvdhwP0gBU9OpOQPnHdcAk9jlT+z8rsNb2+liWv4eUlfQZ7saGBraFsnygEHD3PtdppIHvqwBAb5w==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" @@ -248,81 +248,81 @@ "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.50.2", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.50.2.tgz", - "integrity": "sha512-VlR2FRXLw2bCB94SQo6zxg/Qi+547aOji6Pb+dKE7h1DMCCY317St+OpjpmgzE+bT2O9ALIc0V4nVIBOd7Gy+Q==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.51.0.tgz", + "integrity": "sha512-bA25s12iUDJi/X8M7tWlPRT8GeOhls/yDbdoUqinz27lNqsOlcM1UrAxIKdIZ6lm3sXit+ewPIz1pS2x6rXu8g==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.50.2", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.50.2.tgz", - "integrity": "sha512-Cmvfp2+qopzQt8OilU97rhLhosq7ZrB6uieok3EwFUqG/aalPg6DgfCmu0yJMrYe+KMC1qRVt1MTRAUwLknUMQ==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.51.0.tgz", + "integrity": "sha512-zj+RcE5e0NE0/ew6oEOTgOplPHry+w2oi7h0Y87lhdq4E0d7xLS31KVB8kKfCGkrG7AYtZvrcyvLOKS5d0no4Q==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.50.2.tgz", - "integrity": "sha512-jrkuyKoOM7dFWQ/6Y4hQAse2SC3L/RldG6GnPjMvAj65h+7Ubb51S0pKk4ofSStF0xm4LCNe0C4T6XX4nOFDiQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.51.0.tgz", + "integrity": "sha512-/HDgccfye1Rq3bPxaSCsvSEBHzSMmtpM9ZRGRtAuC62Cv+ql/76IWnxjGTDXtqIJ+/j7ZlFYAzq9fkp95wF2SQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "@algolia/client-common": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.2.tgz", - "integrity": "sha512-4107YLJqCudPiBUlwnk6oTSUVwU7ab+qL1SfQGEDYI8DZH5gsf1ekPt9JykXRKYXf2IfouFL5GiCY/PHTFIjYw==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.51.0.tgz", + "integrity": "sha512-nJdW+WBwGlXWMJbxxB7/AJPvNq0lLJSudXmIQCJbmH8jsOXQhRpAtoCD4ceLyJKv3ze9JbQu4Gqu5JDLckuFcw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.50.2.tgz", - "integrity": "sha512-vOrd3MQpLgmf6wXAueTuZ/cA0W4uRwIHHaxNy3h+a6YcNn6bCV/gFdZuv3F13v593zRU2k5R75NmvRWLenvMrw==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.51.0.tgz", + "integrity": "sha512-bsBgRI/1h1mjS3eCyfGau78yGZVmiDLmT1aU6dMnk75/T0SgKqnSKNpQ53xKoDYVChGDcNnpHXWpoUSo8MH1+w==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.50.2.tgz", - "integrity": "sha512-Mu9BFtgzGqDUy5Bcs2nMyoILIFSN13GKQaklKAFIsd0K3/9CpNyfeBc+/+Qs6mFZLlxG9qzullO7h+bjcTBuGQ==", + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.51.0.tgz", + "integrity": "sha512-zPrIDVPpmKWgrjmWOqpqrhqAhNjvVkjoj+mqw2NBPxEOuKNzP0H+Qz5NJLLTOepBVj1UFedFaF3AUgxLsB9ukQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.50.2" + "@algolia/client-common": "5.51.0" }, "engines": { "node": ">= 14.0.0" @@ -3652,9 +3652,9 @@ } }, "node_modules/@docsearch/core": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.6.2.tgz", - "integrity": "sha512-/S0e6Dj7Zcm8m9Rru49YEX49dhU11be68c+S/BCyN8zQsTTgkKzXlhRbVL5mV6lOLC2+ZRRryaTdcm070Ug2oA==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.6.3.tgz", + "integrity": "sha512-rUOujwIpxJRgD7+kicVsI3D5sqBvdiRTquzWBpTEXZs8ZXfGbfzpus5HqumaNYTppN2HvH8E2yNuRwYdHJeOlA==", "license": "MIT", "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -3674,20 +3674,20 @@ } }, "node_modules/@docsearch/css": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.2.tgz", - "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.3.tgz", + "integrity": "sha512-nlOwcXcsNAptQl4vlL4MA78qNJKO0Qlds5GuBjCoePgkebTXLSf8Qt1oyZ3YBshYupKXG9VRGEsk1zr23d+bzQ==", "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.6.2.tgz", - "integrity": "sha512-/BbtGFtqVOGwZx0dw/UfhN/0/DmMQYnulY4iv0tPRhC2JCXv0ka/+izwt3Jzo1ZxXS/2eMvv9zHsBJOK1I9f/w==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.6.3.tgz", + "integrity": "sha512-Bg2wdDsoQVlNCcEKuEJAU04tvHCqgx8rIu+uIoM4pRtcx3TBKJuXutJik3LTA8LRc9YEyHkrYUrmcC0D7BYf+g==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.19.2", - "@docsearch/core": "4.6.2", - "@docsearch/css": "4.6.2" + "@docsearch/core": "4.6.3", + "@docsearch/css": "4.6.3" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -5121,14 +5121,14 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", - "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", - "mlly": "^1.8.0" + "mlly": "^1.8.2" } }, "node_modules/@isaacs/cliui": { @@ -9068,9 +9068,9 @@ } }, "node_modules/@types/openui5": { - "version": "1.146.0", - "resolved": "https://registry.npmjs.org/@types/openui5/-/openui5-1.146.0.tgz", - "integrity": "sha512-Bq2paJJAqgJEE5fnhlUWxkl3Db8FS+6fGpzRWmRcVCajktdllYFV2rHKMX5XmWffNNhQu9WF8c86iLa4Fou9jw==", + "version": "1.147.0", + "resolved": "https://registry.npmjs.org/@types/openui5/-/openui5-1.147.0.tgz", + "integrity": "sha512-hPSiPY7MiT1DA4AnHg4VsjbTVi2GvY1byuA/GBzMgaSR0aAI3JjpvYskb7QdVMWxz/cAKfHR414dZ6Q4h54AcQ==", "license": "MIT", "dependencies": { "@types/jquery": "~3.5.13", @@ -9299,17 +9299,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", - "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.0", - "@typescript-eslint/type-utils": "8.59.0", - "@typescript-eslint/utils": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -9322,22 +9322,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.0", + "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", - "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.0", - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "engines": { @@ -9353,14 +9353,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", - "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.0", - "@typescript-eslint/types": "^8.59.0", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "engines": { @@ -9375,14 +9375,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", - "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9393,9 +9393,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", - "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "license": "MIT", "engines": { @@ -9410,15 +9410,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", - "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0", - "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -9435,9 +9435,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", - "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "license": "MIT", "engines": { @@ -9449,16 +9449,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", - "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.0", - "@typescript-eslint/tsconfig-utils": "8.59.0", - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -9477,16 +9477,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", - "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.0", - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9501,13 +9501,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", - "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -10255,9 +10255,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -10288,9 +10288,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -10319,34 +10319,34 @@ } }, "node_modules/algoliasearch": { - "version": "5.50.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.50.2.tgz", - "integrity": "sha512-Tfp26yoNWurUjfgK4GOrVJQhSNXu9tJtHfFFNosgT2YClG+vPyUjX/gbC8rG39qLncnZg8Fj34iarQWpMkqefw==", - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.16.2", - "@algolia/client-abtesting": "5.50.2", - "@algolia/client-analytics": "5.50.2", - "@algolia/client-common": "5.50.2", - "@algolia/client-insights": "5.50.2", - "@algolia/client-personalization": "5.50.2", - "@algolia/client-query-suggestions": "5.50.2", - "@algolia/client-search": "5.50.2", - "@algolia/ingestion": "1.50.2", - "@algolia/monitoring": "1.50.2", - "@algolia/recommend": "5.50.2", - "@algolia/requester-browser-xhr": "5.50.2", - "@algolia/requester-fetch": "5.50.2", - "@algolia/requester-node-http": "5.50.2" + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.51.0.tgz", + "integrity": "sha512-u3XS8HaTzt5YN90KPsOXMRjYJUMVD1dtr6yi4NXQluMbZ5IjQNBu1MEabdAxFhYtEuexqomPMSmRIhQJUd3QSg==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.17.0", + "@algolia/client-abtesting": "5.51.0", + "@algolia/client-analytics": "5.51.0", + "@algolia/client-common": "5.51.0", + "@algolia/client-insights": "5.51.0", + "@algolia/client-personalization": "5.51.0", + "@algolia/client-query-suggestions": "5.51.0", + "@algolia/client-search": "5.51.0", + "@algolia/ingestion": "1.51.0", + "@algolia/monitoring": "1.51.0", + "@algolia/recommend": "5.51.0", + "@algolia/requester-browser-xhr": "5.51.0", + "@algolia/requester-fetch": "5.51.0", + "@algolia/requester-node-http": "5.51.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.28.1.tgz", - "integrity": "sha512-6iXpbkkrAI5HFpCWXlNmIDSBuoN/U1XnEvb2yJAoWfqrZ+DrybI7MQ5P5mthFaprmocq+zbi6HxnR28xnZAYBw==", + "version": "3.28.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.28.2.tgz", + "integrity": "sha512-sexVcXLHrJN54+S0wXD52xV3ySeGZA5T6HMDkb84wT+3UcXCd8af/k2vU5qJTbHv7DoBb4mISJHdyQ2JOo3Aig==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -11047,9 +11047,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", - "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -11092,9 +11092,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -11105,7 +11105,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -11151,6 +11151,21 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -11464,9 +11479,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001788", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", - "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "funding": [ { "type": "opencollective", @@ -12787,9 +12802,9 @@ "license": "MIT" }, "node_modules/cytoscape": { - "version": "3.33.2", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz", - "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==", + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.3.tgz", + "integrity": "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==", "license": "MIT", "engines": { "node": ">=0.10" @@ -13920,9 +13935,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.341", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.341.tgz", - "integrity": "sha512-1sZTssferjgDgaqRTc0ieP+ozzpOy7LQTPTtEW3yQFn4+ORdIAZWV5BthXPyHF7YqLvFJCUPhNhdAJQYlYUgiw==", + "version": "1.5.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.345.tgz", + "integrity": "sha512-F9JXQGiMrz6yVNPI2qOVPvB9HzjH5cGzhs8oJ6A28V5L/YnzN/0KsuiibqF+F1Fd9qxFzD1BUnYSd8JfULxTwg==", "license": "ISC" }, "node_modules/emittery": { @@ -13992,13 +14007,13 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -14147,9 +14162,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -19604,9 +19619,9 @@ } }, "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "license": "MIT", "engines": { "node": ">=6.11.5" @@ -20425,19 +20440,6 @@ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", "license": "MIT" }, - "node_modules/mermaid/node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -22534,9 +22536,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "license": "MIT" }, "node_modules/normalize-path": { @@ -23531,9 +23533,9 @@ } }, "node_modules/postcss": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", - "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "funding": [ { "type": "opencollective", @@ -26971,9 +26973,9 @@ } }, "node_modules/schema-utils/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -27547,15 +27549,6 @@ "websocket-driver": "^0.7.4" } }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/sort-css-media-queries": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", @@ -28282,9 +28275,9 @@ } }, "node_modules/terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", + "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -28300,9 +28293,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", @@ -28479,9 +28472,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", "license": "MIT", "engines": { "node": ">=18" @@ -28937,9 +28930,9 @@ } }, "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", "license": "MIT" }, "node_modules/uglify-js": { @@ -29941,9 +29934,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz", + "integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -30570,9 +30563,9 @@ } }, "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", + "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 6192d481b1..d25f57ca9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sap-architecture-center", - "version": "v3.2618.1", + "version": "v3.2618.2", "private": true, "scripts": { "clean": "rm -rf build", @@ -34,7 +34,8 @@ "minimatch": "^3.1.4", "langium": "^4.2.2", "webpack": "~5.105.0", - "webpackbar": "^7.0.0" + "webpackbar": "^7.0.0", + "uuid": "^14.0.0" }, "dependencies": { "@docusaurus/core": "^3.10.0", diff --git a/src/components/FilterBar/CollapsibleFilterBar.module.css b/src/components/FilterBar/CollapsibleFilterBar.module.css index 712ba59e4a..cb430ea74a 100644 --- a/src/components/FilterBar/CollapsibleFilterBar.module.css +++ b/src/components/FilterBar/CollapsibleFilterBar.module.css @@ -1,12 +1,11 @@ .filterBarContainer { width: calc(100% - 16px); - margin-left: 8px; - margin-right: 8px; background: var(--ifm-background-color); border: 1px solid var(--color-border-light); border-radius: var(--border-radius-sm); padding: 12px; - margin-bottom: 16px; + margin: 0 8px 16px 8px; + box-sizing: border-box; } /* Top Bar with Filter Toggle and Clear Button */ diff --git a/src/components/FilterBar/CollapsibleFilterBar.tsx b/src/components/FilterBar/CollapsibleFilterBar.tsx index 6526c6ebc5..4154a5dcee 100644 --- a/src/components/FilterBar/CollapsibleFilterBar.tsx +++ b/src/components/FilterBar/CollapsibleFilterBar.tsx @@ -9,11 +9,8 @@ interface Option { } interface CollapsibleFilterBarProps { - techDomains: Option[]; partners: Option[]; - selectedTechDomains: Option[]; selectedPartners: Option[]; - onTechDomainsChange: (values: Option[]) => void; onPartnersChange: (values: Option[]) => void; resetFilters: () => void; isResetEnabled: boolean; @@ -23,11 +20,8 @@ interface CollapsibleFilterBarProps { } const CollapsibleFilterBar: React.FC = ({ - techDomains, partners, - selectedTechDomains, selectedPartners, - onTechDomainsChange, onPartnersChange, resetFilters, isResetEnabled, @@ -50,7 +44,7 @@ const CollapsibleFilterBar: React.FC = ({ onChange(currentSelection.filter((item) => item.value !== option.value)); }; - const hasActiveFilters = selectedTechDomains.length > 0 || selectedPartners.length > 0 || searchTerm.length > 0; + const hasActiveFilters = selectedPartners.length > 0 || searchTerm.length > 0; return (
@@ -65,7 +59,7 @@ const CollapsibleFilterBar: React.FC = ({ Filters {hasActiveFilters && ( - {selectedTechDomains.length + selectedPartners.length} + {selectedPartners.length} )} @@ -81,16 +75,6 @@ const CollapsibleFilterBar: React.FC = ({ {hasActiveFilters && (
- {selectedTechDomains.map((domain) => ( - - ))} {selectedPartners.map((partner) => ( - ); - })} -
-
-

Technology Partners

diff --git a/src/constant/constants.ts b/src/constant/constants.ts index 4f90781515..83af350747 100644 --- a/src/constant/constants.ts +++ b/src/constant/constants.ts @@ -37,8 +37,8 @@ export const navigationCardsData = [ // Keep items sorted alphabetically by `title` export const techDomain = [ - { id: 'appdev', title: 'Application Dev. & Automation', icon: 'sap-icon://syntax' }, { id: 'ai', title: 'AI & Machine Learning', icon: 'sap-icon://da' }, + { id: 'appdev', title: 'Application Dev. & Automation', icon: 'sap-icon://syntax' }, { id: 'data', title: 'Data & Analytics', icon: 'sap-icon://database' }, { id: 'integration', title: 'Integration', icon: 'sap-icon://exit-full-screen' }, { id: 'opsec', title: 'Operation & Security', icon: 'sap-icon://shield' }, diff --git a/src/css/custom.css b/src/css/custom.css index 1c83ec294d..50b6546c7c 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -39,6 +39,7 @@ --ifm-footer-link-hover-color: #6b84a0; --ifm-footer-title-color: var(--ifm-font-color-base); --doc-sidebar-width: 350px !important; + --doc-sidebar-hidden-width: 30px; --ifm-table-cell-padding: 0.25rem 0.5rem; --ifm-menu-link-padding-vertical: 0.375rem; --ifm-menu-link-padding-horizontal: 0.75rem; @@ -363,10 +364,6 @@ svg[aria-roledescription="flowchart-v2"] span { /* Remove bullet points */ } -.menu__list-item>.menu__link--active { - background-color: var(--ifm-sidebar-selected-item-background-color); -} - .menu__link { /* Changed by PO: All pages should use the same colors */ color: #535353; @@ -376,6 +373,23 @@ svg[aria-roledescription="flowchart-v2"] span { line-height: 1rem; } +/* Align domain category arrows with nested level arrows */ +/* Target domain-level links by the --sublist-caret modifier */ +a.menu__link--sublist-caret { + padding-right: 0.1875rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +/* Keep domain category arrows fixed on hover - only shift the text */ +.menu__link--sublist-caret:hover { + transform: none !important; +} + +.menu__link--sublist-caret:hover [class*="categoryLinkLabel"] { + transform: translateX(4px); +} + /* Changing the padding size of the symbol to expand the content in the sidebar */ .menu__caret { padding: 0.2rem !important; @@ -1372,33 +1386,84 @@ html[data-theme='dark'] .theme-doc-toc-desktop { } /* Interactive Sidebar Links */ -.theme-doc-sidebar-menu .menu__link { +.theme-doc-sidebar-menu .menu__link, +nav[class*="domainSidebar"] .menu__link { transition: transform 0.3s ease, color 0.3s ease, text-shadow 0.3s ease; } -.theme-doc-sidebar-menu .menu__link:hover { +.theme-doc-sidebar-menu .menu__link:hover, +nav[class*="domainSidebar"] .menu__link:hover { transform: translateX(4px); color: var(--ifm-color-primary) !important; } +/* Change [+N] counter color on link hover - Light mode only */ +html[data-theme='light'] .theme-doc-sidebar-menu .menu__link:hover .sidebar-duplicate-counter, +html[data-theme='light'] nav[class*="domainSidebar"] .menu__link:hover .sidebar-duplicate-counter { + color: var(--ifm-color-primary) !important; +} + +/* Prevent [+N] counter color change on hover in dark mode */ +html[data-theme='dark'] .sidebar-duplicate-counter:hover { + color: var(--ifm-color-content-secondary) !important; +} + /* All active links get bold */ -.theme-doc-sidebar-menu .menu__link--active { +.theme-doc-sidebar-menu .menu__link--active, +nav[class*="domainSidebar"] .menu__link--active { font-weight: bold; } +/* Active document links (not folders) - hover animation combining both transforms */ +.menu__list-item:not(.menu__list-item-collapsible) > .menu__link--active:hover { + transform: translateX(4px) translateZ(0) !important; +} + /* Gradient effect on document links (all documents, not folders) */ -.theme-doc-sidebar-menu .menu__list-item:not(.menu__list-item-collapsible) > .menu__link--active { - background: var(--gradient-premium); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; +/* Generic selector that works for desktop, mobile, both themes */ +.menu__list-item:not(.menu__list-item-collapsible) > .menu__link--active { + color: var(--ifm-color-primary) !important; + background: var(--gradient-premium) !important; + background-color: transparent !important; + -webkit-background-clip: text !important; + background-clip: text !important; + -webkit-text-fill-color: transparent !important; + font-weight: bold !important; + /* Force hardware acceleration and separate layer to ensure gradient renders */ + transform: translateZ(0); + will-change: background; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; +} + +/* Dark theme - ensure gradient variable works */ +html[data-theme='dark'] .menu__list-item:not(.menu__list-item-collapsible) > .menu__link--active { + color: var(--ifm-color-primary) !important; + background: var(--gradient-premium) !important; + background-color: transparent !important; + -webkit-background-clip: text !important; + background-clip: text !important; + -webkit-text-fill-color: transparent !important; + font-weight: bold !important; + transform: translateZ(0); + will-change: background; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; +} + +/* Fallback for browsers that don't support background-clip: text */ +@supports not (background-clip: text) { + .menu__list-item:not(.menu__list-item-collapsible) > .menu__link--active { + color: var(--ifm-color-primary) !important; + -webkit-text-fill-color: var(--ifm-color-primary) !important; + background: none !important; + } } /* Folder nodes - default light theme */ .theme-doc-sidebar-menu .menu__list-item-collapsible > .menu__link, .menu__list-item-collapsible > .menu__link { color: #535353; - -webkit-text-fill-color: #535353; } /* Parent folder (expanded but NOT selected) - light theme */ @@ -1417,6 +1482,11 @@ html[data-theme='dark'] .theme-doc-toc-desktop { background-clip: text !important; -webkit-text-fill-color: transparent !important; font-weight: bold !important; + /* Force hardware acceleration and separate layer to ensure gradient renders */ + transform: translateZ(0); + will-change: background; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; } /* Folder nodes - default dark theme */ @@ -1442,12 +1512,38 @@ html[data-theme='dark'] .menu__list-item-collapsible--active > .menu__link.menu_ background-clip: text !important; -webkit-text-fill-color: transparent !important; font-weight: bold !important; + /* Force hardware acceleration and separate layer to ensure gradient renders */ + transform: translateZ(0); + will-change: background; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; } -html[data-theme='dark'] .theme-doc-sidebar-menu .menu__link:hover { +html[data-theme='dark'] .theme-doc-sidebar-menu .menu__link:hover, +html[data-theme='dark'] nav[class*="domainSidebar"] .menu__link:hover { + transform: translateX(4px); text-shadow: 0 0 8px rgba(0, 112, 242, 0.6); } +/* Disable slide animation and color change on mobile - light theme only */ +@media (max-width: 996px) { + .theme-doc-sidebar-menu .menu__link:hover, + nav[class*="domainSidebar"] .menu__link:hover, + html[data-theme='dark'] .theme-doc-sidebar-menu .menu__link:hover, + html[data-theme='dark'] nav[class*="domainSidebar"] .menu__link:hover, + .menu__link--sublist-caret:hover [class*="categoryLinkLabel"], + .menu__list-item:not(.menu__list-item-collapsible) > .menu__link--active:hover { + transform: none !important; + } + + /* Disable color change on mobile in light theme */ + html[data-theme='light'] .theme-doc-sidebar-menu .menu__link:hover, + html[data-theme='light'] nav[class*="domainSidebar"] .menu__link:hover, + html[data-theme='light'] .menu__link:hover .sidebar-duplicate-counter { + color: inherit !important; + } +} + /* Content Container Card */ .theme-doc-markdown { background: transparent; @@ -1603,6 +1699,7 @@ body:has(.homepage-main) footer.footer { } /* On mobile, allow normal scrolling */ + @media (max-width: 768px) { .homepage-main { scroll-snap-type: none !important; diff --git a/src/plugins/security-headers/index.js b/src/plugins/security-headers/index.js index a02736e68b..5bc9b59c1c 100644 --- a/src/plugins/security-headers/index.js +++ b/src/plugins/security-headers/index.js @@ -50,13 +50,6 @@ module.exports = function (_context, _options) { content: 'nosniff', }, }, - { - tagName: 'meta', - attributes: { - 'http-equiv': 'X-Frame-Options', - content: 'DENY', - }, - }, { tagName: 'meta', attributes: { diff --git a/src/plugins/tags-generator/index.js b/src/plugins/tags-generator/index.js index fde60742d2..210be0e5cc 100644 --- a/src/plugins/tags-generator/index.js +++ b/src/plugins/tags-generator/index.js @@ -18,9 +18,8 @@ module.exports = function (context, options) { const docIdToTags = {}; let sidebarContext = null; - let communitySidebarContext = null; - + const defaultDocsInstance = docsPluginContent['default']; if (defaultDocsInstance && defaultDocsInstance.loadedVersions) { @@ -33,30 +32,11 @@ module.exports = function (context, options) { }); }); - // Capture sidebar context from the loaded versions (current/default) + // Capture sidebar context from the loaded versions (includes all sidebars) if (defaultDocsInstance.loadedVersions.length > 0) { const currentVersion = defaultDocsInstance.loadedVersions[0]; if (currentVersion.sidebars) { sidebarContext = currentVersion.sidebars; - // Only log in development mode - if (process.env.NODE_ENV === 'development') { - console.log('✅ Tags plugin captured sidebar context at build time:', Object.keys(sidebarContext)); - } - } - } - } - - // Capture community sidebar context - const communityDocsInstance = docsPluginContent['community']; - if (communityDocsInstance && communityDocsInstance.loadedVersions) { - if (communityDocsInstance.loadedVersions.length > 0) { - const communityVersion = communityDocsInstance.loadedVersions[0]; - if (communityVersion.sidebars) { - communitySidebarContext = communityVersion.sidebars; - // Only log in development mode - if (process.env.NODE_ENV === 'development') { - console.log('✅ Tags plugin captured community sidebar context at build time:', Object.keys(communitySidebarContext)); - } } } } @@ -64,12 +44,7 @@ module.exports = function (context, options) { setGlobalData({ docIdToTags, sidebarContext, - communitySidebarContext, }); - // Only log in development mode - if (process.env.NODE_ENV === 'development') { - console.log('✅ Tags plugin loaded successfully and processed docs with sidebar context.'); - } }, }; }; diff --git a/src/sections/TechnologyDomainSection.tsx b/src/sections/TechnologyDomainSection.tsx index 299fcb0ff5..16b0168b58 100644 --- a/src/sections/TechnologyDomainSection.tsx +++ b/src/sections/TechnologyDomainSection.tsx @@ -23,22 +23,22 @@ interface DomainCardProps { title: string; icon: string; }; + onNavigationStart: () => void; } -function DomainCard({ domain }: DomainCardProps): JSX.Element { - const setTechDomains = useSidebarFilterStore((state) => state.setTechDomains); +function DomainCard({ domain, onNavigationStart }: DomainCardProps): JSX.Element { const docsUrl = useBaseUrl('/docs/ref-arch'); const isHighlighted = domain.id === 'ai' || domain.id === 'data'; - const handleClick = () => { - setTechDomains([domain.id]); + const handlePointerDown = () => { + onNavigationStart(); }; return (
{iconMap[domain.id] || } @@ -113,7 +113,30 @@ export default function TechnologyDomainSection(): JSX.Element { const imgBaseUrl = useBaseUrl('/img/landingPage/'); const history = useHistory(); const setPartners = useSidebarFilterStore((state) => state.setPartners); - const setTechDomains = useSidebarFilterStore((state) => state.setTechDomains); + const [isNavigating, setIsNavigating] = React.useState(false); + + // Disable scroll-snap when navigating to prevent interference + React.useEffect(() => { + if (isNavigating) { + const originalHtmlSnap = document.documentElement.style.scrollSnapType; + const originalBodySnap = document.body.style.scrollSnapType; + + document.documentElement.style.scrollSnapType = 'none'; + document.body.style.scrollSnapType = 'none'; + + const timer = setTimeout(() => { + document.documentElement.style.scrollSnapType = originalHtmlSnap; + document.body.style.scrollSnapType = originalBodySnap; + setIsNavigating(false); + }, 300); + + return () => { + clearTimeout(timer); + document.documentElement.style.scrollSnapType = originalHtmlSnap; + document.body.style.scrollSnapType = originalBodySnap; + }; + } + }, [isNavigating]); // Helper function to get image URL with baseUrl const getImg = (name: string) => `${imgBaseUrl}${name}`; @@ -124,16 +147,13 @@ export default function TechnologyDomainSection(): JSX.Element { e.preventDefault(); const partners = item.filter?.partners ?? []; - const techDomains = item.filter?.techDomains ?? []; - // Set the global store + // Set the global store - only partners filter now if (partners.length) setPartners(partners); - if (techDomains.length) setTechDomains(techDomains); - // Build query string + // Build query string - only partners const params = new URLSearchParams(); if (partners.length) params.set('partners', partners.join(',')); - if (techDomains.length) params.set('techDomains', techDomains.join(',')); history.push(`${docsUrl}?${params.toString()}`); }; @@ -160,7 +180,11 @@ export default function TechnologyDomainSection(): JSX.Element {
{techDomain.map((domain) => ( - + setIsNavigating(true)} + /> ))}
@@ -183,4 +207,4 @@ export default function TechnologyDomainSection(): JSX.Element {
); -} +} \ No newline at end of file diff --git a/src/store/sidebar-store.ts b/src/store/sidebar-store.ts index a4b8b6d8c3..2bdafb91b6 100644 --- a/src/store/sidebar-store.ts +++ b/src/store/sidebar-store.ts @@ -7,6 +7,10 @@ interface SidebarFilterState { partners: string[]; setPartners: (partners: string[]) => void; + // Expanded domain categories (for collapsible sidebar) + expandedDomains: string[]; + setExpandedDomains: (domains: string[]) => void; + resetFilters: () => void; } @@ -17,5 +21,9 @@ export const useSidebarFilterStore = create((set) => ({ partners: [], setPartners: (partners) => set({ partners }), - resetFilters: () => set({ techDomains: [], partners: [] }), + // Start with all domains collapsed by default + expandedDomains: [], + setExpandedDomains: (expandedDomains) => set({ expandedDomains }), + + resetFilters: () => set({ techDomains: [], partners: [], expandedDomains: [] }), })); diff --git a/src/theme/DocSidebar/index.tsx b/src/theme/DocSidebar/index.tsx index f2ea4b623f..0da0b5eabd 100644 --- a/src/theme/DocSidebar/index.tsx +++ b/src/theme/DocSidebar/index.tsx @@ -1,17 +1,28 @@ import React, { useMemo, useEffect, useState } from 'react'; +import clsx from 'clsx'; import DocSidebar from '@theme-original/DocSidebar'; import DocSidebarItems from '@theme-original/DocSidebarItems'; -import { NavbarSecondaryMenuFiller, useWindowSize } from '@docusaurus/theme-common'; +import { NavbarSecondaryMenuFiller, useWindowSize, useThemeConfig } from '@docusaurus/theme-common'; import { useDocsSidebar } from '@docusaurus/plugin-content-docs/client'; import CollapsibleFilterBar from '@site/src/components/FilterBar/CollapsibleFilterBar'; +import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton'; import styles from './styles.module.css'; import { useSidebarFilterStore } from '@site/src/store/sidebar-store'; import useGlobalData from '@docusaurus/useGlobalData'; import tagsMap from '@site/src/constant/tagsMapping.json'; -import { useHistory } from '@docusaurus/router'; +import { useHistory, useLocation } from '@docusaurus/router'; import useBaseUrl from '@docusaurus/useBaseUrl'; import { logger } from '@site/src/utils/logger'; +// Domain definitions with labels +const DOMAIN_DEFINITIONS = [ + { id: 'ai', label: 'AI & Machine Learning' }, + { id: 'appdev', label: 'Application Dev. & Automation' }, + { id: 'data', label: 'Data & Analytics' }, + { id: 'integration', label: 'Integration' }, + { id: 'opsec', label: 'Operation & Security' }, +]; + const categoryIdToTags = Object.entries(tagsMap).reduce((acc, [tagKey, meta]) => { const cat = meta?.categoryid; if (!cat) return acc; @@ -82,18 +93,291 @@ function filterSidebarItems(items, selectedDomains, selectedPartners, docIdToTag return recurse(items); } +// Helper to count occurrences of a docId in items (recursive) +function countDocsInItems(items, docId): number { + let count = 0; + for (const item of items) { + if ((item.type === 'doc' || item.type === 'link') && (item.docId === docId || item.id === docId)) { + count++; + } else if (item.type === 'category') { + // Check if the category itself matches (for parent architectures with href) + if (item.href === docId) { + count++; + } + // Recursively check children + if (item.items) { + count += countDocsInItems(item.items, docId); + } + } + } + return count; +} + +// Helper to count total docs in items (recursive) - for badge display +function countTotalDocsInItems(items): number { + let count = 0; + for (const item of items) { + if (item.type === 'doc' || item.type === 'link') { + count++; + } else if (item.type === 'category') { + // Count the category itself if it has a link (parent architecture that's also a document) + if (item.link || item.docId || item.href) { + count++; + } + // Also count children recursively + if (item.items) { + count += countTotalDocsInItems(item.items); + } + } + } + return count; +} + +// Group sidebar items by technology domain (preserving hierarchy) +function groupSidebarByDomain(items, docIdToTags) { + const domainIds = DOMAIN_DEFINITIONS.map((d) => d.id); + const grouped: Record = {}; + const duplicateCounts: Record = {}; + + // Initialize empty arrays for each domain + domainIds.forEach((id) => { grouped[id] = []; }); + + // Helper: Check if a doc/link belongs to a domain + const itemBelongsToDomain = (item, domainId) => { + const itemId = item.docId || item.id || ''; + const tags = docIdToTags?.[itemId] || []; + + // Direct match + if (tags.includes(domainId)) return true; + + // Check category mappings + const domainTags = categoryIdToTags[domainId] || []; + return domainTags.some((tag) => tags.includes(tag)); + }; + + // Helper: Recursively check if a category contains any docs for this domain + const categoryHasDocsForDomain = (category, domainId): boolean => { + if (!category.items || category.items.length === 0) return false; + + for (const child of category.items) { + if ((child.type === 'doc' || child.type === 'link') && itemBelongsToDomain(child, domainId)) { + return true; + } + if (child.type === 'category' && categoryHasDocsForDomain(child, domainId)) { + return true; + } + } + return false; + }; + + // First pass: Collect ALL document IDs (including parent architectures) from the entire sidebar + const collectAllDocIds = (itemList: any[]): Set => { + const docIds = new Set(); + + const traverse = (item: any) => { + if (item.type === 'doc' || item.type === 'link') { + const docId = item.docId || item.id; + if (docId) docIds.add(docId); + } else if (item.type === 'category') { + // If category has href and children, it's a parent architecture (expandable document) + if (item.href && item.items && item.items.length > 0) { + docIds.add(item.href); + } + // Recursively process children + if (item.items) { + item.items.forEach(traverse); + } + } + }; + + itemList.forEach(traverse); + return docIds; + }; + + // Collect all doc IDs first + const allDocIds = collectAllDocIds(items); + + // Helper: Recursively filter category items by domain + const filterCategoryForDomain = (category, domainId) => { + const filteredItems = []; + + for (const child of category.items || []) { + if ((child.type === 'doc' || child.type === 'link') && itemBelongsToDomain(child, domainId)) { + filteredItems.push(child); + } else if (child.type === 'category' && categoryHasDocsForDomain(child, domainId)) { + filteredItems.push(filterCategoryForDomain(child, domainId)); + } + } + + return { ...category, items: filteredItems }; + }; + + // Group items by domain, preserving category structure + items.forEach((item) => { + domainIds.forEach((domainId) => { + if ((item.type === 'doc' || item.type === 'link') && itemBelongsToDomain(item, domainId)) { + grouped[domainId].push(item); + } else if (item.type === 'category' && categoryHasDocsForDomain(item, domainId)) { + const filteredCategory = filterCategoryForDomain(item, domainId); + grouped[domainId].push(filteredCategory); + } + }); + }); + + // Calculate duplicate counts for all doc IDs + allDocIds.forEach((docId) => { + let count = 0; + domainIds.forEach((domainId) => { + const hasDoc = countDocsInItems(grouped[domainId], docId) > 0; + if (hasDoc) count++; + }); + if (count > 1) { + duplicateCounts[docId] = count - 1; + } + }); + + return { grouped, duplicateCounts }; +} + +// Filter grouped items by partner (preserving hierarchy) +function filterGroupedByPartner(grouped, selectedPartners, docIdToTags) { + if (!selectedPartners?.length) return grouped; + + const expand = (ids) => + Array.from(new Set(ids.flatMap((id) => [id, ...(categoryIdToTags[id] ?? [])]))); + const partnerTags = expand(selectedPartners); + + // Helper: Check if item matches partner filter + const itemMatchesPartner = (item) => { + const itemId = item.docId || item.id || ''; + const tags = docIdToTags?.[itemId] || []; + return partnerTags.some((p) => tags.includes(p)); + }; + + // Helper: Recursively filter category + const filterCategory = (category) => { + const filteredItems = []; + for (const child of category.items || []) { + if ((child.type === 'doc' || child.type === 'link') && itemMatchesPartner(child)) { + filteredItems.push(child); + } else if (child.type === 'category') { + const filteredChild = filterCategory(child); + if (filteredChild.items.length > 0) { + filteredItems.push(filteredChild); + } + } + } + return { ...category, items: filteredItems }; + }; + + const filtered: Record = {}; + Object.entries(grouped).forEach(([domainId, items]) => { + filtered[domainId] = []; + for (const item of items) { + if ((item.type === 'doc' || item.type === 'link') && itemMatchesPartner(item)) { + filtered[domainId].push(item); + } else if (item.type === 'category') { + const filteredCategory = filterCategory(item); + if (filteredCategory.items.length > 0) { + filtered[domainId].push(filteredCategory); + } + } + } + }); + + return filtered; +} + // ============================================================================ -// Desktop Version +// Shared Helper Functions // ============================================================================ -// Constant options defined outside component to avoid recreating on each render -const TECH_DOMAIN_OPTIONS = [ - { value: 'ai', label: 'AI & Machine Learning' }, - { value: 'appdev', label: 'Application Dev. & Automation' }, - { value: 'data', label: 'Data & Analytics' }, - { value: 'integration', label: 'Integration' }, - { value: 'opsec', label: 'Operation & Security' } -]; +// Collect unique doc IDs from grouped items (for result count display) +function collectUniqueDocIds(groupedItems: Record): Set { + const uniqueDocIds = new Set(); + + const traverse = (items: any[]) => { + items.forEach(item => { + if (item.type === 'doc' || item.type === 'link') { + const id = item.docId || item.id || ''; + if (id) uniqueDocIds.add(id); + } else if (item.type === 'category') { + // Count the category itself if it has href (parent architecture) + if (item.href) { + uniqueDocIds.add(item.href); + } + // Recursively count children + if (item.items) { + traverse(item.items); + } + } + }); + }; + + Object.values(groupedItems).forEach(items => traverse(items)); + return uniqueDocIds; +} + +// Add duplicate counters to item customProps recursively +function addDuplicateCountersToItems(items: any[], duplicateCounts: Record): any[] { + return items.map(item => { + if (item.type === 'category') { + // For parent architectures (categories with href), use href for matching + const categoryId = item.href || item.docId || item.id || ''; + const categoryDuplicateCount = duplicateCounts[categoryId]; + + return { + ...item, + customProps: { + ...item.customProps, + ...(categoryDuplicateCount && { duplicateCount: categoryDuplicateCount }) + }, + items: item.items ? addDuplicateCountersToItems(item.items, duplicateCounts) : [] + }; + } else if (item.type === 'doc' || item.type === 'link') { + const itemId = item.docId || item.id || ''; + const duplicateCount = duplicateCounts[itemId]; + + return { + ...item, + customProps: { + ...item.customProps, + ...(duplicateCount && { duplicateCount }) + } + }; + } + + return item; + }); +} + +// Transform domain-grouped data into Docusaurus category structure +function buildDomainCategories( + filteredGrouped: Record, + duplicateCounts: Record, + expandedDomains: string[] +) { + return DOMAIN_DEFINITIONS.map(domain => { + const domainItems = filteredGrouped[domain.id] || []; + const docCount = countTotalDocsInItems(domainItems); + const itemsWithCounters = addDuplicateCountersToItems(domainItems, duplicateCounts); + + return { + type: 'category', + label: `${domain.label} (${docCount})`, + items: itemsWithCounters, + collapsible: true, + collapsed: !expandedDomains.includes(domain.id), + customProps: { + domainId: domain.id + } + }; + }).filter(category => category.items.length > 0); +} + +// ============================================================================ +// Desktop Version +// ============================================================================ const PARTNER_OPTIONS = [ { value: 'aws', label: 'Amazon Web Services' }, { value: 'azure', label: 'Microsoft Azure' }, @@ -108,26 +392,34 @@ function DocSidebarDesktop(props) { const tagsDocId = useGlobalData()['docusaurus-tags-plugin'].default?.docIdToTags; const sidebar = useDocsSidebar(); const shouldShowFilters = sidebar?.name === 'refarchSidebar'; + const location = useLocation(); + const { + navbar: { hideOnScroll }, + docs: { + sidebar: { hideable }, + }, + } = useThemeConfig(); - const techDomains = useSidebarFilterStore((state) => state.techDomains); - const setTechDomains = useSidebarFilterStore((state) => state.setTechDomains); const partners = useSidebarFilterStore((state) => state.partners); const setPartners = useSidebarFilterStore((state) => state.setPartners); const resetFilters = useSidebarFilterStore((state) => state.resetFilters); + const expandedDomains = useSidebarFilterStore((state) => state.expandedDomains); const [searchTerm, setSearchTerm] = useState(''); - // All hooks must be called before any conditional returns - const filteredSidebar = useMemo( - () => filterSidebarItems(props.sidebar, techDomains, partners, tagsDocId), - [props.sidebar, techDomains, partners, tagsDocId] + // Group sidebar items by domain + const grouped = useMemo( + () => groupSidebarByDomain(props.sidebar, tagsDocId), + [props.sidebar, tagsDocId] ); - // Convert string arrays to Option arrays - const selectedTechDomainOptions = useMemo( - () => TECH_DOMAIN_OPTIONS.filter(opt => techDomains.includes(opt.value)), - [techDomains] + // Filter by selected partners + const filteredGrouped = useMemo( + () => filterGroupedByPartner(grouped.grouped, partners, tagsDocId), + [grouped.grouped, partners, tagsDocId] ); + + // Convert string arrays to Option arrays const selectedPartnerOptions = useMemo( () => PARTNER_OPTIONS.filter(opt => partners.includes(opt.value)), [partners] @@ -137,17 +429,6 @@ function DocSidebarDesktop(props) { return ; } - const handleTechDomainsChange = (selected) => { - const selectedKeys = selected.map(opt => opt.value); - setTechDomains(selectedKeys); - - // Sync URL - const params = new URLSearchParams(location.search); - if (selectedKeys.length) params.set('techDomains', selectedKeys.join(',')); - else params.delete('techDomains'); - window.history.replaceState({}, '', `${location.pathname}?${params.toString()}`); - }; - const handlePartnersChange = (selected) => { const selectedKeys = selected.map(opt => opt.value); setPartners(selectedKeys); @@ -156,6 +437,7 @@ function DocSidebarDesktop(props) { const params = new URLSearchParams(location.search); if (selectedKeys.length) params.set('partners', selectedKeys.join(',')); else params.delete('partners'); + params.delete('techDomains'); // Remove old techDomains param window.history.replaceState({}, '', `${location.pathname}?${params.toString()}`); }; @@ -165,43 +447,50 @@ function DocSidebarDesktop(props) { window.history.replaceState({}, '', location.pathname); }; - // Count total filtered docs - const countDocs = (items) => { - let count = 0; - items.forEach(item => { - if (item.type === 'doc' || item.type === 'link') { - count++; - } else if (item.type === 'category' && item.items) { - count += countDocs(item.items); - } - }); - return count; - }; + // Count unique documents (across all domains, no duplicates) + const resultCount = collectUniqueDocIds(filteredGrouped).size; - const resultCount = countDocs(filteredSidebar); - const newProps = { ...props, sidebar: filteredSidebar }; + // Transform domain-grouped data into Docusaurus category structure + const domainCategories = buildDomainCategories( + filteredGrouped, + grouped.duplicateCounts, + expandedDomains + ); return ( -
-
- 0 || partners.length > 0 || searchTerm.length > 0} - searchTerm={searchTerm} - onSearchChange={setSearchTerm} - resultCount={resultCount} +
+
+ 0 || searchTerm.length > 0} + searchTerm={searchTerm} + onSearchChange={setSearchTerm} + resultCount={resultCount} + /> +
+
+
+
-
- -
+ + {hideable && } +
+
); } @@ -210,22 +499,27 @@ function DocSidebarDesktop(props) { // ============================================================================ function FilteredMobileSidebarView({ sidebar, path, onItemClick }) { const tagsDocId = useGlobalData()['docusaurus-tags-plugin'].default?.docIdToTags; - const techDomains = useSidebarFilterStore((state) => state.techDomains); - const setTechDomains = useSidebarFilterStore((state) => state.setTechDomains); const partners = useSidebarFilterStore((state) => state.partners); const setPartners = useSidebarFilterStore((state) => state.setPartners); const resetFilters = useSidebarFilterStore((state) => state.resetFilters); + const expandedDomains = useSidebarFilterStore((state) => state.expandedDomains); const [searchTerm, setSearchTerm] = useState(''); // Convert string arrays to Option arrays - const selectedTechDomainOptions = TECH_DOMAIN_OPTIONS.filter(opt => techDomains.includes(opt.value)); const selectedPartnerOptions = PARTNER_OPTIONS.filter(opt => partners.includes(opt.value)); - const handleTechDomainsChange = (selected) => { - const selectedKeys = selected.map(opt => opt.value); - setTechDomains(selectedKeys); - }; + // Group sidebar items by domain + const grouped = useMemo( + () => groupSidebarByDomain(sidebar, tagsDocId), + [sidebar, tagsDocId] + ); + + // Filter by selected partners + const filteredGrouped = useMemo( + () => filterGroupedByPartner(grouped.grouped, partners, tagsDocId), + [grouped.grouped, partners, tagsDocId] + ); const handlePartnersChange = (selected) => { const selectedKeys = selected.map(opt => opt.value); @@ -238,42 +532,37 @@ function FilteredMobileSidebarView({ sidebar, path, onItemClick }) { window.history.replaceState({}, '', location.pathname); }; - const filteredSidebar = useMemo( - () => filterSidebarItems(sidebar, techDomains, partners, tagsDocId), - [sidebar, techDomains, partners, tagsDocId] - ); - - // Count total filtered docs - const countDocs = (items) => { - let count = 0; - items.forEach(item => { - if (item.type === 'doc' || item.type === 'link') { - count++; - } else if (item.type === 'category' && item.items) { - count += countDocs(item.items); - } - }); - return count; - }; + // Count unique documents + const resultCount = collectUniqueDocIds(filteredGrouped).size; - const resultCount = countDocs(filteredSidebar); + // Transform domain-grouped data into Docusaurus category structure + const domainCategories = buildDomainCategories( + filteredGrouped, + grouped.duplicateCounts, + expandedDomains + ); return ( <> 0 || partners.length > 0 || searchTerm.length > 0} + isResetEnabled={partners.length > 0 || searchTerm.length > 0} searchTerm={searchTerm} onSearchChange={setSearchTerm} resultCount={resultCount} /> - + ); } @@ -306,27 +595,74 @@ function DocSidebarMobile({ shouldShowFilters, ...props }) { const DocSidebarDesktopMemo = React.memo(DocSidebarDesktop); const DocSidebarMobileMemo = React.memo(DocSidebarMobile); +// Helper function to find a doc in sidebar by path +function findDocByPath(items, pathname) { + for (const item of items) { + if (item.type === 'doc' || item.type === 'link') { + if (item.href === pathname || pathname.startsWith(item.href)) { + return item.docId || item.id; + } + } else if (item.type === 'category' && item.items) { + const found = findDocByPath(item.items, pathname); + if (found) return found; + } + } + return null; +} + export default function DocSidebarWrapper(props) { const windowSize = useWindowSize(); const sidebarContext = useDocsSidebar(); const shouldShowFilters = sidebarContext?.name === 'refarchSidebar'; const setPartners = useSidebarFilterStore((state) => state.setPartners); - const setTechDomains = useSidebarFilterStore((state) => state.setTechDomains); + const setExpandedDomains = useSidebarFilterStore((state) => state.setExpandedDomains); const resetFilters = useSidebarFilterStore((state) => state.resetFilters); const history = useHistory(); const docsBase = useBaseUrl('/docs'); + const location = useLocation(); + const tagsDocId = useGlobalData()['docusaurus-tags-plugin']?.default?.docIdToTags; useEffect(() => { if (!location.pathname.startsWith(docsBase)) return; + if (!shouldShowFilters) return; // Only run for ref-arch sidebar + if (!tagsDocId) return; // Wait for tags data to load + if (!props.sidebar) return; // Wait for sidebar to load const params = new URLSearchParams(location.search); - const partnersParam = params.get('partners'); - const techDomainsParam = params.get('techDomains'); + const expandedParam = params.get('expanded'); if (partnersParam) setPartners(partnersParam.split(',')); - if (techDomainsParam) setTechDomains(techDomainsParam.split(',')); - }, [docsBase, setPartners, setTechDomains]); + + // If expanded param is set, use it (explicit choice from landing page) + if (expandedParam) { + setExpandedDomains(expandedParam.split(',')); + return; + } + + // Auto-expand domains for the current doc + // Find the doc ID by matching the current pathname to sidebar items + const docId = findDocByPath(props.sidebar, location.pathname); + + // If we're on a specific doc page + if (docId && tagsDocId[docId]) { + const docTags = tagsDocId[docId] || []; + const domainIds = DOMAIN_DEFINITIONS.map((d) => d.id); + + // Find which domains this doc belongs to + const matchingDomains = domainIds.filter((domainId) => { + // Direct match + if (docTags.includes(domainId)) return true; + // Check category mappings + const domainTags = categoryIdToTags[domainId] || []; + return domainTags.some((tag) => docTags.includes(tag)); + }); + + if (matchingDomains.length > 0) { + setExpandedDomains(matchingDomains); + } + } + }, [location.pathname, location.search, docsBase, setPartners, setExpandedDomains, shouldShowFilters, tagsDocId, props.sidebar]); useEffect(() => { diff --git a/src/theme/DocSidebar/styles.module.css b/src/theme/DocSidebar/styles.module.css index fd7662ea90..5d4b880403 100644 --- a/src/theme/DocSidebar/styles.module.css +++ b/src/theme/DocSidebar/styles.module.css @@ -1,7 +1,6 @@ -.refarchSidebarActive { - height: 100%; - display: flex; - flex-direction: column; +/* Make sidebarViewport take full width of its container */ +:global([class*="sidebarViewport"]) { + width: 100% !important; } .scrollableContent { @@ -9,31 +8,10 @@ flex-grow: 1; } -:global(html[data-sidebar-collapsed='true']) .scrollableContent { - display: none; -} - -:global(html[data-sidebar-collapsed='true']) .sidebarWithFiltersContainer > div:first-child { - display: none !important; -} - -:global(.theme-doc-sidebar-container.theme-doc-sidebar-container-hidden) .sidebarWithFiltersContainer > div:first-child { - display: none !important; -} - -:global([class*="docSidebarContainer"][class*="Hidden"]) .sidebarWithFiltersContainer > div:first-child { - display: none !important; -} - -.theme-doc-sidebar-container-collapsed .custom-sidebar-filters-container { - display: none; -} - -.sidebarMenuList .menu.thin-scrollbar { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; +/* Hide filter bar and sidebar content when collapsed */ +.sidebarHidden { + opacity: 0 !important; + visibility: hidden !important; } .sidebarWithFiltersContainer { @@ -51,6 +29,14 @@ flex-direction: column; } +.sidebar { + border-right: 1px solid var(--ifm-toc-border-color); + display: flex; + flex-direction: column; + height: 100%; + width: var(--doc-sidebar-width); +} + [class^="sidebar_"] { border-right: 1px solid var(--ifm-toc-border-color); } @@ -58,3 +44,58 @@ :global(button[class*="collapseSidebarButton"]) { border-right: none !important; } + +/* Ensure expand button icon is visible */ +:global([class*="expandButton"]) { + cursor: pointer; +} + +:global([class*="expandButtonIcon"]) { + width: 20px; + height: 20px; + display: inline-block; +} + +/* Domain Sidebar Categories */ +.domainSidebar { + display: flex; + flex-direction: column; + padding: 0.5rem; + overflow-y: auto; + flex: 1; +} + +/* Apply Docusaurus thin-scrollbar styling */ +.domainSidebar.thin-scrollbar::-webkit-scrollbar { + width: 7px; +} + +.domainSidebar.thin-scrollbar::-webkit-scrollbar-track { + background: transparent; + border-radius: 10px; +} + +.domainSidebar.thin-scrollbar::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 10px; +} + +.domainSidebar.thin-scrollbar::-webkit-scrollbar-thumb:hover { + background-color: rgba(0, 0, 0, 0.3); +} + +[data-theme='dark'] .domainSidebar.thin-scrollbar::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); +} + +[data-theme='dark'] .domainSidebar.thin-scrollbar::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.3); +} + +.domainSidebarMobile { + display: flex; + flex-direction: column; + padding: 0.5rem 0; +} + +/* Remove custom viewport styling - let Docusaurus handle it */ diff --git a/src/theme/DocSidebarItem/Category/index.tsx b/src/theme/DocSidebarItem/Category/index.tsx new file mode 100644 index 0000000000..590fa86801 --- /dev/null +++ b/src/theme/DocSidebarItem/Category/index.tsx @@ -0,0 +1,325 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + type ComponentProps, + type ReactNode, + useEffect, + useMemo, +} from 'react'; +import clsx from 'clsx'; +import { + ThemeClassNames, + useThemeConfig, + usePrevious, + Collapsible, + useCollapsible, +} from '@docusaurus/theme-common'; +import {isSamePath} from '@docusaurus/theme-common/internal'; +import { + isActiveSidebarItem, + findFirstSidebarItemLink, + useDocSidebarItemsExpandedState, + useVisibleSidebarItems, +} from '@docusaurus/plugin-content-docs/client'; +import Link from '@docusaurus/Link'; +import {translate} from '@docusaurus/Translate'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import DocSidebarItemLink from '@theme/DocSidebarItem/Link'; +import type {Props} from '@theme/DocSidebarItem/Category'; + +import type { + PropSidebarItemCategory, + PropSidebarItemLink, +} from '@docusaurus/plugin-content-docs'; +import DuplicateCounter from '@theme/DocSidebarItem/DuplicateCounter'; +import styles from './styles.module.css'; + +// If we navigate to a category and it becomes active, it should automatically +// expand itself +function useAutoExpandActiveCategory({ + isActive, + collapsed, + updateCollapsed, + activePath, +}: { + isActive: boolean; + collapsed: boolean; + updateCollapsed: (b: boolean) => void; + activePath: string; +}) { + const wasActive = usePrevious(isActive); + const previousActivePath = usePrevious(activePath); + useEffect(() => { + const justBecameActive = isActive && !wasActive; + const stillActiveButPathChanged = + isActive && wasActive && activePath !== previousActivePath; + if ((justBecameActive || stillActiveButPathChanged) && collapsed) { + updateCollapsed(false); + } + }, [ + isActive, + wasActive, + collapsed, + updateCollapsed, + activePath, + previousActivePath, + ]); +} + +/** + * When a collapsible category has no link, we still link it to its first child + * during SSR as a temporary fallback. This allows to be able to navigate inside + * the category even when JS fails to load, is delayed or simply disabled + * React hydration becomes an optional progressive enhancement + * see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188 + * see https://github.com/facebook/docusaurus/issues/3030 + */ +function useCategoryHrefWithSSRFallback( + item: Props['item'], +): string | undefined { + const isBrowser = useIsBrowser(); + return useMemo(() => { + if (item.href && !item.linkUnlisted) { + return item.href; + } + // In these cases, it's not necessary to render a fallback + // We skip the "findFirstCategoryLink" computation + if (isBrowser || !item.collapsible) { + return undefined; + } + return findFirstSidebarItemLink(item); + }, [item, isBrowser]); +} + +function CollapseButton({ + collapsed, + categoryLabel, + onClick, +}: { + collapsed: boolean; + categoryLabel: string; + onClick: ComponentProps<'button'>['onClick']; +}) { + return ( +