diff --git a/memhub/apps/web/src/MemorySpace.tsx b/memhub/apps/web/src/MemorySpace.tsx
index 53cbc36..487bbc0 100644
--- a/memhub/apps/web/src/MemorySpace.tsx
+++ b/memhub/apps/web/src/MemorySpace.tsx
@@ -1049,6 +1049,7 @@ export function MemorySpace({
stale: false,
settledSource: sdkLibrary.settledSource,
settledQuery: sdkLibrary.settledQuery,
+ requestedOffset: sdkLibraryOffset,
unattributedSource: UNATTRIBUTED_SOURCE_ID,
onQuery: setSdkLibraryQuery,
onSource: setSdkLibrarySource,
diff --git a/memhub/apps/web/src/SelectedMemoryDetail.test.tsx b/memhub/apps/web/src/SelectedMemoryDetail.test.tsx
index 4409ff5..a554aa9 100644
--- a/memhub/apps/web/src/SelectedMemoryDetail.test.tsx
+++ b/memhub/apps/web/src/SelectedMemoryDetail.test.tsx
@@ -136,6 +136,70 @@ it("splits long memory text into a bounded heading and readable body", () => {
"selected-memory-body",
);
});
+it("renders an SDK experience from its structured task and solution", () => {
+ const task =
+ "review an uncommitted diff for privacy and correctness blockers";
+ const solution = `Reusable checklist:\n1. Run \`git diff --check\`.\n2. Inspect ${"/very-long-path".repeat(20)}.`;
+ render(
+
,
+ );
+ expect(screen.getByRole("heading", { name: task })).toBeTruthy();
+ const body = document.querySelector(".selected-memory-body");
+ expect(body?.textContent).toBe(solution);
+ expect(
+ screen
+ .getByRole("heading", { name: task })
+ .closest("[data-memory-kind]")
+ ?.getAttribute("data-memory-kind"),
+ ).toBe("experience");
+});
+it("does not apply experience fields to a fact memory", () => {
+ const content = `${"事实".repeat(40)}。补充说明`;
+ render(
+
,
+ );
+ expect(screen.queryByText("不应显示的任务标题")).toBeNull();
+ expect(screen.queryByText("不应显示的经验正文")).toBeNull();
+ expect(
+ document
+ .querySelector(".selected-memory-detail")
+ ?.getAttribute("data-memory-kind"),
+ ).toBe("memory");
+});
it.each([
[
"a late sentence end",
diff --git a/memhub/apps/web/src/SelectedMemoryDetail.tsx b/memhub/apps/web/src/SelectedMemoryDetail.tsx
index 10d2ace..47bf59e 100644
--- a/memhub/apps/web/src/SelectedMemoryDetail.tsx
+++ b/memhub/apps/web/src/SelectedMemoryDetail.tsx
@@ -65,7 +65,7 @@ export function SelectedMemoryDetail({
// (CJK text has none, so it breaks at the limit itself).
const punctuation = memory.content.search(/[。!?!?\n]/);
const space = memory.content.lastIndexOf(" ", HEADING_LIMIT);
- const split =
+ const fallbackSplit =
punctuation > 0 && punctuation < HEADING_LIMIT
? punctuation + 1
: memory.content.length > HEADING_LIMIT
@@ -73,11 +73,26 @@ export function SelectedMemoryDetail({
? space
: HEADING_LIMIT
: -1;
- const heading =
- split > 0 ? memory.content.slice(0, split).trim() : memory.content;
- const body = split > 0 ? memory.content.slice(split).trim() : "";
+ const fallbackHeading =
+ fallbackSplit > 0
+ ? memory.content.slice(0, fallbackSplit).trim()
+ : memory.content;
+ const fallbackBody =
+ fallbackSplit > 0 ? memory.content.slice(fallbackSplit).trim() : "";
+ const experience =
+ detail.sdk_ref?.kind === "experience" &&
+ Boolean(detail.sdk_task?.trim() || detail.sdk_solution?.trim());
+ const heading = experience
+ ? detail.sdk_task?.trim() || fallbackHeading
+ : fallbackHeading;
+ const body = experience
+ ? detail.sdk_solution?.trim() || (detail.sdk_task ? "" : fallbackBody)
+ : fallbackBody;
return (
-
+
{statusLabel}
{!detail.sdk_status &&
diff --git a/memhub/apps/web/src/core-visual.css b/memhub/apps/web/src/core-visual.css
index 18902aa..37ef2c4 100644
--- a/memhub/apps/web/src/core-visual.css
+++ b/memhub/apps/web/src/core-visual.css
@@ -788,12 +788,16 @@ button.rail-button.active::before {
}
.map-list-status {
flex: none;
+ min-height: 28px;
margin: 0;
padding: 4px;
color: var(--tx3);
font-size: 12px;
line-height: 20px;
}
+.map-list-status[data-visible="false"] {
+ visibility: hidden;
+}
.map-filter-notice {
font-size: 12px;
line-height: 1.65;
@@ -879,6 +883,10 @@ button.rail-button.active::before {
background: #fdfefe;
border-color: #c7d3dc;
}
+.map-memory-item:disabled {
+ opacity: 1;
+ cursor: default;
+}
.map-state {
width: 5px;
height: 5px;
@@ -1142,6 +1150,7 @@ button.map-tool {
}
.map-detail-loading {
display: grid;
+ min-height: 280px;
gap: 10px;
padding: 4px 0;
}
@@ -1152,8 +1161,13 @@ button.map-tool {
}
@keyframes map-detail-enter {
from {
- opacity: 0;
- transform: translateY(4px);
+ opacity: 0.35;
+ }
+}
+@media (prefers-reduced-motion: no-preference) {
+ .map-detail-surface,
+ .map-detail-loading {
+ animation: map-detail-enter 140ms cubic-bezier(0, 0, 0.2, 1);
}
}
.map-inspector .wb-detail {
diff --git a/memhub/apps/web/src/selected-memory.css b/memhub/apps/web/src/selected-memory.css
index dd58dcd..80fda64 100644
--- a/memhub/apps/web/src/selected-memory.css
+++ b/memhub/apps/web/src/selected-memory.css
@@ -263,12 +263,14 @@
display: block;
flex: none;
min-width: 0;
+ max-width: 100%;
font-size: 17px;
line-height: 29px;
letter-spacing: normal;
margin: 0;
white-space: pre-wrap;
overflow-wrap: anywhere;
+ word-break: break-word;
}
.selected-memory-source {
margin: 32px 0 22px;