Skip to content

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 23:13
· 134 commits to main since this release
7784e4a
  • Design system refresh addressing v0.6.0 production feedback: typography, type hierarchy, visual personality, and consistent save/error feedback across every admin page. - Typography: adds Plus Jakarta Sans as the app's UI/body face (via --font-sans), replacing the unstyled default system-font stack. Newsreader — previously reserved for the "LatestArr" wordmark — now also sets each page's own <h1> (a new PageHeader component, used on Dashboard, Sources, Recipients, SMTP Profiles, Newsletters, and Templates) at a larger display size, giving the app an actual typographic voice at the one spot per page where it doesn't compete with body text. - Hierarchy: standardizes the in-page section-label tier (previously plain text-sm font-medium — the same weight as plenty of nearby bold body text, e.g. "Template", "Sources", "Recipient groups", "Send history" on the Newsletters page) into a new SubsectionHeading component: a small uppercase, tracked-out label distinct from both the page <h1> and from bold body copy. Page titles and <h2> section headers were already consistent and are unchanged. - Visual personality: adds violet as a restrained secondary accent (Tailwind's own palette, matching how Badge's success/warning variants and the dashboard's stat tiles already work — no new color tokens) via a new accent Badge variant, used on the dashboard's "Optional" checklist tag and a template's "Designed" status. Floating chrome that has real content behind it to blur — Popover, Select, Dialog/Sheet content, and the new toast notifications — gets a translucent, blurred ("glassy") background instead of a flat opaque one; static cards get a faint inset highlight ring for the same read without the wasted compositing cost of blurring nothing. Every button now gets a quick press-down scale on :active (previously only the primary variant's hover-lift existed, from the original Bloom rebrand), and the primary button's hover-lift transition now animates only transform/box-shadow/background-color/color/border-color instead of transition-all. - Toasts: adds a Radix-based toast system (ui/toast.tsx + ui/use-toast.ts + a Toaster mounted once in App.tsx, following this codebase's existing Radix-wrapper pattern) and wires it into every save/update/delete/toggle/test/send action across Sources, Recipients, Groups, SMTP Profiles, Newsletters, and Templates — including several actions (the Newsletters template picker, group-member add/remove, the enabled toggle) that previously gave no feedback at all, or reused unrelated inline error state. The template editor's existing inline "Saved" indicator is untouched; it now gets a corresponding toast alongside it rather than being replaced. - Reuses the existing SourceLogo component on the Newsletters page's linked-source badges, which were missing the per-source-kind icon that the Sources page itself already shows. All recolored/retinted surfaces were checked against WCAG AA (4.5:1) in both themes, including the new translucent surfaces at worst-case backdrop extremes.
  • Redesign the relationship between the top banner and the sidebar footer, addressing feedback from a production user: the header was mostly dead space (just the theme toggle), the sidebar footer's account email got cut off next to the Edit profile/Sign out buttons, and the Star icon linked straight out to GitHub with no context. The version/GitHub/Star/About cluster and the account summary (name, email, edit, sign out) now live in the desktop header, which has the width to give the email room to stay legible (still truncates gracefully with a title tooltip if needed) instead of the cramped 240px sidebar column; the sidebar itself is now just branding-free navigation. The Star icon now opens a small popover — matching the existing About popover's pattern — with a short explanation of why starring helps, and a button that actually links out to GitHub, instead of linking out immediately. The version badge's text is also a size larger while keeping the same mini-card styling. The mobile nav sheet keeps the same compact card layout as before, unchanged.
  • Addresses the most common piece of v0.6.0 production feedback: newsletters rendering "very plain, no images/posters/covers." Only the RomM adapter ever populated posterUrl — Plex, Tautulli, Audiobookshelf, and the BookLore family all left it empty, so their items never got a poster in the default template or a Media List block, even though the rendering side already supported one. Wires poster/cover art through for all four remaining adapters: Plex maps its thumb field to a token-bearing absolute URL; Tautulli adds the thumb/art fields its API was already returning but the client wasn't reading, resolved through its own pms_image_proxy command; Audiobookshelf maps media.coverPath to its token-bearing cover endpoint; and the BookLore-family client gains real OPDS <link rel="...image"> parsing (preferring the full image over the thumbnail), which it had none of before. Rather than putting a source's own URL (frequently LAN-only, and often carrying that source's credentials as a query param) directly into a sent email, a new pipeline step (apps/server/src/pipeline/embed-images.ts) fetches each item's image server-side at send time — using the same stored, decrypted credentials the pipeline already polls that source with, via a new SourceAdapter.fetchImageBytes() (replacing the previously unimplemented resolveImageUrl) — resizes it to a thumbnail with sharp, and attaches it to the outgoing email as a Nodemailer CID attachment. The rendered HTML references it with <img src="cid:...">; a recipient's mail client never makes an outbound request of its own. This is applied uniformly to the default template and to the GrapesJS Media List block. Every step fails soft, per item: an unreachable source, a failed fetch, or an image sharp can't decode falls back to a small blank-pixel data URI for that one item rather than failing the whole send. Embedding only ever happens for images a Media List block's own selection (count/order/showAll/emptyFallback) actually renders — real posterUrls are swapped for opaque placeholder tokens before the template renders, and only the tokens that survive into the compiled HTML get fetched, resized, and attached afterward. Otherwise a block showing 5 items out of an 80-item library would fetch, resize, and attach all 80 posters even though only 5 ever appear in the email, undermining the whole "keep message size sane" point of CID embedding. Referenced images are also fetched/resized concurrently rather than one at a time, and the "most watched" and "empty-pool fallback" item pools are fetched concurrently with each other rather than sequentially. Every adapter's image fetch (fetchImage/fetchOpdsImage) is also now bounded by a 10s AbortSignal.timeout() — since resolvePosterPlaceholders awaits every referenced item's image via Promise.all, one source that hangs instead of erroring would otherwise stall the whole send indefinitely rather than failing that one item's poster softly. Also replaces every adapter client's baseUrl.replace(/\/+$/, "") trailing-slash trim with a new shared trimTrailingSlashes() helper (@latestarr/adapter-core) — a plain linear scan instead of a regex CodeQL flagged as a polynomial-time ("catastrophic backtracking") pattern on uncontrolled input. Also, from the same feedback: - TV episode titles: a tv_episode item's prominent title was just the bare episode name (e.g. "Winter Is Coming"), with the series name buried in the subtitle — it now leads with the series title, with "SxxExx - Episode Name" as the subtitle, for both Plex and Tautulli. Rendered items also now show a release date alongside the added date when the source provides one. - Content-kind labeling: BookLore-family items are now labeled "Ebook", "Comic", or "Book" (derived from the OPDS acquisition link's MIME type), and Audiobookshelf items "Audiobook" or "Podcast", surfaced as a small badge next to the title in both the default template and the Media List block — without widening the shared MediaKind enum for it. - Smarter Media List blocks: a new "Random order" trait picks random items from the matching pool instead of always the first N; a new "Show all items in the period" trait removes the count cap entirely; and a new "When nothing matches this period" trait offers a random-items-instead fallback (sampled from an all-time pool, only fetched when a block actually needs it) or a plain link back to the source, instead of silently rendering an empty section.