---
title: "News Module"
description: "NewsRichEditor Markdown CMS — shortcode widgets, My News, pending revisions, and News Kingdom publishing"
locale: "en"
---
# News Module

Ring news is a full publishing surface: members author articles, promote to the main page when enabled, and readers engage on `/news/[slug]` and `/[username]/[slug]`. Article bodies are **Markdown** (`contentFormat: markdown`) authored in **NewsRichEditor** (WikiRichEditor core + news toolbar). Shortcodes `[[embed:]]` / `[[mood:]]` / `[[video:]]` expand on public pages. TipTap stays only as deferred **TipTapNewsAdapter** for a slash `/` command palette — embeds, Mood, video, and generate-image already ship via the Markdown path.

> **Info**
> Use the **Founder** / **Developer** tabs in the docs sidebar to filter this page. `audience` frontmatter filters content blocks; sidebar visibility is curated in `lib/docs/audience-curated-docs.ts`.

  
- **[News Kingdom architecture](/docs/architecture/news-kingdom.md)** — Migrations, promotion workflow, scoring, and Telegram callbacks.

  
- **[Member blogs](/docs/features/member-blog.md)** — Per-user blog paths and public article URLs.

  
- **[Generative newsroom](/docs/development/generative-newsroom.md)** — Grok drafts Markdown articles, featured images, TTS, locale fan-out.

  
- **[Admin Wiki](/docs/features/admin-wiki.md)** — Postgres Markdown + wikilinks vault — separate from the news CMS.

## What ships today

| Surface | What it does |
|---------|----------------|
| **NewsRichEditor** | WikiRichEditor + toolbar: Embed URL, Mood, Video, Generate image; paste lone URL → `[[embed:]]`; `preserveNewsShortcodes` |
| Shortcodes | `[[mood:ID]]` → ``; `[[embed:URL]]` → `` / iframe; `[[video:SRC]]` → ``; gen-image → `![…](url)` |
| TipTapNewsAdapter (deferred) | Slash `/` palette UX only — file retained, **not** mounted on ArticleEditor |
| My News | `/my-news` — dashboard, charts, create/edit, **Proposed amendments** |
| Create / Revise | `/news/create`; **Revise** → `/news/[slug]/revise` → `pending-revision` |
| Amendments | Author hunk preview → Accept applies body / Reject closes |
| Public render | `NewsMarkdownView` — shortcodes → Markdown → sanitize → restore widgets |
| Collab (gated) | Y.Text `newsMarkdown` on `collab:news:{articleId}` when `NEXT_PUBLIC_COLLAB_ENABLED=true` |

### For founders

## Operator journey

### Author a story

1. Open **My News** (`/my-news`) or `/news/create`.
2. Write in **NewsRichEditor** — headings, lists, images via the Markdown toolbar.
3. Use **Embed URL**, **Mood**, **Video**, or **Generate image** (or paste a lone URL) to insert shortcodes / images.
4. Preview shows the same public Markdown + widget pipeline. Publish when ready; use promotion panels if your clone enables paid/main-page promotion.

### Community revise

1. Any **member** (not the author) sees **Revise** on a published article.
2. They edit a copy and submit — the live page does **not** change yet.
3. On **My News**, the author sees **Proposed amendments: N**.
4. Preview: deleted lines pale red / struck; added lines pale green.
5. **Accept** updates the published body; **Reject** closes that proposal.

  Members improve accuracy without silent overwrite. Authors stay in control of the live article.

### Permissions (simplified)

| Action | Who |
|--------|-----|
| Create / edit own | Member+ (and admins) |
| Propose revision on published | Member+ (author uses Edit) |
| Accept / reject amendments | Article author or platform admin |
| Invite co-editors by email | Scaffolded APIs only (501) — not a full UI yet |
| Live Markdown collab | Needs `NEXT_PUBLIC_COLLAB_ENABLED=true` + native WSS |

### For developers

## News body — Markdown SSOT + NewsRichEditor

Authoring SSOT is **Markdown**. `ArticleEditor` and revise mount **NewsRichEditor**, which wraps WikiRichEditor with news-only widget chrome. CV/wiki keep bare WikiRichEditor.

| Path | Role |
|------|------|
| `features/news/components/news-rich-editor.tsx` | News shell: toolbar, paste→embed, collab hook, `preserveNewsShortcodes` |
| `features/wiki/components/wiki-rich-editor.tsx` | Markdown editor core (shared) |
| `features/wiki/wiki-markdown-codec.ts` | `preserveNewsShortcodes` keeps `[[mood\|embed\|video:]]` literal |
| `features/news/lib/news-shortcodes.ts` | Build/extract/restore `[[mood:]]` / `[[embed:]]` / `[[video:]]` |
| `features/news/lib/editor-widget-detector.ts` | Lone-URL paste → embed shortcode |
| `features/news/lib/render-news-markdown.ts` | Public pipeline (below) |
| `features/news/lib/news-markdown-view.tsx` | Public article body component |
| `features/news/components/article-editor.tsx` | Form; mounts NewsRichEditor; Preview → NewsMarkdownView |
| `features/news/components/editor/tiptap-news-editor.tsx` | TipTapNewsAdapter — slash `/` backlog only |
| `features/news/components/editor/rich-text-editor.tsx` | Re-exports TipTapNewsAdapter (not used by CRUD) |
| `app/api/news/embed-preview/route.ts` | `POST` OG metadata for embed hydration |
| `app/_actions/news.ts` / `news-service.ts` | Persist `contentFormat: 'markdown'` |

