Skip to content

sources: thumbnail-first cards + branded clipping thumbnails#117

Merged
jorgefilipecosta merged 8 commits into
trunkfrom
sources-view-redesign
May 13, 2026
Merged

sources: thumbnail-first cards + branded clipping thumbnails#117
jorgefilipecosta merged 8 commits into
trunkfrom
sources-view-redesign

Conversation

@jorgefilipecosta
Copy link
Copy Markdown
Member

Summary

The project view's Sources grid used to read like a dense Finder grid — tiny uppercase kind labels, two-line titles, no visual identity per section. This PR brings it closer to Heptabase's curated-shelf aesthetic in two steps.

Commit 1 — thumbnail-first cards & section badges

  • Cards lead with a real thumbnail slot: folder stacks (now with an item-count chip overlaid), image / PDF / video thumbs, or a typographic page-preview tile for markdown notes that lack an image.
  • Title moves above the meta row at 13/600 single-line; kind becomes a real chip rather than uppercase muted text.
  • Grid: 140px → 200px min-width, 8px → 14px gap; 24px between groups.
  • Sources / Drafts / Done get colored badges (green / amber / indigo) and tinted count pills, so each section reads as identity rather than chrome.
  • Hover lift + transition; rounded corners bumped to 10px.

Commit 2 — branded clipping thumbnails

  • Main process detects when a .md file wraps a URL (frontmatter source / url / link or first body http(s) link) and records the host + kind on the DirEntry.
  • Background fetcher pulls a thumbnail: i.ytimg.com/<id>/maxresdefault.jpg for YouTube hosts, og:image scrape for everything else. Cached under <project>/.studio-write/thumbs/clip-<sha1>.{jpg,png,webp}. A .err sentinel suppresses retries until the user deletes it.
  • New resources:thumbReady event pings the renderer when a fetch lands; the grid refreshes without the user having to navigate away.
  • Cards swap the page-preview standin for the cached image with a play-triangle overlay on YouTube and a host pill in the corner. When a scrape fails (Wordpress, X, Reddit often block the User-Agent), the card falls back to the page-preview tile with the host pill so the card still has identity.

Test plan

  • npm run test:unit — 281 tests pass locally
  • npm run lint and npm run lint:css — clean (one pre-existing InlineFileEditor warning unrelated to this PR)
  • Open a linked project with mixed content; confirm Sources / Drafts / Done show colored badges and tinted count pills
  • Drill into a folder with subfolders; confirm folder cards show the stacked tiles + item-count chip
  • Drill into a Clippings folder; confirm cards transition from page-preview tiles to og:image / YouTube thumbnails within a few seconds, and that YouTube tiles get the play-triangle overlay
  • Toggle macOS dark mode (or page.emulateMedia({ colorScheme: 'dark' }) via Playwright MCP) and re-verify

Previously every resource card fronted with a small uppercase kind
label and a two-line title, so the grid read like a dense file
manager. The cards now lead with a real thumbnail slot — folder
stacks (with an item-count chip overlaid), image/PDF/video thumbs,
or a typographic page-preview tile for markdown notes that lack an
image. Section headers get a colored badge per group and a tinted
count pill so Sources / Drafts / Done feel like curated shelves
instead of generic affordances.
Markdown notes that wrap a URL (frontmatter source/url/link or a body
http(s) link) used to render as featureless page-preview tiles. The
list endpoint now detects the URL, records the host + kind, and kicks
off a background fetch — YouTube hosts pull the deterministic
i.ytimg.com thumbnail, everything else falls back to an og:image
scrape. Results land in the project's .studio-write/thumbs cache; a
.err sentinel suppresses retries until the user deletes it.

Cards swap the page-preview standin for the cached image with a
play-triangle overlay on YouTube tiles and a host pill in the corner,
so a "Clippings" folder now reads like a curated shelf.
After the first pass the grid still read as a wall of buttons: every
card carried a three-dot menu in the corner, every markdown note —
empty or not — rendered a fake-paper preview tile, and bright PDF /
og:image thumbnails punched through the dark theme. This commit pulls
the visual weight back without losing affordances.

- Hide the card menu button and the section "+" affordance until the
  card cell / header is hovered or focused (kept visible while the
  menu is open and on keyboard focus for a11y).
- Skip the page-preview slot when a markdown note has no excerpt and
  no clipping host — empty cards collapse to short title-only tiles
  instead of fake-paper rectangles.
- Drop the static box-shadow under every card; the lifted shadow now
  reads as a real hover state. Border softened to ~5% fg-on-surface,
  corner radius 10 → 8.
- Frame raster thumbnails with an inset hairline and knock back their
  luminance/saturation in dark mode so PDFs and white og:images sit
  inside the surface instead of puncturing it.
