Skip to content

feat: add a gif node to markup, with serialization and viewer support - #25

Open
shanzez wants to merge 1 commit into
Platform-Collective:developfrom
exavizco:feat/gif-markup-node
Open

feat: add a gif node to markup, with serialization and viewer support#25
shanzez wants to merge 1 commit into
Platform-Collective:developfrom
exavizco:feat/gif-markup-node

Conversation

@shanzez

@shanzez shanzez commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Markup has no way to represent an animated GIF. Images exist, but the image node is disabled in both message composers via kitOptions (AttachmentRefInput.svelte and communication's TextInput.svelte), so a GIF sent in chat has nowhere to live.

This adds a first-class gif node rather than re-enabling images, which leaves the composers' existing image policy untouched.

It is deliberately source-agnostic: the node accepts either a blob reference or an external src, so a workspace-hosted GIF and a third-party one both work. No provider is included here.

What changed

  • MarkupNodeType.gif in text-core, plus a GifNode tiptap extension registered in the server kit as well as the client, so stored markup round-trips through the server.
  • Serialization both ways for markdown and HTML. The HTML serializer reads src or a blob reference — an earlier draft read only attrs.src and silently emitted src="undefined" for blob-backed GIFs.
  • insertGif on TextEditorHandler, implemented in every editor that exposes one. There are five call sites, not four: TextEditor.svelte holds the low-level export the others delegate to.
  • Enabled in both composers, opt-in per consumer. There are two ref-action arrays (defaultRefActions and communication's defaultMessageInputActions) fanning out to ~10 surfaces, so a blanket swap would have changed editors that should not gain the action.
  • Rendering in both markup viewers. NodeContent.svelte had a branch for image and none for gif, so a GIF rendered as the literal text unknown node: "gif". LiteNodeContent.svelte rendered nothing at all.

Two things worth a reviewer's attention

gif is added to nonEmptyNodes. Without it, a message containing only a GIF counts as empty, so canSubmit stays false and the send button never enables — the feature looks broken in exactly the case people use it for. emoji is already on that list for the same reason. This is easy to miss because every serialization test passes while the feature is unusable.

markupViewerCoverage.test.ts is a structural test. It reads the viewer sources and asserts that each composable node type has a branch, because the missing-branch bug above passed the entire serialization suite. Reading files needs node types, hence the types addition to packages/presentation's tsconfig. If you would rather not have a source-reading test, say so and I will drop it — but something needs to catch that class of bug.

Verification

Built and exercised on a self-hosted instance running this node in production chat, including the GIF-only message case. rush validate and rush svelte-check pass on the branch this was developed on. This PR is a clean cherry-pick onto develop; CI here is the first run against this base.

Markup had no way to represent an animated GIF. Images exist, but the image
node is disabled in both message composers via kitOptions, so a GIF sent in
chat had nowhere to live.

This adds a first-class `gif` node rather than re-enabling images, which
keeps the composers' existing image policy untouched.

What changed:

- `MarkupNodeType.gif` plus a GifNode tiptap extension, registered in the
  server kit as well as the client so stored markup round-trips.
- Markdown and HTML serialization both ways. The HTML case reads `src` OR a
  blob reference; an earlier draft read only `src` and silently produced
  `src="undefined"` for blob-backed GIFs.
- `insertGif` on the editor handler, implemented across every editor that
  exposes one so no composer is silently missing it.
- The node enabled in both message composers.
- Rendering in BOTH markup viewers. `NodeContent.svelte` had a branch for
  image and none for gif, so a GIF rendered as the literal text
  `unknown node: "gif"`; `LiteNodeContent.svelte` rendered nothing at all.

Two things worth calling out for reviewers:

`gif` is added to `nonEmptyNodes` in text-core. Without it a message
containing only a GIF counts as empty, so `canSubmit` stays false and the
send button never enables. `emoji` is already on that list for the same
reason. This is easy to miss because serialization tests all pass while the
feature is unusable.

`markupViewerCoverage.test.ts` is a structural test: it reads the viewer
sources and asserts each composable node type has a branch. It exists
because the missing-branch bug above passed every serialization test in the
suite. Reading files needs node types, hence the tsconfig `types` addition
in `packages/presentation`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant