9618231dfc feat(docs): gate MDX docs on Crowdin-safe placeholders (#24540)
Tracked as `DOCS-01` on twentyhq/core-team-issues#2784.

### Problem

Crowdin parses `<foo>` in prose as a tag rather than literal text, so an
angle bracket placeholder is dropped or mangled in every translated
page. Curly braces `{foo}` survive the round trip.

A `no-angle-bracket-placeholders` rule used to guard against this. It
ran as `'error'` on `**/*.mdx` in the root `eslint.config.mjs` from
#15751 (Nov 2025) until #18443 migrated us to oxlint. oxlint has no
`eslint-plugin-mdx` equivalent, so the whole `**/*.mdx` block went with
it and the rule stopped running. The file was carried into
`twenty-oxlint-rules` wired to nothing, and #24535 removed it as dead
code. So the guard has been silently off for about five months, not
missing by design.

This restores it in a form that runs without a JSX AST: a plain text
scan, no new dependencies.

### Change

- `scripts/lint-mdx.ts` scans the 204 English source pages and skips
`l/` (the 2613 generated Crowdin files, not hand-editable). A violation
there is only ever a symptom of one in the source.
- The `lint` target was `{}`, an empty no-op, while the `docs-lint` CI
job invoked `nx lint twenty-docs`. So the job has been passing without
running anything. The target now runs the package's own `.oxlintrc.json`
(nothing was running that either, so `scripts/*.ts` is linted for the
first time) followed by the MDX scan.
- A `test` target plus a vitest config for the package, wired into CI.
14 tests.

### On the backtick handling

The scanner skips angle brackets inside code spans, since they are
legitimate there. The first version did this with a running backtick
parity count over the whole file, which is unsound: one unpaired
backtick in prose flips parity and silently suppresses every finding
after it in the file, while the run still prints clean.
`docker-compose.mdx` was already in that state from line 210.

Fixed in 54dff6e4: backtick runs are now paired within a line, by run
length, which is how inline code actually delimits. Three tests pin it,
all of which fail against the old counter.

### Verification

```
$ npx nx test twenty-docs
Tests  14 passed (14)
```

End to end on the real CI command. Seeding two violations into
`getting-started/introduction.mdx`:

```
$ npx nx lint twenty-docs
getting-started/introduction.mdx:78:5    <workspace-slug> reads as a tag in Crowdin, use {workspace-slug} instead
getting-started/introduction.mdx:78:34   <span> reads as a tag in Crowdin, use {span} instead
2 angle bracket placeholder(s) found in 204 MDX files.
nx lint exit=1
```

Unseeded, the tree is clean: `No angle bracket placeholders in 204 MDX
files.`

To be clear about scope: the gate catches nothing in the tree today. Of
the 83 angle brackets in the English source, 81 sit inside code spans
and 2 are a legitimate `kbd` element. It is a preventive guard, plus the
CI wiring fix above. The live corruption (a backslash before angle
brackets inside inline code in the translated output, 494 across 156
files) is a `crowdin-normalizer` concern, not something a source-side
gate can see, and is handled in a separate PR.

### Not in this PR

`DOCS-01` also covered `mdx-component-newlines` (component tags sharing
a line with prose). Deferring it, but not because the current
occurrences are settled idiom: blame shows 137 of them were authored
while that rule was live at `'error'` and only 68 after it went dark, so
it was never really enforcing. Its opening-tag branch early-returns
whenever the following text is adjacent, so a component wrapping text on
one line never tripped it. Porting it faithfully buys nothing without
redesigning what it checks, which is a separate call that stays on
#2784.


<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/24540?utm_source=github"
rel="nofollow noreferrer noopener" target="_blank">``&lt;img alt="Review
in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"&gt;``</a>

---------

Co-authored-by: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com>
Co-authored-by: Abdul Rahman <ar5438376@gmail.com>
2026-08-24 08:52:23 +00:00

Twenty logo

The #1 Open-Source CRM

Website · Documentation · Roadmap · Discord · Figma

Twenty banner


Why Twenty

Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.

Learn more about why we built Twenty


Installation

Cloud

The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.

Build an app

Scaffold a new app with the Twenty CLI:

npx create-twenty-app my-app

Define objects, fields, and views as code:

import { defineObject, FieldType } from 'twenty-sdk/define';

export default defineObject({
  nameSingular: 'deal',
  namePlural: 'deals',
  labelSingular: 'Deal',
  labelPlural: 'Deals',
  fields: [
    { name: 'name', label: 'Name', type: FieldType.TEXT },
    { name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
    { name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
  ],
});

Then ship it to your workspace:

npx twenty app:publish --private

See the app development guide for objects, views, agents, and logic functions.

Self-hosting

Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.



Everything you need

Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.

Want to go deeper? Read the User Guide for product walkthroughs, or the Documentation for developer reference.

Create your apps

Learn more about apps in doc

Stay on top with version control

Learn more about version control in doc

All the tools you need to build anything

Learn more about primitives in doc

Customize your layouts

Learn more about layouts in doc

AI agents and chats

Learn more about AI in doc

Plus all the tools of a good CRM

Learn more about CRM features in doc


Stack

Thanks

Greptile      Sentry      Crowdin

Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).

Join the Community

Star the repo · Discord · Feature requests · Releases · X · LinkedIn · Crowdin · Contribute

S
Description
No description provided
Readme AGPL-3.0
2.4 GiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 81.2%
MDX 16%
JavaScript 2.4%
Python 0.2%
SCSS 0.1%