Conflict resolutions + integration fixes:
- CreatePost: kept the branch's merge-into-existing meta persistence (equivalent
to main's #86 replace on create, and what the automations flow was built on).
- FacebookSettings.vue: kept both new defaults (previewOnly + meta).
- RunGenerateNode + GenerateNodeConfig.vue: ContentType::InstagramCarousel was
removed on main (#80); an IG carousel is now a multi-image instagram_feed, so
the carousel-capable list uses InstagramFeed.
- GenerateNodeTest: fixtures use the ContentType enum and the new instagram_feed
carousel signal.
Instagram carousels were stored as content_type=instagram_carousel, which the
publisher's match() did not handle — publishing failed with "Unsupported
Instagram content type: instagram_carousel" for any post created via API, MCP,
or template (the AI flow worked only thanks to an inline band-aid that rewrote
carousel to feed before saving).
A carousel is just an Instagram feed post with multiple images: the editor,
preview, and publisher already treat a multi-image feed as a carousel. So
instagram_carousel is a generation format, not a stored content type. Remove it
from the ContentType enum entirely; it now lives only as an AI generation-format
string (wizard card + slide structure + carousel templates are untouched), and
posts always persist as instagram_feed.
- ContentType: drop the InstagramCarousel case; InstagramFeed maxMediaCount 1 -> 10
- StreamPostCreation: resolvedContentType() maps carousel -> feed; band-aid removed
- StartPostCreationRequest: accept instagram_carousel as a generation format
- Frontend: carousel becomes a wizard-local AiFormat; UI/UX unchanged
- API/MCP now reject instagram_carousel as a content_type (Rule::in no longer lists it)
- Added new automation-related routes and controllers for managing automations.
- Introduced automation nodes in the UI with distinct styles and interactions.
- Updated sidebar to include navigation for automations.
- Enhanced post creation logic to support automation metadata.
- Refactored content type and platform enums into types for better type safety.
- Added localization for automation-related terms in English, Spanish, and Portuguese.
- Improved error handling in various components to accommodate new features.
Facebook Stories do not work reliably with images via the Graph API.
Align validation and publisher with video-only (like Reel), while
Instagram Story continues to accept image or video.
Co-authored-by: Cursor <cursoragent@cursor.com>
Core changes:
- Replace Unsplash slide pipeline with gpt-image-2 via Laravel AI SDK.
New AiImageClient builds prompts from a Blade template seeded by the
workspace's ImageStyle enum, content language, brand color (mapped to a
human-readable name via HexColorName helper) and brand description.
- Drop Template B from TemplateImageGenerator: every slide now renders as
Template A (full-bleed photo + bottom gradient + white/grey overlay).
Removes renderTemplateB, roundCorners, blendHex, ensureContrast and the
closing-slide pipeline.
- StreamPostCreation creates the Post directly and dispatches
PostCreationReady with post_id; the wizard kills its preview step and
redirects straight to the post editor on completion. Finalize endpoint
removed.
- New Workspace.image_style enum field with an 8-option visual picker
shared by /workspaces/create and /settings/workspace/brand via a single
BrandForm component (autofill is a prop). 8 sample webp thumbs ship
under public/images/branding/image-styles/.
- Media items gain optional source ('ai'|'unsplash'|'giphy') and
source_meta (recipe needed to regenerate AI images later); the gallery
picker tags Unsplash/Giphy attachments.
- Brand-color autofill: new CssColorFrequencyExtractor parses every
hex/rgb/hsl value in the homepage CSS, clusters perceptually similar
shades in CIE LAB (Delta E 76 < 12), filters neutrals and returns the
most frequent cluster. Solves Tailwind/utility-CSS sites where no
semantic --primary variable is exposed.
- Credits: gpt-image-2 metered at 15 credits/image (low quality default).
- Layout: AuthSplitLayout right column is sticky/h-svh so the form
textarea growth no longer stretches the marketing slider.
- i18n cleanup: localized labels follow the no-em-dash convention.
Two new composables under resources/js/composables/echo/ that abstract
channel naming for the two Reverb subscription patterns we now use:
- useWorkspaceEcho — list/dashboard views: 1 subscription per workspace
delivers events for many entities. Resolves currentWorkspace.id from
Inertia shared props; no-ops when there is no workspace yet.
- usePostEcho — focused views (Show, Edit): per-post channel.
Posts index now subscribes via useWorkspaceEcho to the 3 lifecycle
events (post.created / post.deleted / post.platform.status.updated)
and reloads the posts prop on receipt. `reset: ['posts']` is required
on the reload — without it, InfiniteScroll's merge prop appends every
broadcast onto the existing list and produces duplicates.
Show / Edit migrated from raw useEcho to usePostEcho; AiGenerateDialog,
NotificationBell and AiPostWizard updated to the new event names from
the namespaced convention. AiStream composable moved into the same
echo/ folder for organisation; its bound event names (TextDelta /
StreamEnd / Error) come from the laravel/ai package and stay as-is.
Bonus: comments tab send button was overriding shadcn's size="icon"
(40px) with class="size-9" (36px), which left it visibly shorter than
the textarea. Removed the override and matched the textarea's
min-h to size-10 so both render at 40px.