Skip to content

Commit d30cf3c

Browse files
HMarzbancursoragent
andcommitted
fix(documents): use the file icon on list rows
The size-10 paper was too busy in the list. Grid tiles still paint the first-page paper. Refs #221 Co-authored-by: Cursor <[email protected]>
1 parent 22dacd3 commit d30cf3c

12 files changed

Lines changed: 55 additions & 86 deletions

File tree

‎.cursor/docs/design-system.md‎

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ Inbox only. New cross-cutting working-style preferences go into §Workflow And R
341341
- Settings Documents list UX should follow a Google Docs-like per-row ⋮ menu, not inline visibility toggles. The menu lists Open, Copy link when public, Rename, Duplicate, Favorite or Unfavorite, then Private and Read-only. Favorite sits after Duplicate and before the Private divider. No toolbar star. Name is Favorite, never Bookmark or Pin (see CONTEXT.md §Documents list). Keep it minimal and size-fit for the settings panel. On mobile the Documents toolbar must stay 44px-target size-fit (flex/truncate sort, icon-only Trash, no desktop-width overflow). Private ON must clear and disable Read-only on that menu and on `DocumentSettingsPanel` (see §Document Access).
342342
- Date sorts on Settings → Documents use Date buckets on the rest after Favorites. No Favorites heading.
343343
- Last opened is a sort. The pad owner stamps `lastOpenedAt` via `POST /api/documents/:documentId/opened`. Last modified does not move.
344-
- Owner live list and Owner Trash list lead with `size-10` DocumentPreviewPaper (DocumentGridPreview), not `LuFileText`. Grid is 4/3 first-page paper. No screenshot thumbs. Create stays empty-state only.
344+
- Owner live list and Owner Trash list lead with `LuFileText`, not first-page paper. Grid is 4/3 first-page paper (`DocumentPreviewPaper`). No screenshot thumbs. Create stays empty-state only.
345345
- TOC long heading titles must wrap (e.g. `text-pretty` / `whitespace-pre-line`), never truncate — use the full row width left of the trailing chat/unread rail. Tree connectors use daisyUI nested menu rails on `toc__children` only (vertical rails, no L-elbows). Accept stock rail opacity — do not reintroduce a TOC `::before` strengthen overwrite.
346346
- Mobile heading control stays a one-frame stepper, name first: `[name | − | +]`. Do not replace it with a chip that opens a list. The three cells share one join; no inner box around plus and minus. When the caret is Title, the join uses the same `is-active` color as other heading levels.
347347
- TOC chat/unread/presence all live in-flow in `TocRowTrail`. Desktop presence is `<AvatarStack anchor="right">` right of the chat trigger (right-anchored, fixed right edge, grows left, `translate-x-3` to the column inner edge). The scrollbar must stay inside the column, so do NOT widen `.toc__scroll` to overhang presence beyond the wrapper. That widening pushed the scrollbar out over the editor. Fix broken shipped UI motion (e.g. `RollingNumber`) in place — never strip it for a static fallback unless the maintainer asks.

‎CHANGELOG.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ This file is the product changelog. It names the live webapp. The hocuspocus app
2222
export turns a lone video, audio, or embed URL into a player, and writes each
2323
picture's natural width and height. A labeled link and a filter link stay links.
2424
Paste of a bare file address is unchanged.
25-
- Paint a first-page paper on Settings → Documents grid, list, and Trash. The
26-
paper paints stored DocumentGridPreview JSON in React. It is not a screenshot.
27-
Empty paper shows “Empty document”.
25+
- Paint a first-page paper on Settings → Documents grid tiles. The paper paints
26+
stored DocumentGridPreview JSON in React. It is not a screenshot. Empty paper
27+
shows “Empty document”. List and Trash rows keep the document icon.
2828
- Sort Settings → Documents by Last opened. Opening an owned pad stamps Last
2929
opened. Last modified does not move. A document never opened shows
3030
“Never opened”.

