Commit Graph
1503 Commits
Author SHA1 Message Date
Matthew Meszaros e38de91fec feat: add an in-process postgres task scheduler so delayed sends need no cloud tasks 2026-07-20 09:56:02 +02:00
Matthew Meszaros 8de314ae8b feat: serve public blobs from the backend and fix avatar uploads on filesystem 2026-07-20 09:56:02 +02:00
Matthew Meszaros 0f1886b9dd feat: add a filesystem blob storage backend 2026-07-20 09:56:02 +02:00
Matthew Meszaros 7e84da7f2a feat: add config helpers for the provider switches 2026-07-20 09:56:01 +02:00
Matthew Meszaros d90e07659e chore: remove the dead astra/cassandra code and deps 2026-07-20 09:56:01 +02:00
Matthew Meszaros a813096b8b Merge pull request #75 from warmbly/feature/compose-email
Compose experience, AI everywhere, undo send, and the iOS ports
2026-07-20 06:53:13 +02:00
Matthew Meszaros 0ecaf008f3 feat: iOS From picker parity + tag-scoped Auto: the tag chip row becomes a native Menu filter offering every user tag (not just in-use ones) with color dots and checkmarks, filtering turns the Auto row into Auto in <tag> previewing the best in-tag candidate and picking it carries autoTagID into the compose send as from_tag_id (cleared by explicit picks), the From row reads Auto in <tag> with the best member email via account tags lifted into ComposeStore, and ContactPickerSheet swaps its category chips for the same compact menu idiom 2026-07-20 05:33:06 +02:00
Matthew Meszaros 847f3ba472 feat: document the From menu in the unibox guide: Auto scoring order, tag filtering, tag-scoped Auto picking within a mailbox group, and the from_tag_id API field 2026-07-20 05:28:05 +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 f20300a280 feat: tag-scoped auto mailbox pick on compose: POST /unibox/compose accepts from_tag_id which restricts the automatic sender choice to active mailboxes carrying that tag (best-with-budget-first within the group, 400 when the tag has no active members, ignored with an explicit account; GetByTags is user-scoped so foreign tag ids resolve to no members) 2026-07-20 05:28:05 +02:00
Matthew Meszaros 91c12e91be feat: document undo send: the unibox guide gains an Undo send section (default 30s, 5-120s under Settings Profile, header countdown with cancel, scheduled and smart sends unaffected) and the unibox API reference notes instant sends hold for the undo window with scheduled_at reflecting it and DELETE /unibox/scheduled/:task_id cancelling inside it 2026-07-19 18:57:32 +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 10217039f0 feat: undo send in the iOS app: instant compose and reply sends swap the sent toast for a dark UndoSendBanner capsule counting down to the server's scheduled_at with an Undo button that cancels via DELETE unibox/scheduled/:task_id, compose undo reopens the sheet through the draft-resume path with the full payload and reply undo re-presents UniboxComposer prefilled from a UniboxReplySnapshot, 404 collapses to an Already sent toast, expiry flips the capsule to Sent briefly, and the User model gains undo_send_seconds (default 30 when absent) 2026-07-19 18:53:21 +02:00
Matthew Meszaros db400ac086 feat: undo send window on the backend: migration 000075 adds users.undo_send_seconds (default 30, CHECK 5-120), emailsend queues instant sends (compose, reply, agent-draft approvals) that many seconds into the future so the existing DELETE /unibox/scheduled/:task_id cancel path can still stop them, the value rides /auth/me and a new JWT-only PUT /me/send-preferences updates it with bounds validation and user-cache invalidation 2026-07-19 18:45:50 +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 2aeaa577c8 feat: iOS mailbox tag management: a reusable tag picker sheet (search, colored-dot rows, multi-select, inline Create-tag that POSTs /tags cycling the palette and refreshes the session), a Tags section on the mailbox detail Setup tab editing via the optimistic PATCH /emails/:id path, and bulk Add/Remove tags in the list's selection capsule driving the new PATCH /emails/tags endpoint with reload, select-mode exit, and a light haptic on apply 2026-07-19 16:14:17 +02:00
Matthew Meszaros 07315cb118 feat: iOS compose polish from simulator feedback: the AI menu now targets highlighted text (shared selection quick actions plus a Custom edit through the prompt bar's editing-selection mode, stale-range guarded), the From picker gains tag filter chips built from the account directory and the member's tag definitions (only tags actually in use are offered), and every recipient field gets a plus button opening a browse-contacts sheet with search, category chips, multi-select checkmarks, and already-added rows disabled 2026-07-19 16:14:17 +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 e1750d8988 feat: add PATCH /emails/tags for bulk mailbox tagging: add_tags/remove_tags set semantics across up to 1000 mailboxes in one transaction (ownership of both mailboxes and tags enforced in SQL so stale ids are skipped, composite-PK insert makes re-adds no-ops), naturally idempotent so no Idempotency-Key needed, one audit entry with account/added/removed counts riding the email_account spine, and the endpoint registered in the docs scope map under WRITE_EMAILS 2026-07-19 16:13:43 +02:00
Matthew Meszaros d21711523f feat: port the AI assistant to the iOS app: a sparkles button on the Home hero opens a full-screen chat that streams answers over SSE with live text deltas and tool-step chips, pauses on approval_required with Approve/Always allow/Deny, rehydrates persisted transcripts, and shows remaining credits or the free-model note; the history sheet groups conversations into Today/Yesterday/This week/Earlier with title search, swipe delete, clear-all behind a confirm, live refresh off the ai_session spine, and owner attribution when shared history is on 2026-07-19 15:04:50 +02:00
Matthew Meszaros b5515bef0f feat: bring the iOS reply composer to compose parity: To/Cc/Bcc swap their plain text fields for the shared chip recipient fields with contact autocomplete and category filters, the AI surfaces gate on the Use AI permission instead of manage_campaigns, and the duplicated schedule sheet gives way to the shared ComposeScheduleSheet 2026-07-19 15:04:50 +02:00
Matthew Meszaros 06ad6cf51b feat: port the compose experience to the iOS app: a Gmail-style FAB on the inbox opens a new-email sheet with a scored From picker (Auto pick with the live recommendation, auth dot, today budget, history reasons), chip recipient fields with category-aware contact autocomplete, a suppression warning strip, instant/smart/scheduled sends through POST /unibox/compose, autosaved drafts (client-generated id, debounced idempotent PUT, close keeps the draft, send deletes it) with a Drafts drawer row to resume or swipe-delete, and a grounded AI draft bar that can return a clarifying question answered inline, with grounding and usage on the review card 2026-07-19 15:04:42 +02:00
Matthew Meszaros 0c6ef58bd8 feat: iOS core plumbing for the AI + compose port: APIClient gains a native SSE stream method (Bearer-attached byte stream with one refresh retry on 401, data:-line parsing, 600s timeout for long agent runs, task cancellation aborts the run server-side), OrgPermissions gains the Use AI bit (32768), and the realtime spine maps the ai_session audit entity to a new ai invalidation domain 2026-07-19 15:04:35 +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 2de973df68 feat: add DELETE /ai/sessions/:id so members can remove an assistant conversation: repository delete scoped by org and user with the transcript cascading via FK, not-found on zero rows, an ai_session delete audit entry (which the existing realtime spine turns into live history refreshes), and the endpoint registered in the docs scope map 2026-07-19 10:50:33 +02:00
Matthew Meszaros 459d423bb5 feat: document the assistant's live streaming behavior in the guide: answers stream in word by word and each tool step appears the moment it starts 2026-07-19 10:42:10 +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 4e576daddf feat: stream the dashboard agent's text token by token: the OpenAI-compatible provider now runs chat completions with stream=true whenever a listener is attached, forwarding content deltas as a new EventTextDelta (SSE text_delta) while stitching tool-call fragments by index, capturing usage via stream_options with a sticky compatibility fallback, tolerating backends that ignore streaming, and always closing each block with the authoritative full-text event (now also emitted for commentary alongside tool calls) 2026-07-19 10:41:45 +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