a real editor for the site's copy, and the media param stops following you

Two things.

`?media=` is Media's own folder and now goes when you leave it. It survived
every navigation, so `?section=home&media=GALLERY/CALAVERA+1` was an ordinary
URL — a parameter naming a folder in a section that is not open, carried into
every link and shared or bookmarked that way.

And the intro is a tiptap editor rather than a textarea of raw HTML. HTML in and
HTML out, because that is already what the site's database holds and renders —
introducing a second format would buy a migration and a round-tripping bug class
for nothing.

Declared as the PLUGIN's npmDependencies, not added to the platform's
package.json. That lockfile is frozen and shared, and npm-deps.ts exists for
exactly this: everything lands in plugins/node_modules/, so a plugin's editor
cannot move a version the platform depends on.

The toolbar is only what the site's stylesheet can render. Anything more would
produce copy that looks right while editing and wrong when published, which the
person only discovers after saying it was done.

The content effect compares against getHTML() before writing back, and that
comparison is the whole thing: while somebody types the two already agree, so
nothing is pushed in and the caret stays put. Without it this is the classic
controlled-contentEditable bug where the cursor jumps to the end on every
keystroke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 20:15:55 +01:00
co-authored by Claude Opus 5
parent 3ae8c960e5
commit d57bd3e72f
4 changed files with 250 additions and 8 deletions
+3 -8
View File
@@ -3,9 +3,9 @@ import { ArrowDown, ArrowUp, Check, ImagePlus, Loader2, Trash2 } from 'lucide-re
import { useClient } from 'hooks/useClient';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { readableError } from '../errors';
import { useFlash } from '../useFlash';
import { RichText } from '../RichText';
/**
* The Home page: a hero, an intro, and an ordered gallery.
@@ -174,13 +174,8 @@ export const HomePanel = () => {
<div className="space-y-1">
<p className="text-xs font-medium">Intro</p>
<Textarea
value={intro}
onChange={(event) => setIntro(event.target.value)}
rows={4}
className="text-xs"
placeholder="The paragraph under the hero."
/>
{/* HTML in and HTML out — it is what the site's database already holds and renders. */}
<RichText value={intro} onChange={setIntro} placeholder="The paragraph under the hero." />
</div>
{error && <p className="text-xs text-destructive">{error}</p>}