mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
merge: integrate dashboard-refine (deep-finalize + slug-freeze) into skill-refine
Resolved conflicts: - styles.css: take dashboard-refine (more refined CSS) - SKILL.md, deep-reading.md, paper-resolution.md: take skill-refine (modular skill redesign)
This commit is contained in:
commit
5138a70645
12 changed files with 1172 additions and 43 deletions
398
docs/superpowers/plans/2026-05-10-dashboard-ui-refine.md
Normal file
398
docs/superpowers/plans/2026-05-10-dashboard-ui-refine.md
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
# Dashboard UI Refine 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:** Refine the PaperForge Obsidian dashboard so `paper`, `collection`, and `global` modes feel stable, readable, and visually intentional while fixing the current layout and interaction defects.
|
||||
|
||||
**Architecture:** Keep the existing three-mode dashboard structure in `paperforge/plugin/main.js`, but reduce refresh-related state loss and clean up the dashboard CSS into one authoritative visual system in `paperforge/plugin/styles.css`. Preserve Obsidian structural tokens for surfaces/text/borders, then layer a muted PaperForge accent system on top for badges, metrics, pills, and primary actions.
|
||||
|
||||
**Tech Stack:** Obsidian plugin DOM API, CommonJS plugin runtime, Vitest, CSS custom properties, Obsidian theme variables.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Modify: `paperforge/plugin/main.js`
|
||||
- Dashboard leaf activation / refresh behavior
|
||||
- Paper-mode transient state preservation
|
||||
- Small DOM/class adjustments only if required by the new visual system
|
||||
- Modify: `paperforge/plugin/styles.css`
|
||||
- Consolidate duplicate dashboard CSS
|
||||
- Add stable scroll / bottom-safe-area behavior
|
||||
- Implement the `Quiet Research Desk` palette and hierarchy rules
|
||||
- Modify: `docs/ux-contract.md`
|
||||
- Update workflow contract expectations for `paper`, `collection`, and `global`
|
||||
- Test: `paperforge/plugin/tests/*.mjs`
|
||||
- Keep existing suite green
|
||||
- Add a focused regression test only if a new pure helper or testable branch is extracted
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Lock Scroll Stability and Preserve the Existing Refresh Fix
|
||||
|
||||
**Files:**
|
||||
- Modify: `paperforge/plugin/main.js`
|
||||
- Modify: `paperforge/plugin/styles.css`
|
||||
- Test: `paperforge/plugin/tests/*.mjs`
|
||||
|
||||
- [ ] **Step 1: Add a regression note to the plan execution log**
|
||||
|
||||
Record the two interaction defects at the top of your execution scratchpad / task notes to preserve focus during implementation:
|
||||
|
||||
```text
|
||||
- First discussion expand must not collapse on first click.
|
||||
- Technical-details expand must not trigger width jump from late scrollbar appearance.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the current automated baseline before edits**
|
||||
|
||||
Run: `npm test`
|
||||
Workdir: `paperforge/plugin`
|
||||
Expected: `40 passed` (or current equivalent with zero failures)
|
||||
|
||||
- [ ] **Step 3: Verify the existing discussion-refresh fix remains intact**
|
||||
|
||||
Do not re-implement the `active-leaf-change` guard if it is already present. Instead, confirm the current `main.js` logic still short-circuits leaf-focus-only transitions and does not get accidentally removed while refining UI code.
|
||||
|
||||
Verification target:
|
||||
|
||||
```text
|
||||
If resolved mode and file path are unchanged, active-leaf-change must not rebuild the mode tree.
|
||||
```
|
||||
|
||||
This protection must continue to preserve both:
|
||||
|
||||
```text
|
||||
- discussion expanded/collapsed state
|
||||
- technical-details expanded/collapsed state
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Stabilize the root scroll container in `styles.css`**
|
||||
|
||||
Apply bottom safe-area and stable vertical scrollbar behavior at the dashboard container level.
|
||||
|
||||
Implementation shape:
|
||||
|
||||
```css
|
||||
.paperforge-status-panel {
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
padding: 14px 14px 56px 14px;
|
||||
}
|
||||
```
|
||||
|
||||
If `scrollbar-gutter` alone is insufficient during manual verification, add a compatible fallback without changing mode structure, for example a permanent vertical overflow reservation strategy at the same container boundary.
|
||||
|
||||
- [ ] **Step 5: Run the automated suite again**
|
||||
|
||||
Run: `npm test`
|
||||
Workdir: `paperforge/plugin`
|
||||
Expected: zero failures
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add paperforge/plugin/main.js paperforge/plugin/styles.css
|
||||
git commit -m "fix: stabilize dashboard leaf refresh and scroll layout"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Consolidate Dashboard CSS Authority
|
||||
|
||||
**Files:**
|
||||
- Modify: `paperforge/plugin/styles.css`
|
||||
- Test: `paperforge/plugin/tests/*.mjs`
|
||||
|
||||
- [ ] **Step 1: Identify the authoritative dashboard section blocks**
|
||||
|
||||
Use the existing redesign spec as the source of truth and remove overlapping duplicate selector definitions for:
|
||||
|
||||
```text
|
||||
.paperforge-section-label
|
||||
.paperforge-contextual-btn
|
||||
.paperforge-status-strip
|
||||
.paperforge-paper-overview
|
||||
.paperforge-discussion-card
|
||||
.paperforge-technical-details*
|
||||
.paperforge-workflow-overview*
|
||||
.paperforge-library-snapshot*
|
||||
.paperforge-system-status*
|
||||
.paperforge-global-actions*
|
||||
.paperforge-workflow-toggles*
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rewrite to one authoritative visual system**
|
||||
|
||||
Prefer a single final selector block per dashboard component family. Do not stack another layer of overrides at the bottom of the file.
|
||||
|
||||
Target structure:
|
||||
|
||||
```css
|
||||
/* shared tokens */
|
||||
/* paper mode */
|
||||
/* collection mode */
|
||||
/* global mode */
|
||||
/* dark theme overrides */
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Introduce muted PaperForge accent tokens**
|
||||
|
||||
Keep surfaces/text/borders Obsidian-native, and add a restrained PaperForge tint layer.
|
||||
|
||||
Implementation shape:
|
||||
|
||||
```css
|
||||
.paperforge-status-panel {
|
||||
--pf-paper-accent: ...;
|
||||
--pf-collection-accent: ...;
|
||||
--pf-global-accent: ...;
|
||||
--pf-warm-line: ...;
|
||||
}
|
||||
```
|
||||
|
||||
Do not apply these as large full-card backgrounds.
|
||||
|
||||
- [ ] **Step 4: Run the automated suite**
|
||||
|
||||
Run: `npm test`
|
||||
Workdir: `paperforge/plugin`
|
||||
Expected: zero failures
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add paperforge/plugin/styles.css
|
||||
git commit -m "refactor: unify dashboard style system"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Rebalance Paper Mode
|
||||
|
||||
**Files:**
|
||||
- Modify: `paperforge/plugin/styles.css`
|
||||
- Modify: `paperforge/plugin/main.js` (only if a class hook is needed)
|
||||
- Test: `paperforge/plugin/tests/*.mjs`
|
||||
|
||||
- [ ] **Step 1: Raise paper-mode comfort without changing module order**
|
||||
|
||||
Keep the existing order:
|
||||
|
||||
```text
|
||||
paper header
|
||||
status/file row
|
||||
overview card
|
||||
next-step or complete row
|
||||
discussion card
|
||||
technical details
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Make technical details lighter than primary cards**
|
||||
|
||||
Use disclosure styling that reads as metadata, not as a full secondary card.
|
||||
|
||||
Implementation shape:
|
||||
|
||||
```css
|
||||
.paperforge-technical-details-toggle {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.paperforge-technical-details-body {
|
||||
padding-top: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Keep file actions neutral and discussion readable**
|
||||
|
||||
Preserve neutral file-opening actions in `paper` mode; do not promote them with strong tinting. Maintain body copy at `14px` minimum and keep discussion/overview as the most legible blocks.
|
||||
|
||||
- [ ] **Step 4: Run the automated suite**
|
||||
|
||||
Run: `npm test`
|
||||
Workdir: `paperforge/plugin`
|
||||
Expected: zero failures
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add paperforge/plugin/main.js paperforge/plugin/styles.css
|
||||
git commit -m "style: rebalance paper mode for reading"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Strengthen Collection and Global Hierarchy
|
||||
|
||||
**Files:**
|
||||
- Modify: `paperforge/plugin/styles.css`
|
||||
- Modify: `paperforge/plugin/main.js` (only if action classes or wrappers need a tiny hook)
|
||||
- Test: `paperforge/plugin/tests/*.mjs`
|
||||
|
||||
- [ ] **Step 1: Scale up collection metrics and workflow stage presence**
|
||||
|
||||
Meet the measured targets from the spec:
|
||||
|
||||
```css
|
||||
.paperforge-workflow-stage {
|
||||
min-width: 72px;
|
||||
}
|
||||
|
||||
.paperforge-snapshot-pill {
|
||||
min-width: 72px;
|
||||
}
|
||||
|
||||
.paperforge-collection-actions .paperforge-contextual-btn,
|
||||
.paperforge-global-actions-row .paperforge-contextual-btn {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.paperforge-workflow-stage-value,
|
||||
.paperforge-snapshot-value {
|
||||
font-size: 22px;
|
||||
}
|
||||
```
|
||||
|
||||
Also keep `collection` / `global` labels and detail text at spec minimums:
|
||||
|
||||
```css
|
||||
.paperforge-workflow-stage-label,
|
||||
.paperforge-snapshot-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.paperforge-section-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.paperforge-status-label,
|
||||
.paperforge-status-detail,
|
||||
.paperforge-collection-count {
|
||||
font-size: 13px;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Promote the primary actions explicitly**
|
||||
|
||||
Apply stronger but muted emphasis only to:
|
||||
|
||||
```text
|
||||
global: Open Literature Hub
|
||||
collection: Run OCR
|
||||
```
|
||||
|
||||
Keep `Sync Library` secondary in both modes.
|
||||
|
||||
Enforce button size targets while doing this:
|
||||
|
||||
```css
|
||||
.paperforge-collection-actions .paperforge-contextual-btn,
|
||||
.paperforge-global-actions-row .paperforge-contextual-btn {
|
||||
min-height: 36px;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Increase global homepage presence without changing inventory/order**
|
||||
|
||||
Keep the current order:
|
||||
|
||||
```text
|
||||
library snapshot
|
||||
system status
|
||||
optional issues
|
||||
start working
|
||||
```
|
||||
|
||||
Make cards, labels, and controls read as a homepage rather than a compact utility panel.
|
||||
Keep the OCR module visually heavier than the collection action row by giving the OCR section stronger padding / surface presence than the actions beneath it.
|
||||
|
||||
- [ ] **Step 4: Run the automated suite**
|
||||
|
||||
Run: `npm test`
|
||||
Workdir: `paperforge/plugin`
|
||||
Expected: zero failures
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add paperforge/plugin/main.js paperforge/plugin/styles.css
|
||||
git commit -m "style: strengthen collection and global dashboard hierarchy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Update UX Contract and Final Verification
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/ux-contract.md`
|
||||
- Test: `paperforge/plugin/tests/*.mjs`
|
||||
|
||||
- [ ] **Step 1: Update Workflow 4 contract entries**
|
||||
|
||||
Reflect the refined dashboard expectations in `docs/ux-contract.md`:
|
||||
|
||||
```text
|
||||
- paper mode bottom-safe-area expectation
|
||||
- no scrollbar reflow on technical-details expand
|
||||
- no first-click discussion collapse
|
||||
- preserved module order for collection mode
|
||||
- collection-mode issues remain scoped inside the collection view rather than being promoted into a separate extra module
|
||||
- preserved module order for global mode
|
||||
- light/dark theme verification expectation
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the full automated suite**
|
||||
|
||||
Run: `npm test`
|
||||
Workdir: `paperforge/plugin`
|
||||
Expected: zero failures
|
||||
|
||||
- [ ] **Step 3: Perform manual verification in Obsidian desktop**
|
||||
|
||||
Checklist:
|
||||
|
||||
```text
|
||||
- Windows light theme: paper bottom content is fully visible
|
||||
- Windows light theme: technical details expand without width jump
|
||||
- Windows light theme: first discussion expand does not flash closed
|
||||
- Windows light theme: with discussion expanded, leaf activation alone does not collapse it when resolved identity is unchanged
|
||||
- Windows light theme: with technical details expanded, leaf activation alone does not collapse it when resolved identity is unchanged
|
||||
- Windows light theme: collection feels larger and action hierarchy is obvious
|
||||
- Windows light theme: global feels like a homepage, not a utility panel
|
||||
- Windows light theme: keyboard focus is visible on collection/global action controls
|
||||
- Windows dark theme: contrast, focus visibility, and muted accents remain readable
|
||||
- Windows dark theme: keyboard focus is visible on collection/global action controls
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/ux-contract.md paperforge/plugin/main.js paperforge/plugin/styles.css
|
||||
git commit -m "docs: align dashboard ux contract with refined ui"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Do not add new dashboard modes.
|
||||
- Do not change CLI/data contracts.
|
||||
- Do not introduce theme-specific palettes.
|
||||
- Prefer removing duplicate CSS over overriding it again.
|
||||
- If automated UI regression coverage requires extracting a small pure helper, keep the extraction minimal and local to dashboard behavior.
|
||||
|
||||
## Final Verification Commands
|
||||
|
||||
```bash
|
||||
cd paperforge/plugin
|
||||
npm test
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
Test Files 3 passed
|
||||
Tests 40 passed
|
||||
```
|
||||
161
docs/superpowers/plans/2026-05-10-dashboard-visual-refinement.md
Normal file
161
docs/superpowers/plans/2026-05-10-dashboard-visual-refinement.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# PaperForge Dashboard Visual Refinement Plan
|
||||
|
||||
> **For agentic workers:** Use superpowers:subagent-driven-development or inline execution. Steps use checkbox (`- [ ]`) syntax.
|
||||
|
||||
**Goal:** Apply Native Light Surface Design spec — strict typography, minimal containers, Obsidian-native colors, no decorative shadows, locale-unified UI text.
|
||||
|
||||
**Architecture:** CSS-first: establish --pf-* custom properties and .pf-* utility classes on `.paperforge-status-panel`, then rewrite Sections 39-43 with constrained typography (4 sizes, 3 weights), light borders, and contextual cards only for primary content modules. JS: restructure per-paper layout to merge header/status/files rows, collapse OCR/Analyze into technical details, compact "All Set" state.
|
||||
|
||||
**Tech Stack:** Obsidian DOM API (vanilla JS), CSS with Obsidian semantic variables only.
|
||||
|
||||
**Commit style:** `type(scope): description` (English, semantic, existing pattern).
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
- Modify: `paperforge/plugin/styles.css` (large rewrite of Sections 39-43)
|
||||
- Modify: `paperforge/plugin/main.js` (per-paper layout + locale text)
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Commit current CSS base changes
|
||||
|
||||
**Files:** styles.css (staged change on paperforge-status-panel + utility classes)
|
||||
|
||||
- [ ] Git commit the pending styles.css change
|
||||
|
||||
```bash
|
||||
git add paperforge/plugin/styles.css
|
||||
git commit -m "style(dashboard): establish CSS base --pf-* vars, typography tokens, utility classes"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Rewrite CSS Section 39 — Shared components
|
||||
|
||||
**Files:** styles.css (replace Section 39 "Shared Components")
|
||||
|
||||
Replace the existing Section 39 with:
|
||||
- `.pf-card` — only for primary content modules (no box-shadow)
|
||||
- `.pf-pill` + modifiers `.pf-pill--ok/warn/fail` — 999px radius, subtle
|
||||
- `.pf-btn-secondary` / `.pf-btn-primary` — Obsidian interactive vars
|
||||
- `.pf-complete-row` — compact green text, no card
|
||||
- `.pf-disclosure-row` + `.pf-disclosure-body` — simple text+cursor
|
||||
- `.pf-section-label` — 12px/600/uppercase/no accent border
|
||||
- `.pf-stack-8` / `.pf-stack-12` — spacing utilities
|
||||
- Remove old `.paperforge-section-label` left-border accent
|
||||
- Remove old contextual button styles (replaced by pf-btn-*)
|
||||
- Remove old card ::before elevation pseudo-elements
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Rewrite CSS Section 40 — Per-paper view
|
||||
|
||||
**Files:** styles.css (replace Section 40)
|
||||
|
||||
Apply constrained typography to per-paper components:
|
||||
- `.paperforge-paper-header`: title 16px/600, meta 13px/400, year 12px/400 (no bullet separator, use ` · `)
|
||||
- `.paperforge-status-strip`: remove borders, keep flex + gap
|
||||
- `.paperforge-status-pill`: inherit `.pf-pill` patterns (no colored backgrounds, text-only status colors)
|
||||
- `.paperforge-paper-overview`: use `.pf-card` base (no shadow, no ::before)
|
||||
- `.paperforge-discussion-card`: use `.pf-card` base (no shadow, no ::before)
|
||||
- `.paperforge-discussion-q`: 14px/600
|
||||
- `.paperforge-discussion-a`: 14px/400
|
||||
- `.paperforge-discussion-expand`: text accent only
|
||||
- `.paperforge-paper-files`: move inline with status strip
|
||||
- `.paperforge-technical-details-toggle`: use `.pf-disclosure-row` style
|
||||
- `.paperforge-technical-details-body`: use `.pf-disclosure-body` style
|
||||
- `.paperforge-workflow-toggles`: NOT a card (remove background, border, shadow). Simple flex row.
|
||||
- Dark theme overrides: remove shadow rules, keep only background/border tweaks
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Rewrite CSS Section 41 — Collection/Base
|
||||
|
||||
**Files:** styles.css (replace Section 41)
|
||||
|
||||
- `.paperforge-workflow-overview`: `.pf-card` base, remove shadow
|
||||
- `.paperforge-workflow-stage-value`: 18px/600, accent only for metric
|
||||
- `.paperforge-collection-header`: title 16px/600
|
||||
- `.paperforge-issue-summary`: light border-left accent (orange), no full red border
|
||||
- `.paperforge-collection-actions`: simple flex row
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Rewrite CSS Section 42 — Global/Home
|
||||
|
||||
**Files:** styles.css (replace Section 42)
|
||||
|
||||
- `.paperforge-library-snapshot`: `.pf-card` base, remove shadow
|
||||
- `.paperforge-system-status`: `.pf-card` base, remove shadow
|
||||
- `.paperforge-global-actions`: `.pf-card` base, remove shadow
|
||||
- `.paperforge-snapshot-value`: 18px/600
|
||||
- `.paperforge-status-row`: dot stays, label 14px
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Rewrite JS per-paper layout (merge header + status strip + file row)
|
||||
|
||||
**Files:** main.js — `_renderPaperMode` and helpers
|
||||
|
||||
Layout per spec:
|
||||
```
|
||||
[PAPER badge] title
|
||||
authors · year
|
||||
[PDF ✓] [OCR ✓] [精读 ✓] [打开 PDF] [打开全文]
|
||||
┌ 文章概览 ─────────────┐
|
||||
└──────────────────────┘
|
||||
✓ 已完成,可直接使用
|
||||
┌ 最近讨论 ─────────────┐
|
||||
└──────────────────────┘
|
||||
技术详情 ▸
|
||||
```
|
||||
|
||||
Changes:
|
||||
1. Status strip: place PDF pill + OCR pill + DeepRead pill left, Open PDF + Open Fulltext right (same flex row)
|
||||
2. No separate `paperforge-paper-files` section below — file buttons ARE in the status row
|
||||
3. Workflow toggles (OCR/Analyze checkbox): remove from standalone section, move into `_renderPaperTechnicalDetails` disclosure body
|
||||
4. Next step card: when `nextStep === 'ready'`, render compact `.pf-complete-row` instead of card
|
||||
5. Next step card: remove `'RECOMMENDED NEXT STEP'` all-caps label, use "下一步" (zh) / "Next Step" (en)
|
||||
6. Locale: all UI text use Chinese when T===zh
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Locale cleanup
|
||||
|
||||
**Files:** main.js — ensure all visible UI labels are locale-consistent
|
||||
|
||||
- "Open PDF" → `t('open_pdf')` or hardcoded 中文 `打开 PDF`
|
||||
- "Open Fulltext" → `打开全文`
|
||||
- "Copy /pf-deep Command" → `复制精读命令` or remove
|
||||
- "Run in {0}" → remove or make Chinese
|
||||
- Next-step labels: use Chinese when zh
|
||||
- Section labels: "文章概览" stays, "最近讨论" stays, "技术详情" stays
|
||||
- "Add to OCR Queue" → `加入 OCR`
|
||||
- "Remove from OCR Queue" → `移出 OCR`
|
||||
- "Analyze" label → `精读` / `标记精读`
|
||||
|
||||
Add missing i18n keys to LANG.zh and LANG.en.
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Regression verification
|
||||
|
||||
- [ ] Verify syntax: `node --check main.js`
|
||||
- [ ] Run tests: `npx vitest run` (all 40 pass)
|
||||
- [ ] Copy to test vault
|
||||
- [ ] Visual check: per-paper with 3EWBBTAS (deep-read done), 2Y9M3ILK (discussion), B29TFCL4 (no OCR)
|
||||
- [ ] Check global view layout
|
||||
- [ ] Check collection view layout
|
||||
- [ ] Check dark theme (toggle Obsidian theme to dark)
|
||||
- [ ] Check narrow sidebar width
|
||||
|
||||
---
|
||||
|
||||
## Non-goals
|
||||
- Don't add new fonts
|
||||
- Don't add decorative shadows to cards
|
||||
- Don't add colored backgrounds to status pills
|
||||
- Don't use more than 4 font sizes
|
||||
- Don't use font-weight 700
|
||||
- Don't rebuild existing working modules (only restructure layout, not logic)
|
||||
395
docs/superpowers/specs/2026-05-10-dashboard-ui-refine-design.md
Normal file
395
docs/superpowers/specs/2026-05-10-dashboard-ui-refine-design.md
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
# PaperForge Dashboard UI Refine Design
|
||||
|
||||
> Approved direction: `Quiet Research Desk`
|
||||
> Tone: `Calm editorial`
|
||||
> Color posture: preserve PaperForge's own muted identity more than Obsidian theme accent, while remaining structurally theme-compatible.
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Refine the PaperForge Obsidian dashboard so the three active modes feel stable, readable, and visually intentional.
|
||||
|
||||
This pass must remove current interaction defects, increase visual hierarchy in `global` and `collection` modes, and replace the current overly small / overly uniform surface treatment with a warmer, quieter dashboard language that still fits naturally inside Obsidian.
|
||||
|
||||
---
|
||||
|
||||
## In Scope
|
||||
|
||||
1. Fix layout and interaction defects in the current dashboard UI.
|
||||
2. Simplify and unify dashboard CSS so one visual system controls all three modes.
|
||||
3. Increase perceived weight and readability of `global` and `collection` views.
|
||||
4. Preserve `paper` mode as the calmest mode, optimized for reading and discussion.
|
||||
5. Add restrained color accents inspired by muted Morandi / Maillard / soft American editorial palettes.
|
||||
6. Keep compatibility with Obsidian themes for structure, contrast, and dark/light behavior.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
1. Adding new dashboard modes.
|
||||
2. Changing data contracts or CLI behavior.
|
||||
3. Major DOM restructuring outside the current dashboard components.
|
||||
4. Theme-specific per-theme palettes.
|
||||
5. New analytics, charts, or workflow states.
|
||||
|
||||
---
|
||||
|
||||
## Problems To Solve
|
||||
|
||||
### 1. Bottom clipping in `paper` mode
|
||||
|
||||
The dashboard scroll container ends too tightly against Obsidian's lower chrome. Expanded technical details can be visually blocked by the bottom bar.
|
||||
|
||||
### 2. Scrollbar-induced layout jump
|
||||
|
||||
Opening technical details can push content over the vertical overflow threshold, causing the right scrollbar to appear only in the expanded state. This changes content width and makes text reflow visibly.
|
||||
|
||||
### 3. First discussion expand flash
|
||||
|
||||
The first click on a discussion expand control can trigger an unnecessary mode refresh due to `active-leaf-change`, which destroys transient local UI state.
|
||||
|
||||
### 4. `collection` mode feels underweighted and empty
|
||||
|
||||
The funnel, OCR section, and actions are all visually too small and too light relative to the available vertical space. The action row feels like footer utilities instead of primary workflow controls.
|
||||
|
||||
### 5. `global` mode feels like a compressed utility panel
|
||||
|
||||
Snapshot metrics, system status, and actions do not yet read as a real homepage. Typography and card weight are too restrained.
|
||||
|
||||
### 6. Visual language is too uniform and slightly sterile
|
||||
|
||||
Nearly every module uses the same thin border, small label, and low-energy box treatment. The result is clean but fatiguing.
|
||||
|
||||
### 7. CSS authority is split across duplicated sections
|
||||
|
||||
`styles.css` contains overlapping dashboard definitions from multiple iterations. The file currently mixes the newer native-light-surface pass with later redefinitions, causing inconsistency and making further refinement brittle.
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
|
||||
### Quiet Research Desk
|
||||
|
||||
The dashboard should feel like a well-kept research desk, not an admin console.
|
||||
|
||||
- Calm, not cold
|
||||
- Warm, not decorative
|
||||
- Structured, not dense
|
||||
- Native to Obsidian, not generic web SaaS
|
||||
|
||||
### Mode personality hierarchy
|
||||
|
||||
- `paper`: quietest, most reading-oriented, least chrome
|
||||
- `collection`: strongest workflow personality, denser operational cues
|
||||
- `global`: homepage personality, strongest sense of entry and orientation
|
||||
|
||||
### Structural theme compatibility
|
||||
|
||||
The dashboard should inherit all major structural tokens from Obsidian:
|
||||
|
||||
- surfaces
|
||||
- text hierarchy
|
||||
- borders
|
||||
- hover / active interaction colors
|
||||
- dark/light behavior
|
||||
|
||||
### Selective brand tinting
|
||||
|
||||
PaperForge should keep a light house style of its own through restrained accent zones only:
|
||||
|
||||
- mode badges
|
||||
- section markers
|
||||
- emphasized numbers
|
||||
- status pills
|
||||
- light hover / focus accents
|
||||
|
||||
Accent tinting must never dominate full-card backgrounds.
|
||||
|
||||
---
|
||||
|
||||
## Color Direction
|
||||
|
||||
### Palette family
|
||||
|
||||
Use a muted editorial palette rather than a bright product palette.
|
||||
|
||||
Candidate families:
|
||||
|
||||
- `paper`: sage / dusty teal / cool gray-green
|
||||
- `collection`: terracotta / muted amber / clay brown
|
||||
- `global`: navy-gray / smoky blue / quiet brass accent
|
||||
|
||||
### Rules
|
||||
|
||||
1. No large high-saturation fills.
|
||||
2. No dependence on the user's Obsidian accent as the primary PaperForge identity.
|
||||
3. Accent color should be visible mostly in compact anchors, not whole surfaces.
|
||||
4. Dark mode uses the same family, but with contrast adjusted by theme variables.
|
||||
|
||||
### Token ownership
|
||||
|
||||
Structural tokens come from Obsidian variables:
|
||||
|
||||
- surfaces: `--background-*`
|
||||
- body and metadata text: `--text-*`
|
||||
- borders and separators: `--background-modifier-*`
|
||||
- default hover / active button surfaces: `--interactive-*`
|
||||
- default focus visibility should remain Obsidian-native unless a stronger PaperForge focus ring is required for contrast
|
||||
|
||||
PaperForge-owned tint tokens may be introduced only for:
|
||||
|
||||
- mode badges
|
||||
- section markers
|
||||
- emphasized metric numbers
|
||||
- semantic status pills
|
||||
- optional primary action emphasis
|
||||
- light card-edge or label accents in `global` and `collection`
|
||||
|
||||
PaperForge-owned tints must not replace Obsidian's base surface, body text, or default control contrast model.
|
||||
|
||||
---
|
||||
|
||||
## Visual System Changes
|
||||
|
||||
### Typography
|
||||
|
||||
Raise hierarchy contrast across all modes.
|
||||
|
||||
- Larger metric numbers in `global` and `collection`
|
||||
- Section labels become more deliberate but not louder
|
||||
- Buttons gain more presence through padding and weight, not bright fills
|
||||
- `paper` body text remains the most reading-friendly text block
|
||||
|
||||
Measured targets:
|
||||
|
||||
- `global` / `collection` metric values: minimum `22px`, target `22-24px`
|
||||
- `paper` section body copy: `14px` minimum, no reduction below current reading size
|
||||
- `global` / `collection` section labels: minimum `12px`
|
||||
- `global` / `collection` metadata/detail text: minimum `13px`
|
||||
- primary and contextual action buttons in `global` / `collection`: minimum `13px` text size
|
||||
|
||||
### Spacing
|
||||
|
||||
- Increase inner card padding in `global` and `collection`
|
||||
- Add consistent mode-level vertical rhythm
|
||||
- Add stable bottom safe area to the scroll container
|
||||
- Reduce cramped micro-gaps around pills, buttons, and disclosure areas
|
||||
|
||||
Measured targets:
|
||||
|
||||
- root dashboard bottom safe area: minimum `56px`
|
||||
- primary cards in `global` / `collection`: minimum `16px` inner padding
|
||||
- action buttons: minimum `34px` visual height
|
||||
- disclosure body top padding: minimum `8px`
|
||||
- `collection` / `global` mode section gap: minimum `16px`
|
||||
|
||||
### Surfaces
|
||||
|
||||
- Use fewer visual formulas, not more
|
||||
- Primary content modules may remain carded
|
||||
- Lightweight metadata / disclosure sections should not pretend to be full cards
|
||||
- Remove mixed old/new box-shadow and border treatments
|
||||
|
||||
### Actions
|
||||
|
||||
- `collection` and `global` actions must feel like meaningful controls, not utility links
|
||||
- Action rows should have larger targets and better anchoring in layout
|
||||
- Primary actions may use slightly stronger tinting, but still muted
|
||||
|
||||
Primary action ownership:
|
||||
|
||||
- `global`: `Open Literature Hub` is the primary navigational action; `Sync Library` is secondary
|
||||
- `collection`: `Run OCR` is the primary workflow action; `Sync Library` is secondary
|
||||
- `paper`: keep file-opening actions neutral; do not introduce a new strong primary tint in the status/file row
|
||||
|
||||
Interaction rules:
|
||||
|
||||
- focus rings must remain clearly visible in both light and dark themes
|
||||
- interactive controls must keep at least Obsidian-native focus visibility
|
||||
- tinted buttons or pills must maintain readable text contrast in both light and dark themes
|
||||
|
||||
---
|
||||
|
||||
## Mode-Specific Design
|
||||
|
||||
### Paper Mode
|
||||
|
||||
Keep `paper` mode closest to the current reading companion model.
|
||||
|
||||
Changes:
|
||||
|
||||
1. Add bottom safety padding so technical details and discussion are never cramped by the Obsidian bottom bar.
|
||||
2. Stabilize vertical scrollbar reservation to eliminate width jump when expanding sections.
|
||||
3. Preserve the current header and merged status/file row structure.
|
||||
4. Keep overview and discussion as the main content cards.
|
||||
5. Make technical details lighter and calmer than primary cards.
|
||||
6. Ensure discussion expand/collapse interaction never triggers accidental refresh-state loss.
|
||||
|
||||
Measured targets:
|
||||
|
||||
- technical-details expand/collapse must not cause horizontal text reflow from scrollbar appearance on supported desktop targets
|
||||
- local discussion expand state and technical-details disclosure state must survive leaf activation when resolved mode and file identity stay unchanged
|
||||
- paper mode may use the weakest accent density of the three modes
|
||||
|
||||
### Collection Mode
|
||||
|
||||
Make `collection` mode feel like an operational workspace.
|
||||
|
||||
Changes:
|
||||
|
||||
1. Increase funnel visual weight and number emphasis.
|
||||
2. Make OCR pipeline feel like a central module, not a thin progress strip.
|
||||
3. Enlarge action controls and treat them as workflow entry points.
|
||||
4. Reduce the sense of unused lower space by giving the mode stronger section bodies and spacing.
|
||||
5. Use warmer muted workflow accents than `paper` mode.
|
||||
|
||||
Canonical module order:
|
||||
|
||||
1. collection header
|
||||
2. workflow overview
|
||||
3. OCR pipeline
|
||||
4. collection actions
|
||||
|
||||
Measured targets:
|
||||
|
||||
- workflow stage blocks: minimum `72px` visual width
|
||||
- workflow stage labels: minimum `11px`
|
||||
- collection action buttons: minimum `36px` visual height
|
||||
- OCR module must visually read heavier than the action row beneath it
|
||||
- collection mode must preserve the canonical module order above
|
||||
|
||||
### Global Mode
|
||||
|
||||
Make `global` mode feel like the dashboard homepage.
|
||||
|
||||
Changes:
|
||||
|
||||
1. Increase snapshot card presence.
|
||||
2. Strengthen system status readability.
|
||||
3. Give the start-working area more intentional prominence.
|
||||
4. Keep issues clearly visible when present, but not alarmist.
|
||||
5. Use the calmest version of the homepage accent family so the screen feels trustworthy rather than flashy.
|
||||
|
||||
Measured targets:
|
||||
|
||||
- snapshot metric blocks: minimum `72px` visual width
|
||||
- global action buttons: minimum `36px` visual height
|
||||
- homepage modules should preserve existing inventory and order: snapshot, system status, optional issues, start working
|
||||
|
||||
---
|
||||
|
||||
## Technical Decisions
|
||||
|
||||
### 1. Single CSS authority
|
||||
|
||||
Dashboard-specific duplicated definitions in `styles.css` must be consolidated so each dashboard selector has one authoritative definition block.
|
||||
|
||||
The implementation should prefer the newer redesign architecture and remove overlapping legacy overrides instead of stacking more overrides on top.
|
||||
|
||||
### 2. Stable scroll layout
|
||||
|
||||
The root dashboard scroll container should reserve scrollbar space consistently where supported, rather than only when overflow appears.
|
||||
|
||||
Fallback rule:
|
||||
|
||||
- on desktop targets where native gutter reservation is unavailable or behaves as overlay-only, the implementation must still avoid visible width-jump during technical-details expansion through compatible fallback styling or permanent vertical overflow reservation
|
||||
|
||||
### 3. Bottom safe area
|
||||
|
||||
The main scroll container or mode content container should include explicit bottom padding sized to prevent visual collision with Obsidian's lower status area.
|
||||
|
||||
### 4. Preserve local UI state where appropriate
|
||||
|
||||
Mode refreshes should not happen when only the dashboard leaf becomes active while the resolved mode/file identity stays unchanged.
|
||||
|
||||
State preservation rules:
|
||||
|
||||
- preserve discussion expanded/collapsed state across leaf activation with unchanged identity
|
||||
- preserve technical-details disclosure expanded/collapsed state across leaf activation with unchanged identity
|
||||
- reset those states on actual mode change or file identity change
|
||||
- explicit manual refresh may reset transient state
|
||||
- file content mutation that changes the current entry data may refresh the view, but must not transiently collapse state before the refresh completes
|
||||
|
||||
### 5. Theme-aware implementation model
|
||||
|
||||
Implementation should rely on Obsidian variables for structural tokens, and use PaperForge-owned CSS custom properties for restrained accent tints.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Functional
|
||||
|
||||
1. `paper` mode content is fully readable at the bottom with no visual obstruction from Obsidian chrome.
|
||||
2. Opening technical details no longer causes a first-order layout jump from scrollbar appearance.
|
||||
3. Discussion expand/collapse no longer flashes closed on first click.
|
||||
4. Existing mode routing and dashboard actions continue to work.
|
||||
5. Leaf activation alone with unchanged resolved mode/file identity does not rebuild the current mode tree.
|
||||
|
||||
### Visual
|
||||
|
||||
1. `collection` and `global` metric values are visibly larger than current live values and meet the measured targets above.
|
||||
2. `paper` remains the lowest-accent, reading-first mode.
|
||||
3. The three modes keep the same module inventory/order described in this spec while gaining differentiated emphasis.
|
||||
4. Accent color remains limited to small emphasis zones rather than full-card fills.
|
||||
5. Theme compatibility is preserved in both light and dark contexts.
|
||||
|
||||
### Accessibility / Interaction
|
||||
|
||||
1. Interactive controls retain visible keyboard focus in light and dark themes.
|
||||
2. Contextual action buttons in `global` and `collection` meet the minimum visual height targets above.
|
||||
3. Tinted pills, labels, and emphasized metrics maintain readable contrast against their backgrounds.
|
||||
4. Expanding technical details must not change content wrap width in Windows desktop verification.
|
||||
|
||||
### Code Quality
|
||||
|
||||
1. Dashboard CSS duplication is materially reduced.
|
||||
2. New styling logic is easier to reason about than the current stacked overrides.
|
||||
3. Existing plugin tests continue to pass.
|
||||
|
||||
### UX Contract Deltas Required
|
||||
|
||||
`docs/ux-contract.md` must be updated to reflect:
|
||||
|
||||
1. `paper` mode bottom-safe-area expectation so lower content is not visually obstructed.
|
||||
2. `paper` mode technical-details expansion must not introduce layout reflow from scrollbar appearance.
|
||||
3. `paper` mode discussion expand must not collapse on first click due to leaf activation.
|
||||
4. `collection` mode must preserve current module inventory and order while using larger visual hierarchy.
|
||||
5. `global` mode must preserve current module inventory and order while using larger visual hierarchy.
|
||||
6. light and dark theme verification must be mentioned in dashboard view expectations.
|
||||
|
||||
---
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. Run plugin test suite after implementation.
|
||||
2. Manually verify `paper`, `collection`, and `global` modes in Obsidian.
|
||||
3. Specifically verify:
|
||||
- paper bottom spacing
|
||||
- technical details expansion stability
|
||||
- discussion expand first-click behavior
|
||||
- perceived size and hierarchy in collection/global
|
||||
- light and dark theme legibility
|
||||
- visible keyboard focus on action controls
|
||||
4. Manual verification targets:
|
||||
- Obsidian desktop on Windows in light theme
|
||||
- Obsidian desktop on Windows in dark theme
|
||||
|
||||
---
|
||||
|
||||
## Files Expected To Change
|
||||
|
||||
- `paperforge/plugin/styles.css`
|
||||
- `paperforge/plugin/main.js`
|
||||
- `docs/ux-contract.md`
|
||||
|
||||
Possible test touch-up only if needed:
|
||||
|
||||
- `paperforge/plugin/tests/commands.test.mjs`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
This is a refinement pass, not a dashboard rewrite.
|
||||
|
||||
The best result is a dashboard that feels more comfortable after ten minutes of use, not just prettier in a screenshot.
|
||||
|
|
@ -47,10 +47,10 @@
|
|||
| Step ID | Trigger | Action | Measurable Outcome | Error Contract |
|
||||
|---------|---------|--------|-------------------|----------------|
|
||||
| W4-S1 | User opens Obsidian vault | Plugin loads and registers dashboard view | Plugin view registered; sidebar icon visible; clicking opens PaperForge dashboard panel | Plugin not loaded; console shows "Failed to register PaperForge view" |
|
||||
| W4-S2 | User opens a `.base` file | Collection mode activates | Dashboard switches to `collection` mode; shows: workflow funnel (Total → PDF Ready → OCR Done → Deep Read), OCR pipeline progress bar scoped to current base, issue summary (compact, only when issues exist), action buttons (Sync Library, Run OCR) | Falls to global mode; missing stats |
|
||||
| W4-S3 | User opens a formal note (`.md` with `zotero_key`) | Paper mode activates | Dashboard switches to `paper` mode; shows: status strip (PDF/OCR/DeepRead pills), paper overview card (Pass 1 summary from `## 🔍 精读`), workflow toggle checkboxes (`do_ocr`/`analyze` — sync to Base), next-step card, files row (Open PDF / Open Fulltext), collapsible technical details | Falls to global mode; components empty or erroring |
|
||||
| W4-S2 | User opens a `.base` file | Collection mode activates | Dashboard switches to `collection` mode; preserves module order: header, workflow funnel (Total → PDF Ready → OCR Done → Deep Read), OCR pipeline scoped to current base, collection-scoped issue/health messaging only inside the collection view, then action buttons (Run OCR primary, Sync Library secondary) | Falls to global mode; missing stats; collection issues appear as a separate extra module outside the collection view |
|
||||
| W4-S3 | User opens a formal note (`.md` with `zotero_key`) | Paper mode activates | Dashboard switches to `paper` mode; preserves module order: paper header, status/file row, paper overview card, next-step or complete row, discussion card, collapsible technical details; panel keeps bottom-safe-area padding so final content is fully visible; expanding technical details does not trigger scrollbar reflow/width jump; first discussion expand does not collapse on the first click | Falls to global mode; components empty or erroring; bottom content is clipped; expanding discussion/technical details causes collapse or layout jump |
|
||||
| W4-S4 | User opens `fulltext.md` or other workspace file | Paper mode activates via workspace detection | Dashboard stays in `paper` mode for any file inside a paper workspace directory (`{key} - {title}/`). zotero_key resolved from dirname pattern | Falls to global mode |
|
||||
| W4-S5 | User opens no file or non-workspace `.md` | Global mode activates | Dashboard shows: library snapshot (papers / PDFs / OCR done / deep-read done), system status grid (runtime / index / Zotero export / OCR token), issues panel (only when anomalies exist with Run Doctor / Repair Issues), action buttons (Open Literature Hub / Sync Library) | Shows loading/empty state; console errors |
|
||||
| W4-S5 | User opens no file or non-workspace `.md` | Global mode activates | Dashboard shows, in preserved order: library snapshot (papers / PDFs / OCR done / deep-read done), system status grid (runtime / index / Zotero export / OCR token), optional issues panel (only when anomalies exist with Run Doctor / Repair Issues), then action buttons (Open Literature Hub primary / Sync Library secondary); light and dark themes both keep muted accents, readable contrast, and visible keyboard focus on collection/global action controls | Shows loading/empty state; console errors; module order changes unexpectedly; light/dark theme contrast or focus visibility regresses |
|
||||
| W4-S6 | User toggles `do_ocr` or `analyze` checkbox | Workflow toggle writes to frontmatter | Checkbox state writes to formal note YAML frontmatter via `processFrontMatter`; Base view reflects change immediately (reads from file); dashboard auto-refreshes via `modify` event | Frontmatter not written; notice shows "Note file not found" |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -226,19 +226,35 @@ def generate_review(candidates: list[dict]) -> str:
|
|||
def extract_preserved_deep_reading(text: str) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
match = re.search("^## 🔍 精读\\s*$", text, re.MULTILINE)
|
||||
# Match "## 🔍 精读" or "## 精读" (emoji is optional)
|
||||
match = re.search(r"^##\s*🔍?\s*精读\s*$", text, re.MULTILINE)
|
||||
if not match:
|
||||
return ""
|
||||
start = match.start()
|
||||
preserved = text[start:].strip()
|
||||
# Skip if section body is empty or only contains placeholder text
|
||||
body = re.sub(r"^##\s*🔍?\s*精读\s*$", "", preserved, flags=re.MULTILINE).strip()
|
||||
if not body or _is_placeholder_only(body):
|
||||
return ""
|
||||
return preserved
|
||||
|
||||
|
||||
_PLACEHOLDER_PATTERN = re.compile(r"(待补充)|\(待补充\)|\(TODO\)|\(TBD\)")
|
||||
|
||||
|
||||
def _is_placeholder_only(body: str) -> bool:
|
||||
"""Check if the deep reading body is only placeholder text (no real content)."""
|
||||
cleaned = _PLACEHOLDER_PATTERN.sub("", body).strip()
|
||||
cleaned = re.sub(r"^[-*]\s*$", "", cleaned, flags=re.MULTILINE).strip()
|
||||
return not cleaned
|
||||
|
||||
|
||||
def has_deep_reading_content(text: str) -> bool:
|
||||
preserved = extract_preserved_deep_reading(text)
|
||||
if not preserved:
|
||||
return False
|
||||
body = preserved.replace(DEEP_READING_HEADER, "").strip()
|
||||
# Strip both "## 🔍 精读" and "## 精读" header variants
|
||||
body = re.sub(r"^##\s*🔍?\s*精读\s*$", "", preserved, flags=re.MULTILINE).strip()
|
||||
if not body:
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
p_dr = sub.add_parser("deep-reading", help="Check deep-reading queue status")
|
||||
p_dr.add_argument("--json", action="store_true", help="Output as PFResult JSON")
|
||||
|
||||
# deep-finalize
|
||||
p_df = sub.add_parser("deep-finalize", help="Mark deep reading done and notify dashboard")
|
||||
p_df.add_argument("zotero_key", help="Zotero citation key")
|
||||
p_df.add_argument("--json", action="store_true", help="Output as PFResult JSON")
|
||||
|
||||
# repair
|
||||
p_repair = sub.add_parser("repair", help="Repair divergent literature notes")
|
||||
p_repair.add_argument("--fix", action="store_true", help="Actually apply repairs instead of dry-run")
|
||||
|
|
@ -445,6 +450,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||
|
||||
return deep.run(args)
|
||||
|
||||
if args.command == "deep-finalize":
|
||||
from paperforge.commands import finalize
|
||||
|
||||
return finalize.run(args)
|
||||
|
||||
if args.command == "context":
|
||||
from paperforge.commands import context
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,21 @@ Agent 在正式笔记中创建或更新 `## 精读` 区域,包含:
|
|||
- **Pass 2: 精读还原** — Figure-by-Figure 解析、Table-by-Table 解析、关键方法补课、主要发现与新意
|
||||
- **Pass 3: 深度理解** — 假设挑战与隐藏缺陷、结论扎实性评估、Discussion 解读、个人启发、遗留问题
|
||||
|
||||
## Post-Processing (必须执行)
|
||||
|
||||
精读内容全部写完后,Agent **必须**调用以下命令完成收尾:
|
||||
|
||||
```bash
|
||||
paperforge deep-finalize <zotero_key>
|
||||
```
|
||||
|
||||
该命令会:
|
||||
1. 将正式笔记 frontmatter 中的 `deep_reading_status` 设为 `done`
|
||||
2. 刷新 canonical index(写入 `formal-library.json`)
|
||||
3. Dashboard 检测到 index 变更后自动刷新 `文章概览` 卡片
|
||||
|
||||
> `deep_reading_status` 只在精读全部完成(内容填写 + 验证通过)后才标记为 `done`。不要在准备阶段或中途设置此状态。
|
||||
|
||||
## Error Handling
|
||||
|
||||
### OCR 未完成
|
||||
|
|
@ -148,6 +163,7 @@ Task(
|
|||
2. 用 Bash tool 预跑 `python {{SCRIPT}} figure-index {{FULLTEXT_MD}}` 确认 OCR 存在
|
||||
3. 四个 Task 并行启动,每篇独立
|
||||
4. 等待所有 Task 完成,收集各篇的写入行数和验证结果
|
||||
5. 对每篇已完成精读的论文,调用 `paperforge deep-finalize <zotero_key>` 标记完成并触发 Dashboard 刷新
|
||||
|
||||
**预检(必须)**:
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ _COMMAND_REGISTRY: dict[str, str] = {
|
|||
"status": "paperforge.commands.status",
|
||||
"context": "paperforge.commands.context",
|
||||
"dashboard": "paperforge.commands.dashboard",
|
||||
"finalize": "paperforge.commands.finalize",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
95
paperforge/commands/finalize.py
Normal file
95
paperforge/commands/finalize.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
"""deep-finalize — mark deep reading as done and signal dashboard to refresh."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from paperforge import __version__
|
||||
from paperforge.core.result import PFResult
|
||||
|
||||
|
||||
def _update_frontmatter(text: str, key: str, value: str) -> str:
|
||||
"""Replace or insert a frontmatter field value."""
|
||||
pattern = re.compile(rf"^({re.escape(key)}:\s*)(.*?)$", re.MULTILINE)
|
||||
if pattern.search(text):
|
||||
return pattern.sub(rf"\g<1>{value}", text)
|
||||
# Insert after the first '---' separator
|
||||
fm_end = text.find("---\n", 3)
|
||||
if fm_end != -1:
|
||||
return text[:fm_end] + f"{key}: {value}\n" + text[fm_end:]
|
||||
return text
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> int:
|
||||
"""Mark deep reading as done and refresh the canonical index.
|
||||
|
||||
Called by the AI agent at the end of /pf-deep to signal completion.
|
||||
The dashboard watches formal-library.json and will refresh on change.
|
||||
"""
|
||||
vault: Path = getattr(args, "vault_path", None)
|
||||
if vault is None:
|
||||
from paperforge.config import resolve_vault
|
||||
|
||||
vault = resolve_vault(cli_vault=getattr(args, "vault", None))
|
||||
|
||||
zotero_key: str | None = getattr(args, "zotero_key", None)
|
||||
if not zotero_key:
|
||||
print("[ERROR] zotero_key is required", file=sys.stderr)
|
||||
if getattr(args, "json", False):
|
||||
pf = PFResult(ok=False, command="deep-finalize", version=__version__, error="zotero_key is required")
|
||||
print(pf.to_json())
|
||||
return 1
|
||||
|
||||
# 1. Find and update the formal note frontmatter
|
||||
from paperforge.worker._utils import pipeline_paths
|
||||
|
||||
paths = pipeline_paths(vault)
|
||||
lit_root = paths["literature"]
|
||||
note_updated = False
|
||||
|
||||
if lit_root.exists():
|
||||
for note_file in lit_root.rglob("*.md"):
|
||||
if note_file.name in ("fulltext.md", "deep-reading.md", "discussion.md"):
|
||||
continue
|
||||
try:
|
||||
text = note_file.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
continue
|
||||
if re.search(rf'^\s*zotero_key:\s*"{re.escape(zotero_key)}"', text, re.MULTILINE):
|
||||
new_text = _update_frontmatter(text, "deep_reading_status", "done")
|
||||
note_file.write_text(new_text, encoding="utf-8")
|
||||
note_updated = True
|
||||
break
|
||||
|
||||
# 2. Refresh the canonical index entry (writes formal-library.json)
|
||||
from paperforge.worker.asset_index import refresh_index_entry
|
||||
|
||||
index_ok = refresh_index_entry(vault, zotero_key)
|
||||
|
||||
msg_parts = []
|
||||
if note_updated:
|
||||
msg_parts.append("frontmatter updated")
|
||||
else:
|
||||
msg_parts.append("frontmatter not found (index-only refresh)")
|
||||
msg_parts.append("index refreshed" if index_ok else "full index rebuild triggered")
|
||||
|
||||
message = f"[OK] deep-finalize {zotero_key}: " + ", ".join(msg_parts)
|
||||
print(message)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
pf = PFResult(
|
||||
ok=True,
|
||||
command="deep-finalize",
|
||||
version=__version__,
|
||||
data={
|
||||
"zotero_key": zotero_key,
|
||||
"note_updated": note_updated,
|
||||
"index_refreshed": index_ok,
|
||||
},
|
||||
)
|
||||
print(pf.to_json())
|
||||
|
||||
return 0
|
||||
|
|
@ -1316,7 +1316,7 @@ class PaperForgeStatusView extends ItemView {
|
|||
const actionsRow = view.createEl('div', { cls: 'paperforge-global-actions' });
|
||||
actionsRow.createEl('div', { cls: 'paperforge-section-label', text: 'Start Working' });
|
||||
const btnsRow = actionsRow.createEl('div', { cls: 'paperforge-global-actions-row' });
|
||||
const hubBtn = btnsRow.createEl('button', { cls: 'paperforge-contextual-btn' });
|
||||
const hubBtn = btnsRow.createEl('button', { cls: 'paperforge-contextual-btn primary' });
|
||||
hubBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\uD83D\uDCC1' });
|
||||
hubBtn.createEl('span', { text: 'Open Literature Hub' });
|
||||
hubBtn.addEventListener('click', () => {
|
||||
|
|
@ -1782,7 +1782,6 @@ class PaperForgeStatusView extends ItemView {
|
|||
// ── Header ──
|
||||
const header = view.createEl('div', { cls: 'paperforge-collection-header' });
|
||||
header.createEl('div', { cls: 'paperforge-collection-title', text: domain });
|
||||
header.createEl('div', { cls: 'paperforge-collection-count', text: totalPapers + ' papers' });
|
||||
|
||||
// ── Workflow Overview (funnel) ──
|
||||
const wfSection = view.createEl('div', { cls: 'paperforge-workflow-overview' });
|
||||
|
|
@ -1845,6 +1844,14 @@ class PaperForgeStatusView extends ItemView {
|
|||
|
||||
// ── Contextual Actions ──
|
||||
const actionsRow = view.createEl('div', { cls: 'paperforge-collection-actions' });
|
||||
const ocrActionBtn = actionsRow.createEl('button', { cls: 'paperforge-contextual-btn primary' });
|
||||
ocrActionBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\u229E' });
|
||||
ocrActionBtn.createEl('span', { text: 'Run OCR' });
|
||||
ocrActionBtn.addEventListener('click', () => {
|
||||
const action = ACTIONS.find(a => a.id === 'paperforge-ocr');
|
||||
if (action) this._runAction(action, ocrActionBtn);
|
||||
});
|
||||
|
||||
const syncBtn = actionsRow.createEl('button', { cls: 'paperforge-contextual-btn' });
|
||||
syncBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\u21BB' });
|
||||
syncBtn.createEl('span', { text: 'Sync Library' });
|
||||
|
|
@ -1852,14 +1859,6 @@ class PaperForgeStatusView extends ItemView {
|
|||
const action = ACTIONS.find(a => a.id === 'paperforge-sync');
|
||||
if (action) this._runAction(action, syncBtn);
|
||||
});
|
||||
|
||||
const ocrActionBtn = actionsRow.createEl('button', { cls: 'paperforge-contextual-btn' });
|
||||
ocrActionBtn.createEl('span', { cls: 'paperforge-contextual-btn-icon', text: '\u229E' });
|
||||
ocrActionBtn.createEl('span', { text: 'Run OCR' });
|
||||
ocrActionBtn.addEventListener('click', () => {
|
||||
const action = ACTIONS.find(a => a.id === 'paperforge-ocr');
|
||||
if (action) this._runAction(action, ocrActionBtn);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Refresh current mode (called on index change, D-09, REFR-01) ── */
|
||||
|
|
@ -2095,21 +2094,27 @@ class PaperForgeStatusView extends ItemView {
|
|||
const leafHandler = this.app.workspace.on('active-leaf-change', () => {
|
||||
clearTimeout(this._leafChangeTimer);
|
||||
this._leafChangeTimer = setTimeout(() => {
|
||||
const resolved = this._resolveModeForFile(this.app.workspace.getActiveFile());
|
||||
const nextMode = resolved.mode;
|
||||
const nextFilePath = resolved.filePath;
|
||||
|
||||
// Clicking inside the dashboard can activate its leaf without changing
|
||||
// the underlying paper/base context. Avoid rebuilding the whole mode
|
||||
// tree in that case, or transient UI state like discussion expansion resets.
|
||||
if (this._currentMode === nextMode && this._currentFilePath === nextFilePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._detectAndSwitch();
|
||||
}, 300);
|
||||
});
|
||||
this._modeSubscribers.push({ event: 'active-leaf-change', ref: leafHandler });
|
||||
|
||||
// D-09: File modification -- filter to formal-library.json only
|
||||
// D-09: File modification -- formal-library.json only (deep-finalize signals completion)
|
||||
const modifyHandler = this.app.vault.on('modify', (file) => {
|
||||
if (file && file.path && file.path.endsWith('formal-library.json')) {
|
||||
this._invalidateIndex(); // D-14: invalidate cache
|
||||
this._refreshCurrentMode();
|
||||
return;
|
||||
}
|
||||
if (file && this._currentPaperEntry && file.path === this._currentPaperEntry.note_path) {
|
||||
this._currentPaperEntry = this._findEntry(this._currentPaperKey);
|
||||
this._refreshCurrentMode();
|
||||
}
|
||||
});
|
||||
this._modeSubscribers.push({ event: 'modify', ref: modifyHandler });
|
||||
|
|
|
|||
|
|
@ -1302,7 +1302,7 @@
|
|||
}
|
||||
|
||||
.paperforge-paper-year::before {
|
||||
content: " \u00B7 ";
|
||||
content: " · ";
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
|
|
@ -2085,21 +2085,19 @@
|
|||
|
||||
/* Collection mode */
|
||||
.paperforge-collection-header {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 0 0 0;
|
||||
margin-bottom: -12px;
|
||||
}
|
||||
|
||||
.paperforge-collection-title {
|
||||
font-size: 16px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.paperforge-collection-count {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
|
||||
.paperforge-global-view {
|
||||
display: flex;
|
||||
|
|
@ -2115,25 +2113,24 @@
|
|||
}
|
||||
|
||||
.paperforge-workflow-overview {
|
||||
margin: 10px 0;
|
||||
padding: 18px;
|
||||
background: color-mix(in srgb, var(--background-primary) 94%, var(--background-primary-alt) 6%);
|
||||
padding: 20px;
|
||||
background: color-mix(in srgb, var(--background-primary) 92%, var(--background-secondary) 8%);
|
||||
border: 1px solid color-mix(in srgb, var(--pf-warm-line) 20%, var(--pf-border));
|
||||
border-radius: calc(var(--pf-radius) + 2px);
|
||||
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-collection-accent) 12%, transparent);
|
||||
box-shadow: 0 1px 2px rgba(20, 18, 14, 0.04), 0 10px 24px rgba(20, 18, 14, 0.04);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.paperforge-workflow-overview:hover {
|
||||
border-color: color-mix(in srgb, var(--pf-warm-line) 42%, var(--pf-border));
|
||||
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-collection-accent) 17%, transparent), 0 2px 10px rgba(20, 18, 14, 0.08);
|
||||
box-shadow: 0 2px 10px rgba(20, 18, 14, 0.08), 0 14px 28px rgba(20, 18, 14, 0.06);
|
||||
}
|
||||
|
||||
.paperforge-workflow-funnel {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr;
|
||||
gap: 8px 6px;
|
||||
align-items: center;
|
||||
gap: 10px 6px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
@container pfpanel (max-width: 380px) {
|
||||
|
|
@ -2143,6 +2140,10 @@
|
|||
.paperforge-workflow-funnel > div:nth-child(4) {
|
||||
display: none;
|
||||
}
|
||||
.paperforge-workflow-stage {
|
||||
max-width: none;
|
||||
justify-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
@container pfpanel (max-width: 220px) {
|
||||
|
|
@ -2152,18 +2153,25 @@
|
|||
.paperforge-workflow-arrow {
|
||||
display: none;
|
||||
}
|
||||
.paperforge-workflow-stage {
|
||||
max-width: none;
|
||||
justify-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.paperforge-workflow-stage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 12px 6px;
|
||||
background: color-mix(in srgb, var(--background-primary-alt) 88%, var(--background-primary) 12%);
|
||||
justify-content: center;
|
||||
padding: 14px 16px;
|
||||
background: color-mix(in srgb, var(--background-primary-alt) 86%, var(--background-primary) 14%);
|
||||
border: 1px solid color-mix(in srgb, var(--pf-collection-accent) 22%, var(--pf-border));
|
||||
border-radius: 8px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: 160px;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.paperforge-workflow-stage-value {
|
||||
|
|
@ -2188,6 +2196,7 @@
|
|||
color: var(--text-faint);
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.paperforge-collection-ocr-header {
|
||||
|
|
|
|||
|
|
@ -262,13 +262,22 @@ def _build_entry(item: dict, vault: Path, paths: dict, domain: str, zotero_dir:
|
|||
write_json(meta_path, meta)
|
||||
title_slug = slugify_filename(item["title"])
|
||||
note_path = paths["literature"] / domain / f"{key} - {title_slug}.md"
|
||||
|
||||
# --- Freeze slug: reuse existing workspace if it exists under a different slug ---
|
||||
workspace_dir = paths["literature"] / domain / f"{key} - {title_slug}"
|
||||
if not workspace_dir.exists():
|
||||
for candidate in (paths["literature"] / domain).glob(f"{key} - *"):
|
||||
if candidate.is_dir():
|
||||
workspace_dir = candidate
|
||||
title_slug = workspace_dir.name.split(" - ", 1)[1] if " - " in workspace_dir.name else title_slug
|
||||
break
|
||||
|
||||
if note_path.parent.exists():
|
||||
for stale_note in note_path.parent.glob(f"{key} - *.md"):
|
||||
if stale_note != note_path:
|
||||
stale_note.unlink()
|
||||
|
||||
# Workspace paths (Phase 26: flat-to-workspace migration)
|
||||
workspace_dir = paths["literature"] / domain / f"{key} - {title_slug}"
|
||||
main_note_path = workspace_dir / f"{key} - {title_slug}.md"
|
||||
deep_reading_file = workspace_dir / "deep-reading.md"
|
||||
target_fulltext = workspace_dir / "fulltext.md"
|
||||
|
|
@ -363,10 +372,24 @@ def _build_entry(item: dict, vault: Path, paths: dict, domain: str, zotero_dir:
|
|||
entry["maturity"] = compute_maturity(entry)
|
||||
entry["next_step"] = compute_next_step(entry)
|
||||
|
||||
# --- Workspace: always create and write to workspace path (Phase 38: no flat fallback) ---
|
||||
existing_text = main_note_path.read_text(encoding="utf-8") if main_note_path.exists() else ""
|
||||
|
||||
main_note_path.write_text(frontmatter_note(entry, existing_text), encoding="utf-8")
|
||||
# Slug already frozen above — for existing notes, update frontmatter only (preserve body)
|
||||
if main_note_path.exists():
|
||||
text = main_note_path.read_text(encoding="utf-8")
|
||||
fm_close = text.find("---\n", 4) # closing --- after opening ---
|
||||
if fm_close != -1:
|
||||
body = text[fm_close + 4:] # everything after frontmatter
|
||||
new_full = frontmatter_note(entry, "")
|
||||
new_fm_close = new_full.find("---\n", 4)
|
||||
if new_fm_close != -1:
|
||||
new_fm = new_full[: new_fm_close + 4] # new frontmatter block with closing ---\n
|
||||
main_note_path.write_text(new_fm + body, encoding="utf-8")
|
||||
else:
|
||||
main_note_path.write_text(new_full, encoding="utf-8")
|
||||
else:
|
||||
main_note_path.write_text(frontmatter_note(entry, text), encoding="utf-8")
|
||||
else:
|
||||
existing_text = note_path.read_text(encoding="utf-8") if note_path.exists() else ""
|
||||
main_note_path.write_text(frontmatter_note(entry, existing_text), encoding="utf-8")
|
||||
|
||||
# Write per-workspace paper-meta.json (Phase 37: internal state outside frontmatter)
|
||||
write_paper_meta(workspace_dir, entry, paperforge_version=PAPERFORGE_VERSION)
|
||||
|
|
|
|||
Loading…
Reference in a new issue