diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7fdfb7bca..25decdbcd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1.2.22-alpine +FROM oven/bun:1.3.14-alpine # Install necessary packages for development RUN apk add --no-cache \ @@ -44,4 +44,4 @@ WORKDIR /workspace # Expose the ports we're interested in EXPOSE 3000 EXPOSE 3001 -EXPOSE 3002 \ No newline at end of file +EXPOSE 3002 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fa964c699..9c5c03f90 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -27,11 +27,7 @@ ] } }, - "forwardPorts": [ - 3000, - 3002, - 5432 - ], + "forwardPorts": [3000, 3002, 5432], "postCreateCommand": "bash -c 'bash .devcontainer/post-create.sh || true'", "remoteUser": "bun" -} \ No newline at end of file +} diff --git a/.github/workflows_legacy/docs-embeddings.yml b/.github/workflows_legacy/docs-embeddings.yml index 39d5b8ae5..333b8e006 100644 --- a/.github/workflows_legacy/docs-embeddings.yml +++ b/.github/workflows_legacy/docs-embeddings.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Setup Node uses: actions/setup-node@v4 diff --git a/.github/workflows_legacy/i18n.yml b/.github/workflows_legacy/i18n.yml index d71cdffd6..da4ce18d4 100644 --- a/.github/workflows_legacy/i18n.yml +++ b/.github/workflows_legacy/i18n.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Run Lingo.dev translations env: @@ -115,7 +115,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Install dependencies run: | @@ -155,4 +155,4 @@ jobs: echo "- **🇯🇵 Japanese**: $ja_count/$en_count files ($ja_percentage%)" >> $GITHUB_STEP_SUMMARY echo "- **🇩🇪 German**: $de_count/$en_count files ($de_percentage%)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "🔄 **Auto-translation PR**: Check for new pull request with updated translations" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "🔄 **Auto-translation PR**: Check for new pull request with updated translations" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows_legacy/migrations.yml b/.github/workflows_legacy/migrations.yml index 2bfb6ca1c..b0d6bb8c6 100644 --- a/.github/workflows_legacy/migrations.yml +++ b/.github/workflows_legacy/migrations.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Install dependencies run: bun install @@ -25,4 +25,4 @@ jobs: working-directory: ./packages/db env: DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }} - run: bunx drizzle-kit migrate --config=./drizzle.config.ts \ No newline at end of file + run: bunx drizzle-kit migrate --config=./drizzle.config.ts diff --git a/.github/workflows_legacy/publish-cli.yml b/.github/workflows_legacy/publish-cli.yml index 13f8f5f04..dad9a384b 100644 --- a/.github/workflows_legacy/publish-cli.yml +++ b/.github/workflows_legacy/publish-cli.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Setup Node.js for npm publishing uses: actions/setup-node@v4 @@ -55,4 +55,4 @@ jobs: - name: Log skipped publish if: steps.version_check.outputs.exists == 'true' - run: echo "Skipped publishing because version ${{ steps.package_version.outputs.version }} already exists on npm" \ No newline at end of file + run: echo "Skipped publishing because version ${{ steps.package_version.outputs.version }} already exists on npm" diff --git a/.github/workflows_legacy/publish-ts-sdk.yml b/.github/workflows_legacy/publish-ts-sdk.yml index dfc407d37..4092ce40e 100644 --- a/.github/workflows_legacy/publish-ts-sdk.yml +++ b/.github/workflows_legacy/publish-ts-sdk.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Setup Node.js for npm publishing uses: actions/setup-node@v4 @@ -81,4 +81,4 @@ jobs: ### Documentation See the [README](https://github.com/TradingGoose/TradingGoose-Studio/tree/main/packages/ts-sdk) or the [docs](https://docs.tradinggoose.ai/sdks/typescript) for more information. draft: false - prerelease: false \ No newline at end of file + prerelease: false diff --git a/.github/workflows_legacy/test-build.yml b/.github/workflows_legacy/test-build.yml index 69f9fa7ea..b532ff853 100644 --- a/.github/workflows_legacy/test-build.yml +++ b/.github/workflows_legacy/test-build.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Setup Node uses: actions/setup-node@v4 diff --git a/.github/workflows_legacy/trigger-deploy.yml b/.github/workflows_legacy/trigger-deploy.yml index 1500496a4..43f2b9c22 100644 --- a/.github/workflows_legacy/trigger-deploy.yml +++ b/.github/workflows_legacy/trigger-deploy.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Bun uses: oven-sh/setup-bun@v2 with: - bun-version: 1.2.22 + bun-version: 1.3.14 - name: Install dependencies run: bun install diff --git a/README.md b/README.md index dc053ec78..572180d8d 100644 --- a/README.md +++ b/README.md @@ -1,211 +1,311 @@ -

