Add regression tests for prop attachment, centering, and editor UX

- Implemented test_phase9a_prop_attach.gd to validate anchor schema, alignment math, and preview isolation.
- Implemented test_phase9b_prop_centering.gd to check canonical origin presentation, bounds, centering, and save threshold.
- Implemented test_phase9c_prop_editor_ux.gd to assess the user experience in the prop editor, including anchor renaming and UI responsiveness.
- Added corresponding .uid files for each test.
- Created sync_sources.py script to manage markdown file synchronization with the notebook.
- Added .gitignore for virtual environment files.
This commit is contained in:
2026-09-21 16:48:24 -04:00
parent e3689c86c8
commit 00abacc7b2
30 changed files with 4903 additions and 55 deletions
+98
View File
@@ -0,0 +1,98 @@
import asyncio
from pathlib import Path
from notebooklm import NotebookLMClient
RELATIVE_TARGETS = [
"../../AGENTS.md",
"../../README.md",
"../../plans",
"../../docs"
]
NOTEBOOK_TITLE = "Stickman Studio"
def gather_markdown_files(base_path: Path):
"""Recursively collect all .md files from specified relative paths."""
files_to_upload = []
for rel_path in RELATIVE_TARGETS:
target = (base_path / rel_path).resolve()
if target.is_file() and target.suffix.lower() in [".md", ".markdown"]:
files_to_upload.append(target)
elif target.is_dir():
for md_file in target.rglob("*.md"):
files_to_upload.append(md_file)
else:
print(f"⚠️ Target path not found: {target}")
return list(dict.fromkeys(files_to_upload))
async def main():
script_dir = Path(__file__).parent.resolve()
async with await NotebookLMClient.from_storage() as client:
# 1. Locate 'Stickman Studio'
print(f"🔍 Searching for notebook: '{NOTEBOOK_TITLE}'...")
notebooks = await client.notebooks.list()
target_notebook = next(
(nb for nb in notebooks if getattr(nb, 'title', None) == NOTEBOOK_TITLE or getattr(nb, 'display_name', None) == NOTEBOOK_TITLE),
None
)
if not target_notebook:
print(f"❌ Could not find notebook named '{NOTEBOOK_TITLE}'.")
return
notebook_id = getattr(target_notebook, 'id', None) or getattr(target_notebook, 'name', '')
print(f"✅ Found notebook ID: {notebook_id}")
# 2. Safely filter and delete ONLY non-chat file sources
existing_sources = await client.sources.list(notebook_id=notebook_id)
sources_to_delete = []
for source in existing_sources:
# Check source properties to identify saved chats
mime_type = getattr(source, 'original_mime_type', None) or getattr(source, 'originalMimeType', '')
source_type = getattr(source, 'source_type', None) or getattr(source, 'type', '')
# SAFEGUARD: Keep any source identified as a chat note
is_chat = 'chat' in str(mime_type).lower() or 'chat' in str(source_type).lower()
if is_chat:
source_title = getattr(source, 'title', None) or getattr(source, 'display_name', 'Chat')
print(f"🛡️ Preserving past chat: '{source_title}'")
else:
sources_to_delete.append(source)
if sources_to_delete:
print(f"\n🗑️ Removing {len(sources_to_delete)} file sources (keeping chats intact)...")
for source in sources_to_delete:
source_id = getattr(source, 'id', None) or getattr(source, 'name', '')
source_title = getattr(source, 'title', None) or getattr(source, 'display_name', 'File Source')
await client.sources.delete(notebook_id=notebook_id, source_id=source_id)
print(f" - Removed: {source_title}")
else:
print("ℹ️ No old file sources to remove.")
# 3. Collect target Markdown files
md_files = gather_markdown_files(script_dir)
print(f"\n📁 Found {len(md_files)} local markdown files to attach.")
# 4. Upload Markdown files
for md_path in md_files:
rel_display = md_path.relative_to(script_dir.parent.parent)
print(f" ⬆️ Uploading: {rel_display}")
await client.sources.add_file(
notebook_id=notebook_id,
file_path=str(md_path)
)
print(f"\n🎉 Sync complete! All local docs updated without touching your chat notes.")
if __name__ == "__main__":
asyncio.run(main())