fix(core): preserve empty frontmatter permalink semantics

Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2026-04-13 09:54:44 -05:00
parent 58dd6963bd
commit a3e4badcf9
2 changed files with 55 additions and 20 deletions
+33 -20
View File
@@ -60,6 +60,11 @@ class EntityWriteResult:
search_content: str
def _frontmatter_permalink(value: object) -> str | None:
"""Return an explicit frontmatter permalink only when YAML parsed a real string."""
return value if isinstance(value, str) and value else None
class EntityService(BaseService[EntityModel]):
"""Service for managing entities in the database."""
@@ -287,11 +292,13 @@ class EntityService(BaseService[EntityModel]):
schema.note_type = content_frontmatter["type"]
if "permalink" in content_frontmatter:
content_markdown = self._build_frontmatter_markdown(
schema.title,
schema.note_type,
_coerce_to_string(content_frontmatter["permalink"]),
)
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
if content_permalink is not None:
content_markdown = self._build_frontmatter_markdown(
schema.title,
schema.note_type,
content_permalink,
)
# Get unique permalink (prioritizing content frontmatter) unless disabled
if self.app_config and self.app_config.disable_permalinks:
@@ -394,11 +401,13 @@ class EntityService(BaseService[EntityModel]):
schema.note_type = content_frontmatter["type"]
if "permalink" in content_frontmatter:
content_markdown = self._build_frontmatter_markdown(
schema.title,
schema.note_type,
_coerce_to_string(content_frontmatter["permalink"]),
)
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
if content_permalink is not None:
content_markdown = self._build_frontmatter_markdown(
schema.title,
schema.note_type,
content_permalink,
)
# Check if we need to update the permalink based on content frontmatter (unless disabled)
new_permalink = entity.permalink # Default to existing
@@ -525,11 +534,13 @@ class EntityService(BaseService[EntityModel]):
schema.note_type = content_frontmatter["type"]
if "permalink" in content_frontmatter:
content_markdown = self._build_frontmatter_markdown(
schema.title,
schema.note_type,
_coerce_to_string(content_frontmatter["permalink"]),
)
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
if content_permalink is not None:
content_markdown = self._build_frontmatter_markdown(
schema.title,
schema.note_type,
content_permalink,
)
# --- Permalink Resolution ---
if self.app_config and self.app_config.disable_permalinks:
@@ -668,11 +679,13 @@ class EntityService(BaseService[EntityModel]):
update_data["note_type"] = _coerce_to_string(content_frontmatter["type"])
if "permalink" in content_frontmatter:
content_markdown = self._build_frontmatter_markdown(
_coerce_to_string(update_data.get("title", entity.title)),
_coerce_to_string(update_data.get("note_type", entity.note_type)),
_coerce_to_string(content_frontmatter["permalink"]),
)
content_permalink = _frontmatter_permalink(content_frontmatter["permalink"])
if content_permalink is not None:
content_markdown = self._build_frontmatter_markdown(
_coerce_to_string(update_data.get("title", entity.title)),
_coerce_to_string(update_data.get("note_type", entity.note_type)),
content_permalink,
)
metadata = normalize_frontmatter_metadata(content_frontmatter or {})
update_data["entity_metadata"] = {k: v for k, v in metadata.items() if v is not None}
@@ -27,6 +27,28 @@ async def test_create_entity_with_content_returns_full_and_search_content(
assert result.search_content == "Create body content"
@pytest.mark.asyncio
@pytest.mark.parametrize("permalink_line", ["permalink:", "permalink: null", 'permalink: ""'])
async def test_create_entity_ignores_empty_frontmatter_permalink(
entity_service, file_service, permalink_line: str
) -> None:
result = await entity_service.create_entity_with_content(
EntitySchema(
title="Empty Frontmatter Permalink",
directory="notes",
note_type="note",
content=f"---\n{permalink_line}\n---\nCreate body content",
)
)
file_path = file_service.get_entity_path(result.entity)
file_content, _ = await file_service.read_file(file_path)
assert result.entity.permalink == "test-project/notes/empty-frontmatter-permalink"
assert "permalink: test-project/notes/empty-frontmatter-permalink" in file_content
assert "permalink: None" not in file_content
@pytest.mark.asyncio
async def test_update_entity_with_content_returns_full_and_search_content(
entity_service, file_service