Commit Graph
495 Commits
Author SHA1 Message Date
Matthew Meszaros 4ad91688e3 feat: adopt the shared variable catalog and chip editor on the templates page so templates get the same personalization experience as campaigns 2026-07-22 17:05:08 +02:00
Matthew Meszaros 0fb3147244 feat: add the AI-variable config model, encode/decode, generate client, and useGenerateAIVariable hook for the block preview button 2026-07-22 17:05:08 +02:00
Matthew Meszaros 470d6f43a8 feat: replace the Write with AI blob action with an inline caret AI assistant in the campaign email editor 2026-07-22 17:04:51 +02:00
Matthew Meszaros 1750d4eb45 feat: wire variable chips, per-recipient AI blocks, and conditionals into RichTextEditor, EmailContentEditor, the preview renderer, and chip styles 2026-07-22 17:04:51 +02:00
Matthew Meszaros 04a8463eba feat: add useAnchoredFloating hook wrapping floating-ui with a fixed strategy so editor popovers anchor correctly inside transformed dialogs 2026-07-22 17:04:51 +02:00
Matthew Meszaros 71df2c8a6d feat: add atomic TipTap variable, AI-variable, and conditional chip nodes with literal-token serialization for the campaign email editor 2026-07-22 17:04:51 +02:00
Matthew Meszaros bb6cb330b6 feat: add the custom-field-keys web client and useCustomFieldKeys hook that feed real org fields into the variable picker 2026-07-22 17:04:51 +02:00
Matthew Meszaros 029ebf7d3b feat: add shared template-variable catalog in web/src/lib/templateVars.ts as the single source for standard merge fields across editors, templates, and preview 2026-07-22 17:04:51 +02:00
Matthew Meszaros bb040828b1 feat: rework automation AI steps to mirror campaign step types with one agentic AI step plus an AI switch, per-case switch handles on the canvas, a Stop node, and a step-type add menu 2026-07-22 10:58:35 +02:00
Matthew Meszaros 05aa7bd726 fix: stop the unibox thread pane flashing on load by reconciling the URL and stored thread id in one effect instead of two that fight 2026-07-21 18:34:37 +02:00
Matthew Meszaros f48ae60476 feat: build the ai step editors and smooth out the flow lines 2026-07-21 17:35:11 +02:00
Matthew Meszaros a44b7b0215 feat: add the unified ai step and ai switch to automations 2026-07-21 17:35:11 +02:00
Matthew Meszaros ffc219a66d feat: clean up the dither charts and give them more room 2026-07-21 17:35:11 +02:00
Matthew Meszaros f898e4975f fix: show the real plan badge, mailbox tags and live sidebar stats 2026-07-21 17:35:11 +02:00
Matthew Meszaros 8055c2f05f Merge remote-tracking branch 'origin/main' into self-host/local-stack
# Conflicts:
#	cmd/backend/main.go
2026-07-20 11:53:21 +02:00
Matthew Meszaros 248347c8e6 feat: warn on the mailboxes page when too few mailboxes are warming for a healthy pool, with an add-mailboxes action and a cloud option 2026-07-20 11:50:53 +02:00
Matthew Meszaros 07c6ae0aa7 feat: kill the settings autosave PUT storm: useAutosave becomes single-flight (the change effect re-arms every render because save is an inline closure, and with the baseline only moving after the response every render during an in-flight save fired another request; concurrent callers now share one in-flight promise and a trailing loop picks up edits made while saving), the notifications settings page hydrates its draft from the server exactly once so refetches never stomp or re-trigger mid-save, and the preferences PUT writes its echoed envelope into the react-query cache instead of invalidating (no more refetch per save) 2026-07-20 08:17:50 +02:00
Matthew Meszaros 85ab4ad7b5 feat: replace the notification email cadence enum with a configurable bundling window and drop the instant mode entirely: email_digest_minutes (30 minute floor, 30 default, 1440 max, constants in config) replaces email_digest with no per-event option or NOTIFICATION_EMAIL_ALLOW_INSTANT escape hatch, the handler validates the range and email_delivery now returns min/max minutes so clients render the bounds, the web settings page offers window presets (30m/1h/3h/daily) plus a Custom minutes NumberInput and iOS swaps the cadence menu for window presets including the server value when it matches none, the repo clamps stored values on read, and the guide plus deployment guide describe the window and keep only the NOTIFICATION_EMAIL_DAILY_CAP env 2026-07-20 08:01:22 +02:00
Matthew Meszaros 682ef3ac24 feat: cost guards on the notification email channel: the per-event instant cadence becomes self-host opt-in (NOTIFICATION_EMAIL_ALLOW_INSTANT, default off - PUT rejects it, stored values read back as smart, holds degrade to smart) with the capability exposed as email_delivery on the preferences GET so web and iOS hide the option on hosted deploys, each user gets a rolling 24h budget of non-security notification emails (NOTIFICATION_EMAIL_DAILY_CAP, default 25, 0 unlimited) counted off sent rows with over-budget alerts skipped to the in-app feed while security sign-ins always send, and coalesced group emails re-verify org membership at flush time so a member removed during the hold is dropped from To; deployment guide documents the three envs and the guide notes both limits 2026-07-20 07:44:43 +02:00
Matthew Meszaros 5d930057d9 feat: harden the notifications settings page against version skew: normalizeNotificationPreferences mirrors the backend defaults merge on the client (missing billing_alert/team_activity/email_digest filled with defaults, invalid cadence coerced to smart) and the page normalizes the fetched preferences before drafting, so an older backend or cached response can no longer crash the category rows 2026-07-20 07:36:17 +02:00
Matthew Meszaros 0186ed5c78 feat: web notification center + digest settings: the bell dropdown becomes a real notification center with per-category icon chips and tones, Today/Yesterday/Earlier group headers, compact relative timestamps, unread accent styling, All/Unread filter pills, first-load skeleton, empty state and a settings footer link; the settings page gains Billing and Team category sections and an Email delivery section using the shared OptionSelect radio idiom (Instant/Smart with Recommended badge/Hourly/Daily) riding the same autosave PUT, with the read-in-app/security-immediate/shared-email note; the preferences model adds billing_alert, team_activity and the email_digest union, and OptionSelect labels widen to ReactNode for the badge 2026-07-20 07:21:20 +02:00
Matthew Meszaros 296e781ce5 feat: scoped Auto in the dashboard From menu: filtering by a mailbox tag turns the Auto row into Auto in <tag> previewing the best-scored member, the pick carries the tag through onChange into ComposeWindow state and the send request's from_tag_id, and the collapsed From row chip reads auto with the tag title while the resolved-signature preview resolves to the best mailbox within the tag 2026-07-20 05:28:05 +02:00
Matthew Meszaros c53225e6f3 feat: undo send in the dashboard: instant compose and reply sends feed a new outbox store and an amber header pill counting down to the server scheduled_at with inline Cancel plus a portaled dropdown listing every pending send with per-row cancel, cancelling a compose reopens the docked composer from a full draft seed and cancelling a reply navigates to the thread and reopens the reply composer prefilled via a restore handoff, 404 collapses to Already sent, and the profile settings page gains an autosaved Undo window NumberInput (5-120s) wired to PUT /auth/me/send-preferences patching the cached /auth/me user 2026-07-19 18:56:55 +02:00
Matthew Meszaros e46fc1cd3e feat: make the composer picker search feel alive: contact searches debounce 300ms and keep previous results on screen while the next query loads (new useDebouncedValue hook + keepPrevious option on useSearchContacts), the browse panel and From menu ease their height with a ResizeObserver-driven AnimatedHeight instead of snapping, in-flight searches show a spinner in the search box plus shimmer skeleton rows on first load, FilterMenu closes itself on any outside click or Escape (its trigger lives inside a data-floating panel the shared hook ignores, so opening the sibling filter now closes the other), and the browse panel widens to 400px 2026-07-19 18:36:31 +02:00
Matthew Meszaros 038668ff51 feat: clean up the composer picker chrome: a new compact FilterMenu (quiet bordered trigger showing the active choice, portaled dot-row dropdown) replaces the chip rows — the From menu's tag filter becomes one small All-tags dropdown beside the search box, and the browse-contacts panel collapses its three header rows (search, category chips, sort row) into a single row with search plus category and sort dropdowns, hugs its content height instead of a fixed 380px, shows one category badge per row, and only reveals the Add footer once something is picked 2026-07-19 18:15:56 +02:00
Matthew Meszaros 7eb8510a89 feat: fix the composer's From tag chips and browse-contacts panel: the From menu now offers every defined tag (not just ones already on a mailbox, so a tag created in the bulk bar is immediately selectable), and the browse panel portals to the body with a viewport-anchored fixed position and flip (the compose window clips overflow, so the 380px panel was being cut off inside the To row and reading as a cramped overflowing box), matching the MailboxPicker portal pattern 2026-07-19 17:06:00 +02:00
Matthew Meszaros 02a524616e feat: composer recipient upgrades: a user-plus button on the shared recipient field opens a browse-contacts panel (search, category filter chips, Recent/Name/Email sort, checkbox rows with category badges, already-added rows disabled, Add-N appends with case-insensitive dedupe) available in both compose and reply, and the docked compose window now auto-minimizes to the corner bar when you navigate to another page so the draft follows you without covering the destination 2026-07-19 16:13:58 +02:00
Matthew Meszaros 5fae462bb2 feat: bulk tag mailboxes from the list's selection bar: a Tags popover with an Add/Remove segmented toggle, tag search, checkbox rows with colored dots, an inline Create-tag row (POSTs /tags cycling the editor's palette and updates the profile cache), and an apply button that hits the new PATCH /emails/tags bulk endpoint, invalidates the emails queries, and keeps the row selection for chaining 2026-07-19 16:13:58 +02:00
Matthew Meszaros ccbb6b068c feat: soften the dither charts for the white theme: tones drop from 600 to 500-level hues (slate-900 to slate-600), dither fill dots render at ~84% alpha so they blend into the page while lines and hovered elements stay fully opaque, legend dots match, and the campaign overview legend swaps its dark active pills (which read as a black background with every series on) for the light slate-100/white style the other pages use 2026-07-19 14:17:11 +02:00
Matthew Meszaros 03634358de feat: compose all metrics on one graph: DitherMultiAreaChart draws a smoothed line per series with nested dithered bands (per pixel the smallest containing band wins so overlaps read as layers) and a shared crosshair dot-per-series tooltip; the shared chart becomes MultiTrend, and analytics, campaign overview, and deliverability swap their single-metric switcher for legend toggles with tone dots that show/hide series in place (deliverability starts with sent hidden so failure signals stay readable, and at least one series always stays on) 2026-07-19 14:13:24 +02:00
Matthew Meszaros 71e04c00dd feat: roll dithered charts across the dashboard: the shared DailyBars becomes DailyTrend (smoothed dithered area graph) powering campaign overview, analytics, and deliverability over-time charts with per-metric tones, deliverability provider placement rows use DitherStack, the mailbox warmup chart becomes a dithered bar chart with target ghost bars and tap-to-pin day selection, API-key traffic uses DitherColumns, and the content score, campaign send progress, credits meter, and unibox queue bars all move to DitherMeter 2026-07-19 14:01:53 +02:00
Matthew Meszaros 7502b0f2b4 feat: grow the dither kit for the analytics surfaces: a violet tone, area-chart tooltips anchored to the hovered dot (flipping below near the top edge), ghost reference bars + tap-to-pin selection on DitherBarChart, DitherColumns stacked vertical columns with per-part tones and hover callback, and DitherStack horizontal share bars with a left-to-right reveal 2026-07-19 14:01:53 +02:00
Matthew Meszaros e7144a439d feat: upgrade the AI usage view: the daily bars become a smoothed dithered area graph with a 7d/30d/90d range switcher (breakdowns follow the range, previous data held while the new range loads so nothing flashes), and limit cells drop the boxy stepper input for a big inline-editable value with a dashed-underline affordance, the dithered slider, quiet text presets, and a No cap idle state 2026-07-19 13:50:03 +02:00
Matthew Meszaros e4ad9e3661 feat: make billing tabs real URL paths: the route becomes billing/:tab? with /app/settings/billing/plans, /ai-credits, and /payment slugs (unknown slugs normalize to the overview), Stripe checkout and topup return URLs point at the tab they left from, the header credits meter deep-links to /ai-credits, the settings topbar subtitle matches nested billing paths, and the document title covers the sub-paths 2026-07-19 13:50:03 +02:00
Matthew Meszaros f0b9c5f49c feat: add DitherAreaChart to the dither kit: a monotone-cubic smoothed line with a dithered area fill fading to the baseline, left-to-right draw-in reveal, and a hover crosshair with dashed guide, line-anchored dot, and tooltip — a real graph, not bars 2026-07-19 13:50:03 +02:00
Matthew Meszaros 7524d1ea37 feat: redesign the AI spend-control experience: each workspace and per-member limit is a cell with an animated expand, a dithered slider with window-sized presets and an exact number field, and a live spent-vs-limit meter; edits collect into a floating Reset/Save bar instead of writing eagerly; the 30-day chart, spend stats, and breakdowns move to dithered canvas fills with tooltips and animated counts, the credits balance ring goes dithered, and the ai-credits guide notes the new tab and limit controls 2026-07-19 13:39:07 +02:00
Matthew Meszaros 587bda2039 feat: split the billing settings page into browsable tabs (Overview, Plans, AI & credits, Payment) with an animated sky underline and per-tab fade transition, sync the active tab to ?tab= so Stripe topup returns land back on the AI tab and the header credits meter deep-links to it, route the current-plan button to the Plans tab, and render the usage rows with dithered meters and animated counts 2026-07-19 13:39:07 +02:00
Matthew Meszaros 98cb220147 feat: add hand-rolled dithered canvas primitives in web/src/components/ui/dither.tsx: Bayer-4x4 ordered-dither bar chart with staggered entrance and hover tooltip, progress meter with a dithered leading edge, donut ring with a dithered sweep tip, a pointer+keyboard slider on a dithered track, and a rAF AnimatedNumber — the dither-kit look with zero new dependencies and prefers-reduced-motion fallbacks 2026-07-19 13:39:07 +02:00
Matthew Meszaros f7a1027367 feat: add workspace-shared assistant history: a manage_settings toggle on the organization (assistant_shared_history, off by default) makes every conversation visible to and continuable by every member with the use-AI permission — the service resolves a session to its owner's rows so open/continue/approve/delete all work cross-member while credits still charge the acting member, the org-wide listing joins owner names for attribution in the history rail, the workspace settings page gains the AI assistant section with the toggle and an explicit exposure warning, and the assistant guide's access and privacy sections describe both modes 2026-07-19 13:22:56 +02:00
Matthew Meszaros 68e3ca6b6b feat: extend the per-member AI limit to all three windows: member_limit_daily and member_limit_weekly join the monthly ceiling on org_ai_settings, MemberSpentInWindows sums the acting member's debits per window in one query, checkSpendLimits enforces day/week/month member ceilings with window-specific 429 messages, and the billing card's Per-member limits row grows the same three fields as the workspace caps 2026-07-19 13:22:56 +02:00
Matthew Meszaros 458234cd5d feat: add clear-all assistant history: DELETE /ai/sessions wipes every conversation the member owns in the workspace (transcripts cascade, count returned, audited as an ai_session delete with clear_history metadata), the history rail gains a Clear history footer action behind the in-app confirm that also closes all session-backed tabs, and the assistant guide documents access gating, per-member limits, privacy, and both delete paths 2026-07-19 11:05:18 +02:00
Matthew Meszaros f4014b8b73 feat: add a per-member monthly AI credit limit to the org spend controls: new member_limit_monthly on org_ai_settings flows through the settings repo, PATCH endpoint, and billing UI (its own Per-member limit field on the AI usage card), and checkSpendLimits enforces it by summing the acting member's debits this calendar month via the ledger's actor attribution (new MemberSpentSince query on the partial index from migration 000073), returning a 429-mapped member-limit error while unattributed scheduled work stays exempt 2026-07-19 11:05:18 +02:00
Matthew Meszaros 7ef7d90d8c feat: introduce the Use AI organization permission (bit 15, the last of the 16-bit mask) gating every AI surface: assistant session routes, generation write/edit, and the compose and reply draft endpoints get a member-permission layer (API keys re-check their existing bit), migration 000073 backfills the bit onto all existing roles and member overrides so nobody loses access, defaults grant it to admin and manager but not viewer, and the web mirrors it in the permission catalog with gating on the header spark button, Cmd+I, the panel itself, and the AI drafting caret (plus team-roles and endpoint-map docs) 2026-07-19 11:04:58 +02:00
Matthew Meszaros bacbf5552f feat: upgrade the assistant's full-screen history rail: conversations group under Today/Yesterday/This week/Earlier sections, a search field filters them by title, each row gets a hover (always-visible on touch) delete action behind the in-app confirm that also closes the conversation's open tab and invalidates the list, the New chat button shows its Alt+N kbd chip, and the guide documents deletion and search 2026-07-19 10:50:33 +02:00
Matthew Meszaros 8eb2824ac4 feat: render the assistant's stream like a live agent and surface its keybindings in the panel: text_delta chunks append to a live-flagged trailing block that the closing full-text event replaces, the trailing block of a running turn types itself out through a rAF SmoothText typewriter with the blinking caret so even chunky providers read as continuous generation, and the empty state gains kbd chips for toggle, new chat, pop out, and minimize plus an all-shortcuts link into the shortcuts dialog 2026-07-19 10:42:10 +02:00
Matthew Meszaros ab938575d3 feat: give the AI assistant a floating window mode on desktop: pop out via the header button, Alt+P, or tearing the docked header off its edge, then drag it anywhere by the header and resize from any edge or corner (360-920 wide, min 400 tall, always clamped fully on screen), with the geometry persisted alongside side and width, a dock-back button targeting the remembered edge, and the assistant guide updated 2026-07-19 10:31:20 +02:00
Matthew Meszaros 633615c341 feat: move the compose-drafts invalidation from useMutation onSuccess into the mutationFn promise chain, because closing the composer fires the save and unmounts in the same tick and observer callbacks are skipped after unmount, which left the rail's Drafts list stale until a full reload 2026-07-19 10:31:20 +02:00
Matthew Meszaros b866d2b99d feat: harden the From menu's tag filter and search field: tag chips now derive from the store's mailbox directory sorted by position instead of the candidates fetch (so they show even while candidates load), the search input gets a real slate border instead of the invisible white-on-white fill, and the panel clamps against clientWidth so its right edge cannot slip under a scrollbar 2026-07-19 10:21:25 +02:00
Matthew Meszaros b06be51783 feat: raise the shared tooltip layer from z-50 to z-150 so hover tooltips (like the AI caret's Write with AI hint) render above the docked compose window at z-70 instead of hiding behind it, while staying under the confirm dialog at z-200 2026-07-19 10:21:25 +02:00
Matthew Meszaros f333260b0f feat: derive unibox search params synchronously from the URL scope (lazy initial state plus a render-phase reset keyed on scope and resolved tag accounts) instead of a post-paint effect, so reloading a scoped URL no longer fires and paints the default all-mail query first and then flashes the thread list over to the scoped result 2026-07-19 10:02:43 +02:00