Skip to content

feat: show the character a projected reference names beside its source - #409

Merged
Azganoth merged 2 commits into
mainfrom
feature/projected-reference-character
Sep 5, 2026
Merged

feat: show the character a projected reference names beside its source#409
Azganoth merged 2 commits into
mainfrom
feature/projected-reference-character

Conversation

@Azganoth

@Azganoth Azganoth commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

A projected character reference replaced the character it named, so with the source open the author read © and no longer saw what it renders — the answer the projection was opened to get. A reference naming whitespace, such as   or ​, could not be confirmed from the source at all.

The character is now drawn immediately before the source wherever a reference is projected: the reference's own projection, a marked fragment holding one, and a link label holding one. It reads as the content it renders while the source beside it reads as the marker it is, so a reference in a link label draws its character in the label's styling with © muted beside it, and one in a bold fragment draws its character bold.

It is a Decoration.widget rather than document text. Were the character text, a projected run would hold characters the file does not and an edit would have to guess whether the author meant the character or the source; as a decoration it touches neither the projected text nor the source maps, so every offset between the document and the file is unchanged. The character rides in data-leafdown-preview and is drawn through ::before, the way a persistent block marker already is, which is what keeps it out of every reading of the document: it sits in no text node, so no selection covers it, no copy carries it, and nothing serializes it. white-space: pre keeps the width of a character a reference names as whitespace.

The widget stands outside the document's marks, so the run's own styling reaches the character through a class rather than through the marks the source carries. A positive side puts the widget after the caret at the source's opening offset, so the character and the & read as one position: the caret at the start of the source rests before both, and one move right leaves the pair. Entry direction is unchanged, and a caret entering from the right still starts at the end of the source.

The link source map has no character-reference segment, so a reference in a label arrives inside a text segment and is located from that segment's sourceBoundaries, where each pair is the source one document character was read from. A pair whose source decodes as a whole reference is one; an escape spends two characters on the ampersand it keeps literal and a code span spends one per character, so neither decodes and neither draws a character. The reference's run is split out of its segment's span rather than covered by a second one, because ProseMirror merges decorations over the same range into a single element, where the content class would win over the marker.

getProjectionContentClassName emitted a literal false class token for every absent mark, which .filter(isNonNullish) kept. It now filters on truthiness like its counterpart in the link syntax; the defect was invisible until the character's element carried the result into the DOM.

Related Issue

Closes #308

Verification

pnpm check:frontend passes on the complete change.

Focused tests in sourceProjectionCharacterReference.test.ts prove the character appears for every reference form and in all three owners, that it takes the run's content class while the source takes the marker class, that a backslash or a code span in a label draws none, and that the document size, the caret position, a selection over the source, a copy of it, and the saved Markdown are unchanged by its presence. editorPresentation.test.ts guards the stylesheet rule that draws it.

Manually verified in Chrome against the dev server, on Windows 11, with the editor mounted on a document holding each projection owner:

  1. In a link label the character takes the label's colour and underline while © beside it is muted and stays underlined; in a bold fragment the character is bold and the source is not.
  2. The caret at the start of the source rests before the character, one ArrowRight reaches the position after the &, and one ArrowLeft returns.
  3.   draws at its own width, so whitespace the source hid is visible again.

Not verified in this session: the Tauri window, which the frontend was driven at the Vite dev server in place of; it renders the same editor and stylesheet but not the native shell.

Notes

A reference naming a zero-width character draws nothing, because the character it names is nothing. The rule shows the character rather than a stand-in for it.

Typora's deletion behaviour, where removing the character removes the reference's opening &, is left to its own decision as #308 scoped it, and so is a character shown before the caret arrives, which is marker presentation rather than projection. A reference in a link destination or an image description draws none either; only the three owners the issue enumerates project a reference as label or fragment content.

Adjacent references still resolve a caret at their shared boundary to the reference that follows it, so leaving one leftward lands at the start of its neighbour rather than at the end. That belongs to #406 and #312 rather than to anything this change reaches.

@Azganoth Azganoth added the Feature New feature or request label Sep 5, 2026
@Azganoth Azganoth self-assigned this Sep 5, 2026
@Azganoth
Azganoth enabled auto-merge (squash) September 5, 2026 19:26
@Azganoth
Azganoth merged commit cf97d95 into main Sep 5, 2026
3 checks passed
@Azganoth
Azganoth deleted the feature/projected-reference-character branch September 5, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show the character a projected reference names beside its source

1 participant