Skip to content

bug(docs write --tab --markdown): Pandoc-style explicit heading anchor {#slug} leaks as literal text into the heading #703

@sebsnyk

Description

@sebsnyk

Symptom

gog docs write --replace --markdown --tab=<id> does not consume the Pandoc-style explicit heading-anchor syntax {#slug}. The text {#slug} survives verbatim in the rendered heading instead of being stripped + applied as a stable in-doc anchor (Docs API NamedRange or heading anchor that [text](#slug) links can target).

This is a common pattern for markdown that needs cross-references — authors set explicit anchors so internal [Attachments](#attachments) style links remain stable even if heading text changes. gog's markdown converter handles the reference side since #633 (closed: [text](#slug) resolves to existing heading IDs), but doesn't handle the definition side — author-defined anchors via {#slug} leak as visible characters.

Repro

cat > /tmp/anchor.md <<'MD'
# Top heading

See [Attachments](#attachments) below.

## Attachments {#attachments}

Body of the attachments section.
MD

DOC=$(gog docs create "anchor test" --pageless --file /tmp/anchor.md -j | jq -r .id)
gog docs tabs add "$DOC" --title "Tab 2"
TAB=$(gog docs list-tabs "$DOC" -j | jq -r '.tabs[1].id')

gog docs write "$DOC" --replace --markdown --tab="$TAB" --file /tmp/anchor.md

gog docs read "$DOC" --tab="$TAB"
# Observed: heading reads "Attachments {#attachments}" verbatim
# Expected: heading reads "Attachments"; the anchor "attachments" is registered
#           somewhere the [text](#attachments) link can resolve to.

Expected behaviour

{#slug} at the end of an ATX heading line should be stripped from the rendered heading text and registered as an anchor target the existing [text](#slug) resolver (from #633) can find. Two reasonable implementations:

  1. NamedRange on the heading paragraph — gog already emits CreateNamedRange requests in some paths (per feat(docs): named-range create / list / delete / replace-content commands #692 / feat(docs raw): add --tab to traverse a specific tab; default returns tab 0 only #697 context). Wrap the heading paragraph in a NamedRange whose name is the slug; [text](#slug) resolves via NamedRange lookup.
  2. Synthetic heading-ID lookup table — keep an in-process map of {slug → heading paragraph startIndex} built during markdown parsing; the link resolver consults the map before falling back to generated IDs.

Either way the visible heading text should NOT contain {#slug}.

Acceptance criteria

  • ## Heading text {#anchor-name} in the input markdown produces a Docs heading with rendered text Heading text (no braces, no slug visible).
  • A separate [label](#anchor-name) link elsewhere in the same body resolves to that heading on the resulting Doc (clicking the link scrolls to the heading).
  • Behaviour matches on both the --tab=<id> path and the whole-doc Drive-import path.
  • If the slug already exists from an auto-generated heading ID (gog's existing behaviour), the explicit {#slug} wins and the auto-id is ignored.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions