Inline shortcode chips in the RTE: button and icon with param popovers #8

Closed
opened 2026-07-11 13:00:02 +02:00 by dominik.polakovics · 2 comments

What to build

Inline shortcodes as first-class RTE citizens (docs/DECISIONS.md D5, D12): inline elements (button, icon) declared in the field's allowedInline list appear as atomic, non-editable chips in the text flow. Inserting or clicking a chip opens a popover form generated from the element's sidecar schema. Serialization emits inline shortcode syntax inside the markdown body and parses it back into chips.

Acceptance criteria

  • Toolbar/slash insert offers exactly the field's allowedInline elements
  • Chips render with label/icon from the sidecar, move as atoms (cursor, selection, copy/paste, delete)
  • Clicking a chip opens the schema-generated popover; save updates the chip and the serialized body
  • Round-trip: body with inline shortcodes ⇄ editor state is lossless, including chips adjacent to formatted text
  • Fields without allowedInline show no insert affordance and refuse pasted chips

Blocked by

## What to build Inline shortcodes as first-class RTE citizens (docs/DECISIONS.md D5, D12): inline elements (`button`, `icon`) declared in the field's `allowedInline` list appear as atomic, non-editable chips in the text flow. Inserting or clicking a chip opens a popover form generated from the element's sidecar schema. Serialization emits inline shortcode syntax inside the markdown body and parses it back into chips. ## Acceptance criteria - [ ] Toolbar/slash insert offers exactly the field's `allowedInline` elements - [ ] Chips render with label/icon from the sidecar, move as atoms (cursor, selection, copy/paste, delete) - [ ] Clicking a chip opens the schema-generated popover; save updates the chip and the serialized body - [ ] Round-trip: body with inline shortcodes ⇄ editor state is lossless, including chips adjacent to formatted text - [ ] Fields without `allowedInline` show no insert affordance and refuse pasted chips ## Blocked by - #7
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Inline shortcodes as first-class rich-text citizens: inline elements (button, icon) declared in a field's allowedInline list render as atomic, non-editable chips in the RTE text flow, with a sidecar-schema-generated popover form for their params, and serialize losslessly to inline shortcode syntax inside the markdown body.

Dependency note: this issue is hard-blocked by #7 (the TipTap Solid wrapper and markdown round-trip); the scheduler handles ordering — do not start until #7 is merged. #7 provides the editor, the rte body policy in the sidecar, the allowlist enforcement, and the markdown ⇄ editor conversion layer; #7 explicitly left inline shortcodes as inert non-corrupted placeholders — this issue upgrades them to chips. #3 (transitively) provides the schema-driven form generator the popover reuses. The issue body's docs/DECISIONS.md D5, D12 references point at a file that is not committed — this brief is self-contained.

Current behavior:
After #7, element bodies are edited in a rich-text editor with an allowlist, but inline shortcodes appearing in a body are only guaranteed to survive: they show as inert placeholders, cannot be inserted, moved deliberately, or configured, and their params are invisible to the author. There is no notion of which inline elements a given rich-text field permits. The round-trip engine from #2 already represents inline shortcodes as self-closing shortcode nodes (name, markup flag, named typed params) among the text nodes of a body, and its serializer emits the canonical inline syntax ({{< name k=v />}} / {{% ... /%}}) — and rejects raw {{</{{% inside text nodes, so shortcodes can never be flattened into text.

Desired behavior:

Field configuration. The rte body policy from #7 gains an allowedInline list naming the inline element types the field permits (e.g. button, icon). Each named type must have a sidecar schema (from #3); naming an element without a sidecar is a schema-load error surfaced like #3's other sidecar errors. An absent or empty allowedInline means the field permits no inline chips.

Insertion. The editor's insert affordance (toolbar and/or slash command) offers exactly the field's allowedInline elements, each shown with its sidecar label and icon. Inserting one places a chip at the cursor with the element's schema defaults and opens its popover so required params can be filled. Fields without allowedInline show no insert affordance at all.

Chips. A chip renders inline in the text flow with the sidecar label and icon, visibly distinct from text. Its text is not editable in place. It behaves as one atom: arrow keys skip over it as a unit, selection includes or excludes it whole, backspace/delete removes it whole, and copy/paste within the editor carries the chip with all its params. Pasting content containing a chip into a field whose allowedInline does not permit that element drops the chip while pasting the surrounding text normally — a disallowed chip can never enter a field, by any route.

Popover. Clicking a chip opens a popover form generated purely from the element's sidecar schema — the same schema-driven form generation as #3 (labels, defaults, required, field types), no per-element form code. Validation matches #3's rules: required-missing or type-invalid values block the popover's save and show inline on the field. Saving the popover updates the chip's params and flows into the body's serialized form; cancelling leaves the chip unchanged.

Serialization. The markdown ⇄ editor conversion from #7 maps inline shortcode nodes to chip nodes and back. The round-trip is lossless: a body mixing text, formatting marks, and inline shortcodes — including chips directly adjacent to or inside bold/italic/link/list/heading constructs, chips back-to-back, and chips at the very start or end of the body — parses into the same chips and serializes back to the identical canonical form. Param types (string/bool/int/float) and each shortcode's markup flag ({{< vs {{%) survive the round-trip. Chips are always serialized as shortcode nodes in the element tree, never as literal text.

Key interfaces:

  • The rte body-policy shape from #7, extended with allowedInline (list of inline element names); validated at schema load with actionable errors.
  • A TipTap atomic inline node type whose attributes carry the element name, markup flag, and typed params; generic across inline element types — a new inline element added to the sample site and a field's allowedInline works with zero editor code changes.
  • The #7 conversion layer, extended to map a body's node sequence (text runs + inline shortcode nodes) to the editor document and back losslessly.
  • The schema-driven form renderer from #3, reused inside a popover surface.

Acceptance criteria:

  • The toolbar/slash insert offers exactly the field's allowedInline elements with sidecar label and icon; a field without allowedInline shows no insert affordance
  • Inserting a chip places it at the cursor with schema defaults and opens the popover
  • Chips render with sidecar label/icon and move as atoms: cursor traversal, whole-chip selection, whole-chip delete, and copy/paste within the editor preserving params
  • Clicking a chip opens the schema-generated popover; saving it updates the chip and the serialized body via the canonical save path; invalid values block the popover save with inline errors; cancel changes nothing
  • Round-trip is lossless: bodies mixing chips with formatted text (adjacent, nested in marks, back-to-back, at body boundaries) parse to chips and serialize back to identical canonical inline shortcode syntax, preserving param types and the markup flag
  • Pasting content containing a disallowed chip drops the chip and keeps the surrounding text; naming a sidecar-less element in allowedInline yields an actionable schema-load error
  • The sample site defines button and icon inline elements (shortcode template + sidecar each) and a page whose rich-text body uses them next to formatted text; the behaviors above are covered by tests

