fix(chat): render footnote citations under a single Sources header - #987
Merged
Conversation
The footnote citations from #981 rendered with two headings: the model wrote a literal "Sources" line above its footnote definitions, and remark-gfm then moved the definitions into its own trailing section headed "Footnotes". That default heading is meant to be visually hidden behind `sr-only`, a class our Tailwind build never generates because it appears in no source file, so both headings showed. The superscript links also gave no feedback: fragment navigation resolves against ids that repeat in every message (`#user-content-fn-1` in each one), does nothing on a repeated click of the same fragment, and scrolls nothing when the target is already on screen. - Markdown.tsx accepts a footnote label and id prefix and passes them through remark-rehype: the section heading is now ours (localised, visible) and every message mints unique footnote ids. - ChatHistoryMessage passes "Sources" (per locale, "Bronnen" in Dutch) and a per-message prefix, and intercepts in-message footnote hops to scroll directly, leaving the URL and history alone. - enrichAgenticContent strips a model-written "Sources"/"Footnotes" heading when footnote definitions follow, which repairs messages already persisted under the old prompt ("*Sources*" verbatim in the production row this was reported on). - agent.py stops asking the model for a header above the definitions; the renderer owns the heading now.
The regex that removed a model-written "Sources"/"Footnotes" heading from already-persisted messages was a shim: it compensated in render code, forever, for a few rows written in the hours between #981 and this fix, and it only knew the English words. The handful of affected rows get a one-off data cleanup instead; the renderer keeps owning the header for everything written from now on.
…eading app-wide Three refinements on the footnote citations: - A superscript click now scrolls to the source line and flashes it with the same cyan highlight the transcript page gives a deep-linked chunk, so the hop gives feedback even when the target was already on screen. - The ↩ back-reference links under each source are dropped in agentic chat: they stacked a second, third, Nth arrow icon on every source line, and after an in-place highlight scroll the superscript that brought the reader down is still visible anyway. One icon per source. - Markdown surfaces that do not name a footnote label now hide the renderer's default "Footnotes" heading behind `dembrane-sr-only`, our own visually-hidden class in index.css. Tailwind owns `sr-only` and only generates classes it sees in source, so runtime-injected markup needs a class we define unconditionally.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
Follow-up to #981. On the reported chat (
57493241, dashboard) the agentic reply showed two headings ("Sources" and "Footnotes"), the superscript citation links gave no feedback when clicked, and every source line stacked several arrow icons (the external-link arrow plus one ↩ back-reference per citation).Why it happened
sr-onlyclass, which our Tailwind build never generates (no source file uses it), so the heading that is meant to be invisible is visible.#user-content-fn-1), a repeated click on the same fragment does nothing, and when the footnote list is already on screen (the exact case in the reported chat, verified against the stored row in prod) nothing visibly happens at all.What changed
Markdown.tsxtakes an optional footnote label and id prefix and passes them through remark-rehype: the section heading is ours now (visible, localised) and footnote ids are unique per message. Surfaces that pass no label keep the default "Footnotes" heading but hidden behinddembrane-sr-only, a visually-hidden class we define ourselves inindex.css(our own name so it cannot collide with Tailwind'ssr-onlyutility).ChatHistoryMessage.tsxpassesSources(per locale,Bronnenin Dutch, other catalogs fall back to English until translated) plus a per-message id prefix. A superscript click scrolls to the source line and flashes it with the same cyan highlight the transcript page uses for a deep-linked chunk — feedback even when the target was already on screen, no URL rewrite, no history entries. The ↩ back-references are dropped in agentic chat: one icon per source line.agent.pyno longer asks the model for a header above the definitions; the renderer owns the heading. Prompt test updated to pin this.Already-persisted rows
Exactly one row in all of production stores the old model-written header (verified by query): the beta test chat this was reported on. Agentic chat is beta; the row stays as is. No migration, no render-time compatibility code.
Tests
AgenticChatPanel.test.tsx: 15 passed, including a new case pinning that footnote definition tags become rich transcript links.agent/tests/test_agent_tools.py: 65 passed.tsc --noEmitandbiome lintclean; end-to-end renders verified: one "Sources" heading in chat, hidden "Footnotes" heading on label-less surfaces, unique prefixed ids.Discussion: https://dembraneworkspace.slack.com/archives/C0884QPQF6W/p1786381186184369