From 3102ba55a0f9945ab7f2b56ba8d50ec5b951a7a3 Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Sun, 10 May 2026 19:14:31 +0800 Subject: [PATCH 1/8] style: strengthen collection and global dashboard hierarchy --- paperforge/plugin/styles.css | 1408 ++++++++++------------------------ 1 file changed, 418 insertions(+), 990 deletions(-) diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css index 842b8a35..dae7f145 100644 --- a/paperforge/plugin/styles.css +++ b/paperforge/plugin/styles.css @@ -8,9 +8,10 @@ font-family: inherit; font-size: 14px; color: var(--text-normal); - padding: 14px; + padding: 14px 14px 56px 14px; height: 100%; overflow-y: auto; + scrollbar-gutter: stable; display: flex; flex-direction: column; gap: 12px; @@ -20,6 +21,10 @@ --pf-border-hover: var(--background-modifier-border-hover); --pf-accent: var(--interactive-accent); --pf-radius: var(--radius-m); + --pf-paper-accent: #6f8780; + --pf-collection-accent: #9a6b52; + --pf-global-accent: #5f7180; + --pf-warm-line: #a28a5d; } /* ── Typography tokens ── */ @@ -380,8 +385,8 @@ } .paperforge-ocr-count-value { - font-size: var(--font-ui-medium); - font-weight: var(--font-bold); + font-size: 18px; + font-weight: 600; color: var(--text-normal); line-height: 1.2; } @@ -1203,6 +1208,7 @@ font-weight: var(--font-semibold); padding: 2px 8px; border-radius: 10px; + border: 1px solid transparent; text-transform: uppercase; letter-spacing: 0.5px; line-height: 1.4; @@ -1210,18 +1216,21 @@ } .paperforge-mode-badge.global { - background: var(--interactive-accent); - color: var(--text-on-accent); + background: color-mix(in srgb, var(--pf-global-accent) 12%, var(--background-secondary)); + border-color: color-mix(in srgb, var(--pf-global-accent) 28%, var(--pf-border)); + color: var(--pf-global-accent); } .paperforge-mode-badge.paper { - background: var(--color-cyan); - color: var(--text-on-accent); + background: color-mix(in srgb, var(--pf-paper-accent) 12%, var(--background-secondary)); + border-color: color-mix(in srgb, var(--pf-paper-accent) 28%, var(--pf-border)); + color: var(--pf-paper-accent); } .paperforge-mode-badge.collection { - background: var(--color-purple); - color: var(--text-on-accent); + background: color-mix(in srgb, var(--pf-collection-accent) 12%, var(--background-secondary)); + border-color: color-mix(in srgb, var(--pf-collection-accent) 28%, var(--pf-border)); + color: var(--pf-collection-accent); } .paperforge-mode-name { @@ -1294,36 +1303,6 @@ flex-wrap: wrap; } -.paperforge-contextual-btn { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 14px; - font-size: var(--font-ui-small); - font-weight: var(--font-semibold); - color: var(--text-normal); - background: var(--background-secondary-alt); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - cursor: pointer; - transition: background 0.15s, color 0.15s; - user-select: none; -} - -.paperforge-contextual-btn:hover { - background: var(--background-modifier-hover); - color: var(--text-accent); -} - -.paperforge-contextual-btn:active { - transform: scale(0.97); -} - -.paperforge-contextual-btn-icon { - font-size: 14px; - line-height: 1; -} - /* ========================================================================== SECTION 16 — Next-Step Recommendation Card ========================================================================== */ @@ -1393,6 +1372,8 @@ display: flex; flex-direction: column; gap: 24px; + max-width: 800px; + margin: 0 auto; } .paperforge-collection-metrics { @@ -1687,65 +1668,170 @@ } /* ========================================================================== - SECTION 39 — Shared Components (Native Light Surface Design) + SECTION 39 — Dashboard Visual System ========================================================================== */ -/* ── Section Label ── */ +/* Shared tokens */ .paperforge-section-label { font-size: 12px; font-weight: 600; color: var(--text-muted); + margin: 0 0 10px 0; + padding-left: 10px; + border-left: 3px solid var(--pf-warm-line); text-transform: uppercase; - letter-spacing: 0.04em; - margin-bottom: 6px; + letter-spacing: 0.06em; + line-height: 1.3; +} + +.paperforge-contextual-btn { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 12px; + background: var(--background-secondary); + border: 1px solid var(--pf-border); + border-radius: var(--radius-s); + color: var(--text-muted); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, color 0.15s; + white-space: nowrap; + user-select: none; +} + +.paperforge-contextual-btn:hover { + background: var(--background-modifier-hover); + border-color: color-mix(in srgb, var(--pf-warm-line) 52%, var(--pf-border-hover)); + color: var(--text-normal); +} + +.paperforge-contextual-btn.running { + opacity: 0.6; + pointer-events: none; +} + +.paperforge-contextual-btn:active { + transform: scale(0.97); +} + +.paperforge-contextual-btn:focus-visible { + outline: 2px solid color-mix(in srgb, var(--interactive-accent) 62%, white 38%); + outline-offset: 2px; +} + +.paperforge-contextual-btn-icon { + font-size: 14px; + line-height: 1; } -/* ── Status Pill ── */ .paperforge-status-pill { display: inline-flex; align-items: center; gap: 4px; - padding: 2px 8px; + padding: 2px 10px; + border: 1px solid var(--pf-border); border-radius: 999px; - background: var(--background-primary-alt); - color: var(--text-muted); font-size: 12px; font-weight: 500; - line-height: 1.4; -} - -.paperforge-status-pill-icon { font-size: 11px; line-height: 1; } - -.paperforge-status-pill.ok { color: var(--text-success); } -.paperforge-status-pill.fail { color: var(--text-error); } -.paperforge-status-pill.pending { color: var(--text-muted); } - -/* ── Cards (only primary content modules) ── */ -.paperforge-paper-overview, -.paperforge-discussion-card { - margin: 8px 0; - padding: 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); -} - -/* ── Paper Overview ── */ -.paperforge-paper-overview-header { margin-bottom: 6px; } - -.paperforge-paper-overview-title { - font-size: 12px; - font-weight: 600; + line-height: 1.5; + background: var(--background-primary-alt); color: var(--text-muted); +} + +.paperforge-status-pill.pending { + background: color-mix(in srgb, var(--pf-paper-accent) 9%, var(--background-primary)); + border-color: color-mix(in srgb, var(--pf-paper-accent) 20%, var(--pf-border)); + color: var(--pf-paper-accent); +} + +.paperforge-status-pill.ok { + background: rgba(var(--background-modifier-success-rgb, 0, 200, 100), 0.12); + border-color: rgba(var(--background-modifier-success-rgb, 0, 200, 100), 0.2); + color: var(--text-success, var(--color-green)); +} + +.paperforge-status-pill.fail { + background: rgba(var(--background-modifier-error-rgb, 255, 80, 80), 0.12); + border-color: rgba(var(--background-modifier-error-rgb, 255, 80, 80), 0.22); + color: var(--text-error); +} + +.paperforge-status-pill-icon { + font-size: 11px; + line-height: 1; +} + +.paperforge-library-snapshot, +.paperforge-system-status, +.paperforge-global-actions { + 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: 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, transform 0.2s; +} + +.paperforge-library-snapshot:hover, +.paperforge-system-status:hover, +.paperforge-global-actions:hover { + border-color: color-mix(in srgb, var(--pf-warm-line) 42%, var(--pf-border)); + box-shadow: 0 2px 10px rgba(20, 18, 14, 0.08), 0 14px 28px rgba(20, 18, 14, 0.06); +} + +/* Paper mode */ +.paperforge-status-strip { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; + padding: 10px 0 8px; + margin: 4px 0 6px; +} + +.paperforge-status-strip-left, +.paperforge-status-strip-right { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.paperforge-paper-overview { + margin: 10px 0; + padding: 18px; + background: var(--background-primary); + border: 1px solid color-mix(in srgb, var(--pf-warm-line) 18%, var(--pf-border)); + border-radius: calc(var(--pf-radius) + 2px); + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-paper-accent) 16%, transparent); + transition: border-color 0.2s, box-shadow 0.2s; +} + +.paperforge-paper-overview:hover { + border-color: color-mix(in srgb, var(--pf-warm-line) 34%, var(--pf-border)); + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-paper-accent) 22%, transparent), 0 2px 8px rgba(0, 0, 0, 0.06); +} + +.paperforge-paper-overview-header { + margin-bottom: 8px; +} + +.paperforge-paper-overview-title, +.paperforge-discussion-title { + font-size: 12px; + font-weight: 700; + color: var(--pf-paper-accent); text-transform: uppercase; - letter-spacing: 0.04em; + letter-spacing: 0.06em; } .paperforge-paper-overview-body { font-size: 14px; - font-weight: 400; - color: var(--text-muted); - line-height: 1.55; + color: var(--text-normal); + line-height: 1.72; } .paperforge-paper-overview-excerpt { @@ -1753,38 +1839,58 @@ word-break: break-word; } -.paperforge-paper-overview-expand { - display: inline-block; - margin-top: 4px; - padding: 2px 0; - background: none; - border: none; - color: var(--text-accent); - font-size: 13px; - font-weight: 400; - cursor: pointer; - transition: color 0.15s; +.paperforge-paper-overview-expand, +.paperforge-discussion-expand, +.paperforge-discussion-viewall { + color: var(--pf-paper-accent); } -.paperforge-paper-overview-expand:hover { +.paperforge-paper-overview-expand { + display: inline-block; + margin-top: 6px; + padding: 3px 0; + background: none; + border: none; + border-bottom: 1px dashed currentColor; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: color 0.15s, border-color 0.15s; +} + +.paperforge-paper-overview-expand:hover, +.paperforge-discussion-expand:hover, +.paperforge-discussion-viewall:hover { color: var(--interactive-accent-hover); } -/* ── Discussion Card ── */ -.paperforge-discussion-header { margin-bottom: 8px; } +.paperforge-paper-overview-expand:hover { + border-color: currentColor; +} -.paperforge-discussion-title { - font-size: 12px; - font-weight: 600; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.04em; +.paperforge-discussion-header { + margin-bottom: 10px; +} + +.paperforge-discussion-card { + margin: 10px 0; + padding: 18px; + background: var(--background-primary); + border: 1px solid color-mix(in srgb, var(--pf-warm-line) 18%, var(--pf-border)); + border-radius: calc(var(--pf-radius) + 2px); + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-paper-accent) 11%, transparent); + transition: border-color 0.2s, box-shadow 0.2s; +} + +.paperforge-discussion-card:hover { + border-color: color-mix(in srgb, var(--pf-warm-line) 34%, var(--pf-border)); + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-paper-accent) 16%, transparent), 0 2px 8px rgba(0, 0, 0, 0.06); } .paperforge-discussion-item { - margin-bottom: 8px; - padding-bottom: 8px; - border-bottom: 1px solid var(--background-modifier-border); + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid var(--pf-border); } .paperforge-discussion-item:last-child { @@ -1803,9 +1909,8 @@ .paperforge-discussion-a { font-size: 14px; - font-weight: 400; - color: var(--text-muted); - line-height: 1.55; + color: var(--text-normal); + line-height: 1.68; } .paperforge-discussion-expand { @@ -1814,71 +1919,93 @@ margin-left: 4px; background: none; border: none; - color: var(--text-accent); - font-size: 13px; - font-weight: 400; + border-bottom: 1px dashed currentColor; + font-size: 12px; + font-weight: 500; cursor: pointer; - transition: color 0.15s; + transition: color 0.15s, border-color 0.15s; } -.paperforge-discussion-expand:hover { color: var(--interactive-accent-hover); } - .paperforge-discussion-viewall { display: block; text-align: right; - margin-top: 4px; - font-size: 13px; - font-weight: 400; - color: var(--text-accent); + margin-top: 6px; + font-size: 12px; + font-weight: 500; cursor: pointer; text-decoration: none; transition: color 0.15s; } -.paperforge-discussion-viewall:hover { color: var(--interactive-accent-hover); } - -/* ── Complete state row ── */ -.paperforge-complete-row { - display: flex; - align-items: center; - gap: 6px; - color: var(--text-success); - font-size: 13px; - font-weight: 400; - line-height: 1.4; - padding: 4px 0; +.paperforge-discussion-viewall:hover { + text-decoration: underline; +} + +.paperforge-paper-files { + display: flex; + gap: 8px; + margin: 10px 0; +} + +.paperforge-status-strip-right .paperforge-contextual-btn { + min-height: 34px; + padding: 7px 12px; + background: color-mix(in srgb, var(--background-secondary) 78%, var(--background-primary)); + border-color: color-mix(in srgb, var(--pf-border) 88%, var(--pf-warm-line)); + color: var(--text-muted); +} + +.paperforge-status-strip-right .paperforge-contextual-btn:hover { + background: color-mix(in srgb, var(--background-modifier-hover) 78%, var(--background-primary)); + border-color: color-mix(in srgb, var(--pf-border-hover) 82%, var(--pf-warm-line)); + color: var(--text-normal); } -/* ── Technical Details disclosure ── */ .paperforge-technical-details { - margin: 4px 0; + margin: 8px 0 4px; } .paperforge-technical-details-toggle { - display: inline-block; - padding: 4px 0; + width: 100%; + min-height: 34px; + padding: 8px 0; background: none; border: none; + border-radius: 0; color: var(--text-muted); - font-size: 13px; - font-weight: 400; + font-size: 12px; + font-weight: 600; cursor: pointer; - user-select: none; + text-align: left; + letter-spacing: 0.02em; transition: color 0.15s; } -.paperforge-technical-details-toggle:hover { color: var(--text-normal); } +.paperforge-technical-details-toggle:hover { + background: none; + border-color: transparent; + color: var(--text-normal); +} .paperforge-technical-details-body { - padding: 6px 0 2px 0; + margin-top: 0; + padding: 8px 0 0; + background: none; + border: none; + border-radius: 0; font-size: 12px; - color: var(--text-faint); } .paperforge-technical-row { display: flex; justify-content: space-between; - padding: 2px 0; + gap: 12px; + padding: 6px 0; + border-bottom: 1px solid color-mix(in srgb, var(--pf-border) 72%, transparent); +} + +.paperforge-technical-row:last-child { + border-bottom: none; } .paperforge-technical-label { @@ -1896,87 +2023,27 @@ text-align: right; } -/* ── Contextual buttons ── */ -.paperforge-contextual-btn { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 3px 8px; - background: var(--interactive-normal); - border: 1px solid var(--background-modifier-border); - color: var(--text-normal); - border-radius: var(--button-radius, 4px); - font-size: 12px; - font-weight: 400; - cursor: pointer; - transition: background 0.15s; - white-space: nowrap; -} - -.paperforge-contextual-btn:hover { - background: var(--interactive-hover); - border-color: var(--background-modifier-border-hover); -} - -.paperforge-contextual-btn.running { - opacity: 0.6; - pointer-events: none; -} - -.paperforge-contextual-btn-icon { - font-size: 14px; - line-height: 1; -} - -.paperforge-contextual-btn.primary { - background: var(--interactive-accent); - color: var(--text-on-accent); - border-color: var(--interactive-accent); - font-weight: 500; -} - -.paperforge-contextual-btn.primary:hover { - opacity: 0.85; -} - -/* ========================================================================== - SECTION 40 — Per-Paper View - ========================================================================== */ - -/* ── Status Strip + Files Row (merged) ── */ -.paperforge-status-strip { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 8px; - padding: 6px 0; - margin: 4px 0; -} - -.paperforge-status-strip-left { - display: flex; - align-items: center; - gap: 6px; -} - -.paperforge-status-strip-right { - display: flex; - align-items: center; - gap: 6px; -} - -/* ── Workflow Toggles (inline, not a card) ── */ .paperforge-workflow-toggles { display: flex; + flex-wrap: wrap; gap: 14px; - padding: 6px 0; + margin: 0 0 10px; + padding: 0 0 10px; + background: none; + border: none; + border-bottom: 1px solid color-mix(in srgb, var(--pf-border) 72%, transparent); + border-radius: 0; } .paperforge-workflow-toggle { display: flex; align-items: center; gap: 6px; + min-height: 34px; + padding: 6px 10px; + background: color-mix(in srgb, var(--background-primary-alt) 88%, var(--background-primary)); + border: 1px solid color-mix(in srgb, var(--pf-border) 82%, var(--pf-paper-accent)); + border-radius: 999px; cursor: pointer; font-size: 13px; color: var(--text-muted); @@ -1992,28 +2059,25 @@ } .paperforge-workflow-toggle-label { - font-weight: 500; + font-weight: 600; color: var(--text-normal); } .paperforge-workflow-toggle-hint { color: var(--text-faint); - font-size: 12px; + font-size: 11px; } -/* ========================================================================== - SECTION 41 — Collection / Base View - ========================================================================== */ - +/* Collection mode */ .paperforge-collection-header { - margin-bottom: 8px; + margin-bottom: 10px; } .paperforge-collection-title { font-size: 16px; font-weight: 600; color: var(--text-normal); - line-height: 1.35; + line-height: 1.3; } .paperforge-collection-count { @@ -2022,628 +2086,67 @@ margin-top: 2px; } +.paperforge-global-view { + display: flex; + flex-direction: column; + gap: 18px; + max-width: 800px; + margin: 0 auto; +} + +.paperforge-global-view .paperforge-section-label, +.paperforge-workflow-overview .paperforge-section-label { + margin-bottom: 12px; +} + .paperforge-workflow-overview { - margin: 8px 0; - padding: 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); + margin: 10px 0; + padding: 18px; + background: color-mix(in srgb, var(--background-primary) 94%, var(--background-primary-alt) 6%); + 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); + 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); } .paperforge-workflow-funnel { display: flex; align-items: center; - gap: 3px; - flex-wrap: wrap; - justify-content: center; -} - -.paperforge-workflow-stage { - display: flex; - flex-direction: column; - align-items: center; - padding: 6px 12px; - background: var(--background-primary-alt); - border-radius: 6px; - min-width: 52px; -} - -.paperforge-workflow-stage-value { - font-size: 18px; - font-weight: 600; - color: var(--text-normal); - line-height: 1.2; -} - -.paperforge-workflow-stage-label { - font-size: 11px; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.04em; - margin-top: 2px; - text-align: center; -} - -.paperforge-workflow-arrow { - color: var(--text-faint); - font-size: 12px; - flex-shrink: 0; -} - -.paperforge-collection-ocr-header { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 14px; -} - -.paperforge-issue-summary { - margin: 8px 0; - padding: 10px 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-left: 3px solid var(--color-orange, #e67e22); - border-radius: var(--radius-m); -} - -.paperforge-issue-summary .paperforge-section-label { - color: var(--color-orange, #e67e22); -} - -.paperforge-issue-list { - margin: 4px 0 6px 0; -} - -.paperforge-issue-item { - font-size: 14px; - color: var(--text-muted); - padding: 2px 0; -} - -.paperforge-issue-actions { - display: flex; - gap: 8px; - margin-top: 8px; -} - -.paperforge-collection-actions { - display: flex; - gap: 8px; - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--background-modifier-border); -} - -/* ========================================================================== - SECTION 42 — Global / Home View - ========================================================================== */ - -.paperforge-library-snapshot { - padding: 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); -} - -.paperforge-snapshot-pills { - display: flex; - gap: 8px; - flex-wrap: wrap; -} - -.paperforge-snapshot-pill { - display: flex; - flex-direction: column; - align-items: center; - padding: 8px 14px; - background: var(--background-primary-alt); - border-radius: 6px; - min-width: 56px; - flex: 1; -} - -.paperforge-snapshot-value { - font-size: 18px; - font-weight: 600; - color: var(--text-normal); - line-height: 1.2; -} - -.paperforge-snapshot-label { - font-size: 11px; - color: var(--text-muted); - text-transform: uppercase; - letter-spacing: 0.04em; - margin-top: 2px; - text-align: center; -} - -.paperforge-system-status { - padding: 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); -} - -.paperforge-status-grid { - display: flex; - flex-direction: column; - gap: 1px; -} - -.paperforge-status-row { - display: flex; - align-items: center; - gap: 8px; - padding: 3px 0; -} - -.paperforge-status-dot { - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; - background: var(--background-modifier-border); - transition: background 0.3s; -} - -.paperforge-status-dot.ok { background: var(--text-success); } -.paperforge-status-dot.fail { background: var(--text-error); } - -.paperforge-status-label { - font-size: 14px; - color: var(--text-muted); - min-width: 85px; -} - -.paperforge-status-detail { - font-size: 13px; - color: var(--text-faint); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.paperforge-global-actions { - padding: 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); -} - -.paperforge-global-actions-row { - display: flex; - gap: 8px; -} - -/* ========================================================================== - SECTION 43 — Dark Theme - ========================================================================== */ - -.theme-dark .paperforge-status-pill { - background: var(--background-primary-alt); -} - -.theme-dark .paperforge-status-pill.ok { - color: var(--text-success); -} - -.theme-dark .paperforge-status-pill.fail { - color: var(--text-error); -} - -/* ========================================================================== - SECTION 39 — Dashboard Redesign: Shared Components - ========================================================================== */ - -/* ── Section Label ── */ -.paperforge-section-label { - font-size: 11px; - font-weight: 600; - color: var(--text-muted); - margin: 0 0 10px 0; - padding-left: 10px; - border-left: 3px solid var(--interactive-accent); - text-transform: uppercase; - letter-spacing: 0.06em; - line-height: 1.3; -} - -/* ── Contextual Button ── */ -.paperforge-contextual-btn { - display: inline-flex; - align-items: center; - gap: 5px; - padding: 5px 12px; - background: var(--background-secondary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-s); - color: var(--text-muted); - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, color 0.15s; - white-space: nowrap; -} - -.paperforge-contextual-btn:hover { - background: var(--background-modifier-hover); - border-color: var(--interactive-accent); - color: var(--text-normal); -} - -.paperforge-contextual-btn.running { - opacity: 0.6; - pointer-events: none; -} - -.paperforge-contextual-btn-icon { - font-size: 14px; - line-height: 1; -} - -/* ========================================================================== - SECTION 40 — Per-Paper View (Reading Companion) - ========================================================================== */ - -/* ── Status Strip ── */ -.paperforge-status-strip { - display: flex; gap: 6px; - padding: 8px 0; - margin: 6px 0; -} - -.paperforge-status-pill { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 10px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - line-height: 1.5; - background: var(--background-primary-alt); - color: var(--text-muted); -} - -.paperforge-status-pill.ok { - background: rgba(var(--background-modifier-success-rgb, 0, 200, 100), 0.12); - color: var(--text-success, var(--color-green)); -} - -.paperforge-status-pill.fail { - background: rgba(var(--background-modifier-error-rgb, 255, 80, 80), 0.12); - color: var(--text-error); -} - -.paperforge-status-pill.pending { - background: var(--background-primary-alt); - color: var(--text-muted); -} - -.paperforge-status-pill-icon { - font-size: 11px; - line-height: 1; -} - -/* Theme dark: softer pill backgrounds */ -.theme-dark .paperforge-status-pill.ok { - background: rgba(100, 200, 120, 0.15); -} -.theme-dark .paperforge-status-pill.fail { - background: rgba(255, 100, 100, 0.15); -} - -/* ── Paper Overview Card ── */ -.paperforge-paper-overview { - margin: 10px 0; - padding: 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; -} - -.paperforge-paper-overview::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-paper-overview:hover::before { - opacity: 1; -} - -.paperforge-paper-overview-header { - margin-bottom: 8px; -} - -.paperforge-paper-overview-title { - font-size: 11px; - font-weight: 700; - color: var(--text-accent); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.paperforge-paper-overview-body { - font-size: 13.5px; - color: var(--text-muted); - line-height: 1.65; -} - -.paperforge-paper-overview-excerpt { - white-space: pre-wrap; - word-break: break-word; -} - -.paperforge-paper-overview-expand { - display: inline-block; - margin-top: 6px; - padding: 3px 0; - background: none; - border: none; - border-bottom: 1px dashed var(--text-accent); - color: var(--text-accent); - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: color 0.15s, border-color 0.15s; -} - -.paperforge-paper-overview-expand:hover { - color: var(--interactive-accent-hover); - border-color: var(--interactive-accent-hover); -} - -/* ── Discussion Card ── */ -.paperforge-discussion-card { - margin: 10px 0; - padding: 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; -} - -.paperforge-discussion-card::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-discussion-card:hover::before { - opacity: 1; -} - -.paperforge-discussion-header { - margin-bottom: 10px; -} - -.paperforge-discussion-title { - font-size: 11px; - font-weight: 700; - color: var(--text-accent); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.paperforge-discussion-item { - margin-bottom: 10px; - padding-bottom: 10px; - border-bottom: 1px solid var(--background-modifier-border); -} - -.paperforge-discussion-item:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; -} - -.paperforge-discussion-q { - font-size: 13.5px; - font-weight: 600; - color: var(--text-normal); - margin-bottom: 4px; - line-height: 1.5; -} - -.paperforge-discussion-a { - font-size: 13.5px; - color: var(--text-muted); - line-height: 1.6; -} - -.paperforge-discussion-expand { - display: inline; - padding: 0; - margin-left: 4px; - background: none; - border: none; - border-bottom: 1px dashed var(--text-accent); - color: var(--text-accent); - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: color 0.15s; -} - -.paperforge-discussion-expand:hover { - color: var(--interactive-accent-hover); -} - -.paperforge-discussion-viewall { - display: block; - text-align: right; - margin-top: 6px; - font-size: 12px; - font-weight: 500; - color: var(--text-accent); - cursor: pointer; - text-decoration: none; - transition: color 0.15s; -} - -.paperforge-discussion-viewall:hover { - color: var(--interactive-accent-hover); - text-decoration: underline; -} - -/* ── Paper Files Row ── */ -.paperforge-paper-files { - display: flex; - gap: 8px; - margin: 10px 0; -} - -/* ── Technical Details ── */ -.paperforge-technical-details { - margin: 6px 0; -} - -.paperforge-technical-details-toggle { - width: 100%; - padding: 6px 12px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-s); - color: var(--text-faint); - font-size: 11px; - font-weight: 500; - cursor: pointer; - text-align: left; - transition: background 0.15s, border-color 0.15s, color 0.15s; -} - -.paperforge-technical-details-toggle:hover { - background: var(--background-modifier-hover); - border-color: var(--background-modifier-border-hover); - color: var(--text-muted); -} - -.paperforge-technical-details-body { - margin-top: 4px; - padding: 8px 12px; - background: var(--background-primary-alt); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-s); - font-size: 11px; -} - -.paperforge-technical-row { - display: flex; - justify-content: space-between; - padding: 2px 0; -} - -.paperforge-technical-label { - color: var(--text-faint); - flex-shrink: 0; - margin-right: 12px; -} - -.paperforge-technical-value { - color: var(--text-muted); - max-width: 60%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: right; -} - -/* ========================================================================== - SECTION 41 — Collection / Base View (Batch Workspace) - ========================================================================== */ - -/* ── Collection Header ── */ -.paperforge-collection-header { - margin-bottom: 10px; -} - -.paperforge-collection-title { - font-size: 16px; - font-weight: 600; - color: var(--text-normal); - line-height: 1.3; -} - -.paperforge-collection-count { - font-size: 12px; - color: var(--text-muted); - margin-top: 2px; -} - -/* ── Workflow Overview ── */ -.paperforge-workflow-overview { - margin: 10px 0; - padding: 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; -} - -.paperforge-workflow-overview::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-workflow-overview:hover::before { - opacity: 1; -} - -.paperforge-workflow-funnel { - display: flex; - align-items: center; - gap: 3px; flex-wrap: wrap; - justify-content: center; + justify-content: flex-start; } .paperforge-workflow-stage { display: flex; flex-direction: column; align-items: center; - padding: 8px 12px; - background: var(--background-primary-alt); - border-radius: 6px; - min-width: 56px; + padding: 12px 14px; + background: color-mix(in srgb, var(--background-primary-alt) 88%, var(--background-primary) 12%); + border: 1px solid color-mix(in srgb, var(--pf-collection-accent) 22%, var(--pf-border)); + border-radius: 8px; + min-width: 72px; } .paperforge-workflow-stage-value { - font-size: 20px; + font-size: 26px; font-weight: 700; - color: var(--text-accent); + color: var(--pf-collection-accent); line-height: 1.2; - letter-spacing: -0.3px; + letter-spacing: -0.5px; } .paperforge-workflow-stage-label { - font-size: 10px; + font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.3px; - margin-top: 2px; + margin-top: 4px; text-align: center; } @@ -2653,7 +2156,6 @@ flex-shrink: 0; } -/* ── OCR Pipeline in Base ── */ .paperforge-collection-ocr-header { display: flex; align-items: center; @@ -2661,77 +2163,60 @@ margin-bottom: 14px; } -/* ── Issue Summary (serious blockers only) ── */ .paperforge-issue-summary { margin: 10px 0; padding: 12px 14px; background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-left: 3px solid var(--color-orange); - border-radius: var(--radius-m); + border: 1px solid var(--pf-border); + border-left: 3px solid var(--pf-warm-line); + border-radius: var(--pf-radius); } .paperforge-issue-summary .paperforge-section-label { - color: var(--color-orange); + border-left-color: var(--pf-warm-line); + color: var(--text-normal); } .paperforge-issue-list { - margin: 4px 0 6px 0; + margin: 8px 0 12px 0; + display: flex; + flex-direction: column; + gap: 6px; } .paperforge-issue-item { font-size: 13px; - color: var(--text-muted); - padding: 2px 0; -} - -.paperforge-issue-actions { + font-weight: 500; + color: var(--text-normal); + padding: 6px 10px; + background: color-mix(in srgb, var(--background-secondary) 50%, transparent); + border-radius: calc(var(--pf-radius) - 2px); display: flex; - gap: 8px; - margin-top: 8px; + align-items: center; } -/* ── Collection Actions ── */ +.paperforge-issue-actions, .paperforge-collection-actions { display: flex; gap: 8px; - margin-top: 10px; - padding-top: 10px; - border-top: 1px solid var(--background-modifier-border); } -/* ========================================================================== - SECTION 42 — Global / Home View (System Homepage) - ========================================================================== */ - -/* ── Library Snapshot ── */ -.paperforge-library-snapshot { - padding: 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; +.paperforge-issue-actions { + margin-top: 8px; } -.paperforge-library-snapshot::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-library-snapshot:hover::before { - opacity: 1; +.paperforge-collection-actions { + align-items: center; + flex-wrap: wrap; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid color-mix(in srgb, var(--pf-border) 82%, var(--pf-warm-line)); } +/* Global mode */ .paperforge-snapshot-pills { display: flex; - gap: 8px; + gap: 10px; flex-wrap: wrap; } @@ -2739,55 +2224,32 @@ display: flex; flex-direction: column; align-items: center; - padding: 10px 14px; - background: var(--background-primary-alt); - border-radius: 6px; - min-width: 60px; + 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-global-accent) 22%, var(--pf-border)); + border-radius: 8px; + min-width: 72px; flex: 1; } .paperforge-snapshot-value { - font-size: 20px; + font-size: 26px; font-weight: 700; - color: var(--text-accent); + color: var(--pf-global-accent); line-height: 1.2; - letter-spacing: -0.3px; + letter-spacing: -0.5px; } .paperforge-snapshot-label { - font-size: 10px; + font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.3px; - margin-top: 2px; + margin-top: 4px; text-align: center; } -/* ── System Status ── */ -.paperforge-system-status { - padding: 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; -} - -.paperforge-system-status::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-system-status:hover::before { - opacity: 1; -} - .paperforge-status-grid { display: flex; flex-direction: column; @@ -2797,8 +2259,8 @@ .paperforge-status-row { display: flex; align-items: center; - gap: 8px; - padding: 4px 0; + gap: 10px; + padding: 6px 0; } .paperforge-status-dot { @@ -2806,12 +2268,12 @@ height: 7px; border-radius: 50%; flex-shrink: 0; - background: var(--background-modifier-border); + background: var(--pf-border); transition: background 0.3s; } .paperforge-status-dot.ok { - background: var(--color-green); + background: var(--text-success); } .paperforge-status-dot.fail { @@ -2825,123 +2287,89 @@ } .paperforge-status-detail { - font-size: 12px; + font-size: 13px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -/* ── Global Actions ── */ -.paperforge-global-actions { - padding: 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; -} - -.paperforge-global-actions::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-global-actions:hover::before { - opacity: 1; -} - .paperforge-global-actions-row { display: flex; gap: 8px; + flex-wrap: wrap; } -/* ========================================================================== - SECTION 43 — Workflow Toggle Checkboxes (per-paper) - ========================================================================== */ - -.paperforge-workflow-toggles { - display: flex; - gap: 14px; - padding: 12px 16px; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: var(--radius-m); - margin: 10px 0; - transition: border-color 0.2s, box-shadow 0.2s; - position: relative; -} - -.paperforge-workflow-toggles::before { - content: ''; - position: absolute; - inset: -1px; - border-radius: inherit; - opacity: 0; - box-shadow: 0 2px 8px rgba(0,0,0,0.08); - transition: opacity 0.2s; - pointer-events: none; -} - -.paperforge-workflow-toggles:hover::before { - opacity: 1; -} - -.paperforge-workflow-toggle { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; +.paperforge-collection-actions .paperforge-contextual-btn, +.paperforge-global-actions-row .paperforge-contextual-btn { + min-height: 36px; + padding: 8px 14px; font-size: 13px; - color: var(--text-muted); - user-select: none; -} - -.paperforge-workflow-checkbox { - width: 15px; - height: 15px; - margin: 0; - cursor: pointer; - accent-color: var(--interactive-accent); -} - -.paperforge-workflow-toggle-label { font-weight: 600; - color: var(--text-normal); } -.paperforge-workflow-toggle-hint { - color: var(--text-faint); +.paperforge-global-actions-row .paperforge-contextual-btn.primary { + background: color-mix(in srgb, var(--pf-global-accent) 86%, black 14%); + border-color: color-mix(in srgb, var(--pf-global-accent) 90%, black 10%); + color: var(--text-on-accent); +} + +.paperforge-collection-actions .paperforge-contextual-btn.primary { + background: color-mix(in srgb, var(--pf-collection-accent) 84%, black 16%); + border-color: color-mix(in srgb, var(--pf-collection-accent) 88%, black 12%); + color: var(--text-on-accent); +} + +.paperforge-global-actions-row .paperforge-contextual-btn.primary:hover { + background: color-mix(in srgb, var(--pf-global-accent) 78%, black 22%); + border-color: color-mix(in srgb, var(--pf-global-accent) 82%, black 18%); +} + +.paperforge-collection-actions .paperforge-contextual-btn.primary:hover { + background: color-mix(in srgb, var(--pf-collection-accent) 76%, black 24%); + border-color: color-mix(in srgb, var(--pf-collection-accent) 80%, black 20%); +} + +.paperforge-collection-view .paperforge-ocr-section { + padding: 18px 18px 16px; + background: color-mix(in srgb, var(--background-secondary) 90%, var(--background-primary) 10%); + border-color: color-mix(in srgb, var(--pf-warm-line) 20%, var(--background-modifier-border)); + box-shadow: 0 1px 2px rgba(20, 18, 14, 0.04), 0 8px 20px rgba(20, 18, 14, 0.04); +} + +.paperforge-collection-view .paperforge-ocr-count-label { font-size: 11px; } -/* ========================================================================== - SECTION 44 — Dark Theme Specific Overrides - ========================================================================== */ - -.theme-dark .paperforge-paper-overview, -.theme-dark .paperforge-discussion-card, -.theme-dark .paperforge-workflow-overview, +.theme-dark .paperforge-collection-view .paperforge-ocr-section, .theme-dark .paperforge-library-snapshot, .theme-dark .paperforge-system-status, .theme-dark .paperforge-global-actions, -.theme-dark .paperforge-workflow-toggles { - background: var(--background-primary); - border-color: var(--background-modifier-border); +.theme-dark .paperforge-workflow-overview { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); } -.theme-dark .paperforge-paper-overview::before, -.theme-dark .paperforge-discussion-card::before, -.theme-dark .paperforge-workflow-overview::before, -.theme-dark .paperforge-library-snapshot::before, -.theme-dark .paperforge-system-status::before, -.theme-dark .paperforge-global-actions::before, -.theme-dark .paperforge-workflow-toggles::before { - box-shadow: 0 2px 12px rgba(0,0,0,0.3); +/* Dark theme overrides */ +.theme-dark .paperforge-status-pill.ok { + background: rgba(100, 200, 120, 0.15); +} + +.theme-dark .paperforge-status-pill.fail { + background: rgba(255, 100, 100, 0.15); +} + +.theme-dark .paperforge-library-snapshot:hover, +.theme-dark .paperforge-system-status:hover, +.theme-dark .paperforge-global-actions:hover, +.theme-dark .paperforge-workflow-overview:hover, +.theme-dark .paperforge-workflow-toggles:hover { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); +} + +.theme-dark .paperforge-paper-overview:hover { + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-paper-accent) 22%, transparent), 0 2px 12px rgba(0, 0, 0, 0.3); +} + +.theme-dark .paperforge-discussion-card:hover { + box-shadow: inset 3px 0 0 color-mix(in srgb, var(--pf-paper-accent) 16%, transparent), 0 2px 12px rgba(0, 0, 0, 0.3); } From 6b52d44db99598435511ab1cfcf9e23b2ce6df33 Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Sun, 10 May 2026 19:15:10 +0800 Subject: [PATCH 2/8] docs: align dashboard ux contract with refined ui --- docs/ux-contract.md | 6 +++--- paperforge/plugin/main.js | 29 ++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/ux-contract.md b/docs/ux-contract.md index f3b04c2d..f4dfc155 100644 --- a/docs/ux-contract.md +++ b/docs/ux-contract.md @@ -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" | --- diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index ef4f11d6..01891377 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -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', () => { @@ -1817,6 +1817,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' }); @@ -1824,14 +1832,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) ── */ @@ -2067,6 +2067,17 @@ 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); }); From cc9a5baa98b43469b7bea2ecf311143ff2e86da8 Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Sun, 10 May 2026 19:15:28 +0800 Subject: [PATCH 3/8] docs: add dashboard ui refinement specs and plans --- .../plans/2026-05-10-dashboard-ui-refine.md | 398 ++++++++++++++++++ .../2026-05-10-dashboard-visual-refinement.md | 161 +++++++ .../2026-05-10-dashboard-ui-refine-design.md | 395 +++++++++++++++++ 3 files changed, 954 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-10-dashboard-ui-refine.md create mode 100644 docs/superpowers/plans/2026-05-10-dashboard-visual-refinement.md create mode 100644 docs/superpowers/specs/2026-05-10-dashboard-ui-refine-design.md diff --git a/docs/superpowers/plans/2026-05-10-dashboard-ui-refine.md b/docs/superpowers/plans/2026-05-10-dashboard-ui-refine.md new file mode 100644 index 00000000..2f589d75 --- /dev/null +++ b/docs/superpowers/plans/2026-05-10-dashboard-ui-refine.md @@ -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 +``` diff --git a/docs/superpowers/plans/2026-05-10-dashboard-visual-refinement.md b/docs/superpowers/plans/2026-05-10-dashboard-visual-refinement.md new file mode 100644 index 00000000..62ab09fa --- /dev/null +++ b/docs/superpowers/plans/2026-05-10-dashboard-visual-refinement.md @@ -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) diff --git a/docs/superpowers/specs/2026-05-10-dashboard-ui-refine-design.md b/docs/superpowers/specs/2026-05-10-dashboard-ui-refine-design.md new file mode 100644 index 00000000..8cd7e11a --- /dev/null +++ b/docs/superpowers/specs/2026-05-10-dashboard-ui-refine-design.md @@ -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. From 365498bded0a97bd884d0b647ee28a2792ea8e73 Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Sun, 10 May 2026 20:16:33 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20dashboard=20ui=20refine=20=E2=80=94?= =?UTF-8?q?=20arrow=20icons,=20maillard=20colors,=20responsive=20grid,=20s?= =?UTF-8?q?etup=20bugfix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dashboard UI: - Replace text expand/collapse with SVG arrow + clickable gradient container - Map agent_platform key to display name (OpenCode, not opencode) - Maillard/Morandi palette for OCR progress bars (pending brown, done deep green) - Shorten 'Needs Attention' to 'Attention' to prevent label wrap - Responsive workflow overview: CSS Grid + container queries (narrow: 2x2, wide: 4x1 with max-width 160px stop) Setup Wizard: - Fix Phase 7 verification crash (undefined agents_src/agents_dst variables) Skill restructure: - Move chart-reading references under references/ directory - Remove old pf-*.md script files (deployed via skill_deploy service) - Add new reference files (deep-reading, paper-qa, paper-resolution, save-session) --- paperforge/plugin/main.js | 46 +++- paperforge/plugin/styles.css | 106 +++++--- paperforge/setup_wizard.py | 2 +- paperforge/skills/literature-qa/SKILL.md | 80 ++++++ .../chart-reading/GSEA富集图.md | 0 .../{ => references}/chart-reading/INDEX.md | 0 .../chart-reading/ROC与PR曲线.md | 0 .../chart-reading/Western Blot条带图.md | 0 .../chart-reading/免疫荧光定量图.md | 0 .../chart-reading/折线图与时间序列.md | 0 .../chart-reading/散点图与气泡图.md | 0 .../chart-reading/显微照片与SEM图.md | 0 .../chart-reading/条形图与误差棒.md | 0 .../chart-reading/桑基图与弦图.md | 0 .../chart-reading/森林图与Meta分析.md | 0 .../chart-reading/火山图与曼哈顿图.md | 0 .../chart-reading/热图与聚类图.md | 0 .../chart-reading/生存曲线.md | 0 .../chart-reading/箱式图与小提琴图.md | 0 .../chart-reading/组织学半定量图.md | 0 .../chart-reading/网络图与通路图.md | 0 .../chart-reading/蛋白质结构图.md | 0 .../chart-reading/降维图(PCA-tSNE-UMAP).md | 0 .../chart-reading/雷达图与漏斗图.md | 0 .../literature-qa/references/deep-reading.md | 171 +++++++++++++ .../deep-subagent.md} | 0 .../literature-qa/references/paper-qa.md | 61 +++++ .../references/paper-resolution.md | 173 +++++++++++++ .../literature-qa/references/save-session.md | 55 ++++ .../skills/literature-qa/scripts/pf-deep.md | 237 ------------------ .../skills/literature-qa/scripts/pf-end.md | 67 ----- .../skills/literature-qa/scripts/pf-ocr.md | 102 -------- .../skills/literature-qa/scripts/pf-paper.md | 107 -------- .../skills/literature-qa/scripts/pf-status.md | 94 ------- .../skills/literature-qa/scripts/pf-sync.md | 85 ------- 35 files changed, 648 insertions(+), 738 deletions(-) create mode 100644 paperforge/skills/literature-qa/SKILL.md rename paperforge/skills/literature-qa/{ => references}/chart-reading/GSEA富集图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/INDEX.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/ROC与PR曲线.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/Western Blot条带图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/免疫荧光定量图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/折线图与时间序列.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/散点图与气泡图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/显微照片与SEM图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/条形图与误差棒.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/桑基图与弦图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/森林图与Meta分析.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/火山图与曼哈顿图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/热图与聚类图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/生存曲线.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/箱式图与小提琴图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/组织学半定量图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/网络图与通路图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/蛋白质结构图.md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/降维图(PCA-tSNE-UMAP).md (100%) rename paperforge/skills/literature-qa/{ => references}/chart-reading/雷达图与漏斗图.md (100%) create mode 100644 paperforge/skills/literature-qa/references/deep-reading.md rename paperforge/skills/literature-qa/{prompt_deep_subagent.md => references/deep-subagent.md} (100%) create mode 100644 paperforge/skills/literature-qa/references/paper-qa.md create mode 100644 paperforge/skills/literature-qa/references/paper-resolution.md create mode 100644 paperforge/skills/literature-qa/references/save-session.md delete mode 100644 paperforge/skills/literature-qa/scripts/pf-deep.md delete mode 100644 paperforge/skills/literature-qa/scripts/pf-end.md delete mode 100644 paperforge/skills/literature-qa/scripts/pf-ocr.md delete mode 100644 paperforge/skills/literature-qa/scripts/pf-paper.md delete mode 100644 paperforge/skills/literature-qa/scripts/pf-status.md delete mode 100644 paperforge/skills/literature-qa/scripts/pf-sync.md diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index 01891377..9f732d23 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -1460,11 +1460,20 @@ class PaperForgeStatusView extends ItemView { const truncated = extracted.length > 200 ? extracted.slice(0, 200) + '...' : extracted; excerptEl.setText(truncated); if (extracted.length > 200) { - const expandBtn = body.createEl('button', { cls: 'paperforge-paper-overview-expand', text: '展开' }); + const expandContainer = body.createEl('div', { cls: 'paperforge-expand-container' }); + const expandBtn = expandContainer.createEl('button', { cls: 'paperforge-expand-icon', title: '展开/收起' }); + + // Insert arrow SVG + expandBtn.innerHTML = ''; + let expanded = false; - expandBtn.addEventListener('click', () => { + + // Make the whole container clickable + expandContainer.addEventListener('click', () => { excerptEl.setText(expanded ? truncated : extracted); - expandBtn.setText(expanded ? '展开' : '收起'); + expandBtn.innerHTML = expanded + ? '' + : ''; expanded = !expanded; }); } @@ -1556,14 +1565,23 @@ class PaperForgeStatusView extends ItemView { : (qa.answer || ''); aEl.createEl('span', { cls: 'paperforge-discussion-a-text', text: '解答:' + shortAnswer }); if (qa.answer && qa.answer.length > 150) { - const expandLink = aEl.createEl('button', { cls: 'paperforge-discussion-expand', text: '展开' }); + const expandContainer = aEl.createEl('div', { cls: 'paperforge-expand-container' }); + const expandBtn = expandContainer.createEl('button', { cls: 'paperforge-expand-icon', title: '展开/收起' }); + + // Insert arrow SVG + expandBtn.innerHTML = ''; + let expanded = false; - expandLink.addEventListener('click', () => { + + // Make the whole container clickable + expandContainer.addEventListener('click', () => { const textSpan = aEl.querySelector('.paperforge-discussion-a-text'); if (textSpan) { textSpan.setText(expanded ? ('解答:' + shortAnswer) : ('解答:' + qa.answer)); } - expandLink.setText(expanded ? '展开' : '收起'); + expandBtn.innerHTML = expanded + ? '' + : ''; expanded = !expanded; }); } @@ -1692,9 +1710,19 @@ class PaperForgeStatusView extends ItemView { }); // Show "Run in [agent_platform]" label below the button (DASH-02) - const platform = this.app.plugins.plugins['paperforge']?.settings?.agent_platform || 'opencode'; + const platformKey = this.app.plugins.plugins['paperforge']?.settings?.agent_platform || 'opencode'; + const AGENTS = { + 'opencode': 'OpenCode', + 'claude': 'Claude Code', + 'cursor': 'Cursor', + 'github_copilot': 'GitHub Copilot', + 'windsurf': 'Windsurf', + 'codex': 'Codex', + 'cline': 'Cline' + }; + const platformName = AGENTS[platformKey] || platformKey; const labelEl = card.createEl('div', { cls: 'paperforge-agent-platform-label' }); - labelEl.setText(t('run_in_agent').replace('{0}', platform)); + labelEl.setText(t('run_in_agent').replace('{0}', platformName)); } else if (nextStep === 'ready') { const trigger = card.createEl('button', { cls: 'paperforge-next-step-trigger' }); trigger.createEl('span', { text: '✓ ' + info.label }); @@ -1806,7 +1834,7 @@ class PaperForgeStatusView extends ItemView { { cls: 'pending', value: ocrPending, label: 'Pending' }, { cls: 'active', value: ocrProcessing, label: 'Processing' }, { cls: 'done', value: ocrDone, label: 'Done' }, - { cls: 'failed', value: ocrFailed, label: 'Needs Attention' }, + { cls: 'failed', value: ocrFailed, label: 'Attention' }, ]; for (const l of ocrLabels) { const cnt = ocrCounts.createEl('div', { cls: 'paperforge-ocr-count' }); diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css index dae7f145..f861ef3a 100644 --- a/paperforge/plugin/styles.css +++ b/paperforge/plugin/styles.css @@ -15,6 +15,8 @@ display: flex; flex-direction: column; gap: 12px; + container-type: inline-size; + container-name: pfpanel; --pf-surface: var(--background-secondary); --pf-surface-alt: var(--background-primary-alt); --pf-border: var(--background-modifier-border); @@ -348,7 +350,11 @@ } .paperforge-progress-seg.pending { - background: var(--text-faint); + background: #e2d9c8; +} + +.theme-dark .paperforge-progress-seg.pending { + background: #4a4135; } .paperforge-progress-seg.active { @@ -357,7 +363,11 @@ } .paperforge-progress-seg.done { - background: var(--color-green); + background: #3e5a47; +} + +.theme-dark .paperforge-progress-seg.done { + background: #4f735b; } .paperforge-progress-seg.failed { @@ -1839,33 +1849,49 @@ word-break: break-word; } -.paperforge-paper-overview-expand, -.paperforge-discussion-expand, +.paperforge-expand-container, .paperforge-discussion-viewall { color: var(--pf-paper-accent); } -.paperforge-paper-overview-expand { - display: inline-block; - margin-top: 6px; - padding: 3px 0; - background: none; - border: none; - border-bottom: 1px dashed currentColor; - font-size: 12px; - font-weight: 500; +.paperforge-expand-container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + margin-top: 8px; + padding: 4px 0; cursor: pointer; - transition: color 0.15s, border-color 0.15s; + transition: background-color 0.2s ease, color 0.2s ease; + border-radius: var(--pf-radius); + background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--pf-surface-alt) 50%, transparent)); } -.paperforge-paper-overview-expand:hover, -.paperforge-discussion-expand:hover, +.paperforge-expand-icon { + background: none; + border: none; + padding: 0; + margin: 0; + color: inherit; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; /* Let container handle clicks */ + opacity: 0.6; + transition: opacity 0.2s ease; +} + +.paperforge-expand-container:hover, .paperforge-discussion-viewall:hover { color: var(--interactive-accent-hover); } -.paperforge-paper-overview-expand:hover { - border-color: currentColor; +.paperforge-expand-container:hover { + background: color-mix(in srgb, var(--pf-surface-alt) 80%, transparent); +} + +.paperforge-expand-container:hover .paperforge-expand-icon { + opacity: 1; } .paperforge-discussion-header { @@ -1913,18 +1939,7 @@ line-height: 1.68; } -.paperforge-discussion-expand { - display: inline; - padding: 0; - margin-left: 4px; - background: none; - border: none; - border-bottom: 1px dashed currentColor; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: color 0.15s, border-color 0.15s; -} + .paperforge-discussion-viewall { display: block; @@ -2115,22 +2130,40 @@ } .paperforge-workflow-funnel { - display: flex; + display: grid; + grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr; + gap: 8px 6px; align-items: center; - gap: 6px; - flex-wrap: wrap; - justify-content: flex-start; +} + +@container pfpanel (max-width: 380px) { + .paperforge-workflow-funnel { + grid-template-columns: 1fr auto 1fr; + } + .paperforge-workflow-funnel > div:nth-child(4) { + display: none; + } +} + +@container pfpanel (max-width: 220px) { + .paperforge-workflow-funnel { + grid-template-columns: 1fr; + } + .paperforge-workflow-arrow { + display: none; + } } .paperforge-workflow-stage { display: flex; flex-direction: column; align-items: center; - padding: 12px 14px; + padding: 12px 6px; background: color-mix(in srgb, var(--background-primary-alt) 88%, var(--background-primary) 12%); border: 1px solid color-mix(in srgb, var(--pf-collection-accent) 22%, var(--pf-border)); border-radius: 8px; - min-width: 72px; + min-width: 0; + width: 100%; } .paperforge-workflow-stage-value { @@ -2148,6 +2181,7 @@ letter-spacing: 0.3px; margin-top: 4px; text-align: center; + word-break: break-word; } .paperforge-workflow-arrow { diff --git a/paperforge/setup_wizard.py b/paperforge/setup_wizard.py index f6f58a8a..6bd28b01 100644 --- a/paperforge/setup_wizard.py +++ b/paperforge/setup_wizard.py @@ -1012,7 +1012,7 @@ def headless_setup( "OCR dir": (pf_path / "ocr").exists(), "paperforge.json": pf_json.exists(), "Obsidian plugin": (vault / ".obsidian" / "plugins" / "paperforge" / "main.js").exists(), - "AGENTS.md": True if not agents_src.exists() else agents_dst.exists(), + "AGENTS.md": (vault / "AGENTS.md").exists(), } failed = [k for k, v in checks.items() if not v] if failed: diff --git a/paperforge/skills/literature-qa/SKILL.md b/paperforge/skills/literature-qa/SKILL.md new file mode 100644 index 00000000..2ad91877 --- /dev/null +++ b/paperforge/skills/literature-qa/SKILL.md @@ -0,0 +1,80 @@ +--- +name: literature-qa +description: > + 学术文献精读与问答。MUST trigger when user types /pf-deep, /pf-paper, /pf-end, + or says "精读", "深度阅读", "读一下", "查一下这篇论文", "帮我看看这篇文章", + "这篇文章讲了什么", "保存讨论", "结束讨论", "做这篇文献的问答", + or any phrase about reading/analyzing papers in their Zotero library. + 支持 Zotero key, DOI, 标题, 作者/年份, 自然语言描述定位论文. +license: Apache-2.0 +compatibility: opencode +--- + +# Literature QA — 学术文献精读与问答 + +## 路由表 + +Agent 读到本文件后,首先根据用户意图路由到对应的 reference 文件: + +| 用户意图 | 典型输入 | 加载文件 | +|---------|---------|---------| +| 三阶段精读(指定论文) | `/pf-deep `, `pf-deep `, "精读 XXX", "深度阅读 XXX" | [references/deep-reading.md](references/deep-reading.md) | +| 三阶段精读(查看队列) | `/pf-deep`(无参数), "精读队列", "有哪些该读了" | [references/deep-reading.md](references/deep-reading.md) | +| 论文问答 | `/pf-paper `, `pf-paper `, "做这篇的问答", "帮我看看 XXX" | [references/paper-qa.md](references/paper-qa.md) | +| 保存讨论记录 | `/pf-end`, `pf-end`, "保存", "结束讨论", "完成讨论" | [references/save-session.md](references/save-session.md) | + +> **重要:** 加载 reference 文件后,严格按照该文件的流程执行。不要跳过任何步骤。 + +## 论文定位(所有路由共用,先执行) + +详见 [references/paper-resolution.md](references/paper-resolution.md)。 + +**核心原则:二路定位。Python 干确定的活,Agent 干理解的活。路径全在环境变量里,零硬编码。** + +### 第零步:加载环境变量(每条路由启动时跑一次) + +```pwsh +python -m paperforge.worker.paper_resolver env --vault . --shell pwsh | Invoke-Expression +``` + +此后所有路径用环境变量引用,不再拼接: + +| 变量 | 含义 | +| ------------------ | --------------------------- | +| `$env:PF_VAULT` | vault 根目录 | +| `$env:PF_INDEX_PATH` | formal-library.json 路径 | +| `$env:PF_LITERATURE_DIR` | formal notes 目录 | +| `$env:PF_OCR_DIR` | OCR 结果目录 | + +### 第一步:判断输入类型,选择路径 + +| 输入特征 | 执行命令 | +|---------|---------| +| 8位 key | `python -m paperforge.worker.paper_resolver resolve-key --vault "$env:PF_VAULT"` | +| DOI | `python -m paperforge.worker.paper_resolver resolve-doi "" --vault "$env:PF_VAULT"` | +| 标题片段 | `python -m paperforge.worker.paper_resolver search --title "..." --vault "$env:PF_VAULT"` | +| 作者+年份 | `python -m paperforge.worker.paper_resolver search --author "Smith" --year 2024 --vault "$env:PF_VAULT"` | +| 自然语言 | Agent 读 `$env:PF_INDEX_PATH` 指向的 formal-library.json | + +### 第二步:处理结果 + +- **Python 返回匹配:** 直接使用返回的 workspace(`formal_note_path` 等由 config 动态计算) +- **Python 搜不到:** Agent grep fallback:`rg -l "zotero_key:.*ABC" "$env:PF_LITERATURE_DIR/"` +- **自然语言搜不到:** 告知用户 "未找到,请确认或先运行 `paperforge sync`" +- **命中多篇:** 列出候选清单让用户选 + +## 文件结构 + +``` +literature-qa/ +├── SKILL.md ← 本文件(路由入口) +├── references/ +│ ├── deep-reading.md ← 精读工作流 +│ ├── paper-qa.md ← 问答工作流 +│ ├── save-session.md ← 保存记录工作流 +│ ├── paper-resolution.md ← 论文定位详细协议 +│ ├── deep-subagent.md ← 子代理提示词模板 +│ └── chart-reading/ ← 19 个图表类型阅读指南 +└── scripts/ + └── ld_deep.py ← 精读引擎(Python) +``` diff --git a/paperforge/skills/literature-qa/chart-reading/GSEA富集图.md b/paperforge/skills/literature-qa/references/chart-reading/GSEA富集图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/GSEA富集图.md rename to paperforge/skills/literature-qa/references/chart-reading/GSEA富集图.md diff --git a/paperforge/skills/literature-qa/chart-reading/INDEX.md b/paperforge/skills/literature-qa/references/chart-reading/INDEX.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/INDEX.md rename to paperforge/skills/literature-qa/references/chart-reading/INDEX.md diff --git a/paperforge/skills/literature-qa/chart-reading/ROC与PR曲线.md b/paperforge/skills/literature-qa/references/chart-reading/ROC与PR曲线.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/ROC与PR曲线.md rename to paperforge/skills/literature-qa/references/chart-reading/ROC与PR曲线.md diff --git a/paperforge/skills/literature-qa/chart-reading/Western Blot条带图.md b/paperforge/skills/literature-qa/references/chart-reading/Western Blot条带图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/Western Blot条带图.md rename to paperforge/skills/literature-qa/references/chart-reading/Western Blot条带图.md diff --git a/paperforge/skills/literature-qa/chart-reading/免疫荧光定量图.md b/paperforge/skills/literature-qa/references/chart-reading/免疫荧光定量图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/免疫荧光定量图.md rename to paperforge/skills/literature-qa/references/chart-reading/免疫荧光定量图.md diff --git a/paperforge/skills/literature-qa/chart-reading/折线图与时间序列.md b/paperforge/skills/literature-qa/references/chart-reading/折线图与时间序列.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/折线图与时间序列.md rename to paperforge/skills/literature-qa/references/chart-reading/折线图与时间序列.md diff --git a/paperforge/skills/literature-qa/chart-reading/散点图与气泡图.md b/paperforge/skills/literature-qa/references/chart-reading/散点图与气泡图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/散点图与气泡图.md rename to paperforge/skills/literature-qa/references/chart-reading/散点图与气泡图.md diff --git a/paperforge/skills/literature-qa/chart-reading/显微照片与SEM图.md b/paperforge/skills/literature-qa/references/chart-reading/显微照片与SEM图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/显微照片与SEM图.md rename to paperforge/skills/literature-qa/references/chart-reading/显微照片与SEM图.md diff --git a/paperforge/skills/literature-qa/chart-reading/条形图与误差棒.md b/paperforge/skills/literature-qa/references/chart-reading/条形图与误差棒.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/条形图与误差棒.md rename to paperforge/skills/literature-qa/references/chart-reading/条形图与误差棒.md diff --git a/paperforge/skills/literature-qa/chart-reading/桑基图与弦图.md b/paperforge/skills/literature-qa/references/chart-reading/桑基图与弦图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/桑基图与弦图.md rename to paperforge/skills/literature-qa/references/chart-reading/桑基图与弦图.md diff --git a/paperforge/skills/literature-qa/chart-reading/森林图与Meta分析.md b/paperforge/skills/literature-qa/references/chart-reading/森林图与Meta分析.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/森林图与Meta分析.md rename to paperforge/skills/literature-qa/references/chart-reading/森林图与Meta分析.md diff --git a/paperforge/skills/literature-qa/chart-reading/火山图与曼哈顿图.md b/paperforge/skills/literature-qa/references/chart-reading/火山图与曼哈顿图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/火山图与曼哈顿图.md rename to paperforge/skills/literature-qa/references/chart-reading/火山图与曼哈顿图.md diff --git a/paperforge/skills/literature-qa/chart-reading/热图与聚类图.md b/paperforge/skills/literature-qa/references/chart-reading/热图与聚类图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/热图与聚类图.md rename to paperforge/skills/literature-qa/references/chart-reading/热图与聚类图.md diff --git a/paperforge/skills/literature-qa/chart-reading/生存曲线.md b/paperforge/skills/literature-qa/references/chart-reading/生存曲线.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/生存曲线.md rename to paperforge/skills/literature-qa/references/chart-reading/生存曲线.md diff --git a/paperforge/skills/literature-qa/chart-reading/箱式图与小提琴图.md b/paperforge/skills/literature-qa/references/chart-reading/箱式图与小提琴图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/箱式图与小提琴图.md rename to paperforge/skills/literature-qa/references/chart-reading/箱式图与小提琴图.md diff --git a/paperforge/skills/literature-qa/chart-reading/组织学半定量图.md b/paperforge/skills/literature-qa/references/chart-reading/组织学半定量图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/组织学半定量图.md rename to paperforge/skills/literature-qa/references/chart-reading/组织学半定量图.md diff --git a/paperforge/skills/literature-qa/chart-reading/网络图与通路图.md b/paperforge/skills/literature-qa/references/chart-reading/网络图与通路图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/网络图与通路图.md rename to paperforge/skills/literature-qa/references/chart-reading/网络图与通路图.md diff --git a/paperforge/skills/literature-qa/chart-reading/蛋白质结构图.md b/paperforge/skills/literature-qa/references/chart-reading/蛋白质结构图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/蛋白质结构图.md rename to paperforge/skills/literature-qa/references/chart-reading/蛋白质结构图.md diff --git a/paperforge/skills/literature-qa/chart-reading/降维图(PCA-tSNE-UMAP).md b/paperforge/skills/literature-qa/references/chart-reading/降维图(PCA-tSNE-UMAP).md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/降维图(PCA-tSNE-UMAP).md rename to paperforge/skills/literature-qa/references/chart-reading/降维图(PCA-tSNE-UMAP).md diff --git a/paperforge/skills/literature-qa/chart-reading/雷达图与漏斗图.md b/paperforge/skills/literature-qa/references/chart-reading/雷达图与漏斗图.md similarity index 100% rename from paperforge/skills/literature-qa/chart-reading/雷达图与漏斗图.md rename to paperforge/skills/literature-qa/references/chart-reading/雷达图与漏斗图.md diff --git a/paperforge/skills/literature-qa/references/deep-reading.md b/paperforge/skills/literature-qa/references/deep-reading.md new file mode 100644 index 00000000..3563a5d7 --- /dev/null +++ b/paperforge/skills/literature-qa/references/deep-reading.md @@ -0,0 +1,171 @@ +# 三阶段精读 + +Keshav 三阶段组会式精读。触发后执行以下工作流。 + +> **路径说明:** 本文件中的 `scripts/ld_deep.py` 相对于本 skill 目录。Agent 运行时 skill 目录由平台注入(通常为 `/paperforge/skills/literature-qa/`)。如不确定,AI 应从 SKILL.md 所在目录推断。 + +--- + +## 前置条件检查 + +执行前确认: +- [ ] 已完成论文定位(参考 [paper-resolution.md](paper-resolution.md)),拿到 zotero_key 和 workspace +- [ ] `analyze: true`(在 formal note frontmatter 中,resolver 返回的 workspace 里可查到) +- [ ] `ocr_status: done`(在 resolver 返回的 workspace 里可查到) + +如果前置条件不满足,告知用户并停止。 + +--- + +## 执行流程 + +### Step 1: Prepare(机械操作,跑脚本) + +```bash +python scripts/ld_deep.py prepare --vault . --key +``` + +返回 JSON 解析: +- `status: "ok"` → 继续 +- `status: "error"` → 报告 `message` 给用户,停止 + +Prepare 做的事情(Agent 不需要关心细节): +- 检查 analyze 和 ocr_status +- 生成 figure-map.json 和 chart-type-map.json +- 在 formal note 中插入 `## 🔍 精读` 骨架 + +读 formal note 确认骨架已插入。 + +--- + +### Step 2: Pass 1 — 概览 + +只填 `### Pass 1: 概览` 区域。不碰 Pass 2/3。 + +**填写内容:** + +- **一句话总览**:论文类型 + 核心发现,一句话。 +- **5 Cs 快速评估**: + - **Category**(类型):RCT / 队列研究 / 病例对照 / 综述 / 基础研究 / ... + - **Context**(上下文):该领域当前共识,本文要解决什么问题 + - **Correctness**(合理性初判):初步直觉,逻辑是否有明显漏洞 + - **Contributions**(贡献):1-3 条 + - **Clarity**(清晰度):写作质量,图表可读性 +- **Figure 导读**(基于 fulltext.md 浏览各图 caption): + - 关键主图:列出并一句话概括每个主图要证明什么 + - 证据转折点:哪个 figure 是叙事的关键转折 + - 需要重点展开的 supplementary:如果有 + - 关键表格:列出 + +填完立即保存 formal note。 + +--- + +### Step 3: Pass 2 — 精读还原 + +填 `### Pass 2: 精读还原` 区域。**按 figure 顺序逐个处理。** + +#### 图表类型定位(两步) + +**Step A: Python 给建议(快速初筛)** +```bash +python scripts/ld_deep.py chart-type-scan --vault . --key +``` +输出每个 figure 的关键词命中结果。这只是建议,Agent 不要盲信。 + +**Step B: Agent 读 caption 做最终判断** + +对每个 figure: +1. 读该 figure 的 caption(来自 fulltext.md 或 figure-map.json) +2. 根据 caption 内容,对照 [chart-reading/INDEX.md](chart-reading/INDEX.md) 判断图表类型 +3. 如果 Python 建议和 Agent 判断不一致 → 以 Agent 判断为准 +4. 如果无法确定类型 → 跳过 chart guide,按通用 figure 结构分析 +5. 确定类型后,读对应的 chart-reading 指南(如 `chart-reading/条形图与误差棒.md`),按指南中的检查清单分析 + +#### 每张 Figure 的子标题(固定,不可少) + +按以下格式填入 formal note 中该 figure 的 callout block: + +``` +**图像定位与核心问题**:页码 + 要回答什么问题 +**方法与结果**:实验设计/数据来源/技术手段。核心数据、趋势、对比。 +**图表质量审查**:按 chart-reading 指南检查坐标轴、单位、误差棒、统计标注等。 +**作者解释**:作者在正文中对该图的解读 +**我的理解**:自己的理解(区分于作者解释) +**疑点/局限**:读图时发现的疑问,用 `> [!warning]` 突出 +``` + +#### 每张 Table 的子标题 + +``` +回答什么问题、关键字段/分组、主要结果、我的理解、疑点/局限 +``` + +#### 每张 figure 填完立即保存,再处理下一张。 + +#### 所有 figure/table 处理完后,填: + +**关键方法补课**:简要解释不熟悉的实验技术(1-2 项即可) + +**主要发现与新意**: +- 发现 1:...(来源:Figure X) +- 发现 2:...(来源:Figure Y / Table Z) + +保存。 + +--- + +### Step 4: Postprocess(跑校验脚本,修正问题) + +```bash +python scripts/ld_deep.py postprocess-pass2 --figures --format text +``` + +- 输出 `OK` → 继续 +- 输出错误 → 按错误提示修正(包含行号),修正后重新跑 +- 最多 3 轮修正。3 轮后仍失败 → 报告剩余错误给用户 + +--- + +### Step 5: Pass 3 — 深度理解 + +填 `### Pass 3: 深度理解` 区域。基于 Pass 1/2 已写的内容。 + +**填写内容:** + +- **假设挑战与隐藏缺陷**:隐含假设;如果放宽某个假设结论还成立吗;缺少哪些关键引用;实验/分析技术潜在问题 +- **哪些结论扎实,哪些仍存疑**: + - **较扎实**:... + - **仍存疑**:...(用 `> [!warning]`) +- **Discussion 与 Conclusion 怎么读**:作者真正完成了什么;哪些地方有拔高;哪些是推测 +- **对我的启发**:研究设计上;figure 组织上;方法组合上;未来工作想法 +- **遗留问题**:...(用 `> [!question]`) + +保存。 + +--- + +### Step 6: Final Validation + +```bash +python scripts/ld_deep.py validate-note --fulltext +``` + +- 输出 `OK` → 告知用户精读完成 +- 输出错误 → 修正缺失项,不报告成功直到通过 + +--- + +## Callout 格式规则 + +- `> [!important]`:每个 main finding +- `> [!warning]`:疑问、局限、证据边界、仍存疑条目 +- `> [!question]`:遗留问题 +- **间距:** 相邻 callout block 之间必须有空行,否则 Obsidian 会合并 + - 正确:`> [!important] A\n\n> [!important] B` + - 错误:`> [!important] A\n> [!important] B` + +## Supplementary 规则 + +- 默认不逐张展开 supplementary figure/table +- 仅在以下情况纳入:对主结论形成关键支撑、补足方法可信度、限制主文结论解释范围、作者在正文中明显依赖该补充材料 diff --git a/paperforge/skills/literature-qa/prompt_deep_subagent.md b/paperforge/skills/literature-qa/references/deep-subagent.md similarity index 100% rename from paperforge/skills/literature-qa/prompt_deep_subagent.md rename to paperforge/skills/literature-qa/references/deep-subagent.md diff --git a/paperforge/skills/literature-qa/references/paper-qa.md b/paperforge/skills/literature-qa/references/paper-qa.md new file mode 100644 index 00000000..626f23e0 --- /dev/null +++ b/paperforge/skills/literature-qa/references/paper-qa.md @@ -0,0 +1,61 @@ +# 论文问答 + +交互式论文 Q&A 工作台。不强制要求 OCR,但 OCR 完成后回答更准确。 + +--- + +## 前置条件 + +- [ ] 已完成论文定位(参考 [paper-resolution.md](paper-resolution.md)),拿到 zotero_key 和 workspace +- [ ] OCR 完成(推荐但非强制) + +--- + +## 执行流程 + +### Step 1: 加载论文 + +1. 确认 workspace 路径 +2. 读 `fulltext.md`(如果存在)作为主要回答依据 +3. 读 formal note frontmatter 获取元数据(标题、作者、期刊、年份) +4. 如果 fulltext.md 不存在,告知用户 "OCR 文本不可用,回答将基于元数据和公开信息" + +### Step 2: 显示论文信息 + +``` +已加载论文: [title] ([year], [journal]) +作者: [authors] +Zotero Key: [key] +领域: [domain] +OCR 状态: [done / 不可用] +结束对话时说 "保存" 即可保存讨论。 +请问有什么问题? +``` + +### Step 3: 进入 Q&A 模式 + +- 等待用户提问 +- 每次回答后等待下一个问题 +- 持续到用户说 "保存"、"结束"、"完成" 等关键词 + +--- + +## 回答原则 + +- **严格基于** fulltext.md 中的文本内容回答 +- 引用原文时标注来源页码/章节(如 "第 3 页,Methods 部分") +- 用中文(简体中文)回答 +- 论文中未提及的内容,明确说明 "论文中未提及该内容" +- 需要结合论文以外知识的问题,说明 "该问题需要结合论文以外的知识" + +--- + +## 切换模式 + +用户在当前对话中可以说 "精读这篇文章" 切换到 deep-reading 模式。此时加载 [deep-reading.md](deep-reading.md) 执行精读流程。 + +--- + +## 保存记录 + +用户说 "保存"、"结束"、"完成"、"保存讨论" 时,加载 [save-session.md](save-session.md) 执行保存。不要自动保存。 diff --git a/paperforge/skills/literature-qa/references/paper-resolution.md b/paperforge/skills/literature-qa/references/paper-resolution.md new file mode 100644 index 00000000..763425eb --- /dev/null +++ b/paperforge/skills/literature-qa/references/paper-resolution.md @@ -0,0 +1,173 @@ +# 论文定位协议 + +本文件定义如何将用户输入(key / DOI / 标题 / 作者年份 / 自然语言)解析为论文 workspace。所有子流程(deep-reading, paper-qa, save-session)共用此协议。 + +## 核心原则 + +1. **确定性输入走 Python。** key、DOI、标题片段、作者+年份 —— 这些是机器能精确处理的。 +2. **自然语言走 Agent。** "关于骨再生的那篇"、"去年那篇Nature" —— 需要 AI 理解语义。 +3. **Python 搜不到时 Agent 兜底。** 不是报错,是换个方式再试一次。 +4. **所有路径从环境变量获取。** 每条路由启动时先跑 `python -m paperforge.worker.paper_resolver env --vault . --shell pwsh | Invoke-Expression`。此后 `$env:PF_VAULT`、`$env:PF_LITERATURE_DIR`、`$env:PF_OCR_DIR`、`$env:PF_INDEX_PATH` 全程可用。不写死任何目录名。 + +--- + +## 前置:获取 vault 路径配置(每个路由启动时跑一次) + +```bash +python -m paperforge.worker.paper_resolver paths --vault . +``` + +返回示例(所有路径均由 `paperforge.json` 动态计算): +```json +{ + "ok": true, + "data": { + "vault_root": "/path/to/vault", + "index_path": "/PaperForge/indexes/formal-library.json", + "literature_dir": "/", + "ocr_dir": "/PaperForge/ocr" + } +} +``` + +**后续所有路径操作使用此输出中的值,不要自己拼接。** + +--- + +## 输入类型判断 + +### 类型 1: Zotero Key(8位字符) + +**识别规则:** 8位字母数字组合,如 `XGT9Z257`、`ABC12345` + +**执行命令:** +```bash +python -m paperforge.worker.paper_resolver resolve-key --vault . +``` + +**返回示例(路径由 paperforge.json 配置决定,不固定):** +```json +{ + "ok": true, + "data": { + "match": { + "key": "ABC12345", + "title": "TGF-beta in Bone Regeneration", + "domain": "骨科", + "formal_note_path": "...", + "ocr_path": "...", + "fulltext_path": "...", + "ocr_status": "done" + } + } +} +``` + +**返回 `"match": null` 时:** Agent fallback — 用 `paths` 命令获取的 `literature_dir` 路径 grep frontmatter: +```bash +rg -l "zotero_key:.*ABC12345" / +``` + +--- + +### 类型 2: DOI + +**识别规则:** 以 `10.` 开头的标准 DOI 格式,可能带 URL 前缀 + +**执行命令:** +```bash +python -m paperforge.worker.paper_resolver resolve-doi "10.1016/j.jse.2018.01.001" --vault . +``` + +**返回格式同类型1。** 返回的路径直接使用,不自己拼接。 + +--- + +### 类型 3: 标题片段 + +**识别规则:** 看起来像论文标题的文本(含学术关键词,非自然语言问句) + +**执行命令:** +```bash +python -m paperforge.worker.paper_resolver search --title "Predictive findings on MRI" --vault . +``` + +**返回示例:** +```json +{ + "ok": true, + "data": { + "matches": [{ "key": "...", "title": "...", "formal_note_path": "...", ... }], + "count": 3 + } +} +``` + +--- + +### 类型 4: 作者 + 年份 + +**识别规则:** 包含作者名(英文姓)和年份 + +**执行命令:** +```bash +python -m paperforge.worker.paper_resolver search --author "Smith" --year 2024 --vault . +``` + +--- + +### 类型 5: 自然语言 + +**识别规则:** 中文自然语言描述,如 "关于骨再生的那篇"、"去年Nature上那篇讲TGF的" + +**Agent 操作:** +1. 用 `paths` 命令获取的 `index_path` 读 `formal-library.json` +2. 理解用户意图中的关键信息:主题词、年份、期刊、领域 +3. 在 JSON 的 `title`、`domain`、`journal`、`abstract` 字段中搜索匹配 +4. 如果 formal-library.json 无结果,用 `paths` 命令获取的 `literature_dir` grep formal notes: + ```bash + rg -i -l "骨再生|bone regeneration" / --include "*.md" -g "!*.canvas" + ``` +5. 读匹配的 frontmatter 确认是目标论文 + +--- + +## 多篇匹配处理 + +当搜索返回多个匹配时,Agent 必须列出候选清单,让用户选择: + +``` +找到 3 篇匹配的论文: + +[1] ABC12345 — TGF-beta in Bone Regeneration (2024, 骨科, OCR: done) +[2] DEF67890 — Bone Healing After Fracture (2023, 骨科, OCR: pending) +[3] GHI11111 — Scaffold Design for Bone Repair (2024, 骨科, OCR: done) + +请输入编号选择,或 refine 搜索词。 +``` + +**格式要求:** +- 编号 + key + title + (year, domain, ocr_status) +- 不要只列标题或只列 key + +--- + +## Fallback 顺序 + +``` +输入 + │ + ├── 看起来像 key/DOI/标题/作者年份? + │ └── YES → Python paper_resolver + │ ├── 有结果 → 使用 + │ └── 无结果 → Agent grep fallback + │ ├── 有结果 → 使用 + │ └── 无结果 → 告知用户 + │ + └── 看起来像自然语言? + └── Agent 读 formal-library.json + ├── 有结果 → 列出/使用 + └── 无结果 → Agent grep fallback + ├── 有结果 → 使用 + └── 无结果 → 告知用户 +``` diff --git a/paperforge/skills/literature-qa/references/save-session.md b/paperforge/skills/literature-qa/references/save-session.md new file mode 100644 index 00000000..de023e21 --- /dev/null +++ b/paperforge/skills/literature-qa/references/save-session.md @@ -0,0 +1,55 @@ +# 保存讨论记录 + +将 paper-qa 会话中的 Q&A 记录持久化到论文工作区。 + +--- + +## 触发条件 + +- 用户显式说 "保存"、"保存记录"、"结束"、"完成讨论"、"save" +- 或显式输入 `/pf-end` +- 不要自动触发 + +--- + +## 执行 + +### Step 1: 收集 Q&A 对 + +汇总本次 paper-qa 会话中所有 Q&A,序列化为 JSON 数组: + +```json +[ + { + "question": "用户的问题", + "answer": "Agent 的回答", + "source": "user_question", + "timestamp": "2026-05-10T12:00:00+08:00" + } +] +``` + +`source` 为 `"user_question"`(用户提问)或 `"agent_analysis"`(Agent 主动分析)。 + +### Step 2: 调用 discussion 模块 + +```bash +python -m paperforge.worker.discussion record \ + --vault . \ + --agent pf-paper \ + --model "" \ + --qa-pairs '' +``` + +### Step 3: 确认结果 + +CLI 返回 `{"status": "ok", ...}` → 告知用户记录已保存。 + +返回 `{"status": "error"}` → 记录错误,重试一次。仍失败则告知用户。 + +--- + +## 注意事项 + +- 仅 paper-qa 会话需要记录。deep-reading 的内容直接写入 formal note,不需要通过本文件。 +- 如果无法从 formal-library.json 找到论文 domain/title,记录失败不应影响用户使用。 diff --git a/paperforge/skills/literature-qa/scripts/pf-deep.md b/paperforge/skills/literature-qa/scripts/pf-deep.md deleted file mode 100644 index 3d2af01c..00000000 --- a/paperforge/skills/literature-qa/scripts/pf-deep.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -name: pf-deep -description: Complete three-pass deep reading of an academic paper (Keshav method). Requires OCR fulltext. Searches by Zotero key, title, DOI, or PMID. -allowed-tools: [Read, Bash, Edit] ---- - -# pf-deep - -## Purpose - -基于单篇论文的组会式精读入口。 - -1. 解析 `pf-deep ` 中的查询词 -2. 支持 Zotero key、标题片段、DOI、PMID、关键词 -3. 优先搜索本地 Zotero 并锁定单篇论文 -4. 绑定该论文对应的: - - `/PaperForge/ocr//fulltext.md` - - `/PaperForge/ocr//meta.json` - - `//.../KEY - Title.md` -5. 在正式文献卡片中检查或创建 `## 精读` -6. 以"研究思路 + figure-by-figure"方式一次性完成精读写回 - -## CLI Equivalent - -```bash -# 准备阶段(间接) -python .opencode/skills/pf-deep/scripts/ld_deep.py prepare --vault "" --key -# 返回 JSON:{status, formal_note, fulltext_md, figures, tables} -``` - -> `pf-deep` 是 **Agent 层命令**,通过 Python 代码自动检测论文状态,无需先行 CLI 准备。 - -## Detection(自动检测,无需手动 sync) - -启动时,Agent 执行以下 Python 检测命令,代码会自动判断是否需要精读: - -```bash -python .opencode/skills/pf-deep/scripts/ld_deep.py prepare --vault "" --key -``` - -返回 JSON: -- `status: "ok"` → 就绪,可以开始精读 -- `status: "error"` → 被阻塞(`message` 说明原因:analyze=false / OCR 未完成 / 未找到论文) - -Agent 根据返回的 `status` 决定是否进入精读流程,不自行读取 frontmatter。 - -**队列模式**(无参数时自动检测):Agent 运行: -```bash -python .opencode/skills/pf-deep/scripts/ld_deep.py queue --vault "" -``` -代码自动扫描 canonical index 中 `analyze=true` 且 `deep_reading_status=pending` 的论文,按 OCR 状态分组。 - -## Arguments - -| 参数 | 必需 | 说明 | -|------|------|------| -| `` | 是(queue 模式除外) | Zotero key、标题片段、DOI、PMID 或关键词 | -| `queue` | 否 | 启动批量精读队列模式 | - -### 参数说明 - -1. 如果输入看起来像 8 位 Zotero key,则直接按 key 解析。 -2. 否则先在本地 Zotero 中搜索标题/摘要。 -3. 若命中唯一结果或明显最佳结果,则直接载入。 -4. 若存在多个合理候选,则先列候选清单再让用户选。 -5. 不要强迫用户先知道 Zotero key。 - -## Example - -### 单篇精读(已知 key) - -```bash -pf-deep XGT9Z257 -pf-deep Predictive findings on magnetic resonance imaging -pf-deep 10.1016/j.jse.2018.01.001 -``` - -### 无参数:自动检测队列 - -```bash -pf-deep -``` - -当不提供具体 key/标题时,agent 自动检测精读队列: - -1. 运行 `python .opencode/skills/pf-deep/scripts/ld_deep.py queue --vault ""` 扫描队列 -2. 解析输出的 JSON,按 OCR 状态分组展示 -3. 由用户选择篇目 - -无需先跑 `paperforge sync`。 - -## Output - -Agent 在正式笔记中创建或更新 `## 精读` 区域,包含: - -- **Pass 1: 概览** — 一句话总览、5 Cs 快速评估、Figure 导读 -- **Pass 2: 精读还原** — Figure-by-Figure 解析、Table-by-Table 解析、关键方法补课、主要发现与新意 -- **Pass 3: 深度理解** — 假设挑战与隐藏缺陷、结论扎实性评估、Discussion 解读、个人启发、遗留问题 - -## Error Handling - -### OCR 未完成 -- **表现**:Agent 提示 `ocr_status` 不是 `done` -- **解决**:先运行 `paperforge ocr`,确认 `meta.json` 中 `ocr_status` 变为 `done` - -### 内容已存在(覆盖确认) -- **表现**:正式笔记中已存在 `## 精读` 区域且包含非占位符的实质内容 -- **处理**:Agent **必须**询问用户:追加 / 覆盖 / 跳过 - -### 未找到论文 -- **表现**:Zotero key 无效或搜索无结果 -- **解决**:确认 key 正确,或尝试用标题片段搜索 - -## 精读结构参考 - -### 执行原则 - -- `pf-deep` 对用户来说是一次触发直接完成。 -- 内部逻辑分两步: - 1. 先生成 `## 精读` 骨架和 figure 标题位 - 2. 再补全所有空段 -- 后续再次运行时(用户选择追加): - - 只补空段,不覆盖已有内容 - -### 精读定位 - -这不是综述提取,也不是信息摘录。目标是模拟高水平博士/博士后组会讲解单篇论文的学习型精读。 - -主线必须是: - -1. 文章整体研究思路 -2. 主文 figure 逐张解析 -3. 关键方法补课 -4. 主要发现、新意、疑点与启发 - -### Supplementary 规则 - -- 默认不逐张展开 supplementary figure/table。 -- 仅在以下情况下纳入: - - 对主结论形成关键支撑 - - 补足方法可信度 - - 限制主文结论的解释范围 - - 作者在正文中明显依赖该补充材料 - -### 标准骨架 - -```md -## 精读 - -**证据边界**:区分三层信息:`论文结果`、`作者解释`、`我的理解/推断`。 - -### Pass 1: 概览 - -**一句话总览** -(待补充) - -**5 Cs 快速评估** -- **Category**(类型): -- **Context**(上下文): -- **Correctness**(合理性初判): -- **Contributions**(贡献): -- **Clarity**(清晰度): - -**Figure 导读** -- 关键主图: -- 证据转折点: -- 需要重点展开的 supplementary: -- 关键表格: - -### Pass 2: 精读还原 - -#### Figure-by-Figure 解析 -(每张 figure 下方按以下顺序填写) -- **图像定位与核心问题**:页码 + 要回答什么问题 -- **方法与结果**:方法 + 结果 -- **作者解释**:作者对该图的解读 -- **我的理解**:自己的理解(区分于作者解释) -- **在全文中的作用**:该图在整体故事线中的位置 -- **疑点 / 局限**:读图时发现的疑问 - -#### Table-by-Table 解析 -(如有重要表格,按同样结构展开) - -#### 关键方法补课 -- 方法 1: -- 方法 2: - -#### 主要发现与新意 -**主要发现** -- 发现 1: -- 发现 2: - -### Pass 3: 深度理解 - -#### 假设挑战与隐藏缺陷 -- 隐含假设: -- 如果放宽某个假设,结论还成立吗? -- 缺少哪些关键引用? -- 实验/分析技术的潜在问题: - -#### 哪些结论扎实,哪些仍存疑 -**较扎实** -- - -**仍存疑** -- - -#### Discussion 与 Conclusion 怎么读 -- 作者真正完成了什么: -- 哪些地方有拔高: -- 哪些地方是推测: - -#### 对我的启发 -- 研究设计上: -- figure 组织上: -- 方法组合上: -- 未来工作想法: - -#### 遗留问题 -**遗留问题** -- -``` - -### Figure 节要求 - -每个 figure 小节按以下顺序填写: - -- **图像定位与核心问题**:页码 + 要回答什么问题 -- **方法与结果**:方法 + 结果 -- **作者解释**:作者对该图的解读 -- **我的理解**:自己的理解(区分于作者解释) -- **在全文中的作用**:该图在整体故事线中的位置 -- **疑点 / 局限**:读图时发现的疑问(可酌情用 `> [!warning]` 突出) - -## See Also - -- [pf-paper](pf-paper.md) — 快速摘要与问答 diff --git a/paperforge/skills/literature-qa/scripts/pf-end.md b/paperforge/skills/literature-qa/scripts/pf-end.md deleted file mode 100644 index 0f3a0f04..00000000 --- a/paperforge/skills/literature-qa/scripts/pf-end.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: pf-end -description: Save the paper Q&A discussion record. Triggered when user says "保存" "结束" "save" or types $pf-end. Summarizes all Q&A pairs and writes them to the paper's ai/discussion directory via the discussion module. -allowed-tools: [Read, Bash] ---- - -# pf-end - -## Purpose - -结束当前论文对话并保存讨论记录。需要用户**显式要求**时才执行,不自动触发。 - -1. 汇总本次对话中所有 Q&A 对 -2. 通过 `discussion.record_session()` 写入论文工作区的 `ai/` 目录 -3. 告知用户记录已保存 - -## Trigger - -用户说以下任一关键词时执行(全平台通用): -- `保存` / `记录` / `保存记录` -- `结束` / `完成` -- `save discussion` / `save` / `done` - -也可以显式指定 key: -- `{prefix}pf-end `(OpenCode) -- `保存 `(全平台) - -如果未指定 key,则使用当前已加载的论文 key。 - -## Save Format - -将会话期间的所有 Q&A 序列化为 JSON 数组: - -```json -{ - "question": "用户的问题", - "answer": "Agent 的回答", - "source": "user_question", - "timestamp": "2026-05-06T12:00:00+08:00" -} -``` - -`source` 为 `"user_question"`(用户提问)或 `"agent_analysis"`(Agent 主动分析)。 - -## Command - -```bash -python -m paperforge.worker.discussion record \ - --vault "" \ - --agent pf-paper \ - --model "" \ - --qa-pairs '' -``` - -## Verification - -CLI 返回: -```json -{"status": "ok", "json_path": "Literature/{domain}/{key} - {title}/ai/discussion.json", "md_path": "..."} -``` - -如果 `status` 为 `"error"`,记录错误信息并重试,不要跳过。 - -## See Also - -- [pf-paper](pf-paper.md) — 论文 Q&A 工作台 -- [pf-deep](pf-deep.md) — 完整三阶段精读 diff --git a/paperforge/skills/literature-qa/scripts/pf-ocr.md b/paperforge/skills/literature-qa/scripts/pf-ocr.md deleted file mode 100644 index 000611a3..00000000 --- a/paperforge/skills/literature-qa/scripts/pf-ocr.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -name: pf-ocr -description: Process the PDF OCR queue for formal notes marked do_ocr: true. Uploads PDFs to PaddleOCR API and extracts fulltext with figures. -allowed-tools: [Bash] ---- - -# /pf-ocr - -## Purpose - -处理正式笔记 frontmatter 中 `do_ocr: true` 的 PDF OCR 队列。 - -`paperforge ocr` 会自动读取 `paperforge.json` 定位 ocr 目录和 worker 脚本,运行 OCR 并自动诊断结果。 - -## CLI Equivalent - -```bash -paperforge ocr -``` - -## Prerequisites - -- [ ] formal note frontmatter 中 `do_ocr: true` 已设置 -- [ ] PDF 附件存在(`has_pdf: true`) -- [ ] PaddleOCR API Key 已配置(`.env` 中 `PADDLEOCR_API_TOKEN`) -- [ ] 网络连接正常(可访问 PaddleOCR 服务) - -## Arguments - -| 参数 | 必需 | 说明 | -|------|------|------| -| `--diagnose` | 否 | 仅诊断配置,不上传 PDF | -| `--key ` | 否 | 仅处理指定 Zotero key 的文献 | -| `--vault ` | 否 | 指定 Vault 根目录(默认当前目录) | -| `--live` | 否 | 与 `--diagnose` 联用,执行实时 PDF 测试 | - -### 诊断等级 - -| 等级 | 检查项 | 失败含义 | -|------|--------|---------| -| L1 | API token 存在性 | `PADDLEOCR_API_TOKEN` 缺失或为空 | -| L2 | URL 可达性 | 无法连接 PaddleOCR 服务 | -| L3 | API 格式验证 | 服务可达但响应格式异常 | -| L4 | 实时 PDF 往返 | 完整提交和结果获取失败 | - -> Exit code `0` = 所有检查通过。Exit code `1` = 至少一项检查失败。 - -## Example - -```bash -# 处理所有标记 do_ocr: true 的文献 -paperforge ocr - -# 仅处理指定文献 -paperforge ocr --key ABCDEFG - -# 诊断模式(不实际运行) -paperforge ocr --diagnose - -# 完整诊断(含实时测试) -paperforge ocr --diagnose --live - -# 指定 Vault 目录 -paperforge ocr --vault /path/to/vault -``` - -## Output - -OCR 完成后,每个文献生成以下文件: - -``` -/PaperForge/ocr// -├── fulltext.md # 提取的全文(含 分页标记) -├── images/ # 自动切割的图表图片 -├── meta.json # OCR 元数据(含 ocr_status) -└── figure-map.json # 图表索引(自动生成) -``` - -`meta.json` 中的 `ocr_status` 字段: -- `pending` — 等待处理 -- `processing` — 正在处理 -- `done` — 完成 -- `failed` — 失败 - -## Error Handling - -### API Token 缺失(L1 失败) -- `PADDLEOCR_API_TOKEN` 未设置 → 在 `.env` 文件中添加 - -### 服务不可达(L2 失败) -- 无法连接 PaddleOCR → 检查网络连接 - -### PDF 上传失败(L4 失败) -- PDF 提交后未返回结果 → 检查 PDF 文件是否损坏 - -### OCR 状态卡住 -- `ocr_status` 长期显示 `processing` → 重新设置 `do_ocr: true` 后再次运行 - -## See Also - -- [pf-sync](pf-sync.md) — 文献同步(生成正式笔记) -- [pf-deep](pf-deep.md) — 深度精读(依赖 OCR 结果) diff --git a/paperforge/skills/literature-qa/scripts/pf-paper.md b/paperforge/skills/literature-qa/scripts/pf-paper.md deleted file mode 100644 index aa3e032b..00000000 --- a/paperforge/skills/literature-qa/scripts/pf-paper.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: pf-paper -description: Quick paper Q&A workbench. Load a paper by Zotero key, title, DOI, or PMID and answer questions. Does not require OCR. -allowed-tools: [Read, Bash] ---- - -# pf-paper - -## Purpose - -基于 Zotero OCR 文本的单篇论文工作台入口。 - -1. 解析 `pf-paper ` 中的查询词 -2. 支持 Zotero key、标题片段、DOI、PMID、关键词 -3. 优先搜索本地 Zotero,解析到单篇目标论文 -4. 根据 Vault 根目录的 `paperforge.json` 加载 `/PaperForge/ocr//fulltext.md` 作为主文本 -5. 读取 `meta.json` 显示论文标题、作者、期刊、年份 -6. 进入 Q&A 模式,用中文回答用户关于该论文的问题 -7. 在当前论文上下文中,用户可再说"精读这篇文章"切换到 deep 层 - -## CLI Equivalent - -```bash -# 准备阶段(间接) -paperforge sync # 生成正式笔记 -``` - -> `pf-paper` 是 **Agent 层命令**,无直接 CLI 等效命令。 - -## Prerequisites - -- [ ] 正式笔记已生成(`paperforge sync` 生成) -- [ ] `fulltext.md` 存在(推荐,用于基于原文回答;如不存在则基于元数据回答) -- [ ] discussion.py 模块可用(`python -m paperforge.worker.discussion --help` 可执行) - -> **注意**:与 `/pf-deep` 不同,`pf-paper` **不强制要求** OCR 完成。没有 OCR 时基于论文元数据和公开信息回答。 - -## Arguments - -| 参数 | 必需 | 说明 | -|------|------|------| -| `` | 是 | Zotero key、标题片段、DOI、PMID 或关键词 | -| ` ...` | 否 | 可同时加载多篇论文 | - -### 解析规则 - -1. 如果输入看起来像 8 位 Zotero key,则直接按 key 解析。 -2. 否则先在本地 Zotero 中搜索标题/摘要。 -3. 若命中唯一结果或明显最佳结果,则直接载入。 -4. 若存在多个合理候选,则先列候选清单再让用户选。 -5. 不要强迫用户先知道 Zotero key。 - -## Example - -```bash -pf-paper XGT9Z257 -pf-paper Predictive findings on magnetic resonance imaging -pf-paper 10.1016/j.jse.2018.01.001 -pf-paper XGT9Z257 PQR8KLM -``` - -## Output - -加载成功后显示: - -``` -已加载论文: [title] ([year], [journal]) -Zotero Key: [key] -结束对话时说"保存记录"即可保存讨论。 -请问有什么问题? -``` - -### 回答原则 - -- **严格基于** `fulltext.md` 中的文本内容回答 -- 引用原文时标注来源页码/章节 -- 用中文(简体中文)回答 -- 论文中未提及的内容,明确说明"论文中未提及该内容" -- 需要结合论文以外知识的问题,说明"该问题需要结合论文以外的知识回答" - -## Error Handling - -### 论文未找到 -- **表现**:Zotero key 无效或搜索无结果 -- **解决**:确认 key 正确,或尝试用标题片段搜索 - -### 多个候选结果 -- **表现**:搜索返回多个匹配的论文 -- **处理**:Agent 列出候选清单,让用户选择目标论文 - -### OCR 文件缺失 -- **表现**:`fulltext.md` 不存在 -- **处理**:Agent 基于元数据和公开信息回答,并告知用户"OCR 文本不可用,回答基于元数据" - -## Saving Discussion Record - -当用户说"保存"、"结束"、"完成"等关键词时(或显式输入 `{prefix}pf-end `),必须执行记录保存。具体步骤见 [pf-end](pf-end.md)。 - -### 注意事项 - -- 仅 `/pf-paper` Q&A 需要记录。`/pf-deep` 不记录(精读内容已写入正式笔记)。 -- 如果无法从 formal-library.json 找到论文 domain/title,记录失败不应影响用户使用。 - -## See Also - -- [pf-end](pf-end.md) — 结束对话并保存记录 -- [pf-deep](pf-deep.md) — 完整三阶段精读 diff --git a/paperforge/skills/literature-qa/scripts/pf-status.md b/paperforge/skills/literature-qa/scripts/pf-status.md deleted file mode 100644 index 67d22f94..00000000 --- a/paperforge/skills/literature-qa/scripts/pf-status.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -name: pf-status -description: Check PaperForge installation and runtime status. Verifies configuration, paths, and data integrity. -allowed-tools: [Bash] ---- - -# /pf-status - -## Purpose - -查看 PaperForge 当前安装与运行状态。 - -`paperforge status` 会检查: - -- 安装完整性(Python 包、依赖) -- 配置文件(`paperforge.json`、`.env`) -- 路径连通性(exports、ocr、literature 目录) -- Zotero 数据目录链接状态 -- Better BibTeX 导出文件状态 - -## CLI Equivalent - -```bash -paperforge status -``` - -## Prerequisites - -无特殊前置条件。此命令用于诊断安装问题,即使在配置不完整时也会尽量输出可用信息。 - -## Arguments - -| 参数 | 必需 | 说明 | -|------|------|------| -| `--vault ` | 否 | 指定 Vault 根目录(默认当前目录) | - -## Example - -```bash -# 检查当前目录的 Vault 状态 -paperforge status - -# 检查指定 Vault 的状态 -paperforge status --vault /path/to/vault -``` - -## Output - -典型输出示例: - -``` -PaperForge Lite v1.2 -==================== - -[安装检查] -✓ Python 包: paperforge v1.2.0 -✓ 依赖: requests, pymupdf, pillow - -[配置检查] -✓ paperforge.json: 存在且有效 -✓ .env: 存在 -✓ PADDLEOCR_API_TOKEN: 已设置 - -[路径检查] -✓ exports: /PaperForge/exports/ -✓ ocr: /PaperForge/ocr/ -✓ literature: // -✓ Zotero: /Zotero/ - -[数据检查] -✓ library.json: 存在,包含 150 条文献 -✓ 正式笔记: 150 篇 - -状态: 一切正常 ✅ -``` - -## Error Handling - -### 配置缺失 -- `✗ paperforge.json: 未找到` → 运行 `paperforge doctor` 或重新执行安装 - -### 路径错误 -- `✗ Zotero: 目录不存在或不是有效链接` → 创建 junction/symlink 到 Zotero 数据目录 - -### 依赖缺失 -- `✗ 依赖: requests 未安装` → `pip install requests pymupdf pillow` - -### API Key 未设置 -- `✗ PADDLEOCR_API_TOKEN: 未设置` → 在 `.env` 文件中添加 API token - -## See Also - -- [pf-sync](pf-sync.md) — 文献同步 -- [pf-ocr](pf-ocr.md) — OCR 提取 diff --git a/paperforge/skills/literature-qa/scripts/pf-sync.md b/paperforge/skills/literature-qa/scripts/pf-sync.md deleted file mode 100644 index 02dedf87..00000000 --- a/paperforge/skills/literature-qa/scripts/pf-sync.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -name: pf-sync -description: Sync Zotero Better BibTeX JSON export and generate/update formal literature notes. -allowed-tools: [Bash] ---- - -# /pf-sync - -## Purpose - -同步 Zotero Better BibTeX JSON 导出并生成/更新正式文献笔记。 - -`paperforge sync` 读取 Zotero JSON 中的新条目,直接生成正式文献笔记。 - -自动读取 `paperforge.json` 定位 exports 目录和 literature 目录。 - -## CLI Equivalent - -```bash -paperforge sync -``` - -## Prerequisites - -- [ ] Zotero 已安装且 Better BibTeX 插件已启用 -- [ ] Better BibTeX 已配置自动导出 JSON -- [ ] JSON 导出文件存在(`/PaperForge/exports/library.json`) -- [ ] `paperforge.json` 配置正确(Vault 根目录下) - -## Arguments - -| 参数 | 必需 | 说明 | -|------|------|------| -| `--dry-run` | 否 | 预览变更,不实际写入文件 | -| `--domain ` | 否 | 仅同步指定领域(如 `骨科`) | -| `--selection` | 否 | (已废弃)仅保留以兼容旧版 | -| `--index` | 否 | (已废弃)仅保留以兼容旧版 | -| `--vault ` | 否 | 指定 Vault 根目录(默认当前目录) | - -### 选项 - -```bash -paperforge sync --dry-run # 预览同步结果 -paperforge sync --domain 骨科 # 按领域过滤同步 -``` - -## Example - -```bash -# 同步 Zotero 并生成正式笔记 -paperforge sync - -# 预览模式 -paperforge sync --dry-run - -# 仅同步特定领域 -paperforge sync --domain 骨科 - -# 指定 Vault 目录 -paperforge sync --vault /path/to/vault -``` - -## Output - -``` -[INFO] Found 5 new items -[INFO] Created 骨科/XXXXXXX.md -[INFO] Generated 5 formal notes -[INFO] Output: //骨科/XXXXXXX - Title.md -``` - -生成文件:`/// - .md` - -## Error Handling - -### JSON 文件不存在 -- `[ERROR] library.json not found` → 检查 Better BibTeX 导出路径 - -### 空 JSON 导出 -- `[INFO] Found 0 new items` → 确认 Zotero 中有带 citation key 的文献,Better BibTeX 已启用"Keep updated" - -## See Also - -- [pf-ocr](pf-ocr.md) — OCR 提取(下一步操作) -- [pf-status](pf-status.md) — 检查系统状态 From 47038e6864e57ac1a4227443a278df42e02efd0d Mon Sep 17 00:00:00 2001 From: Research Assistant <research@example.com> Date: Sun, 10 May 2026 20:43:32 +0800 Subject: [PATCH 5/8] style: tighten collection header spacing, fix u00B7 encoding, add stage max-width - Fix CSS unicode escape: \\u00B7 -> literal middle dot character - Remove 'papers' count line in collection header - Tighten collection-header padding from 64px to 14px top only - Reset collection-title margin to 0 - Add max-width: 160px + justify-self: center to workflow stages - workflow-stage-label font-size: 11px -> 12px --- paperforge/plugin/main.js | 1 - paperforge/plugin/styles.css | 25 ++++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index 9f732d23..d1446470 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -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' }); diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css index f861ef3a..1ba83d44 100644 --- a/paperforge/plugin/styles.css +++ b/paperforge/plugin/styles.css @@ -1302,7 +1302,7 @@ } .paperforge-paper-year::before { - content: " \u00B7 "; + content: " · "; color: var(--text-faint); } @@ -2085,21 +2085,18 @@ /* Collection mode */ .paperforge-collection-header { - margin-bottom: 10px; + padding: 14px 0 0 0; } .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; @@ -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,6 +2153,10 @@ .paperforge-workflow-arrow { display: none; } + .paperforge-workflow-stage { + max-width: none; + justify-self: stretch; + } } .paperforge-workflow-stage { @@ -2164,6 +2169,8 @@ border-radius: 8px; min-width: 0; width: 100%; + max-width: 160px; + justify-self: center; } .paperforge-workflow-stage-value { @@ -2175,7 +2182,7 @@ } .paperforge-workflow-stage-label { - font-size: 11px; + font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.3px; From bf990d79a0f61b9c7c97462a9e3da8d28229f840 Mon Sep 17 00:00:00 2001 From: Research Assistant <research@example.com> Date: Sun, 10 May 2026 21:50:27 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20add=20deep-finalize=20command=20?= =?UTF-8?q?=E2=80=94=20signal=20dashboard=20on=20/pf-deep=20completion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new command paperforge deep-finalize <key>: sets deep_reading_status=done in frontmatter + refreshes index - dashboard now only auto-refreshes on formal-library.json changes (removed per-note modify handler) - pf-deep reference updated with Post-Processing section instructing agent to call deep-finalize at end --- paperforge/cli.py | 10 +++ paperforge/command_files/pf-deep.md | 16 +++++ paperforge/commands/__init__.py | 1 + paperforge/commands/finalize.py | 95 +++++++++++++++++++++++++++++ paperforge/plugin/main.js | 7 +-- 5 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 paperforge/commands/finalize.py diff --git a/paperforge/cli.py b/paperforge/cli.py index 1cb7c790..d46b5e93 100644 --- a/paperforge/cli.py +++ b/paperforge/cli.py @@ -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 diff --git a/paperforge/command_files/pf-deep.md b/paperforge/command_files/pf-deep.md index 32658f9d..535ff591 100644 --- a/paperforge/command_files/pf-deep.md +++ b/paperforge/command_files/pf-deep.md @@ -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 刷新 **预检(必须)**: diff --git a/paperforge/commands/__init__.py b/paperforge/commands/__init__.py index 4a570f2f..63dc3add 100644 --- a/paperforge/commands/__init__.py +++ b/paperforge/commands/__init__.py @@ -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", } diff --git a/paperforge/commands/finalize.py b/paperforge/commands/finalize.py new file mode 100644 index 00000000..f4ab74e8 --- /dev/null +++ b/paperforge/commands/finalize.py @@ -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 diff --git a/paperforge/plugin/main.js b/paperforge/plugin/main.js index d1446470..e610303a 100644 --- a/paperforge/plugin/main.js +++ b/paperforge/plugin/main.js @@ -2110,16 +2110,11 @@ class PaperForgeStatusView extends ItemView { }); 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 }); From 9dc7ff43226182096add83b9e606d12ba122d46f Mon Sep 17 00:00:00 2001 From: Research Assistant <research@example.com> Date: Sun, 10 May 2026 22:22:31 +0800 Subject: [PATCH 7/8] style: align collection workflow overview with global library snapshot - Match outer frame padding/background/shadow to library-snapshot - Match inner stage card padding/background/layout to snapshot-pill - Use align-items: stretch on funnel (pill parity), arrow stays centered - Reduce collection-header bottom gap for tighter domain-to-card spacing - Synchronize stage-label font-size (12px -> 11px) --- paperforge/plugin/styles.css | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/paperforge/plugin/styles.css b/paperforge/plugin/styles.css index 1ba83d44..31f0725b 100644 --- a/paperforge/plugin/styles.css +++ b/paperforge/plugin/styles.css @@ -2085,7 +2085,8 @@ /* Collection mode */ .paperforge-collection-header { - padding: 14px 0 0 0; + padding: 10px 0 0 0; + margin-bottom: -12px; } .paperforge-collection-title { @@ -2112,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) { @@ -2163,8 +2163,9 @@ 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; @@ -2182,7 +2183,7 @@ } .paperforge-workflow-stage-label { - font-size: 12px; + font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.3px; @@ -2195,6 +2196,7 @@ color: var(--text-faint); font-size: 11px; flex-shrink: 0; + align-self: center; } .paperforge-collection-ocr-header { From c17972a17a09c8b34cb46c0457f0dc0535dea2e9 Mon Sep 17 00:00:00 2001 From: Research Assistant <research@example.com> Date: Sun, 10 May 2026 22:31:16 +0800 Subject: [PATCH 8/8] fix(sync): freeze workspace slug + frontmatter-only update to prevent deep-reading data loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Slug freeze: reuse existing workspace dir when title slug changes in Zotero - Frontmatter-only: for existing notes, replace only YAML block, never touch body - extract_preserved_deep_reading: match both ##精读 and ##🔍精读 + skip placeholder-only sections --- paperforge/adapters/obsidian_frontmatter.py | 20 +++++++++++-- paperforge/worker/asset_index.py | 33 +++++++++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/paperforge/adapters/obsidian_frontmatter.py b/paperforge/adapters/obsidian_frontmatter.py index 5f84bc0d..69723df1 100644 --- a/paperforge/adapters/obsidian_frontmatter.py +++ b/paperforge/adapters/obsidian_frontmatter.py @@ -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 diff --git a/paperforge/worker/asset_index.py b/paperforge/worker/asset_index.py index a36406ff..7fc28df6 100644 --- a/paperforge/worker/asset_index.py +++ b/paperforge/worker/asset_index.py @@ -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)