Out of scope:

  • Block-level shortcodes as chips or any nested-body editing inside a chip — chips are param-only atoms here
  • Structural element operations outside the body (#6) and the page tree UI (#4)
  • The full v1 element library (#14) and theme styling of the rendered elements (#15) — only button and icon, with minimal templates
  • Media/image pickers for chip params (#12) — params use the #3 field types as-is
  • Extending the formatting allowlist vocabulary or paste sanitization rules (#7 owns those)
  • Drag-and-drop of chips between fields or pages; cross-browser clipboard interchange beyond copy/paste within the editor
  • Git commits on save (#10) and locks/identity/CSRF (#13)
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Inline shortcodes as first-class rich-text citizens: inline elements (`button`, `icon`) declared in a field's `allowedInline` list render as atomic, non-editable chips in the RTE text flow, with a sidecar-schema-generated popover form for their params, and serialize losslessly to inline shortcode syntax inside the markdown body. **Dependency note:** this issue is **hard-blocked by #7** (the TipTap Solid wrapper and markdown round-trip); the scheduler handles ordering — do not start until #7 is merged. #7 provides the editor, the rte body policy in the sidecar, the allowlist enforcement, and the markdown ⇄ editor conversion layer; #7 explicitly left inline shortcodes as inert non-corrupted placeholders — this issue upgrades them to chips. #3 (transitively) provides the schema-driven form generator the popover reuses. The issue body's `docs/DECISIONS.md D5, D12` references point at a file that is not committed — this brief is self-contained. **Current behavior:** After #7, element bodies are edited in a rich-text editor with an allowlist, but inline shortcodes appearing in a body are only guaranteed to survive: they show as inert placeholders, cannot be inserted, moved deliberately, or configured, and their params are invisible to the author. There is no notion of which inline elements a given rich-text field permits. The round-trip engine from #2 already represents inline shortcodes as self-closing shortcode nodes (name, markup flag, named typed params) among the text nodes of a body, and its serializer emits the canonical inline syntax (`{{< name k=v />}}` / `{{% ... /%}}`) — and rejects raw `{{<`/`{{%` inside text nodes, so shortcodes can never be flattened into text. **Desired behavior:** *Field configuration.* The rte body policy from #7 gains an `allowedInline` list naming the inline element types the field permits (e.g. `button`, `icon`). Each named type must have a sidecar schema (from #3); naming an element without a sidecar is a schema-load error surfaced like #3's other sidecar errors. An absent or empty `allowedInline` means the field permits no inline chips. *Insertion.* The editor's insert affordance (toolbar and/or slash command) offers exactly the field's `allowedInline` elements, each shown with its sidecar `label` and `icon`. Inserting one places a chip at the cursor with the element's schema defaults and opens its popover so required params can be filled. Fields without `allowedInline` show no insert affordance at all. *Chips.* A chip renders inline in the text flow with the sidecar label and icon, visibly distinct from text. Its text is not editable in place. It behaves as one atom: arrow keys skip over it as a unit, selection includes or excludes it whole, backspace/delete removes it whole, and copy/paste within the editor carries the chip with all its params. Pasting content containing a chip into a field whose `allowedInline` does not permit that element drops the chip while pasting the surrounding text normally — a disallowed chip can never enter a field, by any route. *Popover.* Clicking a chip opens a popover form generated purely from the element's sidecar schema — the same schema-driven form generation as #3 (labels, defaults, required, field types), no per-element form code. Validation matches #3's rules: required-missing or type-invalid values block the popover's save and show inline on the field. Saving the popover updates the chip's params and flows into the body's serialized form; cancelling leaves the chip unchanged. *Serialization.* The markdown ⇄ editor conversion from #7 maps inline shortcode nodes to chip nodes and back. The round-trip is lossless: a body mixing text, formatting marks, and inline shortcodes — including chips directly adjacent to or inside bold/italic/link/list/heading constructs, chips back-to-back, and chips at the very start or end of the body — parses into the same chips and serializes back to the identical canonical form. Param types (string/bool/int/float) and each shortcode's markup flag (`{{<` vs `{{%`) survive the round-trip. Chips are always serialized as shortcode nodes in the element tree, never as literal text. **Key interfaces:** - The rte body-policy shape from #7, extended with `allowedInline` (list of inline element names); validated at schema load with actionable errors. - A TipTap atomic inline node type whose attributes carry the element name, markup flag, and typed params; generic across inline element types — a new inline element added to the sample site and a field's `allowedInline` works with zero editor code changes. - The #7 conversion layer, extended to map a body's node sequence (text runs + inline shortcode nodes) to the editor document and back losslessly. - The schema-driven form renderer from #3, reused inside a popover surface. **Acceptance criteria:** - [ ] The toolbar/slash insert offers exactly the field's `allowedInline` elements with sidecar label and icon; a field without `allowedInline` shows no insert affordance - [ ] Inserting a chip places it at the cursor with schema defaults and opens the popover - [ ] Chips render with sidecar label/icon and move as atoms: cursor traversal, whole-chip selection, whole-chip delete, and copy/paste within the editor preserving params - [ ] Clicking a chip opens the schema-generated popover; saving it updates the chip and the serialized body via the canonical save path; invalid values block the popover save with inline errors; cancel changes nothing - [ ] Round-trip is lossless: bodies mixing chips with formatted text (adjacent, nested in marks, back-to-back, at body boundaries) parse to chips and serialize back to identical canonical inline shortcode syntax, preserving param types and the markup flag - [ ] Pasting content containing a disallowed chip drops the chip and keeps the surrounding text; naming a sidecar-less element in `allowedInline` yields an actionable schema-load error - [ ] The sample site defines `button` and `icon` inline elements (shortcode template + sidecar each) and a page whose rich-text body uses them next to formatted text; the behaviors above are covered by tests **Out of scope:** - Block-level shortcodes as chips or any nested-body editing inside a chip — chips are param-only atoms here - Structural element operations outside the body (#6) and the page tree UI (#4) - The full v1 element library (#14) and theme styling of the rendered elements (#15) — only `button` and `icon`, with minimal templates - Media/image pickers for chip params (#12) — params use the #3 field types as-is - Extending the formatting allowlist vocabulary or paste sanitization rules (#7 owns those) - Drag-and-drop of chips between fields or pages; cross-browser clipboard interchange beyond copy/paste within the editor - Git commits on save (#10) and locks/identity/CSRF (#13)
Author
Owner

Closing as superseded: the maintainer has decided on a clean-slate reset of the project (issue #29, PR #30). The v1 implementation this issue builds on is being removed and the plan will be re-drawn from scratch; this spec is retired rather than actioned.

Closing as superseded: the maintainer has decided on a clean-slate reset of the project (issue #29, PR #30). The v1 implementation this issue builds on is being removed and the plan will be re-drawn from scratch; this spec is retired rather than actioned.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle#8
No description provided.