mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cacf8310d5 |
@@ -30,7 +30,9 @@ def is_observation(token: Token) -> bool:
|
||||
|
||||
# Check for proper observation format: [category] content
|
||||
match = re.match(r"^\[([^\[\]()]+)\]\s+(.+)", content)
|
||||
has_tags = "#" in content
|
||||
# Match proper hashtags (word characters after #), not HTML color codes
|
||||
# Negative lookbehind ensures # is not preceded by hex digit or equals sign
|
||||
has_tags = bool(re.search(r"(?<![0-9a-fA-F=])#\w+", content))
|
||||
return bool(match) or has_tags
|
||||
|
||||
|
||||
|
||||
@@ -162,9 +162,14 @@ class Observation(Base):
|
||||
|
||||
We can construct these because observations are always defined in
|
||||
and owned by a single entity.
|
||||
|
||||
Note: Content is truncated to 150 chars to prevent exceeding PostgreSQL's
|
||||
btree index limit (2704 bytes) for the permalink column.
|
||||
"""
|
||||
# Truncate content to prevent permalink overflow in database indexes
|
||||
content_for_permalink = self.content[:150] if len(self.content) > 150 else self.content
|
||||
return generate_permalink(
|
||||
f"{self.entity.permalink}/observations/{self.category}/{self.content}"
|
||||
f"{self.entity.permalink}/observations/{self.category}/{content_for_permalink}"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str: # pragma: no cover
|
||||
|
||||
@@ -121,6 +121,35 @@ def test_observation_excludes_markdown_and_wiki_links():
|
||||
assert not is_observation(token), "No space after category should not be valid observation"
|
||||
|
||||
|
||||
def test_observation_html_color_codes():
|
||||
"""Test that HTML color codes are NOT parsed as hashtags (issue #446).
|
||||
|
||||
This validates the fix where content like:
|
||||
- **<font color="#4285F4">Jane:</font>** Welcome...
|
||||
should NOT be treated as an observation due to the HTML color code.
|
||||
"""
|
||||
# Test HTML color codes should NOT be detected as hashtags
|
||||
token = Token("inline", '**<font color="#4285F4">Jane:</font>** Welcome to the deep dive', 0)
|
||||
assert not is_observation(token), "HTML color codes should not be parsed as hashtags"
|
||||
|
||||
token = Token("inline", '<font color="#FF0000">Red text</font>', 0)
|
||||
assert not is_observation(token), "HTML color codes should not be parsed as hashtags"
|
||||
|
||||
token = Token("inline", 'Style: background-color=#ABCDEF', 0)
|
||||
assert not is_observation(token), "CSS color codes should not be parsed as hashtags"
|
||||
|
||||
# Test valid hashtags still work
|
||||
token = Token("inline", "This is a note #hashtag", 0)
|
||||
assert is_observation(token), "Valid hashtags should still be detected"
|
||||
|
||||
token = Token("inline", "[note] Content with #tag1 and #tag2", 0)
|
||||
assert is_observation(token), "Valid hashtags in observations should still work"
|
||||
|
||||
# Test edge case: hashtag next to hex but separated by space
|
||||
token = Token("inline", "Color is ABCDEF #hashtag", 0)
|
||||
assert is_observation(token), "Hashtags after hex should work if properly separated"
|
||||
|
||||
|
||||
def test_relation_plugin():
|
||||
"""Test relation plugin."""
|
||||
md = MarkdownIt().use(relation_plugin)
|
||||
|
||||
@@ -356,3 +356,62 @@ async def test_find_by_category_case_sensitivity(
|
||||
|
||||
upper_case = await repo.find_by_category("TECH")
|
||||
assert len(upper_case) == 0 # Currently case-sensitive
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_observation_permalink_truncation(
|
||||
session_maker: async_sessionmaker, repo, test_project: Project
|
||||
):
|
||||
"""Test that observation permalinks are truncated to prevent database index overflow (issue #446).
|
||||
|
||||
PostgreSQL btree indexes have a maximum size of 2704 bytes. Long observation content
|
||||
was causing permalinks to exceed this limit. The fix truncates content to 150 chars
|
||||
when generating permalinks.
|
||||
"""
|
||||
async with db.scoped_session(session_maker) as session:
|
||||
entity = Entity(
|
||||
project_id=test_project.id,
|
||||
title="test_entity",
|
||||
entity_type="test",
|
||||
permalink="test/test-entity",
|
||||
file_path="test/test_entity.md",
|
||||
content_type="text/markdown",
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
session.add(entity)
|
||||
await session.flush()
|
||||
|
||||
# Create observation with very long content (simulating transcript dialogue)
|
||||
long_content = "This is a very long observation content that would normally cause the permalink to exceed the PostgreSQL btree index limit of 2704 bytes. " * 50
|
||||
obs = Observation(
|
||||
entity_id=entity.id,
|
||||
content=long_content,
|
||||
category="transcript",
|
||||
)
|
||||
session.add(obs)
|
||||
await session.commit()
|
||||
|
||||
# Refresh to get the relationship
|
||||
await session.refresh(obs)
|
||||
await session.refresh(obs, ["entity"])
|
||||
|
||||
# Verify the full content is stored
|
||||
assert len(obs.content) > 150
|
||||
assert obs.content == long_content
|
||||
|
||||
# Verify the permalink is truncated
|
||||
permalink = obs.permalink
|
||||
assert permalink is not None
|
||||
assert len(permalink) < 500 # Should be much shorter than full content
|
||||
|
||||
# Verify permalink contains truncated content, not full content
|
||||
# The permalink format is: {entity.permalink}/observations/{category}/{truncated_content}
|
||||
assert "test-entity/observations/transcript/" in permalink
|
||||
|
||||
# Verify the content portion is limited
|
||||
# Extract the content portion after the last slash
|
||||
content_portion = permalink.split("/")[-1]
|
||||
# The content portion should correspond to truncated content (150 chars max)
|
||||
# After generate_permalink processing (lowercase, spaces to hyphens, etc.)
|
||||
assert len(content_portion) <= 200 # Allow some buffer for URL encoding
|
||||
|
||||
Reference in New Issue
Block a user