You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stacked on #500 (it uses that PR's htmlutil.UnwrapTrixContent). Retarget this to main once #500 merges.
HEY serves rich text for editing inside Action Text's <div class="trix-content"> layout. #500 stopped a contact note nesting one div deeper each time note_html was written back with --note-html. The journal and snippets had the same bug:
hey journal read --json answers the entry's HTML (HEY's content_html) as content, wrapped. hey journal write --content-html "$(hey journal read 2026-03-15 --jq .data.content)" stored the wrapper, and the next read wrapped it again.
hey snippet list answers content_html, wrapped the same way, and hey snippet update --content-html / create --content-html stored it.
Both now take the wrapper off --content-html with htmlutil.UnwrapTrixContent, as contact note set --note-html does. That removes only HEY's own layout: a leading div with exactly class="trix-content", including the nesting that earlier round trips left at the start. The writer's own divs are kept.
hey journal read --json also answers content_markdown, the entry as Markdown, and content_markdown_lossless, matching note_markdown and note_markdown_lossless in #500. Journal entries can hold attachments and images from HEY's web editor, and writing Markdown back would drop them. So when content_markdown_lossless is false, the docs and the skill say to change content and write it back with --content-html instead. For the same reason, hey journal write with no content no longer opens $EDITOR on such an entry. It refuses with a usage error that points at --content-html, and it writes nothing.
What was checked
HEY adds the wrapper only when trix_html_for_rich_text_editing is given an Action Text rich text record. Given a body, it doesn't.
No: a message is served from the entry's body, and draft show answers Markdown only
No
event add/edit --notes
event description
Served as plain text, written as plain text
No
A related finding is left out of this PR. The bulk-reply draft that HEY prefills with the name tag is a rich text record, so it comes back wrapped. The CLI and the TUI send it back inside the reply once, but a fresh draft is read for every bulk reply, so it doesn't build up. The TUI's journal editor already removes the wrapper with its own tokenizer (journalEditorContent).
Summary by cubic
Stops journal entries and snippets from nesting one div deeper each time their HTML is read back and written again, the same fix contact notes got in #500.
HEY serves journal and snippet HTML inside its editor's <div class="trix-content"> wrapper. --content-html on journal write and snippet create/update now takes the wrapper off with htmlutil.UnwrapTrixContent, so a round trip no longer stores it; divs of the writer's own are kept. The flag help says so.
journal read --json answers content_markdown (the form journal write takes) and content_markdown_lossless, which says whether that Markdown holds everything in the entry; --help explains the three fields. When it is false the entry holds an attachment, an image, or other markup — a colour, for instance — Markdown cannot carry, so change content and write it back with --content-html instead. journal write with no content refuses to open $EDITOR on such an entry, answering a usage error pointing at --content-html, and writes nothing.
The skill's recipes for adding to an entry fail their read rather than write what it cannot carry: the Markdown one checks content_markdown_lossless itself, and the HTML one now fails when the day has no entry, so neither writes a dropped entry or one starting with null.
--message-html needs no change: HEY serves messages without the wrapper.
Written for commit eeddd50. Summary will update on new commits.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟢 Approved
The implementation is consistent with the shared helper and includes focused regression coverage.
Review effort: Balanced Findings: None
What changed in this PR
Prevents repeated HTML round trips from nesting HEY’s Trix wrapper and adds Markdown journal output.
Changes:
Unwraps Trix HTML for journal and snippet writes.
Adds content_markdown to journal JSON output.
Adds regression tests and updates documentation.
[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
The reason will be displayed to describe this comment to others. Learn more.
Approved. Cursor Security Agent completed with no findings that need human review; Cursor Bugbot was not present on this run. No reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Approver
This recipe still writes when the day has no entry: journal read succeeds and --jq emits null, so && does not stop and the new entry begins with the literal word null. Add the non-null guard that the prose below requires, and preserve it in the embedding test.
This issue also appears on line 1006 of the same file.
…trip
HEY serves a journal entry's content_html and a snippet's content_html
through trix_html_for_rich_text_editing, and both are rich text attributes,
so each read comes wrapped in Action Text's <div class="trix-content">
layout. hey journal read --json answers that HTML as content, and hey
snippet list answers it as content_html. Writing either back with
--content-html stored the wrapper as part of the entry, and the next read
wrapped it again: the entry sank one div deeper on every trip.
journal write and snippet create/update now take the wrapper off
--content-html with htmlutil.UnwrapTrixContent, as contact note set does
for --note-html. A div of the writer's own, or the wrapper's class anywhere
but the top level, is left alone.
hey journal read --json also answers content_markdown, the entry as
Markdown, which is the form journal write takes by default, so an entry can
be read, changed and written back without handling HTML at all.
--message-html needs nothing: a message's content is served from the
entry's Action Text body rather than the rich text record, so HEY answers
it without the layout, and draft show answers Markdown only.
A journal entry can hold what Markdown has no syntax for: HEY's web editor
attaches files and images to one. Writing content_markdown back in its
place would drop them, the same risk contact notes carry.
hey journal read --json now answers content_markdown_lossless, as contact
note show answers note_markdown_lossless. When it is false, the way to add
to an entry is to change content and write it with --content-html, which
takes HEY's wrapper off. The docs and the skill say so, with a recipe for
each case.
hey journal write with no content, at a terminal, opens $EDITOR on the
day's entry as Markdown and saves what comes back. For an entry holding an
attachment, an image or anything else Markdown has no syntax for, saving
dropped it.
The editor is no longer opened on such an entry: the command answers a
usage error that says why and points at changing content and writing it
back with --content-html, and writes nothing. It is the same test that
sets content_markdown_lossless. A lossless entry and an empty day open the
editor as before.
The hint read as a shell command ending in "$entry...", which run as it
stood would write three literal dots into the entry. It now names the two
commands and a placeholder for the changed HTML, in the form contact note
set's refusal uses.
The general writing paragraph in docs/cli.md now says the journal refuses
its editor the way a contact note does, and a journal entry carrying an
attribute such as a colour is pinned as not lossless.
snippet create, snippet update and journal write describe --content-html as raw HTML, which no longer says everything: the trix-content wrapper HEY serves content_html in is taken off, so HTML read back can be written again. The flag help says so now, as docs/cli.md does.
The recipe for adding to a journal entry read content_markdown and wrote
it back, trusting the reader to have looked at content_markdown_lossless
first. The read now checks the flag itself in --jq and fails when it is
false, so the && stops before anything is written. A day with no entry
fails the same check, instead of starting an entry with the word null.
The quick content_markdown examples say it is written back only when the
flag is true, as the contact note ones do.
…fields
The skill's recipe for adding to an entry as HTML read .data.content bare,
which a day with no entry answers as null, so the && went on to write
"null<p>…</p>". The read now errors when there is no content.
hey journal read --help now says what content, content_markdown and
content_markdown_lossless are, and that the Markdown is written back only
when the flag is true.
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
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.
Stacked on #500 (it uses that PR's
htmlutil.UnwrapTrixContent). Retarget this tomainonce #500 merges.HEY serves rich text for editing inside Action Text's
<div class="trix-content">layout. #500 stopped a contact note nesting one div deeper each timenote_htmlwas written back with--note-html. The journal and snippets had the same bug:hey journal read --jsonanswers the entry's HTML (HEY'scontent_html) ascontent, wrapped.hey journal write --content-html "$(hey journal read 2026-03-15 --jq .data.content)"stored the wrapper, and the next read wrapped it again.hey snippet listanswerscontent_html, wrapped the same way, andhey snippet update --content-html/create --content-htmlstored it.Both now take the wrapper off
--content-htmlwithhtmlutil.UnwrapTrixContent, ascontact note set --note-htmldoes. That removes only HEY's own layout: a leading div with exactlyclass="trix-content", including the nesting that earlier round trips left at the start. The writer's own divs are kept.hey journal read --jsonalso answerscontent_markdown, the entry as Markdown, andcontent_markdown_lossless, matchingnote_markdownandnote_markdown_losslessin #500. Journal entries can hold attachments and images from HEY's web editor, and writing Markdown back would drop them. So whencontent_markdown_losslessisfalse, the docs and the skill say to changecontentand write it back with--content-htmlinstead. For the same reason,hey journal writewith no content no longer opens$EDITORon such an entry. It refuses with a usage error that points at--content-html, and it writes nothing.What was checked
HEY adds the wrapper only when
trix_html_for_rich_text_editingis given an Action Text rich text record. Given a body, it doesn't.journal write --content-htmljournal readcontent,journal listcontent_htmlsnippet create/update --content-htmlsnippet listcontent_htmlcontact note set --note-htmlnote_htmlcompose/reply/forward/draft edit/bulk-reply send --message-htmlthread read,draft showdraft showanswers Markdown onlyevent add/edit --notesdescriptionA related finding is left out of this PR. The bulk-reply draft that HEY prefills with the name tag is a rich text record, so it comes back wrapped. The CLI and the TUI send it back inside the reply once, but a fresh draft is read for every bulk reply, so it doesn't build up. The TUI's journal editor already removes the wrapper with its own tokenizer (
journalEditorContent).Summary by cubic
Stops journal entries and snippets from nesting one div deeper each time their HTML is read back and written again, the same fix contact notes got in #500.
HEY serves journal and snippet HTML inside its editor's
<div class="trix-content">wrapper.--content-htmlonjournal writeandsnippet create/updatenow takes the wrapper off withhtmlutil.UnwrapTrixContent, so a round trip no longer stores it; divs of the writer's own are kept. The flag help says so.journal read --jsonanswerscontent_markdown(the formjournal writetakes) andcontent_markdown_lossless, which says whether that Markdown holds everything in the entry;--helpexplains the three fields. When it is false the entry holds an attachment, an image, or other markup — a colour, for instance — Markdown cannot carry, so changecontentand write it back with--content-htmlinstead.journal writewith no content refuses to open$EDITORon such an entry, answering a usage error pointing at--content-html, and writes nothing.The skill's recipes for adding to an entry fail their read rather than write what it cannot carry: the Markdown one checks
content_markdown_losslessitself, and the HTML one now fails when the day has no entry, so neither writes a dropped entry or one starting withnull.--message-htmlneeds no change: HEY serves messages without the wrapper.Written for commit eeddd50. Summary will update on new commits.