From eeae10fc26e14fcf9c3749ecc8b9d03c84e6237f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 8 Sep 2026 13:37:59 -0700 Subject: [PATCH 1/2] improvement(docs): simplify navigation and share editor previews --- apps/docs/app/[[...slug]]/page.tsx | 58 +- apps/docs/app/layout.tsx | 34 +- apps/docs/app/not-found.tsx | 4 +- .../docs-layout/docs-sidebar.test.tsx | 119 ++++ .../components/docs-layout/docs-sidebar.tsx | 128 ++++ .../components/docs-layout/page-header.tsx | 20 + .../docs-layout/sidebar-components.test.tsx | 221 ++++++ .../docs-layout/sidebar-components.tsx | 239 +++---- apps/docs/components/footer/footer.tsx | 10 +- apps/docs/components/navbar/navbar.tsx | 4 +- apps/docs/components/ui/block-info-card.tsx | 1 + apps/docs/components/ui/icon-mapping.ts | 132 +++- apps/docs/components/ui/response-section.tsx | 120 +--- apps/docs/components/ui/search-trigger.tsx | 2 +- .../components/ui/video-placeholder.test.tsx | 92 +++ apps/docs/components/ui/video-placeholder.tsx | 19 +- .../academy-video-workflows.ts | 121 +++- .../block-display-workflows.ts | 66 +- .../workflow-preview/block-icons.tsx | 119 +--- .../workflow-preview/block-inspector.tsx | 91 +-- .../workflow-preview/block-preview.tsx | 14 +- .../workflow-preview/docs-block-node.tsx | 60 +- .../workflow-preview/docs-block-tile.test.tsx | 42 ++ .../workflow-preview/docs-block-tile.tsx | 35 + .../workflow-preview/docs-container-node.tsx | 8 +- .../components/workflow-preview/examples.ts | 646 ++++++++++++++++-- .../fit-view-after-init.test.tsx | 84 +++ .../workflow-preview/fit-view-after-init.tsx | 26 +- .../workflow-preview/format-references.tsx | 25 - .../docs/components/workflow-preview/index.ts | 1 - .../workflow-preview/output-bundle.tsx | 39 +- .../preview-selection-context.ts | 7 + .../workflow-preview/workflow-data.test.ts | 47 +- .../workflow-preview/workflow-data.ts | 16 +- .../workflow-preview.test.tsx | 244 +++++++ .../workflow-preview/workflow-preview.tsx | 137 ++-- .../content/docs/academy/agents/block.mdx | 9 +- .../content/docs/academy/agents/intro.mdx | 13 +- .../content/docs/academy/agents/memory.mdx | 9 +- .../content/docs/academy/agents/skills.mdx | 13 +- .../docs/academy/agents/tool-calling.mdx | 13 +- .../content/docs/academy/chat/building.mdx | 15 +- apps/docs/content/docs/academy/chat/intro.mdx | 15 +- .../docs/content/docs/academy/files/intro.mdx | 11 +- .../content/docs/academy/files/object.mdx | 19 +- apps/docs/content/docs/academy/index.mdx | 10 +- .../academy/knowledge-bases/connectors.mdx | 9 +- .../docs/academy/knowledge-bases/intro.mdx | 9 +- .../content/docs/academy/tables/intro.mdx | 13 +- .../docs/academy/tables/operations.mdx | 3 +- .../docs/academy/tables/workflow-columns.mdx | 21 +- .../academy/use-cases/document-extraction.mdx | 44 +- .../academy/use-cases/monitoring-research.mdx | 45 +- .../use-cases/sales-data-enrichment.mdx | 47 +- .../academy/use-cases/slack-it-triage.mdx | 44 +- .../docs/academy/workflows/branching.mdx | 3 +- .../docs/academy/workflows/deployment.mdx | 11 +- .../content/docs/academy/workflows/intro.mdx | 5 +- .../content/docs/academy/workflows/logs.mdx | 7 +- .../content/docs/academy/workflows/loops.mdx | 19 +- .../docs/academy/workflows/subworkflows.mdx | 3 +- apps/docs/content/docs/agents/choosing.mdx | 20 +- .../docs/content/docs/agents/custom-tools.mdx | 2 - apps/docs/content/docs/agents/index.mdx | 15 +- apps/docs/content/docs/agents/mcp.mdx | 5 +- apps/docs/content/docs/agents/meta.json | 2 +- apps/docs/content/docs/agents/skills.mdx | 30 +- .../docs/api-reference/authentication.mdx | 2 +- .../content/docs/api-reference/python.mdx | 40 +- .../content/docs/api-reference/typescript.mdx | 272 +------- apps/docs/content/docs/chat/files.mdx | 3 - apps/docs/content/docs/chat/index.mdx | 7 +- apps/docs/content/docs/chat/knowledge.mdx | 6 +- apps/docs/content/docs/chat/mailer.mdx | 5 - apps/docs/content/docs/chat/meta.json | 2 +- apps/docs/content/docs/chat/research.mdx | 6 +- apps/docs/content/docs/chat/tables.mdx | 3 +- apps/docs/content/docs/chat/tasks.mdx | 18 +- apps/docs/content/docs/chat/workflows.mdx | 7 +- apps/docs/content/docs/cli/authentication.mdx | 12 +- apps/docs/content/docs/cli/commands.mdx | 8 +- apps/docs/content/docs/cli/configuration.mdx | 91 +-- apps/docs/content/docs/cli/index.mdx | 30 +- apps/docs/content/docs/cli/knowledge.mdx | 2 +- apps/docs/content/docs/cli/logs.mdx | 2 +- apps/docs/content/docs/cli/output.mdx | 9 +- apps/docs/content/docs/cli/reference.mdx | 4 +- apps/docs/content/docs/cli/scripting.mdx | 32 +- .../docs/content/docs/cli/troubleshooting.mdx | 14 +- .../content/docs/cli/workflow-mcp-servers.mdx | 2 +- apps/docs/content/docs/desktop/index.mdx | 2 +- apps/docs/content/docs/files/editor.mdx | 2 +- apps/docs/content/docs/files/generating.mdx | 11 - apps/docs/content/docs/files/index.mdx | 2 +- apps/docs/content/docs/files/meta.json | 2 +- .../docs/content/docs/files/passing-files.mdx | 11 +- .../content/docs/files/using-in-workflows.mdx | 11 - .../content/docs/getting-started/index.mdx | 12 +- apps/docs/content/docs/index.mdx | 12 +- apps/docs/content/docs/integrations/a2a.mdx | 6 - .../content/docs/integrations/affinity.mdx | 20 +- .../content/docs/integrations/agentmail.mdx | 22 +- .../content/docs/integrations/agentphone.mdx | 22 +- .../content/docs/integrations/agiloft.mdx | 55 +- .../docs/content/docs/integrations/ahrefs.mdx | 12 +- .../integrations/airtable-service-account.mdx | 13 +- .../content/docs/integrations/airtable.mdx | 11 +- .../content/docs/integrations/airweave.mdx | 13 +- .../content/docs/integrations/algolia.mdx | 13 +- .../content/docs/integrations/amplitude.mdx | 16 +- apps/docs/content/docs/integrations/apify.mdx | 21 +- .../docs/content/docs/integrations/apollo.mdx | 22 +- .../content/docs/integrations/appconfig.mdx | 13 +- apps/docs/content/docs/integrations/arxiv.mdx | 12 +- .../integrations/asana-service-account.mdx | 3 +- apps/docs/content/docs/integrations/asana.mdx | 12 +- apps/docs/content/docs/integrations/ashby.mdx | 20 +- .../docs/content/docs/integrations/athena.mdx | 23 +- .../atlassian-service-account.mdx | 25 +- .../integrations/attio-service-account.mdx | 9 +- apps/docs/content/docs/integrations/attio.mdx | 12 +- .../docs/integrations/azure_data_explorer.mdx | 28 +- .../docs/integrations/box-service-account.mdx | 5 +- apps/docs/content/docs/integrations/box.mdx | 21 +- .../content/docs/integrations/brandfetch.mdx | 11 +- .../content/docs/integrations/brightdata.mdx | 17 +- .../content/docs/integrations/browser_use.mdx | 12 +- .../integrations/calcom-service-account.mdx | 11 +- .../docs/content/docs/integrations/calcom.mdx | 13 +- .../content/docs/integrations/calendly.mdx | 44 +- .../content/docs/integrations/circleback.mdx | 11 +- apps/docs/content/docs/integrations/clay.mdx | 12 +- .../integrations/clickup-service-account.mdx | 9 +- .../content/docs/integrations/cloudflare.mdx | 17 +- .../docs/integrations/cloudformation.mdx | 14 +- .../content/docs/integrations/cloudtrail.mdx | 4 +- .../docs/integrations/codepipeline.mdx | 11 +- .../content/docs/integrations/confluence.mdx | 12 +- .../docs/content/docs/integrations/convex.mdx | 27 +- .../docs/content/docs/integrations/cursor.mdx | 15 +- .../content/docs/integrations/dagster.mdx | 38 +- .../content/docs/integrations/datadog.mdx | 12 +- .../content/docs/integrations/datagma.mdx | 2 +- .../content/docs/integrations/daytona.mdx | 20 +- .../content/docs/integrations/deployments.mdx | 8 +- .../content/docs/integrations/discord.mdx | 4 +- .../content/docs/integrations/docusign.mdx | 14 +- .../content/docs/integrations/dropcontact.mdx | 10 +- apps/docs/content/docs/integrations/dspy.mdx | 6 +- .../content/docs/integrations/duckduckgo.mdx | 12 +- .../content/docs/integrations/dynamodb.mdx | 22 +- .../docs/integrations/elasticsearch.mdx | 13 +- .../content/docs/integrations/elevenlabs.mdx | 12 +- .../content/docs/integrations/embeddings.mdx | 2 +- .../docs/content/docs/integrations/enrich.mdx | 6 +- apps/docs/content/docs/integrations/enrow.mdx | 9 +- apps/docs/content/docs/integrations/exa.mdx | 15 +- .../docs/content/docs/integrations/fathom.mdx | 9 +- .../content/docs/integrations/findymail.mdx | 2 +- .../content/docs/integrations/firecrawl.mdx | 33 +- apps/docs/content/docs/integrations/flint.mdx | 20 +- apps/docs/content/docs/integrations/gamma.mdx | 11 +- .../docs/content/docs/integrations/github.mdx | 16 +- apps/docs/content/docs/integrations/gmail.mdx | 16 +- .../integrations/google-service-account.mdx | 8 +- .../content/docs/integrations/google_ads.mdx | 4 +- .../docs/integrations/google_appsheet.mdx | 13 +- .../docs/integrations/google_bigquery.mdx | 11 +- .../docs/integrations/google_books.mdx | 9 +- .../docs/integrations/google_calendar.mdx | 15 +- .../docs/integrations/google_contacts.mdx | 10 +- .../content/docs/integrations/google_docs.mdx | 12 +- .../docs/integrations/google_drive.mdx | 13 +- .../docs/integrations/google_forms.mdx | 14 +- .../content/docs/integrations/google_maps.mdx | 17 +- .../content/docs/integrations/google_meet.mdx | 16 +- .../docs/integrations/google_pagespeed.mdx | 2 +- .../docs/integrations/google_search.mdx | 8 +- .../docs/integrations/google_slides.mdx | 15 +- .../docs/integrations/google_tasks.mdx | 13 +- .../docs/integrations/google_translate.mdx | 9 +- .../docs/integrations/google_vault.mdx | 13 +- .../content/docs/integrations/grafana.mdx | 79 +-- .../content/docs/integrations/greenhouse.mdx | 12 +- .../content/docs/integrations/greptile.mdx | 17 +- .../content/docs/integrations/harmonic.mdx | 4 +- apps/docs/content/docs/integrations/hex.mdx | 19 +- .../integrations/hubspot-service-account.mdx | 11 +- .../docs/integrations/hubspot-setup.mdx | 8 +- .../content/docs/integrations/hubspot.mdx | 13 +- .../content/docs/integrations/huggingface.mdx | 8 +- .../docs/content/docs/integrations/hunter.mdx | 12 +- apps/docs/content/docs/integrations/iam.mdx | 13 +- .../docs/integrations/identity_center.mdx | 15 +- apps/docs/content/docs/integrations/imap.mdx | 13 +- .../content/docs/integrations/incidentio.mdx | 19 +- apps/docs/content/docs/integrations/index.mdx | 8 +- .../content/docs/integrations/infisical.mdx | 22 +- .../content/docs/integrations/intercom.mdx | 14 +- apps/docs/content/docs/integrations/jina.mdx | 16 +- .../integrations/jira_service_management.mdx | 15 +- .../content/docs/integrations/jotform.mdx | 25 +- .../content/docs/integrations/jupyter.mdx | 32 +- .../docs/content/docs/integrations/kalshi.mdx | 13 +- apps/docs/content/docs/integrations/ketch.mdx | 11 +- .../content/docs/integrations/knowledge.mdx | 10 +- .../docs/content/docs/integrations/lambda.mdx | 4 +- .../content/docs/integrations/langsmith.mdx | 14 +- apps/docs/content/docs/integrations/latex.mdx | 12 +- .../docs/integrations/launchdarkly.mdx | 4 - .../content/docs/integrations/leadmagic.mdx | 18 +- .../integrations/linear-service-account.mdx | 15 +- .../content/docs/integrations/linkedin.mdx | 11 +- apps/docs/content/docs/integrations/logs.mdx | 10 +- apps/docs/content/docs/integrations/loops.mdx | 21 +- .../content/docs/integrations/mailchimp.mdx | 33 +- .../content/docs/integrations/mailgun.mdx | 15 +- apps/docs/content/docs/integrations/mem0.mdx | 13 +- .../docs/content/docs/integrations/memory.mdx | 11 +- apps/docs/content/docs/integrations/meta.json | 2 - .../docs/integrations/microsoft_ad.mdx | 4 - .../docs/integrations/microsoft_dataverse.mdx | 51 +- .../integrations/microsoft_dynamics_365.mdx | 16 +- .../docs/integrations/microsoft_excel.mdx | 15 +- .../docs/integrations/microsoft_planner.mdx | 11 +- .../docs/integrations/microsoft_word.mdx | 23 +- .../docs/integrations/millionverifier.mdx | 2 +- apps/docs/content/docs/integrations/modal.mdx | 2 +- .../integrations/monday-service-account.mdx | 12 +- apps/docs/content/docs/integrations/mssql.mdx | 11 +- apps/docs/content/docs/integrations/neo4j.mdx | 13 +- .../integrations/netsuite-service-account.mdx | 6 +- .../content/docs/integrations/netsuite.mdx | 8 +- .../integrations/notion-service-account.mdx | 11 +- .../docs/content/docs/integrations/notion.mdx | 12 +- .../content/docs/integrations/obsidian.mdx | 10 +- apps/docs/content/docs/integrations/okta.mdx | 4 - .../content/docs/integrations/onedrive.mdx | 15 +- .../content/docs/integrations/onepassword.mdx | 14 +- .../docs/content/docs/integrations/openai.mdx | 62 -- .../content/docs/integrations/outlook.mdx | 22 +- .../content/docs/integrations/pagerduty.mdx | 15 +- .../docs/integrations/peopledatalabs.mdx | 4 +- .../content/docs/integrations/perplexity.mdx | 14 +- .../content/docs/integrations/pinecone.mdx | 30 +- .../pipedrive-service-account.mdx | 3 +- .../content/docs/integrations/pipedrive.mdx | 14 +- .../content/docs/integrations/pitchbook.mdx | 20 +- .../content/docs/integrations/polymarket.mdx | 14 +- .../content/docs/integrations/postgresql.mdx | 11 +- .../content/docs/integrations/prospeo.mdx | 21 +- .../docs/content/docs/integrations/qdrant.mdx | 24 +- .../docs/content/docs/integrations/quartr.mdx | 12 +- .../content/docs/integrations/rabbitmq.mdx | 20 +- apps/docs/content/docs/integrations/rds.mdx | 20 +- .../docs/content/docs/integrations/reddit.mdx | 9 +- apps/docs/content/docs/integrations/redis.mdx | 13 +- .../content/docs/integrations/reducto.mdx | 14 +- .../content/docs/integrations/revenuecat.mdx | 13 +- .../content/docs/integrations/rippling.mdx | 25 +- .../docs/content/docs/integrations/rootly.mdx | 23 +- apps/docs/content/docs/integrations/s3.mdx | 16 +- .../content/docs/integrations/sailpoint.mdx | 4 +- .../salesforce-service-account.mdx | 17 +- .../content/docs/integrations/salesforce.mdx | 12 +- .../content/docs/integrations/sap_concur.mdx | 19 +- .../content/docs/integrations/sap_s4hana.mdx | 22 +- .../content/docs/integrations/semrush.mdx | 19 +- .../content/docs/integrations/sendblue.mdx | 2 - .../content/docs/integrations/sendgrid.mdx | 21 +- .../docs/content/docs/integrations/sentry.mdx | 24 +- .../docs/content/docs/integrations/serper.mdx | 12 +- .../content/docs/integrations/servicenow.mdx | 81 +-- apps/docs/content/docs/integrations/ses.mdx | 12 +- .../content/docs/integrations/sharepoint.mdx | 12 +- .../integrations/shopify-service-account.mdx | 16 +- .../content/docs/integrations/shopify.mdx | 11 +- .../content/docs/integrations/sixtyfour.mdx | 11 +- apps/docs/content/docs/integrations/slack.mdx | 21 +- apps/docs/content/docs/integrations/smtp.mdx | 10 +- .../snowflake-service-account.mdx | 10 +- .../content/docs/integrations/snowflake.mdx | 15 +- .../docs/content/docs/integrations/splunk.mdx | 24 +- apps/docs/content/docs/integrations/sqs.mdx | 38 +- apps/docs/content/docs/integrations/ssm.mdx | 13 +- .../content/docs/integrations/stagehand.mdx | 17 +- .../docs/content/docs/integrations/stripe.mdx | 16 +- apps/docs/content/docs/integrations/sts.mdx | 13 +- .../content/docs/integrations/supabase.mdx | 21 +- apps/docs/content/docs/integrations/table.mdx | 19 +- .../content/docs/integrations/tailscale.mdx | 15 +- .../docs/content/docs/integrations/tavily.mdx | 12 +- .../content/docs/integrations/telegram.mdx | 14 +- .../content/docs/integrations/temporal.mdx | 40 +- .../docs/content/docs/integrations/thrive.mdx | 12 +- .../docs/content/docs/integrations/tiktok.mdx | 12 +- .../content/docs/integrations/tinybird.mdx | 29 +- .../integrations/trello-service-account.mdx | 10 +- .../docs/content/docs/integrations/trello.mdx | 15 +- .../content/docs/integrations/trigger_dev.mdx | 13 +- .../docs/content/docs/integrations/twilio.mdx | 10 +- .../content/docs/integrations/twilio_sms.mdx | 12 +- .../docs/integrations/twilio_voice.mdx | 15 +- .../content/docs/integrations/typeform.mdx | 11 +- .../content/docs/integrations/upstash.mdx | 13 +- .../docs/content/docs/integrations/vercel.mdx | 11 +- .../wealthbox-service-account.mdx | 11 +- .../integrations/webflow-service-account.mdx | 12 +- .../content/docs/integrations/webflow.mdx | 13 +- .../content/docs/integrations/whatsapp.mdx | 11 +- .../content/docs/integrations/wikipedia.mdx | 11 +- apps/docs/content/docs/integrations/wiza.mdx | 31 +- .../content/docs/integrations/wordpress.mdx | 14 +- .../content/docs/integrations/workday.mdx | 20 +- apps/docs/content/docs/integrations/x.mdx | 14 +- .../content/docs/integrations/zendesk.mdx | 30 +- apps/docs/content/docs/integrations/zep.mdx | 15 +- .../content/docs/integrations/zerobounce.mdx | 2 +- .../zoho-desk-service-account.mdx | 9 +- .../integrations/zoom-service-account.mdx | 11 +- apps/docs/content/docs/integrations/zoom.mdx | 20 +- .../content/docs/integrations/zoominfo.mdx | 10 +- apps/docs/content/docs/introduction/index.mdx | 12 +- .../content/docs/keyboard-shortcuts/index.mdx | 4 +- .../knowledgebase/chunking-strategies.mdx | 17 +- .../content/docs/knowledgebase/connectors.mdx | 5 - .../knowledgebase/debugging-retrieval.mdx | 12 +- .../docs/content/docs/knowledgebase/index.mdx | 7 +- .../docs/content/docs/knowledgebase/meta.json | 1 - apps/docs/content/docs/knowledgebase/tags.mdx | 6 +- .../docs/knowledgebase/using-in-workflows.mdx | 14 +- .../content/docs/logs-debugging/alerts.mdx | 110 +-- .../content/docs/logs-debugging/index.mdx | 15 +- .../content/docs/logs-debugging/logging.mdx | 4 - .../content/docs/logs-debugging/meta.json | 2 +- apps/docs/content/docs/platform/costs.mdx | 79 +-- .../content/docs/platform/credentials.mdx | 8 +- .../platform/enterprise/access-control.mdx | 2 +- .../docs/platform/enterprise/audit-logs.mdx | 4 +- .../docs/platform/enterprise/data-drains.mdx | 6 +- .../platform/enterprise/data-retention.mdx | 2 +- .../docs/platform/enterprise/forks.mdx | 24 +- .../docs/platform/enterprise/index.mdx | 87 +-- .../docs/platform/enterprise/meta.json | 1 - .../docs/platform/enterprise/scim/meta.json | 2 +- .../platform/enterprise/usage-tracking.mdx | 4 +- .../content/docs/platform/organization.mdx | 14 +- .../content/docs/platform/permissions.mdx | 6 +- .../platform/self-hosting/architecture.mdx | 4 +- .../platform/self-hosting/background-jobs.mdx | 1 + .../docs/platform/self-hosting/docker.mdx | 4 +- .../self-hosting/environment-variables.mdx | 10 +- .../docs/platform/self-hosting/index.mdx | 6 +- .../self-hosting/integrations-oauth.mdx | 4 +- .../docs/platform/self-hosting/kubernetes.mdx | 2 +- .../docs/platform/self-hosting/meta.json | 1 - .../platform/self-hosting/object-storage.mdx | 7 +- .../platform/self-hosting/observability.mdx | 4 +- .../docs/platform/self-hosting/redis.mdx | 2 +- .../docs/platform/self-hosting/security.mdx | 30 +- .../docs/platform/self-hosting/upgrades.mdx | 4 +- .../docs/platform/self-hosting/verify.mdx | 4 +- .../docs/content/docs/platform/workspaces.mdx | 8 +- .../content/docs/quick-reference/index.mdx | 7 +- .../docs/search/connect-your-account.mdx | 10 +- apps/docs/content/docs/search/index.mdx | 2 + apps/docs/content/docs/search/meta.json | 1 - apps/docs/content/docs/tables/index.mdx | 7 +- apps/docs/content/docs/tables/meta.json | 2 +- .../docs/tables/using-in-workflows.mdx | 24 +- .../content/docs/tables/workflow-columns.mdx | 12 +- .../content/docs/workflows/blocks/agent.mdx | 8 +- .../content/docs/workflows/blocks/api.mdx | 4 - .../docs/workflows/blocks/condition.mdx | 3 - .../docs/workflows/blocks/credential.mdx | 12 - .../docs/workflows/blocks/evaluator.mdx | 8 +- .../docs/workflows/blocks/function.mdx | 18 +- .../docs/workflows/blocks/guardrails.mdx | 8 +- .../workflows/blocks/human-in-the-loop.mdx | 32 - .../content/docs/workflows/blocks/logs.mdx | 2 - .../content/docs/workflows/blocks/loop.mdx | 7 +- .../docs/workflows/blocks/parallel.mdx | 20 +- .../docs/content/docs/workflows/blocks/pi.mdx | 15 +- .../docs/workflows/blocks/response.mdx | 17 +- .../content/docs/workflows/blocks/router.mdx | 3 - .../docs/workflows/blocks/variables.mdx | 7 +- .../content/docs/workflows/blocks/wait.mdx | 10 - .../content/docs/workflows/blocks/webhook.mdx | 9 - .../docs/workflows/blocks/workflow.mdx | 14 +- .../content/docs/workflows/connections.mdx | 26 +- .../docs/content/docs/workflows/data-flow.mdx | 14 +- .../workflows/deployment/agent-events.mdx | 20 +- .../content/docs/workflows/deployment/api.mdx | 10 +- .../docs/workflows/deployment/chat.mdx | 10 +- .../docs/workflows/deployment/index.mdx | 47 +- .../content/docs/workflows/deployment/mcp.mdx | 28 +- .../docs/workflows/deployment/meta.json | 2 +- .../content/docs/workflows/how-it-runs.mdx | 10 - apps/docs/content/docs/workflows/index.mdx | 19 +- apps/docs/content/docs/workflows/meta.json | 1 - .../content/docs/workflows/triggers/rss.mdx | 2 - .../docs/workflows/triggers/schedule.mdx | 7 +- .../content/docs/workflows/triggers/sim.mdx | 8 - .../content/docs/workflows/triggers/start.mdx | 4 +- .../docs/workflows/triggers/webhook.mdx | 7 - .../docs/content/docs/workflows/variables.mdx | 11 +- apps/docs/content/integration-navigation.json | 104 +++ apps/docs/lib/academy-captions.test.ts | 42 ++ apps/docs/lib/integration-navigation.test.ts | 201 ++++++ apps/docs/lib/integration-navigation.ts | 90 +++ apps/docs/lib/redirects.ts | 7 + apps/docs/lib/shiki-theme.test.ts | 29 + apps/docs/lib/shiki-theme.ts | 18 +- apps/docs/lib/source.ts | 25 +- apps/docs/openapi-v2-knowledge.json | 2 +- apps/docs/openapi-v2-logs.json | 4 +- apps/docs/openapi-v2-tables.json | 27 +- apps/docs/package.json | 2 + .../captions/academy/agents-block.en.vtt | 119 ++++ .../captions/academy/agents-intro.en.vtt | 89 +++ .../captions/academy/agents-memory.en.vtt | 116 ++++ .../captions/academy/agents-skills.en.vtt | 85 +++ .../academy/agents-tool-calling.en.vtt | 130 ++++ .../captions/academy/chat-building.en.vtt | 81 +++ .../public/captions/academy/chat-intro.en.vtt | 67 ++ .../captions/academy/files-intro.en.vtt | 111 +++ .../captions/academy/files-object.en.vtt | 101 +++ .../captions/academy/kb-connectors.en.vtt | 111 +++ .../captions/academy/knowledge-bases.en.vtt | 120 ++++ .../captions/academy/tables-operations.en.vtt | 91 +++ .../academy/tables-workflow-columns.en.vtt | 182 +++++ .../public/captions/academy/tables.en.vtt | 92 +++ .../captions/academy/what-is-sim.en.vtt | 114 ++++ .../academy/workflows-branching.en.vtt | 81 +++ .../academy/workflows-deployments.en.vtt | 87 +++ .../captions/academy/workflows-intro.en.vtt | 117 ++++ .../captions/academy/workflows-logs.en.vtt | 74 ++ .../captions/academy/workflows-loops.en.vtt | 78 +++ .../academy/workflows-subworkflows.en.vtt | 69 ++ .../components/formatted-text/index.ts | 2 +- apps/sim/blocks/block-tile.tsx | 108 +-- apps/sim/blocks/blocks/github.ts | 2 +- apps/sim/blocks/blocks/sailpoint.ts | 2 +- apps/sim/blocks/blocks/sap_s4hana.ts | 2 +- apps/sim/lib/api/contracts/v2/knowledge.ts | 2 +- apps/sim/lib/api/contracts/v2/logs.ts | 2 +- .../lib/api/contracts/v2/openapi/tables.ts | 16 +- .../lib/copilot/generated/docs-manifest.ts | 1 - apps/sim/tools/apify/run_actor_async.ts | 2 +- apps/sim/tools/apify/run_actor_sync.ts | 2 +- apps/sim/tools/apify/run_task.ts | 2 +- apps/sim/tools/brightdata/scrape_dataset.ts | 2 +- apps/sim/tools/brightdata/sync_scrape.ts | 2 +- .../cloudflare/create_access_application.ts | 2 +- .../cloudflare/update_access_application.ts | 2 +- apps/sim/tools/generated/tool-metadata.ts | 2 +- .../sim/tools/grafana/update_contact_point.ts | 2 +- .../tools/jotform/update_form_properties.ts | 2 +- bun.lock | 6 + package.json | 4 + .../deployment-config/src/integrations.json | 8 +- packages/sim-cli/src/generated/v2-api.ts | 4 +- packages/workflow-renderer/package.json | 4 + .../src/block-tile-view.test.tsx | 44 ++ .../src/formatted-text.test.tsx | 106 +++ .../workflow-renderer/src}/formatted-text.tsx | 26 +- packages/workflow-renderer/src/index.ts | 2 + .../src/subflow/subflow-node-view.tsx | 1 - .../workflow-block/workflow-block-view.tsx | 105 ++- scripts/README.md | 32 +- scripts/generate-cli-docs.ts | 9 +- scripts/generate-docs.test.ts | 121 ++++ scripts/generate-docs.ts | 136 ++-- 473 files changed, 6791 insertions(+), 5248 deletions(-) create mode 100644 apps/docs/components/docs-layout/docs-sidebar.test.tsx create mode 100644 apps/docs/components/docs-layout/docs-sidebar.tsx create mode 100644 apps/docs/components/docs-layout/page-header.tsx create mode 100644 apps/docs/components/docs-layout/sidebar-components.test.tsx create mode 100644 apps/docs/components/ui/video-placeholder.test.tsx create mode 100644 apps/docs/components/workflow-preview/docs-block-tile.test.tsx create mode 100644 apps/docs/components/workflow-preview/docs-block-tile.tsx create mode 100644 apps/docs/components/workflow-preview/fit-view-after-init.test.tsx delete mode 100644 apps/docs/components/workflow-preview/format-references.tsx create mode 100644 apps/docs/components/workflow-preview/preview-selection-context.ts create mode 100644 apps/docs/components/workflow-preview/workflow-preview.test.tsx delete mode 100644 apps/docs/content/docs/integrations/openai.mdx create mode 100644 apps/docs/content/integration-navigation.json create mode 100644 apps/docs/lib/academy-captions.test.ts create mode 100644 apps/docs/lib/integration-navigation.test.ts create mode 100644 apps/docs/lib/integration-navigation.ts create mode 100644 apps/docs/lib/shiki-theme.test.ts create mode 100644 apps/docs/public/captions/academy/agents-block.en.vtt create mode 100644 apps/docs/public/captions/academy/agents-intro.en.vtt create mode 100644 apps/docs/public/captions/academy/agents-memory.en.vtt create mode 100644 apps/docs/public/captions/academy/agents-skills.en.vtt create mode 100644 apps/docs/public/captions/academy/agents-tool-calling.en.vtt create mode 100644 apps/docs/public/captions/academy/chat-building.en.vtt create mode 100644 apps/docs/public/captions/academy/chat-intro.en.vtt create mode 100644 apps/docs/public/captions/academy/files-intro.en.vtt create mode 100644 apps/docs/public/captions/academy/files-object.en.vtt create mode 100644 apps/docs/public/captions/academy/kb-connectors.en.vtt create mode 100644 apps/docs/public/captions/academy/knowledge-bases.en.vtt create mode 100644 apps/docs/public/captions/academy/tables-operations.en.vtt create mode 100644 apps/docs/public/captions/academy/tables-workflow-columns.en.vtt create mode 100644 apps/docs/public/captions/academy/tables.en.vtt create mode 100644 apps/docs/public/captions/academy/what-is-sim.en.vtt create mode 100644 apps/docs/public/captions/academy/workflows-branching.en.vtt create mode 100644 apps/docs/public/captions/academy/workflows-deployments.en.vtt create mode 100644 apps/docs/public/captions/academy/workflows-intro.en.vtt create mode 100644 apps/docs/public/captions/academy/workflows-logs.en.vtt create mode 100644 apps/docs/public/captions/academy/workflows-loops.en.vtt create mode 100644 apps/docs/public/captions/academy/workflows-subworkflows.en.vtt create mode 100644 packages/workflow-renderer/src/block-tile-view.test.tsx create mode 100644 packages/workflow-renderer/src/formatted-text.test.tsx rename {apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text => packages/workflow-renderer/src}/formatted-text.tsx (85%) diff --git a/apps/docs/app/[[...slug]]/page.tsx b/apps/docs/app/[[...slug]]/page.tsx index 02f782880e6..4d94308ae0e 100644 --- a/apps/docs/app/[[...slug]]/page.tsx +++ b/apps/docs/app/[[...slug]]/page.tsx @@ -1,13 +1,15 @@ import type React from 'react' +import { Children, cloneElement, isValidElement } from 'react' import { highlight } from 'fumadocs-core/highlight' import { findNeighbour } from 'fumadocs-core/page-tree' import type { ApiPageProps } from 'fumadocs-openapi/ui' import { createAPIPage } from 'fumadocs-openapi/ui' import { Pre } from 'fumadocs-ui/components/codeblock' import defaultMdxComponents from 'fumadocs-ui/mdx' -import { DocsBody, DocsPage, DocsTitle } from 'fumadocs-ui/page' +import { DocsBody, DocsPage } from 'fumadocs-ui/page' import { notFound } from 'next/navigation' import { PageFooter } from '@/components/docs-layout/page-footer' +import { PageHeader } from '@/components/docs-layout/page-header' import { PageNavigationArrows } from '@/components/docs-layout/page-navigation-arrows' import { LLMCopyButton } from '@/components/page-actions' import { StructuredData } from '@/components/structured-data' @@ -51,6 +53,24 @@ async function ApiCodeBlock({ lang, code }: { lang: string; code: string }) { ) } +interface ApiSlotElementProps extends React.HTMLAttributes { + items?: unknown[] +} + +/** Labels Fumadocs auth selectors while retaining their selection state and content. */ +function labelAuthSelectors(node: React.ReactNode): React.ReactNode { + if (!isValidElement(node)) return node + + const props = node.props + const children = + props.children === undefined ? undefined : Children.map(props.children, labelAuthSelectors) + + if (Array.isArray(props.items)) { + return cloneElement(node, { 'aria-label': 'Authentication method' }, children) + } + return children === undefined ? node : cloneElement(node, undefined, children) +} + const APIPage = createAPIPage(openapi, { renderCodeBlock: (props) => , playground: { enabled: false }, @@ -68,12 +88,14 @@ const APIPage = createAPIPage(openapi, { content: { renderOperationLayout: (slots) => { return ( -
+
{slots.header} {slots.description} {slots.apiPlayground} - {slots.authSchemes &&
{slots.authSchemes}
} + {slots.authSchemes && ( +
{labelAuthSelectors(slots.authSchemes)}
+ )} {slots.parameters} {slots.body &&
{slots.body}
} {slots.responses} @@ -179,6 +201,8 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }> breadcrumb={breadcrumbs} /> component: footer, }} > -
-
-
- -
- + +
+
- {data.title} -
+ + @@ -225,6 +246,8 @@ export default async function Page(props: { params: Promise<{ slug?: string[] }> breadcrumb={breadcrumbs} /> component: footer, }} > -
-
-
- -
- + +
+
- {data.title} -
+ + + + Skip to content + , + title: ( + <> + + Sim documentation home + + ), }} sidebar={{ tabs: false, @@ -74,14 +82,18 @@ export default function RootLayout({ children }: { children: ReactNode }) { footer: null, banner: null, prefetch: false, - components: { - Item: SidebarItem, - Folder: SidebarFolder, - Separator: SidebarSeparator, + }} + slots={{ + sidebar: { + provider: SidebarProvider, + root: DocsSidebar, + trigger: SidebarTrigger, + useSidebar, }, }} containerProps={{ - className: '!pt-0', + className: + '!pt-0 [--text-muted:var(--text-secondary)] [--color-fd-muted-foreground:var(--text-secondary)]', }} > {children} diff --git a/apps/docs/app/not-found.tsx b/apps/docs/app/not-found.tsx index d504a63db8b..a282607e005 100644 --- a/apps/docs/app/not-found.tsx +++ b/apps/docs/app/not-found.tsx @@ -7,13 +7,13 @@ export const metadata = { export default function NotFound() { return ( - +

Page not found

-

+

The page you're looking for doesn't exist or has been moved.

diff --git a/apps/docs/components/docs-layout/docs-sidebar.test.tsx b/apps/docs/components/docs-layout/docs-sidebar.test.tsx new file mode 100644 index 00000000000..a0a51057b11 --- /dev/null +++ b/apps/docs/components/docs-layout/docs-sidebar.test.tsx @@ -0,0 +1,119 @@ +/** @vitest-environment jsdom */ +import { act, type ComponentProps, createElement, type ReactNode } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { DocsSidebar } from '@/components/docs-layout/docs-sidebar' + +const state = vi.hoisted(() => ({ mobile: false, open: false, setOpen: vi.fn() })) +vi.mock('fumadocs-core/utils/use-media-query', () => ({ useMediaQuery: () => state.mobile })) +vi.mock('fumadocs-ui/components/sidebar/base', () => ({ useSidebar: () => state })) +vi.mock('next/navigation', () => ({ usePathname: () => '/integrations/zendesk' })) +vi.mock('fumadocs-ui/contexts/tree', () => ({ + useTreeContext: () => ({ + root: { + $id: 'docs', + children: [{ type: 'page', name: 'Zendesk', url: '/integrations/zendesk' }], + }, + }), +})) +vi.mock('@/components/docs-layout/sidebar-components', () => ({ + SidebarItem: ({ item }: { item: { name: string; url: string } }) => + createElement('a', { href: item.url, 'aria-current': 'page' }, item.name), + SidebarFolder: ({ children }: { children: ReactNode }) => children, + SidebarSeparator: () => null, +})) +vi.mock('@sim/emcn', () => ({ + cn: (...classes: string[]) => classes.join(' '), + Chip: ({ leftIcon: _icon, ...props }: ComponentProps<'button'> & { leftIcon?: unknown }) => + createElement('button', props), + ChipLink: ({ onNavigate, ...props }: ComponentProps<'a'> & { onNavigate?: () => void }) => + createElement('a', { + ...props, + onClick: (event) => { + event.preventDefault() + onNavigate?.() + }, + }), + useScrollEdges: () => ({ top: false, bottom: false }), + scrollFadeAttributes: () => ({}), + scrollFadeClass: 'scroll-fade', +})) +vi.mock('@sim/emcn/icons', () => ({ X: () => null })) + +let container: HTMLDivElement +let root: Root + +beforeEach(() => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) + state.mobile = false + state.open = false + state.setOpen.mockClear() + HTMLDialogElement.prototype.showModal = function () { + this.open = true + } + HTMLDialogElement.prototype.close = function () { + this.open = false + } + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) +}) + +afterEach(() => { + act(() => root.unmount()) + container.remove() + vi.restoreAllMocks() + vi.unstubAllGlobals() +}) + +function render() { + act(() => root.render()) +} + +describe('documentation sidebar surfaces', () => { + it('renders one desktop navigation tree', () => { + render() + expect(container.querySelectorAll('aside[aria-label="Documentation navigation"]')).toHaveLength( + 1 + ) + expect(container.querySelectorAll('a[aria-current="page"]')).toHaveLength(1) + expect(container.querySelector('dialog')).toBeNull() + }) + + it('opens the mobile modal and synchronizes native dismissal', () => { + state.mobile = true + state.open = true + render() + const dialog = container.querySelector('dialog')! + expect(dialog.open).toBe(true) + expect(container.querySelector('aside')).toBeNull() + expect(container.querySelectorAll('a[aria-current="page"]')).toHaveLength(1) + act(() => dialog.dispatchEvent(new Event('close'))) + expect(state.setOpen).toHaveBeenCalledWith(false) + }) + + it('clears mobile disclosure state when resizing to desktop', () => { + state.mobile = true + state.open = true + render() + state.mobile = false + render() + expect(container.querySelector('dialog')).toBeNull() + expect(state.setOpen).toHaveBeenCalledWith(false) + }) + + it('scrolls a deep selected row into view without scrolling the document', () => { + vi.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(function ( + this: Element + ) { + return DOMRect.fromRect( + this.hasAttribute('aria-current') + ? { x: 0, y: 800, width: 250, height: 30 } + : { x: 0, y: 0, width: 280, height: 600 } + ) + }) + render() + expect(container.querySelector('.scroll-fade')?.scrollTop).toBe(242) + expect(document.documentElement.scrollTop).toBe(0) + }) +}) diff --git a/apps/docs/components/docs-layout/docs-sidebar.tsx b/apps/docs/components/docs-layout/docs-sidebar.tsx new file mode 100644 index 00000000000..f2d68258adb --- /dev/null +++ b/apps/docs/components/docs-layout/docs-sidebar.tsx @@ -0,0 +1,128 @@ +'use client' + +import { useEffect, useRef } from 'react' +import { Chip, ChipLink, scrollFadeAttributes, scrollFadeClass, useScrollEdges } from '@sim/emcn' +import { X } from '@sim/emcn/icons' +import type { Node } from 'fumadocs-core/page-tree' +import { useMediaQuery } from 'fumadocs-core/utils/use-media-query' +import { useSidebar } from 'fumadocs-ui/components/sidebar/base' +import { useTreeContext } from 'fumadocs-ui/contexts/tree' +import { usePathname } from 'next/navigation' +import { + SidebarFolder, + SidebarItem, + SidebarSeparator, +} from '@/components/docs-layout/sidebar-components' +import { cn } from '@/lib/utils' + +interface SidebarTreeProps { + nodes: Node[] +} + +function SidebarTree({ nodes }: SidebarTreeProps) { + return nodes.map((node, index) => { + if (node.type === 'separator') return + if (node.type === 'folder') { + return ( + + + + ) + } + return + }) +} + +function SidebarScrollArea() { + const ref = useRef(null) + const edges = useScrollEdges(ref) + const { root } = useTreeContext() + const pathname = usePathname() + + useEffect(() => { + const viewport = ref.current + const current = viewport?.querySelector('[aria-current="page"]') + if (!viewport || !current) return + const bounds = viewport.getBoundingClientRect() + const row = current.getBoundingClientRect() + if (row.top < bounds.top) viewport.scrollTop += row.top - bounds.top - 12 + else if (row.bottom > bounds.bottom) viewport.scrollTop += row.bottom - bounds.bottom + 12 + }, [pathname]) + + return ( +
+
+ +
+
+ ) +} + +/** The native modal supplies focus containment, Escape dismissal, and focus restoration. */ +export function DocsSidebar() { + const dialogRef = useRef(null) + const { open, setOpen } = useSidebar() + const mobile = useMediaQuery('(width < 1024px)') + + useEffect(() => { + const dialog = dialogRef.current + if (!mobile && open) { + setOpen(false) + return + } + if (open && mobile) dialog?.showModal() + else dialog?.close() + }, [open, mobile, setOpen]) + + if (mobile) { + return ( + setOpen(false)} + onClick={(event) => { + if (event.target !== event.currentTarget) return + const bounds = event.currentTarget.getBoundingClientRect() + if (event.clientX < bounds.left || event.clientX > bounds.right) setOpen(false) + }} + className='fixed inset-y-0 right-0 left-auto m-0 h-dvh max-h-none w-[85%] max-w-[380px] border-[var(--border)] border-l bg-[var(--surface-1)] p-0 text-[var(--text-body)] backdrop:bg-black/30 backdrop:backdrop-blur-xs open:flex open:flex-col' + > +
+ Documentation + setOpen(false)} /> +
+ + {open && } +
+ ) + } + + return ( +
+ +
+ ) +} diff --git a/apps/docs/components/docs-layout/page-header.tsx b/apps/docs/components/docs-layout/page-header.tsx new file mode 100644 index 00000000000..9b3b3b20195 --- /dev/null +++ b/apps/docs/components/docs-layout/page-header.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' +import { cn } from '@sim/emcn' +import { DocsTitle } from 'fumadocs-ui/page' + +interface PageHeaderProps { + title: string + children: ReactNode + className?: string +} + +export function PageHeader({ title, children, className }: PageHeaderProps) { + return ( +
+ + {title} + +
{children}
+
+ ) +} diff --git a/apps/docs/components/docs-layout/sidebar-components.test.tsx b/apps/docs/components/docs-layout/sidebar-components.test.tsx new file mode 100644 index 00000000000..57e926197c1 --- /dev/null +++ b/apps/docs/components/docs-layout/sidebar-components.test.tsx @@ -0,0 +1,221 @@ +/** @vitest-environment jsdom */ +import { act, type ComponentProps, createElement, type ReactNode } from 'react' +import type { Folder } from 'fumadocs-core/page-tree' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { SidebarFolder, SidebarItem } from '@/components/docs-layout/sidebar-components' + +const navigation = vi.hoisted(() => ({ pathname: '/search', open: false, setOpen: vi.fn() })) +vi.mock('next/navigation', () => ({ usePathname: () => navigation.pathname })) +vi.mock('fumadocs-ui/components/sidebar/base', () => ({ + useSidebar: () => ({ prefetch: false, open: navigation.open, setOpen: navigation.setOpen }), +})) +vi.mock('@sim/emcn', () => ({ + cn: vi.fn(() => ''), + ChipLink: ({ + active: _active, + fullWidth: _fullWidth, + prefetch: _prefetch, + rightAdornment, + children, + onNavigate, + ...props + }: ComponentProps<'a'> & { + active?: boolean + fullWidth?: boolean + prefetch?: boolean + rightAdornment?: ReactNode + onNavigate?: (event: { preventDefault: () => void }) => void + }) => + createElement( + 'a', + { + ...props, + onClick: (event) => { + event.preventDefault() + if (!event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) + onNavigate?.({ preventDefault: vi.fn() }) + }, + }, + children, + rightAdornment + ), + Chip: ({ + fullWidth: _fullWidth, + rightAdornment, + children, + ...props + }: ComponentProps<'button'> & { + fullWidth?: boolean + rightAdornment?: ReactNode + }) => createElement('button', { type: 'button', ...props }, children, rightAdornment), +})) +vi.mock('@sim/emcn/icons', () => ({ + ChevronRight: (props: ComponentProps<'svg'>) => createElement('svg', props), +})) + +const airtable: Folder = { + type: 'folder', + name: 'Airtable', + index: { type: 'page', name: 'Airtable', url: '/integrations/airtable' }, + children: [ + { type: 'page', name: 'Personal Access Tokens', url: '/integrations/airtable-service-account' }, + ], +} +let container: HTMLDivElement +let root: Root +function renderFolder(item: Folder = airtable) { + act(() => + root.render( + + Guide + + ) + ) +} +function row() { + return container.querySelector('[aria-expanded]')! +} +function clickRow() { + act(() => row().click()) + return row() +} + +beforeEach(() => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) + navigation.pathname = '/search' + navigation.open = false + navigation.setOpen.mockClear() + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) +}) +afterEach(() => { + act(() => root.unmount()) + container.remove() + vi.unstubAllGlobals() +}) + +describe('sidebar navigation', () => { + it('uses one overview link and chevron with no separate button or duplicate children', () => { + renderFolder() + expect(row().getAttribute('href')).toBe('/integrations/airtable') + expect(row().querySelector('svg')).not.toBeNull() + expect(container.querySelector('button')).toBeNull() + expect(row().getAttribute('aria-expanded')).toBe('false') + const content = document.getElementById(row().getAttribute('aria-controls')!)! + expect(content.querySelectorAll('a')).toHaveLength(1) + expect(content.hasAttribute('inert')).toBe(true) + expect(content.getAttribute('aria-hidden')).toBe('true') + navigation.pathname = '/integrations/airtable' + renderFolder() + expect(content.hasAttribute('inert')).toBe(false) + expect(content.getAttribute('aria-hidden')).toBe('false') + }) + + it('opens canonical ancestors for a guide without nested URL segments', () => { + navigation.pathname = '/integrations/airtable-service-account' + renderFolder() + expect(row().getAttribute('aria-expanded')).toBe('true') + expect(row().hasAttribute('aria-current')).toBe(false) + renderFolder({ type: 'folder', name: 'Integrations', children: [airtable] }) + expect(row().getAttribute('aria-expanded')).toBe('true') + }) + + it('does not open an unrelated section based on a relocated guide URL prefix', () => { + navigation.pathname = '/workflows/blocks/logs' + renderFolder({ + type: 'folder', + name: 'Integrations', + children: [ + { + type: 'folder', + name: 'Logs', + index: { type: 'page', name: 'Logs', url: '/integrations/logs' }, + children: [{ type: 'page', name: 'Using Logs in Workflows', url: navigation.pathname }], + }, + ], + }) + expect(row().getAttribute('aria-expanded')).toBe('true') + renderFolder({ + type: 'folder', + name: 'Workflows', + index: { type: 'page', name: 'Workflows', url: '/workflows' }, + children: [{ type: 'page', name: 'Core Blocks', url: '/workflows/blocks' }], + }) + expect(row().getAttribute('aria-expanded')).toBe('false') + }) + + it('toggles the current overview from the same row, including its chevron', () => { + navigation.pathname = '/integrations/airtable' + renderFolder() + expect(row().getAttribute('aria-current')).toBe('page') + expect(clickRow().getAttribute('aria-expanded')).toBe('false') + act(() => + row() + .querySelector('svg')! + .dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })) + ) + expect(row().getAttribute('aria-expanded')).toBe('true') + }) + + it('preserves open-in-new-tab clicks without toggling the folder', () => { + navigation.pathname = '/integrations/airtable' + renderFolder() + act(() => + row().dispatchEvent( + new MouseEvent('click', { bubbles: true, cancelable: true, metaKey: true }) + ) + ) + expect(row().getAttribute('aria-expanded')).toBe('true') + }) + + it('clears manual expansion on navigation so back navigation opens the active section', () => { + navigation.pathname = '/integrations/airtable' + renderFolder() + expect(clickRow().getAttribute('aria-expanded')).toBe('false') + navigation.pathname = '/files' + renderFolder() + navigation.pathname = '/integrations/airtable' + renderFolder() + expect(row().getAttribute('aria-expanded')).toBe('true') + }) + + it('closes the mobile drawer on same-page navigation', () => { + navigation.open = true + navigation.pathname = '/integrations/airtable' + renderFolder() + clickRow() + expect(navigation.setOpen).toHaveBeenCalledWith(false) + expect(row().getAttribute('aria-expanded')).toBe('true') + }) + + it('renders an overview-only folder as a single link without disclosure semantics', () => { + navigation.pathname = '/integrations/airtable' + act(() => + root.render({null}) + ) + expect(container.querySelectorAll('a')).toHaveLength(1) + expect(container.querySelector('[aria-expanded],button')).toBeNull() + expect(container.querySelector('a')?.getAttribute('aria-current')).toBe('page') + }) + + it('keeps folders without an overview as native disclosure buttons', () => { + renderFolder({ type: 'folder', name: 'Shared credential guides', children: airtable.children }) + expect(row().tagName).toBe('BUTTON') + expect(clickRow().getAttribute('aria-expanded')).toBe('true') + }) + + it('marks only the exact current page and closes the drawer for leaf links', () => { + const item = airtable.children[0] + if (item.type !== 'page') throw new Error('Expected page fixture') + navigation.pathname = item.url + act(() => root.render(createElement(SidebarItem, { item }))) + expect(container.querySelector('a')?.getAttribute('aria-current')).toBe('page') + act(() => container.querySelector('a')!.click()) + expect(navigation.setOpen).toHaveBeenCalledWith(false) + navigation.pathname = `${item.url}/other` + act(() => root.render(createElement(SidebarItem, { item }))) + expect(container.querySelector('a')?.getAttribute('aria-current')).toBeNull() + }) +}) diff --git a/apps/docs/components/docs-layout/sidebar-components.tsx b/apps/docs/components/docs-layout/sidebar-components.tsx index 2e4112f69f4..34a516fbbf6 100644 --- a/apps/docs/components/docs-layout/sidebar-components.tsx +++ b/apps/docs/components/docs-layout/sidebar-components.tsx @@ -1,158 +1,119 @@ 'use client' -import { type ReactNode, useState } from 'react' -import { chipActiveSurfaceClass, chipHoverSurfaceClass } from '@sim/emcn' +import { type ReactNode, useId, useState } from 'react' +import { Chip, ChipLink } from '@sim/emcn' import { ChevronRight } from '@sim/emcn/icons' import type { Folder, Item, Separator } from 'fumadocs-core/page-tree' import { useSidebar } from 'fumadocs-ui/components/sidebar/base' -import Link from 'next/link' import { usePathname } from 'next/navigation' import { cn } from '@/lib/utils' -function SidebarChevron({ open, className }: { open: boolean; className?: string }) { - return ( - - ) +interface SidebarItemProps { + item: Item } -function isActive(url: string, pathname: string, nested = true): boolean { - return url === pathname || (nested && pathname.startsWith(`${url}/`)) +interface SidebarFolderProps { + item: Folder + children: ReactNode } -/** - * Rows mirror the app sidebar's chip pill: 30px tall, `rounded-lg`, `px-2`, 14px - * at normal weight, `--text-body` at rest AND when active — only the background - * moves, on the two-surface model — see emcn's `chipHoverSurfaceClass`. - * - * Height, horizontal padding, weight and color are additionally pinned in - * `global.css` (`html #nd-sidebar a…`), which needs `!important` to beat - * fumadocs' own sidebar rules and therefore also beats these utilities. Keep the - * two in step: the classes here describe the intent and drive the mobile layout, - * the stylesheet is what actually lands on desktop. - */ -const ITEM_BASE = - 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-[var(--text-body)] text-sm transition-colors' -const ITEM_ACTIVE_MOBILE = chipActiveSurfaceClass - -const ITEM_DESKTOP = - 'lg:mb-[0.0625rem] lg:block lg:rounded-lg lg:px-2 lg:font-normal lg:text-sm lg:leading-tight' -const ITEM_TEXT = 'lg:text-[var(--text-body)]' -/** - * Unprefixed, and applied only to inactive rows — an unconditional hover in - * `ITEM_BASE` would fade the current page under the pointer below `lg`. - */ -const ITEM_HOVER = chipHoverSurfaceClass -const ITEM_ACTIVE = 'lg:bg-[var(--surface-active)] lg:font-normal lg:text-[var(--text-body)]' - -const FOLDER_TEXT = 'lg:text-[var(--text-body)] lg:font-normal' -const FOLDER_HOVER = chipHoverSurfaceClass -const FOLDER_ACTIVE = 'lg:bg-[var(--surface-active)] lg:text-[var(--text-body)]' - -const itemClass = (active: boolean) => - cn(ITEM_BASE, ITEM_DESKTOP, ITEM_TEXT, active ? cn(ITEM_ACTIVE_MOBILE, ITEM_ACTIVE) : ITEM_HOVER) +interface SidebarSeparatorProps { + item: Separator +} -export function SidebarItem({ item }: { item: Item }) { +export function SidebarItem({ item }: SidebarItemProps) { const pathname = usePathname() - const { prefetch } = useSidebar() - const active = isActive(item.url, pathname, false) + const { prefetch, setOpen } = useSidebar() + const active = item.url === pathname return ( - + { + setOpen(false) + }} + > {item.name} - + ) } -export function SidebarFolder({ item, children }: { item: Folder; children: ReactNode }) { +/** A section is one link: navigate to its overview, then toggle it on subsequent clicks. */ +export function SidebarFolder({ item, children }: SidebarFolderProps) { + const contentId = useId() const pathname = usePathname() - const { prefetch } = useSidebar() - const hasActiveChild = checkHasActiveChild(item, pathname) - const hasChildren = item.children.length > 0 - const defaultOpen = hasActiveChild + const { prefetch, open: drawerOpen, setOpen } = useSidebar() const [manualOpen, setManualOpen] = useState<{ pathname: string; open: boolean } | null>(null) - const open = manualOpen?.pathname === pathname ? manualOpen.open : defaultOpen + if (manualOpen && manualOpen.pathname !== pathname) setManualOpen(null) + const hasChildren = item.children.length > 0 + const active = item.index?.url === pathname + const open = + manualOpen?.pathname === pathname ? manualOpen.open : hasActiveDescendant(item, pathname) const toggleOpen = () => setManualOpen({ pathname, open: !open }) - const active = item.index ? isActive(item.index.url, pathname, false) : false - - if (item.index && !hasChildren) { - return ( - - {item.name} - - ) - } + const chevron = hasChildren ? ( + + ) : undefined return ( -
-
- {item.index ? ( - <> - - {item.name} - - {hasChildren && ( - - )} - - ) : ( - - )} -
+
+ {item.index ? ( + { + if (drawerOpen) { + setManualOpen(null) + setOpen(false) + } else if (active && hasChildren) { + event.preventDefault() + toggleOpen() + } else { + setManualOpen(null) + } + }} + > + {item.name} + + ) : ( + + {item.name} + + )} {hasChildren && (
-
{children}
-
    {children}
+
{children}
)} @@ -160,33 +121,19 @@ export function SidebarFolder({ item, children }: { item: Folder; children: Reac ) } -/** - * Group label. Mirrors the app sidebar's section header: a 12px `--text-muted` - * row at normal weight in sentence case, with the group's 16px top gap carried - * by the label itself (the app's `SIDEBAR_SECTION_GAP_CLASS`). Groups are told - * apart by that gap alone — the app draws no rule between them. - */ -export function SidebarSeparator({ item }: { item: Separator }) { +export function SidebarSeparator({ item }: SidebarSeparatorProps) { return (
-

{item.name}

+

{item.name}

) } -function checkHasActiveChild(node: Folder, pathname: string): boolean { - if (node.index && isActive(node.index.url, pathname)) { - return true - } - - for (const child of node.children) { - if (child.type === 'page' && isActive(child.url, pathname)) { - return true - } - if (child.type === 'folder' && checkHasActiveChild(child, pathname)) { - return true - } - } - - return false +function hasActiveDescendant(node: Folder, pathname: string): boolean { + if (node.index?.url === pathname) return true + return node.children.some((child) => + child.type === 'page' + ? child.url === pathname + : child.type === 'folder' && hasActiveDescendant(child, pathname) + ) } diff --git a/apps/docs/components/footer/footer.tsx b/apps/docs/components/footer/footer.tsx index 4009cc1de72..342f0b733ab 100644 --- a/apps/docs/components/footer/footer.tsx +++ b/apps/docs/components/footer/footer.tsx @@ -12,7 +12,7 @@ import { SIM_SITE_URL } from '@/lib/urls' */ const LINK_CLASS = - 'text-sm text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]' + 'text-sm text-[var(--text-secondary)] transition-colors hover:text-[var(--text-primary)]' interface FooterItem { label: string @@ -23,7 +23,7 @@ interface FooterItem { const PRODUCT_LINKS: FooterItem[] = [ { label: 'Enterprise', href: `${SIM_SITE_URL}/enterprise`, external: true }, { label: 'Chat', href: '/chat' }, - { label: 'Workflows', href: '/introduction' }, + { label: 'Workflows', href: '/workflows' }, { label: 'Knowledge Base', href: '/knowledgebase' }, { label: 'Tables', href: '/tables' }, { label: 'MCP', href: '/agents/mcp' }, @@ -107,7 +107,7 @@ const LEGAL_LINKS: FooterItem[] = [ function FooterColumn({ title, items }: { title: string; items: FooterItem[] }) { return (
-

{title}

+

{title}

{items.map(({ label, href, external }) => external ? ( @@ -169,7 +169,9 @@ export function Footer() { -

© 2026 Sim. All rights reserved.

+

+ © 2026 Sim. All rights reserved. +

) diff --git a/apps/docs/components/navbar/navbar.tsx b/apps/docs/components/navbar/navbar.tsx index fe62a1df658..f8b0997cc01 100644 --- a/apps/docs/components/navbar/navbar.tsx +++ b/apps/docs/components/navbar/navbar.tsx @@ -71,7 +71,7 @@ export function Navbar() { paddingRight: 'calc(var(--toc-offset) + var(--nav-inset))', }} > - + @@ -106,7 +106,7 @@ export function Navbar() { '-mb-px relative flex items-center border-b text-sm tracking-[-0.01em] transition-colors', isActive ? 'border-[var(--text-muted)] font-medium text-[var(--text-primary)]' - : 'border-transparent font-normal text-[var(--text-muted)] hover:border-[var(--border-1)] hover:text-[var(--text-secondary)]' + : 'border-transparent font-normal text-[var(--text-secondary)] hover:border-[var(--border-1)] hover:text-[var(--text-primary)]' )} > {/* Invisible bold text reserves width to prevent layout shift */} diff --git a/apps/docs/components/ui/block-info-card.tsx b/apps/docs/components/ui/block-info-card.tsx index 39f3c25fbd2..d20380c3d93 100644 --- a/apps/docs/components/ui/block-info-card.tsx +++ b/apps/docs/components/ui/block-info-card.tsx @@ -46,6 +46,7 @@ export function BlockInfoCard({ return (