Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
a0f8f81
update all dependencies
bryantgillespie Apr 23, 2026
693035f
add packageManager back
bryantgillespie Apr 23, 2026
40b762b
remove hardcoded url
bryantgillespie Apr 23, 2026
12e54f4
wip
bryantgillespie Apr 26, 2026
fb43401
cleanup from review
bryantgillespie Apr 26, 2026
2e61cb1
cleanup again
bryantgillespie Apr 27, 2026
1c08e8b
switch to json for storing redirects
bryantgillespie Apr 27, 2026
f785c96
convert to ts
bryantgillespie Apr 28, 2026
23d0e7f
cleanup
bryantgillespie Apr 28, 2026
94d9c40
ai cleanup
bryantgillespie Apr 28, 2026
c8949ec
Update README.md
bryantgillespie Apr 28, 2026
4e55dfb
backfill all ids
bryantgillespie Apr 28, 2026
0c01c40
feat(nav): rename Connect→APIs, Automate→Flows, Manage→Hosting
bryantgillespie Apr 28, 2026
fb26a69
feat(llms): rename guide section titles to APIs and Flows
bryantgillespie Apr 28, 2026
853c5b3
docs: replace branded "Directus Connect"/"Directus Automate" with API…
bryantgillespie Apr 28, 2026
5411aef
docs(homepage): drop Data Engine/Data Studio framing
bryantgillespie Apr 28, 2026
0548d61
fix: fold 11.integrations into 12.integrations to clear numeric colli…
bryantgillespie Apr 28, 2026
8d9a727
fix sidebar
bryantgillespie Apr 28, 2026
e1a7980
docs(nav): rename Guides→Guide, Hosting→Deploy
bryantgillespie Apr 28, 2026
2064fcb
kill editor and explorer
bryantgillespie Apr 28, 2026
d6971b0
initial moves
bryantgillespie Apr 30, 2026
bf3949e
wippity
bryantgillespie Apr 30, 2026
13fa1e4
cleanup
bryantgillespie Apr 30, 2026
5d04c17
fix nav
bryantgillespie Apr 30, 2026
8e6cdb2
extract findNavNode
bryantgillespie Apr 30, 2026
1b1f78c
vanilla to vanilla js
bryantgillespie Apr 30, 2026
91eec5c
fix claude bs
bryantgillespie Apr 30, 2026
3039cb3
oops technologies is needed for the imaages
bryantgillespie Apr 30, 2026
45877f7
add chips to tutorials with framworks
bryantgillespie Apr 30, 2026
997dbc8
more cleanup
bryantgillespie Apr 30, 2026
1c32969
All Frameworks
bryantgillespie Apr 30, 2026
7e35e8e
feat(layout): redesign docs experience
bryantgillespie May 21, 2026
459d1ad
feat(layout): add prompt and shader debug components
bryantgillespie May 21, 2026
62c0b4e
fix(layout): restore reference content and skip API prerender
bryantgillespie May 22, 2026
747cfe3
fix(build): stop masking prerender errors
bryantgillespie May 22, 2026
e7e1665
feat(layout): use Directus docs typography
bryantgillespie May 22, 2026
8c19d67
fix(build): inline unhead for preview
bryantgillespie May 22, 2026
79aea9c
chore(layout): remove unused legacy helpers
bryantgillespie May 22, 2026
e17931d
Clean up docs navigation and icons
bryantgillespie May 22, 2026
56ee43f
fix layous
bryantgillespie May 22, 2026
0697c99
Update Railway tutorial image
bryantgillespie May 23, 2026
30b9c2c
Merge origin/main
bryantgillespie May 23, 2026
90cffc0
Skip base-prefixed API prerender
bryantgillespie May 23, 2026
cb79938
fix(build): lazy-load OAS spec to reduce prerender memory
bryantgillespie May 23, 2026
510ac87
increase concurrency
bryantgillespie May 23, 2026
45bbfb2
fix icon
bryantgillespie May 23, 2026
88890e0
feat(hero): painterly Kuwahara shader with fade-up painting layer
bryantgillespie May 26, 2026
9bc44cb
feat(search): add Typesense docs search
bryantgillespie May 21, 2026
73da998
Add Typesense search refinements
bryantgillespie May 26, 2026
9d77993
Address search review feedback
bryantgillespie May 26, 2026
3a2c0ce
docs(mcp): clarify MCP overview title
bryantgillespie May 21, 2026
2e2b762
feat(mcp): add docs MCP server and tools
bryantgillespie May 21, 2026
9da3905
Address MCP review feedback
bryantgillespie May 26, 2026
ba572bd
feat(assistant): add AI docs assistant
bryantgillespie May 21, 2026
b5fdca9
fix(layout): remove invalid navigation field
bryantgillespie May 21, 2026
404f6bf
feat(assistant): add search palette handoff
bryantgillespie May 21, 2026
69ceaab
refactor(assistant): swap @vercel/kv for @upstash/redis
bryantgillespie May 26, 2026
9443dbe
Merge origin/main
bryantgillespie Jun 1, 2026
ea6e9c8
Merge origin/main
bryantgillespie Jun 2, 2026
c3b0888
Add docs assistant hardening
bryantgillespie Jun 2, 2026
cc95c73
Merge branch 'main' into bry/dockem-8-ai-assistant
bryantgillespie Jun 2, 2026
1224dd5
Document assistant contribution setup
bryantgillespie Jun 2, 2026
fd1801c
Allow assistant on docs preview aliases
bryantgillespie Jun 2, 2026
fa3c35b
Add marketing-page fetch tool, update MSCL licensing in assistant
bryantgillespie Jun 2, 2026
d33d159
Add core-tier, data-safety, and v12 grace facts to assistant prompt
bryantgillespie Jun 2, 2026
77927af
Show readable label for get-directus-page in sources list
bryantgillespie Jun 2, 2026
7210440
Address assistant review notes
bryantgillespie Jun 2, 2026
a2245ac
Merge branch 'main' into bry/dockem-8-ai-assistant
bryantgillespie Jun 2, 2026
edd9a3a
Remove assistant ADR
bryantgillespie Jun 2, 2026
d8220d7
Resolve assistant review feedback
bryantgillespie Jun 3, 2026
63b11a0
Fix UTF-8 tool result truncation
bryantgillespie Jun 3, 2026
4a29e71
Fix assistant runtime helper import
bryantgillespie Jun 3, 2026
fe085c0
Avoid assistant module entry in tests
bryantgillespie Jun 3, 2026
7ecdc79
Merge branch 'main' into bry/dockem-8-ai-assistant
bryantgillespie Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,27 @@ TYPESENSE_PRIVATE_API_KEY=typesense_private_api_key_required_for_indexing_only
DIRECTUS_URL=https://marketing-directus-url.com
GOOGLE_TAG_MANAGER_ID=GTM-PTLT3GH
POSTHOG_API_HOST=https://directus.com/ingest
POSTHOG_AI_HOST=https://us.i.posthog.com
POSTHOG_API_KEY=phc_secret_key_here
NUXT_PUBLIC_SITE_URL=https://directus.com
NUXT_PUBLIC_OG_BASE_URL=https://og.directus.com
# Required outside local OG worker dev.
# NUXT_OG_SIGNING_SECRET=shared_secret_from_website_og_worker
# OG_SIGNING_SECRET=shared_secret_from_website_og_worker
# Optional. Fine-grained PAT, public repos read-only. Required for code search and raises GitHub raw rate limits.
# GITHUB_TOKEN=github_pat_...
# Optional. Enables the in-site assistant chat. Get a key at https://openrouter.ai/keys.
# OPENROUTER_API_KEY=sk-or-v1-...
# Optional. Override the default model.
# AI_MODEL=google/gemini-3.1-flash-lite
# ASSISTANT_ENABLED=true
# Required when assistant is enabled. Long random string for daily fingerprint salting.
# ASSISTANT_FP_SECRET=change-me
# Dev only. Allows local reset endpoint to clear assistant limits.
# ASSISTANT_RESET_TOKEN=change-me
# Dev only. Shorten burst window for curl testing.
# RATE_LIMIT_WINDOW_MS=60000
# Optional. PostHog survey id used for thumbs up/down feedback on assistant responses.
# ASSISTANT_FEEDBACK_SURVEY_ID=019e081e-2c3b-0000-04c3-564ad5dff4ed
# Optional. Upstash Redis for cross-instance assistant daily rate limits. Falls back to per-process memory if unset.
# UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
# UPSTASH_REDIS_REST_TOKEN=your_upstash_token
55 changes: 55 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Directus Docs — Domain Language

Shared vocabulary for this codebase. Seeded with rate-limiting terms; extend as other areas are sharpened.

## Language

### Rate limiting

**Burst limit**:
A short-window cap (seconds) on requests per identity, the cheap first gate against rapid-fire abuse.
_Avoid_: throttle, flood limit

**Daily limit**:
A per-UTC-day cap with multiple keyed dimensions (exact IP, IP prefix, fingerprint, IP+fingerprint combo) and degraded modes. The deep backstop; distinct from a burst limit.
_Avoid_: quota, cap

**Policy**:
What a caller declares to a limiter: `max`, `windowSeconds`, and `onStoreError`. The caller picks a policy; it never reimplements the algorithm.
_Avoid_: config, rule, options

**Store**:
The counting backend behind a limiter — `incr(key, ttlSeconds) -> count`. `MemoryStore` for local dev, `UpstashStore` for production (atomic, holds across serverless instances).
_Avoid_: backend, cache, KV (KV is one specific store)

**Verdict**:
The result of a limit check: `{ ok, retryAfter? }`. What the caller acts on.
_Avoid_: result, decision, response

**Fail closed / fail open**:
When the store errors, a `deny` policy fails closed (refuse the request); an `allow` policy fails open (let it through). Expensive/abusable endpoints deny; cheap public reads allow.

### Assistant

**Conversation history**:
The deep, framework-agnostic owner of persisted conversations (`useAssistantHistory`): localStorage sync, sorting, title derivation, compaction, and the `activeId`. Enforces the invariant that `activeId` points to an existing conversation — mutate it only through `setActive`/`clearActive`/`startNew`/`remove`, never by assigning the returned ref.
_Avoid_: conversation store, chat state

**Message transition**:
A change to the chat's message list — reset, open a saved conversation, delete the active one, or the easter egg. All transitions go through the single `setMessages(next)` writer in `useAssistant`, so the persistence watcher reasons about one mutation idiom. `useAssistant` is the Vue-reactive surface; it is not a separate session module.
_Avoid_: chat singleton, assistant session, set chat messages

## Relationships

- A caller passes a **policy** to a limiter and acts on the returned **verdict**.
- A limiter counts against a **store**; the store knows nothing about limits.
- **Burst limit** and **daily limit** are separate limiters. Burst is the cheap first gate; daily is the deep backstop.

## Example dialogue

> **Dev:** "If Upstash is down, does the burst limit block the assistant?"
> **Maintainer:** "Yes — the assistant burst **policy** is `onStoreError: 'deny'`, so it fails closed. The docs API **policy** is `'allow'`, so a store blip never takes down public docs search."

## Flagged ambiguities

- "rate limit" was used for both the burst and daily limiters. Resolved: they are distinct concepts — **burst limit** (short window, single key, process- or store-counted) vs **daily limit** (per-day, multi-key, degraded modes). Only the burst-style limiters share the `checkRateLimit` core.
228 changes: 228 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# Contributing to Directus Docs

This guide covers local development, docs authoring, search indexing, deployment, and the optional AI Assistant.

## Running the Docs

### Requirements

- Node.js 22.18 or later
- pnpm

### Install Dependencies

```bash
pnpm install
```

### Setup Environment

Copy the example environment file:

```bash
cp .env.example .env
```

Update `.env` with the service keys you need for the feature you are working on. Most docs-only changes do not require every optional secret.

### Run Development Server

