zotero-kb/docs/superpowers/plans/2026-04-16-import-window-responsive.md
Saberlve cf7e2feb19 feat: Implement project management features including rename and delete functionality
- Added project renaming capability in the workspace with appropriate API endpoints.
- Implemented project deletion functionality, ensuring project directories are removed.
- Updated UI to support project renaming and deletion, including inline editing and confirmation dialogs.
- Enhanced batch generation feature for project items with selection and progress tracking.
- Added tests for project renaming and deletion to ensure functionality and integrity.
2026-04-21 16:55:42 +08:00

255 lines
8.9 KiB
Markdown

# Import Window Responsive 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:** Make the floating import window recompute a viewport-appropriate size and position every time it opens, and stack collection/item panes vertically on narrower viewports.
**Architecture:** Keep the existing floating-window shell and collection importer flow, but replace reopen-time geometry reuse with viewport-derived geometry helpers. Add a narrow-layout breakpoint in the template CSS and a resize handler that constrains normal-mode geometry without overriding minimized or maximized state.
**Tech Stack:** FastAPI template rendering, inline HTML/CSS/JavaScript, pytest, Node syntax checking
---
### Task 1: Update UI Contract Tests For Responsive Window Behavior
**Files:**
- Modify: `tests/test_ui.py`
- Test: `tests/test_ui.py`
- [ ] **Step 1: Write the failing test**
```python
def test_index_has_responsive_import_window_geometry_helpers(tmp_path) -> None:
html = _get_index_html(tmp_path)
assert "function computeResponsiveWindowRect()" in html
assert "function applyResponsiveWindowRect()" in html
assert "function syncWindowToViewport()" in html
assert "window.addEventListener(\"resize\"" in html
```
```python
def test_index_has_responsive_import_window_layout_rules(tmp_path) -> None:
html = _get_index_html(tmp_path)
assert ".window-pane.items" in html
assert "@media (max-width: 980px)" in html
assert ".window-body {" in html
assert "grid-template-columns: 1fr;" in html
assert "border-bottom: 1px solid var(--line);" in html
```
- [ ] **Step 2: Run test to verify it fails**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py -q`
Expected: FAIL because the new responsive geometry helper names are not present yet.
- [ ] **Step 3: Keep the existing syntax guard test**
Do not remove `test_index_inline_script_is_valid_javascript`; it remains the regression guard for the inline script.
- [ ] **Step 4: Run test to verify the contract file still parses**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py::test_index_inline_script_is_valid_javascript -q`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add tests/test_ui.py
git commit -m "test: define responsive import window contract"
```
### Task 2: Implement Viewport-Derived Window Geometry
**Files:**
- Modify: `src/zotero_kb/templates/index.html`
- Test: `tests/test_ui.py`
- [ ] **Step 1: Write the failing test**
Use the Task 1 tests as the failing contract. Do not add production code first.
- [ ] **Step 2: Run the failing test**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py -q`
Expected: FAIL with missing responsive helper function names or missing layout assertions.
- [ ] **Step 3: Write minimal implementation**
Add responsive geometry helpers to the inline script in `src/zotero_kb/templates/index.html`:
```javascript
function computeResponsiveWindowRect() {
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const margin = viewportWidth <= 980 ? 8 : 24;
const minWidth = viewportWidth <= 980 ? 320 : 640;
const minHeight = viewportHeight <= 980 ? 420 : 480;
const maxWidth = Math.max(minWidth, viewportWidth - margin * 2);
const maxHeight = Math.max(minHeight, viewportHeight - margin * 2);
const width = Math.min(maxWidth, Math.max(minWidth, Math.round(viewportWidth * 0.88)));
const height = Math.min(maxHeight, Math.max(minHeight, Math.round(viewportHeight * 0.82)));
const left = Math.max(margin, Math.round((viewportWidth - width) / 2));
const top = Math.max(margin, Math.round((viewportHeight - height) / 2));
return { width, height, left, top };
}
function applyResponsiveWindowRect() {
const fw = elements.importWindow;
if (!fw || state.isWindowMinimized || state.isWindowMaximized) return;
const rect = computeResponsiveWindowRect();
fw.style.width = `${rect.width}px`;
fw.style.height = `${rect.height}px`;
fw.style.left = `${rect.left}px`;
fw.style.top = `${rect.top}px`;
state.windowWidth = rect.width;
state.windowHeight = rect.height;
state.windowX = rect.left;
state.windowY = rect.top;
}
function syncWindowToViewport() {
const fw = elements.importWindow;
if (!fw || state.isWindowMinimized || state.isWindowMaximized) return;
const margin = window.innerWidth <= 980 ? 8 : 24;
const rect = computeResponsiveWindowRect();
const currentWidth = parseFloat(fw.style.width) || rect.width;
const currentHeight = parseFloat(fw.style.height) || rect.height;
const width = Math.min(currentWidth, rect.width);
const height = Math.min(currentHeight, rect.height);
const maxLeft = Math.max(margin, window.innerWidth - width - margin);
const maxTop = Math.max(margin, window.innerHeight - height - margin);
const left = Math.min(Math.max(parseFloat(fw.style.left) || rect.left, margin), maxLeft);
const top = Math.min(Math.max(parseFloat(fw.style.top) || rect.top, margin), maxTop);
fw.style.width = `${width}px`;
fw.style.height = `${height}px`;
fw.style.left = `${left}px`;
fw.style.top = `${top}px`;
state.windowWidth = width;
state.windowHeight = height;
state.windowX = left;
state.windowY = top;
}
```
Update `openImportWindow()` to call `applyResponsiveWindowRect()` after `initWindowFromSession()` when the window is not minimized or maximized.
- [ ] **Step 4: Run the focused tests**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py -q`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add src/zotero_kb/templates/index.html tests/test_ui.py
git commit -m "feat: derive import window geometry from viewport"
```
### Task 3: Add Responsive Layout Switching And Resize Wiring
**Files:**
- Modify: `src/zotero_kb/templates/index.html`
- Test: `tests/test_ui.py`
- [ ] **Step 1: Write the failing test**
Use the layout assertions from Task 1 as the failing contract. No new production code before running them.
- [ ] **Step 2: Run the failing test**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py -q`
Expected: FAIL if the resize listener or stacked layout rules are still missing.
- [ ] **Step 3: Write minimal implementation**
Extend the existing media query and event wiring in `src/zotero_kb/templates/index.html`:
```css
@media (max-width: 980px) {
.window-frame {
min-width: 0;
width: calc(100vw - 1rem);
height: calc(100vh - 1rem);
left: 0.5rem;
top: 0.5rem;
}
.window-toolbar,
.window-toolbar-actions {
align-items: stretch;
}
.window-body {
grid-template-columns: 1fr;
}
.window-pane.collections {
border-right: 0;
border-bottom: 1px solid var(--line);
}
}
```
Wire the resize handler:
```javascript
window.addEventListener("resize", () => {
syncWindowToViewport();
});
```
- [ ] **Step 4: Run the focused tests**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py -q`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add src/zotero_kb/templates/index.html tests/test_ui.py
git commit -m "feat: make import window layout responsive"
```
### Task 4: End-To-End Verification
**Files:**
- Modify: none
- Test: `tests/test_ui.py`, `tests/test_api.py`
- [ ] **Step 1: Run the UI test suite**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py -q`
Expected: PASS with all UI contract tests green.
- [ ] **Step 2: Run the API regression suite**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_api.py -q`
Expected: PASS with all API tests green.
- [ ] **Step 3: Run fresh JS syntax verification through pytest**
Run: `UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_ui.py::test_index_inline_script_is_valid_javascript -q`
Expected: PASS
- [ ] **Step 4: Manual verification**
Run the app:
```bash
UV_CACHE_DIR=/tmp/uv-cache uv run python main.py
```
Manual checks:
- open the import window at one browser zoom level and note the size
- close it, change browser zoom, reopen it, confirm it recenters and resizes
- shrink the viewport below the narrow breakpoint, reopen it, confirm the panes stack vertically
- maximize and minimize the window, confirm those modes still behave correctly
- [ ] **Step 5: Commit**
```bash
git add src/zotero_kb/templates/index.html tests/test_ui.py docs/superpowers/specs/2026-04-16-import-window-responsive-design.md docs/superpowers/plans/2026-04-16-import-window-responsive.md
git commit -m "feat: make import window adapt to viewport changes"
```