diff --git a/package-lock.json b/package-lock.json
index 8d8284b34e..fec30c09e2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,12 +24,15 @@
"bottleneck": "^2.19.5",
"express": "^5.2.1",
"fetch-socks": "^1.3.2",
+ "fuse.js": "^7.3.0",
"http-proxy-middleware": "^3.0.5",
"https-proxy-agent": "^9.0.0",
"jose": "^6.1.3",
"js-yaml": "^4.1.0",
"jsonc-parser": "^3.3.1",
"lowdb": "^7.0.1",
+ "lucide-react": "^1.14.0",
+ "mermaid": "^11.14.0",
"monaco-editor": "^0.55.1",
"next": "^16.2.3",
"next-intl": "^4.11.0",
@@ -76,6 +79,8 @@
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint-config-next": "16.2.4",
+ "glob": "^13.0.6",
+ "gray-matter": "^4.0.3",
"husky": "^9.1.7",
"jsdom": "^29.1.1",
"lint-staged": "^16.2.7",
@@ -95,6 +100,32 @@
"keytar": "^7.9.0"
}
},
+ "../omniroute-docs-site": {
+ "name": "docs-site",
+ "version": "0.1.0",
+ "extraneous": true,
+ "dependencies": {
+ "@mdx-js/loader": "^3.1.1",
+ "@mdx-js/react": "^3.1.1",
+ "@next/mdx": "^16.2.4",
+ "fuse.js": "^7.3.0",
+ "next": "16.2.4",
+ "react": "19.2.4",
+ "react-dom": "19.2.4"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.2.4",
+ "glob": "^13.0.6",
+ "gray-matter": "^4.0.3",
+ "tailwindcss": "^4",
+ "typescript": "^5"
+ }
+ },
"node_modules/@adobe/css-tools": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
@@ -134,6 +165,19 @@
"react-dom": ">=16.0.0"
}
},
+ "node_modules/@antfu/install-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz",
+ "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "package-manager-detector": "^1.3.0",
+ "tinyexec": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/@asamuzakjp/css-color": {
"version": "5.1.11",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
@@ -441,6 +485,12 @@
"node": ">=18"
}
},
+ "node_modules/@braintree/sanitize-url": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
+ "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==",
+ "license": "MIT"
+ },
"node_modules/@bramus/specificity": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
@@ -454,6 +504,43 @@
"specificity": "bin/cli.js"
}
},
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz",
+ "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "12.0.0",
+ "@chevrotain/types": "12.0.0"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz",
+ "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "12.0.0"
+ }
+ },
+ "node_modules/@chevrotain/regexp-to-ast": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz",
+ "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz",
+ "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz",
+ "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==",
+ "license": "Apache-2.0"
+ },
"node_modules/@csstools/color-helpers": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
@@ -1550,6 +1637,23 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "license": "MIT"
+ },
+ "node_modules/@iconify/utils": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.1.tgz",
+ "integrity": "sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==",
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/install-pkg": "^1.1.0",
+ "@iconify/types": "^2.0.0",
+ "mlly": "^1.8.2"
+ }
+ },
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
@@ -2102,6 +2206,15 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/@mermaid-js/parser": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz",
+ "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==",
+ "license": "MIT",
+ "dependencies": {
+ "langium": "^4.0.0"
+ }
+ },
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
@@ -4125,24 +4238,159 @@
"assertion-error": "^2.0.1"
}
},
+ "node_modules/@types/d3": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
+ "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/d3-axis": "*",
+ "@types/d3-brush": "*",
+ "@types/d3-chord": "*",
+ "@types/d3-color": "*",
+ "@types/d3-contour": "*",
+ "@types/d3-delaunay": "*",
+ "@types/d3-dispatch": "*",
+ "@types/d3-drag": "*",
+ "@types/d3-dsv": "*",
+ "@types/d3-ease": "*",
+ "@types/d3-fetch": "*",
+ "@types/d3-force": "*",
+ "@types/d3-format": "*",
+ "@types/d3-geo": "*",
+ "@types/d3-hierarchy": "*",
+ "@types/d3-interpolate": "*",
+ "@types/d3-path": "*",
+ "@types/d3-polygon": "*",
+ "@types/d3-quadtree": "*",
+ "@types/d3-random": "*",
+ "@types/d3-scale": "*",
+ "@types/d3-scale-chromatic": "*",
+ "@types/d3-selection": "*",
+ "@types/d3-shape": "*",
+ "@types/d3-time": "*",
+ "@types/d3-time-format": "*",
+ "@types/d3-timer": "*",
+ "@types/d3-transition": "*",
+ "@types/d3-zoom": "*"
+ }
+ },
"node_modules/@types/d3-array": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
"license": "MIT"
},
+ "node_modules/@types/d3-axis": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
+ "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-brush": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
+ "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-chord": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
+ "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
+ "license": "MIT"
+ },
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
"license": "MIT"
},
+ "node_modules/@types/d3-contour": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
+ "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-array": "*",
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-dispatch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz",
+ "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-drag": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
+ "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-dsv": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
+ "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
+ "license": "MIT"
+ },
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
"license": "MIT"
},
+ "node_modules/@types/d3-fetch": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
+ "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-dsv": "*"
+ }
+ },
+ "node_modules/@types/d3-force": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
+ "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-format": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
+ "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-geo": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
+ "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/geojson": "*"
+ }
+ },
+ "node_modules/@types/d3-hierarchy": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
+ "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
+ "license": "MIT"
+ },
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
@@ -4158,6 +4406,24 @@
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
"license": "MIT"
},
+ "node_modules/@types/d3-polygon": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
+ "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-quadtree": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
+ "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-random": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
+ "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
+ "license": "MIT"
+ },
"node_modules/@types/d3-scale": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
@@ -4167,6 +4433,18 @@
"@types/d3-time": "*"
}
},
+ "node_modules/@types/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-selection": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
+ "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
+ "license": "MIT"
+ },
"node_modules/@types/d3-shape": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
@@ -4182,12 +4460,37 @@
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
"license": "MIT"
},
+ "node_modules/@types/d3-time-format": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
+ "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
+ "license": "MIT"
+ },
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
+ "node_modules/@types/d3-transition": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
+ "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/d3-zoom": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
+ "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
@@ -4219,6 +4522,12 @@
"@types/estree": "*"
}
},
+ "node_modules/@types/geojson": {
+ "version": "7946.0.16",
+ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
+ "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
+ "license": "MIT"
+ },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@@ -4908,6 +5217,16 @@
"win32"
]
},
+ "node_modules/@upsetjs/venn.js": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz",
+ "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "d3-selection": "^3.0.0",
+ "d3-transition": "^3.0.1"
+ }
+ },
"node_modules/@vitejs/plugin-react": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
@@ -5071,7 +5390,6 @@
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
- "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -5954,6 +6272,34 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/chevrotain": {
+ "version": "12.0.0",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz",
+ "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "12.0.0",
+ "@chevrotain/gast": "12.0.0",
+ "@chevrotain/regexp-to-ast": "12.0.0",
+ "@chevrotain/types": "12.0.0",
+ "@chevrotain/utils": "12.0.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/chevrotain-allstar": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.3.tgz",
+ "integrity": "sha512-2X4mkroolSMKqW+H22pyPMUVDqYZzPhephTmg/NODKb1IGYPHfxfhcW0EjS7wcPJNbze2i4vBWT7zT5FKF2lrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash-es": "^4.18.1"
+ },
+ "peerDependencies": {
+ "chevrotain": "^12.0.0"
+ }
+ },
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
@@ -6145,6 +6491,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -6177,6 +6532,12 @@
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "license": "MIT"
+ },
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
@@ -6240,6 +6601,15 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/cose-base": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
+ "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^1.0.0"
+ }
+ },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -6324,6 +6694,95 @@
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
+ "node_modules/cytoscape": {
+ "version": "3.33.3",
+ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.3.tgz",
+ "integrity": "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/cytoscape-cose-bilkent": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
+ "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^1.0.0"
+ },
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
+ }
+ },
+ "node_modules/cytoscape-fcose": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
+ "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "cose-base": "^2.2.0"
+ },
+ "peerDependencies": {
+ "cytoscape": "^3.2.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/cose-base": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
+ "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
+ "license": "MIT",
+ "dependencies": {
+ "layout-base": "^2.0.0"
+ }
+ },
+ "node_modules/cytoscape-fcose/node_modules/layout-base": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
+ "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
+ "license": "MIT"
+ },
+ "node_modules/d3": {
+ "version": "7.9.0",
+ "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
+ "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "3",
+ "d3-axis": "3",
+ "d3-brush": "3",
+ "d3-chord": "3",
+ "d3-color": "3",
+ "d3-contour": "4",
+ "d3-delaunay": "6",
+ "d3-dispatch": "3",
+ "d3-drag": "3",
+ "d3-dsv": "3",
+ "d3-ease": "3",
+ "d3-fetch": "3",
+ "d3-force": "3",
+ "d3-format": "3",
+ "d3-geo": "3",
+ "d3-hierarchy": "3",
+ "d3-interpolate": "3",
+ "d3-path": "3",
+ "d3-polygon": "3",
+ "d3-quadtree": "3",
+ "d3-random": "3",
+ "d3-scale": "4",
+ "d3-scale-chromatic": "3",
+ "d3-selection": "3",
+ "d3-shape": "3",
+ "d3-time": "3",
+ "d3-time-format": "4",
+ "d3-timer": "3",
+ "d3-transition": "3",
+ "d3-zoom": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
@@ -6336,6 +6795,43 @@
"node": ">=12"
}
},
+ "node_modules/d3-axis": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
+ "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-brush": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
+ "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "3",
+ "d3-transition": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-chord": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
+ "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
@@ -6345,6 +6841,89 @@
"node": ">=12"
}
},
+ "node_modules/d3-contour": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
+ "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-delaunay": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+ "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+ "license": "ISC",
+ "dependencies": {
+ "delaunator": "5"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dispatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+ "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-drag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
+ "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+ "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+ "license": "ISC",
+ "dependencies": {
+ "commander": "7",
+ "iconv-lite": "0.6",
+ "rw": "1"
+ },
+ "bin": {
+ "csv2json": "bin/dsv2json.js",
+ "csv2tsv": "bin/dsv2dsv.js",
+ "dsv2dsv": "bin/dsv2dsv.js",
+ "dsv2json": "bin/dsv2json.js",
+ "json2csv": "bin/json2dsv.js",
+ "json2dsv": "bin/json2dsv.js",
+ "json2tsv": "bin/json2dsv.js",
+ "tsv2csv": "bin/dsv2dsv.js",
+ "tsv2json": "bin/dsv2json.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-dsv/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
@@ -6354,6 +6933,32 @@
"node": ">=12"
}
},
+ "node_modules/d3-fetch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
+ "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dsv": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-force": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+ "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-quadtree": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-format": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
@@ -6363,6 +6968,27 @@
"node": ">=12"
}
},
+ "node_modules/d3-geo": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
+ "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.5.0 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-hierarchy": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+ "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
@@ -6384,6 +7010,73 @@
"node": ">=12"
}
},
+ "node_modules/d3-polygon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
+ "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-quadtree": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+ "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-random": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
+ "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-sankey": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz",
+ "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-array": "1 - 2",
+ "d3-shape": "^1.2.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-array": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
+ "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "internmap": "^1.0.0"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/d3-path": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
+ "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/d3-sankey/node_modules/d3-shape": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
+ "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "d3-path": "1"
+ }
+ },
+ "node_modules/d3-sankey/node_modules/internmap": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
+ "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
+ "license": "ISC"
+ },
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
@@ -6400,6 +7093,28 @@
"node": ">=12"
}
},
+ "node_modules/d3-scale-chromatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+ "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-interpolate": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-selection": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
+ "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
@@ -6445,6 +7160,51 @@
"node": ">=12"
}
},
+ "node_modules/d3-transition": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
+ "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3",
+ "d3-dispatch": "1 - 3",
+ "d3-ease": "1 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-timer": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "d3-selection": "2 - 3"
+ }
+ },
+ "node_modules/d3-zoom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
+ "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-drag": "2 - 3",
+ "d3-interpolate": "1 - 3",
+ "d3-selection": "2 - 3",
+ "d3-transition": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dagre-d3-es": {
+ "version": "7.0.14",
+ "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz",
+ "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==",
+ "license": "MIT",
+ "dependencies": {
+ "d3": "^7.9.0",
+ "lodash-es": "^4.17.21"
+ }
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -6529,6 +7289,12 @@
"node": "*"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -6679,6 +7445,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delaunator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
+ "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==",
+ "license": "ISC",
+ "dependencies": {
+ "robust-predicates": "^3.0.2"
+ }
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -7546,6 +8321,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
@@ -7734,6 +8523,19 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
+ "node_modules/extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
@@ -8115,6 +8917,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/fuse.js": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz",
+ "integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/krisk"
+ }
+ },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -8349,6 +9164,52 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/gray-matter": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-yaml": "^3.13.1",
+ "kind-of": "^6.0.2",
+ "section-matter": "^1.0.0",
+ "strip-bom-string": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/gray-matter/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/gray-matter/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/hachure-fill": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
+ "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==",
+ "license": "MIT"
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -9032,6 +9893,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -9709,6 +10580,31 @@
"node": ">=4.0"
}
},
+ "node_modules/katex": {
+ "version": "0.16.45",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz",
+ "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==",
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/katex/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/keytar": {
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz",
@@ -9731,6 +10627,21 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/khroma": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz",
+ "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/koffi": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.16.1.tgz",
@@ -9741,6 +10652,24 @@
"url": "https://liberapay.com/Koromix"
}
},
+ "node_modules/langium": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.3.tgz",
+ "integrity": "sha512-sOPIi4hISFnY7twwV97ca1TsxpBtXq0URu/LL1AvxwccPG/RIBBlKS7a/f/EL6w8lTNaS0EFs/F+IdSOaqYpng==",
+ "license": "MIT",
+ "dependencies": {
+ "@chevrotain/regexp-to-ast": "~12.0.0",
+ "chevrotain": "~12.0.0",
+ "chevrotain-allstar": "~0.4.3",
+ "vscode-languageserver": "~9.0.1",
+ "vscode-languageserver-textdocument": "~1.0.11",
+ "vscode-uri": "~3.1.0"
+ },
+ "engines": {
+ "node": ">=20.10.0",
+ "npm": ">=10.2.3"
+ }
+ },
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -9761,6 +10690,12 @@
"node": ">=0.10"
}
},
+ "node_modules/layout-base": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
+ "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
+ "license": "MIT"
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -10139,6 +11074,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash-es": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
+ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -10260,6 +11201,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz",
+ "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -10299,6 +11249,18 @@
"node": ">=10"
}
},
+ "node_modules/marked": {
+ "version": "16.4.2",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz",
+ "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -10499,6 +11461,48 @@
"node": ">= 8"
}
},
+ "node_modules/mermaid": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz",
+ "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@braintree/sanitize-url": "^7.1.1",
+ "@iconify/utils": "^3.0.2",
+ "@mermaid-js/parser": "^1.1.0",
+ "@types/d3": "^7.4.3",
+ "@upsetjs/venn.js": "^2.0.0",
+ "cytoscape": "^3.33.1",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "cytoscape-fcose": "^2.2.0",
+ "d3": "^7.9.0",
+ "d3-sankey": "^0.12.3",
+ "dagre-d3-es": "7.0.14",
+ "dayjs": "^1.11.19",
+ "dompurify": "^3.3.1",
+ "katex": "^0.16.25",
+ "khroma": "^2.1.0",
+ "lodash-es": "^4.17.23",
+ "marked": "^16.3.0",
+ "roughjs": "^4.6.6",
+ "stylis": "^4.3.6",
+ "ts-dedent": "^2.2.0",
+ "uuid": "^11.1.0"
+ }
+ },
+ "node_modules/mermaid/node_modules/uuid": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz",
+ "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
"node_modules/micromark": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
@@ -11063,6 +12067,18 @@
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
+ "node_modules/mlly": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
+ "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.3"
+ }
+ },
"node_modules/monaco-editor": {
"version": "0.55.1",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
@@ -11628,6 +12644,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/package-manager-detector": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz",
+ "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==",
+ "license": "MIT"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -11705,6 +12727,12 @@
"node": ">= 0.8"
}
},
+ "node_modules/path-data-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz",
+ "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==",
+ "license": "MIT"
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -11780,7 +12808,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
@@ -11883,6 +12910,17 @@
"node": ">=16.20.0"
}
},
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
"node_modules/pkijs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz",
@@ -11950,6 +12988,22 @@
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
"license": "MIT"
},
+ "node_modules/points-on-curve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz",
+ "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==",
+ "license": "MIT"
+ },
+ "node_modules/points-on-path": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz",
+ "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==",
+ "license": "MIT",
+ "dependencies": {
+ "path-data-parser": "0.1.0",
+ "points-on-curve": "0.2.0"
+ }
+ },
"node_modules/polished": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
@@ -12621,6 +13675,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/robust-predicates": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz",
+ "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
+ "license": "Unlicense"
+ },
"node_modules/rolldown": {
"version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
@@ -12662,6 +13722,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/roughjs": {
+ "version": "4.6.6",
+ "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",
+ "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "hachure-fill": "^0.5.2",
+ "path-data-parser": "^0.1.0",
+ "points-on-curve": "^0.2.0",
+ "points-on-path": "^0.2.1"
+ }
+ },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -12714,6 +13786,12 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/rw": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
@@ -12833,6 +13911,20 @@
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
+ "node_modules/section-matter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/secure-json-parse": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
@@ -13291,6 +14383,13 @@
"node": ">= 10.x"
}
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -13564,6 +14663,16 @@
"node": ">=4"
}
},
+ "node_modules/strip-bom-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -13804,7 +14913,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
"integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@@ -13962,6 +15070,15 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@@ -14200,6 +15317,12 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
+ "node_modules/ufo": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
+ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
+ "license": "MIT"
+ },
"node_modules/unbox-primitive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -14728,6 +15851,55 @@
}
}
},
+ "node_modules/vscode-jsonrpc": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
+ "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/vscode-languageserver": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz",
+ "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-languageserver-protocol": "3.17.5"
+ },
+ "bin": {
+ "installServerIntoExtension": "bin/installServerIntoExtension"
+ }
+ },
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz",
+ "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==",
+ "license": "MIT",
+ "dependencies": {
+ "vscode-jsonrpc": "8.2.0",
+ "vscode-languageserver-types": "3.17.5"
+ }
+ },
+ "node_modules/vscode-languageserver-textdocument": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz",
+ "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.17.5",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz",
+ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==",
+ "license": "MIT"
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "license": "MIT"
+ },
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
diff --git a/package.json b/package.json
index 5e1e0d290e..e2dd6925b6 100644
--- a/package.json
+++ b/package.json
@@ -62,6 +62,7 @@
"homepage": "https://omniroute.online",
"scripts": {
"dev": "node scripts/run-next.mjs dev",
+ "prebuild:docs": "node scripts/generate-docs-index.mjs",
"build": "node scripts/build-next-isolated.mjs",
"build:cli": "node --import tsx/esm scripts/prepublish.ts",
"start": "node scripts/run-next.mjs start",
@@ -121,12 +122,15 @@
"bottleneck": "^2.19.5",
"express": "^5.2.1",
"fetch-socks": "^1.3.2",
+ "fuse.js": "^7.3.0",
"http-proxy-middleware": "^3.0.5",
"https-proxy-agent": "^9.0.0",
"jose": "^6.1.3",
"js-yaml": "^4.1.0",
"jsonc-parser": "^3.3.1",
"lowdb": "^7.0.1",
+ "lucide-react": "^1.14.0",
+ "mermaid": "^11.14.0",
"monaco-editor": "^0.55.1",
"next": "^16.2.3",
"next-intl": "^4.11.0",
@@ -172,6 +176,8 @@
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
"eslint-config-next": "16.2.4",
+ "glob": "^13.0.6",
+ "gray-matter": "^4.0.3",
"husky": "^9.1.7",
"jsdom": "^29.1.1",
"lint-staged": "^16.2.7",
diff --git a/scripts/build-next-isolated.mjs b/scripts/build-next-isolated.mjs
index b6ccc994e0..88e1ffacab 100644
--- a/scripts/build-next-isolated.mjs
+++ b/scripts/build-next-isolated.mjs
@@ -184,15 +184,21 @@ export async function main() {
await resetStandaloneOutput(projectRoot);
+ console.log("[build-next-isolated] Generating docs index...");
+ try {
+ const { execSync } = await import("node:child_process");
+ execSync("node scripts/generate-docs-index.mjs", { cwd: projectRoot, stdio: "inherit" });
+ } catch (docGenErr) {
+ console.warn(
+ "[build-next-isolated] Docs index generation failed (non-fatal):",
+ docGenErr?.message
+ );
+ }
+
const result = await runNextBuild();
if (result.code === 0 && (await exists(path.join(projectRoot, ".next", "standalone")))) {
console.log("[build-next-isolated] Copying static assets for standalone server...");
try {
- await fs.cp(
- path.join(projectRoot, "public"),
- path.join(projectRoot, ".next", "standalone", "public"),
- { recursive: true }
- );
await fs.cp(
path.join(projectRoot, ".next", "static"),
path.join(projectRoot, ".next", "standalone", ".next", "static"),
@@ -202,6 +208,17 @@ export async function main() {
console.warn("[build-next-isolated] Non-fatal error copying static assets:", copyErr);
}
+ try {
+ await fs.cp(
+ path.join(projectRoot, "docs"),
+ path.join(projectRoot, ".next", "standalone", "docs"),
+ { recursive: true }
+ );
+ console.log("[build-next-isolated] Copied docs/ to standalone output");
+ } catch (docsCopyErr) {
+ console.warn("[build-next-isolated] Non-fatal error copying docs/:", docsCopyErr?.message);
+ }
+
try {
await pruneStandaloneArtifacts(projectRoot);
} catch (pruneErr) {
diff --git a/scripts/generate-docs-index.mjs b/scripts/generate-docs-index.mjs
new file mode 100644
index 0000000000..482c435f11
--- /dev/null
+++ b/scripts/generate-docs-index.mjs
@@ -0,0 +1,230 @@
+#!/usr/bin/env node
+
+/**
+ * Build-time script: scans docs/*.md and generates
+ * src/app/docs/lib/docs-auto-generated.ts with static navigation + search data.
+ *
+ * This file is imported by both client and server components — NO fs/path imports.
+ * Run via: node scripts/generate-docs-index.mjs
+ * Automatically runs as prebuild step.
+ */
+
+import fs from "node:fs";
+import path from "node:path";
+import matter from "gray-matter";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.resolve(__dirname, "..");
+const DOCS_DIR = path.join(ROOT, "docs");
+const OUT_FILE = path.join(ROOT, "src", "app", "docs", "lib", "docs-auto-generated.ts");
+
+const SECTION_CATEGORIES = {
+ "Getting Started": [
+ "SETUP_GUIDE",
+ "USER_GUIDE",
+ "CLI_TOOLS",
+ "ARCHITECTURE",
+ "QUICK_START",
+ "GETTING_STARTED",
+ ],
+ Features: [
+ "FEATURES",
+ "AUTO_COMBO",
+ "COMPRESSION_GUIDE",
+ "RTK_COMPRESSION",
+ "COMPRESSION_ENGINES",
+ "COMPRESSION_RULES_FORMAT",
+ "COMPRESSION_LANGUAGE_PACKS",
+ "FREE_TIERS",
+ ],
+ "API & Protocols": ["API_REFERENCE", "MCP_SERVER", "A2A_SERVER"],
+ Deployment: [
+ "DOCKER_GUIDE",
+ "VM_DEPLOYMENT_GUIDE",
+ "FLY_IO_DEPLOYMENT_GUIDE",
+ "TERMUX_GUIDE",
+ "PWA_GUIDE",
+ ],
+ Operations: ["PROXY_GUIDE", "RESILIENCE_GUIDE", "ENVIRONMENT", "TROUBLESHOOTING"],
+ Development: [
+ "CODEBASE_DOCUMENTATION",
+ "COVERAGE_PLAN",
+ "I18N",
+ "RELEASE_CHECKLIST",
+ "UNINSTALL",
+ "CONTRIBUTING",
+ "CHANGELOG",
+ "CODE_OF_CONDUCT",
+ ],
+};
+
+const SECTION_ORDER = {
+ "Getting Started": 1,
+ Features: 2,
+ "API & Protocols": 3,
+ Deployment: 4,
+ Operations: 5,
+ Development: 6,
+};
+
+function categorizeFile(fileName) {
+ const stem = fileName.replace(/\.md$/i, "").toUpperCase().replace(/-/g, "_");
+ for (const [section, patterns] of Object.entries(SECTION_CATEGORIES)) {
+ if (patterns.some((p) => stem === p)) {
+ return section;
+ }
+ }
+ return "Other";
+}
+
+function extractTitleFromContent(content) {
+ const match = content.match(/^#\s+(.+)$/m);
+ if (match) {
+ return match[1]
+ .replace(/^📖\s*/, "")
+ .replace(/^🌐\s*/, "")
+ .replace(/\s*—\s*OmniRoute\s*$/i, "")
+ .replace(/\s*—\s*OmniRoute Docs\s*$/i, "")
+ .trim();
+ }
+ return "";
+}
+
+function extractHeadings(content) {
+ const headings = [];
+ const regex = /^(#{2,4})\s+(.+)$/gm;
+ let match;
+ while ((match = regex.exec(content)) !== null) {
+ headings.push(match[2].replace(/\*\*/g, "").replace(/\*/g, "").replace(/`/g, "").trim());
+ }
+ return headings.slice(0, 10);
+}
+
+function extractContentPreview(content) {
+ const stripped = content
+ .replace(/^---[\s\S]*?---/m, "")
+ .replace(/^#{1,6}\s+.+$/gm, "")
+ .replace(/```[\s\S]*?```/g, "")
+ .replace(/!\[[^\]]*\]\([^)]+\)/g, "")
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
+ .replace(/[*_`~>#|]/g, "")
+ .replace(/\s+/g, " ")
+ .trim();
+ return stripped.slice(0, 300);
+}
+
+// ---------- Main ----------
+
+const files = fs.readdirSync(DOCS_DIR).filter((f) => f.endsWith(".md") || f.endsWith(".mdx"));
+
+const docs = [];
+for (const fileName of files) {
+ const filePath = path.join(DOCS_DIR, fileName);
+ const fileContent = fs.readFileSync(filePath, "utf8");
+ const { data: frontmatter, content } = matter(fileContent);
+
+ const slug =
+ frontmatter.slug ||
+ fileName
+ .replace(/\.mdx?$/i, "")
+ .toLowerCase()
+ .replace(/_/g, "-");
+ const title = frontmatter.title || extractTitleFromContent(content) || slug.replace(/-/g, " ");
+ const section = frontmatter.section || categorizeFile(fileName);
+ const order = frontmatter.order ?? 999;
+ const headings = extractHeadings(content);
+ const contentPreview = frontmatter.description || extractContentPreview(content);
+
+ docs.push({ slug, title, fileName, section, order, content: contentPreview, headings });
+}
+
+docs.sort((a, b) => {
+ const sectionA = SECTION_ORDER[a.section] ?? 99;
+ const sectionB = SECTION_ORDER[b.section] ?? 99;
+ if (sectionA !== sectionB) return sectionA - sectionB;
+ return a.order - b.order;
+});
+
+// Build navigation sections
+const sectionMap = new Map();
+for (const doc of docs) {
+ const items = sectionMap.get(doc.section) || [];
+ items.push(doc);
+ sectionMap.set(doc.section, items);
+}
+const orderedSections = [...new Set([...Object.keys(SECTION_ORDER), ...sectionMap.keys()])];
+const navSections = orderedSections
+ .filter((s) => sectionMap.has(s))
+ .sort((a, b) => (SECTION_ORDER[a] ?? 99) - (SECTION_ORDER[b] ?? 99))
+ .map((title) => ({
+ title,
+ items: sectionMap.get(title).map((doc) => ({
+ slug: doc.slug,
+ title: doc.title,
+ fileName: doc.fileName,
+ })),
+ }));
+
+// Build search index
+const searchIndex = docs.map((doc) => ({
+ slug: doc.slug,
+ title: doc.title,
+ fileName: doc.fileName,
+ section: doc.section,
+ content: doc.content,
+ headings: doc.headings,
+}));
+
+// Add api-explorer synthetic entry if api-reference exists
+if (searchIndex.some((item) => item.slug === "api-reference")) {
+ if (!searchIndex.some((item) => item.slug === "api-explorer")) {
+ searchIndex.push({
+ slug: "api-explorer",
+ title: "API Explorer",
+ fileName: "API_REFERENCE.md",
+ section: "API & Protocols",
+ content: "interactive try it live api explorer endpoint test request response curl example",
+ headings: ["Try It", "Endpoints"],
+ });
+ }
+}
+
+// ---------- Write output ----------
+
+const output = `// AUTO-GENERATED by scripts/generate-docs-index.mjs — DO NOT EDIT MANUALLY
+// Regenerate with: node scripts/generate-docs-index.mjs
+
+export interface AutoGenDocItem {
+ slug: string;
+ title: string;
+ fileName: string;
+}
+
+export interface AutoGenNavSection {
+ title: string;
+ items: AutoGenDocItem[];
+}
+
+export interface AutoGenSearchItem {
+ slug: string;
+ title: string;
+ fileName: string;
+ section: string;
+ content: string;
+ headings: string[];
+}
+
+export const autoNavSections: AutoGenNavSection[] = ${JSON.stringify(navSections, null, 2)};
+
+export const autoSearchIndex: AutoGenSearchItem[] = ${JSON.stringify(searchIndex, null, 2)};
+
+export const autoAllSlugs: string[] = ${JSON.stringify(
+ docs.map((d) => d.slug),
+ null,
+ 2
+)};
+`;
+
+fs.writeFileSync(OUT_FILE, output, "utf8");
+console.log(`✅ Generated ${OUT_FILE} with ${docs.length} docs, ${navSections.length} sections`);
diff --git a/src/app/docs/[slug]/page.tsx b/src/app/docs/[slug]/page.tsx
new file mode 100644
index 0000000000..36a169629f
--- /dev/null
+++ b/src/app/docs/[slug]/page.tsx
@@ -0,0 +1,326 @@
+import { notFound } from "next/navigation";
+import Link from "next/link";
+import { docsNavigation } from "../lib/docsNavigation";
+import { autoAllSlugs, autoNavSections } from "../lib/docs-auto-generated";
+import fs from "fs";
+import path from "path";
+import matter from "gray-matter";
+import { Metadata } from "next";
+import { DocCodeBlocks } from "../components/DocCodeBlocks";
+import { FeedbackWidget } from "../components/FeedbackWidget";
+import { DocsPageAnalytics } from "../components/DocsPageAnalytics";
+import { DocsLazyWrapper } from "../components/DocsLazyWrapper";
+import { MermaidChartsClient } from "../components/MermaidChartsClient";
+
+export function generateStaticParams() {
+ return autoAllSlugs.map((slug) => ({ slug }));
+}
+
+export function getDocItemBySlug(slug: string) {
+ for (const section of docsNavigation) {
+ const item = section.items.find((item) => item.slug === slug);
+ if (item) {
+ return { sectionTitle: section.title, item };
+ }
+ }
+ for (const section of autoNavSections) {
+ const item = section.items.find((i) => i.slug === slug);
+ if (item) {
+ return {
+ sectionTitle: section.title,
+ item: { slug: item.slug, title: item.title, fileName: item.fileName },
+ };
+ }
+ }
+ return null;
+}
+
+export function getAllDocSlugsFlat(): string[] {
+ return autoAllSlugs;
+}
+
+export function getPrevNextSlugs(currentSlug: string) {
+ const allSlugs = getAllDocSlugsFlat();
+ const idx = allSlugs.indexOf(currentSlug);
+ return {
+ prev: idx > 0 ? allSlugs[idx - 1] : null,
+ next: idx < allSlugs.length - 1 ? allSlugs[idx + 1] : null,
+ };
+}
+
+export function extractHeadings(content: string): { id: string; text: string; level: number }[] {
+ const headings: { id: string; text: string; level: number }[] = [];
+ const regex = /^(#{2,4})\s+(.+)$/gm;
+ let match;
+ while ((match = regex.exec(content)) !== null) {
+ const level = match[1].length;
+ const text = match[2].replace(/\*\*/g, "").replace(/\*/g, "").replace(/`/g, "");
+ const id = text
+ .toLowerCase()
+ .replace(/[^\w\s-]/g, "")
+ .replace(/\s+/g, "-");
+ headings.push({ id, text, level });
+ }
+ return headings;
+}
+
+export function extractMermaidCharts(content: string): string[] {
+ const charts: string[] = [];
+ const regex = /```mermaid\n([\s\S]*?)```/g;
+ let match;
+ while ((match = regex.exec(content)) !== null) {
+ charts.push(match[1].trim());
+ }
+ return charts;
+}
+
+export function renderMarkdown(content: string): string {
+ return content
+ .replace(/^####\s+(.*)$/gm, '
$1
')
+ .replace(/^###\s+(.*)$/gm, '$1
')
+ .replace(/^##\s+(.*)$/gm, '$1
')
+ .replace(/^#\s+(.*)$/gm, '$1
')
+ .replace(
+ /```mermaid\n([\s\S]*?)```/g,
+ '$1
'
+ )
+ .replace(
+ /```(\w*)\n([\s\S]*?)```/g,
+ ''
+ )
+ .replace(/`([^`]+)`/g, '$1')
+ .replace(/\*\*(.*?)\*\*/g, "$1")
+ .replace(/\*(.*?)\*/g, "$1")
+ .replace(/__(.*?)__/g, "$1")
+ .replace(/_(.*?)_/g, "$1")
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
+ .replace(
+ /!\[([^\]]+)\]\(([^)]+)\)/g,
+ '
'
+ )
+ .replace(/^(\*|\-)\s+(.*)$/gm, '$2')
+ .replace(/^(\d+)\.\s+(.*)$/gm, '$2')
+ .replace(/^\|\s*(.+?)\s*\|$/gm, (match) => {
+ if (match.match(/^\|\s*[-:]+[-|\s:]*$/)) return "";
+ const cells = match
+ .split("|")
+ .filter((c) => c.trim())
+ .map((c) => `${c.trim()} | `)
+ .join("");
+ return `${cells}
`;
+ })
+ .replace(
+ /(.*<\/tr>\n?)+/g,
+ (match) =>
+ ``
+ )
+ .replace(
+ /^>\s+(.*)$/gm,
+ '$1
'
+ )
+ .replace(/^---$/gm, '
');
+}
+
+function cleanHeadingIds(html: string): string {
+ return html.replace(
+ /id="([^"]*)"/g,
+ (_, id) =>
+ `id="${id
+ .toLowerCase()
+ .replace(/[^\w\s-]/g, "")
+ .replace(/\s+/g, "-")}"`
+ );
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}): Promise {
+ const { slug } = await params;
+ const docItem = getDocItemBySlug(slug);
+
+ if (!docItem) {
+ return {
+ title: "Document Not Found",
+ };
+ }
+
+ return {
+ title: `${docItem.item.title} — OmniRoute Docs`,
+ description: `OmniRoute documentation: ${docItem.item.title}`,
+ };
+}
+
+export default async function DocPage({ params }: { params: Promise<{ slug: string }> }) {
+ const { slug } = await params;
+ const docItem = getDocItemBySlug(slug);
+
+ if (!docItem) {
+ notFound();
+ }
+
+ const { sectionTitle, item } = docItem;
+
+ let pageTitle = item.title;
+ let htmlContent = "";
+ let headings: { id: string; text: string; level: number }[] = [];
+ let loadError: string | null = null;
+ let version: string | null = null;
+ let lastUpdated: string | null = null;
+ let mermaidCharts: string[] = [];
+
+ try {
+ const docsRoot = path.join(process.cwd(), "docs");
+ const fileContent = fs.readFileSync(path.join(docsRoot, item.fileName), "utf8");
+ const { content, data: frontmatter } = matter(fileContent);
+ pageTitle = (frontmatter.title as string) || item.title;
+ version = (frontmatter.version as string) || null;
+ lastUpdated = (frontmatter.lastUpdated as string) || null;
+ mermaidCharts = extractMermaidCharts(content);
+ headings = extractHeadings(content);
+ const rawHtml = renderMarkdown(content);
+ htmlContent = cleanHeadingIds(rawHtml);
+ } catch (error) {
+ console.error(`Failed to read doc file for slug: ${slug}`, error);
+ loadError = error instanceof Error ? error.message : "Unknown error";
+ }
+
+ if (loadError) {
+ return (
+
+
Error Loading Documentation
+
Failed to load {item.fileName}. Please try again later.
+
Error: {loadError}
+
+ );
+ }
+
+ const { prev, next } = getPrevNextSlugs(slug);
+ const prevItem = prev ? getDocItemBySlug(prev) : null;
+ const nextItem = next ? getDocItemBySlug(next) : null;
+
+ const breadcrumbJsonLd = {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Docs",
+ item: `https://omniroute.online/docs`,
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: sectionTitle,
+ item: `https://omniroute.online/docs/${slug}`,
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: pageTitle,
+ },
+ ],
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
{pageTitle}
+ {version && (
+
+ v{version}
+
+ )}
+
+
+ {lastUpdated && (
+
Last updated: {lastUpdated}
+ )}
+
+
+
+
+
+ {mermaidCharts.length > 0 && (
+
+
+
+ )}
+
+
+
+
+
+
+ {prevItem ? (
+
+
arrow_back
+ {prevItem.item.title}
+
+ ) : (
+
+ )}
+ {nextItem ? (
+
+ {nextItem.item.title}
+
arrow_forward
+
+ ) : (
+
+ )}
+
+
+
+ {headings.length > 0 && (
+
+ )}
+
+ >
+ );
+}
diff --git a/src/app/docs/api-explorer/page.tsx b/src/app/docs/api-explorer/page.tsx
new file mode 100644
index 0000000000..217c62360f
--- /dev/null
+++ b/src/app/docs/api-explorer/page.tsx
@@ -0,0 +1,20 @@
+import { Metadata } from "next";
+import { ApiExplorerClient } from "../components/ApiExplorerClient";
+
+export const metadata: Metadata = {
+ title: "API Explorer — OmniRoute Docs",
+ description: "Interactive API explorer — try OmniRoute endpoints live with real-time responses",
+};
+
+export default function ApiExplorerPage() {
+ return (
+
+
API Explorer
+
+ Try OmniRoute endpoints live. Select an endpoint, configure your request, and see the
+ response in real time.
+
+
+
+ );
+}
diff --git a/src/app/docs/components/ApiExplorerClient.tsx b/src/app/docs/components/ApiExplorerClient.tsx
new file mode 100644
index 0000000000..169a21f98f
--- /dev/null
+++ b/src/app/docs/components/ApiExplorerClient.tsx
@@ -0,0 +1,298 @@
+"use client";
+
+import React, { useState, useEffect, useCallback } from "react";
+
+interface ApiEndpoint {
+ method: string;
+ path: string;
+ description: string;
+ tag: string;
+}
+
+const API_ENDPOINTS: ApiEndpoint[] = [
+ {
+ method: "POST",
+ path: "/v1/chat/completions",
+ description: "OpenAI-compatible chat completions with streaming support",
+ tag: "Chat",
+ },
+ {
+ method: "POST",
+ path: "/v1/responses",
+ description: "OpenAI Responses API format",
+ tag: "Responses",
+ },
+ {
+ method: "GET",
+ path: "/v1/models",
+ description: "List available models across all providers",
+ tag: "Models",
+ },
+ {
+ method: "POST",
+ path: "/v1/embeddings",
+ description: "Generate text embeddings",
+ tag: "Embeddings",
+ },
+ {
+ method: "POST",
+ path: "/v1/images/generations",
+ description: "Generate images from text prompts",
+ tag: "Images",
+ },
+ {
+ method: "POST",
+ path: "/v1/audio/transcriptions",
+ description: "Transcribe audio files",
+ tag: "Audio",
+ },
+ {
+ method: "POST",
+ path: "/v1/audio/speech",
+ description: "Text-to-speech generation",
+ tag: "Audio",
+ },
+ {
+ method: "POST",
+ path: "/v1/moderations",
+ description: "Content moderation check",
+ tag: "Moderations",
+ },
+ {
+ method: "POST",
+ path: "/v1/rerank",
+ description: "Re-rank documents by relevance",
+ tag: "Rerank",
+ },
+ {
+ method: "POST",
+ path: "/v1/search",
+ description: "Web search across 5 providers",
+ tag: "Search",
+ },
+ {
+ method: "POST",
+ path: "/v1/videos/generations",
+ description: "Generate videos from prompts",
+ tag: "Video",
+ },
+ {
+ method: "POST",
+ path: "/v1/music/generations",
+ description: "Generate music from prompts",
+ tag: "Music",
+ },
+];
+
+const METHOD_COLORS: Record = {
+ GET: "bg-emerald-500/10 text-emerald-600 border-emerald-500/20",
+ POST: "bg-blue-500/10 text-blue-600 border-blue-500/20",
+ PUT: "bg-amber-500/10 text-amber-600 border-amber-500/20",
+ DELETE: "bg-red-500/10 text-red-600 border-red-500/20",
+ PATCH: "bg-purple-500/10 text-purple-600 border-purple-500/20",
+};
+
+const EXAMPLE_BODIES: Record = {
+ "/v1/chat/completions": JSON.stringify(
+ { model: "openai/gpt-4o-mini", messages: [{ role: "user", content: "Hello!" }], stream: true },
+ null,
+ 2
+ ),
+ "/v1/models": "",
+ "/v1/embeddings": JSON.stringify(
+ { model: "openai/text-embedding-3-small", input: "Hello world" },
+ null,
+ 2
+ ),
+ "/v1/images/generations": JSON.stringify(
+ { model: "openai/dall-e-3", prompt: "A sunset over mountains", n: 1 },
+ null,
+ 2
+ ),
+ "/v1/responses": JSON.stringify(
+ { model: "openai/gpt-4o-mini", input: "What is OmniRoute?" },
+ null,
+ 2
+ ),
+};
+
+export function ApiExplorerClient() {
+ const [selected, setSelected] = useState(null);
+ const [baseUrl, setBaseUrl] = useState("http://localhost:20128");
+ const [apiKey, setApiKey] = useState("");
+ const [requestBody, setRequestBody] = useState("");
+ const [response, setResponse] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [filterTag, setFilterTag] = useState(null);
+
+ const allTags = [...new Set(API_ENDPOINTS.map((e) => e.tag))];
+ const filteredEndpoints = filterTag
+ ? API_ENDPOINTS.filter((e) => e.tag === filterTag)
+ : API_ENDPOINTS;
+
+ const handleSelect = useCallback((endpoint: ApiEndpoint) => {
+ setSelected(endpoint);
+ setResponse(null);
+ const example = EXAMPLE_BODIES[endpoint.path] || "";
+ setRequestBody(example);
+ }, []);
+
+ const handleTryIt = async () => {
+ if (!selected) return;
+ setLoading(true);
+ setResponse(null);
+
+ try {
+ const headers: Record = { "Content-Type": "application/json" };
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
+
+ const opts: RequestInit = { method: selected.method, headers };
+ if (selected.method !== "GET" && requestBody.trim()) {
+ opts.body = requestBody;
+ }
+
+ const res = await fetch(`${baseUrl}${selected.path}`, opts);
+ const contentType = res.headers.get("content-type") || "";
+
+ if (contentType.includes("text/event-stream")) {
+ setResponse("SSE stream started — check the terminal/devtools for real-time output.");
+ } else {
+ const data = await res.json();
+ setResponse(JSON.stringify(data, null, 2));
+ }
+ } catch (err) {
+ setResponse(`Error: ${err instanceof Error ? err.message : "Request failed"}`);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+ {allTags.map((tag) => (
+
+ ))}
+
+
+
+ {filteredEndpoints.map((endpoint) => (
+
+ ))}
+
+
+
+
+
+ {selected ? (
+
+
+
+ {selected.method}
+
+ {selected.path}
+
+
{selected.description}
+
+
+
+ {selected.method !== "GET" && (
+
+
+
+ )}
+
+
+
+ {response !== null && (
+
+
+
+ {response}
+
+
+ )}
+
+ ) : (
+
+
api
+
Select an endpoint to explore
+
+ Choose an API from the sidebar to see details and try it live
+
+
+ )}
+
+
+ );
+}
diff --git a/src/app/docs/components/CodeBlockCopyButton.tsx b/src/app/docs/components/CodeBlockCopyButton.tsx
new file mode 100644
index 0000000000..f4f6c242b2
--- /dev/null
+++ b/src/app/docs/components/CodeBlockCopyButton.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import React, { useState, useRef } from "react";
+
+export function CodeBlockCopyButton({ text }: { text: string }) {
+ const [copied, setCopied] = useState(false);
+ const timeoutRef = useRef>();
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopied(true);
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = setTimeout(() => setCopied(false), 2000);
+ } catch {
+ const textarea = document.createElement("textarea");
+ textarea.value = text;
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textarea);
+ setCopied(true);
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = setTimeout(() => setCopied(false), 2000);
+ }
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/docs/components/DocCodeBlocks.tsx b/src/app/docs/components/DocCodeBlocks.tsx
new file mode 100644
index 0000000000..cb54dc5287
--- /dev/null
+++ b/src/app/docs/components/DocCodeBlocks.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import React, { useEffect, useRef, useState } from "react";
+
+export function DocCodeBlocks() {
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ const container = containerRef.current?.parentElement;
+ if (!container) return;
+
+ const pres = container.querySelectorAll("pre");
+ pres.forEach((pre) => {
+ if (pre.parentElement?.querySelector(".copy-btn")) return;
+
+ pre.parentElement?.classList.add("relative");
+ const btn = document.createElement("button");
+ btn.className = "copy-btn absolute top-2 right-2 p-1.5 bg-bg border border-border rounded-md hover:bg-bg-subtle transition-colors opacity-0 group-hover:opacity-100";
+ btn.setAttribute("aria-label", "Copy code");
+ btn.innerHTML = 'content_copy';
+
+ btn.addEventListener("click", async () => {
+ const code = pre.querySelector("code")?.textContent || pre.textContent || "";
+ try {
+ await navigator.clipboard.writeText(code);
+ } catch {
+ const textarea = document.createElement("textarea");
+ textarea.value = code;
+ document.body.appendChild(textarea);
+ textarea.select();
+ document.execCommand("copy");
+ document.body.removeChild(textarea);
+ }
+ btn.innerHTML = 'check';
+ btn.setAttribute("aria-label", "Copied");
+ setTimeout(() => {
+ btn.innerHTML = 'content_copy';
+ btn.setAttribute("aria-label", "Copy code");
+ }, 2000);
+ });
+
+ pre.parentElement?.appendChild(btn);
+ });
+ }, []);
+
+ return ;
+}
\ No newline at end of file
diff --git a/src/app/docs/components/DocsI18n.tsx b/src/app/docs/components/DocsI18n.tsx
new file mode 100644
index 0000000000..49a76a01f7
--- /dev/null
+++ b/src/app/docs/components/DocsI18n.tsx
@@ -0,0 +1,200 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { useSearchParams, useRouter, usePathname } from "next/navigation";
+
+type Locale = "en" | "pt-BR" | "es" | "fr" | "de" | "ja" | "zh-CN" | "ko" | "ru" | "ar";
+
+const SECTION_LABELS: Record> = {
+ en: {
+ "Getting Started": "Getting Started",
+ Features: "Features",
+ "API & Protocols": "API & Protocols",
+ Deployment: "Deployment",
+ Operations: "Operations",
+ Development: "Development",
+ },
+ "pt-BR": {
+ "Getting Started": "Primeiros Passos",
+ Features: "Recursos",
+ "API & Protocols": "API & Protocolos",
+ Deployment: "Implantação",
+ Operations: "Operações",
+ Development: "Desenvolvimento",
+ },
+ es: {
+ "Getting Started": "Primeros Pasos",
+ Features: "Características",
+ "API & Protocols": "API y Protocolos",
+ Deployment: "Despliegue",
+ Operations: "Operaciones",
+ Development: "Desarrollo",
+ },
+ fr: {
+ "Getting Started": "Démarrage",
+ Features: "Fonctionnalités",
+ "API & Protocols": "API et Protocoles",
+ Deployment: "Déploiement",
+ Operations: "Opérations",
+ Development: "Développement",
+ },
+ de: {
+ "Getting Started": "Erste Schritte",
+ Features: "Funktionen",
+ "API & Protocols": "API & Protokolle",
+ Deployment: "Bereitstellung",
+ Operations: "Betrieb",
+ Development: "Entwicklung",
+ },
+ ja: {
+ "Getting Started": "はじめに",
+ Features: "機能",
+ "API & Protocols": "APIとプロトコル",
+ Deployment: "デプロイ",
+ Operations: "運用",
+ Development: "開発",
+ },
+ "zh-CN": {
+ "Getting Started": "快速开始",
+ Features: "功能",
+ "API & Protocols": "API与协议",
+ Deployment: "部署",
+ Operations: "运维",
+ Development: "开发",
+ },
+ ko: {
+ "Getting Started": "시작하기",
+ Features: "기능",
+ "API & Protocols": "API 및 프로토콜",
+ Deployment: "배포",
+ Operations: "운영",
+ Development: "개발",
+ },
+ ru: {
+ "Getting Started": "Начало работы",
+ Features: "Возможности",
+ "API & Protocols": "API и Протоколы",
+ Deployment: "Развертывание",
+ Operations: "Эксплуатация",
+ Development: "Разработка",
+ },
+ ar: {
+ "Getting Started": "البدء",
+ Features: "الميزات",
+ "API & Protocols": "API والبروتوكولات",
+ Deployment: "النشر",
+ Operations: "العمليات",
+ Development: "التطوير",
+ },
+};
+
+function detectLocale(): Locale {
+ if (typeof navigator === "undefined") return "en";
+ const lang = navigator.language;
+ if (lang.startsWith("pt-BR")) return "pt-BR";
+ if (lang.startsWith("es")) return "es";
+ if (lang.startsWith("fr")) return "fr";
+ if (lang.startsWith("de")) return "de";
+ if (lang.startsWith("ja")) return "ja";
+ if (lang.startsWith("zh")) return "zh-CN";
+ if (lang.startsWith("ko")) return "ko";
+ if (lang.startsWith("ru")) return "ru";
+ if (lang.startsWith("ar")) return "ar";
+ return "en";
+}
+
+export function useDocsLocale() {
+ const searchParams = useSearchParams();
+ return useMemo(() => {
+ const langParam = searchParams.get("lang");
+ if (langParam && langParam in SECTION_LABELS) return langParam as Locale;
+ return detectLocale();
+ }, [searchParams]);
+}
+
+export function useLocalizedSectionTitle(englishTitle: string): string {
+ const locale = useDocsLocale();
+ return SECTION_LABELS[locale]?.[englishTitle] ?? englishTitle;
+}
+
+export function getAvailableLocales(): Locale[] {
+ return Object.keys(SECTION_LABELS) as Locale[];
+}
+
+export const LOCALE_NAMES: Record = {
+ en: "English",
+ "pt-BR": "Português (Brasil)",
+ es: "Español",
+ fr: "Français",
+ de: "Deutsch",
+ ja: "日本語",
+ "zh-CN": "简体中文",
+ ko: "한국어",
+ ru: "Русский",
+ ar: "العربية",
+};
+
+const COMMON_LOCALES: Locale[] = ["en", "pt-BR", "es", "fr", "de", "ja", "zh-CN", "ko", "ru", "ar"];
+
+export function DocsLocaleSwitcher() {
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const pathname = usePathname();
+ const [isOpen, setIsOpen] = useState(false);
+ const currentLocale = useMemo(() => {
+ const langParam = searchParams.get("lang");
+ if (langParam && langParam in SECTION_LABELS) return langParam as Locale;
+ if (typeof navigator !== "undefined") {
+ const navLocale = detectLocale();
+ if (navLocale in SECTION_LABELS) return navLocale;
+ }
+ return "en";
+ }, [searchParams]);
+
+ const handleLocaleChange = (locale: Locale) => {
+ setIsOpen(false);
+ const params = new URLSearchParams(searchParams.toString());
+ if (locale === "en") {
+ params.delete("lang");
+ } else {
+ params.set("lang", locale);
+ }
+ const queryString = params.toString();
+ router.push(`${pathname}${queryString ? `?${queryString}` : ""}`);
+ };
+
+ return (
+
+
+ {isOpen && (
+
+ {COMMON_LOCALES.map((locale) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/app/docs/components/DocsLazyWrapper.tsx b/src/app/docs/components/DocsLazyWrapper.tsx
new file mode 100644
index 0000000000..0f4ca746c9
--- /dev/null
+++ b/src/app/docs/components/DocsLazyWrapper.tsx
@@ -0,0 +1,24 @@
+import { Suspense } from "react";
+
+export function DocsLazyWrapper({ children }: { children: React.ReactNode }) {
+ return }>{children};
+}
+
+function DocsSkeleton() {
+ return (
+
+ );
+}
diff --git a/src/app/docs/components/DocsPageAnalytics.tsx b/src/app/docs/components/DocsPageAnalytics.tsx
new file mode 100644
index 0000000000..3cb2a4c5ac
--- /dev/null
+++ b/src/app/docs/components/DocsPageAnalytics.tsx
@@ -0,0 +1,94 @@
+"use client";
+
+import { useEffect, useCallback, useRef } from "react";
+
+interface PageAnalyticsProps {
+ slug: string;
+ title: string;
+ section: string;
+}
+
+const STORAGE_KEY = "omniroute_docs_analytics";
+const MAX_EVENTS = 200;
+
+interface AnalyticsEvent {
+ slug: string;
+ title: string;
+ section: string;
+ timestamp: number;
+ referrer: string;
+}
+
+function getEvents(): AnalyticsEvent[] {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (!raw) return [];
+ return JSON.parse(raw);
+ } catch {
+ return [];
+ }
+}
+
+function pruneEvents(events: AnalyticsEvent[]): AnalyticsEvent[] {
+ return events.slice(-MAX_EVENTS);
+}
+
+export function DocsPageAnalytics({ slug, title, section }: PageAnalyticsProps) {
+ const trackedRef = useRef(false);
+
+ const trackPageView = useCallback(() => {
+ if (trackedRef.current) return;
+ trackedRef.current = true;
+
+ try {
+ const events = getEvents();
+ events.push({
+ slug,
+ title,
+ section,
+ timestamp: Date.now(),
+ referrer: typeof document !== "undefined" ? document.referrer : "",
+ });
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(pruneEvents(events)));
+ } catch {
+ // localStorage unavailable — silent fail
+ }
+ }, [slug, title, section]);
+
+ useEffect(() => {
+ trackPageView();
+ }, [trackPageView]);
+
+ return null; // invisible tracking component
+}
+
+export function getPopularPages(
+ limit = 5
+): { slug: string; title: string; section: string; views: number }[] {
+ if (typeof window === "undefined") return [];
+
+ try {
+ const events = getEvents();
+ const counts = new Map();
+
+ for (const event of events) {
+ const existing = counts.get(event.slug);
+ if (existing) {
+ existing.views++;
+ } else {
+ counts.set(event.slug, {
+ slug: event.slug,
+ title: event.title,
+ section: event.section,
+ views: 1,
+ });
+ }
+ }
+
+ return Array.from(counts.values())
+ .sort((a, b) => b.views - a.views)
+ .slice(0, limit);
+ } catch {
+ return [];
+ }
+}
diff --git a/src/app/docs/components/DocsSearchClient.tsx b/src/app/docs/components/DocsSearchClient.tsx
new file mode 100644
index 0000000000..3c9a7b1f1c
--- /dev/null
+++ b/src/app/docs/components/DocsSearchClient.tsx
@@ -0,0 +1,175 @@
+"use client";
+
+import React, { useState, useEffect, useRef, useCallback } from "react";
+import Link from "next/link";
+import Fuse from "fuse.js";
+import { SEARCH_INDEX, SearchItem } from "../lib/searchIndex";
+import { docsNavigation } from "../lib/docsNavigation";
+
+const fuseTitles = new Fuse(SEARCH_INDEX, {
+ keys: [
+ { name: "title", weight: 3 },
+ { name: "slug", weight: 1 },
+ ],
+ threshold: 0.3,
+ includeScore: true,
+});
+
+const fuseContent = new Fuse(SEARCH_INDEX, {
+ keys: [
+ { name: "title", weight: 3 },
+ { name: "content", weight: 2 },
+ { name: "headings", weight: 2 },
+ ],
+ threshold: 0.4,
+ includeScore: true,
+});
+
+export function DocsSearchClient({ onResultClick }: { onResultClick?: () => void }) {
+ const [query, setQuery] = useState("");
+ const [results, setResults] = useState[]>([]);
+ const [isOpen, setIsOpen] = useState(false);
+ const inputRef = useRef(null);
+ const containerRef = useRef(null);
+
+ const handleSearch = useCallback((value: string) => {
+ setQuery(value);
+ if (value.trim().length === 0) {
+ setResults([]);
+ setIsOpen(false);
+ return;
+ }
+ const titleResults = fuseTitles.search(value.trim()).slice(0, 4);
+ const contentResults = fuseContent.search(value.trim()).slice(0, 4);
+
+ const seen = new Set();
+ const merged: Fuse.FuseResult[] = [];
+ for (const r of [...titleResults, ...contentResults]) {
+ if (!seen.has(r.item.slug)) {
+ seen.add(r.item.slug);
+ merged.push(r);
+ }
+ }
+ setResults(merged.slice(0, 8));
+ setIsOpen(true);
+ }, []);
+
+ const handleSelect = useCallback(() => {
+ setQuery("");
+ setResults([]);
+ setIsOpen(false);
+ onResultClick?.();
+ }, [onResultClick]);
+
+ const handleClear = useCallback(() => {
+ setQuery("");
+ setResults([]);
+ setIsOpen(false);
+ inputRef.current?.focus();
+ }, []);
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
+ e.preventDefault();
+ inputRef.current?.focus();
+ inputRef.current?.select();
+ }
+ if (e.key === "Escape") {
+ setIsOpen(false);
+ inputRef.current?.blur();
+ }
+ };
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, []);
+
+ useEffect(() => {
+ const handleClickOutside = (e: MouseEvent) => {
+ if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
+ setIsOpen(false);
+ }
+ };
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, []);
+
+ return (
+
+
+
+ search
+
+ handleSearch(e.target.value)}
+ onFocus={() => {
+ if (results.length > 0) setIsOpen(true);
+ }}
+ placeholder="Search docs..."
+ className="w-full pl-8 pr-8 py-2 text-sm bg-bg-subtle border border-border rounded-lg
+ focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary
+ placeholder:text-text-muted transition-colors"
+ aria-label="Search documentation"
+ />
+ {query && (
+
+ )}
+ {!query && (
+
+ ⌘K
+
+ )}
+
+
+ {isOpen && results.length > 0 && (
+
+
+ {results.map((result) => {
+ const item = result.item;
+ const section = docsNavigation.find((s) => s.items.some((i) => i.slug === item.slug));
+ return (
+
+
article
+
+
{item.title}
+ {section &&
{section.title}
}
+ {item.content && (
+
+ {item.content.slice(0, 80)}...
+
+ )}
+
+
+ arrow_forward
+
+
+ );
+ })}
+
+
+ )}
+
+ {isOpen && query.trim().length > 0 && results.length === 0 && (
+
+
+ No results found for “{query}”
+
+
+ )}
+
+ );
+}
diff --git a/src/app/docs/components/DocsSidebarClient.tsx b/src/app/docs/components/DocsSidebarClient.tsx
new file mode 100644
index 0000000000..cab7e39e70
--- /dev/null
+++ b/src/app/docs/components/DocsSidebarClient.tsx
@@ -0,0 +1,144 @@
+"use client";
+
+import React, { useState } from "react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { cn } from "@/shared/utils/cn";
+import { docsNavigation } from "../lib/docsNavigation";
+import { DocsSearchClient } from "./DocsSearchClient";
+
+export function DocsSidebarClient({ mobileOnly = false }: { mobileOnly?: boolean }) {
+ const pathname = usePathname();
+ const [isOpen, setIsOpen] = useState(false);
+
+ // Extract slug from pathname (e.g., /docs/setup-guide -> setup-guide)
+ const currentSlug = pathname.split("/").pop();
+
+ const isActive = (slug: string) => currentSlug === slug;
+
+ if (mobileOnly) {
+ return (
+
+
+
+ {isOpen && (
+
+
+ setIsOpen(false)} />
+
+
+ )}
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+function MobileSidebarContent({
+ currentSlug,
+ onClose,
+}: {
+ currentSlug: string;
+ onClose: () => void;
+}) {
+ const isActive = (slug: string) => currentSlug === slug;
+
+ return (
+
+
+
OmniRoute Docs
+
+
+
+
+
+
+
+
+ {docsNavigation.map((section, sectionIdx) => (
+
+
+ {section.title}
+
+
+ {section.items.map((item) => (
+ -
+
+ {item.title}
+
+
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/docs/components/FeedbackWidget.tsx b/src/app/docs/components/FeedbackWidget.tsx
new file mode 100644
index 0000000000..96c38e8383
--- /dev/null
+++ b/src/app/docs/components/FeedbackWidget.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import React, { useState } from "react";
+
+export function FeedbackWidget({ slug }: { slug: string }) {
+ const [feedback, setFeedback] = useState<"yes" | "no" | null>(null);
+ const [submitted, setSubmitted] = useState(false);
+
+ const handleFeedback = (type: "yes" | "no") => {
+ setFeedback(type);
+ setSubmitted(true);
+ try {
+ const stored = JSON.parse(localStorage.getItem("docs-feedback") || "{}");
+ stored[slug] = type;
+ localStorage.setItem("docs-feedback", JSON.stringify(stored));
+ } catch {}
+ };
+
+ if (submitted) {
+ return (
+
+
+ check_circle
+
+
Thanks for your feedback!
+
+ );
+ }
+
+ return (
+
+
Was this page helpful?
+
+
+
+
+
+ );
+}
diff --git a/src/app/docs/components/MermaidChartsClient.tsx b/src/app/docs/components/MermaidChartsClient.tsx
new file mode 100644
index 0000000000..0339690bd9
--- /dev/null
+++ b/src/app/docs/components/MermaidChartsClient.tsx
@@ -0,0 +1,19 @@
+"use client";
+
+import dynamic from "next/dynamic";
+
+const MermaidDiagram = dynamic(() => import("./MermaidDiagram").then((mod) => mod.MermaidDiagram), {
+ ssr: false,
+ loading: () => ,
+});
+
+export function MermaidChartsClient({ charts }: { charts: string[] }) {
+ if (charts.length === 0) return null;
+ return (
+
+ {charts.map((chart, i) => (
+
+ ))}
+
+ );
+}
diff --git a/src/app/docs/components/MermaidDiagram.tsx b/src/app/docs/components/MermaidDiagram.tsx
new file mode 100644
index 0000000000..37ed20113b
--- /dev/null
+++ b/src/app/docs/components/MermaidDiagram.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import { useEffect, useRef, useId } from "react";
+
+interface MermaidDiagramProps {
+ chart: string;
+}
+
+export function MermaidDiagram({ chart }: MermaidDiagramProps) {
+ const containerRef = useRef(null);
+ const uniqueId = useId().replace(/:/g, "_");
+ const renderedRef = useRef(false);
+
+ useEffect(() => {
+ if (renderedRef.current || !containerRef.current) return;
+
+ const renderDiagram = async () => {
+ try {
+ const mermaid = (await import("mermaid")).default;
+ mermaid.initialize({
+ startOnLoad: false,
+ theme: "default",
+ securityLevel: "loose",
+ fontFamily: "inherit",
+ });
+
+ const { svg } = await mermaid.render(`mermaid-${uniqueId}`, chart);
+ if (containerRef.current) {
+ containerRef.current.innerHTML = svg;
+ renderedRef.current = true;
+ }
+ } catch (error) {
+ console.error("Mermaid render error:", error);
+ if (containerRef.current) {
+ containerRef.current.innerHTML = `${escapeHtml(chart)}`;
+ }
+ }
+ };
+
+ renderDiagram();
+ }, [chart, uniqueId]);
+
+ return (
+
+ );
+}
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
diff --git a/src/app/docs/components/WhatsNewSection.tsx b/src/app/docs/components/WhatsNewSection.tsx
new file mode 100644
index 0000000000..d1338871a1
--- /dev/null
+++ b/src/app/docs/components/WhatsNewSection.tsx
@@ -0,0 +1,150 @@
+"use client";
+
+import { useState, useEffect } from "react";
+
+interface WhatsNewEntry {
+ version: string;
+ date: string;
+ title: string;
+ description: string;
+ slug?: string;
+ type: "feature" | "improvement" | "fix" | "breaking";
+}
+
+const WHATS_NEW_ENTRIES: WhatsNewEntry[] = [
+ {
+ version: "3.7",
+ date: "2025-06",
+ title: "Documentation Site Overhaul",
+ description:
+ "Full interactive docs site with search, API explorer, Mermaid diagrams, and accessibility compliance.",
+ slug: "/docs",
+ type: "feature",
+ },
+ {
+ version: "3.6",
+ date: "2025-05",
+ title: "MCP Server 37 Tools + A2A Protocol",
+ description:
+ "Expanded MCP to 37 tools with scoped auth. Added A2A v0.3 agent-to-agent protocol.",
+ slug: "mcp-server",
+ type: "feature",
+ },
+ {
+ version: "3.5",
+ date: "2025-04",
+ title: "RTK + Caveman Stacked Compression",
+ description:
+ "Stacked compression pipeline: RTK → Caveman. Save 78-95% eligible tokens on tool outputs.",
+ slug: "rtk-compression",
+ type: "feature",
+ },
+ {
+ version: "3.4",
+ date: "2025-03",
+ title: "1proxy Free Proxy Marketplace",
+ description:
+ "Built-in free proxy marketplace with quality scoring, auto-rotation, and circuit breaker.",
+ slug: "proxy-guide",
+ type: "feature",
+ },
+ {
+ version: "3.3",
+ date: "2025-02",
+ title: "DeepSeek V3.2 + GLM-5.1 Added",
+ description:
+ "New cheap providers: DeepSeek V3.2 at $0.27/M and GLM-5.1 at $0.5/1M with 128K output.",
+ slug: "free-tiers",
+ type: "feature",
+ },
+ {
+ version: "3.2",
+ date: "2025-01",
+ title: "Responses API Full Support",
+ description:
+ "Complete /v1/responses endpoint for Codex and OpenAI Responses API compatibility.",
+ slug: "api-reference",
+ type: "feature",
+ },
+];
+
+const TYPE_BADGES: Record = {
+ feature: { label: "Feature", color: "bg-green-500/10 text-green-600 border-green-500/20" },
+ improvement: { label: "Improvement", color: "bg-blue-500/10 text-blue-600 border-blue-500/20" },
+ fix: { label: "Fix", color: "bg-yellow-500/10 text-yellow-600 border-yellow-500/20" },
+ breaking: { label: "Breaking", color: "bg-red-500/10 text-red-600 border-red-500/20" },
+};
+
+export function WhatsNewSection() {
+ const [expanded, setExpanded] = useState(false);
+
+ const displayEntries = expanded ? WHATS_NEW_ENTRIES : WHATS_NEW_ENTRIES.slice(0, 3);
+
+ return (
+
+
+ new_releases
+ What's New
+
+
+ {displayEntries.map((entry) => (
+
+
+
+ {TYPE_BADGES[entry.type]?.label ?? "Feature"}
+
+
+
+
+ {entry.title}
+ v{entry.version}
+
+
{entry.description}
+
+
{entry.date}
+
+ ))}
+
+ {WHATS_NEW_ENTRIES.length > 3 && (
+
+ )}
+
+ );
+}
+
+export function MigrationGuideBanner({
+ fromVersion,
+ toVersion,
+}: {
+ fromVersion: string;
+ toVersion: string;
+}) {
+ return (
+
+
+ upgrade
+
+ Migration Guide: v{fromVersion} → v{toVersion}
+
+
+
+ This page describes features from v{toVersion}. If you're upgrading from v{fromVersion}
+ , check the breaking changes above.
+
+
+ );
+}
diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx
new file mode 100644
index 0000000000..4890d479c4
--- /dev/null
+++ b/src/app/docs/layout.tsx
@@ -0,0 +1,68 @@
+import Link from "next/link";
+import { ReactNode, Suspense } from "react";
+import { DocsSidebarClient } from "./components/DocsSidebarClient";
+import { DocsLocaleSwitcher } from "./components/DocsI18n";
+
+export const metadata = {
+ title: {
+ template: "%s — OmniRoute Docs",
+ default: "OmniRoute Documentation",
+ },
+ description:
+ "Comprehensive documentation for OmniRoute AI gateway — setup, API, compression, deployment, and more.",
+ robots: {
+ index: true,
+ follow: true,
+ },
+};
+
+export default function DocsLayout({ children }: { children: ReactNode }) {
+ return (
+
+
+ Skip to main content
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/app/docs/lib/docs-auto-generated.ts b/src/app/docs/lib/docs-auto-generated.ts
new file mode 100644
index 0000000000..072f0a65ec
--- /dev/null
+++ b/src/app/docs/lib/docs-auto-generated.ts
@@ -0,0 +1,798 @@
+// AUTO-GENERATED by scripts/generate-docs-index.mjs — DO NOT EDIT MANUALLY
+// Regenerate with: node scripts/generate-docs-index.mjs
+
+export interface AutoGenDocItem {
+ slug: string;
+ title: string;
+ fileName: string;
+}
+
+export interface AutoGenNavSection {
+ title: string;
+ items: AutoGenDocItem[];
+}
+
+export interface AutoGenSearchItem {
+ slug: string;
+ title: string;
+ fileName: string;
+ section: string;
+ content: string;
+ headings: string[];
+}
+
+export const autoNavSections: AutoGenNavSection[] = [
+ {
+ title: "Getting Started",
+ items: [
+ {
+ slug: "architecture",
+ title: "OmniRoute Architecture",
+ fileName: "ARCHITECTURE.md",
+ },
+ {
+ slug: "cli-tools",
+ title: "CLI Tools Setup Guide",
+ fileName: "CLI-TOOLS.md",
+ },
+ {
+ slug: "setup-guide",
+ title: "Setup Guide",
+ fileName: "SETUP_GUIDE.md",
+ },
+ {
+ slug: "user-guide",
+ title: "User Guide",
+ fileName: "USER_GUIDE.md",
+ },
+ ],
+ },
+ {
+ title: "Features",
+ items: [
+ {
+ slug: "auto-combo",
+ title: "OmniRoute Auto-Combo Engine",
+ fileName: "AUTO-COMBO.md",
+ },
+ {
+ slug: "compression-engines",
+ title: "Compression Engines",
+ fileName: "COMPRESSION_ENGINES.md",
+ },
+ {
+ slug: "compression-guide",
+ title: "🗜️ Prompt Compression Guide",
+ fileName: "COMPRESSION_GUIDE.md",
+ },
+ {
+ slug: "compression-language-packs",
+ title: "Compression Language Packs",
+ fileName: "COMPRESSION_LANGUAGE_PACKS.md",
+ },
+ {
+ slug: "compression-rules-format",
+ title: "Compression Rules Format",
+ fileName: "COMPRESSION_RULES_FORMAT.md",
+ },
+ {
+ slug: "features",
+ title: "OmniRoute — Dashboard Features Gallery",
+ fileName: "FEATURES.md",
+ },
+ {
+ slug: "free-tiers",
+ title: "🆓 Free LLM API Providers — Consolidated Directory",
+ fileName: "FREE_TIERS.md",
+ },
+ {
+ slug: "rtk-compression",
+ title: "RTK Compression",
+ fileName: "RTK_COMPRESSION.md",
+ },
+ ],
+ },
+ {
+ title: "API & Protocols",
+ items: [
+ {
+ slug: "a2a-server",
+ title: "OmniRoute A2A Server Documentation",
+ fileName: "A2A-SERVER.md",
+ },
+ {
+ slug: "api-reference",
+ title: "API Reference",
+ fileName: "API_REFERENCE.md",
+ },
+ {
+ slug: "mcp-server",
+ title: "OmniRoute MCP Server Documentation",
+ fileName: "MCP-SERVER.md",
+ },
+ ],
+ },
+ {
+ title: "Deployment",
+ items: [
+ {
+ slug: "docker-guide",
+ title: "🐳 Docker Guide",
+ fileName: "DOCKER_GUIDE.md",
+ },
+ {
+ slug: "fly-io-deployment-guide",
+ title: "OmniRoute Fly.io 部署指南",
+ fileName: "FLY_IO_DEPLOYMENT_GUIDE.md",
+ },
+ {
+ slug: "pwa-guide",
+ title: "Progressive Web App (PWA) Guide",
+ fileName: "PWA_GUIDE.md",
+ },
+ {
+ slug: "termux-guide",
+ title: "Termux Headless Setup",
+ fileName: "TERMUX_GUIDE.md",
+ },
+ {
+ slug: "vm-deployment-guide",
+ title: "OmniRoute — Deployment Guide on VM with Cloudflare",
+ fileName: "VM_DEPLOYMENT_GUIDE.md",
+ },
+ ],
+ },
+ {
+ title: "Operations",
+ items: [
+ {
+ slug: "environment",
+ title: "Environment Variables Reference",
+ fileName: "ENVIRONMENT.md",
+ },
+ {
+ slug: "proxy-guide",
+ title: "OmniRoute Proxy Guide",
+ fileName: "PROXY_GUIDE.md",
+ },
+ {
+ slug: "resilience-guide",
+ title: "🛡️ Resilience Guide",
+ fileName: "RESILIENCE_GUIDE.md",
+ },
+ {
+ slug: "troubleshooting",
+ title: "Troubleshooting",
+ fileName: "TROUBLESHOOTING.md",
+ },
+ ],
+ },
+ {
+ title: "Development",
+ items: [
+ {
+ slug: "codebase-documentation",
+ title: "omniroute — Codebase Documentation",
+ fileName: "CODEBASE_DOCUMENTATION.md",
+ },
+ {
+ slug: "coverage-plan",
+ title: "Test Coverage Plan",
+ fileName: "COVERAGE_PLAN.md",
+ },
+ {
+ slug: "i18n",
+ title: "i18n — Internationalization Guide",
+ fileName: "I18N.md",
+ },
+ {
+ slug: "release-checklist",
+ title: "Release Checklist",
+ fileName: "RELEASE_CHECKLIST.md",
+ },
+ {
+ slug: "uninstall",
+ title: "OmniRoute — Uninstall Guide",
+ fileName: "UNINSTALL.md",
+ },
+ ],
+ },
+];
+
+export const autoSearchIndex: AutoGenSearchItem[] = [
+ {
+ slug: "architecture",
+ title: "OmniRoute Architecture",
+ fileName: "ARCHITECTURE.md",
+ section: "Getting Started",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "Executive Summary",
+ "Scope and Boundaries",
+ "In Scope",
+ "Out of Scope",
+ "Dashboard Surface (Current)",
+ "High-Level System Context",
+ "Core Runtime Components",
+ "1) API and Routing Layer (Next.js App Routes)",
+ "2) SSE + Translation Core",
+ "3) Persistence Layer",
+ ],
+ },
+ {
+ slug: "cli-tools",
+ title: "CLI Tools Setup Guide",
+ fileName: "CLI-TOOLS.md",
+ section: "Getting Started",
+ content:
+ "This guide explains how to install and configure all supported AI coding CLI tools to use OmniRoute as the unified backend, giving you centralized key management, cost tracking, model switching, and request logging across every tool. The dashboard cards in /dashboard/cli-tools are generated from src",
+ headings: [
+ "How It Works",
+ "Supported Tools (Dashboard Source of Truth)",
+ "CLI fingerprint sync (Agents + Settings)",
+ "Step 1 — Get an OmniRoute API Key",
+ "Step 2 — Install CLI Tools",
+ "Step 3 — Set Global Environment Variables",
+ "Step 4 — Configure Each Tool",
+ "Claude Code",
+ "OpenAI Codex",
+ "OpenCode",
+ ],
+ },
+ {
+ slug: "setup-guide",
+ title: "Setup Guide",
+ fileName: "SETUP_GUIDE.md",
+ section: "Getting Started",
+ content:
+ "Complete setup reference for OmniRoute. For the quick version, see the Quick Start in README. - Install Methods - CLI Tool Configuration - Protocol Setup (MCP + A2A) - Timeout Configuration - Split-Port Mode - Void Linux (xbps-src) - Uninstalling -------------------- --------------------------------",
+ headings: [
+ "Table of Contents",
+ "Install Methods",
+ "npm (recommended)",
+ "pnpm",
+ "Arch Linux (AUR)",
+ "From Source",
+ "Docker",
+ "CLI Options",
+ "CLI Tool Configuration",
+ "1) Connect Providers and Create API Key",
+ ],
+ },
+ {
+ slug: "user-guide",
+ title: "User Guide",
+ fileName: "USER_GUIDE.md",
+ section: "Getting Started",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "Table of Contents",
+ "💰 Pricing at a Glance",
+ "🎯 Use Cases",
+ 'Case 1: "I have Claude Pro subscription"',
+ 'Case 2: "I want zero cost"',
+ 'Case 3: "I need 24/7 coding, no interruptions"',
+ 'Case 4: "I want FREE AI in OpenClaw"',
+ "📖 Provider Setup",
+ "🔐 Subscription Providers",
+ "Claude Code (Pro/Max)",
+ ],
+ },
+ {
+ slug: "auto-combo",
+ title: "OmniRoute Auto-Combo Engine",
+ fileName: "AUTO-COMBO.md",
+ section: "Features",
+ content:
+ "Self-managing model chains with adaptive scoring The Auto-Combo Engine dynamically selects the best provider/model for each request using a 6-factor scoring function: Factor Weight Description :--------- :----- :---------------------------------------------- Quota 0.20 Remaining capacity [0..1] Heal",
+ headings: [
+ "How It Works",
+ "Mode Packs",
+ "Self-Healing",
+ "Bandit Exploration",
+ "API",
+ "Task Fitness",
+ "Files",
+ ],
+ },
+ {
+ slug: "compression-engines",
+ title: "Compression Engines",
+ fileName: "COMPRESSION_ENGINES.md",
+ section: "Features",
+ content:
+ "OmniRoute compression is built around engine contracts. A mode can run one engine directly (caveman or rtk) or a deterministic stacked pipeline that executes multiple engines in order. Mode Engine path Intended input ------------ ---------------------------------- -----------------------------------",
+ headings: [
+ "Modes",
+ "Engine Registry",
+ "Caveman",
+ "RTK",
+ "Stacked Pipelines",
+ "Compression Combos",
+ "API Surface",
+ "MCP Tools",
+ "Validation",
+ ],
+ },
+ {
+ slug: "compression-guide",
+ title: "🗜️ Prompt Compression Guide",
+ fileName: "COMPRESSION_GUIDE.md",
+ section: "Features",
+ content:
+ "Save 15-95% on eligible context automatically. For a quick overview, see the README Compression section. OmniRoute implements a modular prompt compression pipeline that runs proactively before requests hit upstream providers. This means your token savings happen transparently — no changes needed to ",
+ headings: [
+ "Overview",
+ "Compression Modes",
+ "Off",
+ "Lite Mode (~15% savings, <1ms latency)",
+ "Standard Mode (~30% savings)",
+ "Aggressive Mode (~50% savings)",
+ "Ultra Mode (~75% savings)",
+ "RTK Mode (60-90% upstream range)",
+ "Stacked Mode (78-95% eligible range)",
+ "Upstream Savings Math",
+ ],
+ },
+ {
+ slug: "compression-language-packs",
+ title: "Compression Language Packs",
+ fileName: "COMPRESSION_LANGUAGE_PACKS.md",
+ section: "Features",
+ content:
+ "Caveman compression can load language-specific rule packs in addition to the built-in English rules. This keeps the core engine stable while allowing Portuguese, Spanish, German, French, Japanese, and future language packs to evolve independently. Language packs live under: Current shipped packs inc",
+ headings: [
+ "Location",
+ "Language Detection",
+ "Config Shape",
+ "Adding a Language Pack",
+ "API",
+ "Operational Notes",
+ ],
+ },
+ {
+ slug: "compression-rules-format",
+ title: "Compression Rules Format",
+ fileName: "COMPRESSION_RULES_FORMAT.md",
+ section: "Features",
+ content:
+ "Compression rules are JSON files loaded at runtime. They are intentionally data-only so new language packs and RTK command filters can be reviewed without changing engine code. Caveman rule packs live under: Each pack contains replacements that apply to normal prose after protected regions are isola",
+ headings: [
+ "Caveman Rule Packs",
+ "Caveman Fields",
+ "RTK Filter Packs",
+ "RTK Fields",
+ "Safety Rules",
+ "Validation",
+ ],
+ },
+ {
+ slug: "features",
+ title: "OmniRoute — Dashboard Features Gallery",
+ fileName: "FEATURES.md",
+ section: "Features",
+ content:
+ "🌐 Main README translations: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indones",
+ headings: [
+ "🔌 Providers",
+ "🎨 Combos",
+ "📊 Analytics",
+ "🏥 System Health",
+ "🔧 Translator Playground",
+ "🎮 Model Playground _(v2.0.9+)_",
+ "🎨 Themes _(v2.0.5+)_",
+ "⚙️ Settings",
+ "🔧 CLI Tools",
+ "🤖 CLI Agents _(v2.0.11+)_",
+ ],
+ },
+ {
+ slug: "free-tiers",
+ title: "🆓 Free LLM API Providers — Consolidated Directory",
+ fileName: "FREE_TIERS.md",
+ section: "Features",
+ content:
+ "The ultimate aggregated reference for all permanently free LLM API providers. Consolidated from 6 community repositories. Use with OmniRoute to route through 25+ free providers simultaneously. Last consolidated: May 2026 · Sources: awesome-free-llm-apis, awesome-free-llm-apis2, free-llm-api-resource",
+ headings: [
+ "Table of Contents",
+ "Quick Comparison",
+ "Provider APIs (First-Party)",
+ "Google Gemini 🇺🇸",
+ "Mistral AI 🇫🇷",
+ "Cohere 🇨🇦",
+ "Z.AI (Zhipu AI) 🇨🇳",
+ "IBM watsonx 🇺🇸",
+ "Inference Providers (Third-Party)",
+ "Groq 🇺🇸",
+ ],
+ },
+ {
+ slug: "rtk-compression",
+ title: "RTK Compression",
+ fileName: "RTK_COMPRESSION.md",
+ section: "Features",
+ content:
+ "RTK compression is OmniRoute's command-aware compression engine for terminal and tool output. It is designed for coding-agent sessions where most context growth comes from test logs, build output, package manager noise, shell transcripts, Docker output, git output, and stack traces. RTK can run dire",
+ headings: [
+ "What It Compresses",
+ "Filter Resolution",
+ "Filter DSL",
+ "Configuration",
+ "API",
+ "Raw Output Recovery",
+ "Verify Gate",
+ "Extending RTK",
+ ],
+ },
+ {
+ slug: "a2a-server",
+ title: "OmniRoute A2A Server Documentation",
+ fileName: "A2A-SERVER.md",
+ section: "API & Protocols",
+ content:
+ "Agent-to-Agent Protocol v0.3 — OmniRoute as an intelligent routing agent Returns the Agent Card describing OmniRoute's capabilities, skills, and authentication requirements. Sends a message to a skill and waits for the complete response. Response: Same as message/send but returns Server-Sent Events ",
+ headings: [
+ "Agent Discovery",
+ "Authentication",
+ "Enablement",
+ "JSON-RPC 2.0 Methods",
+ "message/send — Synchronous Execution",
+ "message/stream — SSE Streaming",
+ "tasks/get — Query Task Status",
+ "tasks/cancel — Cancel a Task",
+ "Available Skills",
+ "Task Lifecycle",
+ ],
+ },
+ {
+ slug: "api-reference",
+ title: "API Reference",
+ fileName: "API_REFERENCE.md",
+ section: "API & Protocols",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "Table of Contents",
+ "Chat Completions",
+ "Custom Headers",
+ "Embeddings",
+ "Image Generation",
+ "List Models",
+ "Compatibility Endpoints",
+ "Dedicated Provider Routes",
+ "Semantic Cache",
+ "Dashboard & Management",
+ ],
+ },
+ {
+ slug: "mcp-server",
+ title: "OmniRoute MCP Server Documentation",
+ fileName: "MCP-SERVER.md",
+ section: "API & Protocols",
+ content:
+ "Model Context Protocol server with 37 tools across routing, cache, compression, memory, skills, and proxy operations OmniRoute MCP is built-in. Start it with: Or via the open-sse transport: See MCP Client Configuration for Claude Desktop, Cursor, Cline, and compatible MCP client setup. -------------",
+ headings: [
+ "Installation",
+ "IDE Configuration",
+ "Essential Tools (8)",
+ "Advanced Tools (8)",
+ "Cache Tools (2)",
+ "Compression Tools (5)",
+ "Other Tool Groups",
+ "Authentication",
+ "Audit Logging",
+ "Files",
+ ],
+ },
+ {
+ slug: "docker-guide",
+ title: "🐳 Docker Guide",
+ fileName: "DOCKER_GUIDE.md",
+ section: "Deployment",
+ content:
+ "Complete Docker deployment reference. For a quick start, see the README Docker section. - Quick Run - With Environment File - Docker Compose - Docker Compose with Caddy (HTTPS) - Cloudflare Quick Tunnel - Image Tags - Important Notes --------------------- -------- ------ --------------------- diegos",
+ headings: [
+ "Table of Contents",
+ "Quick Run",
+ "With Environment File",
+ "Docker Compose",
+ "Docker Compose with Caddy (HTTPS Auto-TLS)",
+ "Cloudflare Quick Tunnel",
+ "Tunnel Notes",
+ "Image Tags",
+ "Important Notes",
+ "See Also",
+ ],
+ },
+ {
+ slug: "fly-io-deployment-guide",
+ title: "OmniRoute Fly.io 部署指南",
+ fileName: "FLY_IO_DEPLOYMENT_GUIDE.md",
+ section: "Deployment",
+ content:
+ "本文档记录 OmniRoute 在 Fly.io 上的实际部署方法,适用于两类场景: - 首次把当前项目部署到 Fly.io - 后续代码更新后继续发布 - 新项目参考同样流程部署 本文基于当前项目已经验证通过的配置整理,应用名为 omniroute。 当前仓库中的 fly.toml 已确认包含以下关键项: 说明: - app = 'omniroute' 决定实际部署到哪个 Fly 应用 - destination = '/data' 决定持久卷挂载目录 - 本项目必须让 DATADIR=/data,否则数据库和密钥会写到容器临时目录 --- Windows PowerShell: 如果安装脚",
+ headings: [
+ "1. 部署目标",
+ "2. 当前项目关键配置",
+ "3. 必备工具",
+ "3.1 安装 Fly CLI",
+ "3.2 登录 Fly 账号",
+ "3.3 检查登录状态",
+ "4. 首次部署当前项目",
+ "4.1 获取代码并进入目录",
+ "4.2 确认应用名",
+ "4.3 创建应用",
+ ],
+ },
+ {
+ slug: "pwa-guide",
+ title: "Progressive Web App (PWA) Guide",
+ fileName: "PWA_GUIDE.md",
+ section: "Deployment",
+ content:
+ 'OmniRoute ships as a fully installable Progressive Web App. When you access the dashboard from any mobile browser — Android (Chrome) or iOS (Safari) — you can "Add to Home Screen" and get a native app-like experience with no app store required. A Progressive Web App turns the OmniRoute web dashboard',
+ headings: [
+ "What Is a PWA?",
+ "Installation",
+ "Android (Chrome)",
+ "iOS (Safari)",
+ "Desktop (Chrome / Edge)",
+ "Features",
+ "Fullscreen Experience",
+ "Offline Support",
+ "Offline Page",
+ "App Icons",
+ ],
+ },
+ {
+ slug: "termux-guide",
+ title: "Termux Headless Setup",
+ fileName: "TERMUX_GUIDE.md",
+ section: "Deployment",
+ content:
+ "OmniRoute can run as a headless server on Android through Termux. The Electron desktop app is not supported in Termux, but the web dashboard and OpenAI-compatible API work from the local browser or from other devices on the same network. Install Termux from F-Droid or GitHub releases, then update pa",
+ headings: [
+ "Prerequisites",
+ "Install",
+ "Run",
+ "Background Execution",
+ "Access From Other Devices",
+ "Data Directory",
+ "Limitations",
+ "Troubleshooting",
+ "better-sqlite3 Build Errors",
+ "Port Already In Use",
+ ],
+ },
+ {
+ slug: "vm-deployment-guide",
+ title: "OmniRoute — Deployment Guide on VM with Cloudflare",
+ fileName: "VM_DEPLOYMENT_GUIDE.md",
+ section: "Deployment",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "Prerequisites",
+ "1. Configure the VM",
+ "1.1 Create the instance",
+ "1.2 Connect via SSH",
+ "1.3 Update the system",
+ "1.4 Install Docker",
+ "1.5 Install nginx",
+ "1.6 Configure Firewall (UFW)",
+ "2. Install OmniRoute",
+ "2.1 Create configuration directory",
+ ],
+ },
+ {
+ slug: "environment",
+ title: "Environment Variables Reference",
+ fileName: "ENVIRONMENT.md",
+ section: "Operations",
+ content:
+ "Complete reference for every environment variable recognized by OmniRoute. For a quick-start template, see .env.example. These must be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults. Variable Required Default Source File Descript",
+ headings: [
+ "Table of Contents",
+ "1. Required Secrets",
+ "Generation Commands",
+ "2. Storage & Database",
+ "Scenarios",
+ "3. Network & Ports",
+ "Port Modes",
+ "4. Security & Authentication",
+ "Hardening Checklist",
+ "5. Input Sanitization & PII Protection",
+ ],
+ },
+ {
+ slug: "proxy-guide",
+ title: "OmniRoute Proxy Guide",
+ fileName: "PROXY_GUIDE.md",
+ section: "Operations",
+ content:
+ "Bypass geographic blocks, protect your identity, and route AI traffic through any proxy — with zero configuration complexity. OmniRoute includes a full-featured proxy management system that lets you route upstream AI provider traffic through HTTP, HTTPS, or SOCKS5 proxies. Whether you're in a blocke",
+ headings: [
+ "Table of Contents",
+ "Why Use Proxies?",
+ "Architecture Overview",
+ "Key Components",
+ "3-Level Proxy System",
+ "How Resolution Works",
+ "What Gets Proxied",
+ "Proxy Registry (CRUD)",
+ "Creating a Proxy",
+ "Updating a Proxy",
+ ],
+ },
+ {
+ slug: "resilience-guide",
+ title: "🛡️ Resilience Guide",
+ fileName: "RESILIENCE_GUIDE.md",
+ section: "Operations",
+ content:
+ "How OmniRoute keeps your AI coding workflow running when providers fail. OmniRoute implements a multi-layered resilience system that ensures zero downtime: ------------ ------- ------------------------------------ Queue Size 10 Max queued requests per connection Pacing Interval 0ms Minimum gap betwe",
+ headings: [
+ "Overview",
+ "Request Queue & Pacing",
+ "Connection Cooldown",
+ "Circuit Breaker",
+ "Wait For Cooldown",
+ "Anti-Thundering Herd",
+ "Combo Fallback Chains",
+ "13 Routing Strategies",
+ "TLS Fingerprint Spoofing",
+ "Health Dashboard",
+ ],
+ },
+ {
+ slug: "troubleshooting",
+ title: "Troubleshooting",
+ fileName: "TROUBLESHOOTING.md",
+ section: "Operations",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "Quick Fixes",
+ "Node.js Compatibility",
+ 'Login page crashes or shows "Module self-registration" error',
+ 'macOS: dlopen / "slice is not valid mach-o file"',
+ "Proxy Issues",
+ 'Provider validation shows "fetch failed"',
+ 'Token health check fails with "fetch failed"',
+ 'SOCKS5 proxy returns "invalid onRequestStart method"',
+ "Provider Issues",
+ '"Language model did not provide messages"',
+ ],
+ },
+ {
+ slug: "codebase-documentation",
+ title: "omniroute — Codebase Documentation",
+ fileName: "CODEBASE_DOCUMENTATION.md",
+ section: "Development",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "1. What Is omniroute?",
+ "2. Architecture Overview",
+ "Core Principle: Hub-and-Spoke Translation",
+ "3. Project Structure",
+ "4. Module-by-Module Breakdown",
+ "4.1 Config (open-sse/config/)",
+ "Credential Loading Flow",
+ "4.2 Executors (open-sse/executors/)",
+ "4.3 Handlers (open-sse/handlers/)",
+ "Request Lifecycle (chatCore.ts)",
+ ],
+ },
+ {
+ slug: "coverage-plan",
+ title: "Test Coverage Plan",
+ fileName: "COVERAGE_PLAN.md",
+ section: "Development",
+ content:
+ "Last updated: 2026-03-28 There are multiple coverage numbers depending on how the report is computed. For planning, only one of them is useful. Metric Scope Statements / Lines Branches Functions Notes -------------------- ----------------------------------------------------- -----------------: -----",
+ headings: [
+ "Baseline",
+ "Rules",
+ "Current command set",
+ "Milestones",
+ "Priority hotspots",
+ "Execution checklist",
+ "Phase 1: 56.95% -> 60%",
+ "Phase 2: 60% -> 65%",
+ "Phase 3: 65% -> 70%",
+ "Phase 4: 70% -> 75%",
+ ],
+ },
+ {
+ slug: "i18n",
+ title: "i18n — Internationalization Guide",
+ fileName: "I18N.md",
+ section: "Development",
+ content:
+ "OmniRoute supports 30 languages with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew. 🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇩🇪 Deutsch 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇯🇵 日本語 🇰🇷 한국어 🇸🇦 العربية 🇮🇳 ",
+ headings: [
+ "Quick Reference",
+ "Architecture",
+ "Source of Truth",
+ "Runtime Flow",
+ "Supported Locales",
+ "Adding a New Language",
+ "1. Register the Locale",
+ "2. Add to Generator",
+ "3. Generate Initial Translation",
+ "4. Review & Fix Auto-Translations",
+ ],
+ },
+ {
+ slug: "release-checklist",
+ title: "Release Checklist",
+ fileName: "RELEASE_CHECKLIST.md",
+ section: "Development",
+ content:
+ "Use this checklist before tagging or publishing a new OmniRoute release. 1. Bump package.json version (x.y.z) in the release branch. 2. Move release notes from [Unreleased] in CHANGELOG.md to a dated section: - [x.y.z] — YYYY-MM-DD 3. Keep [Unreleased] as the first changelog section for upcoming wor",
+ headings: ["Version and Changelog", "API Docs", "Runtime Docs", "Automated Check"],
+ },
+ {
+ slug: "uninstall",
+ title: "OmniRoute — Uninstall Guide",
+ fileName: "UNINSTALL.md",
+ section: "Development",
+ content:
+ "🌐 Languages: 🇺🇸 English 🇧🇷 Português (Brasil) 🇪🇸 Español 🇫🇷 Français 🇮🇹 Italiano 🇷🇺 Русский 🇨🇳 中文 (简体) 🇩🇪 Deutsch 🇮🇳 हिन्दी 🇹🇭 ไทย 🇺🇦 Українська 🇸🇦 العربية 🇯🇵 日本語 🇻🇳 Tiếng Việt 🇧🇬 Български 🇩🇰 Dansk 🇫🇮 Suomi 🇮🇱 עברית 🇭🇺 Magyar 🇮🇩 Bahasa Indonesia 🇰🇷 한국어 🇲\ud83c",
+ headings: [
+ "Quick Uninstall (v3.6.2+)",
+ "Keep Your Data",
+ "Full Removal",
+ "Manual Uninstall",
+ "NPM Global Install",
+ "pnpm Global Install",
+ "Docker",
+ "Docker Compose",
+ "Electron Desktop App",
+ "Source Install (git clone)",
+ ],
+ },
+ {
+ slug: "api-explorer",
+ title: "API Explorer",
+ fileName: "API_REFERENCE.md",
+ section: "API & Protocols",
+ content: "interactive try it live api explorer endpoint test request response curl example",
+ headings: ["Try It", "Endpoints"],
+ },
+];
+
+export const autoAllSlugs: string[] = [
+ "architecture",
+ "cli-tools",
+ "setup-guide",
+ "user-guide",
+ "auto-combo",
+ "compression-engines",
+ "compression-guide",
+ "compression-language-packs",
+ "compression-rules-format",
+ "features",
+ "free-tiers",
+ "rtk-compression",
+ "a2a-server",
+ "api-reference",
+ "mcp-server",
+ "docker-guide",
+ "fly-io-deployment-guide",
+ "pwa-guide",
+ "termux-guide",
+ "vm-deployment-guide",
+ "environment",
+ "proxy-guide",
+ "resilience-guide",
+ "troubleshooting",
+ "codebase-documentation",
+ "coverage-plan",
+ "i18n",
+ "release-checklist",
+ "uninstall",
+];
diff --git a/src/app/docs/lib/docsNavigation.ts b/src/app/docs/lib/docsNavigation.ts
new file mode 100644
index 0000000000..3b118d727e
--- /dev/null
+++ b/src/app/docs/lib/docsNavigation.ts
@@ -0,0 +1,36 @@
+import { autoNavSections } from "./docs-auto-generated";
+
+export interface DocNavItem {
+ slug: string;
+ title: string;
+ fileName: string;
+}
+
+export interface DocNavSection {
+ title: string;
+ items: DocNavItem[];
+}
+
+const MANUAL_OVERRIDES: Record> = {
+ "setup-guide": { title: "Setup Guide" },
+ "user-guide": { title: "User Guide" },
+ "cli-tools": { title: "CLI Tools" },
+ "compression-rules-format": { title: "Rules Format" },
+ "compression-language-packs": { title: "Language Packs" },
+ "vm-deployment-guide": { title: "VM Deployment" },
+ "fly-io-deployment-guide": { title: "Fly.io Deployment" },
+ "codebase-documentation": { title: "Codebase Docs" },
+ "release-checklist": { title: "Release Checklist" },
+};
+
+export const docsNavigation: DocNavSection[] = autoNavSections.map((section) => ({
+ title: section.title,
+ items: section.items.map((item) => {
+ const override = MANUAL_OVERRIDES[item.slug];
+ return {
+ slug: item.slug,
+ title: (override?.title ?? item.title) as string,
+ fileName: item.fileName,
+ };
+ }),
+}));
diff --git a/src/app/docs/lib/searchIndex.ts b/src/app/docs/lib/searchIndex.ts
new file mode 100644
index 0000000000..f3340749c7
--- /dev/null
+++ b/src/app/docs/lib/searchIndex.ts
@@ -0,0 +1,12 @@
+import { autoSearchIndex } from "./docs-auto-generated";
+
+export interface SearchItem {
+ slug: string;
+ title: string;
+ fileName: string;
+ section: string;
+ content: string;
+ headings: string[];
+}
+
+export const SEARCH_INDEX: SearchItem[] = autoSearchIndex as SearchItem[];
diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx
index a1b9f4d071..a6113cea60 100644
--- a/src/app/docs/page.tsx
+++ b/src/app/docs/page.tsx
@@ -1,610 +1,107 @@
import Link from "next/link";
-import { useTranslations } from "next-intl";
-import { APP_CONFIG } from "@/shared/constants/config";
-import { FREE_PROVIDERS, OAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/providers";
-import {
- DOCS_DEPLOYMENT_GUIDES,
- DOCS_ENDPOINT_ROWS,
- DOCS_FEATURE_ITEMS,
- DOCS_MANAGEMENT_ENDPOINT_ROWS,
- DOCS_MCP_TOOL_GROUPS,
- DOCS_TOC_ITEMS,
- DOCS_TROUBLESHOOTING_KEYS,
- DOCS_USE_CASE_ITEMS,
-} from "./content";
+import { Metadata } from "next";
+import { docsNavigation } from "./lib/docsNavigation";
+import { WhatsNewSection } from "./components/WhatsNewSection";
-function ProviderTable({
- title,
- providers,
- colorDot,
- maxHeight,
-}: {
- title: string;
- providers: Record;
- colorDot: string;
- maxHeight?: string;
-}) {
- const t = useTranslations("docs");
- const entries = Object.values(providers) as any[];
- const isLargeList = entries.length > 20;
+export const metadata: Metadata = {
+ title: "OmniRoute Documentation",
+ description:
+ "Everything you need to route, compress, and scale your AI — setup guides, API reference, compression, deployment, and more.",
+ openGraph: {
+ title: "OmniRoute Documentation",
+ description:
+ "Comprehensive docs for OmniRoute AI gateway — setup, API, compression, deployment, and more.",
+ type: "website",
+ url: "https://omniroute.online/docs",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "OmniRoute Documentation",
+ description: "Comprehensive docs for OmniRoute AI gateway",
+ },
+};
+const featuredLinks = [
+ {
+ slug: "setup-guide",
+ title: "Setup Guide",
+ icon: "rocket_launch",
+ desc: "Get OmniRoute running in 3 minutes",
+ },
+ {
+ slug: "api-reference",
+ title: "API Reference",
+ icon: "code",
+ desc: "All endpoints with examples",
+ },
+ {
+ slug: "compression-guide",
+ title: "Compression Guide",
+ icon: "compress",
+ desc: "Save 15-95% eligible tokens automatically",
+ },
+];
+
+export default function DocsHomePage() {
return (
-
-
-
-
{title}
-
- {t("providersCount", { count: entries.length })}
-
+
+
+
OmniRoute Documentation
+
+ Everything you need to route, compress, and scale your AI
+
+
+ Press{" "}
+
+ ⌘K
+ {" "}
+ to search the docs
+
-
- {entries.map((p) => (
-
+ {featuredLinks.map((link) => (
+
- {p.alias}/
- {p.name}
+
+ {link.icon}
+
+
+ {link.title}
+
+ {link.desc}
+
+ ))}
+
+
+
+ {docsNavigation.map((section, sectionIdx) => (
+
+
{section.title}
+
+ {section.items.map((item) => (
+ -
+
+ {item.title}
+
+
+ ))}
+
))}
-
- );
-}
-export default function DocsPage() {
- const t = useTranslations("docs");
-
- const totalProviders =
- Object.keys(FREE_PROVIDERS).length +
- Object.keys(OAUTH_PROVIDERS).length +
- Object.keys(APIKEY_PROVIDERS).length;
-
- const endpointRows = DOCS_ENDPOINT_ROWS.map((row) => ({
- ...row,
- note: t(row.noteKey),
- }));
- const managementEndpointRows = DOCS_MANAGEMENT_ENDPOINT_ROWS.map((row) => ({
- ...row,
- note: t(row.noteKey),
- }));
-
- const featureItems = DOCS_FEATURE_ITEMS.map((item) => ({
- ...item,
- title: t(item.titleKey),
- text: t(item.textKey),
- }));
-
- const useCases = DOCS_USE_CASE_ITEMS.map((item) => ({
- ...item,
- title: t(item.titleKey),
- text: t(item.textKey),
- }));
- const deploymentGuides = DOCS_DEPLOYMENT_GUIDES.map((item) => ({
- ...item,
- title: t(item.titleKey),
- text: t(item.textKey),
- }));
-
- const troubleshootingItems = DOCS_TROUBLESHOOTING_KEYS.map((key) => t(key));
- const tocItems = DOCS_TOC_ITEMS.map((item) => ({ ...item, label: t(item.labelKey) }));
- const mcpToolGroups = DOCS_MCP_TOOL_GROUPS.map((group) => ({
- ...group,
- title: t(group.titleKey),
- text: t(group.textKey),
- }));
- const totalMcpTools = DOCS_MCP_TOOL_GROUPS.reduce((sum, group) => sum + group.tools.length, 0);
-
- const providerPrefixRows = [
- ...Object.values(FREE_PROVIDERS).map((p) => ({ ...p, type: "free" as const })),
- ...Object.values(OAUTH_PROVIDERS).map((p) => ({ ...p, type: "oauth" as const })),
- ...Object.values(APIKEY_PROVIDERS).map((p) => ({ ...p, type: "apiKey" as const })),
- ];
-
- const getProviderTypeLabel = (type: "free" | "oauth" | "apiKey") => {
- if (type === "free") return t("providerTypeFree");
- if (type === "oauth") return t("providerTypeOAuth");
- return t("providerTypeApiKey");
- };
-
- return (
-
-
-
-
-
-
- {t("documentationVersion", { version: APP_CONFIG.version })}
-
-
- {APP_CONFIG.name} {t("docsLabel")}
-
-
- {t("docsHeroDescription")}
-
-
-
-
-
-
-
-
-
- {t("quickStart")}
-
- -
- {t("quickStartStep1Title")}
-
- {t("quickStartStep1Prefix")}{" "}
- npx omniroute{" "}
- {t("quickStartStep1Middle")}{" "}
- npm start.
-
-
- -
- {t("quickStartStep2Title")}
-
{t("quickStartStep2Text")}
-
- -
- {t("quickStartStep3Title")}
-
{t("quickStartStep3Text")}
-
- -
- {t("quickStartStep4Title")}
-
- {t("quickStartStep4Prefix")}{" "}
- https://<host>/v1.{" "}
- {t("quickStartStep4Suffix")}{" "}
- gh/gpt-5.1-codex.
-
-
-
-
-
-
- {t("deploymentGuides")}
-
-
-
-
- {t("features")}
-
- {featureItems.map((item) => (
-
-
- {item.icon}
-
-
-
{item.title}
-
{item.text}
-
-
- ))}
-
-
-
-
-
-
-
{t("supportedProviders")}
-
- {t("providersAcrossConnectionTypes", { count: totalProviders })}
-
-
-
- {t("manageProviders")}
-
-
-
-
-
-
-
- {t("commonUseCases")}
-
- {useCases.map((item) => (
-
- {item.title}
- {item.text}
-
- ))}
-
-
-
-
- {t("clientCompatibility")}
-
-
- {t("clientCherryStudioTitle")}
-
- -
- {t("baseUrlLabel")}:{" "}
-
https://<host>/v1
-
- -
- {t("chatEndpointLabel")}:{" "}
-
/chat/completions
-
- -
- {t("modelRecommendationLabel")} (
-
gh/...,{" "}
- cc/...)
-
-
-
-
- {t("clientCodexTitle")}
-
- -
- {t("clientCodexBullet1")}
gh/.
-
- -
- {t("clientCodexBullet2")}{" "}
-
/responses.
-
- -
- {t("clientCodexBullet3")}{" "}
-
/chat/completions.
-
-
-
-
- {t("clientCursorTitle")}
-
- -
- {t("clientCursorBullet1")}
cu/{" "}
- {t("clientCursorBullet1Suffix")}
-
- - {t("clientCursorBullet2")}
- - {t("supportsChat")}
-
-
-
- {t("clientClaudeTitle")}
-
- -
- {t("clientClaudeBullet1Prefix")}{" "}
-
cc/{" "}
- {t("clientClaudeBullet1Middle")}{" "}
- antigravity/{" "}
- {t("clientClaudeBullet1Suffix")}
-
- - {t("oauthAutoRefresh")}
- - {t("fullStreaming")}
-
-
-
- {t("clientWindsurfTitle")}
-
- - {t("clientWindsurfBullet1")}
- - {t("clientWindsurfBullet2")}
- - {t("clientWindsurfBullet3")}
-
-
-
- {t("clientClineTitle")}
-
- - {t("clientClineBullet1")}
- - {t("clientClineBullet2")}
- - {t("clientClineBullet3")}
-
-
-
- {t("clientKimiTitle")}
-
- - {t("clientKimiBullet1")}
- - {t("clientKimiBullet2")}
- - {t("clientKimiBullet3")}
-
-
-
-
-
-
- {t("protocolsTitle")}
- {t("protocolsDescription")}
-
-
-
- {t("protocolMcpTitle")}
- {t("protocolMcpDesc")}
-
- - {t("protocolMcpStep1")}
- - {t("protocolMcpStep2")}
- - {t("protocolMcpStep3")}
-
-
- {`omniroute --mcp`}
-
-
-
-
- {t("protocolA2aTitle")}
- {t("protocolA2aDesc")}
-
- - {t("protocolA2aStep1")}
- - {t("protocolA2aStep2")}
- - {t("protocolA2aStep3")}
-
-
- {`GET /.well-known/agent.json
-POST /a2a (JSON-RPC: message/send | message/stream)`}
-
-
-
-
- {t("protocolAcpTitle")}
- {t("protocolAcpDesc")}
-
- - {t("protocolAcpStep1")}
- - {t("protocolAcpStep2")}
- - {t("protocolAcpStep3")}
-
-
- {`Dashboard -> Agents
-Dashboard -> CLI Tools`}
-
-
-
-
-
-
{t("protocolTroubleshootingTitle")}
-
- - {t("protocolTroubleshooting1")}
- - {t("protocolTroubleshooting2")}
- - {t("protocolTroubleshooting3")}
-
-
-
-
-
-
-
- {t("apiReference")}
-
-
-
-
- | {t("method")} |
- {t("path")} |
- {t("notes")} |
-
-
-
- {endpointRows.map((row) => (
-
-
-
- {row.method}
-
- |
- {row.path} |
- {row.note} |
-
- ))}
-
-
-
-
-
-
- {t("modelPrefixes")}
-
- {t("modelPrefixesDescriptionStart")}{" "}
- gh/gpt-5.1-codex{" "}
- {t("modelPrefixesDescriptionEnd")}
-
-
-
-
-
- | {t("prefix")} |
- {t("provider")} |
- {t("type")} |
-
-
-
- {providerPrefixRows.map((p) => (
-
-
- {p.alias}/
- |
- {p.name} |
-
-
-
- {getProviderTypeLabel(p.type)}
-
- |
-
- ))}
-
-
-
-
-
-
- {t("managementApiReference")}
- {t("managementApiDescription")}
-
-
-
-
- | {t("method")} |
- {t("path")} |
- {t("notes")} |
-
-
-
- {managementEndpointRows.map((row) => (
-
-
-
- {row.method}
-
- |
- {row.path} |
- {row.note} |
-
- ))}
-
-
-
-
-
-
- {t("troubleshooting")}
-
- {troubleshootingItems.map((item) => (
- - {item}
- ))}
-
-
-
+
);
}
diff --git a/src/shared/components/docs/APIReference.stories.tsx b/src/shared/components/docs/APIReference.stories.tsx
new file mode 100644
index 0000000000..74d4d3e48b
--- /dev/null
+++ b/src/shared/components/docs/APIReference.stories.tsx
@@ -0,0 +1,35 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import APIReference from "./APIReference";
+
+const meta: Meta
= {
+ title: "Docs/APIReference",
+ component: APIReference,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ name: "handleChat()",
+ type: "async function",
+ description: "Processes a chat completion request and streams the response to the client.",
+ params: [
+ {
+ name: "request",
+ type: "ChatRequest",
+ description: "The structured request containing messages and model config.",
+ },
+ {
+ name: "options",
+ type: "RequestOptions",
+ description: "Optional overrides for the request pipeline.",
+ },
+ ],
+ returns: {
+ type: "Promise",
+ description:
+ "A promise that resolves to a Server-Sent Events stream containing model chunks.",
+ },
+ },
+};
diff --git a/src/shared/components/docs/APIReference.tsx b/src/shared/components/docs/APIReference.tsx
new file mode 100644
index 0000000000..f7292f6e02
--- /dev/null
+++ b/src/shared/components/docs/APIReference.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import React from "react";
+import { cn } from "@/shared/utils/cn";
+
+interface APIReferenceProps {
+ name: string;
+ type: string;
+ description: string;
+ params?: { name: string; type: string; description: string }[];
+ returns?: { type: string; description: string };
+ className?: string;
+}
+
+export default function APIReference({
+ name,
+ type,
+ description,
+ params,
+ returns,
+ className,
+}: APIReferenceProps) {
+ return (
+
+
+
+ {name}
+
+
+ {type}
+
+
+
{description}
+
+ {params && params.length > 0 && (
+
+
Parameters
+
+
+
+
+ |
+ Name
+ |
+
+ Type
+ |
+
+ Description
+ |
+
+
+
+ {params.map((param, i) => (
+
+ | {param.name} |
+ {param.type} |
+ {param.description} |
+
+ ))}
+
+
+
+
+ )}
+
+ {returns && (
+
+
Returns
+
+
Type:
+
{returns.type}
+
{returns.description}
+
+
+ )}
+
+ );
+}
diff --git a/src/shared/components/docs/Callout.stories.tsx b/src/shared/components/docs/Callout.stories.tsx
new file mode 100644
index 0000000000..c831b7b7bf
--- /dev/null
+++ b/src/shared/components/docs/Callout.stories.tsx
@@ -0,0 +1,40 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import Callout from "./Callout";
+
+const meta: Meta = {
+ title: "Docs/Callout",
+ component: Callout,
+ argTypes: {
+ type: {
+ control: "select",
+ options: ["info", "warning", "danger"],
+ },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ type: "info",
+ title: "Information",
+ children: "This is a helpful hint to guide users through the documentation.",
+ },
+};
+
+export const Warning: Story = {
+ args: {
+ type: "warning",
+ title: "Warning",
+ children: "Be careful when modifying these settings as they can affect system stability.",
+ },
+};
+
+export const Danger: Story = {
+ args: {
+ type: "danger",
+ title: "Critical",
+ children: "Deleting this resource is irreversible and will remove all associated data.",
+ },
+};
diff --git a/src/shared/components/docs/Callout.tsx b/src/shared/components/docs/Callout.tsx
new file mode 100644
index 0000000000..80b1b707a4
--- /dev/null
+++ b/src/shared/components/docs/Callout.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import React from "react";
+import { cn } from "@/shared/utils/cn";
+import { DOCS_TOKENS } from "./tokens";
+
+type CalloutType = "info" | "warning" | "danger";
+
+interface CalloutProps {
+ type: CalloutType;
+ title: string;
+ children: React.ReactNode;
+ className?: string;
+}
+
+export default function Callout({ type, title, children, className }: CalloutProps) {
+ const theme = DOCS_TOKENS.colors[type];
+
+ return (
+
+
+
+ {title}
+
+
{children}
+
+
+ );
+}
diff --git a/src/shared/components/docs/CodeBlock.stories.tsx b/src/shared/components/docs/CodeBlock.stories.tsx
new file mode 100644
index 0000000000..cb6b3cc529
--- /dev/null
+++ b/src/shared/components/docs/CodeBlock.stories.tsx
@@ -0,0 +1,25 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import CodeBlock from "./CodeBlock";
+
+const meta: Meta = {
+ title: "Docs/CodeBlock",
+ component: CodeBlock,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ code: 'const hello = "world";\nconsole.log(hello);',
+ language: "typescript",
+ filename: "example.ts",
+ },
+};
+
+export const WithoutFilename: Story = {
+ args: {
+ code: "npm install @omniroute/core",
+ language: "bash",
+ },
+};
diff --git a/src/shared/components/docs/CodeBlock.tsx b/src/shared/components/docs/CodeBlock.tsx
new file mode 100644
index 0000000000..882484877b
--- /dev/null
+++ b/src/shared/components/docs/CodeBlock.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import React from "react";
+import { cn } from "@/shared/utils/cn";
+
+interface CodeBlockProps {
+ code: string;
+ language?: string;
+ filename?: string;
+ children?: React.ReactNode;
+ className?: string;
+}
+
+export default function CodeBlock({
+ code,
+ language = "typescript",
+ filename,
+ children,
+ className,
+}: CodeBlockProps) {
+ const copyToClipboard = async () => {
+ await navigator.clipboard.writeText(code);
+ };
+
+ return (
+
+ {filename && (
+
+ {filename}
+
+
+ )}
+
+ {children || code}
+
+
+ );
+}
diff --git a/src/shared/components/docs/DocsBreadcrumbs.stories.tsx b/src/shared/components/docs/DocsBreadcrumbs.stories.tsx
new file mode 100644
index 0000000000..0e326035fc
--- /dev/null
+++ b/src/shared/components/docs/DocsBreadcrumbs.stories.tsx
@@ -0,0 +1,20 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import DocsBreadcrumbs from "./DocsBreadcrumbs";
+
+const meta: Meta = {
+ title: "Docs/DocsBreadcrumbs",
+ component: DocsBreadcrumbs,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ labels: {
+ intro: "Introduction",
+ routing: "Routing Engine",
+ providers: "Provider Ecosystem",
+ },
+ },
+};
diff --git a/src/shared/components/docs/DocsBreadcrumbs.tsx b/src/shared/components/docs/DocsBreadcrumbs.tsx
new file mode 100644
index 0000000000..231cb1798b
--- /dev/null
+++ b/src/shared/components/docs/DocsBreadcrumbs.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import React from "react";
+import { usePathname } from "next/navigation";
+import Link from "next/link";
+import { cn } from "@/shared/utils/cn";
+
+interface DocsBreadcrumbsProps {
+ labels: Record;
+ className?: string;
+}
+
+export default function DocsBreadcrumbs({ labels, className }: DocsBreadcrumbsProps) {
+ const pathname = usePathname();
+ if (!pathname || pathname === "/") return null;
+
+ const segments = pathname.split("/").filter(Boolean);
+ const crumbs = segments.map((seg, idx) => ({
+ label: labels[seg] || seg.charAt(0).toUpperCase() + seg.slice(1),
+ href: "/" + segments.slice(0, idx + 1).join("/"),
+ isLast: idx === segments.length - 1,
+ }));
+
+ return (
+
+ );
+}
diff --git a/src/shared/components/docs/DocsSidebar.stories.tsx b/src/shared/components/docs/DocsSidebar.stories.tsx
new file mode 100644
index 0000000000..001b64f558
--- /dev/null
+++ b/src/shared/components/docs/DocsSidebar.stories.tsx
@@ -0,0 +1,31 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import DocsSidebar from "./DocsSidebar";
+
+const meta: Meta = {
+ title: "Docs/DocsSidebar",
+ component: DocsSidebar,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ sections: [
+ {
+ title: "Getting Started",
+ children: [
+ { title: "Introduction", href: "/docs/intro" },
+ { title: "Quick Start", href: "/docs/quickstart" },
+ ],
+ },
+ {
+ title: "Core Concepts",
+ children: [
+ { title: "Routing", href: "/docs/routing" },
+ { title: "Providers", href: "/docs/providers" },
+ ],
+ },
+ ],
+ },
+};
diff --git a/src/shared/components/docs/DocsSidebar.tsx b/src/shared/components/docs/DocsSidebar.tsx
new file mode 100644
index 0000000000..2ca4eaef8e
--- /dev/null
+++ b/src/shared/components/docs/DocsSidebar.tsx
@@ -0,0 +1,77 @@
+"use client";
+
+import React, { useState } from "react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { cn } from "@/shared/utils/cn";
+import { DOCS_TOKENS } from "./tokens";
+
+interface NavItem {
+ title: string;
+ href: string;
+ children?: NavItem[];
+}
+
+interface DocsSidebarProps {
+ sections: NavItem[];
+ currentPath?: string;
+ className?: string;
+}
+
+export default function DocsSidebar({ sections, currentPath, className }: DocsSidebarProps) {
+ const pathname = usePathname();
+ const [collapsed, setCollapsed] = useState(false);
+
+ const isActive = (href: string) => pathname === href || currentPath === href;
+
+ return (
+
+
+ {!collapsed && OmniRoute Docs}
+
+
+
+
+ {sections.map((section, idx) => (
+
+ {!collapsed && (
+
+ {section.title}
+
+ )}
+
+ {section.children?.map((item, childIdx) => (
+
+ {item.title}
+
+ ))}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/shared/components/docs/DocsThemeProvider.tsx b/src/shared/components/docs/DocsThemeProvider.tsx
new file mode 100644
index 0000000000..960c3465c6
--- /dev/null
+++ b/src/shared/components/docs/DocsThemeProvider.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import React from "react";
+import { cn } from "@/shared/utils/cn";
+import { DOCS_TOKENS } from "./tokens";
+
+interface ThemeProviderProps {
+ children: React.ReactNode;
+ initialTheme?: "light" | "dark" | "system";
+}
+
+export default function DocsThemeProvider({
+ children,
+ initialTheme = "system",
+}: ThemeProviderProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/shared/components/docs/Tabs.stories.tsx b/src/shared/components/docs/Tabs.stories.tsx
new file mode 100644
index 0000000000..3d8521e6c4
--- /dev/null
+++ b/src/shared/components/docs/Tabs.stories.tsx
@@ -0,0 +1,26 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import Tabs from "./Tabs";
+
+const meta: Meta = {
+ title: "Docs/Tabs",
+ component: Tabs,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ tabs: [
+ {
+ label: "Installation",
+ content: 'Run npm install to get started.
',
+ },
+ { label: "Configuration", content: 'Edit your config.json file.
' },
+ {
+ label: "Usage",
+ content: 'Import the provider and start routing.
',
+ },
+ ],
+ },
+};
diff --git a/src/shared/components/docs/Tabs.tsx b/src/shared/components/docs/Tabs.tsx
new file mode 100644
index 0000000000..bd5374c335
--- /dev/null
+++ b/src/shared/components/docs/Tabs.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import React, { useState } from "react";
+import { cn } from "@/shared/utils/cn";
+
+interface Tab {
+ label: string;
+ content: React.ReactNode;
+}
+
+interface TabsProps {
+ tabs: Tab[];
+ defaultIndex?: number;
+ className?: string;
+}
+
+export default function Tabs({ tabs, defaultIndex = 0, className }: TabsProps) {
+ const [activeIndex, setActiveIndex] = useState(defaultIndex);
+
+ return (
+
+
+ {tabs.map((tab, index) => (
+
+ ))}
+
+
+ {tabs[activeIndex].content}
+
+
+ );
+}
diff --git a/src/shared/components/docs/tokens.ts b/src/shared/components/docs/tokens.ts
new file mode 100644
index 0000000000..47bff62583
--- /dev/null
+++ b/src/shared/components/docs/tokens.ts
@@ -0,0 +1,38 @@
+export const DOCS_TOKENS = {
+ colors: {
+ info: {
+ bg: "var(--color-bg-subtle)",
+ border: "var(--color-accent)",
+ text: "var(--color-text-main)",
+ darkBg: "rgba(99, 102, 241, 0.1)",
+ },
+ warning: {
+ bg: "var(--color-bg-subtle)",
+ border: "var(--color-warning)",
+ text: "var(--color-text-main)",
+ darkBg: "rgba(245, 158, 11, 0.1)",
+ },
+ danger: {
+ bg: "var(--color-bg-subtle)",
+ border: "var(--color-error)",
+ text: "var(--color-text-main)",
+ darkBg: "rgba(239, 68, 68, 0.1)",
+ },
+ code: {
+ bg: "var(--color-sidebar)",
+ text: "var(--color-text-main)",
+ border: "var(--color-border)",
+ },
+ },
+ typography: {
+ headingLg: "text-3xl font-bold tracking-tight text-text-primary",
+ headingMd: "text-2xl font-semibold tracking-tight text-text-primary",
+ headingSm: "text-xl font-semibold tracking-tight text-text-primary",
+ body: "text-base leading-relaxed text-text-main",
+ muted: "text-sm text-text-muted",
+ },
+ spacing: {
+ cardGap: "gap-6",
+ sectionGap: "gap-12",
+ },
+};
diff --git a/tests/unit/docs-site-overhaul.test.ts b/tests/unit/docs-site-overhaul.test.ts
new file mode 100644
index 0000000000..efa6eb5358
--- /dev/null
+++ b/tests/unit/docs-site-overhaul.test.ts
@@ -0,0 +1,309 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import {
+ extractHeadings,
+ renderMarkdown,
+ getDocItemBySlug,
+ getAllDocSlugsFlat,
+ getPrevNextSlugs,
+} from "../../src/app/docs/[slug]/page";
+import { docsNavigation } from "../../src/app/docs/lib/docsNavigation";
+import { SEARCH_INDEX } from "../../src/app/docs/lib/searchIndex";
+
+// ──────────────────────────────────────────────
+// docsNavigation structure
+// ──────────────────────────────────────────────
+
+test("docsNavigation has 6 sections", () => {
+ assert.equal(docsNavigation.length, 6);
+});
+
+test("every section has title and items", () => {
+ for (const section of docsNavigation) {
+ assert.ok(section.title, "section must have a title");
+ assert.ok(Array.isArray(section.items), "section.items must be an array");
+ assert.ok(section.items.length > 0, "section must have at least one item");
+ }
+});
+
+test("every doc item has slug, title, fileName", () => {
+ for (const section of docsNavigation) {
+ for (const item of section.items) {
+ assert.ok(item.slug, "item must have a slug");
+ assert.ok(item.title, "item must have a title");
+ assert.ok(item.fileName, "item must have a fileName");
+ }
+ }
+});
+
+// ──────────────────────────────────────────────
+// getDocItemBySlug
+// ──────────────────────────────────────────────
+
+test("getDocItemBySlug returns section title and item for known slug", () => {
+ const result = getDocItemBySlug("setup-guide");
+ assert.ok(result, "setup-guide should be found");
+ assert.equal(result.item.slug, "setup-guide");
+ assert.equal(result.sectionTitle, "Getting Started");
+});
+
+test("getDocItemBySlug returns null for unknown slug", () => {
+ const result = getDocItemBySlug("nonexistent-page");
+ assert.equal(result, null);
+});
+
+test("getDocItemBySlug finds items in all sections", () => {
+ const sectionTitles = docsNavigation.map((s) => s.title);
+ for (const section of docsNavigation) {
+ const firstItem = section.items[0];
+ const result = getDocItemBySlug(firstItem.slug);
+ assert.ok(result, `should find ${firstItem.slug}`);
+ assert.ok(sectionTitles.includes(result.sectionTitle));
+ }
+});
+
+// ──────────────────────────────────────────────
+// getAllDocSlugsFlat
+// ──────────────────────────────────────────────
+
+test("getAllDocSlugsFlat returns all slugs from all sections", () => {
+ const slugs = getAllDocSlugsFlat();
+ const totalItems = docsNavigation.reduce((sum, s) => sum + s.items.length, 0);
+ assert.equal(slugs.length, totalItems);
+});
+
+test("getAllDocSlugsFlat includes setup-guide as first slug", () => {
+ const slugs = getAllDocSlugsFlat();
+ assert.ok(slugs.includes("setup-guide"));
+});
+
+// ──────────────────────────────────────────────
+// getPrevNextSlugs
+// ──────────────────────────────────────────────
+
+test("getPrevNextSlugs returns null prev for first slug", () => {
+ const slugs = getAllDocSlugsFlat();
+ const firstSlug = slugs[0];
+ const { prev } = getPrevNextSlugs(firstSlug);
+ assert.equal(prev, null);
+});
+
+test("getPrevNextSlugs returns null next for last slug", () => {
+ const slugs = getAllDocSlugsFlat();
+ const lastSlug = slugs[slugs.length - 1];
+ const { next } = getPrevNextSlugs(lastSlug);
+ assert.equal(next, null);
+});
+
+test("getPrevNextSlugs returns correct prev and next for middle slug", () => {
+ const slugs = getAllDocSlugsFlat();
+ const middleIdx = Math.floor(slugs.length / 2);
+ const middleSlug = slugs[middleIdx];
+ const { prev, next } = getPrevNextSlugs(middleSlug);
+ assert.equal(prev, slugs[middleIdx - 1]);
+ assert.equal(next, slugs[middleIdx + 1]);
+});
+
+// ──────────────────────────────────────────────
+// extractHeadings
+// ──────────────────────────────────────────────
+
+test("extractHeadings extracts h2, h3, h4 headings", () => {
+ const md = `## First Section\nSome text\n### Subsection\nMore text\n#### Details\nEnd`;
+ const headings = extractHeadings(md);
+ assert.equal(headings.length, 3);
+ assert.equal(headings[0].text, "First Section");
+ assert.equal(headings[0].level, 2);
+ assert.equal(headings[1].text, "Subsection");
+ assert.equal(headings[1].level, 3);
+ assert.equal(headings[2].text, "Details");
+ assert.equal(headings[2].level, 4);
+});
+
+test("extractHeadings generates valid id from heading text", () => {
+ const md = `## Getting Started Guide\n### API Reference\n#### Step 1: Install`;
+ const headings = extractHeadings(md);
+ assert.equal(headings[0].id, "getting-started-guide");
+ assert.equal(headings[1].id, "api-reference");
+ assert.equal(headings[2].id, "step-1-install");
+});
+
+test("extractHeadings returns empty array for content without headings", () => {
+ const md = "Just some text without any headings";
+ const headings = extractHeadings(md);
+ assert.equal(headings.length, 0);
+});
+
+test("extractHeadings strips bold and code from heading text", () => {
+ const md = "## **Bold** Heading\n### \`Code\` Heading";
+ const headings = extractHeadings(md);
+ assert.equal(headings[0].text, "Bold Heading");
+ assert.equal(headings[1].text, "Code Heading");
+});
+
+// ──────────────────────────────────────────────
+// renderMarkdown
+// ──────────────────────────────────────────────
+
+test("renderMarkdown converts headings to HTML", () => {
+ const html = renderMarkdown("# Title\n## Section\n### Subsection\n#### Details");
+ assert.ok(html.includes(' {
+ const html = renderMarkdown("```js\nconst x = 1;\n```");
+ assert.ok(html.includes(' {
+ const html = renderMarkdown("Use `npm install` to install");
+ assert.ok(html.includes(' {
+ const html = renderMarkdown("This is **bold** text");
+ assert.ok(html.includes("bold"));
+});
+
+test("renderMarkdown converts italic text", () => {
+ const html = renderMarkdown("This is *italic* text");
+ assert.ok(html.includes("italic"));
+});
+
+test("renderMarkdown converts links", () => {
+ const html = renderMarkdown("[OmniRoute](https://omniroute.online)");
+ assert.ok(html.includes('OmniRoute"));
+});
+
+test("renderMarkdown converts unordered lists", () => {
+ const html = renderMarkdown("- Item 1\n- Item 2");
+ assert.ok(html.includes(''));
+});
+
+test("renderMarkdown converts ordered lists", () => {
+ const html = renderMarkdown("1. First\n2. Second");
+ assert.ok(html.includes(''));
+});
+
+test("renderMarkdown converts blockquotes", () => {
+ const html = renderMarkdown("> This is a quote");
+ assert.ok(html.includes(" {
+ const html = renderMarkdown("---");
+ assert.ok(html.includes('
{
+ const navSlugs = getAllDocSlugsFlat();
+ // searchIndex and nav slugs should have significant overlap
+ const indexSlugs = SEARCH_INDEX.map((item) => item.slug);
+ for (const slug of navSlugs) {
+ assert.ok(indexSlugs.includes(slug), `SEARCH_INDEX missing slug: ${slug}`);
+ }
+});
+
+test("SEARCH_INDEX entries have required fields", () => {
+ for (const item of SEARCH_INDEX) {
+ assert.ok(item.slug, "item must have slug");
+ assert.ok(item.title, "item must have title");
+ assert.ok(item.fileName, "item must have fileName");
+ assert.ok(item.section, "item must have section");
+ assert.ok(typeof item.content === "string", "item must have content string");
+ assert.ok(Array.isArray(item.headings), "item must have headings array");
+ }
+});
+
+test("SEARCH_INDEX entries have non-empty content", () => {
+ for (const item of SEARCH_INDEX) {
+ assert.ok(item.content.length > 0, `${item.slug} should have content`);
+ }
+});
+
+// ──────────────────────────────────────────────
+// Mermaid extraction
+// ──────────────────────────────────────────────
+
+test("extractMermaidCharts extracts mermaid blocks from content", async () => {
+ const { extractMermaidCharts } = await import("../../src/app/docs/[slug]/page");
+ const content =
+ "## Diagram\n\n```mermaid\ngraph TD\n A-->B\n```\n\nSome text\n\n```mermaid\nsequenceDiagram\n Alice->>Bob: Hi\n```";
+ const charts = extractMermaidCharts(content);
+ assert.equal(charts.length, 2);
+ assert.ok(charts[0].includes("graph TD"));
+ assert.ok(charts[1].includes("Alice->>Bob"));
+});
+
+test("extractMermaidCharts returns empty array when no mermaid blocks", async () => {
+ const { extractMermaidCharts } = await import("../../src/app/docs/[slug]/page");
+ const content = "## Heading\n\nSome text with ```js\ncode\n```";
+ const charts = extractMermaidCharts(content);
+ assert.equal(charts.length, 0);
+});
+
+// ──────────────────────────────────────────────
+// Mermaid rendering in markdown
+// ──────────────────────────────────────────────
+
+test("renderMarkdown converts mermaid code blocks to fallback divs", () => {
+ const markdown = "```mermaid\ngraph TD\n A-->B\n```";
+ const html = renderMarkdown(markdown);
+ assert.ok(
+ html.includes("mermaid-diagram-fallback"),
+ "Should contain mermaid-diagram-fallback class"
+ );
+ assert.ok(html.includes("data-mermaid"), "Should contain data-mermaid attribute");
+});
+
+// ──────────────────────────────────────────────
+// Analytics component
+// ──────────────────────────────────────────────
+
+test("DocsPageAnalytics is importable", async () => {
+ const mod = await import("../../src/app/docs/components/DocsPageAnalytics");
+ assert.ok(mod.DocsPageAnalytics, "DocsPageAnalytics should be exported");
+ assert.ok(typeof mod.getPopularPages === "function", "getPopularPages should be exported");
+});
+
+// ──────────────────────────────────────────────
+// What's New and Migration Guide
+// ──────────────────────────────────────────────
+
+test("WhatsNewSection is importable", async () => {
+ const mod = await import("../../src/app/docs/components/WhatsNewSection");
+ assert.ok(mod.WhatsNewSection, "WhatsNewSection should be exported");
+ assert.ok(mod.MigrationGuideBanner, "MigrationGuideBanner should be exported");
+});
+
+// ──────────────────────────────────────────────
+// i18n locale system
+// ──────────────────────────────────────────────
+
+test("DocsI18n is importable", async () => {
+ const mod = await import("../../src/app/docs/components/DocsI18n");
+ assert.ok(mod.useLocalizedSectionTitle, "useLocalizedSectionTitle should be exported");
+ assert.ok(mod.getAvailableLocales, "getAvailableLocales should be exported");
+ assert.ok(mod.LOCALE_NAMES, "LOCALE_NAMES should be exported");
+ assert.equal(mod.LOCALE_NAMES.en, "English");
+ assert.equal(mod.LOCALE_NAMES["zh-CN"], "简体中文");
+});
+
+test("getAvailableLocales returns 10 locales", async () => {
+ const { getAvailableLocales } = await import("../../src/app/docs/components/DocsI18n");
+ const locales = getAvailableLocales();
+ assert.equal(locales.length, 10);
+ assert.ok(locales.includes("en"));
+ assert.ok(locales.includes("pt-BR"));
+ assert.ok(locales.includes("zh-CN"));
+});