Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor whose sections an LLM refines while you write, and RAG question answering over the documents that result. FastAPI + Postgres/pgvector on the back, SvelteKit on the front, everything OpenAI-compatible and self-hostable. Squashed into a single commit; the development history stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
This commit is contained in:
co-authored by
Claude Opus 5
parent
68d3a43191
commit
784b76baf7
@@ -0,0 +1,70 @@
|
||||
"""Unit tests for the active-section boundary — the authoritative computation
|
||||
the refinement endpoint uses (client mirrors it only for a visual hint)."""
|
||||
|
||||
from app.authoring.sections import active_section, slice_lines
|
||||
|
||||
DOC = """## Zweck
|
||||
Dieser Ablauf beschreibt die Rechnungsstellung.
|
||||
|
||||
## Ablauf
|
||||
Erst exportieren, dann versenden.
|
||||
|
||||
## Fallstricke
|
||||
"""
|
||||
|
||||
|
||||
def _span(md: str, cursor_line: int) -> tuple[int, int]:
|
||||
section = active_section(md, cursor_line)
|
||||
return section.start_line, section.end_line
|
||||
|
||||
|
||||
def test_cursor_in_a_section_selects_from_its_heading_to_the_next() -> None:
|
||||
# Line 5 is "Erst exportieren, dann versenden." under "## Ablauf" (line 4).
|
||||
assert _span(DOC, 5) == (4, 5)
|
||||
_prefix, section, _suffix = slice_lines(DOC, 4, 5)
|
||||
assert section == "## Ablauf\nErst exportieren, dann versenden."
|
||||
|
||||
|
||||
def test_cursor_on_the_heading_selects_that_section() -> None:
|
||||
assert _span(DOC, 1) == (1, 2)
|
||||
|
||||
|
||||
def test_trailing_blank_lines_are_excluded() -> None:
|
||||
# "## Ablauf" body is followed by a blank line before "## Fallstricke";
|
||||
# the blank must not be part of the section.
|
||||
start, end = _span(DOC, 4)
|
||||
assert (start, end) == (4, 5)
|
||||
|
||||
|
||||
def test_content_before_the_first_heading_is_its_own_section() -> None:
|
||||
md = "Eine Einleitung ohne Überschrift.\n\n## Danach\nText."
|
||||
assert _span(md, 1) == (1, 1)
|
||||
|
||||
|
||||
def test_a_document_without_headings_is_one_section() -> None:
|
||||
md = "Nur Fließtext.\nZweite Zeile."
|
||||
assert _span(md, 2) == (1, 2)
|
||||
|
||||
|
||||
def test_nested_headings_stop_at_a_same_or_higher_level() -> None:
|
||||
md = "## A\natext\n### A1\nsub\n## B\nbtext"
|
||||
# Cursor in "## A" (line 1) spans through its subsection "### A1" up to
|
||||
# the line before "## B".
|
||||
assert _span(md, 1) == (1, 4)
|
||||
# Cursor in the subsection spans only the subsection.
|
||||
assert _span(md, 4) == (3, 4)
|
||||
|
||||
|
||||
def test_a_heading_inside_a_code_fence_is_not_a_boundary() -> None:
|
||||
md = "## Code\n```\n## nicht echt\n```\nfertig"
|
||||
assert _span(md, 3) == (1, 5)
|
||||
|
||||
|
||||
def test_a_large_section_narrows_to_the_paragraph_at_the_cursor() -> None:
|
||||
big = "\n\n".join(f"Absatz {i} " + "x" * 400 for i in range(6))
|
||||
md = f"## Groß\n{big}"
|
||||
start, end = _span(md, 6) # somewhere deep in the section
|
||||
_prefix, section, _suffix = slice_lines(md, start, end)
|
||||
# Narrowed: a single paragraph, not the whole oversized section.
|
||||
assert "\n\n" not in section
|
||||
assert section.startswith("Absatz")
|
||||
Reference in New Issue
Block a user