TradingGoose Studio

- AI Workflow Platform for Tecnical LLM Trading + + + + TradingGoose-Studio +

+

+ + Discord + Website +

+ +

+ License + GitHub stars + Last commit -

- Discord

-

- Gemini -Perplexity +

+ Ask Gemini + Ask Perplexity

+
- - - - - - - Project Screenshot - +# Build your own AI trading system ---- +**TradingGoose-Studio is an open-source, AI-native trading analysis and automation system.** -## What is TradingGoose Studio? +Build your own AI trading system by composing workflows that analyze market signals and portfolio state, then act through approvals, alerts, connected services, or brokers. -TradingGoose Studio is an **AI workflow platform for technical LLM trading**, it combines both technical + LLM analysis for your trading decision. -It is built for analytics, research, charting, monitoring, and workflow automation. +**Monitor → Analyze → Execute → Review** -> **Early Stage Notice** -> -> TradingGoose Studio is still in a very early development stage. -> You should expect rough edges, incomplete features, and a meaningful number of bugs. -> Breaking changes are normal at this stage and may happen very frequently, including on a near-weekly basis. -> If you are building on top of the project, expect to update your setup, workflows, and integrations often. +Built for traders, indicator authors, workflow builders, and self-hosting teams that want control over how automation makes and executes decisions. +## How it works +```mermaid +flowchart LR + BUILD["1 · Build & deploy
Create your AI trading workflow"] --> MONITOR["2 · Configure a monitor
Choose a trigger source"] + MONITOR --> MARKET["Market signal
Indicator condition"] + MONITOR --> PORTFOLIO["Portfolio state
Account or position condition"] + MARKET --> RUN["3 · Signal matches
The AI workflow analyzes and decides"] + PORTFOLIO --> RUN + RUN --> OUTCOME["4 · Configured outcome
Approve · alert · service · broker action"] +``` - - - - Project Overview + TradingGoose workspace with Copilot, charts, indicators, and workflows ---- -### Copilot-MCP +## The four pillars + +Four product surfaces work together to turn market context into a controlled AI trading system. + + + + + + + + + + +
+

📊 Trading workspace

+

Organize the views and tools for a trading process in saved layouts.

+
+

📡 Monitor system

+

Use one system for market-data indicator events and connected-broker portfolio or position conditions. Both trigger deployed workflows.

+
+

🔀 Decision workflows

+

Compose AI agents with market context, tools, rules, conditions, and approvals on a visual canvas.

+
+

🎯 Controlled execution

+

Run the alerts, service calls, or broker actions you configure and review each execution.