‎CONTEXT.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Shared names for docs.plus domain concepts. Architecture reviews and deepenings
1919
_Avoid_: public fleet (omits Favorite, DocumentGridPreview, and Last opened)
2020
- **Owner Trash list** — the owner's soft-deleted Documents. Includes DocumentGridPreview and Last opened. Omits Favorite. Same NULL fill as the Owner live list.
2121
_Avoid_: Owner live list
22-
- **DocumentGridPreview** — the first-page extract painted on Documents paper (grid, list thumb, Trash thumb). SQL NULL means never extracted. `{ heading: null, lines: [] }` means empty or a failed extract. The paper omits heading at paint when it equals Title.
22+
- **DocumentGridPreview** — the first-page extract painted on Documents grid paper. SQL NULL means never extracted. `{ heading: null, lines: [] }` means empty or a failed extract. The paper omits heading at paint when it equals Title. List and Trash rows paint `LuFileText`.
2323
_Avoid_: DocumentPreview (admin stale-doc shape), screenshot, thumbnail
2424
- **Last opened** — `DocumentMetadata.lastOpenedAt`, stamped when the owner opens the pad. Sort key `lastOpenedAt_desc`. Does not move Last modified. Owner lists only.
2525
- **Date buckets** — Today / Yesterday / Previous 7 days / Previous 30 days / Earlier / Never opened. Used on date sorts in Settings → Documents. Favorites stay unbucketed. Never opened is for a null sort timestamp (Last opened with no stamp).

‎apps/webapp/CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ Follow industry overlay UX (Material, Apple HIG, Radix/shadcn) and dim-not-lift
217217

218218
- Glossary: root [`CONTEXT.md`](../../CONTEXT.md) §Documents list. Owner-only. Not **Bookmark** (chat + hyperlink picker) and not **Pin** (channel messages).
219219
- ⋮ **Favorite** / **Unfavorite** after Duplicate, before the Private divider. The menu stays open. No toolbar star. No Favorites heading.
220-
- Owner live list pins Favorites first, then `sort`. Accent `LuStar` (`text-accent fill-accent`) after the list title and top-left on the grid paper. List hairline between the Favorite block and the rest. Date sorts add Today / Yesterday / Previous 7 days / Previous 30 days / Earlier headers on the rest (and on the whole list when there are no Favorites). Last opened is a sort. Owner Trash list includes DocumentGridPreview and `lastOpenedAt`, fills a missing `preview`, and omits `isFavorite`. The fleet omits DocumentGridPreview and Last opened. `{ heading: null, lines: [] }` is empty. The paper omits heading when it equals Title.
220+
- Owner live list pins Favorites first, then `sort`. Accent `LuStar` (`text-accent fill-accent`) after the list title and top-left on the grid paper. List and Trash rows paint `LuFileText`, not first-page paper. List hairline between the Favorite block and the rest. Date sorts add Today / Yesterday / Previous 7 days / Previous 30 days / Earlier headers on the rest (and on the whole list when there are no Favorites). Last opened is a sort. Owner Trash list includes DocumentGridPreview and `lastOpenedAt`, fills a missing `preview`, and omits `isFavorite`. The fleet omits DocumentGridPreview and Last opened. `{ heading: null, lines: [] }` is empty. The paper omits heading when it equals Title.
221221
- Soft-delete keeps `DocumentFavorite`. Purge cascade-drops it.
222222

223223
### Document Version History

