Releases: BeamLabEU/phoenix_kit
Releases · BeamLabEU/phoenix_kit
v1.7.132
1.7.132 - 2026-06-07
Added
PhoenixKitWeb.Components.AITranslate.Embed— ause-able macro that
wires the host side of the AI-translate modal so consumers stop hand-copying
it. Viaattach_hooklifecycle hooks it injects the sixai_*handle_event
clauses (ai_toggle_modal,ai_select_endpoint,ai_select_prompt,
ai_select_scope,ai_generate_prompt,ai_translate_lang) and the
{:ai_translation, …}handle_infoclause, composing with the host's own
handlers (non-AI events/messages pass straight through). Form re-sync defaults
to assigning both:changesetand:form; hosts whose sync differs override
ai_translate_assign_form/2. Because lifecycle hooks run before the host's
own callbacks and:halton the events they own, a host clause for those AI
events is shadowed — the moduledoc documents this so the AI clauses aren't
re-implemented in the host.- V131 migration adds a generic
metadata JSONB NOT NULL DEFAULT '{}'
column tophoenix_kit_staff_people(mirrors theentity_datashape). The
first consumer is staff soft-delete, which stashes the prior lifecycle status
undermetadata["trashed_from_status"]; the column is general-purpose so
future per-person metadata needs no new migration. Idempotent
ADD COLUMN IF NOT EXISTS;@current_version→ 131.
Fixed
- Media detail comments: forward Leaf editor events to the embedded
CommentsComponent. The Leaf rich-text composer reports its content to the
host LiveView via a{:leaf_changed, …}process message;media_detail
wasn't forwarding it, so "Post Comment" silently posted empty content. Adds
the runtime forward (resolved against the optionalphoenix_kit_commentsdep)
plus ahandle_infocatch-all so unmatched messages don't crash the LV.
Changed
- Extract
PhoenixKitWeb.CommentsForwarding.forward_leaf_changed/2— the
{:leaf_changed, _}forwarding contract (optional-dep guard + runtime
apply/3+:pass/contract-drift handling, logging unexpected returns) now
lives in one module.MediaBrowser.EmbedandMediaDetailboth delegate to
it instead of carrying near-verbatim copies, so a future
forward_leaf_event/2contract change touches one call site. As a side
effect theMediaBrowser.Embedmacro no longer injectsrequire Loggerinto
every host (the onlyLoggeruse moved into the shared module). - Document required host wiring on the callback-message components
MediaSelectorModal,MarkdownEditor, andMediaGallery— loud
"required host wiring / silent failure otherwise" moduledoc contracts. These
stay docs-only by design (per-consumer handling, not uniform boilerplate, so
no Embed macro).MediaSelectorModalalso documents the:notifyalternative
for LiveComponent consumers. - Dependency bumps (
mix.lock):bandit1.11.1 → 1.12.0,etcher
0.6.5 → 0.6.6,fresco0.6.3 → 0.7.1,spitfire0.3.12 → 0.3.13,tesla
1.18.3 → 1.20.0.
v1.7.130
Cumulative release since 1.7.128 — bundles the generic AI-translation pipeline (PR #582), the per-user Etcher color palette (PR #581), and follow-up security/quality fixes. No new migration (still V128).
Added
- Generic AI-driven translation pipeline in core, so feature modules plug in via a small adapter instead of re-implementing the whole stack:
PhoenixKit.Modules.AI.Translatablebehaviour (fetch/2,source_fields/2,put_translation/4, optionalpubsub_topics/1), exposed via the optionalai_translatables/0callback onPhoenixKit.Moduleand discovered throughPhoenixKit.ModuleRegistry.PhoenixKit.Modules.AI.Translationsorchestration — availability, endpoint/prompt defaults, idempotent shared prompt,enqueue/1+enqueue_all_missing/2, per-resource + global PubSub topics,missing_languages/3.PhoenixKit.Modules.AI.TranslateWorker— generic one-job-per-language Oban worker with retry classification,{:snooze, 30}on rate-limit, and anai.translation_addedaudit entry.
- Shared AI-translate UI —
PhoenixKitWeb.Components.AITranslate(button/modal/progress/stall hint) +AITranslate.{FormGlue,FormBinding}LiveView glue behind a 3-callback binding.
Changed
- AI-translation broadcasts keep translated content off broad topics: the full payload (with
:fields) goes only to the per-resource topic; the global + adapter topics receive a content-free summary.
Fixed
- Sanitize the per-user Etcher color palette on both write and read.
MediaCanvasViewerfilters the client-suppliedetcher:colors-changedpayload to short, color-shaped strings (deduped, capped at 24) before persisting, andload_user_colors/1re-sanitizes on read — so a palette stored before the guard shipped, or written by any other path, never reaches<Etcher.layer colors={…}>untrusted.
📦 https://hex.pm/packages/phoenix_kit/1.7.130 · 📖 https://hexdocs.pm/phoenix_kit/1.7.130
1.7.106 - 2026-05-08
Added
- V111 migration: PDF library tables for the upcoming catalogue PDF subtab (PR #516)
phoenix_kit_cat_pdfs— thin per-upload row.file_uuidFK tophoenix_kit_files(uuid)ON DELETE RESTRICT (catalogue manages the file lifecycle; core prune can't remove files referenced by a live catalogue row). Soft-delete viastatussentinel (active/trashed) +trashed_at. Two uploads of identical content (different filenames) → two rows sharing onephoenix_kit_filesrow + one extractionphoenix_kit_cat_pdf_extractions— keyed byfile_uuidPK. Worker state machine (pending → extracting → extracted | scanned_no_text | failed) +page_count+extracted_at+error_message. Cascades on file hard deletephoenix_kit_cat_pdf_page_contents— content-addressed dedup cache. PK oncontent_hash(SHA-256 hex of normalized page text). Same page text across multiple PDFs is stored once. GIN trigram index lives here so the search index doesn't grow with cross-PDF duplicationphoenix_kit_cat_pdf_pages— composite PK(file_uuid, page_number);content_hashFK to the dedup cache (RESTRICT — orphaned content rows GC'd by a catalogue-side helper, not by FK cascade)- Enables
pg_trgmextension;@current_version110 → 111
PhoenixKit.KnownPackages— live catalog of known external PhoenixKit packages, replacing the previously hardcoded list inModuleRegistry.known_external_packages/0(PR #523)- Fetched on demand from
https://hex.pm/api/packages?search=phoenix_kit_&sort=nameand cached for 10 minutes in an ETS named table (:phoenix_kit_known_packages_cache) - Stale-while-revalidate with cap: on Hex failure, serves cached data up to
:max_stale_age_ms(default 24h); beyond that, drops the cache and falls back to:extra_known_packagesconfig entries only :warninglog on stale-served and empty-cache-extras-only;:errorlog when cache exceeds max stale age — operationally distinct alert levelsLink-header pagination with a 20-page cap (@max_pages) so a malformedLinkheader pointing back to the same page can't loop foreverextra_known_packagesconfig knob — parent apps with private/forked packages declare them inline and they take precedence over Hex entries on thepackagededup key (source: "config"baked in)hex_docs_icon_name: hero-<name>convention — package authors append the marker to their Hex package description and the catalog UI picks it up; default ishero-puzzle-piece
- Fetched on demand from
- Per-module gettext support on Dashboard sidebar labels and tooltips (PR #522)
PhoenixKit.Dashboard.Tabgainsgettext_backend: module() | nil(defaultnil) andgettext_domain: String.t()(default"default") fields, pluslocalized_label/1andlocalized_tooltip/1resolvers that callGettext.dgettext/3when a backend is set and fall back to the raw label otherwisePhoenixKit.Dashboard.Groupgains the same two fields pluslocalized_label/1Tab.divider/1andTab.group_header/1accept the new opts;Tab.new/1round-trips both viaget_attr/2- 14 render sites in
Sidebar,AdminSidebar,TabItemswaptab.label→Tab.localized_label(tab)and equivalents — mechanically uniform, no shape changes - Hot-reload safety via
Map.get/2(not pattern matching) on the new fields — old-shape%Tab{}cached in ETS or:persistent_termfrom before the upgrade falls through as ifgettext_backendwerenilrather than raisingFunctionClauseError. Pinned by an explicitMap.delete(:gettext_backend)regression test guides/per-module-i18n.md— public guide for module developers (setup checklist,mix.exs/ backend /.poflow,dynamic_children/2locale handling, dividers and group headers, tooltips, greenfield template, retrofitting checklist, smoke test pattern, common pitfalls including the hot-reload safety contract)dev_docs/instructions/2026-05-08-per-module-i18n-procedure.md— internal operational procedure capturing every gotcha hit during the Newsletters pilot (skip-worktree on mix.exs, path-dep workflow during local dev, conditional CI skip pattern for graceful degradation)
:per_translation_urlsattr on the threeLanguageSwitchervariants —language_switcher_dropdown/1,language_switcher_buttons/1,language_switcher_inline/1(PR #525)- Each entry is
%{code: <display_code>, url: <full_url>}. Both atom-keyed and string-keyed entries accepted (useful when the list comes from JSON/JSONB rather than Elixir code) - Resolves each language's
base_codeagainst the list viaDialectMapper.extract_base/1so"en-US"and"en"both resolve cleanly. Falls back to the locale-rewrite default when no entry matches OR the matched entry has anilURL (e.g. an unpublished draft) - Useful when a feature module has computed canonical URLs that the simple locale-rewrite default can't reproduce — for example publishing's per-language URL slugs where
/en/blog/my-postand/fr/blog/mon-articlearen't related by segment swap. Passassigns[:phoenix_kit_publishing_translations]from the layout - 7 new tests in
test/phoenix_kit_web/components/core/language_switcher_test.exspin the contract (atom-keyed, string-keyed, full-dialect normalization, per-language fallback, nil/empty/missing-attr pass-through)
- Each entry is
- Drag-handle scoping + sortable feedback infrastructure (PR #525)
<.table_default>emitsdata-sortable-handle=".pk-drag-handle"when@on_reorderis set; only the.pk-drag-handleelement getscursor-grabstyling. Click-to-expand / button-press / text-selection on a card no longer fights with SortableJS drag detectionSortableGridJS hook: newsortable:flashLV→client event handler. The host LV pushes{uuid: "...", status: "ok" | "error"}after eachreorder_itemsattempt; the hook appliespk-sortable-flash-{ok,err}class for ~1.2s, idempotent via reflow trigger. Queries every[data-id]element so table-view + card-view both animate. Defensive status-validation guard — unknown values bail rather than falling into the err-class branch<tr>cell-width preservation viaonChoose/onUnchoose— SortableJS'sforceFallback: true+fallbackOnBody: trueclones the dragged<tr>todocument.body, where it loses its<table>ancestor and<td>s collapse to content width. The hook now snapshots computed widths and pins them inline before the drag preview renders;onUnchooserestores themdata-sortable-handleattr threads to SortableJS'shandleoption for any caller;moved_idalways included in thereorder_itemspayload (was only on cross-container moves) so the LV can push back asortable:flashkeyed to the just-moved row
- MediaBrowser modal viewer becomes the default click target for non-admin / non-select_mode browsers, with read-only image / video / PDF / icon preview, metadata sidebar, Download button, prev/next chevrons (and ←/→ keyboard shortcuts), and Esc / backdrop close (PR #519)
- Mobile-fullscreen layout via
position: fixed; inset: 0— bypasses daisyUI's grid + iOS Safari's 100vh/100dvh quirks. Desktop reverts to95vw × 90vhcentered modal with rounded corners. The!-prefix utility chain on.modal-boxis required because daisyUI v5's defaults win the cascade over plain Tailwind utilities MediaImageZoomJS hook lazy-loads Panzoom 4.6.0 from jsDelivr when the modal opens; image supports wheel/pinch/double-tap zoom and drag-pan. Listener attaches to the parent so the cursor doesn't have to land on the image;destroyedcleanup removes the wheel listener and destroys the Panzoom instance- Bulk-select still reachable — clicking the toolbar's Select button flips
select_modeon, and from then on clicks toggle selection instead of opening the modal
- Mobile-fullscreen layout via
- LiveView login redirect now carries the original request path as
?return_to=(PR #519)- New
login_path_with_return_to/1private helper inPhoenixKitWeb.Users.AuthreadsPhoenix.LiveView.get_connect_info(socket, :uri), encodespath?queryviaURI.encode_www_form/1, and threads it into the redirect target. Wired into the fourredirect_require_loginpaths inon_mounthooks - Trailing-slash self-loop guard:
String.trim_trailing(path, "/")on both sides of the equality check, so/users/log-inand/users/log-in/are treated as the same path and no return-to round-trips back to itself - Pairs with the existing
?return_to=flow inlogin.ex(sanitize_return_to/1→:user_return_tosession →log_in_user/3)
- New
PhoenixKit.ModuleRegistry.get_module_key_for_namespace/1— symmetric with the existingget_by_key/1. Resolves a top-level Elixir namespace string (e.g."PhoenixKitEntities") to the registered plugin'smodule_key/0(PR #521)- Iterates
all_modules/0, matches onModule.split(mod) == [top_namespace](exact, single segment), returns the key string ornilfor unmatched - Reads from
:persistent_termso there's no GenServer roundtrip on the hot path
- Iterates
- Microsoft 365 OAuth tenant override + generic
interpolate_url/3helper inPhoenixKit.Integrations.OAuth— providers can now substitute{key}placeholders inauth_url/token_urlfrom per-rowintegration_data, falling back to a provider-level:url_defaultsmap (PR #516)- Closes the previously hardcoded
/common/Microsoft tenant — single-tenant operators got AADSTS50194 errors. Newtenant_idsetup field withcommondefault; multi-tenant remains the default behavior. Three pinning tests intest/phoenix_kit/integrations/oauth_test.exs - Wired into
authorization_url/5,exchange_code/4,refresh_access_token/2. URLs without{pass through unchanged (zero impact on Google / OpenRouter / Mistral / DeepSeek)
- Closes the previously hardcoded
- "Resolve a LiveView module to its permission key" block-comment on
PhoenixKitWeb.Users.Auth.permission_key_for_admin_view/1documenting the four-step resolution order (static map → custom-tabs →PhoenixKit.Modules.<X>.Web.*namespace → registered-...
1.7.100 - 2026-04-22
Added
- V103 migration: nullable self-FK
parent_uuidonphoenix_kit_cat_categorieswith b-tree index on(parent_uuid)for arbitrary-depth category trees. Existing rows stayNULLand become roots — no backfill. No DB-levelON DELETEcascade (subtree cascades are owned by the context layer so they go through soft-delete + activity log) (PR #503) scope_folder_idattr onPhoenixKitWeb.Live.Components.MediaSelectorModal— filters the browse query to the given folder plus any files reached viaFolderLink, and assigns newly-uploaded files into that folder (adopt as home if orphan, else add aFolderLink). Plugins scoping the picker to a single domain object (e.g. a catalogue item) pass this after lazy-creating their folder (PR #503)PhoenixKit.Settings.Setting.optional_settings/0accessor exposing@optional_settingsfor invariant tests- Invariant test (
test/phoenix_kit/settings/setting_test.exs) asserting every empty-string default inPhoenixKit.Settings.get_defaults/0is also in@optional_settings, to prevent the class of bug fixed in PR #502 from recurring
Changed
PhoenixKit.Modules.Storage.Filechangesetfile_typeallowlist widened from["image", "video", "document", "archive"]to include"audio"and"other"so non-image/video uploads bucket cleanly (PR #503)MediaSelectorModal.load_files/2refactored into four composablescope_files_by_{user,folder,type,search}helpers — credo cyclomatic-complexity fix from adding the new scope branch (PR #503)
Fixed
- Settings batch save no longer rolls back when
site_icon_file_uuidordefault_tab_titleis left empty on the General Settings form. Both keys added to@optional_settingsinPhoenixKit.Settings.Settingand seeded with empty-string defaults inPhoenixKit.Settings.get_defaults/0(PR #502) MediaSelectorModal.maybe_set_folder/2errors (from thefolder_uuidupdate orFolderLinkinsert) now log a warning viawarn_on_folder_error/3instead of being silently discarded by_ =. Previously, a failed scope assignment after a successful upload left no trace
1.7.98 - 2026-04-16
Changes
- Add
MediaBrowserlive_component withscope_folder_idscoping (PR #495) - Add trash bucket, drag-drop upload, and breadcrumb improvements to media browser (PR #497)
- Scope-aware Storage helpers; tenant isolation fixes; cron wiring
- Fix scope guard gaps in restore, delete_selected, navigate_to_folder
- Fix flash of root view on page refresh with URL params
- Fix trash filter persisting across URL-driven navigation
v1.7.97
Added
- V97 migration: per-item
markup_percentageoverride on catalogue items (#493) - V98 migration:
alternative_formatscolumn on storage dimensions PhoenixKit.Modules.Shared.Components.ImageSet— responsive<picture>component with AVIF/WebP/JPEG<source>entries (#490)PhoenixKit.Modules.Storage.VariantNaming— format-suffix parsing utility- Multi-format variant generation (WebP/AVIF alongside primary format per dimension)
- Variant dimensions and file sizes on media detail page
- UUID search support on media page search bar
Changed
- V95 migration made truly idempotent for
folder_uuidcolumn - Dimensions table format cell renders as
JPEG + WEBP, AVIF(fixed stray separator)
Fixed
- Long text overflow in media detail sidebar
- Missing original file size in variant download buttons
Hex: https://hex.pm/packages/phoenix_kit/1.7.97
Docs: https://hexdocs.pm/phoenix_kit/1.7.97
1.7.96 - 2026-04-13
Added
- Sortable languages in admin (drag-and-drop reorder)
- hide_source option on DraggableList component
- Wiggle animation for reorder mode with prefers-reduced-motion support
Changed
- Dedup language codes in reorder, use MapSet for lookup
- Extract wiggle CSS to JS-injected styles with pk- prefix
1.7.95 - 2026-04-11
Added
- V95 migration: media folders and folder links tables
- V96 migration: catalogue_uuid FK on catalogue items for direct catalogue membership
1.7.93 - 2026-04-08
- Fix installer to auto-inject PhoenixKitHooks into app.js
- Fix decrypt after legacy integration migration
- Activity logging for Integrations
- Simplify integration status (connected/disconnected)
- Cookie max_age set to 60 days
- OAuth remember_me by default
1.7.92 - 2026-04-07
- Fix Google token refresh for named integration connections
- Add V94 migration for Document Creator sync