diff --git a/AGENTS.md b/AGENTS.md index 22d905334b..b5ad59f37f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,6 +84,7 @@ To check or apply style, read the relevant pass file(s) and use them as guidance - **Python SDK functions**: Module-level functions in the Python SDK are listed in the [Global Functions overview](/models/ref/python/functions). - **`.editorconfig`**: An `.editorconfig` file in the repository root enforces indentation and whitespace automatically. Most editors apply it with no configuration. If yours doesn't support it natively, install the EditorConfig plugin (https://editorconfig.org/#download). +- **Consistent language tab labels**: When an example offers multiple languages — in a `` or across `` blocks — label every tab with the same canonical name everywhere: `Python`, `TypeScript` (never `Typescript`), `Bash`. In a ``, give each fence a lowercase lexer **and** that canonical title (e.g. a `python` fence titled `Python`); never leave a language fence untitled. The reader's **Python/TypeScript** choice carries from page to page via `code-group-language-persist.js` (repo root), which matches those two labels case-insensitively — so inconsistent casing of them silently resets it. Other labels like `Bash` are only for in-page consistency and are intentionally not persisted across pages. Don't add a competing per-page persistence script. ## Working with the repository diff --git a/code-group-language-persist.js b/code-group-language-persist.js new file mode 100644 index 0000000000..30b7ec22ec --- /dev/null +++ b/code-group-language-persist.js @@ -0,0 +1,183 @@ +/** + * code-group-language-persist.js + * + * Remembers the language a reader picks in a code example and re-applies it on + * every page, for BOTH of Mintlify's tab systems: + * + * 1. (code-only tabs): Mintlify already persists these in + * localStorage["code"], but CASE-SENSITIVELY by exact label. + * 2. / (content tabs, e.g. Python vs TypeScript on + * /weave/quickstart): these have NO cross-page persistence in Mintlify — + * they reset to the first tab on every page. The URL #slug hash only + * deep-links within a single page and is dropped on navigation. + * + * Content tabs are the dominant language-switch pattern in these docs (hundreds + * of /), so without this the reader + * has to re-pick their language on every page. This script gives both systems + * unified, case-insensitive, cross-page persistence. + * + * How it works: + * - Remember: a capture-phase pointerdown/click listener stores the chosen + * tab's label in localStorage when the label is an SDK language (see + * LANGUAGES — only Python/TypeScript). The whitelist stops both descriptive + * tabs ("Web App", "Summary metrics") and incidental code tabs ("Bash", + * "cURL") from clobbering the saved language. + * - Restore: on load, after hydration, and on SPA route changes (a + * MutationObserver), any tab whose label matches the saved language (compared + * case-insensitively) is selected. Tabs already active are skipped, so this + * coexists with Mintlify's own restore without fighting it. + * + * Selection uses a real pointer sequence, not element.click(): Mintlify's tabs + * activate on pointer/mouse-down, and a synthetic click() does not switch them. + * + * Depends only on the ARIA tab contract Mintlify renders (role="tab", with + * data-state="active" for code groups or aria-selected="true" for content + * tabs), so it is resilient to theme class-name churn. + * + * Environment: + * - Loaded globally by Mintlify for every docs page (any .js in the content + * root is injected — see .mintignore). Wrapped in an IIFE. + */ + +(function () { + var STORAGE_KEY = 'wandb.docs.codeLanguage'; + + /** + * The SDK languages whose choice should follow the reader from page to page, + * compared lower-cased. Deliberately limited to Python and TypeScript — the + * two W&B SDK languages and the only genuine cross-page preference. + * + * Incidental code tabs (Bash, shell, cURL, YAML, JSON, …) are intentionally + * NOT listed: persisting them would let a click on, say, a Bash tab overwrite + * the saved Python/TypeScript choice, and a later page whose tabs are only + * Python/TypeScript has no Bash match — so it would silently fall back to its + * first tab, defeating the persistence. Restore is case-insensitive, so the + * canonical labels here cover "python"/"Python"/"TypeScript" alike; short + * aliases (py/ts) are omitted on purpose — storing "ts" would not match a + * "TypeScript" tab and would reintroduce that same fall-back bug. + */ + var LANGUAGES = { + python: 1, typescript: 1 + }; + + /** Read the saved language, tolerant of disabled/throwing localStorage. */ + function read() { + try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; } + } + + /** Persist the chosen language, tolerant of private-mode write failures. */ + function write(value) { + try { localStorage.setItem(STORAGE_KEY, value); } catch (e) { /* ignore */ } + } + + /** + * Visible label of a tab element. + * @param {Element} tab + * @returns {string} + */ + function labelOf(tab) { + return (tab.textContent || '').trim(); + } + + /** + * Whether a label is one of the known languages we should remember. + * @param {string} label + * @returns {boolean} + */ + function isLanguage(label) { + return !!LANGUAGES[label.toLowerCase()]; + } + + /** + * Whether a tab is the currently-selected one in its group. Code groups use + * data-state="active"; content tabs use aria-selected="true". + * @param {Element} tab + * @returns {boolean} + */ + function isActive(tab) { + return tab.getAttribute('data-state') === 'active' || + tab.getAttribute('aria-selected') === 'true'; + } + + /** + * Selects a tab the way a real click does. Mintlify's tabs activate on + * pointer/mouse-down, not on a synthetic element.click(), so dispatch a full + * pointer sequence. + * @param {Element} tab + */ + function activate(tab) { + var opts = { bubbles: true, cancelable: true, view: window, button: 0, isPrimary: true }; + if (window.PointerEvent) tab.dispatchEvent(new PointerEvent('pointerdown', opts)); + tab.dispatchEvent(new MouseEvent('mousedown', opts)); + if (window.PointerEvent) tab.dispatchEvent(new PointerEvent('pointerup', opts)); + tab.dispatchEvent(new MouseEvent('mouseup', opts)); + tab.dispatchEvent(new MouseEvent('click', opts)); + } + + /** + * Capture-phase selection handler. Records the chosen tab's label when it + * names a language, so the choice survives navigation. Bound to pointerdown + * (when tabs actually select) and click (covers keyboard activation). + * @param {Event} e + */ + function onSelect(e) { + var tab = e.target && e.target.closest ? e.target.closest('[role="tab"]') : null; + if (!tab) return; + var label = labelOf(tab); + if (label && isLanguage(label)) write(label); + } + + /** + * Selects the saved language in every group that isn't already on it. + * Self-terminating: tabs already active are skipped, so the synthetic clicks + * we trigger here do not cause an observe/click feedback loop, and we never + * fight Mintlify's own already-applied selection. + */ + function restore() { + var want = read(); + if (!want) return; + want = want.toLowerCase(); + var tabs = document.querySelectorAll('[role="tab"]'); + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + if (labelOf(tab).toLowerCase() === want && !isActive(tab)) { + activate(tab); + } + } + } + + // Debounce restore() into a single run after a burst of DOM mutations. + var pending = false; + function schedule() { + if (pending) return; + pending = true; + setTimeout(function () { pending = false; restore(); }, 80); + } + + function start() { + // Capture phase so we observe the selection around Mintlify's own handling. + document.addEventListener('pointerdown', onSelect, true); + document.addEventListener('click', onSelect, true); + + restore(); + // Catch tabs that hydrate after first paint. + [300, 800, 1500].forEach(function (ms) { setTimeout(restore, ms); }); + + // Re-apply whenever new content renders (client-side route changes). + if (document.body) { + new MutationObserver(schedule).observe(document.body, { + childList: true, + subtree: true + }); + } + + // Re-apply after back/forward (bfcache) restores. + window.addEventListener('pageshow', restore); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', start); + } else { + start(); + } +})(); diff --git a/fr/weave/guides/tracking/view-call.mdx b/fr/weave/guides/tracking/view-call.mdx index 13dda03ce0..e71e5e9c2b 100644 --- a/fr/weave/guides/tracking/view-call.mdx +++ b/fr/weave/guides/tracking/view-call.mdx @@ -129,7 +129,7 @@ Utilisez `weave.Markdown` pour personnaliser l’affichage de vos informations d ``` - + ```plaintext This feature is not available in the TypeScript SDK yet. ``` diff --git a/ja/weave/guides/tracking/view-call.mdx b/ja/weave/guides/tracking/view-call.mdx index 903dd506c3..da86d06e74 100644 --- a/ja/weave/guides/tracking/view-call.mdx +++ b/ja/weave/guides/tracking/view-call.mdx @@ -129,7 +129,7 @@ W&B Weave で Calls を作成したら、1 つの call を開いて、その ``` - + ```plaintext この機能は、TypeScript SDK ではまだ利用できません。 ``` diff --git a/ko/weave/guides/tracking/view-call.mdx b/ko/weave/guides/tracking/view-call.mdx index 84c7661e97..47e2823b29 100644 --- a/ko/weave/guides/tracking/view-call.mdx +++ b/ko/weave/guides/tracking/view-call.mdx @@ -129,7 +129,7 @@ W&B Weave에서 Calls를 만든 후에는 특정 Call을 열어 입력, 출 ``` - + ```plaintext 이 기능은 아직 TypeScript SDK에서 사용할 수 없습니다. ``` diff --git a/weave/concepts/what-is-weave.mdx b/weave/concepts/what-is-weave.mdx index da9f21c56a..7fcfd911a1 100644 --- a/weave/concepts/what-is-weave.mdx +++ b/weave/concepts/what-is-weave.mdx @@ -84,23 +84,23 @@ To get started with Weave: -```Python Python +```python Python pip install weave ``` -```Typescript Typescript +```typescript TypeScript npm install weave ``` 3. In your script, import Weave and initialize a project. Replace `` with your W&B team name and `` with your W&B project name. -```Python Python +```python Python import weave client = weave.init('/') ``` -```TypeScript Typescript +```typescript TypeScript import * as weave from 'weave'; const client = await weave.init('/'); ``` diff --git a/weave/guides/platform/weave-projects.mdx b/weave/guides/platform/weave-projects.mdx index b3953906d9..247b9d0676 100644 --- a/weave/guides/platform/weave-projects.mdx +++ b/weave/guides/platform/weave-projects.mdx @@ -68,13 +68,13 @@ When you open the new project's workspace, the UI offers quickstarts for both W& To create a Weave project directly from your code, provide a new project path as an argument to `weave.init()`: -```python +```python Python import weave weave.init('my-team/my-llm-app') ``` -```typescript +```typescript TypeScript import * as weave from 'weave'; await weave.init('my-team/my-llm-app'); diff --git a/weave/guides/tracking/get-call-object.mdx b/weave/guides/tracking/get-call-object.mdx index d0d691a7e7..8213ef6957 100644 --- a/weave/guides/tracking/get-call-object.mdx +++ b/weave/guides/tracking/get-call-object.mdx @@ -15,7 +15,7 @@ def my_op(): my_op() ``` -```typescript Typescript lines +```typescript TypeScript lines function myFunction() { ... } diff --git a/weave/guides/tracking/tracing.mdx b/weave/guides/tracking/tracing.mdx index e36f294a33..009f86eff4 100644 --- a/weave/guides/tracking/tracing.mdx +++ b/weave/guides/tracking/tracing.mdx @@ -17,7 +17,7 @@ An **Op** is a versioned, tracked function. When you decorate a function with `@ ... } ``` -```typescript Typescript +```typescript TypeScript function myFunction() { ... } diff --git a/weave/guides/tracking/view-call.mdx b/weave/guides/tracking/view-call.mdx index 1d95270f71..d8926586d2 100644 --- a/weave/guides/tracking/view-call.mdx +++ b/weave/guides/tracking/view-call.mdx @@ -126,7 +126,7 @@ if __name__ == "__main__": rag_step("Tell me about OpenAI") ``` - + ```plaintext This feature is not available in the TypeScript SDK yet. ```