From a18b62707d091152d15df9b44f0f284d6ab47bc4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:15:31 +0000 Subject: [PATCH 1/3] chore(release): v0.1.36 --- CHANGELOG.md | 17 +++++++++++++++++ package-lock.json | 6 +++--- packages/cli/package.json | 2 +- packages/docs-site/package.json | 4 ++-- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c25775d..02be13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,23 @@ The project uses Semantic Versioning. Each release contains the same three chang - None. +## [0.1.36] - 2026-09-28 + +### New Features + +- Deploy free documentation hosting for public GitHub repositories with `cortex deploy`, publishing your existing Markdown to a `*.cortexdocs.dev` subdomain. +- Serve a live MCP server directly from a public repository with `cortex mcp-serve https://github.com/OWNER/REPO`, without a local checkout. +- Documentation sites are now published through Cloudflare Static Assets for direct, low-latency delivery of hosted docs. + +### Bug Fixes + +- Fixed in-page links to Markdown headings so they scroll to the correct section in the hosted documentation site, even before content has fully loaded. +- Removed a conflicting cache-control header on static hosted assets that could cause stale or inconsistent caching behavior. + +### Improvements + +- None. + ## [0.1.35] - 2026-09-22 ### New Features diff --git a/package-lock.json b/package-lock.json index 9e430a0..e479a49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18436,7 +18436,7 @@ }, "packages/cli": { "name": "@cortex-docs/cli", - "version": "0.1.35", + "version": "0.1.36", "bundleDependencies": [ "@cortex-docs/core", "@cortex-docs/codegen", @@ -18631,9 +18631,9 @@ }, "packages/docs-site": { "name": "@cortex-docs/docs-site", - "version": "0.1.35", + "version": "0.1.36", "dependencies": { - "@cortex-docs/cli": "0.1.35" + "@cortex-docs/cli": "0.1.36" } }, "packages/docs-ui": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 5bd0848..b7ba844 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cortex-docs/cli", - "version": "0.1.35", + "version": "0.1.36", "description": "Cortex Docs CLI for SDKs, API documentation, and MCP servers", "license": "MIT", "repository": { diff --git a/packages/docs-site/package.json b/packages/docs-site/package.json index 84837f8..d8c3aef 100644 --- a/packages/docs-site/package.json +++ b/packages/docs-site/package.json @@ -1,6 +1,6 @@ { "name": "@cortex-docs/docs-site", - "version": "0.1.35", + "version": "0.1.36", "private": true, "description": "Product documentation for Cortex Docs", "scripts": { @@ -9,6 +9,6 @@ "clean": "rm -rf .cortex" }, "dependencies": { - "@cortex-docs/cli": "0.1.35" + "@cortex-docs/cli": "0.1.36" } } From 5741cee9c52e02ee7c45b98c3c34259f44145fc0 Mon Sep 17 00:00:00 2001 From: Nick Chisiu <8492343+nickchisiu@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:50:17 +0300 Subject: [PATCH 2/3] fix: adapt primary button text to configured colors --- e2e/docs-ui-static.spec.ts | 23 ++++++++ .../docs-ui/__tests__/primary-theme.test.ts | 28 ++++++++++ packages/docs-ui/app/layout.tsx | 36 +------------ packages/docs-ui/app/page.tsx | 34 +----------- packages/docs-ui/lib/primary-theme.ts | 54 +++++++++++++++++++ 5 files changed, 109 insertions(+), 66 deletions(-) create mode 100644 packages/docs-ui/__tests__/primary-theme.test.ts create mode 100644 packages/docs-ui/lib/primary-theme.ts diff --git a/e2e/docs-ui-static.spec.ts b/e2e/docs-ui-static.spec.ts index 20825b0..64ad71d 100644 --- a/e2e/docs-ui-static.spec.ts +++ b/e2e/docs-ui-static.spec.ts @@ -30,6 +30,29 @@ test.describe('Cloudflare Static Assets export', () => { await expect(page.locator('.cortex-cookie-settings-button')).toHaveCount(0); }); + for (const appearance of ['light', 'dark']) { + for (const [color, background, foreground] of [ + ['#c2410c', 'rgb(194, 65, 12)', 'rgb(255, 255, 255)'], + ['#2563eb', 'rgb(37, 99, 235)', 'rgb(255, 255, 255)'], + ['#7c3aed', 'rgb(124, 58, 237)', 'rgb(255, 255, 255)'], + ['#ffffff', 'rgb(255, 255, 255)', 'rgb(10, 10, 10)'], + ]) { + test(`adapts the homepage button text to ${color} in ${appearance} mode`, async ({ + page, + }) => { + await page.route('**/api/config*', async (route) => { + const response = await route.fetch(); + const config = await response.json(); + await route.fulfill({ json: { ...config, primaryColor: color } }); + }); + await page.goto(`/?appearance=${appearance}`); + const button = page.getByRole('link', { name: 'Getting Started', exact: true }); + await expect(button).toHaveCSS('background-color', background); + await expect(button).toHaveCSS('color', foreground); + }); + } + } + test('supports client navigation between generated documentation pages', async ({ page }) => { await page.goto('/docs/quickstart'); await expect(page).toHaveTitle('Petstore Docs'); diff --git a/packages/docs-ui/__tests__/primary-theme.test.ts b/packages/docs-ui/__tests__/primary-theme.test.ts new file mode 100644 index 0000000..4c8b901 --- /dev/null +++ b/packages/docs-ui/__tests__/primary-theme.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; +import { primaryPalette, primaryThemeCss } from '../lib/primary-theme'; + +describe('primary button contrast', () => { + it.each(['#c2410c', '#2563eb', '#7c3aed', '#000000', '#0000ff'])( + 'uses white text on %s', + (color) => { + expect(primaryPalette(color).foreground).toBe('#ffffff'); + }, + ); + + it.each(['#ffffff', '#ffff00', '#00ff00', '#f97316', '#ff0000'])( + 'uses dark text on a light fill (%s)', + (color) => { + expect(primaryPalette(color).foreground).toBe('#0a0a0a'); + }, + ); + + it('keeps the chosen button fill instead of brightening it in dark mode', () => { + expect(primaryPalette('#2563eb').background).toBe('rgb(37,99,235)'); + expect(primaryThemeCss('#2563eb').match(/--color-primary:rgb\(37,99,235\)/g)).toHaveLength(3); + }); + + it('ignores invalid colors before producing CSS', () => { + expect(primaryThemeCss('red')).toBe(''); + expect(primaryThemeCss('#fff; color:red')).toBe(''); + }); +}); diff --git a/packages/docs-ui/app/layout.tsx b/packages/docs-ui/app/layout.tsx index 579714e..345e3ac 100644 --- a/packages/docs-ui/app/layout.tsx +++ b/packages/docs-ui/app/layout.tsx @@ -13,6 +13,7 @@ import { import { SearchProvider } from '@/components/docs/search-provider'; import { GoogleAnalytics } from '@/components/docs/google-analytics'; import { sanitizeSvg } from '@/lib/sanitize-svg'; +import { primaryThemeCss } from '@/lib/primary-theme'; interface LoadedSiteConfig extends SiteConfig { customHeadHtml?: string; @@ -27,33 +28,6 @@ function emptySiteConfig(): LoadedSiteConfig { }; } -function adj(r: number, g: number, b: number, mul: number) { - return `rgb(${Math.min(255, Math.round(r * mul))},${Math.min(255, Math.round(g * mul))},${Math.min(255, Math.round(b * mul))})`; -} - -function parsePrimary(hex: string) { - const r = parseInt(hex.slice(1, 3), 16); - const g = parseInt(hex.slice(3, 5), 16); - const b = parseInt(hex.slice(5, 7), 16); - const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255; - const lightBgMul = lum < 0.15 ? 1.8 : lum < 0.3 ? 1.3 : 1; - const darkBgMul = lum < 0.15 ? 4 : lum < 0.3 ? 2.2 : lum < 0.4 ? 1.4 : 1; - const textMul = - lum > 0.9 ? 0.45 : lum > 0.7 ? 0.88 : lum > 0.5 ? 0.92 : lum < 0.15 ? 3.5 : lum < 0.3 ? 2 : 1; - const lightColor = adj(r, g, b, lightBgMul); - const darkColor = adj(r, g, b, darkBgMul); - const lightText = adj(r, g, b, textMul); - const darkText = adj(r, g, b, darkBgMul); - const lightLum = lum * lightBgMul; - const darkLum = Math.min(1, lum * darkBgMul); - const lightFg = lightLum > 0.5 ? '#0a0a0a' : '#fafafa'; - const darkFg = darkLum > 0.5 ? '#0a0a0a' : '#fafafa'; - const targetTintLum = 0.45; - const tintMul = lum > 0 ? Math.max(1, targetTintLum / lum) : 6; - const cardTint = adj(r, g, b, Math.min(tintMul, 10)); - return { lightColor, darkColor, lightFg, darkFg, lightText, darkText, cardTint }; -} - function createThemeInitializationScript(defaultTheme: 'light' | 'dark' | 'system'): string { return `(()=>{try{const e=document.documentElement,p=new URLSearchParams(location.search).get("appearance");let t=p==="light"||p==="dark"?p:"";if(!t){try{const s=localStorage.getItem("theme");if(s==="light"||s==="dark"||s==="system")t=s}catch{}}if(!t)t="${defaultTheme}";const r=t==="system"?(matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"):t;e.classList.remove("light","dark");e.classList.add(r);e.style.colorScheme=r}catch{}})();`; } @@ -196,13 +170,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) const { customHeadHtml, ...siteConfig } = readSiteConfig(); const defaultTheme = siteConfig.theme ?? 'system'; - const pc = siteConfig.primaryColor; - const primaryCss = pc - ? (() => { - const p = parsePrimary(pc); - return `html{--color-primary:${p.lightColor}!important;--color-primary-foreground:${p.lightFg}!important;--primary-text:${p.lightText};--primary-card-tint:${p.cardTint}}html.dark{--color-primary:${p.darkColor}!important;--color-primary-foreground:${p.darkFg}!important;--primary-text:${p.darkText};--primary-card-tint:${p.cardTint}}@media(prefers-color-scheme:dark){html:not(.light){--color-primary:${p.darkColor}!important;--color-primary-foreground:${p.darkFg}!important;--primary-text:${p.darkText};--primary-card-tint:${p.cardTint}}}`; - })() - : ''; + const primaryCss = primaryThemeCss(siteConfig.primaryColor ?? ''); return ( - `rgb(${Math.min(255, Math.round(r * m))},${Math.min(255, Math.round(g * m))},${Math.min(255, Math.round(b * m))})`; - const lbm = lum < 0.15 ? 1.8 : lum < 0.3 ? 1.3 : 1; - const dbm = lum < 0.15 ? 4 : lum < 0.3 ? 2.2 : lum < 0.4 ? 1.4 : 1; - const tm = - lum > 0.9 - ? 0.45 - : lum > 0.7 - ? 0.88 - : lum > 0.5 - ? 0.92 - : lum < 0.15 - ? 3.5 - : lum < 0.3 - ? 2 - : 1; - const tintMul = lum > 0 ? Math.max(1, 0.45 / lum) : 6; - const lightColor = a(lbm), - darkColor = a(dbm), - lightText = a(tm), - darkText = a(dbm), - cardTint = a(Math.min(tintMul, 10)); - const lightLum = lum * lbm, - darkLum = Math.min(1, lum * dbm); - const lightFg = lightLum > 0.5 ? '#0a0a0a' : '#fafafa'; - const darkFg = darkLum > 0.5 ? '#0a0a0a' : '#fafafa'; let s = document.querySelector('style[data-primary]') as HTMLStyleElement; if (!s) { s = document.createElement('style'); s.setAttribute('data-primary', ''); document.body.prepend(s); } - s.textContent = `html{--color-primary:${lightColor}!important;--color-primary-foreground:${lightFg}!important;--primary-text:${lightText};--primary-card-tint:${cardTint}}html.dark{--color-primary:${darkColor}!important;--color-primary-foreground:${darkFg}!important;--primary-text:${darkText};--primary-card-tint:${cardTint}}@media(prefers-color-scheme:dark){html:not(.light){--color-primary:${darkColor}!important;--color-primary-foreground:${darkFg}!important;--primary-text:${darkText};--primary-card-tint:${cardTint}}}`; + s.textContent = primaryThemeCss(data.primaryColor); } }) .catch(() => {}); diff --git a/packages/docs-ui/lib/primary-theme.ts b/packages/docs-ui/lib/primary-theme.ts new file mode 100644 index 0000000..92176ce --- /dev/null +++ b/packages/docs-ui/lib/primary-theme.ts @@ -0,0 +1,54 @@ +type Rgb = [number, number, number]; + +function luminance(color: Rgb): number { + const linear = color.map((channel) => { + const value = channel / 255; + return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4; + }); + return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]; +} + +export function primaryPalette(hex: string) { + const color: Rgb = [ + parseInt(hex.slice(1, 3), 16), + parseInt(hex.slice(3, 5), 16), + parseInt(hex.slice(5, 7), 16), + ]; + const brightness = (0.299 * color[0] + 0.587 * color[1] + 0.114 * color[2]) / 255; + const adjusted = (multiplier: number) => + `rgb(${color.map((channel) => Math.min(255, Math.round(channel * multiplier))).join(',')})`; + const backgroundLuminance = luminance(color); + const whiteContrast = 1.05 / (backgroundLuminance + 0.05); + const darkContrast = (backgroundLuminance + 0.05) / (luminance([10, 10, 10]) + 0.05); + const foreground = whiteContrast >= darkContrast ? '#ffffff' : '#0a0a0a'; + const textMultiplier = + brightness > 0.9 + ? 0.45 + : brightness > 0.7 + ? 0.88 + : brightness > 0.5 + ? 0.92 + : brightness < 0.15 + ? 3.5 + : brightness < 0.3 + ? 2 + : 1; + const darkTextMultiplier = + brightness < 0.15 ? 4 : brightness < 0.3 ? 2.2 : brightness < 0.4 ? 1.4 : 1; + const tintMultiplier = brightness > 0 ? Math.max(1, 0.45 / brightness) : 6; + return { + // Keep the configured fill in both themes. Brighten standalone accents separately. + background: adjusted(1), + foreground, + lightText: adjusted(textMultiplier), + darkText: adjusted(darkTextMultiplier), + cardTint: adjusted(Math.min(tintMultiplier, 10)), + }; +} + +export function primaryThemeCss(hex: string): string { + if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return ''; + const p = primaryPalette(hex); + const shared = `--color-primary:${p.background}!important;--color-primary-foreground:${p.foreground}!important;--primary-card-tint:${p.cardTint}`; + return `html{${shared};--primary-text:${p.lightText}}html.dark{${shared};--primary-text:${p.darkText}}@media(prefers-color-scheme:dark){html:not(.light){${shared};--primary-text:${p.darkText}}}`; +} From 3f8b193100ebb06e279220db7cbabec943f6b99e Mon Sep 17 00:00:00 2001 From: Nick Chisiu <8492343+nickchisiu@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:56:40 +0300 Subject: [PATCH 3/3] fix: show configured title in documentation header --- packages/docs-ui/components/docs/docs-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs-ui/components/docs/docs-header.tsx b/packages/docs-ui/components/docs/docs-header.tsx index 474cbe4..3ec3fdc 100644 --- a/packages/docs-ui/components/docs/docs-header.tsx +++ b/packages/docs-ui/components/docs/docs-header.tsx @@ -379,7 +379,7 @@ export function DocsHeader() { )} {!hasCustomLogo && ( - {project || siteTitle || 'Cortex'} + {siteTitle || project || 'Cortex'} )}