feat(web): clarify Subject Memory status

This commit is contained in:
2026-10-05 13:56:33 +09:00
parent cc97987482
commit fbb09e6597
10 changed files with 460 additions and 120 deletions
@@ -66,7 +66,13 @@ ${"detail-wide-code-".repeat(18)}
<script>alert("not rendered")</script>`;
const subjects = [
subject(representativeSubjectId, "Release coordination", "active", 42),
subject(
representativeSubjectId,
"Release coordination",
"active",
42,
connectedWorker("Release coordination Worker with a deliberately long display name"),
),
subject(emptySubjectId, "Empty ready subject", "active", 0),
subject(staleSubjectId, "Stale surface subject", "active", 19),
subject(failedSubjectId, "Failed surface subject", "active", 8),
@@ -75,7 +81,37 @@ const subjects = [
];
const pagedSubject = subject(pagedSubjectId, "Subject on the next page", "active", 1);
function subject(id: string, role: string, state: "active" | "retired", storeRevision: number) {
function connectedWorker(displayName: string) {
return {
runtime_id: "embedded-worker-runtime",
worker_id: "release-coordination-worker",
host_id: "fixture-host",
display_name: displayName,
label: displayName,
profile: "builtin:companion",
singleton_key: `subjektiv:${representativeSubjectId}`,
tags: [],
workspace: {
visibility: "workspace",
identity: workspaceId,
workspace_id: workspaceId,
},
state: "idle",
last_seen_at: "2026-01-02T03:04:05Z",
pinned: false,
retention_state: "retained",
implementation: { kind: "embedded", display_hint: "Fixture Worker" },
diagnostics: [],
};
}
function subject(
id: string,
role: string,
state: "active" | "retired",
storeRevision: number,
currentWorker?: ReturnType<typeof connectedWorker>,
) {
return {
id,
role,
@@ -83,6 +119,7 @@ function subject(id: string, role: string, state: "active" | "retired", storeRev
store_revision: storeRevision,
created_at: "2026-01-01T00:00:00Z",
updated_at: "2026-01-02T03:04:05Z",
...(currentWorker ? { current_worker: currentWorker } : {}),
};
}
@@ -125,7 +162,7 @@ function memoriesFor(subjectId: string) {
1,
"working_assumption",
"retracted",
"T-672 UNIQUE END MARKER",
"Memory list end marker for scroll validation",
),
];
}
@@ -235,7 +272,7 @@ function memoryDetail(memoryId: string, requestedRevision: number | null) {
evidence_id: "evidence-message-0001",
origin: { kind: "human_input", account_id: "account-fixture" },
evidence_kind: "message",
label: "T-672 product direction",
label: "Memory information design source",
summary: "Bounded source reference retained with the committed revision.",
}],
source_refs_total: 1,
@@ -16,7 +16,7 @@ const memoryId =
const subjectPageCursor = "fixture-subject-page-2";
const createInteractionArtifacts = join(
repositoryRoot,
"target/web-ux/t-689-browser-interactions",
"target/web-ux/t-692-browser-interactions",
);
async function freePort(): Promise<number> {
@@ -51,7 +51,7 @@ async function markerIsInsideMain(page: Page): Promise<boolean> {
const document = (globalThis as any).document;
const main = document.querySelector("main");
const marker = [...document.querySelectorAll("h3")].find((heading) =>
heading.textContent === "T-672 UNIQUE END MARKER"
heading.textContent === "Memory list end marker for scroll validation"
);
if (!main || !marker) return false;
const mainRect = main.getBoundingClientRect();
@@ -262,7 +262,7 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
assertEquals(await page.locator('a[href="https://example.com"]').count(), 1);
assertEquals(await page.locator('a[href^="javascript:"]').count(), 0);
assertEquals(
await page.getByRole("article", { name: "Resident surface" }).locator("img, script")
await page.getByRole("article", { name: "Resident context" }).locator("img, script")
.count(),
0,
);
@@ -282,7 +282,7 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
await page.locator("main").hover();
await page.mouse.wheel(0, 100_000);
} else if (scenario.label === "keyboard") {
await page.getByText("Surface provenance", { exact: true }).focus();
await page.getByText("Surface sources and diagnostics", { exact: true }).focus();
await page.keyboard.press("End");
} else {
await swipeMainToEnd(page);
@@ -313,17 +313,17 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
level: 1,
}).waitFor();
assertEquals(await detailPage.getByText("candidate-release-decision-0001").count(), 1);
assertEquals(await detailPage.getByText("T-672 product direction").count(), 1);
assertEquals(await detailPage.getByText("Memory information design source").count(), 1);
assertEquals(await detailPage.getByText("memory-derived-source-0002").count(), 1);
assertEquals(await detailPage.getByRole("heading", { name: "Revision history" }).count(), 1);
assertEquals(await detailPage.getByText("Revision 3", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Revision 2", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Revision 1", { exact: true }).count(), 0);
assertEquals(await detailPage.getByText("Memory revision 3", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Memory revision 2", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Memory revision 1", { exact: true }).count(), 0);
await detailPage.getByRole("navigation", { name: "Revision history pages" }).getByRole(
"link",
{ name: "Next page →" },
).click();
await detailPage.getByText("Revision 1", { exact: true }).waitFor();
await detailPage.getByText("Memory revision 1", { exact: true }).waitFor();
assert(new URL(detailPage.url()).searchParams.has("revision_cursor"));
assertEquals(
await detailPage.getByRole("navigation", { name: "Revision history pages" }).getByRole(
@@ -352,6 +352,15 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
const paginationPage = await paginationContext.newPage();
await paginationPage.goto(`${baseUrl}/w/${workspaceId}/memory`);
await paginationPage.getByRole("heading", { name: "Subjects", level: 1 }).waitFor();
assertEquals(
await paginationPage.getByText(
"Release coordination Worker with a deliberately long display name",
{ exact: true },
).count(),
1,
);
assertEquals(await paginationPage.getByText("Store revision", { exact: true }).count(), 0);
assert((await paginationPage.getByText("Not connected", { exact: true }).count()) > 0);
assertEquals(await paginationPage.getByText("Subject on the next page").count(), 0);
await paginationPage.getByRole("navigation", { name: "Subject pages" }).getByRole(
"link",
@@ -451,10 +460,12 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
await createPage.locator("code.subject-id", { hasText: "created-subject-0001" }).count(),
1,
);
const currentWorker = createPage.locator(".subject-facts div").filter({
hasText: "Current worker",
});
assert((await currentWorker.textContent())?.includes("None"));
const currentWorker = createPage.getByText("Worker connection", { exact: true }).locator(
"..",
);
assert((await currentWorker.textContent())?.includes("Not connected"));
await createPage.getByText("Resident context has not been generated.").waitFor();
await createPage.getByText("No committed Memories yet.").waitFor();
assertEquals(mutationPaths, [
`/api/w/${workspaceId}/subjektiv/subjects`,
`/api/w/${workspaceId}/subjektiv/subjects`,
@@ -472,21 +483,29 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
const stateCases = [
{
id: "empty-subject",
text: "Resident surface is ready and empty.",
text: "Resident context is current but empty.",
role: "status" as const,
},
{
id: "stale-subject",
text: "Resident context needs to be refreshed.",
role: "status" as const,
},
{ id: "stale-subject", text: "Resident surface is stale.", role: "status" as const },
{
id: "failed-subject",
text: "Resident surface generation failed.",
text: "Resident context generation failed.",
role: "alert" as const,
},
{
id: "ungenerated-subject",
text: "Resident surface has not been generated.",
text: "Resident context has not been generated.",
role: "status" as const,
},
{ id: "error-subject", text: "Resident surface unavailable.", role: "alert" as const },
{
id: "error-subject",
text: "Resident context status unavailable.",
role: "alert" as const,
},
];
for (const stateCase of stateCases) {
const page = await stateContext.newPage();
@@ -494,8 +513,14 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
const state = page.getByRole(stateCase.role).filter({ hasText: stateCase.text });
await state.waitFor();
assert((await state.textContent())?.includes(stateCase.text));
if (stateCase.id === "stale-subject") {
assertEquals(await page.getByText("A resolved stale-surface lesson").count(), 1);
}
if (stateCase.id === "failed-subject") {
assertEquals(await page.getByText("A retracted failed-surface constraint").count(), 1);
}
if (stateCase.id !== "empty-subject") {
assertEquals(await page.getByRole("article", { name: "Resident surface" }).count(), 0);
assertEquals(await page.getByRole("article", { name: "Resident context" }).count(), 0);
}
await assertNoPageWideOverflow(page);
await page.close();
@@ -122,9 +122,33 @@
"dataState": "Ready generated Markdown surface with long content, unsafe Markdown, wide table/code, provenance refs, and three lifecycle states.",
"ready": {
"kind": "selector",
"selector": "[data-memory-view='subject'][data-memory-state='ready'] article[aria-label='Resident surface']"
"selector": "[data-memory-view='subject'][data-memory-state='ready'] article[aria-label='Resident context']"
},
"capturePoints": [{ "id": "initial", "label": "Ready subject" }]
"capturePoints": [
{ "id": "initial", "label": "Ready subject" },
{
"id": "subject-diagnostics",
"label": "Subject diagnostics",
"interaction": [
{ "action": "click", "selector": "details.subject-technical-details summary" }
],
"ready": {
"kind": "selector",
"selector": "details.subject-technical-details[open]"
}
},
{
"id": "surface-diagnostics",
"label": "Surface sources and diagnostics",
"interaction": [
{ "action": "click", "selector": "details.surface-details summary" }
],
"ready": {
"kind": "selector",
"selector": "details.surface-details[open]"
}
}
]
},
{
"id": "detail",
@@ -150,6 +174,18 @@
},
"capturePoints": [{ "id": "initial", "label": "Ready empty" }]
},
{
"id": "ungenerated",
"label": "Ungenerated subject context",
"path": "/w/memory-review/memory/ungenerated-subject",
"goal": "Distinguish a Subject with no generated resident context from lost committed Memory.",
"dataState": "Ungenerated resident context and a successful empty committed Memory list.",
"ready": {
"kind": "selector",
"selector": "[data-memory-view='subject'][data-memory-state='ungenerated']"
},
"capturePoints": [{ "id": "initial", "label": "Not generated" }]
},
{
"id": "stale",
"label": "Stale subject surface",
@@ -122,9 +122,33 @@
"dataState": "Ready generated Markdown surface with long content, unsafe Markdown, wide table/code, provenance refs, and three lifecycle states.",
"ready": {
"kind": "selector",
"selector": "[data-memory-view='subject'][data-memory-state='ready'] article[aria-label='Resident surface']"
"selector": "[data-memory-view='subject'][data-memory-state='ready'] article[aria-label='Resident context']"
},
"capturePoints": [{ "id": "initial", "label": "Ready subject" }]
"capturePoints": [
{ "id": "initial", "label": "Ready subject" },
{
"id": "subject-diagnostics",
"label": "Subject diagnostics",
"interaction": [
{ "action": "click", "selector": "details.subject-technical-details summary" }
],
"ready": {
"kind": "selector",
"selector": "details.subject-technical-details[open]"
}
},
{
"id": "surface-diagnostics",
"label": "Surface sources and diagnostics",
"interaction": [
{ "action": "click", "selector": "details.surface-details summary" }
],
"ready": {
"kind": "selector",
"selector": "details.surface-details[open]"
}
}
]
},
{
"id": "detail",
@@ -150,6 +174,18 @@
},
"capturePoints": [{ "id": "initial", "label": "Ready empty" }]
},
{
"id": "ungenerated",
"label": "Ungenerated subject context",
"path": "/w/memory-review/memory/ungenerated-subject",
"goal": "Distinguish a Subject with no generated resident context from lost committed Memory.",
"dataState": "Ungenerated resident context and a successful empty committed Memory list.",
"ready": {
"kind": "selector",
"selector": "[data-memory-view='subject'][data-memory-state='ungenerated']"
},
"capturePoints": [{ "id": "initial", "label": "Not generated" }]
},
{
"id": "stale",
"label": "Stale subject surface",