Start the development server on `http://localhost:3000`:

```bash
pnpm dev
```

### Building Locally

```bash
pnpm build
```

## Authoring Content

Pages live as Markdown files under `content/`. Frontmatter fields are validated by the schema in `content.config.ts`.

### Framework Guides

Framework guides live under `content/frameworks/<framework>/`. The numeric prefix on filenames (`01.`, `02.`, ...) controls sidebar sort order only. It has no semantic meaning; renumber freely.

The `section` frontmatter field controls grouping on the `/frameworks/<framework>` hub page:

- `section: start-here` appears in the "Start Here" block at the top.
- `section: guides` or unset appears in the "Guides" block below.

Minimal frontmatter for a new framework guide:

```yaml
---
title: Fetch Data from Directus with Foo
description: Learn how to integrate Directus in your Foo app.
section: start-here
technologies:
- foo
navigation:
title: Data Fetching
---
```

## Repository Tooling

The repository includes scripts that keep docs routes stable when files move and that index the docs into Typesense.

```bash
pnpm stable-ids:ensure # Add missing stableId frontmatter
pnpm stable-ids:check # Validate stableId frontmatter
pnpm redirects:sync # Update redirects.json for moved pages
pnpm redirects:check # Check redirect coverage without writing files
pnpm index:docs # Build the search index in Typesense
pnpm typesense:cleanup-preview # Delete stale Typesense preview indexes
pnpm typecheck:scripts # Type check repository scripts
```

Stable IDs give each public docs page a permanent identity. Nuxt Content derives its unique page IDs from file paths, so moving a page changes its built-in ID. Redirect sync compares the current branch to `origin/main`, so moved pages keep their old URLs working.

CI runs `pnpm stable-ids:check` and `pnpm redirects:check` for docs changes.

- New docs page: run `pnpm stable-ids:ensure`, then commit the new `stableId`.
- Moved docs page: keep the existing `stableId`, run `pnpm redirects:sync`, then commit `redirects.json`.
- Deleted, split, or merged docs page: run `pnpm redirects:sync`, review `.docs/redirect-decisions-needed.md`, choose target redirects, then re-run `pnpm redirects:check`.
- Before opening a PR: run `pnpm stable-ids:check` and `pnpm redirects:check`.

Redirect scripts compare against `origin/main` by default. To check a release branch or another target, fetch it first, then pass `--base` directly to the script:

```bash
git fetch origin release/v13
node scripts/redirects-sync.ts --base origin/release/v13 --no-write --fail-on-unresolved
node scripts/redirects-sync.ts --base origin/release/v13 --write-deterministic --fail-on-unresolved
```

## AI Assistant

The in-site AI Assistant is optional. It is disabled unless `OPENROUTER_API_KEY` is present and `ASSISTANT_ENABLED` is not set to `false`.

### Required for Vercel

```bash
OPENROUTER_API_KEY=sk-or-v1-...
ASSISTANT_FP_SECRET=long-random-string
```

`ASSISTANT_FP_SECRET` salts daily fingerprint identifiers. Use a long random value and keep it secret.

### Recommended for Vercel

Use one supported Redis env pair for cross-instance burst and daily limits:

```bash
UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_upstash_token
```

or:

```bash
KV_REST_API_URL=https://your-db.upstash.io
KV_REST_API_TOKEN=your_upstash_token
```

Without Redis env vars, limits fall back to per-process memory. That is fine for local development, but not enough for production or preview deployments.

### Optional

```bash
AI_MODEL=google/gemini-3.1-flash-lite
ASSISTANT_ENABLED=true
ASSISTANT_FEEDBACK_SURVEY_ID=019e081e-2c3b-0000-04c3-564ad5dff4ed
GITHUB_TOKEN=github_pat_...
POSTHOG_AI_HOST=https://us.i.posthog.com
```