Autosave: dirty-gated debounce (~3s) `PUT /api/news/[id]` with `{ content }` (Markdown). Explicit Save appends a version commit with `contentFormat: 'markdown'`.

### Public sanitize pipeline

`renderNewsMarkdownToHtml` order (mandatory):

1. Legacy HTML tip → escaped conversion stub (**never** trust raw HTML as the tip body)
2. Extract shortcodes → placeholders
3. `simpleMarkdownToHtml`
4. `sanitizeMarkdownHtml` (MD-only allowlist)
5. Restore widgets (`ring-embed` / `ring-mood-player` / ``)
6. `sanitizeNewsHtml` — final belt for iframes + custom elements

### CRDT collab (Markdown Y.Text — wired)

`NewsRichEditor` calls `useNewsMarkdownCollab` (`features/news/lib/use-news-markdown-collab.ts`). Gate: `isNewsCollabEnabled()` → `NEXT_PUBLIC_COLLAB_ENABLED=true` (`news-collab-gate.ts`). Channel: `useCollaboration('news:{articleId}')` → `collab:news:{articleId}`; Y.Text key `newsMarkdown`. Not TipTap Collaboration.

### My News & create routes

| Route helper (`constants/routes.ts`) | Path |
|--------------------------------------|------|
| `ROUTES.MY_NEWS` | `/my-news` |
| `ROUTES.NEWS_CREATE` | `/news/create` |
| `ROUTES.NEWS_REVISE(slug)` | `/news/[slug]/revise` |
| `ROUTES.MY_NEWS_AMENDMENTS(articleId)` | `/my-news/[articleId]/amendments` |
| `ROUTES.MY_NEWS_AMENDMENT_PREVIEW(...)` | `/my-news/[articleId]/amendments/[revisionId]` |

UI: `app/[locale]/(protected)/my-news/`, `components/wrappers/my-news-wrapper.tsx`.

### Pending revisions API

Collection: `news_revisions` via `DatabaseService`.

| Method | Route | Behavior |
|--------|-------|----------|
| `GET` | `/api/news/[id]/revisions?status=pending-revision` | List |
| `POST` | `/api/news/[id]/revisions` | `{ proposedContent }` → `pending-revision` |
| `GET` | `/api/news/revisions/[revisionId]` | One revision |
| `PATCH` | `/api/news/revisions/[revisionId]` | `{ action: 'accept' \| 'reject' }` |

Service: `features/news/services/revision-service.ts` · Permissions: `news-collaboration-permissions.ts` · Diff UI: `revision-diff.ts` + `revision-hunk-preview.tsx`.

```mermaid
sequenceDiagram
  participant Member
  participant ReviseAPI as POST_revisions
  participant Author
  participant PatchAPI as PATCH_revision
  participant News as news_doc
  Member->>ReviseAPI: proposedContent Markdown
  ReviseAPI-->>Author: pending-revision
  Author->>PatchAPI: accept
  PatchAPI->>News: content equals proposedContent
```

### ACL / invite scaffolding (not full product)

| Piece | Status |
|-------|--------|
| `lib/database/schema/news-collaboration.sql` | Conceptual schema notes |
| `GET/POST /api/news/[id]/collaborators` | **501** stub |
| `GET/POST /api/news/collaboration/invite/[token]` | **501** stub |
| Roles `owner\|admin\|editor\|reviewer\|viewer` | Types only |

### Core news service

`features/news/services/news-service.ts` — CRUD, stats, page views. Promotion / Telegram: [News Kingdom](/docs/architecture/news-kingdom.md). AI drafts: [Generative newsroom](/docs/development/generative-newsroom.md) (`article-generator.ts` prompts Markdown).

## Related reading

- [features/member-blog](/docs/features/member-blog.md) — Same-workflow: member blogs share public article surfaces with news.

- [architecture/news-kingdom](/docs/architecture/news-kingdom.md) — Deep-dive: promotion, Telegram, and News Kingdom migrations.

- [development/generative-newsroom](/docs/development/generative-newsroom.md) — Next-step: Grok generates Markdown drafts into the same news CMS.

- [features/scientific-editor](/docs/features/scientific-editor.md) — See-also: publications TipTap editor — not the news Markdown CMS.

- [architecture/real-time](/docs/architecture/real-time.md) — Depends-on: Tunnel / Yjs channels behind NEXT_PUBLIC_COLLAB_ENABLED.

- [features/admin-wiki](/docs/features/admin-wiki.md) — See-also: Admin Wiki is Markdown + wikilinks — not news shortcodes (NewsRichEditor).

News Kingdom migrations (`002` / `003`) and promotion remain required for paid main-page flows. NewsRichEditor Markdown + shortcodes + pending-revision ship without changing `NewsStatus` (`draft | published | archived | deleted`).
