Wiki upgrade: rich-text editing, categories, drafts, links/backlinks, tags, search, revisions #3

Merged
whitlocktech merged 5 commits from wiki-upgrade into main 2026-06-27 21:22:48 +00:00
Owner

Wiki CMS upgrade

Upgrades the CMS wiki from a flat single-table page store into a feature-complete wiki, delivered in four independently shippable phases. Design + process live in WIKI_UPGRADE.md.

Staff-only (admin/editor) — no public editing. All schema changes are additive and idempotent (applied by ensureSchema() on boot), so existing databases upgrade with no data loss; existing pages backfill to published = 1.

Phase 1 — Foundation & safety

  • New wiki_categories table; wiki_pages gains category_id, excerpt, published/published_at, sort_order, and a FULLTEXT(title, body) index.
  • Server-side HTML sanitization on save (sanitize-html allowlist) + client DOMPurify on render — closes a stored-XSS gap (bodies were previously rendered raw via dangerouslySetInnerHTML).
  • Drafts vs published; public endpoints serve published only.
  • Public index is now data-driven (sections + descriptions); removed the hardcoded blurbs/Roman numerals.

Phase 2 — Authoring UX

  • TipTap (ProseMirror) WYSIWYG editor replacing the raw-HTML textarea: headings, lists, quote/code, links, inline images, undo/redo.
  • Generalized POST /admin/uploads for inline images (reuses the screenshot multer config).
  • Editor is lazy-loaded as its own chunk so the public bundle doesn't ship TipTap.

Phase 3 — Connectivity

  • Internal links to /wiki/<slug> via an in-editor page picker; wiki_links index rebuilt on every save.
  • Backlinks ("Linked from") on articles; links to non-existent pages render as red links.
  • Tags (wiki_tags / wiki_page_tags), auto-pruned when orphaned; tag + category filtering (chips + flat index view).

Phase 4 — Discovery & trust

  • MariaDB FULLTEXT search (MATCH … AGAINST) over title + body; public search box + admin filter.
  • Revision history (wiki_revisions): every save snapshots the page; the admin History modal shows a word-level diff (jsdiff) of any revision against current, with one-click restore (history stays append-only).

New tables

wiki_categories, wiki_tags, wiki_page_tags, wiki_links, wiki_revisions (plus new columns on wiki_pages).

New dependencies

  • server: sanitize-html
  • client: @tiptap/react (+ starter-kit / link / image), dompurify, diff

Testing

Verified end-to-end against MariaDB 11 via the local dev setup: migration runs clean on boot; XSS payloads neutralized; drafts hidden from the public API; rich content (headings/lists/link/inline image) round-trips sanitized; backlinks + red-link detection; tag filtering + orphan pruning; FULLTEXT search matches on body and title; revision diff + restore. Client builds clean; server boots with no errors. Each phase's exit-check is recorded in WIKI_UPGRADE.md.

Notes

  • Every admin write logs to activity_log (wiki.create/update/publish/delete, wiki.category.*, wiki.revision.restore, upload).
  • No new roles and no public-contribution workflow (out of scope by design).

🤖 Generated with Claude Code

## Wiki CMS upgrade Upgrades the CMS wiki from a flat single-table page store into a feature-complete wiki, delivered in four independently shippable phases. Design + process live in [`WIKI_UPGRADE.md`](WIKI_UPGRADE.md). Staff-only (admin/editor) — no public editing. All schema changes are **additive and idempotent** (applied by `ensureSchema()` on boot), so existing databases upgrade with no data loss; existing pages backfill to `published = 1`. ### Phase 1 — Foundation & safety - New `wiki_categories` table; `wiki_pages` gains `category_id`, `excerpt`, `published`/`published_at`, `sort_order`, and a `FULLTEXT(title, body)` index. - **Server-side HTML sanitization on save** (`sanitize-html` allowlist) + client DOMPurify on render — closes a stored-XSS gap (bodies were previously rendered raw via `dangerouslySetInnerHTML`). - Drafts vs published; public endpoints serve published only. - Public index is now data-driven (sections + descriptions); removed the hardcoded blurbs/Roman numerals. ### Phase 2 — Authoring UX - TipTap (ProseMirror) WYSIWYG editor replacing the raw-HTML textarea: headings, lists, quote/code, links, inline images, undo/redo. - Generalized `POST /admin/uploads` for inline images (reuses the screenshot multer config). - Editor is lazy-loaded as its own chunk so the public bundle doesn't ship TipTap. ### Phase 3 — Connectivity - Internal links to `/wiki/<slug>` via an in-editor page picker; `wiki_links` index rebuilt on every save. - Backlinks ("Linked from") on articles; links to non-existent pages render as **red links**. - Tags (`wiki_tags` / `wiki_page_tags`), auto-pruned when orphaned; tag + category filtering (chips + flat index view). ### Phase 4 — Discovery & trust - MariaDB FULLTEXT search (`MATCH … AGAINST`) over title + body; public search box + admin filter. - Revision history (`wiki_revisions`): every save snapshots the page; the admin **History** modal shows a word-level diff (jsdiff) of any revision against current, with one-click **restore** (history stays append-only). ### New tables `wiki_categories`, `wiki_tags`, `wiki_page_tags`, `wiki_links`, `wiki_revisions` (plus new columns on `wiki_pages`). ### New dependencies - server: `sanitize-html` - client: `@tiptap/react` (+ starter-kit / link / image), `dompurify`, `diff` ### Testing Verified end-to-end against MariaDB 11 via the local dev setup: migration runs clean on boot; XSS payloads neutralized; drafts hidden from the public API; rich content (headings/lists/link/inline image) round-trips sanitized; backlinks + red-link detection; tag filtering + orphan pruning; FULLTEXT search matches on body and title; revision diff + restore. Client builds clean; server boots with no errors. Each phase's exit-check is recorded in `WIKI_UPGRADE.md`. ### Notes - Every admin write logs to `activity_log` (`wiki.create/update/publish/delete`, `wiki.category.*`, `wiki.revision.restore`, `upload`). - No new roles and no public-contribution workflow (out of scope by design). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
whitlocktech added 5 commits 2026-06-27 21:08:36 +00:00
Detailed design + process for upgrading the CMS wiki: rich-text (TipTap)
editing, server+client sanitization, categories, drafts/publish, tags,
internal links/backlinks, inline images, FULLTEXT search, and revision
history. Staff-only (admin/editor). Additive, idempotent schema migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Foundation & safety phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive, idempotent via ensureSchema):
- new wiki_categories table; wiki_pages gains category_id, excerpt,
  published, published_at, sort_order, and a FULLTEXT index
