Skip to content

Stop journal entries and snippets nesting deeper on every HTML round trip - #505

Merged
robzolkos merged 7 commits into
mainfrom
journal-html-unwrap
Sep 27, 2026
Merged

robzolkos merged 7 commits into
mainfrom
journal-html-unwrap

Conversation

@robzolkos

@robzolkos robzolkos commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

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.

Input Read back from Wrapped on read? Nests?
journal write --content-html journal read content, journal list content_html Yes (rich text record) Yes. Fixed
snippet create/update --content-html snippet list content_html Yes (rich text record) Yes. Fixed
contact note set --note-html note_html Yes Fixed in #500
compose/reply/forward/draft edit/bulk-reply send --message-html thread read, draft show 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.

Review in cubic

Copilot AI balanced review requested due to automatic review settings September 26, 2026 18:50
@robzolkos
robzolkos requested a review from a team as a code owner September 26, 2026 18:50
Copilot AI previously approved these changes Sep 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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.

File Description
internal/​cmd/​journal.go Unwraps HTML and exposes Markdown.
internal/​cmd/​journal_test.go Tests stable HTML and Markdown round trips.
internal/​cmd/​snippet.go Normalizes snippet HTML writes.
internal/​cmd/​snippet_test.go Tests create/update round trips.
docs/​cli.md Documents round-trip behavior.
skills/​hey/​SKILL.md Updates agent guidance and examples.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@robzolkos
robzolkos requested a balanced review from Copilot September 26, 2026 18:58
Copilot AI dismissed their stale review, a newer Copilot review was requested September 26, 2026 18:58
Copilot AI previously approved these changes Sep 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approved

The implementation consistently applies the shared unwrapping behavior and includes comprehensive regression coverage.

Review effort: Balanced
Findings: None

@robzolkos
robzolkos requested a balanced review from Copilot September 26, 2026 19:05
Copilot AI dismissed their stale review, a newer Copilot review was requested September 26, 2026 19:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The losslessness guard can permit destructive edits of unsupported HTML attributes, and its recovery hint appends literal ellipses.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)

Comment thread internal/cmd/journal.go
Comment thread internal/cmd/journal.go Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The changed snippet behavior is missing from both snippet commands’ ordinary help text.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
Resolved since last review (2)

Comment thread internal/cmd/snippet.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The skill’s copyable recipes can write literal null into an empty journal day.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Low severity Guard against null entries before writing

skills/​hey/​SKILL.md:997

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The skill’s HTML recipe can write literal null for an empty day, and journal read help omits the new safety contract.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)

Comment thread skills/hey/SKILL.md Outdated
Comment thread internal/cmd/journal.go
Base automatically changed from contact-notes-markdown to main September 27, 2026 02:29
…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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approved

The behavior is consistently implemented, documented, and covered by focused regression tests.

Review effort: Balanced
Findings: None

Resolved since last review (2)

@robzolkos
robzolkos merged commit b60e69f into main Sep 27, 2026
26 checks passed
@robzolkos
robzolkos deleted the journal-html-unwrap branch September 27, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants