fix(source): retain complete multiline HTML headings when folding
Use parser token spans for raw heading ends, preserving authored multiline attributes, nested title markup and closing syntax. Three newline regressions reproduce the previous header loss; all 589 core tests and required 100% line coverage pass. Refs #10 Co-Authored-By: Codex (GPT-6) <noreply@openai.com> Signed-off-by: James Maes <jmaes@dmdbrands.com>
This commit is contained in:
5 files changed
+88
-6
No files matched your search
+25
-1
@@ -2,6 +2,30 @@
|
||||
|
||||
Updated: 2026-10-11
|
||||
|
||||
## Raw multiline heading fold reproduced and corrected - 2026-10-11
|
||||
|
||||
Exact764c7ac / CI38179931820 is terminal: focused20/21, native98/99,
|
||||
published98/99, zero skips. All failures are the newly added raw-HTML case at
|
||||
its complete-header fold assertion. Published failure prevents the new installed
|
||||
registration lifecycle readbacks from running; do not claim those passed.
|
||||
Retain current-focused-764c7ac.log/current-native-764c7ac.log and downloaded TRXs
|
||||
under artifacts/reader-regressions-38179931820 and
|
||||
artifacts/current-installed-evidence-38179931820.
|
||||
|
||||
The parser mapped a raw heading's end to its opening line, hiding remaining
|
||||
multiline attributes/title/closing syntax when folded. Three actual failing core
|
||||
regressions reproduced this; existing AngleSharp token spans now retain the
|
||||
complete header, respecting quoted attributes, comments and nested title markup.
|
||||
All34 source mapping/folding cases and all589 core tests pass with100% measured
|
||||
line coverage. Release build has zero warnings/errors and format verification
|
||||
passes. The unchanged raw-HTML runtime case and full distributed suites must
|
||||
verify the correction on the next pushed head.
|
||||
|
||||
No production publication, signing verification, OS default selection or local
|
||||
desktop input. Preserve owner preview55472. Actual chooser/Explorer, concurrent
|
||||
stable/dev isolation, full fidelity audit, genuine maintainer review and final
|
||||
signed release artifacts remain required.
|
||||
|
||||
## All distributed source cases pass; next release acceptance checks - 2026-10-11
|
||||
|
||||
Exact9994eda CI38176727468 is terminal success: focused20/20, native98/98,
|
||||
@@ -10,7 +34,7 @@ Actual AppKit apply/restart advances stable0.1.0→0.4.0 and devbeta.1→beta.2;
|
||||
post-upgrade channel/versions and target DLL hashes are verified by the harness.
|
||||
Retain actual TRXs and before/after JSON under artifacts/current-installed-evidence-38176727468.
|
||||
These fixtures are unsigned and sequential; they do not prove production signing
|
||||
or simultaneous stable/dev coexistence. Signed cleanupc89ce5c remains local.
|
||||
or simultaneous stable/dev coexistence. Signed cleanupc89ce5c was pushed as parent of764c7ac.
|
||||
|
||||
New read-only harness checks require all five Open With/Default Apps extensions
|
||||
at real initial install and after upgrade, the same owned ProgID, complete owned
|
||||
|
||||
+3
-1
@@ -3,7 +3,9 @@
|
||||
- [x] Exact6374611: native97/98 and published97/98, focused19/20, zero skips; actual sustained colors/hover/focus and outline pass.
|
||||
- [x] Exact9994eda: offscreen native geometry correction passes focused20/20 and full native98/98; original focus/both-direction glyph/wheel assertions unchanged.
|
||||
- [x] Exact9994eda: published98/98 and installed stable/dev97/97 each, real update/apply/restart and target hashes pass; zero skips.
|
||||
- [ ] Verify the clean1.0 milestone candidate, actual install/update/uninstall registration readbacks and new raw-HTML runtime case on the next exact head.
|
||||
- [x] Exact764c7ac: retain focused20/21, native98/99 and published98/99; only the new raw multiline-heading fold fails, zero skips. Installed lifecycle phase is not reached.
|
||||
- [x] Reproduce multiline raw-header loss in core tests and correct parser token end spans;589 core/100% measured lines, zero-warning Release/format pass.
|
||||
- [ ] Verify the correction with unchanged raw-HTML runtime assertions and actual install/update/uninstall registration readbacks on the next exact head.
|
||||
|
||||
- [x] Generate and publish the GUI-only AppKit application with the approved identity.
|
||||
- [x] Configure/read back repository protections and establish the initial hosted CI baseline.
|
||||
|
||||
@@ -10,7 +10,7 @@ The full port and public release are not complete; do not add a conformance badg
|
||||
| Required inventory | `K22-REPO-42` remains pending: signed inventory proposal6b4fd17 is in standards draftPR36; its checks pass. App/starter are Product with adoption-in-progress status. Review/merge is required before claiming registration in main. |
|
||||
| Shared community defaults | `K22-REPO-10` warns: no owner `.github` repository. App governance/security/contributing files exist; shared owner defaults remain optional setup work. |
|
||||
| Workflow reviews | `K22-CI-02/03/31` require review of PR/push tests, documented command alignment and full release gate reuse. Current callers visibly run PR/push(main) CI and the release reuses CI/security. No human review acknowledgement is inferred. |
|
||||
| Test evidence | Exact9994eda passes586 core tests/100% measured lines, focused20/20 and full native98/98, zero skips. CI38176727468 is terminal: published98/98 and installed stable/dev97/97 each, zero skips; actual apply/restart passes both. Diagnostic cleanup needs fresh exact-head acceptance. |
|
||||
| Test evidence | Exact9994eda passes586 core tests/100% measured lines, focused20/20 and full native98/98, zero skips. CI38176727468 is terminal: published98/98 and installed stable/dev97/97 each, zero skips; actual apply/restart passes both. Diagnostic-free764c7ac passes98/99 native/published and20/21 focused: the new multiline raw-header fold fails. Local correction passes589 core/100% lines; fresh native/installed registration acceptance remains required. |
|
||||
| Release | Stable/dev synthetic installed updates call AppKit apply/restart successfully. Actual verified Azure identity/profile/signer permissions and exact signed distributed artifacts remain deferred. Owner authorized stable1.0/dev publication after all requirements pass; signing and review requirements remain unmet. |
|
||||
|
||||
Full acceptance remains in `docs/PRODUCT.md` and `docs/TODO.md`; maintainer review,
|
||||
|
||||
@@ -73,7 +73,7 @@ internal sealed class DocumentSourceMap
|
||||
else if(node is HtmlInline inline && IsHeadingTag(inline.Tag))
|
||||
{
|
||||
int start = offset + inline.Span.Start;
|
||||
inline.Tag = inline.Tag.Insert(3, Attribute(AddPosition(start, EndOfLine(start))));
|
||||
inline.Tag = inline.Tag.Insert(3, Attribute(AddPosition(start, EndOfLine(offset + inline.Span.End))));
|
||||
}
|
||||
}
|
||||
return Markdown.ToHtml(document, pipeline);
|
||||
@@ -110,12 +110,41 @@ internal sealed class DocumentSourceMap
|
||||
|
||||
|
||||
|
||||
/// <summary>Tokenizer spans preserve complete raw headings across lines without mistaking quoted attributes or comments for closing syntax.</summary>
|
||||
private static HtmlParser CreateSourceParser(Dictionary<int, int> headingEnds)
|
||||
{
|
||||
int pendingHeading = -1;
|
||||
return new HtmlParser(new HtmlParserOptions
|
||||
{
|
||||
IsKeepingSourceReferences = true,
|
||||
OnToken = (token, range) =>
|
||||
{
|
||||
if(token.Name.Length == 2 && token.Name[0] == 'h' && token.Name[1] is >= '1' and <= '6')
|
||||
{
|
||||
if(token.Type == HtmlTokenType.StartTag)
|
||||
{
|
||||
pendingHeading = range.Start.Index;
|
||||
headingEnds.Add(pendingHeading, range.End.Index - 1);
|
||||
}
|
||||
else if(token.Type == HtmlTokenType.EndTag && pendingHeading >= 0)
|
||||
{
|
||||
headingEnds[pendingHeading] = range.End.Index - 1;
|
||||
pendingHeading = -1;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Source slices retain quote/list indentation offsets while emitted raw HTML omits those Markdown prefixes.</summary>
|
||||
private void AnnotateHtmlBlock(HtmlBlock block, int offset)
|
||||
{
|
||||
StringLine[] lines = block.Lines.Lines;
|
||||
string html = string.Join('\n', lines.Take(block.Lines.Count).Select(line => line.Slice.ToString()));
|
||||
using AngleSharp.Html.Dom.IHtmlDocument document = new HtmlParser(new HtmlParserOptions { IsKeepingSourceReferences = true }).ParseDocument(html);
|
||||
var headingEnds = new Dictionary<int, int>();
|
||||
using AngleSharp.Html.Dom.IHtmlDocument document = CreateSourceParser(headingEnds).ParseDocument(html);
|
||||
int[] lineStarts = new int[block.Lines.Count];
|
||||
int length = 0;
|
||||
for(int index = 0; index < lineStarts.Length; index++)
|
||||
@@ -139,7 +168,11 @@ internal sealed class DocumentSourceMap
|
||||
changes = [];
|
||||
inserts.Add(index, changes);
|
||||
}
|
||||
changes.Add((column + 3, Attribute(AddPosition(start, EndOfLine(start)))));
|
||||
int endPosition = headingEnds[position];
|
||||
int endIndex = Array.BinarySearch(lineStarts, endPosition);
|
||||
endIndex = endIndex >= 0 ? endIndex : ~endIndex - 1;
|
||||
int end = offset + lines[endIndex].Slice.Start + endPosition - lineStarts[endIndex];
|
||||
changes.Add((column + 3, Attribute(AddPosition(start, EndOfLine(end)))));
|
||||
}
|
||||
foreach((int index, List<(int Position, string Attribute)> changes) in inserts)
|
||||
{
|
||||
|
||||
@@ -93,6 +93,29 @@ public sealed class DocumentSourceTextTests
|
||||
|
||||
|
||||
|
||||
/// <summary>A folded multiline HTML heading retains all authored attributes, nested title markup and closing syntax.</summary>
|
||||
[Theory]
|
||||
[InlineData("\n")]
|
||||
[InlineData("\r\n")]
|
||||
[InlineData("\r")]
|
||||
public void SetCollapsed_RawMultilineHeading_PreservesCompleteHeader(string newline)
|
||||
{
|
||||
string header = string.Join(newline, "<H2", " title='quoted > value'>", "Raw <em>child</em>", "<!-- </h2> is a comment -->", "</H2>");
|
||||
string original = "# Root" + newline
|
||||
+ header + newline + "Hidden body" + newline + "<h3>First peer</h3><h3>Second peer</h3>" + newline + "Peer body" + newline + newline + "# Last" + newline + "Tail";
|
||||
var source = new DocumentSourceText(original, new DocumentOutline(original));
|
||||
Assert.True(source.SetCollapsed([1]));
|
||||
Assert.Contains(header.ReplaceLineEndings("\r"), source.Text, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Hidden body", source.Text, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("First peer", source.Text, StringComparison.Ordinal);
|
||||
Assert.Contains("# Last\rTail", source.Text, StringComparison.Ordinal);
|
||||
Assert.Equal(new[] { 0, 1, 4 }, source.Headings.Select(heading => heading.Index));
|
||||
Assert.True(source.SetCollapsed([]));
|
||||
Assert.Equal(original, source.GetCopyText(0, source.Text.Length));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>Folding accepts only bounded canonical snapshots with matching source anchors.</summary>
|
||||
[Fact]
|
||||
public void Create_InvalidSnapshot_RejectsInput()
|
||||
|
||||
Reference in new issue
Block a user