Image-columns config in template editor + Categories tab last in sidebar#21
Merged
Merged
Conversation
Composer now writes a caller-supplied :data opt into the Document
changeset, and schema_to_file_map/1 exposes the data field. This lets
the host app store the template/image selection ("recipe") that
produced a document so it can be re-created later.
Replace boolean categories_trash/types_trash with string status_mode assigns (active/trashed); replace toggle_categories_trash and toggle_types_trash with a single switch_status event using phx-value-target and phx-value-mode, matching DocumentsLive. Extract status_subtabs/1 component shared by Categories and Types columns; render in the DocumentsLive visual style (border-b-2 with border-primary for Active and border-error for Trash). Auto-hide the entire sub-tab row when the Trash list is empty and the current mode is active. Track trashed_categories_count and trashed_types_count, reusing the loaded list as count when viewing trash to avoid a second query.
Two related soft-delete UX improvements for Documents/Templates:
1. Deletion metadata is now stamped into the existing JSONB data field
on Document and Template (no migration). On soft-delete,
data["deleted"] = %{"at" => ISO-8601, "by_uuid" => actor_uuid} is
merged via PostgreSQL || operator so existing keys (composer
recipe etc.) are preserved. On restore, data - 'deleted' removes
only the deletion key. A new "Deleted" column appears only on
the trash tab (both list and cards views, Documents and Templates),
showing formatted timestamp + display name resolved via a single
PhoenixKit.Users.Auth.get_users_by_uuids/1 batch lookup.
2. When the underlying Google Drive file is missing (HTTP 404 from
the Drive parents/move endpoint), restore no longer fails with
the generic 'try again' message. do_move_file/2 distinguishes
404 and returns {:error, :drive_file_not_found}, propagated
through restore_document/2 and restore_template/2 to the
LiveView, which renders a yellow dismissible warning:
'File is missing in Google Drive — it cannot be restored. You
can permanently delete this record.'
Coverage:
- 80 existing unit tests still pass.
- New tests in test/errors_test.exs (error message) and
test/integration/drive_bound_actions_test.exs (stamp on delete
with and without actor, preserve other data keys, clear on
restore, Drive 404 on GET-parents and PATCH-move, both Document
and Template paths).
…, empty-uuid short-circuit
- Wrap data column with COALESCE(data, '{}'::jsonb) in both fragments
of stamp_deleted_data/2 and clear_deleted_data/1, so a row with a
NULL data column still gets the deleted metadata written / cleared
correctly. The data column is nullable in the migration.
- Extend restore_document/2 and restore_template/2 tests to include a
sibling 'recipe' key in data alongside 'deleted', and assert it
survives restore — previously only the delete path verified
preservation.
- Skip Auth.get_users_by_uuids/1 when the trashed UUID list is empty
(active tab, or trash tab with no by_uuid stamped rows).
Brings in: - composed-document data persistence (1643a97) - Google Docs page/column width helpers + table-based image insertion (ee0b312, 5d13816) - Unified Active/Trash sub-tabs on Categories page (064c0d2) - _phoenix_kit_sources.css @source directive regen (91605f2) - mix format + scale_height unit comment (8f0c3a7) - columns-aware orchestrator with two-phase table insertion (182de63) - Deletion metadata in data['deleted'] JSONB + Drive 404 restore warning (fc69fa1) - COALESCE-safe JSONB ops + restore preservation test + empty-uuid short-circuit (6944db2)
Bump the :admin_document_creator_categories Tab priority from 647 to 651 so it sorts after Documents (648) and Templates (649). Children of an admin module tab are ordered by ascending priority, so Categories previously appeared first; now it appears last as intended.
Adds a 'columns' field (1..4) to image_list variable configs in the
Document Creator template editor and propagates the saved config to
consumers via image_slots_for_template/1.
- variable.ex: default_image_config(:image_list) now includes
columns: 1 so new variables get a sensible default.
- documents.ex: coerce_config/1 parses and clamps 'columns' to 1..4
(mirrors the existing max_count handling). image_slots_for_template/1
now returns %{name, kind, config} — config is merged from the saved
variable.config with defaults, normalised to string keys so
consumers do not need to handle both atom and string forms.
- variable_config_form.ex: adds a Columns select (1..4) to the
:image_list variant of the form, matching the existing separator/
max_count fields visually.
- _phoenix_kit_sources.css: regenerated by the compile step.
ddon
added a commit
that referenced
this pull request
May 21, 2026
- google_docs_client: make Phase-2 table identification drift-proof.
`match_new_tables/3` matches new tables by document order instead of a
startIndex set-difference, so pre-existing tables located after a
multi-column placeholder no longer trip the count guard and leave grids
empty. Extract `fill_matched_tables/5`. Adds 5 unit tests.
- documents_live: resolve trashed-by display names when the trashed lists
change (load/sync/patch) instead of on every render via assign_files.
- taxonomy: add count_categories/1 and count_types_for_category/2; use them
for the trash badges instead of length(list_*(status: "deleted")).
- documents: stamp/clear data["deleted"] only on the schema that owns the
google_doc_id (derived from folder_key/type), not both tables.
- tests: fix stale assertions that never run in the sandbox (no local DB) —
image_slots_for_template now returns %{name, kind, config}; image
object sizes are emitted in PT, not EMU.
- docs: add dev_docs/pull_requests entries for #20 and #21 with review and
follow-up notes.
Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Two changes on top of PR #20 (
feature/image-columns).1.
columnsfield in the image_list variable config (template editor)Lets template authors set a default number of columns (1..4) per image_list variable, matching the existing
max_countandseparatorfields. The selected value is read by the consumer (Andi's order document picker) and ends up inimage_params[slot]["columns"], which the existinggoogle_docs_client.exalready dispatches on (cols >= 2 -> two-phase table insertion, cols == 1 -> inline).variable.ex:default_image_config(:image_list)now includescolumns: 1.documents.ex:coerce_config/1parses and clampscolumnsto 1..4 (mirrorsmax_count).image_slots_for_template/1now returns%{name, kind, config}so consumers can readcolumns/max_count/etc. without re-querying the template. The merged config is normalised to string keys to avoid mixed atom/string-key maps leaking to callers.web/components/variable_config_form.ex: a Columns<select>(1..4) is rendered for:image_listvariables, sharing the visual style of the existing fields.2. Sidebar: Categories tab moved last
Child tabs are sorted by ascending
priority. The Categories tab hadpriority: 647, which placed it before Documents (648) and Templates (649). Bumped to 651 so it now sorts after both.Notes
main.columnsvalue lives intemplate.variables[*].config(existing:mapfield).image_slots_for_template/1's old callers destructured%{name, kind}which still pattern-matches on the new map;column_overridesin the consumer are additive on the wire.