# Zotero Collection Import Modal Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Replace the crowded inline Zotero importer with a modal-based importer that preserves session selection state, keeps rows title-only by default, and shows abstracts through delayed preview. **Architecture:** Keep the existing FastAPI backend and current collection/item endpoints unchanged. Refactor the single-page `index.html` template so the main page only renders an import trigger, while a hidden modal owns the collection tree, item list, sticky actions, and preview popover state. Reuse the existing in-page state object and fetch helpers, extending them for modal lifecycle and delayed preview behavior. **Tech Stack:** FastAPI, inline HTML/CSS/vanilla JavaScript, pytest --- ## File Structure - Modify: `src/zotero_kb/templates/index.html` - Replace the inline importer markup with a single trigger button - Add modal shell, overlay, sticky action bar, and abstract preview popover - Update the in-page JavaScript state and event handlers for modal lifecycle and delayed preview - Modify: `tests/test_ui.py` - Replace assertions for the old inline importer with assertions for the trigger button, modal shell, and preview hooks - Modify: `README.md` - Update the UI walkthrough so it describes opening the import modal instead of using the inline importer ### Task 1: Lock The Modal UI Contract In Tests **Files:** - Modify: `tests/test_ui.py` - Read: `src/zotero_kb/templates/index.html` - [ ] **Step 1: Write the failing test** Replace the existing inline-import assertions with this test body: ```python from zotero_kb.api import create_app from zotero_kb.config import AppConfig def test_index_contains_import_modal_controls(tmp_path) -> None: app = create_app( AppConfig( workspace_dir=tmp_path / "workspace", zotero_data_dir=tmp_path / "zotero", bridge_file=tmp_path / "bridge.json", ) ) for route in app.routes: if getattr(route, "path", None) == "/" and "GET" in getattr(route, "methods", set()): html = route.endpoint() break else: raise AssertionError("GET / route not found") assert 'id="create-project-form"' in html assert 'id="open-import-modal-button"' in html assert 'id="import-modal"' in html assert 'id="close-import-modal-button"' in html assert 'id="modal-collection-tree"' in html assert 'id="modal-collection-items"' in html assert 'id="modal-selected-count"' in html assert 'id="modal-select-descendants-button"' in html assert 'id="modal-clear-selection-button"' in html assert 'id="modal-import-selected-items-button"' in html assert 'id="item-preview-popover"' in html assert 'const ABSTRACT_PREVIEW_DELAY_MS = 3000;' in html assert 'isImportModalOpen: false' in html assert 'id="recommend-form"' in html assert 'id="plan-form"' in html ``` - [ ] **Step 2: Run test to verify it fails** Run: ```bash UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py::test_index_contains_import_modal_controls -v ``` Expected: FAIL because the template still contains `collection-tree`, `collection-items`, and the old inline action ids instead of the modal ids. - [ ] **Step 3: Update the test file** Make `tests/test_ui.py` exactly: ```python from zotero_kb.api import create_app from zotero_kb.config import AppConfig def test_index_contains_import_modal_controls(tmp_path) -> None: app = create_app( AppConfig( workspace_dir=tmp_path / "workspace", zotero_data_dir=tmp_path / "zotero", bridge_file=tmp_path / "bridge.json", ) ) for route in app.routes: if getattr(route, "path", None) == "/" and "GET" in getattr(route, "methods", set()): html = route.endpoint() break else: raise AssertionError("GET / route not found") assert 'id="create-project-form"' in html assert 'id="open-import-modal-button"' in html assert 'id="import-modal"' in html assert 'id="close-import-modal-button"' in html assert 'id="modal-collection-tree"' in html assert 'id="modal-collection-items"' in html assert 'id="modal-selected-count"' in html assert 'id="modal-select-descendants-button"' in html assert 'id="modal-clear-selection-button"' in html assert 'id="modal-import-selected-items-button"' in html assert 'id="item-preview-popover"' in html assert 'const ABSTRACT_PREVIEW_DELAY_MS = 3000;' in html assert 'isImportModalOpen: false' in html assert 'id="recommend-form"' in html assert 'id="plan-form"' in html ``` - [ ] **Step 4: Run test to verify it still fails for the right reason** Run: ```bash UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py::test_index_contains_import_modal_controls -v ``` Expected: FAIL on missing modal ids in `index.html`. - [ ] **Step 5: Commit** ```bash git add tests/test_ui.py git commit -m "test: define modal import ui contract" ``` ### Task 2: Replace The Inline Importer Markup With A Modal Shell **Files:** - Modify: `src/zotero_kb/templates/index.html` - Test: `tests/test_ui.py` - [ ] **Step 1: Write the failing implementation target** In `src/zotero_kb/templates/index.html`, replace the old inline import section: ```html

从 Zotero 导入

按当前 Zotero collection 结构浏览,选择目录后批量导入该目录及子目录的文献。

Collections
Collection Items
已选 0 篇
``` with a trigger plus modal shell: ```html

导入文献

打开子界面浏览 Zotero collection 树,再批量导入到当前项目。

``` - [ ] **Step 2: Run the UI test to verify the markup is still missing** Run: ```bash UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py::test_index_contains_import_modal_controls -v ``` Expected: FAIL until the new markup and ids are present. - [ ] **Step 3: Add the modal CSS and markup** In the `