fix(workspace): give synced memory blocks a title

Merged
SA
2mo ago
+109-0

Summary

Memory blocks synced to the workspace arrive with no heading in the UI.

A create runs the backend's LLM extractor, which writes its own title into the
record's metadata. The repair update that follows restores the block verbatim,
and it replaces the metadata dict wholesale — so the extractor's heading is
written and then immediately dropped. buildMetadata never supplied one of its
own, so every synced block ends up title-less.

memory-api.ts already documents the extractor overwriting memory_type/title;
what was missing is that our own replacement never puts a title back.

Change

blockTitle(block) borrows the block's leading markdown heading — which blocks
conventionally open with — and falls back to the block id when the content starts
with body text:

// "# Staging Model Convention\n\n- prefix stg_"  -> "Staging Model Convention"
// "Snowflake account is acme-prod."              -> "warehouse/snowflake"

Falling back rather than borrowing the first body line avoids inventing a heading
the user never wrote. Headings are capped at 120 characters, since a block can run
to MEMORY_MAX_BLOCK_SIZE and its first line can be most of that.

The archive path is unaffected — it spreads the record's existing metadata before
overriding, so a stored title survives archiving.

Verification

  • 89 tests pass in memory-sync.test.ts, including 7 new ones covering heading
    levels, leading blank lines, the body-text fallback, a bare #hashtag, and
    truncation.
  • 958 tests pass across test/memory/ and test/altimate/workspace/.
  • tsgo --noEmit clean; oxlint reports no new warnings on the changed files.

Scope

Existing records keep their missing heading — this fixes writes from here on. A
backfill would mean re-syncing each block, which is a separate call.

🤖 Generated with Claude Code


Summary by cubic

Fixes synced memory blocks arriving in the workspace with no heading in the UI. The create's extractor writes its own title into the metadata, but the repair update replaces the metadata dict wholesale and drops it, so buildMetadata never supplied one. It now does via blockTitle, which borrows the block's leading markdown heading—skipping a training block's metadata comment—and falls back to the block id. Parsing follows CommonMark (leading spaces, decorative closing hash runs), and truncation counts code points so an emoji at the boundary is never split. Existing records keep their missing heading; only new writes are fixed. The archive path is unaffected since it preserves stored metadata.

Summary by CodeRabbit

  • New Features
    • Workspace records now include a readable title derived from a leading Markdown heading in the block.
    • Titles support headings at any level, ignore leading blank lines and training metadata, and remove closing hash markers.
    • Titles are limited to 120 Unicode characters with an ellipsis when truncated.
    • Records beginning with body text or without a usable heading fall back to the block identifier, ensuring every synced record has a title.