diff --git a/coulomb_social/apps/spaces/content.py b/coulomb_social/apps/spaces/content.py index dbebc5b..d7ff175 100644 --- a/coulomb_social/apps/spaces/content.py +++ b/coulomb_social/apps/spaces/content.py @@ -52,6 +52,7 @@ _ALLOWED_ATTRS = { "img": ["src", "alt", "title"], "a": ["href", "title", "rel"], "code": ["class"], + "ol": ["start"], "th": ["align"], "td": ["align"], } @@ -100,7 +101,8 @@ def render_markdown(text: str) -> str: """Render the supported Markdown subset. Extensions are fenced code, tables, newline-to-break and sane lists. - Raw HTML, script, event handlers and non-http(s)/mailto URLs are removed. + Ordered-list starting numbers are preserved. Unsupported HTML, scripts, + event handlers and non-http(s)/mailto URLs are removed. Relative asset URLs are kept so copied page images still resolve. This is not acceptance of a private corpus or of arbitrary Bubble markup. """ diff --git a/docs/release/ordered-list-fidelity-2026-10-31.json b/docs/release/ordered-list-fidelity-2026-10-31.json new file mode 100644 index 0000000..cbcbd89 --- /dev/null +++ b/docs/release/ordered-list-fidelity-2026-10-31.json @@ -0,0 +1,42 @@ +{ + "schema": "coulomb.ordered-list-fidelity.v1", + "verified_at": "2026-10-31T17:20:32.943192+00:00", + "source_articles_sha256": "f33967c79c0f8a60a7d504f5d5e4082f8bc9e3c34f6a3160cb200fd380831d62", + "prior_audit_sha256": "3926e24a3b903f5e1d1c7133dc5f9df1e958183432c5131949ce838eb60acf1e", + "current_audit_sha256": "d8f382e24d7adfe3c43dc71f32f8f6e647bf68239cbcdc6769a6c8fb85d666b9", + "records": 245, + "rendered_cards_verified": 242, + "source_bodies_unchanged": true, + "missing_bodies_retained": 3, + "ordered_list_starts_restored": 15, + "affected_records": 4, + "markup_change_cases_before": 6, + "markup_change_cases_after": 3, + "remaining_removed_tags": { + "topic": 3 + }, + "remaining_removed_attributes": [ + { + "tag": "topic", + "attribute": "name", + "count": 2 + } + ], + "private_review_directory": "~/.config/coulomb-social/auth/exports/rendering-review-20261031-numbering", + "source_hashes": { + "coulomb_social/apps/spaces/content.py": "5d6bb5c800b3e776fff385fa73284f4916403676dc2a8bc06b8fadc7d2387633", + "tests/test_rendering_audit.py": "ae092753f4d1d006bb88f1ce11581975c105c5231819f9409ddaaf2fc83d32f5" + }, + "validation": { + "focused_passed": 14, + "full_gate_passed": 650, + "full_gate_warnings": 69, + "failures": 0, + "errors": 0, + "skipped": 0, + "full_gate_seconds": 91.85 + }, + "production_deployed": false, + "original_bubble_render_compared": false, + "productive_fidelity_accepted": false +} diff --git a/tests/test_rendering_audit.py b/tests/test_rendering_audit.py index ec7e902..956dee9 100644 --- a/tests/test_rendering_audit.py +++ b/tests/test_rendering_audit.py @@ -52,6 +52,29 @@ def test_serialized_line_break_difference_is_not_reported_as_lost_markup(): assert not audit["visible_text_changed_by_sanitization"] +def test_continued_numbered_lists_keep_their_starts_through_sanitization(): + body = "2. Second item\n3. Third item\n\nSeparate paragraph.\n\n8. Eighth item" + audit, rendered = audit_body(body) + assert '
    ' in rendered + assert '
      ' in rendered + assert not audit["markup_changed_by_sanitization"] + assert not audit["visible_text_changed_by_sanitization"] + assert not audit["productive_fidelity_accepted"] + + +def test_list_start_permission_does_not_allow_event_handlers_or_custom_markup(): + body = ('
      1. Fourth item
      ' + 'Placeholder' + 'Link') + audit, rendered = audit_body(body) + assert '
        ' in rendered + assert 'onclick' not in rendered and 'javascript:' not in rendered + assert '