- Calm the meta row: kind chip is sentence-case at 10/500 against a
  tinted fill (was uppercase 10/600 on a hairline border), the .MD
  chip drops for markdown (the implicit default), and dates older
  than 30 days are omitted at card scale where the exact day no
  longer carries signal.
- Section labels: 13 → 14/600 with a touch of negative tracking so
  "Sources" / "Drafts" / "Done" feel like real headings.
The last round still let every card claim the row's tallest height,
so empty markdown notes rendered as 340 px rectangles and notes with
content rendered as 16:10 tiles full of illegible 8 px preview text.
This round drops both: cards now size to their actual content and
excerpts are typeset as readable body copy.

- Grid: grid-auto-rows: min-content + align-items: start, so short
  text cards no longer inflate to the height of the folder cards in
  the same row.
- Drop the fake-paper preview tile for markdown leaves. When a note
  has an excerpt, render it inline in the card body at 12/400 muted
  (3-line clamp) — readable text, not a 16:10 rectangle full of 8 px
  content nobody can parse.
- Untitled drafts get an italic + muted title treatment so the eye
  skips past them as unfinished rather than parsing them as named
  files.
- Clipping cards that never landed an og:image get the host as a
  small pill in the meta row (next to the kind chip) instead of
  inside a thumbnail-shaped frame.
- Dark-mode raster filter knocked back further: brightness .94 →
  .86, saturate .9 → .85.
- Folder count chip: smaller and quieter, with a subtle blur, so it
  reads as a secondary indicator rather than competing with the
  stacked tiles.
- Section labels: 14/600 → 15/600 with a touch of negative tracking.
Three changes that together remove the remaining "early-stage product"
feeling from the grid:

- Each section now renders folder cards and leaf cards into two
  separate .resources-grid-cards subgrids so they never co-occupy a
  row. Previously a tall folder beside short text notes left a large
  dead-space gap on the right of the row; with the split, folders
  fill their shelf and leaves start fresh on the next row.
- Replace the markdown-only folder text-stack with an abstract
  paper-stack visual. The previous implementation stamped the
  children's titles + excerpts at 7-8 px, which read as illegible
  decoration. Three layered tiles with a fan rotation and a faint
  lined-paper motif on the topmost tile communicate "stack of N
  notes" without trying to render the contents at thumbnail scale.
  The folder name now carries the identity; the visual just signals
  the kind.
- Drop the card's `min-height: 64px`. Untitled drafts collapse from
  ~150 px to ~65 px and text cards with a one-line excerpt sit at
  ~92 px, so empty cards finally stop dominating the grid.

Also removes the orphan page-preview CSS that the previous round
stopped rendering.
The previous round replaced the markdown-only folder thumbnails with
a generic abstract paper-stack visual. That fixed the illegibility of
the old 7 px text but it also stripped every folder of its identity —
Logs, Notes, References, Clippings all looked the same.

Restore the per-child content tiles, but at readable sizes this time:
title goes from 7 px → 10 px / 600 and the excerpt from 6 px → 8 px,
with one fewer excerpt line so the bigger text fits. The fan rotation
and stack-depth shadow stay, so the visual still reads as "a stack of
N notes" — but now you can actually tell that Logs has dated daily
entries, References has people, and Clippings has article titles.

The abstract paper-stack survives as the fallback for empty folders
(no children to summarize). Folders with image / PDF / video children
continue to use the real thumbnail stack from earlier rounds.
Two residual visual issues from the last "professional design" pass:

- A stranded final folder (e.g. Clippings when the section has 9
  folders in a 4-column grid) left a large horizontal void to its
  right. The split-grid structure prevented leaves from filling the
  empty cells. New `BucketedGrid` component measures the actual
  column count via ResizeObserver and promotes the first N leaves
  into the same row as the orphan folder, then renders the rest in
  a second grid below. The last folder row now mixes folder + leaf
  cards (top-aligned), eliminating the void without reintroducing
  ragged mixed rows on every other shelf.
- The Assets folder's white PDF/page thumbnails still punched
  through the dark surface at the default knockdown. Image tiles
  inside a folder stack now get a stronger dark-mode filter
  (brightness .7, saturate .78) while standalone leaf-card thumbs
  keep the lighter knockdown — those are the card's primary content
  and shouldn't be dimmed further.
The card's hover state used `transform: translateY(-1px)`. The eye
tracks the content inside the card (title, preview, meta), not the
card's outer edge, so even a single-pixel shift read as a visible
jump. A 120 ms ease wasn't slow enough to smooth it out either.

Drop the translate. The shadow, border-color, and a faint surface
tint already communicate "this card is hovered" without moving
anything. The card sits perfectly still under the cursor now.
@jorgefilipecosta jorgefilipecosta merged commit da0fa44 into trunk May 13, 2026
@jorgefilipecosta jorgefilipecosta deleted the sources-view-redesign branch May 13, 2026 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant