Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .claude/agent-memory/ux-designer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ Common token mistakes in this PR to watch for in future reviews:

WorkItemPicker (`client/src/components/WorkItemPicker/`) is the existing reference for search-as-you-type inline pickers. DocumentBrowser is a richer version of that pattern — a full grid browser rather than a dropdown list.

## Story 8.4 — Document Linking Spec (Issue #357)

- Documents section: full-width panel BELOW the two-column contentGrid, ABOVE the footer
- Linked doc display: mini-card strip using CSS Grid `auto-fill minmax(180px, 1fr)` — NOT the full DocumentCard (different semantics)
- Card action tray: View / Open in Paperless / Unlink — tray uses `--color-bg-secondary` background with `border-top: 1px solid var(--color-border)`
- Unlink confirmation: uses `.btnDanger` (outline red, not `.btnConfirmDelete` solid red) — unlinking is reversible
- Picker modal: wide (860px max), not the default 28rem `.modalContent` size — `min(860px, calc(100vw - 2rem))`
- Single-click document selection in modal (no separate confirm step) — linking is reversible via Unlink
- Not-configured banner: neutral tokens (`--color-bg-secondary`, `--color-border`) NOT `--color-primary-bg` which is blue-tinted
- Count badge in heading uses `--color-bg-tertiary` + `--color-text-muted` (neutral pill, not status-colored)
- Skeleton: show 2 cards (not spinner text); uses same shimmer as DocumentSkeleton from 8.3
- `srAnnouncement` visually-hidden live region announces "Document linked: title" / "Document unlinked: title"
- Mobile modal: full-viewport sheet (width:100vw; height:100vh; border-radius:0) at < 768px

## PR #364 Review Findings — Document Browser (for future reference)

Common misses in this PR to watch for in card/grid components:
Expand Down
1 change: 1 addition & 0 deletions .claude/metrics/review-metrics.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@
{"pr":362,"issues":[354],"epic":8,"type":"feat","mergedAt":"2026-03-02T00:00:00Z","filesChanged":9,"linesChanged":2196,"fixLoopCount":1,"reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":1,"informational":0},"round":1},{"agent":"security-engineer","verdict":"request-changes","findings":{"critical":0,"high":0,"medium":1,"low":3,"informational":1},"round":1},{"agent":"product-owner","verdict":"request-changes","findings":{"critical":0,"high":0,"medium":1,"low":1,"informational":0},"round":1},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":2},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":2}],"totalFindings":{"critical":0,"high":0,"medium":2,"low":5,"informational":1}}
{"pr":363,"issues":[355],"epic":8,"type":"feat","mergedAt":"2026-03-02T00:00:00Z","filesChanged":7,"linesChanged":1745,"fixLoopCount":0,"reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":1,"informational":2},"round":1},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1}],"totalFindings":{"critical":0,"high":0,"medium":0,"low":1,"informational":2}}
{"pr":368,"issues":[356],"epic":null,"type":"fix","mergedAt":"2026-03-02T13:23:45Z","filesChanged":16,"linesChanged":325,"fixLoopCount":0,"reviews":[{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":2,"informational":1},"round":1},{"agent":"security-engineer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":1},"round":1},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":1},{"agent":"ux-designer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":2,"informational":2},"round":1}],"totalFindings":{"critical":0,"high":0,"medium":0,"low":4,"informational":4}}
{"pr":372,"issues":[357],"epic":8,"type":"feat","mergedAt":"2026-03-02T17:00:00Z","filesChanged":13,"linesChanged":2556,"fixLoopCount":1,"reviews":[{"agent":"security-engineer","verdict":"comment","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":1},"round":1},{"agent":"product-architect","verdict":"request-changes","findings":{"critical":0,"high":0,"medium":3,"low":0,"informational":0},"round":1},{"agent":"product-architect","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":2},{"agent":"ux-designer","verdict":"request-changes","findings":{"critical":0,"high":0,"medium":4,"low":0,"informational":0},"round":1},{"agent":"ux-designer","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":2},{"agent":"product-owner","verdict":"request-changes","findings":{"critical":0,"high":0,"medium":1,"low":0,"informational":0},"round":1},{"agent":"product-owner","verdict":"approve","findings":{"critical":0,"high":0,"medium":0,"low":0,"informational":0},"round":2}],"totalFindings":{"critical":0,"high":0,"medium":8,"low":0,"informational":1}}
181 changes: 181 additions & 0 deletions client/src/components/documents/LinkedDocumentCard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
.card {
background: var(--color-bg-primary);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
display: flex;
flex-direction: column;
transition:
box-shadow var(--transition-normal),
border-color var(--transition-normal);
}

.card:hover {
box-shadow: var(--shadow-md);
border-color: var(--color-border-strong);
}

.thumbContainer {
position: relative;
aspect-ratio: 4 / 3;
overflow: hidden;
background: var(--color-bg-tertiary);
}

.thumb {
width: 100%;
height: 100%;
object-fit: cover;
}

.thumbFallback {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-muted);
font-size: 2rem;
opacity: 0.35;
}

.body {
padding: var(--spacing-3);
display: flex;
flex-direction: column;
gap: var(--spacing-1);
flex: 1;
}

.title {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
margin: 0;
}

.meta {
font-size: var(--font-size-xs);
color: var(--color-text-muted);
margin: 0;
}

.tags {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-1);
margin-top: var(--spacing-0-5);
}

.tagChip {
padding: var(--spacing-0-5) var(--spacing-1-5);
font-size: 0.625rem;
background: var(--color-primary-bg);
color: var(--color-primary-badge-text);
border-radius: var(--radius-full);
white-space: nowrap;
}

.actions {
display: flex;
align-items: center;
gap: var(--spacing-1);
padding: var(--spacing-2) var(--spacing-3);
border-top: 1px solid var(--color-border);
background: var(--color-bg-secondary);
}

.viewButton {
flex: 1;
padding: var(--spacing-1-5) 0;
background: transparent;
border: none;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--color-primary);
cursor: pointer;
text-align: left;
transition: color var(--transition-fast);
border-radius: var(--radius-sm);
}

.viewButton:hover {
color: var(--color-primary-hover);
}

.viewButton:focus-visible {
outline: none;
box-shadow: var(--shadow-focus-subtle);
}

.openLink {
flex-shrink: 0;
padding: var(--spacing-1);
background: transparent;
border: none;
color: var(--color-text-muted);
font-size: var(--font-size-sm);
cursor: pointer;
border-radius: var(--radius-sm);
transition: color var(--transition-fast);
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
}

.openLink:hover {
color: var(--color-primary);
}

.openLink:focus-visible {
outline: none;
box-shadow: var(--shadow-focus-subtle);
}

.unlinkButton {
flex-shrink: 0;
padding: var(--spacing-1);
background: transparent;
border: none;
color: var(--color-text-muted);
font-size: var(--font-size-sm);
cursor: pointer;
border-radius: var(--radius-sm);
transition:
color var(--transition-fast),
background var(--transition-fast);
}

.unlinkButton:hover {
color: var(--color-danger);
background: var(--color-overlay-delete);
}

.unlinkButton:focus-visible {
outline: none;
box-shadow: var(--shadow-focus-danger);
}

/* Touch targets on mobile */
@media (max-width: 1023px) {
.viewButton,
.openLink,
.unlinkButton {
min-height: 44px;
min-width: 44px;
display: flex;
align-items: center;
justify-content: center;
}

.viewButton {
text-align: center;
flex: 1;
}
}
Loading
Loading