feat: Phase 7I + UI refresh (capture form, memory/domain/activity pages, topnav)
Closes three gaps the user surfaced: (1) OpenClaw agents run blind
without AtoCore context, (2) mobile/desktop chats can't be captured
at all, (3) wiki UI hadn't kept up with backend capabilities.
Phase 7I — OpenClaw two-way bridge
- Plugin now calls /context/build on before_agent_start and prepends
the context pack to event.prompt, so whatever LLM runs underneath
(sonnet, opus, codex, local model) answers grounded in AtoCore
knowledge. Captured prompt stays the user's original text; fail-open
with a 5s timeout. Config-gated via injectContext flag.
- Plugin version 0.0.0 → 0.2.0; README rewritten.
UI refresh
- /wiki/capture — paste-to-ingest form for Claude Desktop / web / mobile
/ ChatGPT / other. Goes through normal /interactions pipeline with
client="claude-desktop|claude-web|claude-mobile|chatgpt|other".
Fixes the rotovap/mushroom-on-phone gap.
- /wiki/memories/{id} (Phase 7E) — full memory detail: content, status,
confidence, refs, valid_until, domain_tags (clickable to domain
pages), project link, source chunk, graduated-to-entity link, full
audit trail, related-by-tag neighbors.
- /wiki/domains/{tag} (Phase 7F) — cross-project view: all active
memories with the given tag grouped by project, sorted by count.
Case-insensitive, whitespace-tolerant. Also surfaces graduated
entities carrying the tag.
- /wiki/activity — autonomous-activity timeline feed. Summary chips
by action (created/promoted/merged/superseded/decayed/canonicalized)
and by actor (auto-dedup-tier1, auto-dedup-tier2, confidence-decay,
phase10-auto-promote, transient-to-durable, tag-canon, human-triage).
Answers "what has the brain been doing while I was away?"
- Home refresh: persistent topnav (Home · Activity · Capture · Triage
· Dashboard), "What the brain is doing" snippet above project cards
showing recent autonomous-actor counts, link to full activity.
Tests: +10 (capture page, memory detail + 404, domain cross-project +
empty + tag normalization, activity feed + groupings, home topnav,
superseded-source detail after merge). 440 → 450.
Known next: capture-browser extension for Claude.ai web (bigger
project, deferred); voice/mobile relay (adjacent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
158
tests/test_wiki_pages.py
Normal file
158
tests/test_wiki_pages.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""Tests for the new wiki pages shipped in the UI refresh:
|
||||
- /wiki/capture (7I follow-up)
|
||||
- /wiki/memories/{id} (7E)
|
||||
- /wiki/domains/{tag} (7F)
|
||||
- /wiki/activity (activity feed)
|
||||
- home refresh (topnav + activity snippet)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from atocore.engineering.wiki import (
|
||||
render_activity,
|
||||
render_capture,
|
||||
render_domain,
|
||||
render_homepage,
|
||||
render_memory_detail,
|
||||
)
|
||||
from atocore.engineering.service import init_engineering_schema
|
||||
from atocore.memory.service import create_memory
|
||||
from atocore.models.database import init_db
|
||||
|
||||
|
||||
def _init_all():
|
||||
"""Wiki pages read from both the memory and engineering schemas, so
|
||||
tests need both initialized (the engineering schema is a separate
|
||||
init_engineering_schema() call)."""
|
||||
init_db()
|
||||
init_engineering_schema()
|
||||
|
||||
|
||||
def test_capture_page_renders(tmp_data_dir):
|
||||
_init_all()
|
||||
html = render_capture()
|
||||
assert "Capture a conversation" in html
|
||||
assert "cap-prompt" in html
|
||||
assert "cap-response" in html
|
||||
# Topnav present
|
||||
assert "topnav" in html
|
||||
# Source options for mobile/desktop
|
||||
assert "claude-desktop" in html
|
||||
assert "claude-mobile" in html
|
||||
|
||||
|
||||
def test_memory_detail_renders(tmp_data_dir):
|
||||
_init_all()
|
||||
m = create_memory(
|
||||
"knowledge", "APM uses NX bridge for DXF → STL",
|
||||
project="apm", confidence=0.7, domain_tags=["apm", "nx", "cad"],
|
||||
)
|
||||
html = render_memory_detail(m.id)
|
||||
assert html is not None
|
||||
assert "APM uses NX" in html
|
||||
assert "Audit trail" in html
|
||||
# Tag links go to domain pages
|
||||
assert '/wiki/domains/apm' in html
|
||||
assert '/wiki/domains/nx' in html
|
||||
# Project link present
|
||||
assert '/wiki/projects/apm' in html
|
||||
|
||||
|
||||
def test_memory_detail_404(tmp_data_dir):
|
||||
_init_all()
|
||||
assert render_memory_detail("nonexistent-id") is None
|
||||
|
||||
|
||||
def test_domain_page_lists_memories(tmp_data_dir):
|
||||
_init_all()
|
||||
create_memory("knowledge", "optics fact 1", project="p04-gigabit",
|
||||
domain_tags=["optics"])
|
||||
create_memory("knowledge", "optics fact 2", project="p05-interferometer",
|
||||
domain_tags=["optics", "metrology"])
|
||||
create_memory("knowledge", "other", project="p06-polisher",
|
||||
domain_tags=["firmware"])
|
||||
|
||||
html = render_domain("optics")
|
||||
assert "Domain: <code>optics</code>" in html
|
||||
assert "p04-gigabit" in html
|
||||
assert "p05-interferometer" in html
|
||||
assert "optics fact 1" in html
|
||||
assert "optics fact 2" in html
|
||||
# Unrelated memory should NOT appear
|
||||
assert "other" not in html or "firmware" not in html
|
||||
|
||||
|
||||
def test_domain_page_empty(tmp_data_dir):
|
||||
_init_all()
|
||||
html = render_domain("definitely-not-a-tag")
|
||||
assert "No memories currently carry" in html
|
||||
|
||||
|
||||
def test_domain_page_normalizes_tag(tmp_data_dir):
|
||||
_init_all()
|
||||
create_memory("knowledge", "x", domain_tags=["firmware"])
|
||||
# Case-insensitive
|
||||
assert "firmware" in render_domain("FIRMWARE")
|
||||
# Whitespace tolerant
|
||||
assert "firmware" in render_domain(" firmware ")
|
||||
|
||||
|
||||
def test_activity_feed_renders(tmp_data_dir):
|
||||
_init_all()
|
||||
m = create_memory("knowledge", "activity test")
|
||||
html = render_activity()
|
||||
assert "Activity Feed" in html
|
||||
# The newly-created memory should appear as a "created" event
|
||||
assert "created" in html
|
||||
# Short timestamp format
|
||||
assert m.id[:8] in html
|
||||
|
||||
|
||||
def test_activity_feed_groups_by_action_and_actor(tmp_data_dir):
|
||||
_init_all()
|
||||
for i in range(3):
|
||||
create_memory("knowledge", f"m{i}", actor="test-actor")
|
||||
|
||||
html = render_activity()
|
||||
# Summary row should show "created: 3" or similar
|
||||
assert "created" in html
|
||||
assert "test-actor" in html
|
||||
|
||||
|
||||
def test_homepage_has_topnav_and_activity(tmp_data_dir):
|
||||
_init_all()
|
||||
create_memory("knowledge", "homepage test")
|
||||
html = render_homepage()
|
||||
# Topnav with expected items
|
||||
assert "🏠 Home" in html
|
||||
assert "📡 Activity" in html
|
||||
assert "📥 Capture" in html
|
||||
assert "/wiki/capture" in html
|
||||
assert "/wiki/activity" in html
|
||||
# Activity snippet
|
||||
assert "What the brain is doing" in html
|
||||
|
||||
|
||||
def test_memory_detail_shows_superseded_sources(tmp_data_dir):
|
||||
"""After a merge, sources go to status=superseded. Detail page should
|
||||
still render them."""
|
||||
from atocore.memory.service import (
|
||||
create_merge_candidate, merge_memories,
|
||||
)
|
||||
_init_all()
|
||||
m1 = create_memory("knowledge", "alpha variant 1", project="test")
|
||||
m2 = create_memory("knowledge", "alpha variant 2", project="test")
|
||||
cid = create_merge_candidate(
|
||||
memory_ids=[m1.id, m2.id], similarity=0.9,
|
||||
proposed_content="alpha merged",
|
||||
proposed_memory_type="knowledge", proposed_project="test",
|
||||
)
|
||||
merge_memories(cid, actor="auto-dedup-tier1")
|
||||
|
||||
# Source detail page should render and show the superseded status
|
||||
html1 = render_memory_detail(m1.id)
|
||||
assert html1 is not None
|
||||
assert "superseded" in html1
|
||||
assert "auto-dedup-tier1" in html1 # audit trail shows who merged
|
||||
Reference in New Issue
Block a user