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
]