From 77ee827a58f95deff070a4c1b1a759f52350cbd7 Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Wed, 3 Jun 2026 21:37:13 -0700 Subject: [PATCH] release: archivebox 0.9.34rc47 --- .../progressmonitor/progress_monitor.html | 10 ++- archivebox/search/config.py | 2 +- archivebox/tests/test_opencode_agent.py | 84 +++++++++++++------ archivebox/tests/test_search.py | 6 +- etc/package.json | 2 +- pyproject.toml | 8 +- 6 files changed, 78 insertions(+), 34 deletions(-) diff --git a/archivebox/progressmonitor/templates/progressmonitor/progress_monitor.html b/archivebox/progressmonitor/templates/progressmonitor/progress_monitor.html index 2220364c..7ddbb651 100644 --- a/archivebox/progressmonitor/templates/progressmonitor/progress_monitor.html +++ b/archivebox/progressmonitor/templates/progressmonitor/progress_monitor.html @@ -35,6 +35,9 @@ top: 0; z-index: 10; } + #progress-monitor.collapsed .header-bar { + cursor: pointer; + } #progress-monitor .header-left { display: flex; align-items: center; @@ -1884,9 +1887,14 @@ } // Collapse toggle - collapseBtn.addEventListener('click', function() { + collapseBtn.addEventListener('click', function(event) { + event.stopPropagation(); setCollapsedState(!isCollapsed); }); + monitor.querySelector('.header-bar').addEventListener('click', function(event) { + if (event.target.closest('button, a, input, select, textarea, [role="button"]')) return; + if (isCollapsed) setCollapsedState(false); + }); crawlTree.addEventListener('click', function(event) { const actionBtn = event.target.closest('.crawl-action-btn'); diff --git a/archivebox/search/config.py b/archivebox/search/config.py index d7d2de3d..71948a24 100644 --- a/archivebox/search/config.py +++ b/archivebox/search/config.py @@ -62,7 +62,7 @@ def get_search_mode_options(config: dict[str, Any] | None = None, **config_kwarg ] options = [ {"value": "meta", "label": "meta"}, - {"value": "contents", "label": "contents"}, + {"value": "contents", "label": "deep"}, ] if backend_names: options.extend( diff --git a/archivebox/tests/test_opencode_agent.py b/archivebox/tests/test_opencode_agent.py index e0a2ce3a..fcf0f564 100644 --- a/archivebox/tests/test_opencode_agent.py +++ b/archivebox/tests/test_opencode_agent.py @@ -1,5 +1,4 @@ import os -import sqlite3 import pytest import requests @@ -149,6 +148,17 @@ def test_opencode_project_current_is_seeded_data_project(admin_client, tmp_path, monkeypatch.setattr(views, "_machine_config", lambda: {"OPENCODE_ENABLED": True, "OPENCODE_WORKDIR": str(workdir)}) monkeypatch.setattr(views, "_ensure_opencode", lambda settings: (True, "")) + def fake_request(method, url, **kwargs): + assert url == "http://127.0.0.1:4096/project/current" + assert kwargs["params"] == (("directory", str(workdir)),) + upstream = requests.Response() + upstream.status_code = 200 + upstream._content = (f'{{"id":"global","worktree":"{workdir.resolve()}","name":"data"}}').encode() + upstream.headers["Content-Type"] = "application/json" + return upstream + + monkeypatch.setattr(views.requests, "request", fake_request) + response = admin_client.get( f"/admin/agent/opencode/project/current?directory={workdir}", HTTP_HOST=ADMIN_TEST_HOST, @@ -167,6 +177,17 @@ def test_opencode_path_reports_data_as_worktree(admin_client, tmp_path, db, monk monkeypatch.setattr(views, "_machine_config", lambda: {"OPENCODE_ENABLED": True, "OPENCODE_WORKDIR": str(workdir)}) monkeypatch.setattr(views, "_ensure_opencode", lambda settings: (True, "")) + def fake_request(method, url, **kwargs): + assert url == "http://127.0.0.1:4096/path" + assert kwargs["params"] == (("directory", str(workdir)),) + upstream = requests.Response() + upstream.status_code = 200 + upstream._content = (f'{{"directory":"{workdir.resolve()}","worktree":"{workdir.resolve()}"}}').encode() + upstream.headers["Content-Type"] = "application/json" + return upstream + + monkeypatch.setattr(views.requests, "request", fake_request) + response = admin_client.get( f"/admin/agent/opencode/path?directory={workdir}", HTTP_HOST=ADMIN_TEST_HOST, @@ -284,10 +305,9 @@ def test_opencode_starts_without_opening_browser(tmp_path, monkeypatch): return FakeProcess() monkeypatch.setattr(views, "_health", lambda settings: next(health_checks)) - monkeypatch.setattr(views.shutil, "which", lambda binary: "/usr/bin/false") monkeypatch.setattr(views.subprocess, "Popen", fake_popen) - settings = views._settings({"OPENCODE_WORKDIR": str(tmp_path)}) + settings = views._settings({"OPENCODE_WORKDIR": str(tmp_path), "OPENCODE_BINARY": "/usr/bin/false"}) ok, error = views._ensure_opencode(settings) assert ok, error @@ -310,35 +330,47 @@ def test_opencode_state_dir_is_separate_from_workdir(tmp_path): assert settings["config_home"] == workdir / "opencode" / "config" assert settings["data_home"] == workdir / "opencode" / "data" assert settings["state_home"] == workdir / "opencode" / "state" - skill = workdir / "opencode" / "config" / "opencode" / "skills" / "archivebox" / "SKILL.md" - assert skill.exists() - assert f"ArchiveBox collection directory: {workdir.resolve()}" in skill.read_text() + editable_skill = workdir / "opencode" / "SKILL.md" + loaded_skill = workdir / "opencode" / "config" / "opencode" / "skills" / "archivebox" / "SKILL.md" + assert editable_skill.exists() + assert loaded_skill.is_symlink() + assert loaded_skill.resolve() == editable_skill.resolve() + assert f"ArchiveBox collection directory: {workdir.resolve()}" in editable_skill.read_text() -def test_opencode_seeds_global_project_under_embedded_data_dir(tmp_path): +def test_opencode_ensures_default_session_with_public_api(tmp_path, monkeypatch): from abx_plugins.plugins.opencode import views workdir = tmp_path / "data" settings = views._settings({"OPENCODE_WORKDIR": str(workdir)}) - db_path = settings["data_home"] / "opencode" / "opencode.db" - db_path.parent.mkdir(parents=True) - with sqlite3.connect(db_path) as db: - db.execute( - """ - CREATE TABLE project ( - id text PRIMARY KEY, - worktree text NOT NULL, - name text, - time_created integer NOT NULL, - time_updated integer NOT NULL, - sandboxes text NOT NULL - ) - """, - ) + calls = [] - views._ensure_global_project(settings) + def fake_get(url, **kwargs): + calls.append(("GET", url, kwargs)) + response = requests.Response() + response.status_code = 200 + response._content = b"[]" if url.endswith("/session") else b"{}" + return response - with sqlite3.connect(db_path) as db: - row = db.execute("SELECT id, worktree, name, sandboxes FROM project").fetchone() + def fake_post(url, **kwargs): + calls.append(("POST", url, kwargs)) + response = requests.Response() + response.status_code = 200 + response._content = b'{"id":"session"}' + return response - assert row == ("global", str(workdir.resolve()), "data", "[]") + monkeypatch.setattr(views.requests, "get", fake_get) + monkeypatch.setattr(views.requests, "post", fake_post) + + views._ensure_default_session(settings) + + assert calls[0] == ( + "GET", + "http://127.0.0.1:4096/project/current", + {"params": {"directory": str(workdir.resolve())}, "timeout": 30}, + ) + assert calls[1][0:2] == ("GET", "http://127.0.0.1:4096/session") + assert calls[1][2]["params"] == {"directory": str(workdir.resolve()), "roots": "true", "limit": 55} + assert calls[2][0:2] == ("POST", "http://127.0.0.1:4096/session") + assert calls[2][2]["params"] == {"directory": str(workdir.resolve())} + assert calls[2][2]["json"] == {} diff --git a/archivebox/tests/test_search.py b/archivebox/tests/test_search.py index 15da3821..c08f6bc3 100644 --- a/archivebox/tests/test_search.py +++ b/archivebox/tests/test_search.py @@ -128,6 +128,7 @@ def test_search_mode_options_use_canonical_backend_names(monkeypatch): options = get_search_mode_options() + assert {"value": "contents", "label": "deep"} in options assert {"value": "deep:ripgrep", "label": "deep:ripgrep"} in options assert all(not option["label"].startswith("deep: ") for option in options) @@ -176,7 +177,8 @@ class TestAdminSnapshotSearch: assert response.status_code == 200 assert response.context["cl"].search_mode == "deep:ripgrep" assert b'name="search_mode"' in response.content - assert b'value="contents"' in response.content + assert b'' in response.content + assert b">contents<" not in response.content assert b'value="deep:ripgrep"' in response.content assert b">deep:ripgrep<" in response.content @@ -388,6 +390,8 @@ class TestPublicIndexSearch: assert response.status_code == 200 assert response.context["search_mode"] == "deep:ripgrep" assert b'name="search_mode"' in response.content + assert b'' in response.content + assert b">contents<" not in response.content assert b'value="deep:ripgrep"' in response.content assert b">deep:ripgrep<" in response.content diff --git a/etc/package.json b/etc/package.json index f9e84b3e..f0883ec8 100644 --- a/etc/package.json +++ b/etc/package.json @@ -1,6 +1,6 @@ { "name": "archivebox", - "version": "0.9.34rc40", + "version": "0.9.34rc47", "repository": "github:ArchiveBox/ArchiveBox", "license": "MIT", "dependencies": { diff --git a/pyproject.toml b/pyproject.toml index 4647ab44..b83e719c 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "archivebox" -version = "0.9.34rc46" +version = "0.9.34rc47" requires-python = ">=3.13" description = "Self-hosted internet archiving solution." authors = [{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"}] @@ -79,9 +79,9 @@ dependencies = [ ### Extractor dependencies (optional binaries detected at runtime via shutil.which) ### Binary/Package Management "abxbus==2.5.10", # EventBus API - "abxpkg>=1.11.162", # for: detecting, versioning, and installing binaries via apt/brew/pip/npm - "abx-plugins>=1.11.165", # shared ArchiveBox plugin package with Chrome/Puppeteer dependency wiring - "abx-dl>=1.11.165", # shared ArchiveBox downloader package with blocking install preflight + "abxpkg>=1.11.163", # for: detecting, versioning, and installing binaries via apt/brew/pip/npm + "abx-plugins>=1.11.166", # shared ArchiveBox plugin package with Chrome/Puppeteer dependency wiring + "abx-dl>=1.11.166", # shared ArchiveBox downloader package with blocking install preflight ### UUID7 backport for Python <3.14 "uuid7>=0.1.0; python_version < '3.14'", # provides the uuid_extensions module on Python 3.13 ]