refactor(session-reference): label discovery from projections alone

A title now comes from an attached session's live projection cut or a cold
one's durable checkpoint, and from nothing else. Attachment is decided by the
session store at read time, so a session that attached after the listing is no
longer answered from a checkpoint its log has moved past — the stale-title case
`api-session.list` already handles this way.

The log fold and its per-log memo are gone. Folding one title costs a whole
log, and this call sits under every keystroke; a session no projection answers
for is labeled by its id and regains its title the first time it is opened.

`lib` leaves the default exclusions: Ruby gems and many npm packages keep
sources there, and the miss would be silent and total. A traversal whose root
is unreadable now rejects instead of publishing an empty index over entries
that are still good, which is what the stale-while-revalidate path claimed but
could not do while every readdir error was swallowed. A drill marks the menu
drilled only when its edit actually reached the draft.

Refs #3154
Refs #3180
This commit is contained in:
Yichen Jiang
2026-08-27 15:21:08 +08:00
parent db14361372
commit 8e9da9debf
22 changed files with 248 additions and 380 deletions
@@ -6,5 +6,5 @@
- img
- option "reference.txt"
- text: Sessions
- option "Reference order target {{cwd}} · {{age}}"
- option "Research notes {{cwd}} · {{age}}"
- option "reference-order-target-session {{cwd}} · {{age}}"
- option "reference-source-session {{cwd}} · {{age}}"
+14 -9
View File
@@ -157,7 +157,12 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
expect(snapshot).toContain('Sessions')
expect(snapshot).not.toContain('text: reference Files & folders')
expect(snapshot).toContain('reference.txt')
expect(snapshot).toContain('Research notes')
// A seed reaches disk as a log alone, and the Host labels a session from
// its projections: no checkpoint, so the row is its id. The fixture's own
// title (`Research notes`) is unreachable here by construction, and the
// package suite owns the titled paths.
expect(snapshot).toContain(SOURCE_SESSION_ID)
expect(snapshot).not.toContain('Research notes')
expect(snapshot).not.toContain('text: Subagents')
await input.fill('@reference')
@@ -170,12 +175,12 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
await expect.poll(() => fileReference.locator('svg').count()).toBe(1)
await expect.poll(() => input.textContent()).toBe('reference.txt ')
await input.fill('@Research')
await menu.getByRole('option', { name: /Research notes/ }).click()
await input.fill('@reference-source')
await menu.getByRole('option', { name: new RegExp(SOURCE_SESSION_ID) }).click()
const sessionReference = page.locator('[data-composer-chip]').last()
await expect.poll(() => sessionReference.textContent()).toBe('Research notes')
await expect.poll(() => sessionReference.textContent()).toBe(SOURCE_SESSION_ID)
await expect.poll(() => sessionReference.locator('svg').count()).toBe(1)
await expect.poll(() => input.textContent()).toBe('Research notes ')
await expect.poll(() => input.textContent()).toBe(`${SOURCE_SESSION_ID} `)
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
@@ -195,16 +200,16 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through
await input.click()
await page.keyboard.press('ControlOrMeta+A')
await page.keyboard.press('ArrowLeft')
await page.keyboard.type('@Research')
await menu.getByRole('option', { name: /Research notes/ }).click()
await page.keyboard.type('@reference-source')
await menu.getByRole('option', { name: new RegExp(SOURCE_SESSION_ID) }).click()
// Both chips survive the boundary insert: the session chip lands ahead of
// the intact file chip.
const chips = input.locator('[data-composer-chip]')
await expect.poll(() => chips.count()).toBe(2)
await expect.poll(() => chips.first().textContent()).toBe('Research notes')
await expect.poll(() => chips.first().textContent()).toBe(SOURCE_SESSION_ID)
await expect.poll(() => chips.last().textContent()).toBe('reference.txt')
await expect.poll(() => input.textContent()).toBe('Research notes reference.txt ')
await expect.poll(() => input.textContent()).toBe(`${SOURCE_SESSION_ID} reference.txt `)
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])