From 86c463ceede1d1593c7c8aba9cbf800578a97b27 Mon Sep 17 00:00:00 2001 From: PiotrWodecki Date: Thu, 12 Jun 2025 13:22:00 +0200 Subject: [PATCH 01/56] Use shiki instead of prism for snippets --- docusaurus.config.ts | 33 +++++- package.json | 5 +- src/css/_shiki.css | 96 +++++++++++++++ src/css/custom.css | 2 + src/theme/MDXComponents/Code.tsx | 27 +++++ src/theme/MDXComponents/Pre.tsx | 6 + yarn.lock | 193 ++++++++++++++++++++++++++++++- 7 files changed, 353 insertions(+), 9 deletions(-) create mode 100644 src/css/_shiki.css create mode 100644 src/theme/MDXComponents/Code.tsx create mode 100644 src/theme/MDXComponents/Pre.tsx diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 1e641293..ddc83264 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -1,6 +1,31 @@ -import { themes as prismThemes } from "prism-react-renderer"; import type { Config } from "@docusaurus/types"; import type * as Preset from "@docusaurus/preset-classic"; +import { BundledLanguage, bundledLanguages } from "shiki"; +import type { MDXPlugin } from "@docusaurus/mdx-loader"; +import rehypeShiki, { RehypeShikiOptions } from "@shikijs/rehype"; +import { + transformerMetaHighlight, + transformerNotationDiff, + transformerNotationHighlight, + transformerNotationFocus, +} from "@shikijs/transformers"; + +const rehypeShikiPlugin = [ + rehypeShiki, + { + themes: { + light: "catppuccin-latte", + dark: "catppuccin-macchiato", + }, + langs: Object.keys(bundledLanguages) as BundledLanguage[], + transformers: [ + transformerMetaHighlight(), + transformerNotationDiff(), + transformerNotationHighlight(), + transformerNotationFocus(), + ], + } satisfies RehypeShikiOptions, +] satisfies MDXPlugin; function injectTypeDocSidebar(items) { return items.map((item) => { @@ -94,6 +119,7 @@ const config: Config = { path: "docs", routeBasePath: "/", editUrl: "https://github.com/fishjam-cloud/documentation/tree/main/", + beforeDefaultRehypePlugins: [rehypeShikiPlugin], remarkPlugins: [ [require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }], ], @@ -186,11 +212,6 @@ const config: Config = { ], copyright: `Copyright © ${new Date().getFullYear()} Software Mansion, Inc. All trademarks and copyrights belong to their respective owners.`, }, - prism: { - theme: prismThemes.gruvboxMaterialLight, - darkTheme: prismThemes.gruvboxMaterialDark, - additionalLanguages: ["bash"], - }, } satisfies Preset.ThemeConfig, plugins: [ diff --git a/package.json b/package.json index c657d236..2a4d2872 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,14 @@ "@docusaurus/remark-plugin-npm2yarn": "^3.7.0", "@docusaurus/theme-mermaid": "^3.7.0", "@mdx-js/react": "^3.1.0", + "@shikijs/rehype": "^3.6.0", + "@shikijs/transformers": "^3.6.0", "clsx": "^2.0.0", "docusaurus-lunr-search": "3.6.0", "prism-react-renderer": "^2.4.1", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "shiki": "^3.6.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.7.0", diff --git a/src/css/_shiki.css b/src/css/_shiki.css new file mode 100644 index 00000000..e7f04162 --- /dev/null +++ b/src/css/_shiki.css @@ -0,0 +1,96 @@ +[data-theme="dark"] pre { + color: var(--shiki-dark) !important; + background-color: var(--shiki-dark-bg) !important; +} + +[data-theme="dark"] pre span { + color: var(--shiki-dark) !important; +} + +.shiki { + --docusaurus-shiki-code-highlight: color(from var(--ifm-color-emphasis-500) srgb r g b / 0.3); + + --docusaurus-shiki-diff-add-bg: color(from var(--ifm-color-success-light) srgb r g b / 0.1); + --docusaurus-shiki-diff-add: var(--ifm-color-success-light); + --docusaurus-shiki-diff-remove-bg: color(from var(--ifm-color-danger-light) srgb r g b / 0.1); + --docusaurus-shiki-diff-remove: var(--ifm-color-danger-light); +} + +.shiki code { + /* display as flex-column to remove gap from line-breaks */ + display: flex; + flex-direction: column; + width: min-content; + min-width: 100%; +} + +.shiki .line:not(:last-child):empty::after { + /* display additional space so that empty lines don't collapse */ + /* note that this is only visual and content does not get copied */ + content: " "; +} + +/* highlighted */ +.shiki .line.highlighted { + position: relative; + display: inline-block; + margin: 0 calc(var(--ifm-pre-padding) * -1); + padding: 0 var(--ifm-pre-padding); + width: calc(100% + calc(var(--ifm-pre-padding) * 2)); +} + +.shiki .line.highlighted { + background-color: var(--docusaurus-shiki-code-highlight) !important; +} + +/* diff */ +.shiki .line.diff { + position: relative; + display: inline-block; + margin: 0 calc(var(--ifm-pre-padding) * -1); + padding: 0 var(--ifm-pre-padding); + width: calc(100% + calc(var(--ifm-pre-padding) * 2)); +} + +.shiki .line.diff::before { + position: absolute; + left: 0; + top: 0; + bottom: 0; + padding: 0 0.5em 0 0.2em; +} + +.shiki .line.diff.add { + background-color: var(--docusaurus-shiki-diff-add-bg) !important; +} + +.shiki .line.diff.add::before { + color: var(--docusaurus-shiki-diff-add); + content: "+"; +} + +.shiki .line.diff.remove { + background-color: var(--docusaurus-shiki-diff-remove-bg) !important; + user-select: none; +} + +.shiki .line.diff.remove::before { + color: var(--docusaurus-shiki-diff-remove); + content: "-"; +} + +/* focus */ +.shiki:has(.focused) .line:not(.focused) { + filter: blur(0.095rem); + opacity: 0.4; + transition: filter 0.35s, opacity 0.35s; +} + +.shiki:has(.focused):hover .line:not(.focused) { + filter: blur(0); + opacity: 1; +} + +.shiki .line.focused { + opacity: 1; +} \ No newline at end of file diff --git a/src/css/custom.css b/src/css/custom.css index ff132a18..b4e41dc4 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -46,3 +46,5 @@ html[data-theme="light"] header.heroBanner_src-pages-index-module { border-radius: var(--ifm-global-radius); background-color: white; } + +@import url('./_shiki.css'); \ No newline at end of file diff --git a/src/theme/MDXComponents/Code.tsx b/src/theme/MDXComponents/Code.tsx new file mode 100644 index 00000000..8cf78395 --- /dev/null +++ b/src/theme/MDXComponents/Code.tsx @@ -0,0 +1,27 @@ +import type { ComponentProps, ReactNode } from "react"; +import React from "react"; +import CodeInline from "@theme/CodeInline"; +import type { Props } from "@theme/MDXComponents/Code"; + +function shouldBeInline(props: Props) { + return ( + // empty code blocks have no props.children, + // see https://github.com/facebook/docusaurus/pull/9704 + typeof props.children !== "undefined" && + React.Children.toArray(props.children).every( + (el) => typeof el === "string" && !el.includes("\n"), + ) + ); +} + +function CodeBlock(props: ComponentProps<"code">): JSX.Element { + return ; +} + +export default function MDXCode(props: Props): ReactNode { + return shouldBeInline(props) ? ( + + ) : ( + )} /> + ); +} diff --git a/src/theme/MDXComponents/Pre.tsx b/src/theme/MDXComponents/Pre.tsx new file mode 100644 index 00000000..e779ba48 --- /dev/null +++ b/src/theme/MDXComponents/Pre.tsx @@ -0,0 +1,6 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/MDXComponents/Pre"; + +export default function MDXPre(props: Props): ReactNode | undefined { + return
;
+}
diff --git a/yarn.lock b/yarn.lock
index 504ed907..98c92fdb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4350,6 +4350,39 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@shikijs/core@npm:3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/core@npm:3.6.0"
+  dependencies:
+    "@shikijs/types": "npm:3.6.0"
+    "@shikijs/vscode-textmate": "npm:^10.0.2"
+    "@types/hast": "npm:^3.0.4"
+    hast-util-to-html: "npm:^9.0.5"
+  checksum: 10c0/cd12f225df50d6773a3005a8a86fcd1d3c7dcb3b90a01f97f2269ecdc5600f8f3df28181ee5f5bb6ff623090bfe7b98ac9353ae24691a84b0a34e127db6a5f13
+  languageName: node
+  linkType: hard
+
+"@shikijs/engine-javascript@npm:3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/engine-javascript@npm:3.6.0"
+  dependencies:
+    "@shikijs/types": "npm:3.6.0"
+    "@shikijs/vscode-textmate": "npm:^10.0.2"
+    oniguruma-to-es: "npm:^4.3.3"
+  checksum: 10c0/30a455f58ded393c6a8de22d090b64aeab06c0a063e71fccedc05de91900fa4bb691ffcaaf5d3799cf8a040849fe71685d3f1f3b29764e34111fd425310b924a
+  languageName: node
+  linkType: hard
+
+"@shikijs/engine-oniguruma@npm:3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/engine-oniguruma@npm:3.6.0"
+  dependencies:
+    "@shikijs/types": "npm:3.6.0"
+    "@shikijs/vscode-textmate": "npm:^10.0.2"
+  checksum: 10c0/2e3a1fb02d823be5d998a310fa2e5e34e92a7498996bed2ec52eb9368dbba99e10571619a364c406d79f3047b966fecc7afb29635a8fdfa6a25c9ee5cd8f2f34
+  languageName: node
+  linkType: hard
+
 "@shikijs/engine-oniguruma@npm:^1.24.2":
   version: 1.24.2
   resolution: "@shikijs/engine-oniguruma@npm:1.24.2"
@@ -4360,6 +4393,48 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@shikijs/langs@npm:3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/langs@npm:3.6.0"
+  dependencies:
+    "@shikijs/types": "npm:3.6.0"
+  checksum: 10c0/b904e230c5b4e1cd0c1c09d36b3704ac961b8b27802581ee375c2c1f92f5df5e93dc88ddf882be094fe42c3bd374c67e10f2f14d4974d0cb8e04efc8ba3492f0
+  languageName: node
+  linkType: hard
+
+"@shikijs/rehype@npm:^3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/rehype@npm:3.6.0"
+  dependencies:
+    "@shikijs/types": "npm:3.6.0"
+    "@types/hast": "npm:^3.0.4"
+    hast-util-to-string: "npm:^3.0.1"
+    shiki: "npm:3.6.0"
+    unified: "npm:^11.0.5"
+    unist-util-visit: "npm:^5.0.0"
+  checksum: 10c0/c31f77c0942d81ad42df0504f3cf653eacc3aaf0a37b6cf927a9b6009e47b8a3c3959581f36e5b2f139608b8f57d49ed974c8bec8b377fb680a4c5d629c47a4b
+  languageName: node
+  linkType: hard
+
+"@shikijs/themes@npm:3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/themes@npm:3.6.0"
+  dependencies:
+    "@shikijs/types": "npm:3.6.0"
+  checksum: 10c0/33be969bc56ea86590d63042a24ab715527f30f9488cca6fe17dfff67894ca1e75e83ddca9c9b5d449a8e8586ad014ace8b92b389e73be2c35280d6f805b87a3
+  languageName: node
+  linkType: hard
+
+"@shikijs/transformers@npm:^3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/transformers@npm:3.6.0"
+  dependencies:
+    "@shikijs/core": "npm:3.6.0"
+    "@shikijs/types": "npm:3.6.0"
+  checksum: 10c0/c08d65bffe2c484ce2e6b0b3016379833fa79d53e22fdfa575c10d0b6c3a35580742a2e9ef2032288178d87c99505544f62bde01434d79c87f0ff9b2c4f03fb6
+  languageName: node
+  linkType: hard
+
 "@shikijs/types@npm:1.24.2, @shikijs/types@npm:^1.24.2":
   version: 1.24.2
   resolution: "@shikijs/types@npm:1.24.2"
@@ -4370,6 +4445,23 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@shikijs/types@npm:3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/types@npm:3.6.0"
+  dependencies:
+    "@shikijs/vscode-textmate": "npm:^10.0.2"
+    "@types/hast": "npm:^3.0.4"
+  checksum: 10c0/5ea6246541b18e67bde854c4b72fb3aecde4a7da080be827645e4728945b782df9dc6498115085afbcf180e21e06e949bc3b8c3162233a2bdf4313979f49a6f7
+  languageName: node
+  linkType: hard
+
+"@shikijs/vscode-textmate@npm:^10.0.2":
+  version: 10.0.2
+  resolution: "@shikijs/vscode-textmate@npm:10.0.2"
+  checksum: 10c0/36b682d691088ec244de292dc8f91b808f95c89466af421cf84cbab92230f03c8348649c14b3251991b10ce632b0c715e416e992dd5f28ff3221dc2693fd9462
+  languageName: node
+  linkType: hard
+
 "@shikijs/vscode-textmate@npm:^9.3.0":
   version: 9.3.1
   resolution: "@shikijs/vscode-textmate@npm:9.3.1"
@@ -9278,6 +9370,25 @@ __metadata:
   languageName: node
   linkType: hard
 
+"hast-util-to-html@npm:^9.0.5":
+  version: 9.0.5
+  resolution: "hast-util-to-html@npm:9.0.5"
+  dependencies:
+    "@types/hast": "npm:^3.0.0"
+    "@types/unist": "npm:^3.0.0"
+    ccount: "npm:^2.0.0"
+    comma-separated-tokens: "npm:^2.0.0"
+    hast-util-whitespace: "npm:^3.0.0"
+    html-void-elements: "npm:^3.0.0"
+    mdast-util-to-hast: "npm:^13.0.0"
+    property-information: "npm:^7.0.0"
+    space-separated-tokens: "npm:^2.0.0"
+    stringify-entities: "npm:^4.0.0"
+    zwitch: "npm:^2.0.4"
+  checksum: 10c0/b7a08c30bab4371fc9b4a620965c40b270e5ae7a8e94cf885f43b21705179e28c8e43b39c72885d1647965fb3738654e6962eb8b58b0c2a84271655b4d748836
+  languageName: node
+  linkType: hard
+
 "hast-util-to-jsx-runtime@npm:^2.0.0":
   version: 2.3.0
   resolution: "hast-util-to-jsx-runtime@npm:2.3.0"
@@ -9323,6 +9434,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"hast-util-to-string@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "hast-util-to-string@npm:3.0.1"
+  dependencies:
+    "@types/hast": "npm:^3.0.0"
+  checksum: 10c0/b5fa1912a6ba6131affae52a0f4394406c4c0d23c2b0307f1d69988f1030c7bb830289303e67c5ad8f674f5f23a454c1dcd492c39e45a22c1f46d3c9bce5bd0c
+  languageName: node
+  linkType: hard
+
 "hast-util-to-text@npm:^2.0.1":
   version: 2.0.1
   resolution: "hast-util-to-text@npm:2.0.1"
@@ -11892,6 +12012,8 @@ __metadata:
     "@docusaurus/tsconfig": "npm:^3.7.0"
     "@docusaurus/types": "npm:^3.7.0"
     "@mdx-js/react": "npm:^3.1.0"
+    "@shikijs/rehype": "npm:^3.6.0"
+    "@shikijs/transformers": "npm:^3.6.0"
     clsx: "npm:^2.0.0"
     docusaurus-lunr-search: "npm:3.6.0"
     docusaurus-plugin-typedoc: "npm:^1.2.3"
@@ -11900,6 +12022,7 @@ __metadata:
     prism-react-renderer: "npm:^2.4.1"
     react: "npm:^18.0.0"
     react-dom: "npm:^18.0.0"
+    shiki: "npm:^3.6.0"
     typedoc: "npm:^0.27.9"
     typedoc-plugin-markdown: "npm:^4.4.2"
     typescript: "npm:~5.8.2"
@@ -12184,6 +12307,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"oniguruma-parser@npm:^0.12.1":
+  version: 0.12.1
+  resolution: "oniguruma-parser@npm:0.12.1"
+  checksum: 10c0/b843ea54cda833efb19f856314afcbd43e903ece3de489ab78c527ddec84859208052557daa9fad4bdba89ebdd15b0cc250de86b3daf8c7cbe37bac5a6a185d3
+  languageName: node
+  linkType: hard
+
+"oniguruma-to-es@npm:^4.3.3":
+  version: 4.3.3
+  resolution: "oniguruma-to-es@npm:4.3.3"
+  dependencies:
+    oniguruma-parser: "npm:^0.12.1"
+    regex: "npm:^6.0.1"
+    regex-recursion: "npm:^6.0.2"
+  checksum: 10c0/bc034e84dfee4dbc061cf6364023e66e1667fb8dc3afcad3b7d6a2c77e2d4a4809396ee2fb8c1fd3d6f00f76f7ca14b773586bf862c5f0c0074c059e2a219252
+  languageName: node
+  linkType: hard
+
 "open@npm:^8.0.9, open@npm:^8.4.0":
   version: 8.4.2
   resolution: "open@npm:8.4.2"
@@ -13580,6 +13721,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"property-information@npm:^7.0.0":
+  version: 7.1.0
+  resolution: "property-information@npm:7.1.0"
+  checksum: 10c0/e0fe22cff26103260ad0e82959229106563fa115a54c4d6c183f49d88054e489cc9f23452d3ad584179dc13a8b7b37411a5df873746b5e4086c865874bfa968e
+  languageName: node
+  linkType: hard
+
 "proto-list@npm:~1.2.1":
   version: 1.2.4
   resolution: "proto-list@npm:1.2.4"
@@ -13988,6 +14136,31 @@ __metadata:
   languageName: node
   linkType: hard
 
+"regex-recursion@npm:^6.0.2":
+  version: 6.0.2
+  resolution: "regex-recursion@npm:6.0.2"
+  dependencies:
+    regex-utilities: "npm:^2.3.0"
+  checksum: 10c0/68e8b6889680e904b75d7f26edaf70a1a4dc1087406bff53face4c2929d918fd77c72223843fe816ac8ed9964f96b4160650e8d5909e26a998c6e9de324dadb1
+  languageName: node
+  linkType: hard
+
+"regex-utilities@npm:^2.3.0":
+  version: 2.3.0
+  resolution: "regex-utilities@npm:2.3.0"
+  checksum: 10c0/78c550a80a0af75223244fff006743922591bd8f61d91fef7c86b9b56cf9bbf8ee5d7adb6d8991b5e304c57c90103fc4818cf1e357b11c6c669b782839bd7893
+  languageName: node
+  linkType: hard
+
+"regex@npm:^6.0.1":
+  version: 6.0.1
+  resolution: "regex@npm:6.0.1"
+  dependencies:
+    regex-utilities: "npm:^2.3.0"
+  checksum: 10c0/687b3e063d4ca19b0de7c55c24353f868a0fb9ba21512692470d2fb412e3a410894dd5924c91ea49d8cb8fa865e36ec956e52436ae0a256bdc095ff136c30aba
+  languageName: node
+  linkType: hard
+
 "regexpu-core@npm:^5.3.1":
   version: 5.3.2
   resolution: "regexpu-core@npm:5.3.2"
@@ -14688,6 +14861,22 @@ __metadata:
   languageName: node
   linkType: hard
 
+"shiki@npm:3.6.0, shiki@npm:^3.6.0":
+  version: 3.6.0
+  resolution: "shiki@npm:3.6.0"
+  dependencies:
+    "@shikijs/core": "npm:3.6.0"
+    "@shikijs/engine-javascript": "npm:3.6.0"
+    "@shikijs/engine-oniguruma": "npm:3.6.0"
+    "@shikijs/langs": "npm:3.6.0"
+    "@shikijs/themes": "npm:3.6.0"
+    "@shikijs/types": "npm:3.6.0"
+    "@shikijs/vscode-textmate": "npm:^10.0.2"
+    "@types/hast": "npm:^3.0.4"
+  checksum: 10c0/a643ce72a2b9dacb7f7238999d4d062d2ea81c39da96b182d7ffe69f0c5a902e00095dfec526a831e67897d709853d95c5d76d3502c3b4a11b8644e8013b67be
+  languageName: node
+  linkType: hard
+
 "side-channel@npm:^1.0.4, side-channel@npm:^1.0.6":
   version: 1.0.6
   resolution: "side-channel@npm:1.0.6"
@@ -15541,7 +15730,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"unified@npm:^11.0.0, unified@npm:^11.0.3, unified@npm:^11.0.4":
+"unified@npm:^11.0.0, unified@npm:^11.0.3, unified@npm:^11.0.4, unified@npm:^11.0.5":
   version: 11.0.5
   resolution: "unified@npm:11.0.5"
   dependencies:
@@ -16424,7 +16613,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"zwitch@npm:^2.0.0":
+"zwitch@npm:^2.0.0, zwitch@npm:^2.0.4":
   version: 2.0.4
   resolution: "zwitch@npm:2.0.4"
   checksum: 10c0/3c7830cdd3378667e058ffdb4cf2bb78ac5711214e2725900873accb23f3dfe5f9e7e5a06dcdc5f29605da976fc45c26d9a13ca334d6eea2245a15e77b8fc06e

From e7ebc131eb89f430a0476164fc0c11e91941b0da Mon Sep 17 00:00:00 2001
From: PiotrWodecki 
Date: Tue, 24 Jun 2025 13:21:32 +0200
Subject: [PATCH 02/56] Use Shiki/Twoslash for snippets

---
 docusaurus.config.ts             |   4 +-
 package.json                     |   3 +
 src/theme/MDXComponents/Code.tsx |   5 ++
 yarn.lock                        | 142 +++++++++++++++++++++++++++++--
 4 files changed, 146 insertions(+), 8 deletions(-)

diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index ddc83264..d51bcda6 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -10,15 +10,17 @@ import {
   transformerNotationFocus,
 } from "@shikijs/transformers";
 
+import { transformerTwoslash } from "@shikijs/twoslash";
+
 const rehypeShikiPlugin = [
   rehypeShiki,
   {
     themes: {
       light: "catppuccin-latte",
-      dark: "catppuccin-macchiato",
     },
     langs: Object.keys(bundledLanguages) as BundledLanguage[],
     transformers: [
+      transformerTwoslash(),
       transformerMetaHighlight(),
       transformerNotationDiff(),
       transformerNotationHighlight(),
diff --git a/package.json b/package.json
index 2a4d2872..3fcf2894 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
     "@mdx-js/react": "^3.1.0",
     "@shikijs/rehype": "^3.6.0",
     "@shikijs/transformers": "^3.6.0",
+    "@types/react-dom": "^19.1.6",
     "clsx": "^2.0.0",
     "docusaurus-lunr-search": "3.6.0",
     "prism-react-renderer": "^2.4.1",
@@ -39,6 +40,8 @@
     "@docusaurus/module-type-aliases": "^3.7.0",
     "@docusaurus/tsconfig": "^3.7.0",
     "@docusaurus/types": "^3.7.0",
+    "@fishjam-cloud/react-client": "^0.18.0",
+    "@shikijs/twoslash": "^3.6.0",
     "docusaurus-plugin-typedoc": "^1.2.3",
     "markdown-spellcheck": "^1.3.1",
     "prettier": "^3.5.3",
diff --git a/src/theme/MDXComponents/Code.tsx b/src/theme/MDXComponents/Code.tsx
index 8cf78395..878ece75 100644
--- a/src/theme/MDXComponents/Code.tsx
+++ b/src/theme/MDXComponents/Code.tsx
@@ -3,6 +3,11 @@ import React from "react";
 import CodeInline from "@theme/CodeInline";
 import type { Props } from "@theme/MDXComponents/Code";
 
+// imports for doc type checking purposes
+import "../../../packages/web-client-sdk/packages/react-client/src/types/public";
+import "../../../packages/mobile-client-sdk/packages/react-native-client/src/types";
+import "../../../packages/js-server-sdk/packages/js-server-sdk/src/types";
+
 function shouldBeInline(props: Props) {
   return (
     // empty code blocks have no props.children,
diff --git a/yarn.lock b/yarn.lock
index 98c92fdb..bbff52d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2858,6 +2858,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@binbat/whip-whep@npm:^1.1.1-sdp-trickle-throw":
+  version: 1.1.1-sdp-trickle-throw
+  resolution: "@binbat/whip-whep@npm:1.1.1-sdp-trickle-throw"
+  checksum: 10c0/073dc456898c51cccfa3bc766741d84b883881bf0dfa9b663bb3e42d5eba3d9843447c85892662d7c98221fdbb4e9d4858b56533241f919c5699b8bb54a059d7
+  languageName: node
+  linkType: hard
+
 "@braintree/sanitize-url@npm:^7.0.4":
   version: 7.1.1
   resolution: "@braintree/sanitize-url@npm:7.1.1"
@@ -2865,6 +2872,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@bufbuild/protobuf@npm:^2.2.3":
+  version: 2.5.2
+  resolution: "@bufbuild/protobuf@npm:2.5.2"
+  checksum: 10c0/30f0ede04b5318eda502759044329f44af27e0bebd9853d7d9baf5bcb4f4b17f813eb8904d98718d991bd56d33565ed18f8c9c65067626c3d4e55a4e039fe9b6
+  languageName: node
+  linkType: hard
+
 "@chevrotain/cst-dts-gen@npm:11.0.3":
   version: 11.0.3
   resolution: "@chevrotain/cst-dts-gen@npm:11.0.3"
@@ -4050,6 +4064,30 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@fishjam-cloud/react-client@npm:^0.18.0":
+  version: 0.18.0
+  resolution: "@fishjam-cloud/react-client@npm:0.18.0"
+  dependencies:
+    "@fishjam-cloud/ts-client": "npm:0.18.0"
+    events: "npm:3.3.0"
+    lodash.isequal: "npm:4.5.0"
+  checksum: 10c0/31e887f198985c659bf0b70dd4ca05e3dcb798556a61bbdaf224f4b5ae373ab68ef0f6124e1300b5273dbdabb523a4bd10e0feaa2441bbb35676d69ba4817073
+  languageName: node
+  linkType: hard
+
+"@fishjam-cloud/ts-client@npm:0.18.0":
+  version: 0.18.0
+  resolution: "@fishjam-cloud/ts-client@npm:0.18.0"
+  dependencies:
+    "@binbat/whip-whep": "npm:^1.1.1-sdp-trickle-throw"
+    "@bufbuild/protobuf": "npm:^2.2.3"
+    events: "npm:^3.3.0"
+    typed-emitter: "npm:^2.1.0"
+    uuid: "npm:^11.1.0"
+  checksum: 10c0/8c1cd19e4fba9ccd2727ac43850fa44cacabefb8a4e1c5459b6fd0c00cf71d2a29bb15fef93627730408ba3f60dc19067d9eccf79ed533580e407d59240450b3
+  languageName: node
+  linkType: hard
+
 "@gerrit0/mini-shiki@npm:^1.24.0":
   version: 1.24.3
   resolution: "@gerrit0/mini-shiki@npm:1.24.3"
@@ -4435,6 +4473,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@shikijs/twoslash@npm:^3.6.0":
+  version: 3.6.0
+  resolution: "@shikijs/twoslash@npm:3.6.0"
+  dependencies:
+    "@shikijs/core": "npm:3.6.0"
+    "@shikijs/types": "npm:3.6.0"
+    twoslash: "npm:^0.3.1"
+  peerDependencies:
+    typescript: ">=5.5.0"
+  checksum: 10c0/bdb447b1d657557d7f6f89d82fb70c9b47ae74ae864e34f51a231bc7b096320ad2cf6efd1217ad03e5deac14315f9e7fcc24ea872c962699176df229c9c38606
+  languageName: node
+  linkType: hard
+
 "@shikijs/types@npm:1.24.2, @shikijs/types@npm:^1.24.2":
   version: 1.24.2
   resolution: "@shikijs/types@npm:1.24.2"
@@ -5289,6 +5340,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/react-dom@npm:^19.1.6":
+  version: 19.1.6
+  resolution: "@types/react-dom@npm:19.1.6"
+  peerDependencies:
+    "@types/react": ^19.0.0
+  checksum: 10c0/7ba74eee2919e3f225e898b65fdaa16e54952aaf9e3472a080ddc82ca54585e46e60b3c52018d21d4b7053f09d27b8293e9f468b85f9932ff452cd290cc131e8
+  languageName: node
+  linkType: hard
+
 "@types/react-router-config@npm:*, @types/react-router-config@npm:^5.0.7":
   version: 5.0.11
   resolution: "@types/react-router-config@npm:5.0.11"
@@ -5432,6 +5492,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@typescript/vfs@npm:^1.6.1":
+  version: 1.6.1
+  resolution: "@typescript/vfs@npm:1.6.1"
+  dependencies:
+    debug: "npm:^4.1.1"
+  peerDependencies:
+    typescript: "*"
+  checksum: 10c0/3878686aff4bf26813dad9242aa8e01c5c9734f4d37f31035f93e9c8b850f15ec6a4480f04cf3a3a1cbf78a4e796ae1be5d6c54f7f7c91556eafee913a8d0da4
+  languageName: node
+  linkType: hard
+
 "@ungap/structured-clone@npm:^1.0.0":
   version: 1.2.0
   resolution: "@ungap/structured-clone@npm:1.2.0"
@@ -8427,7 +8498,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"events@npm:^3.2.0":
+"events@npm:3.3.0, events@npm:^3.2.0, events@npm:^3.3.0":
   version: 3.3.0
   resolution: "events@npm:3.3.0"
   checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6
@@ -10692,6 +10763,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"lodash.isequal@npm:4.5.0":
+  version: 4.5.0
+  resolution: "lodash.isequal@npm:4.5.0"
+  checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f
+  languageName: node
+  linkType: hard
+
 "lodash.memoize@npm:^4.1.2":
   version: 4.1.2
   resolution: "lodash.memoize@npm:4.1.2"
@@ -12011,9 +12089,12 @@ __metadata:
     "@docusaurus/theme-mermaid": "npm:^3.7.0"
     "@docusaurus/tsconfig": "npm:^3.7.0"
     "@docusaurus/types": "npm:^3.7.0"
+    "@fishjam-cloud/react-client": "npm:^0.18.0"
     "@mdx-js/react": "npm:^3.1.0"
     "@shikijs/rehype": "npm:^3.6.0"
     "@shikijs/transformers": "npm:^3.6.0"
+    "@shikijs/twoslash": "npm:^3.6.0"
+    "@types/react-dom": "npm:^19.1.6"
     clsx: "npm:^2.0.0"
     docusaurus-lunr-search: "npm:3.6.0"
     docusaurus-plugin-typedoc: "npm:^1.2.3"
@@ -14563,6 +14644,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"rxjs@npm:*":
+  version: 7.8.2
+  resolution: "rxjs@npm:7.8.2"
+  dependencies:
+    tslib: "npm:^2.1.0"
+  checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45
+  languageName: node
+  linkType: hard
+
 "safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1":
   version: 5.1.2
   resolution: "safe-buffer@npm:5.1.2"
@@ -15578,6 +15668,32 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tslib@npm:^2.1.0":
+  version: 2.8.1
+  resolution: "tslib@npm:2.8.1"
+  checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62
+  languageName: node
+  linkType: hard
+
+"twoslash-protocol@npm:0.3.1":
+  version: 0.3.1
+  resolution: "twoslash-protocol@npm:0.3.1"
+  checksum: 10c0/626dc624448033c52a0231fcfb751677c9a11a70fa39728177bd7f6fb90134ee5807d3553d0fb3c89cae2a68baf8f54d6013f64487b5ac3ea9b6c43c8b2c9d39
+  languageName: node
+  linkType: hard
+
+"twoslash@npm:^0.3.1":
+  version: 0.3.1
+  resolution: "twoslash@npm:0.3.1"
+  dependencies:
+    "@typescript/vfs": "npm:^1.6.1"
+    twoslash-protocol: "npm:0.3.1"
+  peerDependencies:
+    typescript: ^5.5.0
+  checksum: 10c0/30f924cfb3043b1aa3cb0b1fbda9d25f8b74c1e82adb0cd66e991b1e5872e8648ede5f77fad06b91389e16788c25126b2f5c294c39e618587743ebf7160573cc
+  languageName: node
+  linkType: hard
+
 "type-fest@npm:^0.21.3":
   version: 0.21.3
   resolution: "type-fest@npm:0.21.3"
@@ -15609,6 +15725,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"typed-emitter@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "typed-emitter@npm:2.1.0"
+  dependencies:
+    rxjs: "npm:*"
+  dependenciesMeta:
+    rxjs:
+      optional: true
+  checksum: 10c0/01fc354ba8e87bd39b1bf4fe1c96fe7ecff7fde83161003b0f8c7f4b285a368052e185ba655dd8c102c4445301b7a1e032c8972f181b440fc95bd810450f1314
+  languageName: node
+  linkType: hard
+
 "typedarray-to-buffer@npm:^3.1.5":
   version: 3.1.5
   resolution: "typedarray-to-buffer@npm:3.1.5"
@@ -15652,22 +15780,22 @@ __metadata:
   linkType: hard
 
 "typescript@npm:~5.8.2":
-  version: 5.8.2
-  resolution: "typescript@npm:5.8.2"
+  version: 5.8.3
+  resolution: "typescript@npm:5.8.3"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6
+  checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48
   languageName: node
   linkType: hard
 
 "typescript@patch:typescript@npm%3A~5.8.2#optional!builtin":
-  version: 5.8.2
-  resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5"
+  version: 5.8.3
+  resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2
+  checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
   languageName: node
   linkType: hard
 

From 135c7dc2908a0cf723fc6f3e27f174d823a09f5f Mon Sep 17 00:00:00 2001
From: PiotrWodecki 
Date: Wed, 25 Jun 2025 14:20:50 +0200
Subject: [PATCH 03/56] Fix all type errors in docs apart from api ref

---
 docs/audio-calls.mdx                  |    1 +
 docs/livestreaming.mdx                |    2 +
 docs/production/examples/fastify.mdx  |   26 +-
 docs/production/server.mdx            |   10 +-
 docs/react-native/background.mdx      |    9 +
 docs/react-native/connecting.mdx      |    6 +-
 docs/react-native/installation.mdx    |    1 +
 docs/react-native/list-other.mdx      |    1 +
 docs/react-native/livestreaming.mdx   |    5 +-
 docs/react-native/metadata.mdx        |   18 +-
 docs/react-native/quick-setup.mdx     |    9 +-
 docs/react-native/reconnect.mdx       |    9 +-
 docs/react-native/screensharing.mdx   |    2 +-
 docs/react-native/start-streaming.mdx |    6 +-
 docs/react/connecting.mdx             |   19 +-
 docs/react/custom-sources.mdx         |   19 +-
 docs/react/installation.mdx           |    1 +
 docs/react/list-other.mdx             |    2 +
 docs/react/livestreaming.mdx          |   10 +-
 docs/react/managing-devices.mdx       |    3 +
 docs/react/metadata.mdx               |    6 +-
 docs/react/start-streaming.mdx        |    4 +-
 docs/react/stream-middleware.mdx      |    3 +-
 package.json                          |    6 +
 yarn.lock                             | 2712 ++++++++++++++++++++++++-
 25 files changed, 2732 insertions(+), 158 deletions(-)

diff --git a/docs/audio-calls.mdx b/docs/audio-calls.mdx
index bdac4017..9d0789a9 100644
--- a/docs/audio-calls.mdx
+++ b/docs/audio-calls.mdx
@@ -27,6 +27,7 @@ Using this feature is as easy as setting the `roomType` field to `audio_only` wh
   
   
     ```ts
+    // @errors: 2304
     const createdRoom = await fishjamClient.createRoom({ roomType: 'audio_only' });
     ```
 
diff --git a/docs/livestreaming.mdx b/docs/livestreaming.mdx
index fa185873..9944cd4d 100644
--- a/docs/livestreaming.mdx
+++ b/docs/livestreaming.mdx
@@ -29,6 +29,7 @@ https://fishjam.io/api/v1/connect/${ROOM_MANAGER_ID}/room-manager?roomName=foo&p
   
   
     ```ts
+    // @errors: 2304
     const createdRoom = await fishjamClient.createRoom({ roomType: 'livestream' });
 
     const peer = await fishjamClient.createPeer(createdRoom.id)
@@ -71,6 +72,7 @@ https://fishjam.io/api/v1/connect/${ROOM_MANAGER_ID}/room-manager//li
   
   
     ```ts
+    // @errors: 2304
     const viewerToken = await fishjamClient.createLivestreamViewerToken(room.id)
     ```
 
diff --git a/docs/production/examples/fastify.mdx b/docs/production/examples/fastify.mdx
index 11cb8859..c3f1105c 100644
--- a/docs/production/examples/fastify.mdx
+++ b/docs/production/examples/fastify.mdx
@@ -16,7 +16,7 @@ If you wish to see more general info, visit [**set up your server**](/production
 Use [`@fastify/env` package](https://github.com/fastify/fastify-env) to load and set environment variables in your Fastify instance.
 
 ```ts title='main.ts'
-import { Fastify } from "fastify";
+import Fastify from "fastify";
 import fastifyEnv from "@fastify/env";
 
 const fastify = Fastify();
@@ -49,6 +49,7 @@ This will provide types of FishjamClient wherever you access `fastify.fishjam` i
 Then, declare the plugin by invoking the `fp` function with the setup function with as an argument.
 
 ```ts title='fishjamPlugin.ts'
+// @errors: 2339
 import fastifyPlugin from "fastify-plugin";
 import { FishjamClient } from "@fishjam-cloud/js-server-sdk";
 
@@ -71,7 +72,8 @@ export const fishjamPlugin = fastifyPlugin((fastify) => {
 Now, after registering the plugin, we will be able to use Fishjam client by accessing the `fastify.fishjam` property.
 
 ```ts title='main.ts'
-import { Fastify } from "fastify";
+// @errors: 2307 2339
+import Fastify from "fastify";
 import fastifyEnv from "@fastify/env";
 
 import { fishjamPlugin } from "./fishjamPlugin";
@@ -97,7 +99,7 @@ To receive and parse the Fishjam protobuf messages, add a content type parser to
 Then, you will be able to access the parsed message at `request.Body`.
 
 ```ts title='main.ts'
-import { Fastify } from "fastify";
+import Fastify, { FastifyRequest } from "fastify";
 import { ServerMessage } from "@fishjam-cloud/js-server-sdk/proto";
 
 const fastify = Fastify();
@@ -105,12 +107,12 @@ const fastify = Fastify();
 fastify.addContentTypeParser(
   "application/x-protobuf",
   { parseAs: "buffer" },
-  async (_: FastifyRequest, body: Buffer) => ServerMessage.decode(body),
+  async (_: FastifyRequest, body: Buffer) => ServerMessage.decode(new Uint8Array(body)),
 );
 
 fastify.post<{ Body: ServerMessage }>("/fishjam-webhook", (request) => {
   // handle the message
-  console.log(request.Body);
+  console.log(request.body);
 });
 ```
 
@@ -122,6 +124,7 @@ Let's create another plugin in `fishjamNotifierPlugin.ts` file.
 In this case, we don't need to extend the Fastify instance type, because the plugin doesn't decorate the Fastify instance with any properties. It will work in the background.
 
 ```ts title='fishjamNotifierPlugin.ts'
+// @errors: 2339
 import { type FastifyInstance } from "fastify";
 import fp from "fastify-plugin";
 import { FishjamWSNotifier } from "@fishjam-cloud/js-server-sdk";
@@ -132,24 +135,25 @@ export const fishjamNotifierPlugin = fp((fastify) => {
 
   const fishjamNotifier = new FishjamWSNotifier(
     { fishjamUrl, managementToken },
-    onError: (err) => fastify.log.error(err),
-    onClose: () => fastify.log.info("Websocket connection to Fishjam closed"),
-    onConnectionFailed: () => fastify.log.error("Failed to connect Fishjam notifier")
+    (err) => fastify.log.error(err),
+    () => fastify.log.info("Websocket connection to Fishjam closed"),
+    () => fastify.log.error("Failed to connect Fishjam notifier")
   );
 
   // handle the messages
   const handleRoomCreated = console.log;
-  const handlePeerCreated = console.log;
+  const handlePeerAdded = console.log;
 
   fishjamNotifier.on("roomCreated", handleRoomCreated);
-  fishjamNotifier.on("peerCreated", handlePeerCreated);
+  fishjamNotifier.on("peerAdded", handlePeerAdded);
 });
 ```
 
 Don't forget to register your plugin.
 
 ```ts title='main.ts'
-import { Fastify } from "fastify";
+// @errors: 2307
+import Fastify from "fastify";
 import fastifyEnv from "@fastify/env";
 
 import { fishjamPlugin } from "./fishjamPlugin";
diff --git a/docs/production/server.mdx b/docs/production/server.mdx
index e0715e3f..75fa0877 100644
--- a/docs/production/server.mdx
+++ b/docs/production/server.mdx
@@ -56,6 +56,7 @@ They are required to proceed. Now, we are ready to dive into the code.
   
   
     ```ts
+    // @errors: 2322
     import { FishjamClient } from '@fishjam-cloud/js-server-sdk';
 
     const fishjamUrl = process.env.FISHJAM_URL;
@@ -89,6 +90,7 @@ Create a room to get the `roomId` and be able to start adding peers.
   
   
     ```ts
+    // @errors: 2304
     const createdRoom = await fishjamClient.createRoom();
 
     const theSameRoom = await fishjamClient.getRoom(createdRoom.id);
@@ -125,9 +127,10 @@ At any time you can terminate user's access by deleting the peer.
   
   
     ```ts
-    const { peer, peerToken } = await fishjamClient.createPeer(roomId);
+    // @errors: 2304
+    const { peer, peerToken } = await fishjamClient.createPeer(created_room.id);
 
-    await fishjamClient.deletePeer(roomId, peer.id);
+    await fishjamClient.deletePeer(created_room.id, peer.id);
     ```
 
   
@@ -153,6 +156,7 @@ or [web SDK](/react/metadata). This metadata can be only set when creating the p
   
   
     ```ts
+    // @errors: 2304
     const { peer, peerToken } = await fishjamClient.createPeer(roomId, {
       metadata: { realName: 'Keanu Reeves' },
     });
@@ -186,6 +190,7 @@ Simply pass the your webhook url as a `webhookUrl` parameter when creating a roo
   
   
     ```ts
+    // @errors: 2304
     const webhookUrl = "https://example.com/";
     await fishjamClient.createRoom({ webhookUrl });
     ```
@@ -215,6 +220,7 @@ It sets up a websocket connection with a Fishjam instance and provides a simple
   
   
     ```ts
+    // @errors: 18004
     import { FishjamWSNotifier } from '@fishjam-cloud/js-server-sdk';
 
     const onClose = console.log;
diff --git a/docs/react-native/background.mdx b/docs/react-native/background.mdx
index 75640809..40d5dc7b 100644
--- a/docs/react-native/background.mdx
+++ b/docs/react-native/background.mdx
@@ -72,6 +72,15 @@ granted and only then allow to start a service.
 :::
 
 ```tsx
+import {
+  useForegroundService,
+  useCamera,
+  useMicrophone,
+} from "@fishjam-cloud/react-native-client";
+
+const { isCameraOn } = useCamera();
+const { isMicrophoneOn } = useMicrophone();
+
 useForegroundService({
   channelId: "io.fishjam.example.fishjamchat.foregroundservice.channel",
   channelName: "Fishjam Chat Notifications",
diff --git a/docs/react-native/connecting.mdx b/docs/react-native/connecting.mdx
index 1f3df195..4c0bf32c 100644
--- a/docs/react-native/connecting.mdx
+++ b/docs/react-native/connecting.mdx
@@ -25,6 +25,7 @@ the Room's Peer, and return the token required to use that Room.
 To use that, simply call `fetch`:
 
 ```ts
+// @errors: 2304
 const response = await fetch(
   `https://fishjam.io/api/v1/connect/${ROOM_MANAGER_ID}room-manager/?roomName=${roomName}&peerName=${peerName}`,
 );
@@ -46,7 +47,8 @@ follow our [server setup instructions](/production/server).
 In order to connect, call [`joinRoom`](/api/mobile/functions/useConnection#joinroom) method with data from the previous step:
 
 ```tsx
-import { useCallback } from "react";
+// @errors: 2304
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useConnection } from "@fishjam-cloud/react-native-client";
 
@@ -82,7 +84,7 @@ async function getRoomDetails(roomName: string, peerName: string) {
 In order to close the connection, you have to call [`leaveRoom`](/api/mobile/functions/useConnection#leaveroom) method.
 
 ```tsx
-import { useCallback } from "react";
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useConnection } from "@fishjam-cloud/react-native-client";
 
diff --git a/docs/react-native/installation.mdx b/docs/react-native/installation.mdx
index 30755829..718367d9 100644
--- a/docs/react-native/installation.mdx
+++ b/docs/react-native/installation.mdx
@@ -46,6 +46,7 @@ Before using the camera or microphone, ask the user for permissions.
 
 ```tsx
 import { useCameraPermissions } from "expo-camera";
+import React, { useEffect } from "react";
 
 const [permission, requestPermission] = useCameraPermissions();
 
diff --git a/docs/react-native/list-other.mdx b/docs/react-native/list-other.mdx
index 498493c7..01740101 100644
--- a/docs/react-native/list-other.mdx
+++ b/docs/react-native/list-other.mdx
@@ -10,6 +10,7 @@ other peers, together with the tracks that they are streaming.
 ### Example code that show all videos
 
 ```tsx
+import React from "react";
 import { View } from "react-native";
 import {
   usePeers,
diff --git a/docs/react-native/livestreaming.mdx b/docs/react-native/livestreaming.mdx
index a5429525..aafbc092 100644
--- a/docs/react-native/livestreaming.mdx
+++ b/docs/react-native/livestreaming.mdx
@@ -62,7 +62,7 @@ This guide assumes you already finished either the [Quick Setup](/react-native/q
 Here is an example of a simple component that immediately streams the front camera:
 
 ```tsx
-import { useEffect } from "react";
+import React, { useEffect } from "react";
 import {
   useCamera,
   useConnection,
@@ -118,7 +118,8 @@ Here is an example of a component that just receives a livestream.
 The `LivestreamView` component will render the video stream once you connect to Fishjam using the connect method from [`useLivestream`](/api/mobile/functions/useLivestream).
 
 ```tsx
-import { useCallback, useEffect } from "react";
+// @errors: 2304
+import React, { useCallback, useEffect } from "react";
 import { View } from "react-native";
 import {
   useLivestream,
diff --git a/docs/react-native/metadata.mdx b/docs/react-native/metadata.mdx
index 03b3e5fb..9eecdf9c 100644
--- a/docs/react-native/metadata.mdx
+++ b/docs/react-native/metadata.mdx
@@ -14,7 +14,8 @@ import ReadingMetadata from "../_common/metadata/reading.mdx";
 
 
 ```tsx
-import { useCallback } from "react";
+// @errors: 2304
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useConnection } from "@fishjam-cloud/react-native-client";
 
@@ -43,7 +44,7 @@ export function JoinRoomButton() {
 
 
 ```tsx
-import { useCallback } from "react";
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useUpdatePeerMetadata } from "@fishjam-cloud/react-native-client";
 
@@ -69,6 +70,7 @@ export function UpdateNameButton() {
 
 
 ```tsx
+import React from "react";
 import { Text, View } from "react-native";
 import { usePeers } from "@fishjam-cloud/react-native-client";
 
@@ -76,17 +78,21 @@ type PeerMetadata = {
   displayName: string;
 };
 
+type ServerMetadata = {
+  realName: string;
+};
+
 export function ListAllNames() {
   // highlight-next-line
-  const { remotePeers } = usePeers();
+  const { remotePeers } = usePeers();
 
   return (
     
       {remotePeers.map((peer) => (
         // highlight-start
-        
-          Display name: {peer.metadata.peer.displayName}
-          Real name: {peer.metadata.server.realName}
+        
+          Display name: {peer.metadata.peer?.displayName || 'Unknown'}
+          Real name: {peer.metadata.server?.realName || 'Unknown'}
         
         // highlight-end
       ))}
diff --git a/docs/react-native/quick-setup.mdx b/docs/react-native/quick-setup.mdx
index 60183f6f..aba69883 100644
--- a/docs/react-native/quick-setup.mdx
+++ b/docs/react-native/quick-setup.mdx
@@ -43,6 +43,7 @@ In a few simple steps, you will be able to implement a simple video call functio
 Use your Room Manager URL to fetch a peer token to get a new room:
 
 ```ts
+// @errors: 2304
 const response = await fetch(
   `https://fishjam.io/api/v1/connect/${ROOM_MANAGER_ID}/room-manager/?roomName=${roomName}&peerName=${peerName}`,
 );
@@ -68,7 +69,8 @@ Keep in mind that this won't work on the iOS Simulator, as the Simulator can't a
 To start streaming, you have to prepare your camera and join the room:
 
 ```tsx
-import { useCallback } from "react";
+// @errors: 2304
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useCamera, useConnection } from "@fishjam-cloud/react-native-client";
 
@@ -106,6 +108,7 @@ export function StartStreamingButton({
 Once you are connected, you can check the connection status with [`useConnection`](/api/mobile/functions/useConnection) hook
 
 ```ts
+// @errors: 2304
 const { peerStatus } = useConnection();
 ```
 
@@ -126,6 +129,7 @@ import {
   VideoRendererView,
 } from "@fishjam-cloud/react-native-client";
 import { View } from "react-native";
+import React from "react";
 
 export function TracksView() {
   const { remotePeers } = usePeers();
@@ -153,7 +157,8 @@ export function TracksView() {
 Here is how it all could work for a minimal, working example:
 
 ```tsx
-import { useEffect, useState } from "react";
+// @errors: 2304
+import React, { useEffect, useState } from "react";
 import { FishjamRoom } from "@fishjam-cloud/react-native-client";
 
 type RoomData = {
diff --git a/docs/react-native/reconnect.mdx b/docs/react-native/reconnect.mdx
index 4c1272d4..9b06b543 100644
--- a/docs/react-native/reconnect.mdx
+++ b/docs/react-native/reconnect.mdx
@@ -7,20 +7,19 @@ sidebar_position: 7
 If your connection is lost while you are connected to a room, the app will automatically handle the reconnection process.
 You can monitor these events by utilizing the [`useConnection`](/api/mobile/functions/useConnection) hook.
 
-### Example hook that logs the current status to the console:
+### Example hook that logs the current status to the console
 
 ```ts
 import { useEffect, useRef } from "react";
 import {
-  ConnectionStatus,
+  ReconnectionStatus,
   useConnection,
 } from "@fishjam-cloud/react-native-client";
 
 export function useLogConnectionStatus() {
-  const prevStatus = useRef("idle");
+  const prevStatus = useRef("idle");
 
-  // highlight-next-line
-  const { reconnectionStatus } = useConnection();
+  const { reconnectionStatus } = useConnection(); // [!code highlight]
 
   useEffect(() => {
     if (prevStatus.current === reconnectionStatus) return;
diff --git a/docs/react-native/screensharing.mdx b/docs/react-native/screensharing.mdx
index 559587b4..e988eebe 100644
--- a/docs/react-native/screensharing.mdx
+++ b/docs/react-native/screensharing.mdx
@@ -169,7 +169,7 @@ You can enable/disable screen sharing with [`toggleScreenShare`](/api/mobile/fun
 And check current state with [`isScreenShareOn`](/api/mobile/functions/useScreenShare#isscreenshareon) property.
 
 ```tsx
-import { useCallback } from "react";
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useScreenShare } from "@fishjam-cloud/react-native-client";
 
diff --git a/docs/react-native/start-streaming.mdx b/docs/react-native/start-streaming.mdx
index 159e18d6..5b2387ac 100644
--- a/docs/react-native/start-streaming.mdx
+++ b/docs/react-native/start-streaming.mdx
@@ -20,7 +20,7 @@ First, you have to enable your camera by calling [`prepareCamera`](/api/mobile/f
 You can open show camera preview with [`VideoPreviewView`](/api/mobile/functions/VideoPreviewView) component
 
 ```tsx
-import { useEffect } from "react";
+import React, { useEffect } from "react";
 import {
   useCamera,
   VideoPreviewView,
@@ -47,7 +47,7 @@ This way, you can either automatically choose camera (front/back) or allow user
 To change camera, simply call [`switchCamera`](/api/mobile/functions/useCamera#switchcamera) method.
 
 ```tsx
-import { useCallback } from "react";
+import React, { useCallback } from "react";
 import { Button } from "react-native";
 import { useCamera } from "@fishjam-cloud/react-native-client";
 
@@ -76,6 +76,7 @@ To change camera state, you use [`toggleCamera`](/api/mobile/functions/useCamera
 
 ```tsx
 import { Button } from "react-native";
+import React from "react";
 import { useCamera } from "@fishjam-cloud/react-native-client";
 
 export function ToggleCameraButton() {
@@ -98,6 +99,7 @@ Microphone works similar to camera. In order to enable it, you have to call [`to
 
 ```tsx
 import { Button } from "react-native";
+import React from "react";
 import { useMicrophone } from "@fishjam-cloud/react-native-client";
 
 export function ToggleMicrophoneButton() {
diff --git a/docs/react/connecting.mdx b/docs/react/connecting.mdx
index c073c354..3ac9ffc9 100644
--- a/docs/react/connecting.mdx
+++ b/docs/react/connecting.mdx
@@ -14,19 +14,19 @@ Use the [`useConnection`](/api/web/functions/useConnection) hook to get
 the [`joinRoom`](/api/web/functions/useConnection#joinroom) function.
 
 ```tsx
+// @errors: 2304
 import { useConnection } from "@fishjam-cloud/react-client";
-import { useCallback } from "react";
+import React, { useCallback } from "react";
 
 export function JoinRoomButton() {
-  // highlight-next-line
-  const { joinRoom } = useConnection();
+  const { joinRoom } = useConnection(); // [!code highlight]
 
   const onJoinRoomPress = useCallback(async () => {
-    // highlight-next-line
     await joinRoom({
-      url: FISHJAM_URL,
-      peerToken: PEER_TOKEN,
-    });
+      // [!code highlight]
+      url: FISHJAM_URL, // [!code highlight]
+      peerToken: PEER_TOKEN, // [!code highlight]
+    }); // [!code highlight]
   }, [joinRoom]);
 
   return ;
@@ -38,9 +38,9 @@ export function JoinRoomButton() {
 In order to close connection, use the [`leaveRoom`](/api/web/functions/useConnection#leaveroom) method
 from [`useConnection`](/api/web/functions/useConnection) hook.
 
-```ts
+```tsx
 import { useConnection } from "@fishjam-cloud/react-client";
-import { useCallback } from "react";
+import React, { useCallback } from "react";
 
 export function LeaveRoomButton() {
   // highlight-next-line
@@ -48,5 +48,4 @@ export function LeaveRoomButton() {
 
   return ;
 }
-
 ```
diff --git a/docs/react/custom-sources.mdx b/docs/react/custom-sources.mdx
index 7302e74b..004673fd 100644
--- a/docs/react/custom-sources.mdx
+++ b/docs/react/custom-sources.mdx
@@ -27,17 +27,19 @@ To create a custom source, you only need to do two things:
 #### Usage Example
 
 ```tsx
+// @errors: 1109
+import React, { useEffect } from "react";
 import { useCustomSource } from "@fishjam-cloud/react-client";
 
 export function CameraControl() {
-  const stream: MediaStream = ...
+  const stream: MediaStream = ...;
   const { setStream } = useCustomSource("my-custom-source");
 
   useEffect(() => {
     setStream(stream);
   }, [stream, setStream]);
 
-  ...
+  // ...
 }
 ```
 
@@ -53,8 +55,9 @@ If you wish to remove a custom source, then you should call the [`setStream`](/a
 #### Usage Example
 
 ```tsx
+// @errors: 2304
 const { setStream } = useCustomSource("my-custom-source");
-...
+// ...
 await setStream(null);
 ```
 
@@ -71,7 +74,7 @@ This is particularly useful if you are using [Three.js](https://threejs.org/) or
 #### Usage Example
 
 ```tsx
-import { useCallback, useState } from "react";
+import React, { useCallback, useState } from "react";
 
 export function CanvasExample() {
   const [canvasStream, setCanvasStream] = useState();
@@ -100,6 +103,8 @@ If you want to see a full example React app which uses Fishjam with Smelter, the
 :::
 
 ```tsx
+// @errors: 2304
+import React from "react";
 const { stream } = await smelter.registerOutput("example-output", , {
   type: "stream",
   video: {
@@ -118,7 +123,8 @@ If you have a `