# Zotero Collection Import Fullscreen 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:** Convert the current centered import modal into a full-screen modal with left-side collection browsing, right-side direct-item selection, top-bar actions, and delayed abstract preview.
**Architecture:** Keep the existing FastAPI backend and reuse the current collection tree and item import endpoints. Refactor the single-page `index.html` template so the import flow becomes a full-screen modal with a fixed top bar, a left collection tree, and a right item list that only loads direct collection items (`include_descendants=false`). Preserve session-scoped selection state across collection switches and modal reopen, but remove collection-level bulk-select behavior.
**Tech Stack:** FastAPI, inline HTML/CSS/vanilla JavaScript, pytest
---
## File Structure
- Modify: `src/zotero_kb/templates/index.html`
- Convert the current centered modal into a full-screen modal shell
- Remove collection-level bulk-select behavior
- Change collection item loading to direct-only
- Update item rows to `title + year`
- Add delayed preview behavior and mobile details-button fallback
- Modify: `tests/test_ui.py`
- Update UI contract assertions for the new full-screen modal structure and preview hooks
- Modify: `README.md`
- Update the user walkthrough to describe the full-screen modal and one-by-one item selection
### Task 1: Update The UI Contract For The Fullscreen Modal
**Files:**
- Modify: `tests/test_ui.py`
- Read: `src/zotero_kb/templates/index.html`
- [ ] **Step 1: Write the failing assertions**
Extend `test_index_contains_import_modal_controls` so it asserts the new full-screen modal hooks and removes the old collection-bulk-select assumption.
Use this function body:
```python
def test_index_contains_import_modal_controls(tmp_path) -> None:
html = _get_index_html(tmp_path)
assert 'id="open-import-modal-button"' in html
assert 'id="import-modal"' in html
assert 'id="close-import-modal-button"' in html
assert 'id="import-modal-project-label"' in html
assert 'id="modal-selected-count"' in html
assert 'id="modal-clear-selection-button"' in html
assert 'id="modal-import-selected-items-button"' in html
assert 'id="modal-collection-tree"' in html
assert 'id="modal-collection-items"' in html
assert 'id="item-preview-popover"' in html
assert 'const ABSTRACT_PREVIEW_DELAY_MS = 3000;' in html
assert 'function scheduleAbstractPreview(item, target)' in html
assert 'function cancelAbstractPreview()' in html
assert 'function showAbstractPreview(item, target)' in html
assert 'details-button' in html
assert 'isImportModalOpen: false' in html
assert 'document.body.classList.toggle("modal-open"' in html
assert 'id="modal-select-descendants-button"' not in html
```
- [ ] **Step 2: Run the targeted UI test and confirm 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 current template still contains the old top-bar/selection contract and may still include `modal-select-descendants-button`.
- [ ] **Step 3: Update the test file**
Edit `tests/test_ui.py` so the full file becomes:
```python
from zotero_kb.api import create_app
from zotero_kb.config import AppConfig
def _get_index_html(tmp_path) -> str:
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()):
return route.endpoint()
raise AssertionError("GET / route not found")
def test_index_contains_base_page_forms(tmp_path) -> None:
html = _get_index_html(tmp_path)
assert 'id="create-project-form"' in html
assert 'id="recommend-form"' in html
assert 'id="plan-form"' in html
def test_index_contains_import_modal_controls(tmp_path) -> None:
html = _get_index_html(tmp_path)
assert 'id="open-import-modal-button"' in html
assert 'id="import-modal"' in html
assert 'id="close-import-modal-button"' in html
assert 'id="import-modal-project-label"' in html
assert 'id="modal-selected-count"' in html
assert 'id="modal-clear-selection-button"' in html
assert 'id="modal-import-selected-items-button"' in html
assert 'id="modal-collection-tree"' in html
assert 'id="modal-collection-items"' in html
assert 'id="item-preview-popover"' in html
assert 'const ABSTRACT_PREVIEW_DELAY_MS = 3000;' in html
assert 'function scheduleAbstractPreview(item, target)' in html
assert 'function cancelAbstractPreview()' in html
assert 'function showAbstractPreview(item, target)' in html
assert 'details-button' in html
assert 'isImportModalOpen: false' in html
assert 'document.body.classList.toggle("modal-open"' in html
assert 'id="modal-select-descendants-button"' not in html
```
- [ ] **Step 4: Run the targeted UI test again**
Run:
```bash
UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py::test_index_contains_import_modal_controls -v
```
Expected: still FAIL, now because the current template does not yet match the new full-screen modal contract.
- [ ] **Step 5: Commit**
```bash
git add tests/test_ui.py
git commit -m "test: define fullscreen import modal contract"
```
### Task 2: Convert The Centered Modal To A Fullscreen Modal Shell
**Files:**
- Modify: `src/zotero_kb/templates/index.html`
- Test: `tests/test_ui.py`
- [ ] **Step 1: Replace the current modal container styles**
In `src/zotero_kb/templates/index.html`, replace the current centered-card modal CSS:
```css
.modal-card {
position: relative;
z-index: 1;
width: min(1100px, calc(100vw - 2rem));
max-height: calc(100vh - 2rem);
margin: 1rem auto;
display: grid;
grid-template-rows: auto auto 1fr auto;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 24px;
box-shadow: 0 24px 60px rgba(35, 31, 21, 0.2);
overflow: hidden;
}
```
with a full-screen shell:
```css
.modal-card {
position: relative;
z-index: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto 1fr;
background: var(--surface);
border: 0;
border-radius: 0;
box-shadow: none;
overflow: hidden;
}
.modal-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--line);
background: rgba(255, 253, 248, 0.98);
}
.modal-toolbar-actions {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.modal-body {
display: grid;
grid-template-columns: 22rem minmax(0, 1fr);
min-height: 0;
}
.modal-pane {
min-height: 0;
padding: 1rem 1.25rem;
}
.modal-pane.collections {
border-right: 1px solid var(--line);
}
```
- [ ] **Step 2: Replace the modal markup**
Replace the current modal header/footer structure:
```html