feat: shared supervisor timeline, story assignment and time-spiral view

Each source version is extracted once for every review scope; events carry spans and projects from their sources, and attention density is counted from messages. After publication, events are assigned to project features and sub-threads (cross-project stories only when enabled), with user rename, merge, remove, move and undo. The story graph gains a lazily loaded Three.js time-spiral view. Includes the 3D concept demo, real-data DeepSeek check scripts and docs.
This commit is contained in:
mesalogo
2026-10-02 15:21:48 +08:00
parent 3d4e9577b5
commit 5dc65457d9
53 changed files with 4051 additions and 83 deletions
@@ -87,7 +87,9 @@ it('enforces exact scope, rejects as_of and stale/changed cursors, and never wri
const before = f.sql.prepare('SELECT total_changes() AS n').get()!.n
f.sql.exec('PRAGMA query_only = ON')
expect(f.db.readStoryGraph('story_graph_search', input, f.other.id)).toMatchObject({ items: [], coverage: { status: 'unknown' } })
expect(f.read('story_graph_search', { ...input, scope: { kind: 'global' } })).toMatchObject({ items: [] })
// Global is the root of the one shared timeline: it sees the project's current events.
const global = f.read('story_graph_search', { ...input, page_size: 50, scope: { kind: 'global' } }) as Page
expect(global.items.map(item => item.object_ref.type)).toEqual(['event'])
expect(() => f.db.readStoryGraph('story_graph_get_context', { object_ref: { type: 'entity', id } }, f.other.id)).toThrow('scope_mismatch')
expect(() => f.read('story_graph_get_context', { object_ref: { type: 'entity', id: 'missing' } })).toThrow('object_not_found')
expect(() => f.db.readStoryGraph('story_graph_search', input)).toThrow('scope_required')