- `ASSISTANT_ENABLED=false` is the kill switch. Set it before build/deploy to hide the assistant and skip the server route; the server also rejects requests when the flag is false.
- `GITHUB_TOKEN` is required for the assistant's source-code search tool and raises GitHub raw-file rate limits.
- `ASSISTANT_FEEDBACK_SURVEY_ID` enables thumbs up/down feedback on assistant responses.
- `POSTHOG_AI_HOST` only needs to be set when AI telemetry should use a different PostHog host than `POSTHOG_API_HOST`.

### Local Testing

Useful assistant tests:

```bash
pnpm exec vitest run modules/assistant/index.test.ts modules/assistant/runtime/composables/useAssistant.test.ts modules/assistant/runtime/server/utils/admit.test.ts modules/assistant/runtime/server/utils/abuse-gate.test.ts modules/assistant/runtime/server/utils/bind-tools.test.ts modules/assistant/runtime/server/utils/rate-limit.test.ts modules/assistant/runtime/server/utils/request-context.test.ts server/utils/rate-limit.test.ts server/utils/docs-api-limit.test.ts
```

Dev-only helpers:

```bash
ASSISTANT_RESET_TOKEN=change-me
RATE_LIMIT_WINDOW_MS=60000
POSTHOG_AI_DEBUG=true
POSTHOG_AI_SELF_TEST=true
```

The reset/status endpoints are only registered in dev and only respond from a local development context.

## Search

Search is powered by [Typesense](https://typesense.org). The browser palette (`UCommandPalette`-based) lives at `app/components/DocsSearchPalette.vue` and queries Typesense directly via `app/services/typesenseService.ts`. The official `typesense` npm client is used by the indexer only.

### Indexing

The indexer at `scripts/index-docs.ts` walks `/content`, chunks each Markdown page, attaches synonyms, and pushes everything to Typesense. OpenAPI indexing is deferred to a later branch. Run it locally with:

```bash
pnpm index:docs
```

CI runs the same command on every push to `main` (production index) and on every PR commit (per-branch preview index). See `.github/workflows/search-index.yml`.

### Collection Naming

Indexes use a blue/green slot pattern with a stable alias:

- `main` -> alias `directus-docs`, slots `directus-docs-a` / `directus-docs-b`
- Branch `bry/foo` -> alias `directus-docs-preview-bry-foo`, slots `...-a` / `...-b`
- Local branch runs use the same branch-derived alias as CI

Each indexer run writes to whichever slot the alias is not currently pointing at, swaps the alias, then deletes the previous slot.

For one-off writes, override the index target with `TYPESENSE_INDEX_TARGET=...`.

The browser reads from `TYPESENSE_COLLECTION` when set. Otherwise it derives the same branch alias as the indexer. The app reads the alias, never the `-a` / `-b` slot name.

### Preview Cleanup

PR preview indexes are deleted when same-repo PRs close. The cleanup job deletes the branch alias and both fixed slots:

```bash
pnpm typesense:cleanup-preview --branch bry/foo
```

For one-time cleanup of accumulated preview indexes, run a dry run first:

```bash
pnpm typesense:cleanup-preview --stale --dry-run
pnpm typesense:cleanup-preview --stale
```

Stale cleanup keeps preview aliases for currently open PR branches and deletes the rest. It requires `TYPESENSE_URL`, `TYPESENSE_PRIVATE_API_KEY`, and authenticated `gh`.

### Ranking

Section boosts and personalization live in `buildPersonalizedSortBy` in `app/composables/useDocsSearch.ts`. The same `sectionPriority` array drives both the Typesense `_eval` boost order and the chip-bar render order in the palette.

### Synonyms

Search synonyms live in `server/data/synonyms.ts` and are pushed to Typesense on every indexer run. Two formats: `multiway` (equivalent terms) and `oneway` (directional shorthand -> canonical, e.g. `db -> database`). Header comment in the file explains both.

### Search-Friendly Content

Write H2s and first paragraphs so they work as standalone search results.

## Deploying the Docs

The documentation automatically deploys to Vercel when changes are merged into the main branch.

1. Open a pull request.
2. Review the deploy preview.
3. Once the PR is approved and merged to `main`, Vercel builds and deploys the updated documentation.
Loading
Loading