Skip to content

Feature/comments on words - #165

Closed
erklu wants to merge 20 commits into
mainfrom
feature/comments-on-words
Closed

erklu wants to merge 20 commits into
mainfrom
feature/comments-on-words

Conversation

@erklu

@erklu erklu commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Comments on words in the transcript editor

Refs #131 (the comment half; the highlight half is in its own PR, and this one builds on it)

Depends on the highlights PR. The branch is made on top of feature/highlight-words, so until that is merged the diff here also shows it. Open this PR against that branch, or wait and retarget it at main once the highlights are in. The files and rules it shares with highlights are named below.

Select words and press Ctrl + Shift + K, or use the Comment button in the toolbar, to write a note on them. The note is for the reader: it never changes the text. Words that have a note are underlined with a dotted magenta line. The note is written, read and changed in a card in the right column under the video, built like the Validate card, and the card follows the caret: put the caret in (or click) commented words and it shows that comment.

Transcriptions only, for now, for the same reason as highlights: a set of subtitles is saved as plain SRT, which has nowhere to keep a note. In the subtitle editor the key is not bound, there is no card, the shortcuts dialog does not list it and the export option is not offered.

The history has two steps on purpose: the first commits had the note in a small box beside the words; testing showed it was hard to find and read, so the second replaces the box with the card. The end result is what is described here.

Behaviour

  • Whole words, one note per word, one paragraph per note. A selection that takes any part of a word takes all of it. A word has at most one comment: asking to comment on words that already have one opens that comment instead of laying a second over it, so nothing else (drawing, navigation, export) has to deal with overlap. A comment belongs to one paragraph: a selection reaching across paragraphs is refused with a notice ("A comment has to stay within one paragraph"), so a stretch of words and the note about it cannot be pulled apart by a split or a merge nobody asked for.
  • The card. One comment at a time, shut until it is needed (it opens when a comment is written or opened, when commented words are clicked, and when a file with comments is opened, showing the first one without moving the text). Header with Previous, "Comment 2 of 5" and Next, and Close; then the words it is on (a button that takes you to them), where they are (the speaker and the start time, as shown beside the text; a transcription does not show a paragraph number), a text area, and Remove (existing comments), Cancel and Save (disabled while empty). Ctrl/⌘ + Enter saves, Esc abandons. Previous/Next are disabled while a new comment is being written.
  • The text shows which comment the card is on, so Previous/Next can be followed: the paragraph gets a magenta outline with a thicker left rule (as the Validate card marks its caption, in the comment colour), and the words of that comment get a light magenta tint on top of whatever else they are (a highlight still shows through it). Previous/Next also scroll the paragraph to the middle, without moving the caret or taking focus. A comment being written marks its paragraph only. It goes when the card is closed or the comment is gone.
  • The caret drives the card, the same idea as the Validate cards. The card shows the comment the caret is in. An open card follows the caret; a shut card opens only for a click, so an arrow key that crosses commented words does not throw a card up under someone who is typing. A selection that is not collapsed (someone selecting words to copy them) does not open it.
  • What was typed is kept when the reader moves on: to another comment, back into the text, with Previous/Next, or by closing the card, if it is not empty and not what was there (nobody loses a sentence to a stray click). Escape and Cancel are how to abandon it. After Save, Cancel or Remove the selection goes back on the words.
  • Keys typed in the text area stay in it. Otherwise Ctrl+Z would undo the transcript while a sentence is being written, and Ctrl+S would save. (The page's own key handler is global and does not look at where a key came from.)
  • They follow the words, by the same rules as highlights (carry_highlights, reused so the two cannot drift apart): through typing, split, merge, deleting across paragraphs, Find & Replace. A word corrected inside a commented stretch stays in it; a word typed between two commented words joins it; any other new word does not. When all of a comment's words are gone, the comment goes with them (the card hides if it was showing it), and Undo brings both back. Differences from highlights: a split across a comment keeps it on both halves (each with the same text, as separate notes); a merge keeps neighbouring comments as separate notes; the one-word moves (Ctrl/⌘ + ↑/↓) take the word out of the comment, which stays behind with the rest of its words.
  • Saved with the transcription as "comments": [{"words": [...], "text": "..."}] per segment, only when there are any. Read back on open; anything unusable in the file is skipped (junk shapes, empty text, non-integer, boolean, negative or out-of-range positions, a word already taken by an earlier comment), and a text over 1000 characters is cut. Files saved before this open exactly as before. The text is limited to 1000 characters (the text area and the server).
  • RTF export has one new checkbox under RTF Options, Include comments, unchecked by default and hidden when there is no comment in what is exported (bulk exports included). Each comment is written as a Word comment on its words (author "Scribe": the editor does not know the reader's name). A clean export is byte-for-byte what it was. It works together with Include highlights and Include my edits: the pieces are cut where a comment begins or ends so a marker never lands inside one, and the text between the markers is the caption's own text throughout. Highlights show in the export only when Include highlights is ticked, which is a separate option. No other format carries comments.

Appearance

This does change how things look, so for review:

  • Commented words get a dotted underline, 3 px thick and 4 px below the text, in a deep magenta (#a1136a; dark mode #ff9fd2). It is a pattern and not colour alone (1.4.1), and different from the solid bars under flagged and changed words. It is thick and set off from the text because on words that are both highlighted and commented a thinner line close to the text was lost against the highlight's own frame lines (rendered in Chromium, both themes, one line and wrapped, before choosing). Magenta rather than blue, because blue is already the progress/playing tint. It measures 7.5:1 on white and at least 6.3:1 on every wash a word can have (highlight, flagged, changed, playing); in dark mode 9.4:1 on the surface and at least 4.6:1 on the others (all above 3:1, 1.4.11). It is drawn with text-decoration, so it takes no room (the text does not move), touches no box-shadow (the other markings keep theirs and show together with it), and nothing is added after the last word (a marker there would sit where the caret goes, and ::after is already used by the review/edit words).
  • The "you are here" marking: the paragraph the card is on has a 2 px outline and a 3 px left rule in the comment colour, and the words of the comment a tint of the same colour at 18 %, laid as a background-image over their own background. Text keeps at least 6.7:1 on it in light mode and 5.9:1 in dark, over every wash a word can have, and the comment line at least 4.3:1 and 3.4:1; no layout change (the rule swaps padding for border like the Validate marking does).
  • The card reuses the Validate card's classes and look (stripe down the left, tinted header) with the comment colour as its tone, so it sits in the same place and has the same shape. It is hidden until needed.
  • A new "Comment" button in the toolbar (the add_comment icon from Material Symbols Outlined, which ships with NiceGUI), quiet like the others, beside Highlight, so transcriptions only. Tooltip naming the key, aria-keyshortcuts, and a mouse press on it does not take focus (so the selection it acts on is not lost). One new tab stop in the toolbar; the tab order inside the text is unchanged.
  • A new "Comments" group in the transcription's shortcuts dialog (Ctrl + Shift + K, Ctrl/⌘ + Enter, Esc); one new checkbox in the RTF options when there is something to carry.
  • Commented words are exposed to assistive technology through aria-description="Comment: …" on the first word of the comment (not a role: mark is already taken by highlights, and comment is not in ARIA 1.2). The card's heading is focusable and a visually hidden live label says which comment is showing.

Implementation

  • utils/caption.py: WordComment(words, text), SRTCaption.comments (copied deeply for undo, saved).
  • utils/srt_comments.py (new): CommentsMixin with carry_comments, split_comments, join_comments, drop_word_from_comments, shift_comments, comment_target, set_comment, remove_comment, restore_comments, word_range.
  • utils/comments_panel.py (new): CommentsPanel, built like ValidationPanel: open_for, open_first, follow, commit, save, remove, cancel, close, go_next/go_previous, go_to_words, sync.
  • utils/srt_review.py: carry_marks carries comments too (so typing, split with unsent text and Replace all follow); review_runs gives words cm/cf/ct/cl. utils/srt.py: restore in parse_txt, and the sites that carry marks (split, merges, delete_range, one-word moves).
  • utils/srt_export.py: export_rtf(..., comments=False), rtf_comments_available(), rtf_date(), comment_ranges(), the checkbox.
  • utils/transcript_editor.js: the key (Ctrl only, by event.code, ahead of the block-scoped lists), commentSelection, reportCommentCaret (once per comment, from the caret update and from click), runClass, run attributes, isCommentOnShow (the data-comment-current attribute).
  • utils/transcript_editor.py: open_comment, caret_comment, comment_selection, comments_panel, mark_comment and the props commentId/commentIndex; refresh() ends by syncing the card. pages/srt.py: the button and the card in the right column; utils/srt_render.py: dialog group; utils/styles.py: the underline and the card's tone in both themes; AGENTS.md: a "Comments" section.

Notes

  • A comment has no author or date of its own; the export writes "Scribe" and the moment of the export. Adding them is a small change if wanted (and the date belongs to the model, not the export).
  • Comments do not follow a word moved to the neighbouring paragraph (the one-word moves), by design; see above.
  • Existing test changed: the transcription's shortcut list in tests/test_transcript_editor.py gets the new group.
  • Not in this PR: comments (or highlights, or My edits) kept for subtitles.
  • Not in this PR: a comment across more than one paragraph. It stays within one on purpose for this first version (a selection across paragraphs is refused with a notice). It can be added later by letting a comment be several parts, one per paragraph, that share a group id and one text, the way highlights already work per paragraph.

How it was checked, and what was not

Logic, saving, RTF text and the component source are covered by tests. The card's behaviour (write, Save, Remove, Esc, Ctrl+Enter, ⌘+Enter, Close, Previous/Next, following the caret, sync after edits and undo) was driven with NiceGUI's own User simulation. The component's caret reporting and the key (including that Alt, ⌘, plain Ctrl and subtitles leave it alone) were driven against the real component in jsdom. The underline and the "you are here" marking were rendered in Chromium on every wash and in both themes; the marking's props and attributes were driven in jsdom against the real component. The RTF was converted with LibreOffice and opened in Word (two comments by "Scribe" with the right ranges).

Not yet verified: the card in a real browser end to end (focus moving in and out of the text area, the caret following with a real selection), whether Ctrl + Shift + K is free in the reader's browser, the tab order (the card comes after the video in the page; no skip link was added), and whether a screen reader speaks aria-description and the live label sensibly.

Tests

tests/test_comments.py (the model, saving and reopening including junk, targeting, adding/changing/removing, following every kind of edit, drawing, RTF and when it is offered, the card, the handlers and bad input, the button, the component source, the styles including the contrast of the line on every wash). Full suite: 1338 passed, 3 failed: the 3 pre-existing failures on main (TestAddingACaptionForSpeech).

erklu and others added 6 commits October 18, 2026 07:44
#131)

Select text and press Ctrl+Shift+H, or use the Highlight button in the
toolbar, to highlight it; do it again to take the highlight off. A note
to self made while reviewing against the recording -- it never changes
the text.

- Anchored to whole words (SRTCaption.highlighted_words), kept apart from
  the "my edits" marks. carry_highlights() moves them with the words
  through typing, split, merge, delete across captions, word moves and
  Find & Replace; a word typed between two highlighted words joins the
  highlight, any other new word does not earn one.
- Saved with the transcription ("highlighted" next to "edited").
  Transcriptions only for now: a set of subtitles is plain SRT and has
  nowhere to keep them.
- Drawn yellow with a line above and below so colour is not the only cue
  (1.4.1, 1.4.11), role="mark", distinguishable from flagged words, my
  edits and the word being played, in both themes.
- RTF export can carry highlights and changed words; the options are
  unchecked by default and only shown when there is something to carry
  (a clean export is unchanged).
- Shortcuts dialog gets a "Highlighting" group for transcriptions.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
The plain Material Icons "highlight" is a torch. The marker pen is in
Material Symbols Outlined, which ships with NiceGUI (checked: the
ligature is in the bundled font).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
…mments in RTF (#131)

A comment is a note on a stretch of words with a text of its own. It never
changes the text, follows its words through every edit by the same rules as
a highlight, and is saved with the transcription as "comments" per segment.

- One comment per word and one caption per comment; a selection across
  paragraphs is refused, and asking to comment on commented words opens the
  existing comment.
- Ctrl+Shift+K or the toolbar's Comment button opens a small box beside the
  words; a click on commented words shows it without taking focus. Save,
  Cancel, Remove, Ctrl/Cmd+Enter, Escape; what is typed is kept when the
  reader moves on. Keys typed in the box stay in it.
- Drawn with a dotted underline that takes no room, in both themes.
- RTF export can carry them as Word comments (off by default, hidden when
  there are none).
- Transcriptions only, like highlights.

Builds on the highlights branch.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
…ords (#131)

The popover is gone. A comment is now written, read and changed in a card
in the right column, built like the validation panel: one comment at a time,
shut until needed, with Previous/Next and Close, and in the colour of the
underline on the commented words (magenta, because blue is the progress tint).

The card follows the caret: put the caret in commented words, or click them,
and it shows that comment. An open card follows; a shut one opens only for a
click. What is typed is kept when the reader moves on, and abandoned only by
Escape or Cancel.

The underline is now a dotted 3px line 4px below the text, because a thinner
one closer in was lost against the highlight's frame lines on words that are
both highlighted and commented.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
…131)

A transcription does not show a caption's number, so "Paragraph N" on the
card was something nobody could use. The card now says who is speaking and
when the caption starts, as the editor shows them beside the text.

And the text shows which comment the card is on, so Previous and Next can be
followed: the caption gets a rule and an outline in the comment colour, as
the validation panel marks its caption, and the words of the comment get a
tint laid over whatever else they are, so a highlight still shows through.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
@erklu
erklu requested a review from krihal October 18, 2026 12:37
krihal and others added 14 commits October 19, 2026 08:09
…G 1.3.1, 4.1.2)

Two things the card showed wrong, found testing comments in the test
environment:

- A comment's card says who is speaking and when. A renamed speaker did not
  change it, because sync() left a card that was "still right" untouched. It
  is now drawn again (without scrolling or touching the text box, so what is
  being typed is kept).
- Deleting the words a comment hangs on, while its card was open, left the
  card there until the caret moved. set_text() does not render the block being
  typed into, so no refresh() and no sync() followed. It now calls sync(), and
  find_again() finds a comment whose words moved with an edit (a word typed
  into the middle of them, a word deleted before them) by its text and the
  words it still shares, since an edit builds new WordComment objects.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
Moving the first word to the previous block or the last word to the next one
left a comment behind and dropped it altogether when it was on that word
alone. A comment is on a word, so it now goes where the word goes: one on the
moved word alone moves with it, and one on that word and others is on both
blocks (a copy with the same text), as when a block is split. The two helpers
that took the word out of a comment are no longer used and are removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
Both are flat Quasar buttons, which take the primary blue (#5898d4) for
their text: 3.06:1 on the card's white, under the 4.5:1 that 14px text
needs (WCAG 1.4.3). In light mode they now take the brand colour
(#082954, 14.4:1), measured on the test environment. Dark mode is
unchanged: the global flat-button rule already draws them white.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
…nts-on-words

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
…-words

AGENTS.md: kept both the panel line from comments-follow-the-text and the
Remove/Cancel contrast line.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
…s-on-words

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
A per-user preference beside My edits (transcriptions only, default on).
Hidden comments are not drawn and not read out, and the comments card is put
away, but every comment stays on its words and follows the text through every
edit. The comment command, while they are hidden, turns the display on and
opens the comment as usual, and says so in a toast.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
Hiding comments is a way of looking at the text for a while, not a
preference, and a hidden comment forgotten for days is worse than switching
them off again. Every editor now opens with them shown.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
The comment said the editor does not know the reader's name. It does (the
username is in the token); the author is "Scribe" so that no personal
identifier ends up in exported files. Comment and AGENTS.md only.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LCjpD3ehigLiUGH1oVQQAW
(cherry picked from commit 780d78fede6cd6e454e6080fe84f3a49ab8be347)
@erklu

erklu commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator Author

Closing this PR: all of its commits are already in develop, so it will reach main with the develop → main release PR. No changes are lost, and the branch is kept.

@erklu erklu closed this Oct 21, 2026
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.

2 participants