Show a post's image on its card, and show it full size #172

Open
blindxfish wants to merge 1 commits from blindxfish/devplacepy:PostCardPolish into master
Contributor

Brings post image rendering in line with the original DevPlace, where a post's image is a full-width part of the card rather than a thumbnail chip. Two things were in the way.

1. Feed and profile cards never rendered attachments at all

_attachment_display.html iterates a context variable named attachments, so every caller binds it before the include - _comment.html uses {% set attachments = item.get('attachments', []) %}, messages.html uses {% with %}, post.html and gist_detail.html get it from their route context, project_detail.html sets it from other_attachments.

_post_card.html was the one caller that did not. It guarded on item.attachments but then included the partial with nothing bound, so the loop ran over whatever attachments happened to be in the surrounding page context and the card rendered an empty <div class="attachment-gallery">. Every post with an image looked image-less on /feed and on a profile.

The same defect had a second, worse face: project_detail.html sets attachments at template scope for the project's own files and renders devlog post cards further down the page, so a devlog post that had its own attachments would have rendered the project's files as if they belonged to the post.

Fixed by binding it the way _comment.html already does, and pinned by tests/e2e/feed.py::test_feed_card_shows_the_post_image.

2. A single image is now a hero, not a chip

When the gallery holds exactly one item the partial adds a single class, and attachments.css gives that item the full content column: width: 100%, max-height: 480px, object-fit: contain, and no hover scale (scaling a full-width image looks broken). That mirrors the original's w-full object-contain treatment. A multi-item gallery keeps the existing 240x200 chips.

The single branch serves att['url'], never thumbnail_url. A thumbnail is 200px on its longest side, so stretching one to the column width is visibly blurry - that is the whole reason the src is a conditional rather than "thumbnail whenever one exists". Verified by screenshotting a real rendered card, not just by asserting on markup.

Because the partial is shared, this lands on post cards, post detail, comments, gists, projects and chat bubbles at once.

GIFs

No change was needed - they were already correct end to end, and this PR confirms it with a test. .gif is in ALLOWED_UPLOAD_TYPES, THUMBNAIL_EXTENSIONS deliberately excludes it so animation is never flattened into a static JPEG, both main.py's INLINE_MEDIA_EXTENSIONS and the nginx map $uri $upload_disposition serve it inline, and both render pipelines already match .gif. An animated GIF simply took the original-file path already; it now displays at hero size like any other single image. test_animated_gif_post_serves_the_original_file asserts the original .gif is what reaches the page.

Worth knowing before merge

The hero serves the full-resolution original everywhere the partial renders, including chat bubbles and comments, which previously showed the 200px thumbnail. Images are lazy-loaded and bounded by max_upload_size_mb, but a large phone photo in a DM now transfers on render instead of on lightbox click. The clean long-term fix is a mid-size (~1200px) derivative generated beside the thumbnail and used for heroes; that belongs in its own change since it touches attachments.py and needs its own tests.

Tests

Four new e2e tests, plus create_post_with_files() moved into tests/conftest.py so the feed and post tiers share one composer-with-attachments helper:

  • feed.py::test_feed_card_shows_the_post_image - the regression guard for the binding bug
  • post.py::test_single_image_post_shows_the_original_full_size - hero serves the original, not the thumbnail
  • post.py::test_multiple_image_post_keeps_thumbnails - multi-image galleries still use thumbnails
  • post.py::test_animated_gif_post_serves_the_original_file

Full suite in the project's Docker image: 3355 passed, 9 failed in 26m56s. All nine failures are the tests/api/projects/workspace.py container-manager tests, which fail with FileNotFoundError: 'docker' because that image only ships the docker CLI when built with INSTALL_DOCKER_CLI=true; they reproduce identically on a pristine HEAD checkout in the same container. Collected count matches exactly: master's 3360 plus the 4 tests added here.

🤖 Generated with Claude Code

Brings post image rendering in line with the original DevPlace, where a post's image is a full-width part of the card rather than a thumbnail chip. Two things were in the way. ## 1. Feed and profile cards never rendered attachments at all `_attachment_display.html` iterates a context variable named `attachments`, so every caller binds it before the include - `_comment.html` uses `{% set attachments = item.get('attachments', []) %}`, `messages.html` uses `{% with %}`, `post.html` and `gist_detail.html` get it from their route context, `project_detail.html` sets it from `other_attachments`. `_post_card.html` was the one caller that did not. It guarded on `item.attachments` but then included the partial with nothing bound, so the loop ran over whatever `attachments` happened to be in the surrounding page context and the card rendered an **empty** `<div class="attachment-gallery">`. Every post with an image looked image-less on `/feed` and on a profile. The same defect had a second, worse face: `project_detail.html` sets `attachments` at template scope for the project's own files and renders devlog post cards further down the page, so a devlog post that had its own attachments would have rendered *the project's* files as if they belonged to the post. Fixed by binding it the way `_comment.html` already does, and pinned by `tests/e2e/feed.py::test_feed_card_shows_the_post_image`. ## 2. A single image is now a hero, not a chip When the gallery holds exactly one item the partial adds a `single` class, and `attachments.css` gives that item the full content column: `width: 100%`, `max-height: 480px`, `object-fit: contain`, and no hover scale (scaling a full-width image looks broken). That mirrors the original's `w-full object-contain` treatment. A multi-item gallery keeps the existing 240x200 chips. **The `single` branch serves `att['url']`, never `thumbnail_url`.** A thumbnail is 200px on its longest side, so stretching one to the column width is visibly blurry - that is the whole reason the `src` is a conditional rather than "thumbnail whenever one exists". Verified by screenshotting a real rendered card, not just by asserting on markup. Because the partial is shared, this lands on post cards, post detail, comments, gists, projects and chat bubbles at once. ## GIFs No change was needed - they were already correct end to end, and this PR confirms it with a test. `.gif` is in `ALLOWED_UPLOAD_TYPES`, `THUMBNAIL_EXTENSIONS` deliberately excludes it so animation is never flattened into a static JPEG, both `main.py`'s `INLINE_MEDIA_EXTENSIONS` and the nginx `map $uri $upload_disposition` serve it `inline`, and both render pipelines already match `.gif`. An animated GIF simply took the original-file path already; it now displays at hero size like any other single image. `test_animated_gif_post_serves_the_original_file` asserts the original `.gif` is what reaches the page. ## Worth knowing before merge The hero serves the full-resolution original everywhere the partial renders, including chat bubbles and comments, which previously showed the 200px thumbnail. Images are lazy-loaded and bounded by `max_upload_size_mb`, but a large phone photo in a DM now transfers on render instead of on lightbox click. The clean long-term fix is a mid-size (~1200px) derivative generated beside the thumbnail and used for heroes; that belongs in its own change since it touches `attachments.py` and needs its own tests. ## Tests Four new e2e tests, plus `create_post_with_files()` moved into `tests/conftest.py` so the feed and post tiers share one composer-with-attachments helper: - `feed.py::test_feed_card_shows_the_post_image` - the regression guard for the binding bug - `post.py::test_single_image_post_shows_the_original_full_size` - hero serves the original, not the thumbnail - `post.py::test_multiple_image_post_keeps_thumbnails` - multi-image galleries still use thumbnails - `post.py::test_animated_gif_post_serves_the_original_file` Full suite in the project's Docker image: **3355 passed, 9 failed in 26m56s**. All nine failures are the `tests/api/projects/workspace.py` container-manager tests, which fail with `FileNotFoundError: 'docker'` because that image only ships the docker CLI when built with `INSTALL_DOCKER_CLI=true`; they reproduce identically on a pristine `HEAD` checkout in the same container. Collected count matches exactly: master's 3360 plus the 4 tests added here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
blindxfish added 1 commit 2026-08-17 23:12:58 +02:00
Show a post's image on its card, and show it full size
Some checks failed
DevPlace CI / test (pull_request) Has been cancelled
24a79177e8
_attachment_display.html iterates a context variable named
`attachments`, so every caller binds it before the include.
_post_card.html was the one caller that did not: it guarded on
item.attachments but included the partial with nothing bound, so the
gallery looped over whatever `attachments` happened to be in the
surrounding page context and rendered empty. Every post with an image
looked image-less on the feed and on profiles, and on a project page -
where project_detail.html sets `attachments` at template scope for the
project's own files - a devlog card would have rendered the project's
files as its own.

With the image actually reaching the card, render a lone one properly:
a gallery holding exactly one item gets a `single` class and takes the
full content column (max-height 480px, object-fit contain, no hover
scale), matching the original DevPlace. That branch serves the stored
original rather than thumbnail_url, because a thumbnail is 200px on its
longest side and stretching it to the column width is visibly blurry.

Animated GIFs needed no change and now have a test proving it: they
never had a thumbnail to flatten, so they already took the original-file
path and simply render larger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner

Great, but please rebase.

Great, but please rebase.
blindxfish force-pushed PostCardPolish from 24a79177e8 to 8856c38b4d 2026-08-18 15:45:16 +02:00 Compare
Some checks are pending
DevPlace CI / test (pull_request) Blocked by required conditions
This pull request can be merged automatically.
You are not authorized to merge this pull request.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u PostCardPolish:blindxfish-PostCardPolish
git checkout blindxfish-PostCardPolish
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: retoor/devplacepy#172
No description provided.