fix(format): stop /format from rewriting thematic breaks as list items - #320
harsh-thakkar7 wants to merge 1 commit into
Conversation
47d7f64 to
2646a30
Compare
The tight-list normalisation rule matched the first two characters of any
run of -, * or + at the start of a line, so a thematic break was rewritten
into a list item: "---" became "- -" and "***" became "* **". Because the
rule is applied over the whole document, /format silently destroyed every
thematic break in the document, and with it any YAML frontmatter block
whose delimiters are "---".
Require the character after the list marker to be a non-marker non-space,
so a marker run stays verbatim while genuine tight list items ("-item")
are still normalised exactly as before.
Co-Authored-By: OpenCode <opencode@opencode.ai>
2646a30 to
19402c2
Compare
Eswar0108
left a comment
There was a problem hiding this comment.
Review: approve
Verified this independently rather than relying on the PR's own test run.
The fix. The change is a single negative lookahead in
backend/ai_logic.py::_apply_markdown_formatting_rules:
- line = re.sub(r"^(\s*)([-*+])(\S)", r"\1\2 \3", line)
+ line = re.sub(r"^(\s*)([-*+])(?![-*+])([^\s])", r"\1\2 \3", line)Requiring the third character to be neither whitespace nor another marker is the
right guard: it keeps ---, ***, ***, and - - - verbatim while still
normalising genuine tight list items.
Evidence.
tests/test_ai_automation_logic.py: 37 passed, 15 subtests passed.- Reverting only
ai_logic.pytoupstream/mainand keeping the new tests:
5 failed, 35 passed —test_thematic_break_is_not_split_into_a_list_item
(3 subtests),test_yaml_frontmatter_delimiters_are_preserved, and
test_format_automation_preserves_thematic_breaks. The tests genuinely pin the
behaviour rather than passing vacuously. - I also re-implemented the rule in isolation and probed it directly. Thematic
breaks (---,***,* * *, indented---, YAML frontmatter delimiters) all
pass through unchanged;-item,*item,+item,-nested,1.item,#head
all still normalise correctly. No regression in the original behaviour. - CI is green (Backend, Frontend).
Worth calling out: the YAML frontmatter case is the strongest part of this
change. Preserving --- delimiters matters beyond rendering — backend/knowledge_logic.py
parses frontmatter, so a /format run that rewrites a note's delimiters would
corrupt the knowledge base. The test covers that path specifically.
Nit (non-blocking): a run of 5+ markers (-----) is also a valid thematic
break, and it is preserved here as a side effect rather than by intent. That is
the right outcome; just noting the comment explains the 3+ case and the code
happens to cover more.
Ready to merge from my side.
Summary
/formatrewrote every Markdown thematic break in the document into a list item, turning---into- -and***into* **, and destroying any YAML frontmatter block in the process.Root cause
_apply_markdown_formatting_rulesnormalises tight list markers (-item→- item). The rule had no notion of a marker run, so it fired on the first two characters of any repetition of-,*or+at the start of a line (backend/ai_logic.py:858, before this change):For
---the regex matches-plus the second-and rewrites the pair to- -.***likewise became* **. Since this pass runs over the whole document, a single/formatinvocation silently corrupted all of them.---is not an edge case here: it is a standard thematic break (<hr>) and it is also the YAML frontmatter delimiter that this repo parses inbackend/knowledge_logic.py(extract_note_title,chunk_markdown_document). The word-count normaliser already special-cases it (backend/word_count.py:28), andtests/test_features.py:96asserts horizontal-rule support — so it is a first-class construct that/formatwas destroying. The existing code was also inconsistent with itself:___survived untouched, because the rule's character class is[-*+], which excludes_.Before / after
Request:
POST /api/ai/chatwith{"message": "format", "document_text": "---\ntitle: My Note\n---\n\nIntro paragraph.\n\n---\n\nOutro paragraph.\n"}proposed_action.contentbefore this change:After this change:
The frontmatter is gone in the "before" output; the document is no longer parseable as a note and the intended horizontal rule is a bullet list.
The fix
Require the character after the list marker to be neither another marker nor whitespace, so a marker run stays verbatim while a genuine tight list item is still normalised:
This is a one-line change to one rule. Heading, ordered-list and blank-line handling are untouched.
Tests
Added
TestFormattingRulesPreserveThematicBreakstotests/test_ai_automation_logic.py, covering thematic breaks (---,***, indented and spaced variants), frontmatter delimiters, the end-to-end/formatautomation path, and a guard that tight list items (-item,1.item,#head) are still normalised exactly as before.5 of the new tests fail without this fix. Verified by restoring the unfixed file from upstream and re-running:
The
test_tight_list_items_are_still_normalisedguard passes both before and after, confirming the existing normalisation behaviour is preserved rather than merely removed.Verification
All CI gates run locally on the branch, against
origin/mainat9e77fa0:.venv/bin/python -m pytestcd frontend && npm testcd frontend && npm run lintcd frontend && npm run build.venv/bin/ruff format --check(changed files).venv/bin/ruff check(changed files)Baseline on
origin/mainis 195 backend tests and 23 frontend tests, so this PR is +4 backend tests, +0 frontend tests (the new tests are subtests within 4 new test methods; the unfixed run above fails 5 of them).