Files
ATOCore/tests/test_wiki_pages.py
Anto01 6e43cc7383 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>
2026-04-19 10:14:15 -04:00

159 lines
4.9 KiB
Python

"""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