‎apps/webapp/src/components/settings/SettingsPanelSkeleton.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const DocumentsSkeleton = () => {
6565
<div className="divide-base-300 divide-y">
6666
{[0, 1, 2, 3, 4].map((i) => (
6767
<div key={i} className="flex items-center gap-3 py-3">
68-
<div className="skeleton size-10 shrink-0 rounded" />
68+
<div className="skeleton size-[18px] shrink-0 rounded" />
6969
<div className="flex min-w-0 flex-1 flex-col gap-1">
7070
<div className="skeleton rounded-field h-4 w-3/4" />
7171
<div className="skeleton rounded-field h-3 w-16 sm:hidden" />

‎apps/webapp/src/components/settings/components/DocumentGridTile.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function DocumentGridTile({
7575
}}
7676
className="flex flex-col text-left focus-visible:outline-none">
7777
<span className="rounded-t-box relative flex aspect-[4/3] items-end justify-center bg-[var(--pad-well)] px-3.5 pt-2.5">
78-
<DocumentPreviewPaper preview={doc.preview} title={doc.title} variant="tile" />
78+
<DocumentPreviewPaper preview={doc.preview} title={doc.title} />
7979
{doc.isFavorite && (
8080
<span className="absolute top-2 left-2 z-10">
8181
<LuStar size={14} className="text-accent fill-accent" aria-label="Favorite" />

‎apps/webapp/src/components/settings/components/DocumentListRow.tsx‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import TextInput from '@components/ui/TextInput'
22
import { useRouter } from 'next/router'
33
import { useEffect, useRef, useState } from 'react'
4-
import { LuEye, LuLock, LuStar } from 'react-icons/lu'
4+
import { LuEye, LuFileText, LuLock, LuStar } from 'react-icons/lu'
55

66
import useCommitDocumentRename from '../hooks/useCommitDocumentRename'
77
import { type DocumentMembersEntry } from '../hooks/useDocumentMembers'
88
import type { DocumentSortKey, OwnedDocument } from '../types'
99
import { documentListDate } from '../utils/documentListDate'
1010
import DocumentMembersCluster from './DocumentMembersCluster'
11-
import DocumentPreviewPaper from './DocumentPreviewPaper'
1211
import DocumentRowMenu from './DocumentRowMenu'
1312

1413
interface DocumentListRowProps {
@@ -104,7 +103,7 @@ function DocumentListRow({
104103
if (isRenaming) {
105104
return (
106105
<li data-doc-row className="flex items-center gap-3 px-2 py-2">
107-
<DocumentPreviewPaper preview={doc.preview} title={doc.title} variant="row" />
106+
<LuFileText size={18} className="text-base-content/40 shrink-0" />
108107
<TextInput
109108
ref={inputRef}
110109
size="sm"
@@ -149,7 +148,7 @@ function DocumentListRow({
149148
}
150149
}}
151150
className="rounded-field focus-visible:ring-primary flex min-w-0 flex-1 items-center gap-3 px-2 py-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none">
152-
<DocumentPreviewPaper preview={doc.preview} title={doc.title} variant="row" />
151+
<LuFileText size={18} className="text-base-content/40 shrink-0" />
153152
<span className="flex min-w-0 flex-1 flex-col">
154153
<span className="flex min-w-0 items-center gap-1.5">
155154
<span className="text-base-content truncate font-medium">{label}</span>

‎apps/webapp/src/components/settings/components/DocumentPreviewPaper.tsx‎

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ const visibleHeading = (
2323
interface DocumentPreviewPaperProps {
2424
preview?: DocumentGridPreview | null
2525
title?: string | null
26-
variant: 'tile' | 'row'
2726
}
2827

2928
/** Heading that equals Title is omitted here, not in the stored extract. */
30-
function DocumentPreviewPaper({ preview, title, variant }: DocumentPreviewPaperProps) {
29+
function DocumentPreviewPaper({ preview, title }: DocumentPreviewPaperProps) {
3130
const [hideImage, setHideImage] = useState(false)
3231
const heading = visibleHeading(preview?.heading, title)
3332
const lines = preview?.lines ?? []
@@ -36,75 +35,47 @@ function DocumentPreviewPaper({ preview, title, variant }: DocumentPreviewPaperP
3635
preview?.imageSrc && !hideImage && isHttpSrc(preview.imageSrc) ? preview.imageSrc : undefined
3736
const empty = preview != null && !heading && lines.length === 0 && list.length === 0 && !imageSrc
3837

39-
const body =
40-
empty && variant === 'tile' ? (
41-
<span
42-
className="text-base-content/30 absolute inset-0 grid place-items-center text-[9px]"
43-
aria-hidden>
44-
Empty document
45-
</span>
46-
) : preview != null ? (
47-
<>
48-
{heading ? (
49-
<span
50-
className={
51-
variant === 'tile'
52-
? 'mb-1.5 block text-[11px] leading-tight font-bold'
53-
: 'mb-0.5 block truncate text-[7px] leading-tight font-bold'
54-
}>
55-
{heading}
56-
</span>
57-
) : null}
58-
{lines.map((line, i) => (
59-
<span
60-
key={i}
61-
className={
62-
variant === 'tile'
63-
? 'text-base-content/80 mb-1.5 block text-[7.5px] leading-[1.35]'
64-
: 'text-base-content/80 mb-0.5 block truncate text-[5.5px] leading-tight'
65-
}>
66-
{line}
67-
</span>
68-
))}
69-
{variant === 'tile' && imageSrc ? (
70-
<img
71-
alt=""
72-
src={imageSrc}
73-
className="mt-1 mb-1.5 h-7 w-full object-cover"
74-
onError={() => setHideImage(true)}
75-
/>
76-
) : null}
77-
{variant === 'tile' && list.length > 0 ? (
78-
<ul className="text-base-content/80 mb-1.5 list-disc pl-2.5 text-[7.5px] leading-[1.35]">
79-
{list.map((item, i) => (
80-
<li key={i}>{item}</li>
81-
))}
82-
</ul>
83-
) : null}
84-
{variant === 'tile' ? (
85-
<span
86-
aria-hidden
87-
className="from-base-100 pointer-events-none absolute inset-x-0 bottom-0 h-9 bg-gradient-to-t to-transparent"
88-
/>
89-
) : null}
90-
</>
91-
) : null
92-
93-
if (variant === 'row') {
94-
return (
95-
<span
96-
aria-hidden
97-
className="border-base-300 bg-base-100 relative size-10 shrink-0 overflow-hidden rounded-[2px] border px-1 pt-1">
98-
{body}
99-
</span>
100-
)
101-
}
102-
10338
return (
10439
<span
10540
aria-hidden
10641
className="border-base-300 bg-base-100 relative h-full w-full overflow-hidden rounded-t-[2px] border border-b-0 px-3 pt-2.5">
107-
{body}
42+
{empty ? (
43+
<span
44+
className="text-base-content/30 absolute inset-0 grid place-items-center text-[9px]"
45+
aria-hidden>
46+
Empty document
47+
</span>
48+
) : preview != null ? (
49+
<>
50+
{heading ? (
51+
<span className="mb-1.5 block text-[11px] leading-tight font-bold">{heading}</span>
52+
) : null}
53+
{lines.map((line, i) => (
54+
<span key={i} className="text-base-content/80 mb-1.5 block text-[7.5px] leading-[1.35]">
55+
{line}
56+
</span>
57+
))}
58+
{imageSrc ? (
59+
<img
60+
alt=""
61+
src={imageSrc}
62+
className="mt-1 mb-1.5 h-7 w-full object-cover"
63+
onError={() => setHideImage(true)}
64+
/>
65+
) : null}
66+
{list.length > 0 ? (
67+
<ul className="text-base-content/80 mb-1.5 list-disc pl-2.5 text-[7.5px] leading-[1.35]">
68+
{list.map((item, i) => (
69+
<li key={i}>{item}</li>
70+
))}
71+
</ul>
72+
) : null}
73+
<span
74+
aria-hidden
75+
className="from-base-100 pointer-events-none absolute inset-x-0 bottom-0 h-9 bg-gradient-to-t to-transparent"
76+
/>
77+
</>
78+
) : null}
10879
</span>
10980
)
11081
}

‎apps/webapp/src/components/settings/components/DocumentsSection.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const DocumentsBodySkeleton = ({ viewMode }: { viewMode: DocumentViewMode }) =>
141141
<div className="divide-base-300 divide-y">
142142
{[0, 1, 2, 3, 4].map((i) => (
143143
<div key={i} className="flex items-center gap-3 py-3">
144-
<div className="skeleton size-10 shrink-0 rounded" />
144+
<div className="skeleton size-[18px] shrink-0 rounded" />
145145
<div className="flex min-w-0 flex-1 flex-col gap-1">
146146
<div className="skeleton rounded-field h-4 w-3/4" />
147147
<div className="skeleton rounded-field h-3 w-16 sm:hidden" />

0 commit comments

Comments
 (0)