- migration ALTERs guarded with IF NOT EXISTS for existing databases
- seed reworked into 4 sections with the 8 starter pages assigned

Security:
- new utils/sanitizeHtml.js (sanitize-html allowlist); wiki bodies are
  sanitized on every save, and the article renders through DOMPurify
- strips <script>, event handlers (onerror), and javascript: URLs

Backend:
- public: published-only list with ?category filter + /wiki/categories
- admin: extended page CRUD, PATCH publish toggle, category CRUD;
  drafts visible to admin, hidden from public
- all writes logged to activity_log

Frontend:
- data-driven public wiki index (sections + real descriptions; removed
  hardcoded blurbs/Roman numerals) with ?category filtering
- article: category breadcrumb + sanitized render
- admin: Section/Status columns, draft/publish + section + excerpt in the
  editor, and a Manage sections modal

Verified end-to-end against MariaDB 11: migration clean, XSS neutralized,
drafts hidden, client builds, server boots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the raw-HTML textarea in the wiki editor with a TipTap (ProseMirror)
WYSIWYG editor.

- new RichTextEditor component: bold/italic/strike, H2/H3, bullet+ordered
  lists, blockquote, code block, divider, link, inline image, undo/redo
- generalized POST /admin/uploads (reuses the screenshot multer config) →
  { url }; the editor uploads inline images through it
- editor output still passes through the Phase 1 server-side sanitizer on
  save and DOMPurify on render
- lazy-loaded as its own chunk so the public bundle doesn't ship TipTap
  (public ~82kB gzip; editor chunk ~106kB gzip loaded only in admin)
- RTE styling added to theme.css (toolbar, active states, prose content)

Verified: uploads serve as images; H2/H3 + lists + link + inline image
round-trip through the WYSIWYG and render sanitized on the public page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Connectivity phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive new tables): wiki_tags, wiki_page_tags, wiki_links.

Internal links & backlinks:
- new wiki.links.js parses a saved body for /wiki/<slug> (and data-wiki-slug)
  targets; wiki_links is rebuilt on every save
- article shows a "Linked from" section (published backlinks) and renders
  links to non-existent pages as red links (server returns missing_links)
- editor gains an internal-link picker listing existing pages

Tags:
- pages accept a tags[] array; tags upsert on save, page tag-set is replaced,
  and orphaned tags are auto-pruned (on save and delete)
- public/admin list filter by ?tag=; /wiki/tags lists tags with published counts
- article shows tag chips; the index has a flat tag-filtered view; editor has a
  comma-separated tags field

Verified end-to-end: A->B backlink appears, red link detected, link index
rebuilds on edit, tag filtering + chips + pruning all work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive): wiki_revisions table (per-save content snapshots).
The FULLTEXT index on wiki_pages(title, body) shipped in Phase 1.

Search:
- MATCH ... AGAINST natural-language search over title + body, ordered by
  relevance
- public: GET /public/wiki?q= (published only); admin: GET /admin/wiki?q=
  (all statuses)
- public wiki index gains a search box; admin list gains a search field

Revision history:
- every create/update snapshots the page into wiki_revisions
- admin endpoints: list revisions, get one, and restore (restore overwrites
  the page, rebuilds links, and appends a new revision — history stays
  append-only); logged as wiki.revision.restore
- editor gains a History modal: revision list + word-level diff (jsdiff) of a
  chosen revision against the current page, with one-click restore

Verified end-to-end: search matches body and title; two edits produce three
revisions; diff renders added/removed words; restore reverts and records a new
revision. No console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
whitlocktech merged commit 75b13159d7 into main 2026-06-27 21:22:48 +00:00
Sign in to join this conversation.
No description provided.