diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx
index 13d36fc85..888e2dcf4 100644
--- a/src/components/FileExplorer.tsx
+++ b/src/components/FileExplorer.tsx
@@ -8,6 +8,7 @@ import svelteIconUrl from '~/images/file-icons/svelte.svg?url'
import vueIconUrl from '~/images/file-icons/vue.svg?url'
import markoIconUrl from '~/images/file-icons/marko.svg?url'
import emberIconUrl from '~/images/ember-logo.svg?url'
+import octaneIconUrl from '~/images/octane-logo.svg?url'
import textIconUrl from '~/images/file-icons/txt.svg?url'
import type { GitHubFileNode } from '~/utils/documents.server'
import { twMerge } from 'tailwind-merge'
@@ -19,6 +20,8 @@ const getFileIconPath = (filename: string) => {
case 'ts':
case 'tsx':
return typescriptIconUrl
+ case 'tsrx':
+ return octaneIconUrl
case 'js':
case 'jsx':
return javascriptIconUrl
diff --git a/src/components/markdown/codeBlock.shared.tsx b/src/components/markdown/codeBlock.shared.tsx
index ffbbdcc30..61a047d11 100644
--- a/src/components/markdown/codeBlock.shared.tsx
+++ b/src/components/markdown/codeBlock.shared.tsx
@@ -82,6 +82,7 @@ export function getCodeBlockLanguageFromFilePath(filePath: string) {
}
if (['cts', 'mts'].includes(ext)) return 'ts'
+ if (ext === 'tsrx') return 'tsx'
if (['cjs', 'mjs'].includes(ext)) return 'js'
if (ext === 'gts') return 'ts'
if (ext === 'gjs') return 'js'
diff --git a/src/images/octane-logo.svg b/src/images/octane-logo.svg
new file mode 100644
index 000000000..7598b95f3
--- /dev/null
+++ b/src/images/octane-logo.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/libraries/frameworks.tsx b/src/libraries/frameworks.tsx
index c4214b9a8..665abe102 100644
--- a/src/libraries/frameworks.tsx
+++ b/src/libraries/frameworks.tsx
@@ -4,6 +4,7 @@ import emberLogo from '../images/ember-logo.svg'
import jsLogo from '../images/js-logo.svg'
import litLogo from '../images/lit-logo.svg'
import markoLogo from '../images/marko-logo.svg'
+import octaneLogo from '../images/octane-logo.svg'
import qwikLogo from '../images/qwik-logo.svg'
import preactLogo from '../images/preact-logo.svg'
import reactLogo from '../images/react-logo.svg'
@@ -69,6 +70,13 @@ export const frameworkOptions = [
color: 'bg-cyan-500',
fontColor: 'text-cyan-500',
},
+ {
+ label: 'Octane',
+ value: 'octane',
+ logo: octaneLogo,
+ color: 'bg-rose-500',
+ fontColor: 'text-rose-500',
+ },
{
label: 'Qwik',
value: 'qwik',
diff --git a/src/libraries/libraries.ts b/src/libraries/libraries.ts
index 09bb239b2..0080b0e7c 100644
--- a/src/libraries/libraries.ts
+++ b/src/libraries/libraries.ts
@@ -300,6 +300,7 @@ export const table: LibrarySlim = {
frameworks: [
'angular',
'ember',
+ 'octane',
'react',
'preact',
'solid',
@@ -474,7 +475,16 @@ export const store: LibrarySlim = {
'The immutable-reactive data store that powers the core of TanStack libraries and their framework adapters.',
badge: 'alpha',
repo: 'tanstack/store',
- frameworks: ['react', 'preact', 'solid', 'svelte', 'vue', 'angular', 'lit'],
+ frameworks: [
+ 'react',
+ 'preact',
+ 'solid',
+ 'svelte',
+ 'vue',
+ 'angular',
+ 'lit',
+ 'octane',
+ ],
corePackageName: '@tanstack/store',
npmPackageNames: ['@tanstack/store'],
latestVersion: 'v0',
diff --git a/src/libraries/types.ts b/src/libraries/types.ts
index 15fb0bfe1..4af16e416 100644
--- a/src/libraries/types.ts
+++ b/src/libraries/types.ts
@@ -9,6 +9,7 @@ export type Framework =
| 'ember'
| 'lit'
| 'marko'
+ | 'octane'
| 'preact'
| 'qwik'
| 'react'
diff --git a/src/routes/_library/$libraryId/$version.docs.framework.$framework.examples.$.tsx b/src/routes/_library/$libraryId/$version.docs.framework.$framework.examples.$.tsx
index 1f8e007bd..e40db2ccb 100644
--- a/src/routes/_library/$libraryId/$version.docs.framework.$framework.examples.$.tsx
+++ b/src/routes/_library/$libraryId/$version.docs.framework.$framework.examples.$.tsx
@@ -487,7 +487,13 @@ function determineStartingFilePath(
const preferenceFiles = new Set([
getExampleStartingFileName(framework, libraryId),
...['__root', 'App', 'main', 'index', 'page', 'action']
- .map((name) => [`${name}.tsx`, `${name}.ts`, `${name}.js`, `${name}.jsx`])
+ .map((name) => [
+ `${name}.tsrx`,
+ `${name}.tsx`,
+ `${name}.ts`,
+ `${name}.js`,
+ `${name}.jsx`,
+ ])
.flat(),
'README.md',
])
diff --git a/src/utils/llms.ts b/src/utils/llms.ts
index 517276016..6015eb7f4 100644
--- a/src/utils/llms.ts
+++ b/src/utils/llms.ts
@@ -40,6 +40,7 @@ const frameworkLabels: Record = {
ember: 'Ember',
lit: 'Lit',
marko: 'Marko',
+ octane: 'Octane',
preact: 'Preact',
qwik: 'Qwik',
react: 'React',
diff --git a/src/utils/npm-packages.ts b/src/utils/npm-packages.ts
index c84be6bdf..4503d3d2b 100644
--- a/src/utils/npm-packages.ts
+++ b/src/utils/npm-packages.ts
@@ -31,6 +31,7 @@ export const frameworkMeta: Record =
alpine: { name: 'Alpine', color: '#77C1D2' },
marko: { name: 'Marko', color: '#44bfef' },
ember: { name: 'Ember', color: '#E04E39' },
+ octane: { name: 'Octane', color: '#FF415A' },
qwik: { name: 'Qwik', color: '#18B6F6' },
vanilla: { name: 'Vanilla', color: '#F7DF1E' },
}
diff --git a/src/utils/sandbox.ts b/src/utils/sandbox.ts
index 8fd0d5b4f..51c2379ed 100644
--- a/src/utils/sandbox.ts
+++ b/src/utils/sandbox.ts
@@ -31,9 +31,11 @@ export function getExampleStartingFileName(
? 'svelte'
: framework === 'vue'
? 'vue'
- : ['angular', 'lit'].includes(framework)
- ? 'ts'
- : 'tsx'
+ : framework === 'octane'
+ ? 'tsrx'
+ : ['angular', 'lit'].includes(framework)
+ ? 'ts'
+ : 'tsx'
return `${file}.${ext}` as const
}
diff --git a/tests/code-highlighting.test.ts b/tests/code-highlighting.test.ts
index cda46e666..e456d3740 100644
--- a/tests/code-highlighting.test.ts
+++ b/tests/code-highlighting.test.ts
@@ -3,6 +3,11 @@ import test from 'node:test'
import { defaultHighlighter } from '@tanstack/highlight'
import { renderCodeFence } from '@tanstack/highlight/markdown'
import { createThemeBaseCss } from '@tanstack/highlight/theme'
+import { getCodeBlockLanguageFromFilePath } from '../src/components/markdown/codeBlock.shared'
+
+test('TSRX files use TypeScript JSX highlighting', () => {
+ assert.equal(getCodeBlockLanguageFromFilePath('src/main.tsrx'), 'tsx')
+})
test('inline diff notation renders as line decorations', () => {
const rendered = renderCodeFence(
diff --git a/tests/octane-framework-support.test.ts b/tests/octane-framework-support.test.ts
new file mode 100644
index 000000000..b3e852827
--- /dev/null
+++ b/tests/octane-framework-support.test.ts
@@ -0,0 +1,23 @@
+import assert from 'node:assert/strict'
+import { libraries, store, table } from '../src/libraries/libraries'
+import { getFrameworkPackageName } from '../src/libraries/frameworkSupport'
+
+assert.deepEqual(
+ libraries
+ .filter((library) => library.frameworks.includes('octane'))
+ .map((library) => library.id),
+ ['table', 'store'],
+ 'only Table and Store advertise Octane support',
+)
+
+assert.equal(
+ getFrameworkPackageName('octane', table.id, table),
+ '@tanstack/octane-table',
+)
+
+assert.equal(
+ getFrameworkPackageName('octane', store.id, store),
+ '@tanstack/octane-store',
+)
+
+console.log('Octane framework support tests passed')
diff --git a/tests/repo-path.test.ts b/tests/repo-path.test.ts
index eaf8434d5..b990d1ebc 100644
--- a/tests/repo-path.test.ts
+++ b/tests/repo-path.test.ts
@@ -61,4 +61,10 @@ assert.equal(
'Router examples default to main.tsx when directory contents are unavailable',
)
+assert.equal(
+ getExampleStartingPath('octane', 'table'),
+ 'src/main.tsrx',
+ 'Octane examples default to main.tsrx',
+)
+
console.log('repo-path tests passed')