diff --git a/content/code-security/concepts/code-scanning/copilot-autofix-for-code-scanning.md b/content/code-security/concepts/code-scanning/copilot-autofix-for-code-scanning.md
index 87ed9e599475..0fcabd9d1166 100644
--- a/content/code-security/concepts/code-scanning/copilot-autofix-for-code-scanning.md
+++ b/content/code-security/concepts/code-scanning/copilot-autofix-for-code-scanning.md
@@ -15,7 +15,7 @@ category:
## How {% data variables.copilot.copilot_autofix_short %} works
-{% data variables.copilot.copilot_autofix_short %} translates the description and location of an alert into code changes that may fix the alert. It interfaces with the large language model {% data variables.copilot.copilot_gpt_53_codex %} from OpenAI, which has sufficient generative capabilities to produce both suggested fixes in code and explanatory text for those fixes.
+{% data variables.copilot.copilot_autofix_short %} translates the description and location of an alert into code changes that may fix the alert. It interfaces with the large language model {% data variables.copilot.copilot_gpt_51 %} from OpenAI, which has sufficient generative capabilities to produce both suggested fixes in code and explanatory text for those fixes.
## Enabling and managing {% data variables.copilot.copilot_autofix_short %}
diff --git a/content/code-security/responsible-use/responsible-use-autofix-code-scanning.md b/content/code-security/responsible-use/responsible-use-autofix-code-scanning.md
index e2bf3a9d38d1..9e395e7c4c10 100644
--- a/content/code-security/responsible-use/responsible-use-autofix-code-scanning.md
+++ b/content/code-security/responsible-use/responsible-use-autofix-code-scanning.md
@@ -20,7 +20,7 @@ category:
{% data reusables.rai.code-scanning.copilot-autofix-note %}
-{% data variables.copilot.copilot_autofix_short %} generates potential fixes that are relevant to the existing source code and translates the description and location of an alert into code changes that may fix the alert. {% data variables.copilot.copilot_autofix_short %} uses internal {% data variables.product.prodname_copilot %} APIs interfacing with the large language model {% data variables.copilot.copilot_gpt_53_codex %} from OpenAI, which has sufficient generative capabilities to produce both suggested fixes in code and explanatory text for those fixes.
+{% data variables.copilot.copilot_autofix_short %} generates potential fixes that are relevant to the existing source code and translates the description and location of an alert into code changes that may fix the alert. {% data variables.copilot.copilot_autofix_short %} uses internal {% data variables.product.prodname_copilot %} APIs interfacing with the large language model {% data variables.copilot.copilot_gpt_51 %} from OpenAI, which has sufficient generative capabilities to produce both suggested fixes in code and explanatory text for those fixes.
{% data variables.copilot.copilot_autofix_short %} is allowed by default and enabled for every repository using {% data variables.product.prodname_codeql %}, but you can choose to opt out and disable {% data variables.copilot.copilot_autofix_short %}. To learn how to disable {% data variables.copilot.copilot_autofix_short %} at the enterprise, organization and repository levels, see [AUTOTITLE](/code-security/code-scanning/managing-code-scanning-alerts/disabling-autofix-for-code-scanning).
diff --git a/content/copilot/concepts/agents/enterprise-management.md b/content/copilot/concepts/agents/enterprise-management.md
index 93359745e970..2e84292d0d8d 100644
--- a/content/copilot/concepts/agents/enterprise-management.md
+++ b/content/copilot/concepts/agents/enterprise-management.md
@@ -17,17 +17,9 @@ The AI Controls view provides a centralized platform where you can manage and mo
* View and filter a list of agent sessions in your enterprise over the last 24 hours
* Find a detailed record of agentic audit log events
-## {% data variables.copilot.copilot_cloud_agent %}
-
-{% data reusables.organizations.copilot-policy-ent-overrides-org %}
-
-Enterprise owners and AI managers can control how {% data variables.copilot.copilot_cloud_agent %} is adopted across the enterprise by choosing one of four policy states. This allows you to pilot adoption progressively and manage risk.
-
-If you choose the **Enabled for selected organizations** policy, you can select organizations individually or based on organization custom properties. This lets you define dynamic groups of organizations that align with your existing organizational structure—for example, by region, compliance tier, or department. You can manage this policy setting using the REST API endpoints or directly in the AI Controls page. See [REST API endpoints for Copilot coding agent management](/rest/copilot/copilot-coding-agent-management#copilot-coding-agent-policy-states). Please note that using custom properties to enable CCA is evaluated once at the time of configuration. Organizations will not be automatically enabled or disabled for CCA if the custom property is added, removed, or modified later.
-
## {% data variables.copilot.copilot_custom_agents %}
-{% data variables.copilot.copilot_custom_agents %} are specialized versions of {% data variables.copilot.copilot_cloud_agent %} that you can configure with tailored prompts, tools, and context, making them excel at specific tasks. {% data variables.copilot.custom_agents_caps_short %} can be defined and managed at the enterprise level for greater control and compliance, or at the organization and repository levels to allow teams the flexibility to build for their specific needs.
+{% data variables.copilot.copilot_custom_agents %} are specialized versions of {% data variables.copilot.copilot_cloud_agent %} that you can configure with tailored prompts, tools, and context, making them excel at specific tasks. {% data variables.copilot.custom_agents_caps_short %} can be defined and managed at the enterprise level for greater control and compliance, or at the organization and repository levels to allow teams the flexibility to build for their specific needs.
You can manage your enterprise-level {% data variables.copilot.custom_agents_short %}:
* From the AI Controls view
diff --git a/content/copilot/concepts/policies.md b/content/copilot/concepts/policies.md
index 12ea03cfd54b..f2c930b5dd39 100644
--- a/content/copilot/concepts/policies.md
+++ b/content/copilot/concepts/policies.md
@@ -42,10 +42,6 @@ Enterprise owners can choose to set policies for {% data variables.product.prodn
If a policy is defined at the enterprise level, the policy applies to all users and control of the policy is disabled at the organization level.
-### Granular organization selection
-
-**For the {% data variables.copilot.copilot_cloud_agent %} policy**, enterprise owners can choose to enable the feature for specific organizations rather than applying a blanket enterprise-wide setting. When **Enabled for selected organizations** is selected by an enterprise owner or an AI manager, only the selected organizations can enable the feature. Organizations can be selected individually or by using organization custom properties.
-
### No policy
If an enterprise owner selects **No policy**, the impact depends on whether a user has access to {% data variables.product.prodname_copilot_short %} through an organization or directly from the enterprise.
diff --git a/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/manage-copilot-cloud-agent.md b/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/manage-copilot-cloud-agent.md
index d99aa44d4d83..ea8a382464d3 100644
--- a/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/manage-copilot-cloud-agent.md
+++ b/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/manage-copilot-cloud-agent.md
@@ -2,7 +2,7 @@
title: Managing GitHub Copilot cloud agent in your enterprise
intro: 'Enable members of your enterprise to use {% data variables.copilot.copilot_cloud_agent %} and control the repositories where it is available.'
allowTitleToDifferFromFilename: true
-permissions: Enterprise owners and AI managers
+permissions: Enterprise owners
product: '{% data reusables.gated-features.copilot-cloud-agent %}
Contact Sales {% octicon "link-external" height:16 %}'
versions:
feature: copilot
@@ -34,20 +34,16 @@ category:
{% data variables.copilot.copilot_cloud_agent %} and use of third-party MCP servers are blocked by default for users to whom you have assigned a {% data variables.product.prodname_copilot_short %} license. You can allow members to use these features from the AI Controls tab for your enterprise. See [AUTOTITLE](/copilot/managing-copilot/managing-copilot-for-your-enterprise/managing-policies-and-features-for-copilot-in-your-enterprise#configuring-policies-for-github-copilot).
-* On the "Agents" page, click **{% data variables.copilot.copilot_cloud_agent %}**, then select **Enabled everywhere**, **Let organizations decide**, or **Enable for selected organizations**.
+* On the "Agents" page, click **{% data variables.copilot.copilot_cloud_agent %}**, then select **Enabled everywhere** or **Let organizations decide**.
* On the "MCP" page, for the "MCP servers in {% data variables.product.prodname_copilot_short %}" policy, select **Enabled everywhere** or **Let organizations decide**.
### Next steps
* If you selected **Enabled everywhere**, tell organization owners that {% data variables.copilot.copilot_cloud_agent %} is enabled for all members. By default, the agent will be available in all repositories, but it is possible to opt out some or all repositories.
* If you selected **Let organizations decide**, discuss member enablement with organization owners.
-* If you selected **Enable for selected organizations** ({% data variables.copilot.copilot_cloud_agent %} only), tell the owners of the selected organizations that {% data variables.copilot.copilot_cloud_agent %} is enabled for their members. Organizations that aren't selected will not have access.
For more information, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-organization/add-copilot-cloud-agent).
-> [!NOTE]
-> When the {% data variables.copilot.copilot_cloud_agent %} policy is set to **Enabled for selected organizations**, you can select individual organizations in the UI. To select organizations based on custom properties instead, use the REST API. See [AUTOTITLE](/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/custom-properties) and [AUTOTITLE](/rest/copilot/copilot-coding-agent-management#selecting-organizations-with-custom-properties).
-
## Disabling {% data variables.copilot.copilot_cloud_agent %} in your repositories
{% data variables.product.prodname_copilot_short %} policies, like the "{% data variables.copilot.copilot_cloud_agent %}" and "MCP servers on {% data variables.product.prodname_dotcom_the_website %}" policies described above, affect only the users you assign a {% data variables.product.prodname_copilot_short %} license to.
diff --git a/content/copilot/tutorials/index.md b/content/copilot/tutorials/index.md
index c5b83415315d..bc91db4f59cb 100644
--- a/content/copilot/tutorials/index.md
+++ b/content/copilot/tutorials/index.md
@@ -1,7 +1,7 @@
---
-title: Tutorials for {% data variables.product.prodname_copilot %}
+title: 'Tutorials for {% data variables.product.prodname_copilot %}'
shortTitle: Tutorials
-intro: Build skills and knowledge about {% data variables.product.prodname_copilot %} through examples and hands-on activities.
+intro: 'Build skills and knowledge about {% data variables.product.prodname_copilot %} through examples and hands-on activities.'
versions:
feature: copilot
children:
@@ -9,7 +9,7 @@ children:
- /customization-library
- /cloud-agent
- /spark
- - /customize-code-review
+ - /use-custom-instructions
- /enhance-agent-mode-with-mcp
- /compare-ai-models
- /speed-up-development-work
@@ -54,4 +54,3 @@ includedCategories:
- Modernize applications
- Roll Copilot out at scale
---
-
diff --git a/content/copilot/tutorials/customize-code-review.md b/content/copilot/tutorials/use-custom-instructions.md
similarity index 98%
rename from content/copilot/tutorials/customize-code-review.md
rename to content/copilot/tutorials/use-custom-instructions.md
index a1cf10f84fcf..60c4f7ae31e1 100644
--- a/content/copilot/tutorials/customize-code-review.md
+++ b/content/copilot/tutorials/use-custom-instructions.md
@@ -1,7 +1,7 @@
---
title: Using custom instructions to unlock the power of Copilot code review
shortTitle: Customize code review
-intro: Learn how to write effective custom instructions that help {% data variables.product.prodname_copilot %} provide more relevant and actionable code reviews.
+intro: 'Learn how to write effective custom instructions that help {% data variables.product.prodname_copilot %} provide more relevant and actionable code reviews.'
versions:
feature: copilot
contentType: tutorials
@@ -9,8 +9,6 @@ category:
- Accelerate PR velocity
- Team collaboration
- Configure Copilot
-redirect_from:
- - /copilot/tutorials/use-custom-instructions
---
## Introduction
diff --git a/data/reusables/organizations/copilot-policy-ent-overrides-org.md b/data/reusables/organizations/copilot-policy-ent-overrides-org.md
index a9ad35c611ed..6dae2be58dfe 100644
--- a/data/reusables/organizations/copilot-policy-ent-overrides-org.md
+++ b/data/reusables/organizations/copilot-policy-ent-overrides-org.md
@@ -1 +1 @@
-{% data variables.product.prodname_copilot_short %} policies are also managed at the enterprise level. If your enterprise owner has selected a specific policy, such as enabling a feature everywhere, disabling it everywhere, or enabling it for selected organizations only, you cannot override that setting at the organization level. For information on how policies combine, see [AUTOTITLE](/copilot/concepts/policies).
+{% data variables.product.prodname_copilot_short %} policies are also managed at the enterprise level. If your organization is part of an enterprise, and explicit settings have been selected at the enterprise level, you cannot override those settings at the organization level. For information on how policies combine, see [AUTOTITLE](/copilot/concepts/policies).
diff --git a/src/article-api/lib/get-all-toc-items.ts b/src/article-api/lib/get-all-toc-items.ts
index bf3e94824931..0dc3f9a87a1c 100644
--- a/src/article-api/lib/get-all-toc-items.ts
+++ b/src/article-api/lib/get-all-toc-items.ts
@@ -1,13 +1,10 @@
import type { Context, Page } from '@/types'
import type { LinkData } from '@/article-api/transformers/types'
import { resolvePath } from './resolve-path'
-import { renderLiquid } from '@/content-render/liquid/index'
interface PageWithChildren extends Page {
children?: string[]
category?: string[]
- rawTitle: string
- rawIntro?: string
}
interface TocItem extends LinkData {
@@ -29,11 +26,14 @@ export async function getAllTocItems(
page: Page,
context: Context,
options: {
+ recurse?: boolean
+ renderIntros?: boolean
/** Only recurse into children whose resolved path starts with this prefix.
* Prevents cross-product traversal (e.g. /en/rest listing /enterprise-admin). */
basePath?: string
} = {},
): Promise {
+ const { recurse = true, renderIntros = true } = options
const pageWithChildren = page as PageWithChildren
const languageCode = page.languageCode || 'en'
@@ -70,15 +70,19 @@ export async function getAllTocItems(
)
const href = childPermalink ? childPermalink.href : childHref
- const title = await renderPropFast(childPage, 'title', context)
- const intro = await renderPropFast(childPage, 'intro', context)
+ const title = await childPage.renderTitle(context, { unwrap: true })
+
+ let intro = ''
+ if (renderIntros && childPage.intro) {
+ intro = await childPage.renderProp('intro', context, { textOnly: true })
+ }
const category = childPage.category || []
// Only recurse if the child is within the same product section
const withinSection = href.startsWith(basePath)
const childTocItems =
- withinSection && childPage.children && childPage.children.length > 0
+ recurse && withinSection && childPage.children && childPage.children.length > 0
? await getAllTocItems(childPage, context, { ...options, basePath })
: []
@@ -135,38 +139,3 @@ export function flattenTocItems(
recurse(tocItems)
return result
}
-
-/**
- * Check whether a string contains markdown link syntax that would need
- * processing by the unified pipeline (e.g. link rewriting, AUTOTITLE).
- *
- * Use this to short-circuit expensive rendering when the text is
- * Liquid-only and contains no markdown that needs transformation.
- */
-function hasMarkdownLinks(text: string): boolean {
- return text.includes('[') && text.includes('](/')
-}
-
-const RAW_PROP_MAP = {
- title: 'rawTitle',
- intro: 'rawIntro',
-} as const
-
-/**
- * Fast-path rendering for page properties. Renders Liquid only, skipping
- * the full unified pipeline. Falls back to page.renderProp() when the
- * Liquid output contains markdown links that need rewriting.
- */
-async function renderPropFast(
- page: PageWithChildren,
- prop: keyof typeof RAW_PROP_MAP,
- context: Context,
-): Promise {
- const raw = page[RAW_PROP_MAP[prop]]
- if (!raw) return ''
- const rendered = await renderLiquid(raw, context)
- if (hasMarkdownLinks(rendered)) {
- return page.renderProp(prop, context, { textOnly: true })
- }
- return rendered.trim()
-}
diff --git a/src/article-api/transformers/bespoke-landing-transformer.ts b/src/article-api/transformers/bespoke-landing-transformer.ts
index 8f3916f390e3..652b69910ff1 100644
--- a/src/article-api/transformers/bespoke-landing-transformer.ts
+++ b/src/article-api/transformers/bespoke-landing-transformer.ts
@@ -95,7 +95,10 @@ export class BespokeLandingTransformer implements PageTransformer {
// Note: For bespoke-landing pages, the site shows ALL articles regardless of includedCategories
// (includedCategories only filters for discovery-landing pages)
if (bespokePage.children && bespokePage.children.length > 0) {
- const tocItems = await getAllTocItems(page, context)
+ const tocItems = await getAllTocItems(page, context, {
+ recurse: true,
+ renderIntros: true,
+ })
// Flatten to get all leaf articles (excludeParents: true means only get articles, not category pages)
const allArticles = flattenTocItems(tocItems, { excludeParents: true })
diff --git a/src/article-api/transformers/discovery-landing-transformer.ts b/src/article-api/transformers/discovery-landing-transformer.ts
index 6cf5a3d9e0fd..3fdf462a95f4 100644
--- a/src/article-api/transformers/discovery-landing-transformer.ts
+++ b/src/article-api/transformers/discovery-landing-transformer.ts
@@ -132,7 +132,10 @@ export class DiscoveryLandingTransformer implements PageTransformer {
// recursion (e.g. /rest listing /enterprise-admin children that
// point outside the /rest hierarchy).
if (discoveryPage.children && discoveryPage.children.length > 0) {
- const tocItems = await getAllTocItems(page, context)
+ const tocItems = await getAllTocItems(page, context, {
+ recurse: true,
+ renderIntros: true,
+ })
// Flatten to get all leaf articles (excludeParents: true means only get articles, not category pages)
let allArticles = flattenTocItems(tocItems, { excludeParents: true })
diff --git a/src/journeys/lib/journey-path-resolver.ts b/src/journeys/lib/journey-path-resolver.ts
index 847966cf16fd..cd1fa9876b9f 100644
--- a/src/journeys/lib/journey-path-resolver.ts
+++ b/src/journeys/lib/journey-path-resolver.ts
@@ -233,21 +233,6 @@ export async function resolveJourneyContext(
}
}
- // Build the list of guides available for the current version.
- // fetchGuideData returns null for guides that don't exist in the current version,
- // so this filters out unavailable guides for correct counts and navigation.
- const availableGuides = (
- await Promise.all(
- track.guides.map(async (guide, i) => {
- const guideData = await fetchGuideData(guide.href, context)
- return guideData ? { rawIndex: i, ...guideData } : null
- }),
- )
- ).filter((g): g is { rawIndex: number; href: string; title: string } => g !== null)
-
- const filteredIndex = availableGuides.findIndex((g) => g.rawIndex === guideIndex)
- const filteredCount = availableGuides.length
-
result = {
trackId: track.id,
trackName: track.id,
@@ -255,25 +240,31 @@ export async function resolveJourneyContext(
journeyTitle: journeyPage.title || '',
journeyPath:
journeyPage.permalink || Permalink.relativePathToSuffix(journeyPage.relativePath || ''),
- currentGuideIndex: filteredIndex >= 0 ? filteredIndex : guideIndex,
- numberOfGuides: filteredCount,
+ currentGuideIndex: guideIndex,
+ numberOfGuides: track.guides.length,
alternativeNextStep: renderedAlternativeNextStep,
}
- // Set up previous guide using the version-filtered list
- if (filteredIndex > 0) {
- const prev = availableGuides[filteredIndex - 1]
- result.prevGuide = { href: prev.href, title: prev.title }
+ // Set up previous guide
+ if (guideIndex > 0) {
+ const prevGuidePath = track.guides[guideIndex - 1].href
+ const guideData = await fetchGuideData(prevGuidePath, context)
+ if (guideData) {
+ result.prevGuide = guideData
+ }
}
- // Set up next guide using the version-filtered list
- if (filteredIndex >= 0 && filteredIndex < filteredCount - 1) {
- const next = availableGuides[filteredIndex + 1]
- result.nextGuide = { href: next.href, title: next.title }
+ // Set up next guide
+ if (guideIndex < track.guides.length - 1) {
+ const nextGuidePath = track.guides[guideIndex + 1].href
+ const guideData = await fetchGuideData(nextGuidePath, context)
+ if (guideData) {
+ result.nextGuide = guideData
+ }
}
- // Only populate nextTrackFirstGuide when on the last guide of the filtered track
- if (filteredIndex === filteredCount - 1) {
+ // Only populate nextTrackFirstGuide when on the last guide of the track
+ if (guideIndex === track.guides.length - 1) {
foundTrackIndex = trackIndex
if (
@@ -329,18 +320,16 @@ export async function resolveJourneyTracks(
? await renderContent(track.description, context, { textOnly: true })
: track.description
- const guides = (
- await Promise.all(
- track.guides.map(async (guide: { href: string; alternativeNextStep?: string }) => {
- const linkData = await getLinkData(guide.href, context, { title: true })
- if (!linkData?.[0]) return null
- return {
- href: linkData[0].href,
- title: linkData[0].title || '',
- }
- }),
- )
- ).filter((g): g is { href: string; title: string } => g !== null)
+ const guides = await Promise.all(
+ track.guides.map(async (guide: { href: string; alternativeNextStep?: string }) => {
+ const linkData = await getLinkData(guide.href, context, { title: true })
+ const baseHref = linkData?.[0]?.href || guide.href
+ return {
+ href: baseHref,
+ title: linkData?.[0]?.title || 'Untitled Guide',
+ }
+ }),
+ )
return {
id: track.id,
diff --git a/src/journeys/tests/journey-path-resolver.ts b/src/journeys/tests/journey-path-resolver.ts
index b9effe317146..0678b7291c79 100644
--- a/src/journeys/tests/journey-path-resolver.ts
+++ b/src/journeys/tests/journey-path-resolver.ts
@@ -1,27 +1,19 @@
-import { afterEach, describe, expect, test, vi } from 'vitest'
+import { describe, expect, test, vi } from 'vitest'
import { resolveJourneyContext, resolveJourneyTracks } from '../lib/journey-path-resolver'
-import getLinkData from '@/journeys/lib/get-link-data'
import type { Page } from '@/types'
// Mock modules since we just want to test journey functions, not their dependencies or
// against real content files
vi.mock('@/journeys/lib/get-link-data', () => ({
- default: vi.fn(async (rawLinks: string | string[] | undefined) => {
- const path = Array.isArray(rawLinks) ? rawLinks[0] : rawLinks
- if (!path) return undefined
- return [
- {
- href: `/en/enterprise-cloud@latest${path}`,
- title: `Mock Title for ${path}`,
- page: {} as unknown as Page,
- },
- ]
- }),
+ default: async (path: string) => [
+ {
+ href: `/en/enterprise-cloud@latest${path}`,
+ title: `Mock Title for ${path}`,
+ },
+ ],
}))
-const mockGetLinkData = vi.mocked(getLinkData)
-
vi.mock('@/content-render/index', () => ({
renderContent: async (content: string) => content,
}))
@@ -277,156 +269,4 @@ describe('journey-path-resolver', () => {
expect(result[0].description).toBeUndefined()
})
})
-
- describe('resolveJourneyContext with version-filtered guides', () => {
- afterEach(() => {
- // Restore the default implementation after each test in this block
- mockGetLinkData.mockImplementation(async (rawLinks: string | string[] | undefined) => {
- const path = Array.isArray(rawLinks) ? rawLinks[0] : rawLinks
- if (!path) return undefined
- return [
- {
- href: `/en/enterprise-cloud@latest${path}`,
- title: `Mock Title for ${path}`,
- page: {} as unknown as Page,
- },
- ]
- })
- })
-
- const mockContext = {
- currentProduct: 'github',
- currentLanguage: 'en',
- currentVersion: 'enterprise-cloud@latest',
- }
-
- // Track with 4 guides where the 2nd guide is unavailable in the current version:
- // raw indices: 0=setup, 1=unavailable, 2=config, 3=deploy
- // filtered: 0=setup, 1=config, 2=deploy
- const mockPages = {
- 'enterprise-onboarding/index': {
- layout: 'journey-landing',
- title: 'Enterprise onboarding',
- permalink: '/enterprise-onboarding',
- journeyTracks: [
- {
- id: 'getting_started',
- title: 'Getting started',
- guides: [
- { href: '/enterprise-onboarding/setup' },
- { href: '/enterprise-onboarding/unavailable' },
- { href: '/enterprise-onboarding/config' },
- { href: '/enterprise-onboarding/deploy' },
- ],
- },
- ],
- },
- } as unknown as Record
-
- test('resolveJourneyTracks filters out guides unavailable for the current version', async () => {
- mockGetLinkData.mockImplementation(async (rawLinks: string | string[] | undefined) => {
- const path = Array.isArray(rawLinks) ? rawLinks[0] : rawLinks
- if (path === '/enterprise-onboarding/config') return undefined
- if (!path) return undefined
- return [
- {
- href: `/en/enterprise-cloud@latest${path}`,
- title: `Mock Title for ${path}`,
- page: {} as unknown as Page,
- },
- ]
- })
-
- const tracks = [
- {
- id: 'getting_started',
- title: 'Getting started',
- guides: [
- { href: '/enterprise-onboarding/setup' },
- { href: '/enterprise-onboarding/config' },
- ],
- },
- ]
- const result = await resolveJourneyTracks(tracks, mockContext)
-
- // /enterprise-onboarding/config is unavailable, so only /enterprise-onboarding/setup remains
- expect(result[0].guides).toHaveLength(1)
- expect(result[0].guides[0].href).toBe(
- '/en/enterprise-cloud@latest/enterprise-onboarding/setup',
- )
- })
-
- test('numberOfGuides reflects only version-available guides', async () => {
- mockGetLinkData.mockImplementation(async (rawLinks: string | string[] | undefined) => {
- const path = Array.isArray(rawLinks) ? rawLinks[0] : rawLinks
- if (path === '/enterprise-onboarding/unavailable') return undefined
- if (!path) return undefined
- return [
- {
- href: `/en/enterprise-cloud@latest${path}`,
- title: `Mock Title for ${path}`,
- page: {} as unknown as Page,
- },
- ]
- })
-
- const result = await resolveJourneyContext(
- '/enterprise-onboarding/config',
- mockPages,
- mockContext,
- )
-
- expect(result?.numberOfGuides).toBe(3) // 3 available, not 4 raw
- })
-
- test('currentGuideIndex reflects position in version-filtered list', async () => {
- mockGetLinkData.mockImplementation(async (rawLinks: string | string[] | undefined) => {
- const path = Array.isArray(rawLinks) ? rawLinks[0] : rawLinks
- if (path === '/enterprise-onboarding/unavailable') return undefined
- if (!path) return undefined
- return [
- {
- href: `/en/enterprise-cloud@latest${path}`,
- title: `Mock Title for ${path}`,
- page: {} as unknown as Page,
- },
- ]
- })
-
- const result = await resolveJourneyContext(
- '/enterprise-onboarding/config',
- mockPages,
- mockContext,
- )
-
- // config is at raw index 2, but filtered index 1 (setup=0, config=1, deploy=2)
- expect(result?.currentGuideIndex).toBe(1)
- })
-
- test('prevGuide skips unavailable guides', async () => {
- mockGetLinkData.mockImplementation(async (rawLinks: string | string[] | undefined) => {
- const path = Array.isArray(rawLinks) ? rawLinks[0] : rawLinks
- if (path === '/enterprise-onboarding/unavailable') return undefined
- if (!path) return undefined
- return [
- {
- href: `/en/enterprise-cloud@latest${path}`,
- title: `Mock Title for ${path}`,
- page: {} as unknown as Page,
- },
- ]
- })
-
- // config's predecessor in the raw list is "unavailable", but in filtered list it's "setup"
- const result = await resolveJourneyContext(
- '/enterprise-onboarding/config',
- mockPages,
- mockContext,
- )
-
- expect(result?.prevGuide?.href).toBe(
- '/en/enterprise-cloud@latest/enterprise-onboarding/setup',
- )
- })
- })
})
diff --git a/src/languages/lib/correct-translation-content.ts b/src/languages/lib/correct-translation-content.ts
index ed9baaf11c90..35eeb7f96739 100644
--- a/src/languages/lib/correct-translation-content.ts
+++ b/src/languages/lib/correct-translation-content.ts
@@ -73,22 +73,9 @@ export function correctTranslatedContentStrings(
content = content.replace(/\{%-? para (?:la )?entrada en /g, (match) => {
return match.replace(/para (?:la )?entrada en/, 'for entry in')
})
- // `{% para el modelo en X %}` — "for the model in" = for model in
- content = content.replace(/\{%-? para el modelo en /g, (match) => {
- return match.replace('para el modelo en', 'for model in')
- })
content = content.replace(/\{%-? cuando /g, (match) => {
return match.replace('cuando', 'when')
})
- // `{% icono "X" ... %}` — "icono" = "icon" = octicon
- content = content.replaceAll('{% icono ', '{% octicon ')
- content = content.replaceAll('{%- icono ', '{%- octicon ')
- // `{% octicon "bombilla" %}` — Spanish "bombilla" = "light-bulb" (translated octicon name)
- content = content.replaceAll('{% octicon "bombilla"', '{% octicon "light-bulb"')
- content = content.replaceAll('{%- octicon "bombilla"', '{%- octicon "light-bulb"')
- // `{% capturar X %}` — "capturar" = "to capture" = capture
- content = content.replaceAll('{% capturar ', '{% capture ')
- content = content.replaceAll('{%- capturar ', '{%- capture ')
// Translated block tags
content = content.replaceAll('{% nota %}', '{% note %}')
content = content.replaceAll('{%- nota %}', '{%- note %}')
@@ -229,9 +216,6 @@ export function correctTranslatedContentStrings(
// `{% ウィンドウズ %}` — "Windows" = windows (platform tag)
content = content.replaceAll('{% ウィンドウズ %}', '{% windows %}')
content = content.replaceAll('{%- ウィンドウズ %}', '{%- windows %}')
- // `{% ウィンドウ %}` — "Window" (without ズ suffix) = windows (alternate transliteration)
- content = content.replaceAll('{% ウィンドウ %}', '{% windows %}')
- content = content.replaceAll('{%- ウィンドウ %}', '{%- windows %}')
}
if (context.code === 'pt') {
@@ -419,9 +403,6 @@ export function correctTranslatedContentStrings(
// `{% Linux %}` — capitalized linux platform tag
content = content.replaceAll('{% Linux %}', '{% linux %}')
content = content.replaceAll('{%- Linux %}', '{%- linux %}')
- // `{% джетмозги %}` — Russian literal translation of "JetBrains" (джет=jet, мозги=brains)
- content = content.replaceAll('{% джетмозги %}', '{% jetbrains %}')
- content = content.replaceAll('{%- джетмозги %}', '{%- jetbrains %}')
// Fix double quotes in Russian YAML files that cause parsing errors
content = content.replace(/href=""https:\/\//g, 'href="https://')
@@ -512,27 +493,6 @@ export function correctTranslatedContentStrings(
// `{% éclipse %}` — French accent on "eclipse" platform tag
content = content.replaceAll('{% éclipse %}', '{% eclipse %}')
content = content.replaceAll('{%- éclipse %}', '{%- eclipse %}')
- // `{% données_reutilisables.X %}` — underscore form of "données réutilisables" (no accent)
- content = content.replaceAll('{% données_reutilisables.', '{% data reusables.')
- content = content.replaceAll('{%- données_reutilisables.', '{%- data reusables.')
- // `{% données_réutilisables.X %}` — underscore form with accent
- content = content.replaceAll('{% données_réutilisables.', '{% data reusables.')
- content = content.replaceAll('{%- données_réutilisables.', '{%- data reusables.')
- // `{% composants réutilisables.X %}` — "composants" = "components" as alias for data reusables
- content = content.replaceAll('{% composants réutilisables.', '{% data reusables.')
- content = content.replaceAll('{%- composants réutilisables.', '{%- data reusables.')
- // Fully-translated `{% données réutilisables propriétés-personnalisées valeurs-requises %}`
- // → `{% data reusables.organizations.custom-properties-required-values %}`
- // Note: the generic `{% données ` → `{% data ` fix above may already have transformed
- // `données` to `data`, so we match both the original and the partially-corrected form.
- content = content.replaceAll(
- '{% données réutilisables propriétés-personnalisées valeurs-requises %}',
- '{% data reusables.organizations.custom-properties-required-values %}',
- )
- content = content.replaceAll(
- '{% data réutilisables propriétés-personnalisées valeurs-requises %}',
- '{% data reusables.organizations.custom-properties-required-values %}',
- )
// Remove orphaned {% endif %} tags when no ifversion/elsif opener exists in the content.
// Caused by translations where only the closing tag survived (e.g. user-api.md reusable).
if (
@@ -582,12 +542,6 @@ export function correctTranslatedContentStrings(
// `{% 윈도우즈 %}` — Korean transliteration of "windows"
content = content.replaceAll('{% 윈도우즈 %}', '{% windows %}')
content = content.replaceAll('{%- 윈도우즈 %}', '{%- windows %}')
- // `{% 엔드맥 %}` — Korean translation of "endmac" (end + mac)
- content = content.replaceAll('{% 엔드맥 %}', '{% endmac %}')
- content = content.replaceAll('{%- 엔드맥 %}', '{%- endmac %}')
- // `{% 주석 끝 %}` — Korean "주석 끝" (note end) = endnote
- content = content.replaceAll('{% 주석 끝 %}', '{% endnote %}')
- content = content.replaceAll('{%- 주석 끝 %}', '{%- endnote %}')
}
if (context.code === 'de') {
diff --git a/src/languages/tests/correct-translation-content.ts b/src/languages/tests/correct-translation-content.ts
index 12e7244705dd..cc0a32e35622 100644
--- a/src/languages/tests/correct-translation-content.ts
+++ b/src/languages/tests/correct-translation-content.ts
@@ -91,34 +91,6 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{%- encabezados de fila %}', 'es')).toBe('{%- rowheaders %}')
})
- test('fixes icono → octicon', () => {
- expect(fix('{% icono "copilot" aria-hidden="true" aria-label="Copilot" %}', 'es')).toBe(
- '{% octicon "copilot" aria-hidden="true" aria-label="Copilot" %}',
- )
- expect(fix('{%- icono "check" %}', 'es')).toBe('{%- octicon "check" %}')
- })
-
- test('fixes octicon "bombilla" → octicon "light-bulb"', () => {
- expect(fix('{% octicon "bombilla" aria-label="The light-bulb icon" %}', 'es')).toBe(
- '{% octicon "light-bulb" aria-label="The light-bulb icon" %}',
- )
- expect(fix('{%- octicon "bombilla" aria-label="The light-bulb icon" %}', 'es')).toBe(
- '{%- octicon "light-bulb" aria-label="The light-bulb icon" %}',
- )
- })
-
- test('fixes capturar → capture', () => {
- expect(fix('{% capturar service_name %}runner{% endcapture %}', 'es')).toBe(
- '{% capture service_name %}runner{% endcapture %}',
- )
- })
-
- test('fixes para el modelo en → for model in', () => {
- expect(fix('{% para el modelo en tables.copilot.model-comparison %}', 'es')).toBe(
- '{% for model in tables.copilot.model-comparison %}',
- )
- })
-
test('fixes multiple or-translations in single ifversion', () => {
expect(fix('{% ifversion fpt o ghec o ghes %}', 'es')).toBe(
'{% ifversion fpt or ghec or ghes %}',
@@ -290,11 +262,6 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{% ウィンドウズ %}', 'ja')).toBe('{% windows %}')
expect(fix('{%- ウィンドウズ %}', 'ja')).toBe('{%- windows %}')
})
-
- test('fixes ウィンドウ (without ズ) → windows', () => {
- expect(fix('{% ウィンドウ %}', 'ja')).toBe('{% windows %}')
- expect(fix('{%- ウィンドウ %}', 'ja')).toBe('{%- windows %}')
- })
})
// ─── PORTUGUESE (pt) ───────────────────────────────────────────────
@@ -649,11 +616,6 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{% Linux %}', 'ru')).toBe('{% linux %}')
expect(fix('{%- Linux %}', 'ru')).toBe('{%- linux %}')
})
-
- test('fixes джетмозги → jetbrains', () => {
- expect(fix('{% джетмозги %}', 'ru')).toBe('{% jetbrains %}')
- expect(fix('{%- джетмозги %}', 'ru')).toBe('{%- jetbrains %}')
- })
})
// ─── FRENCH (fr) ───────────────────────────────────────────────────
@@ -750,27 +712,6 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{% éclipse %}', 'fr')).toBe('{% eclipse %}')
expect(fix('{%- éclipse %}', 'fr')).toBe('{%- eclipse %}')
})
-
- test('fixes données_reutilisables → data reusables', () => {
- expect(fix('{% données_reutilisables.user-settings.ssh %}', 'fr')).toBe(
- '{% data reusables.user-settings.ssh %}',
- )
- expect(fix('{% données_réutilisables.codespaces.foo %}', 'fr')).toBe(
- '{% data reusables.codespaces.foo %}',
- )
- })
-
- test('fixes composants réutilisables → data reusables', () => {
- expect(fix('{% composants réutilisables.répertoires.barre-latérale-sujets %}', 'fr')).toBe(
- '{% data reusables.répertoires.barre-latérale-sujets %}',
- )
- })
-
- test('fixes fully-translated données réutilisables propriétés-personnalisées path', () => {
- expect(
- fix('{% données réutilisables propriétés-personnalisées valeurs-requises %}', 'fr'),
- ).toBe('{% data reusables.organizations.custom-properties-required-values %}')
- })
})
// ─── KOREAN (ko) ──────────────────────────────────────────────────
@@ -849,16 +790,6 @@ describe('correctTranslatedContentStrings', () => {
expect(fix('{% 윈도우즈 %}', 'ko')).toBe('{% windows %}')
expect(fix('{%- 윈도우즈 %}', 'ko')).toBe('{%- windows %}')
})
-
- test('fixes 엔드맥 → endmac', () => {
- expect(fix('{% 엔드맥 %}', 'ko')).toBe('{% endmac %}')
- expect(fix('{%- 엔드맥 %}', 'ko')).toBe('{%- endmac %}')
- })
-
- test('fixes 주석 끝 → endnote', () => {
- expect(fix('{% 주석 끝 %}', 'ko')).toBe('{% endnote %}')
- expect(fix('{%- 주석 끝 %}', 'ko')).toBe('{%- endnote %}')
- })
})
// ─── GERMAN (de) ──────────────────────────────────────────────────