Pluggable recipe card designs - #65
Merged
Merged
Conversation
Move the design-agnostic Pillow helpers (cover_fill, build_gradient, load_font, make_qr, embed_recipe_exif) and the shared constants (QR size, blur radius, gradient colours) out of the cards operations module into a new rendering module. Introduce a RenderedCard value object for the (canvas, json_str, embed_exif) compose result. This is a pure extraction with no behaviour change: _compose_card now delegates to the shared primitives and returns a RenderedCard. It sets up the shared foundation that the upcoming pluggable card designs will reuse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a designs/ package with a CardDesign abstract base class (the polymorphic seam for pluggable card designs) and a ClassicDesign that reproduces the current square-card composition. The four legacy templates collapse into one ClassicDesign parameterized by label_style / background_effect / info_side; its template_name property reproduces the existing persisted strings for backward compatibility. Decouple get_recipe_cover_lines from CardTemplate: it now takes a label_style string, so ClassicDesign (and future designs) no longer depend on the templates module. ClassicDesign is not yet wired into operations; that swap happens next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the template + info_side parameters of the card operations with a single design: CardDesign object across the domain operations, the create/ preview/batch use cases, and the interface views. The interface now builds a ClassicDesign from the modal's option values via _resolve_design (replacing _resolve_card_template / _resolve_info_side); the batch use case uses a fixed ClassicDesign. Delete the now-unused templates module and the monolithic _compose_card (its logic lives in ClassicDesign.render). RecipeCard.template and the recipe.card.created event still carry the same template_name strings, so persisted rows and event history are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the OFL-licensed Archivo (variable) and Space Mono TTFs under interfaces/static/fonts, and bake the stacked filmcase logo (icon + Archivo Black wordmark, faithful to filmcase_stacked_tight.svg) to a 2x transparent PNG in the original-branding images. Add the shared rendering helpers the new portrait designs need: Archivo/Space Mono loaders, draw_tracked_text (Pillow has no letter-spacing), the inline filmcase wordmark, and rounded-corner mask / clip / paste helpers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ApertureDesign: a dark, story-format (1080x1920) card with a blurred, darkened photo background, a rounded hero photo, frosted-glass parameter tiles (a Film Simulation + White Balance hero row and a 3-column grid), and a bottom import module with the stacked logo and QR on a white pill. The QR stays the shared 300px so every design scans the same way. Add reusable queries the designs share: get_recipe_field_lines (format an ordered field subset, reusing the applicability rules), is_monochromatic, and get_sensor_names; get_recipe_cover_lines now builds on the first. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ContactSheetDesign: a light paper spec-sheet card (1080x1920) with a blurred photo frame behind a rounded paper panel that holds a red-accented header, a hero photo, a two-column key/value list of every parameter with hairline rows, and a hairline-separated import module with the logo + QR directly on the paper. Add a shared rendering.line_height helper used by both portrait designs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let users pick between the Classic, Aperture, and Contact Sheet designs via htmx tabs in the create-card modal. Each tab swaps in only that design's options (Classic keeps label style / background effect / info side; the photo-centric designs just describe themselves) and refreshes the live preview. The gradient background option is hidden for the photo-required designs, which auto-select an image. The preview/create views and the use cases now resolve the design from a single 'design' parameter via _resolve_design; a new RecipeCardDesignOptions view serves each tab's controls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ADR 012 recording the CardDesign abstraction that supersedes ADR 005's flat CardTemplate composition model: its flaws (one hardcoded square layout, layout-coupled info_side, monolithic compose function, single-design modal) and the decision taken to fix them, including per-design canvas size, shared QR/logo/EXIF globals, the ABC choice, the photo-required rule, and the htmx design tabs. Link it from the docs index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The card modal's background-image picker requested images with ?w=160, but ImageFile only honours the width parameter, so each of the 12 thumbnails downloaded the full-size original. Use ?width=600 - the same size the image gallery renders - so the picker reuses the already-generated, cached thumbnails instead of triggering a fresh 160px thumbnail generation on first open. Card creation and preview still read the full-resolution file, so output quality is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add RECIPE_CARD_APERTURE_SCRIM_TOP_OPACITY / _BOTTOM_OPACITY settings (percentages, default 30 / 80) controlling the top->bottom darkening gradient over the Aperture card background. The design reads them at render time (not import time), so they can be tuned from the env file without code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preview <img> URL is keyed only on the options, so re-rendering the pane with the same image/design (or after an env-driven change) reused the browser's cached image and never refetched. Send Cache-Control: no-store on the preview-file response and add a per-render cache-busting nonce to the preview <img> URL, so every pane render pulls a freshly generated preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CardDesign.render now takes a background_photo_path instead of the Image model, decoupling composition from persistence. Saved cards still render from the full-resolution original; previews render from the 600px gallery thumbnail (generated once, reused from cache), so each preview avoids decoding and resizing the multi-megapixel source - a big speedup for the blur-heavy Aperture/Contact Sheet designs. Preview photo quality is slightly softer, which is fine for a small on-screen preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preview pane previously rendered the card to produce its path, then the browser loaded the <img> from the file view which rendered it a second time. The pane now just validates the recipe/image exist and returns the <img>; the file view is the sole renderer, composing the card lazily when the image loads. This halves preview work per option change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expand the recipe-card section of web_interface.md to describe the Classic, Aperture, and Contact Sheet designs (canvas sizes, options, the shared QR, colour/B&W field handling, and the photo requirement for the portrait designs), link ADR 012, and add example images of the two new designs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework docs/web_interface.md around the recipe workflow: - Add a numbered table of contents and hierarchical section numbering (1, 1.1, 2.1.1, ...), with matching heading anchors. - Move each section's screenshot directly below its title, and refresh the gallery / image-detail / recipe-detail / explorer screenshots. - Split recipe import/create/delete into its own section, and restructure Recipe Detail around the key tools (send to camera, create recipe card, distribution & versions) as sub-sections. - Document the three card designs with a single side-by-side example image, explain the sharing motivation, and note the open-format plan. - Expand 'Send recipe to camera' with the connection steps and a short demo GIF; add the motivation and version-timeline walkthrough for distribution & versions, with its panel and change-group images. - Rephrase throughout to avoid em dashes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Recipe cards used to be a single square layout with a handful of config toggles. This PR turns card rendering into a set of pluggable designs and adds two new ones built for full-screen sharing.
A card is now produced by a
CardDesign(anabc.ABC): each design owns its own composition logic and canvas size, while the shared concerns (the QR code and its spec, the logo, EXIF embedding, file saving) stay common so every card scans and imports the same way regardless of how it looks. The four legacy templates collapse into a singleClassicDesignthat keeps their persisted identifiers, so existing cards and therecipe.card.createdevents are unaffected.Two new portrait designs (1080×1920, 9:16, sized for Instagram Stories/Reels) are included:
The create-card modal gains htmx tabs, so a user picks a design and sees only that design's options with a live preview. The two photo-centric designs are built around a real example photo and require a background image; Classic keeps the gradient fallback.
Around the feature, previews are made cheaper (composed once, sourced from the cached gallery thumbnail, and no longer served stale from the browser cache), the Aperture darkening gradient is configurable through settings, and the web-interface docs are restructured to walk through the whole recipe workflow, including the new designs.
Rationale and the trade-offs (notably the choice of an ABC and per-design canvas sizes) are recorded in ADR 012.