+
+ +## Features + +| Capability | What you can do | +| --- | --- | +| **Custom market views** | Pair charts, watchlists, portfolios, and orders in saved layouts. | +| **Indicator authoring** | Use built-in studies or PineTS scripts, then reuse them as signal inputs. | +| **Context-rich agents** | Give agents market, portfolio, knowledge, memory, and tool context. | +| **Deterministic controls** | Route, loop, run in parallel, wait, branch on conditions, and require approval. | +| **Connected actions** | Use Alpaca or Tradier, APIs, SDKs, webhooks, and MCP from the same system. | +| **Run history** | Inspect streamed outputs, execution logs, and order records after every run. | + +## Problems TradingGoose-Studio solves + +| Without TradingGoose-Studio | With TradingGoose-Studio | +| --- | --- | +| ❌ Market context, portfolio state, AI analysis, and actions live in separate tools. | ✔️ One workspace connects them through monitors and workflows. | +| ❌ An indicator can tell you something happened, but not what to do next. | ✔️ Monitor events feed a workflow that can analyze, gate, alert, or act. | +| ❌ AI trading logic is trapped in prompts or one-off scripts. | ✔️ Build and deploy a visual AI trading system you can inspect and change. | +| ❌ Automation can act without a clear review point. | ✔️ Put rules and approvals in the workflow and inspect each execution. | +| ❌ You have to accept a fixed strategy or hosted stack. | ✔️ Bring your data, models, tools, and broker connections and define your own system. | + +## Ready to build with + +This is the build kit already included in TradingGoose-Studio. Canvas types and agent actions are counted separately: the action total only includes built-in tools the Agent runtime can resolve. + +| | | +| --- | --- | +| **151 user-addable canvas types** | 15 core blocks, 124 tool blocks, 10 trigger blocks, and loop and parallel control-flow containers. | +| **10 trigger types** | Start workflows from manual or input runs, schedules, APIs, chats, webhooks, RSS or IMAP, indicator events, and portfolio-state conditions. | +| **254 agent-callable actions across 71 integrations** | Give agents market research, data, communications, storage, web access, and configured trading actions. | +| **86 built-in PineTS studies** | Use them in charts, Copilot, and Function blocks; author or import a trigger-enabled indicator when a market monitor should start a workflow. | +| **4 market-data providers** | Alpaca, Finnhub, Alpha Vantage and Yahoo Finance. All support series; Alpaca and Finnhub also provide live data. | +| **2 trading providers** | Alpaca and Tradier provide portfolio context and configured order actions. | +| **16 AI providers** | Choose from direct, cloud, and local model-provider options for agents. | + +Connect the credentials, endpoint, or broker account required by each provider. Custom tools and connected MCP servers are workspace-specific, so they are deliberately not included in the fixed counts above. + +### The Systems + + + + + + + + + + +
+ Provider adapters — AI providers, market-data feeds, and trading services are exposed through consistent interfaces for models, market context, portfolio state, and broker accounts. + + Indicator runtime — PineTS compilation, series normalization, local or sandboxed execution, and trigger detection turn custom indicator code into reusable signals. +
+ Workflow executor — Versioned definitions run through agent, function, condition, router, loop, parallel, wait, trigger, and response handlers, with background jobs and streamed run events. + + Persistence and extension boundary — PostgreSQL and Drizzle persist workspace, workflow, monitor, run, and order state; Redis, Socket.IO, and Yjs synchronize it; APIs, SDKs, MCP, webhooks, and custom tools extend it. +
+ +## Copilot MCP + +The Copilot-MCP allows Claude Code, Cursor, OpenCode, Codex, Antigravity, or Gemini CLI to control/edit your TradingGoose workspace. It requires Node.js 18 or newer and access to the TradingGoose instance. + +Choose your platform and open only the instructions you need. +
+macOS / Linux / WSL + +```bash +curl -fsSL https://tradinggoose.ai/mcp/setup | sh +``` -You can install TradingGoose MCP to use any local agentic tool like Codex, Claude Code, Cursor, ZCode as Copilot to perform TradingGoose-Studio operations +For a self-hosted instance, replace `https://tradinggoose.ai` with its URL, such as `http://localhost:3000`. -#### Mac/Linux: -connect to the hosted instance: -``` -curl -fsSL https://TradingGoose.ai/mcp/setup | sh -``` +
-connect to self-hosted instance: -``` -curl -fsSL http://localhost:3000/mcp/setup | sh -``` +
+Windows PowerShell -#### Windows -connect to the hosted instance: +```powershell +irm https://tradinggoose.ai/mcp/setup | iex ``` -irm https://TradingGoose.ai/mcp/setup | iex -``` -connect to self-hosted instance: -``` -irm http://localhost:3000/mcp/setup | iex -``` +For a self-hosted instance, replace `https://tradinggoose.ai` with its URL, such as `http://localhost:3000`. -## Quick Start +
+
+The setup endpoint opens an interactive target picker. Append a supported target to configure it directly—for example, `/mcp/setup/codex`. Self-hosted MCP setup requires `API_ENCRYPTION_KEY`. -### Requirements +## FAQ -- **Bun** v1.3+ -- **Docker** (for PostgreSQL) -- **Colima** or **Docker Desktop** (macOS) +
+What can the Monitor system watch? -### Setup Steps +It supports two sources: indicator events from market data, and conditions on a connected broker account's portfolio or positions. Both sources can start a deployed workflow. -#### 1. Install dependencies -``` +
+ +
+Can I build my own AI trading system? + +Yes. Compose monitors, AI agents, tools, rules, approvals, and actions into a system shaped around your own strategy. Deterministic conditions and approvals remain available as controls around the AI analysis. + +
+ +
+Can TradingGoose place trades automatically? + +Only when you explicitly configure a workflow and connect a broker account. TradingGoose does not place trades on its own and is not financial advice. + +
+ +
+Can I write my own indicators? + +Yes. Use the built-in studies or author custom indicators with PineTS, then use their events as monitor inputs. + +
+ +
+Can I self-host it? + +Yes. Run the repository locally or deploy it on your own infrastructure with your own database, credentials, and integrations. + +
+ +## Development + +### Run the repository locally + +
+Installation + +Requires Bun 1.3.14, Node.js 24.x, and Docker. The repository pins Bun `1.3.14`. + +```bash +git clone https://github.com/TradingGoose/TradingGoose-Studio.git +cd TradingGoose-Studio bun install -``` -#### 2. Start PostgreSQL database and Redis -``` -docker run --name tradinggoose-db ` - -e POSTGRES_USER=tradinggoose ` - -e POSTGRES_PASSWORD= ` - -e POSTGRES_DB=tradinggoose ` - -p 5432:5432 ` - -d pgvector/pgvector:pg17 - -docker run -d --name tradinggoose-redis -p 6379:6379 redis -``` +cp apps/tradinggoose/.env.example apps/tradinggoose/.env +cp packages/db/.env.example packages/db/.env -#### 3. Setup environment variables -``` -cd apps/tradinggoose && cp .env.example .env -cd ../../packages/db && cp .env.example .env +docker run --name tradinggoose-db --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=postgres --env POSTGRES_DB=tradinggoose --publish 5432:5432 --detach pgvector/pgvector:pg17 +docker run --name tradinggoose-redis --publish 6379:6379 --detach redis:7.2.1-alpine ``` -#### Edit .env files (see configuration below) -#### 4. Run database migrations -``` +Set `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/tradinggoose"` in both environment files. Replace the `BETTER_AUTH_SECRET`, `ENCRYPTION_KEY`, and `INTERNAL_API_SECRET` placeholders in `apps/tradinggoose/.env` with separate 64-character hexadecimal values. Set `API_ENCRYPTION_KEY` too if you will use API keys or MCP. + +```bash bun run db:migrate -``` -#### 5. Start full development servers -``` bun run dev:full ``` -## Docker Compose +Open [http://localhost:3000](http://localhost:3000). The realtime service runs on port `3002`. -If you use Docker Compose, copy `apps/tradinggoose/.env.example.docker` to -`apps/tradinggoose/.env` and set the required secrets before running the -compose manifests. The `.env` must include `POSTGRES_*`, -`NEXT_PUBLIC_APP_URL`, `NEXT_PUBLIC_SOCKET_URL`, `BETTER_AUTH_SECRET`, -`ENCRYPTION_KEY`, and `INTERNAL_API_SECRET`. Set `API_ENCRYPTION_KEY` when -API-key access or MCP token issuance is used; it encrypts API keys at rest in -the app container. -`NEXT_PUBLIC_SOCKET_URL` should point at `http://localhost:3002` for local -Compose runs; production deployments must override it with a browser-reachable -public URL. The prod and Ollama compose files also require `IMAGE_TAG` and -`OLLAMA_IMAGE_TAG` respectively. +
-``` -docker compose --env-file ./apps/tradinggoose/.env -f docker-compose.local.yml up +
+Repository checks + +Run these from the repository root: + +```bash +bun run build +bun run test +bun run type-check +bun run lint:check +bun run format:check +bun run docs:audit ``` +
+ +
+Tech stack + +| Area | Technologies | +| --- | --- | +| Framework | Next.js | +| Runtime | Bun | +| Database | PostgreSQL Drizzle ORM | +| Authentication | Better Auth | +| UI | shadcn/ui Tailwind CSS | +| State | Zustand | +| Realtime | Socket.IO Yjs | +| Flow editor | React Flow | +| Documentation | Fumadocs | +| Monorepo | Turborepo | +| Background jobs | Trigger.dev | +| Remote execution | E2B | +| Charting | Lightweight Charts | +| Indicator engine | PineTS | +| Drawing tools | Line Tools | + +
## Contributing Pull requests are welcome. -If you want to improve TradingGoose Studio, fix a bug, tighten the docs, or ship -an idea that makes the platform better for traders and builders, open a PR. -Small, focused changes are preferred and easier to review. +If you want to improve TradingGoose-Studio, fix a bug, tighten the docs, or ship an idea that makes the platform better for traders and builders, open a PR. Small, focused changes are preferred and easier to review. -This project is moving quickly and is expected to have bugs and frequent breaking -changes. Contributors should expect the codebase, interfaces, and workflows to -change often, sometimes week to week. +This project is moving quickly and is expected to have bugs and frequent breaking changes. Contributors should expect the codebase, interfaces, and workflows to change often, sometimes week to week. -- Read the [Contributing Guide](.github/CONTRIBUTING.md) for setup, workflow, and PR expectations -- Review the [Code of Conduct](.github/CODE_OF_CONDUCT.md) before participating in project spaces -- Open an issue first if you want feedback on larger changes or architecture work +- Read the [Contributing Guide](.github/CONTRIBUTING.md) for setup, workflow, and PR expectations. +- Review the [Code of Conduct](.github/CODE_OF_CONDUCT.md) before participating in project spaces. +- Follow the [Security Policy](.github/SECURITY.md) when reporting vulnerabilities. +- Open an issue first if you want feedback on larger changes or architecture work. -| Tech Stack | | -| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Framework | Next.JS | -| Runtime | Bun | -| Database | Postgres Drizzle ORM | -| Authentication | Better Auth | -| UI | Shadcn Tailwind CSS | -| State Management | Zustand | -| Realtime | Socket.io Yjs | -| Flow Editor | React Flow | -| Docs | Fumadocs | -| Monorepo | Turborepo | -| Background Jobs | Trigger.dev | -| Remote Execution | E2B | -| Charting | Lightweight Charts | -| Indicator Engine | PineTS | -| Drawing Tools | Line Tools | +## License +TradingGoose-Studio is licensed under **AGPL-3.0**. See [LICENSE](LICENSE) for the complete terms and [NOTICE](NOTICE), [THIRD-PARTY-LICENSES](THIRD-PARTY-LICENSES), and [LICENSES](LICENSES/) for third-party notices and license texts. +The combined project remains AGPL-3.0 so users can use, study, modify, self-host, and redistribute it under the same terms. TradingGoose-Studio integrates PineTS under its AGPL terms; the corresponding attribution and license text are preserved in the repository. -## Special Thanks +Apache-2.0 notices and the full text for included Apache-licensed components are preserved in [LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt) and [THIRD-PARTY-LICENSES](THIRD-PARTY-LICENSES). -Special thanks to the [Sim Studio](https://github.com/simstudioai) team for open-sourcing the original project this repository is built on top of. -TradingGoose Studio started from Sim Studio [`v0.4.25`](https://github.com/simstudioai/sim/releases/tag/v0.4.25). +The chart drawing tools vendored in `apps/tradinggoose/widgets/widgets/data_chart/plugins/` carry their own [MPL-2.0 license](apps/tradinggoose/widgets/widgets/data_chart/plugins/LICENSE). Those modified source files remain covered by MPL-2.0 at the file level; the repository-level AGPL-3.0 distribution does not replace those terms. --- -## License - -The combined TradingGoose Studio project is licensed under **AGPL-3.0-only** - see the [LICENSE](LICENSE) file for details. -This is not because the Sim Studio upstream is Apache-2.0; those upstream notices remain preserved. The combined -project is AGPL-3.0-only because TradingGoose Studio integrates PineTS under its AGPL terms, and this project is -intentionally kept as free software so users can use, study, modify, self-host, and redistribute it under the same terms. -Additional attributions and third-party license texts are in [NOTICE](NOTICE) and [THIRD-PARTY-LICENSES](THIRD-PARTY-LICENSES). -The Apache-2.0 text preserved for Sim Studio and Lightweight Charts is in [LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt). -Those Apache-related notices still stay in the repository because this project is derived from Apache-licensed upstream -code and also distributes Apache-licensed third-party components whose attribution notices must be preserved. - -The chart drawing tools vendored in `apps/tradinggoose/widgets/widgets/data_chart/plugins/` -carry their own MPL-2.0 license file at -`apps/tradinggoose/widgets/widgets/data_chart/plugins/LICENSE`. -Those vendored modified source files remain covered by the MPL-2.0 at the file level. -The project's overall AGPL-3.0-only distribution does not replace or remove the MPL-2.0 -terms that continue to apply to that directory. - ---- - - - - - - - - Project Footer - +

+ + + + TradingGoose + +

diff --git a/apps/docs/app/[lang]/layout.tsx b/apps/docs/app/[lang]/layout.tsx index 02c79e589..47bffa22a 100644 --- a/apps/docs/app/[lang]/layout.tsx +++ b/apps/docs/app/[lang]/layout.tsx @@ -1,11 +1,11 @@ import type { ReactNode } from 'react' +import { Analytics } from '@vercel/analytics/next' import { defineI18nUI } from 'fumadocs-ui/i18n' -import { DocsLayout } from '@/components/layout/docs' import { RootProvider } from 'fumadocs-ui/provider/next' import { Geist_Mono, Inter } from 'next/font/google' import Image from 'next/image' import { notFound } from 'next/navigation' -import { Analytics } from '@vercel/analytics/next' +import { DocsLayout } from '@/components/layout/docs' import '../global.css' import { i18n } from '@/lib/i18n' import { source } from '@/lib/source' @@ -110,7 +110,7 @@ export default async function Layout({ children, params }: LayoutProps) { title: 'Documentations', url: `/${locale}`, logo: ( -
+
TradingGoosediv, -#nd-sidebar-mobile [data-radix-scroll-area-viewport]>div { +#nd-sidebar [data-radix-scroll-area-viewport] > div, +#nd-sidebar-mobile [data-radix-scroll-area-viewport] > div { min-width: 0 !important; display: block !important; } diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx index 5e83f6074..0c69f9c42 100644 --- a/apps/docs/app/layout.config.tsx +++ b/apps/docs/app/layout.config.tsx @@ -7,5 +7,4 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared' * Home Layout: app/(home)/layout.tsx * Docs Layout: app/docs/layout.tsx */ -export const baseOptions: BaseLayoutProps = { -} +export const baseOptions: BaseLayoutProps = {} diff --git a/apps/docs/cli.json b/apps/docs/cli.json index 15844394d..06dee4a2d 100644 --- a/apps/docs/cli.json +++ b/apps/docs/cli.json @@ -8,4 +8,4 @@ }, "baseDir": "", "commands": {} -} \ No newline at end of file +} diff --git a/apps/docs/components/language-toggle.tsx b/apps/docs/components/language-toggle.tsx index 13e1967b1..30714f2a4 100644 --- a/apps/docs/components/language-toggle.tsx +++ b/apps/docs/components/language-toggle.tsx @@ -1,19 +1,15 @@ -'use client'; -import { type ButtonHTMLAttributes, type HTMLAttributes } from 'react'; -import { useI18n } from 'fumadocs-ui/contexts/i18n'; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from './ui/popover'; -import { cn } from '../lib/cn'; -import { buttonVariants } from './ui/button'; +'use client' +import type { ButtonHTMLAttributes, HTMLAttributes } from 'react' +import { useI18n } from 'fumadocs-ui/contexts/i18n' +import { cn } from '../lib/cn' +import { buttonVariants } from './ui/button' +import { Popover, PopoverContent, PopoverTrigger } from './ui/popover' -export type LanguageSelectProps = ButtonHTMLAttributes; +export type LanguageSelectProps = ButtonHTMLAttributes export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { - const context = useI18n(); - if (!context.locales) throw new Error('Missing ``'); + const context = useI18n() + if (!context.locales) throw new Error('Missing ``') return ( @@ -25,27 +21,27 @@ export function LanguageToggle(props: LanguageSelectProps): React.ReactElement { color: 'ghost', className: 'gap-1.5 p-1.5', }), - props.className, + props.className )} > {props.children} - -

+ +

{context.text.chooseLanguage}

{context.locales.map((item) => ( - ); + ) } export function LargeSearchToggle({ hideIfDisabled, ...props }: ComponentProps<'button'> & { - hideIfDisabled?: boolean; + hideIfDisabled?: boolean }) { - const { enabled, hotKey, setOpenSearch } = useSearchContext(); - const { text } = useI18n(); - if (hideIfDisabled && !enabled) return null; + const { enabled, hotKey, setOpenSearch } = useSearchContext() + const { text } = useI18n() + if (hideIfDisabled && !enabled) return null return ( - ); + ) } diff --git a/apps/docs/components/sidebar.tsx b/apps/docs/components/sidebar.tsx index 7702082de..1bc5d9d0f 100644 --- a/apps/docs/components/sidebar.tsx +++ b/apps/docs/components/sidebar.tsx @@ -1,6 +1,4 @@ -'use client'; -import { ChevronDown, ExternalLink } from 'lucide-react'; -import { usePathname } from 'fumadocs-core/framework'; +'use client' import { type ComponentProps, createContext, @@ -8,33 +6,28 @@ import { Fragment, type ReactNode, useContext, - useMemo, useEffect, + useMemo, useState, -} from 'react'; -import Link, { type LinkProps } from 'fumadocs-core/link'; -import { useOnChange } from 'fumadocs-core/utils/use-on-change'; -import { cn } from '../lib/cn'; -import { ScrollArea, ScrollViewport } from './ui/scroll-area'; -import { isActive } from '../lib/is-active'; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from './ui/collapsible'; -import { type ScrollAreaProps } from '@radix-ui/react-scroll-area'; -import { useSidebar } from 'fumadocs-ui/contexts/sidebar'; -import { cva } from 'class-variance-authority'; -import type { - CollapsibleContentProps, - CollapsibleTriggerProps, -} from '@radix-ui/react-collapsible'; -import type * as PageTree from 'fumadocs-core/page-tree'; -import { useTreeContext, useTreePath } from 'fumadocs-ui/contexts/tree'; -import { useMediaQuery } from 'fumadocs-core/utils/use-media-query'; -import { Presence } from '@radix-ui/react-presence'; -import { useSidebarResize } from '../hooks/use-sidebar-resize'; -import { getFolderHref, getFolderSlug } from '@/lib/page-tree'; +} from 'react' +import type { CollapsibleContentProps, CollapsibleTriggerProps } from '@radix-ui/react-collapsible' +import { Presence } from '@radix-ui/react-presence' +import type { ScrollAreaProps } from '@radix-ui/react-scroll-area' +import { cva } from 'class-variance-authority' +import { usePathname } from 'fumadocs-core/framework' +import Link, { type LinkProps } from 'fumadocs-core/link' +import type * as PageTree from 'fumadocs-core/page-tree' +import { useMediaQuery } from 'fumadocs-core/utils/use-media-query' +import { useOnChange } from 'fumadocs-core/utils/use-on-change' +import { useSidebar } from 'fumadocs-ui/contexts/sidebar' +import { useTreeContext, useTreePath } from 'fumadocs-ui/contexts/tree' +import { ChevronDown, ExternalLink } from 'lucide-react' +import { getFolderHref } from '@/lib/page-tree' +import { useSidebarResize } from '../hooks/use-sidebar-resize' +import { cn } from '../lib/cn' +import { isActive } from '../lib/is-active' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible' +import { ScrollArea, ScrollViewport } from './ui/scroll-area' export interface SidebarProps { /** @@ -43,30 +36,30 @@ export interface SidebarProps { * * @defaultValue 0 */ - defaultOpenLevel?: number; + defaultOpenLevel?: number /** * Prefetch links * * @defaultValue true */ - prefetch?: boolean; + prefetch?: boolean /** * Children to render */ - Content: ReactNode; + Content: ReactNode /** * Alternative children for mobile */ - Mobile?: ReactNode; + Mobile?: ReactNode } interface InternalContext { - defaultOpenLevel: number; - prefetch: boolean; - level: number; + defaultOpenLevel: number + prefetch: boolean + level: number } const itemVariants = cva( @@ -75,84 +68,75 @@ const itemVariants = cva( variants: { active: { true: 'bg-fd-accent text-fd-foreground', - false: - 'hover:bg-fd-secondary hover:text-fd-foreground', + false: 'hover:bg-fd-secondary hover:text-fd-foreground', }, }, - }, -); + } +) -const Context = createContext(null); +const Context = createContext(null) const FolderContext = createContext<{ - open: boolean; - setOpen: React.Dispatch>; -} | null>(null); + open: boolean + setOpen: React.Dispatch> +} | null>(null) function sidebarWidthFromString(value: string | null, fallback: number) { - if (!value) return fallback; - const numeric = Number.parseFloat(value); - if (Number.isNaN(numeric)) return fallback; - return value.trim().endsWith('rem') ? numeric * 16 : numeric; + if (!value) return fallback + const numeric = Number.parseFloat(value) + if (Number.isNaN(numeric)) return fallback + return value.trim().endsWith('rem') ? numeric * 16 : numeric } -export function Sidebar({ - defaultOpenLevel = 0, - prefetch = true, - Mobile, - Content, -}: SidebarProps) { - const isMobile = useMediaQuery('(width < 768px)') ?? false; +export function Sidebar({ defaultOpenLevel = 0, prefetch = true, Mobile, Content }: SidebarProps) { + const isMobile = useMediaQuery('(width < 768px)') ?? false const context = useMemo(() => { return { defaultOpenLevel, prefetch, level: 1, - }; - }, [defaultOpenLevel, prefetch]); + } + }, [defaultOpenLevel, prefetch]) return ( {isMobile && Mobile != null ? Mobile : Content} - ); + ) } export function SidebarContent(props: ComponentProps<'aside'>) { - const { collapsed, setCollapsed } = useSidebar(); - const DEFAULT_WIDTH = 224; - const MIN_WIDTH = 224; - const MAX_WIDTH = 352; - const COLLAPSED_WIDTH = 48; + const { collapsed, setCollapsed } = useSidebar() + const DEFAULT_WIDTH = 224 + const MIN_WIDTH = 224 + const MAX_WIDTH = 352 + const COLLAPSED_WIDTH = 48 - const [width, setWidth] = useState(DEFAULT_WIDTH); - const [isDragging, setIsDragging] = useState(false); + const [width, setWidth] = useState(DEFAULT_WIDTH) + const [isDragging, setIsDragging] = useState(false) useEffect(() => { - const match = document.cookie.match(/docs-sidebar:width=([^;]+)/); - if (!match) return; - const cookieWidth = sidebarWidthFromString( - decodeURIComponent(match[1]), - DEFAULT_WIDTH, - ); - const clamped = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, cookieWidth)); - setWidth(clamped); - }, []); + const match = document.cookie.match(/docs-sidebar:width=([^;]+)/) + if (!match) return + const cookieWidth = sidebarWidthFromString(decodeURIComponent(match[1]), DEFAULT_WIDTH) + const clamped = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, cookieWidth)) + setWidth(clamped) + }, []) // Keep layout padding/nav offsets in sync with the sidebar width. useEffect(() => { - const currentWidth = collapsed ? COLLAPSED_WIDTH : width; - const px = `${currentWidth}px`; - document.documentElement.style.setProperty('--fd-sidebar-width', px); - document.documentElement.style.setProperty('--sidebar-offset', px); - }, [collapsed, width, COLLAPSED_WIDTH]); + const currentWidth = collapsed ? COLLAPSED_WIDTH : width + const px = `${currentWidth}px` + document.documentElement.style.setProperty('--fd-sidebar-width', px) + document.documentElement.style.setProperty('--sidebar-offset', px) + }, [collapsed, width, COLLAPSED_WIDTH]) const { dragRef, handleMouseDown } = useSidebarResize({ direction: 'right', currentWidth: `${width}px`, onResize: (nextWidth) => { - const parsed = sidebarWidthFromString(nextWidth, DEFAULT_WIDTH); - const clamped = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, parsed)); - setWidth(clamped); + const parsed = sidebarWidthFromString(nextWidth, DEFAULT_WIDTH) + const clamped = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, parsed)) + setWidth(clamped) }, onToggle: () => setCollapsed((prev) => !prev), isCollapsed: collapsed, @@ -161,20 +145,20 @@ export function SidebarContent(props: ComponentProps<'aside'>) { setIsDraggingRail: setIsDragging, widthCookieName: 'docs-sidebar:width', widthCookieMaxAge: 60 * 60 * 24 * 30, - }); + }) return ( - ); + ) } -export function SidebarContentMobile({ - className, - children, - ...props -}: ComponentProps<'aside'>) { - const { open, setOpen } = useSidebar(); - const state = open ? 'open' : 'closed'; +export function SidebarContentMobile({ className, children, ...props }: ComponentProps<'aside'>) { + const { open, setOpen } = useSidebar() + const state = open ? 'open' : 'closed' return ( <>
setOpen(false)} /> {({ present }) => (