Public changelog
What's shipping on Farrenio
Notable releases of the FCC Platform — security, performance, analytics, and product surface, in reverse-chronological order. Older items are abbreviated; ping us at contact@farrenio.com for the full Git history.
Cross-layer hardening pass: security, performance, real-time inbox
June 12, 2026- Marketing inbox: real-time bell + toast + tab-title flash + Web Audio beep for verified demo requests, new Aria chat sessions, and contact-form submits. Per-admin unread tracking, "Mark all read", mute toggle persisted to localStorage.
- New WebSocket channel /ws/admin-inbox; events also persisted in admin_inbox_events (TTL 180d) so they survive reconnects and tab refreshes.
- PVA2C Edit dialog: pencil icon on every row opens a full editor for pva_number / order_number / contract overrides / pva_date / BNR rate date override. Backend re-renders the stored PDF on save.
- BNR rate now defaults to the last working day of the month BEFORE pva_date (Romanian invoicing convention). Admin can override the BNR date per document. Rate truncated to 2 decimals (not rounded) so PDF math balances.
- PVA2C generator now uses contractor cost rate (what Farrenio PAYS the contractor) — not the bill rate (what Farrenio CHARGES the beneficiary). Critical accounting fix.
- Signature + Branding dialogs on the PVA2C page, mirroring PVA2B. Achizitor signature stamped under row 1 on every PVA2C. "SC " prefix removed; company name editable verbatim via Branding.
- PVA2C email send: SMTP profile picker ("Send from"), default CC to florin.stoica@farrenio.com, quick-add chips for platform owners + admins.
- Subject line now includes the order number — "PVA2C #N (Comanda M) — Contractor — Period".
- Public /chat/sessions/{id}/messages-since now strips operator_email/name from responses — operator PII no longer leaks to visitors.
- /chat/persist gains a per-IP cap (20 distinct sessions per window) so session_id rotation can no longer flood the admin inbox.
- Content-Disposition headers go through a safe builder that strips CR/LF and RFC 5987-encodes the filename — closes a response-header injection vector on signed-PDF + CSV downloads.
- /login reads X-Forwarded-For for auth_block / allowlist matching, so the platform behind Caddy attributes login attempts to the real client IP (not the proxy loopback).
- Provider signature + logo: AND-validation on extension AND MIME, response Content-Type pinned to image/png|jpeg regardless of stored value. Stops stored-XSS via a text/html-MIME upload.
- WebSocket JWT now carried via Sec-WebSocket-Protocol subprotocol (fcc.jwt.<token>) — token no longer lands in Caddy access logs, browser history, or Referer. ?token= still accepted as backward-compat.
- WS connections cycle on token rotation: AuthContext fires fcc:auth_token_changed; useWebSocket listens + cross-tab storage event also force a reconnect.
- /ws/alerts unscoped channel restricted to platform_owner + admin (was admitting service_desk/technician, leaking cross-tenant alert payloads).
- SSH_ENCRYPTION_KEY env var required at backend boot; opt-in FCC_ALLOW_DERIVED_SSH_KEY=1 falls back to a MONGO_URL-derived key for dev only.
- ConnectionManager.broadcast: sequential await → asyncio.gather. One slow client no longer stalls the entire WS fanout.
- Alert evaluator: N+1 find_one → one $group $first aggregate per system. ~60 index seeks/pass → 1.
- /api/inbox-summary chat_sessions_unread: in-Python scan of messages[] over 30 days → single indexed count_documents using the new denormalized last_user_message_at field.
- admin_inbox_events compound indexes (seen_by, kind+ip+ts, event_id unique) + 180d TTL.
- pva2c_documents.pva_number now UNIQUE — concurrent renumbers surface as clean 409 instead of silent data corruption.
- AdminInboxContext split into stable actions + reactive events — bell badge & dropdown no longer re-render the entire authenticated tree on every WS event.
- Toast + beep throttled to 1 per kind per 5s (bell badge still increments per event). Tab-title flash race with PageTitle fixed via MutationObserver guard.
- push_admin_event no longer broadcasts when Mongo insert fails (toast vs persisted-event consistency).
- _resolve_pva2c_bnr_lookup raises a clean 400 instead of 500 when a legacy PVA2C has an empty pva_date.
- Frontend WS hooks read auth_token (was reading the unused token key) — fixes the "Polling" indicator across dashboard/SAP/log WS connections.
- MarketingInboxBell rows are stable across re-renders (Row component hoisted out of map).
Public surface, in-app tour, analyst-grade analytics
May 29, 2026- 7 new standalone public pages launched: /about, /aws, /security, /pricing, /faq, /use-cases, /press, /roadmap, /changelog, /status, /404 — each with its own conversion narrative.
- Interactive ROI calculator + "Compare plans" 27-row feature matrix under the landing pricing section.
- SEO pass: robots.txt tightened to deny in-app routes, sitemap.xml rebuilt with 12 public URLs + per-page lastmod / priority, JSON-LD FAQPage + Organization + Offers structured data.
- Branded /404 page (recovery search), branded ErrorBoundary fallback, landing nav surfaces standalone pages instead of just anchors.
- In-app product tour: 9-step guided overlay with spotlight cutouts. Trigger via the Lightbulb icon in the header or share with ?tour=1.
- "What's new" modal (this one) in the in-app header with a pulsing badge until the latest release is opened.
- Keyboard shortcuts: press ? for the cheatsheet, g then a letter to navigate (g d, g a, g s, …).
- Per-route browser tab titles — "Visitor analytics · Farrenio" etc. — so multi-tab operators can tell pages apart.
- Scroll-to-top buttons (landing + every in-app page), sidebar logo refreshes the Dashboard on click, support+feedback FABs promoted into the header.
- New Visitor Analytics surface: daily history, weekday × hour heatmap, engagement KPIs, top referrers & exit pages, CSV exports.
- Conversion funnel: anonymous → consented → engaged → Aria chat → demo confirmed.
- "vs. previous period" toggle on Visitor Analytics KPI tiles with Δ chips.
- Anonymous visitor tracking (consent-free ping) — see who arrives even when cookies declined.
- Live "Online now" panel (consented + anonymous), Geo-IP + B2B email enrichment, returning-visitor detection.
- Marketing CRM now a proper workspace, not just a list. Contacts aggregate from Aria chat, demo requests, contractors AND platform users (each row shows provenance). New category dimension: lead / customer / partner / contractor / team / other. Per-contact detail page with tabs: Overview, Activity (log calls / meetings / notes / emails / documents / tasks), Tasks (priority, due, assignee, reminders, recurrence, .ics export + Google Calendar deep-link), Emails (compose with {{variables}}, live preview, send via platform SMTP). Email Templates page with starter templates and Campaigns page (template + audience filter → send-now to a filtered slice). Calendar OAuth (Google/Outlook/Teams/Zoom) deferred to a follow-up — .ics export covers the workflow today.
- Unified CRM Tasks queue at /admin/crm/tasks — every task across every contact in one inbox, with "only mine" toggle, status tiles (open / in progress / done / cancelled), priority chips, overdue + due-≤24h counters, stand-alone tasks (no contact). Campaign scheduler now runs in the background: campaigns with a future send_at fire automatically on the 60-second loop (atomic claim, so replicas don't double-send). Fixed: .ics download from the Tasks tab (was unauthed, now blob-fetched with JWT).
- Email templates now support HTML alongside plain text. Editor has HTML / Plain text / Preview tabs (sandboxed iframe), one-click "Brand template" inserts a beautifully designed Farrenio HTML scaffold (hero band, CTA button, brand footer — all inline styles for Gmail / Outlook compatibility), and "From plain →" generates HTML from the plain body. Sent as multipart/alternative so every inbox renders the version it prefers. Contact-detail Emails tab shows the HTML preview inline when a template is HTML-enabled.
- Subscription visibility round. Contact detail page now leads with a Subscription panel showing whether the contact receives campaigns (green) or is unsubscribed (amber), with timestamps + who/why on each transition. Operator-driven Subscribe / Unsubscribe toggle with optional note — audit-logged separately from the recipient-driven /u/<token> path. Contacts page header gets a two-up Subscribed / Unsubscribed split tile (clickable to filter); the row Status badge now shows a MailX icon when unsubscribed so the do-not-email contacts pop visually. Contact List members table now flags unsubscribed entries with an amber banner ("N contacts in this list are unsubscribed and will be skipped") plus a per-row Subscription column — so when a list is used as a campaign audience, the operator can see at a glance which entries will be silently dropped at send time. Safety net unchanged: audience_filter has always excluded `status: unsubscribed`; this round just makes it observable.
- Campaigns get three operational improvements. (1) Per-recipient send log: click Recipients on a sent campaign to see who got it, who opened (with count + timestamp), who failed (with SMTP error), and who unsubscribed. (2) Copy / Repeat: new Duplicate button on every row creates a fresh draft seeded from the source (same template, same filters or list, status=draft). Sent campaigns show as "Repeat", drafts/scheduled as "Duplicate" — same endpoint, contextual label. (3) Outlook-compatible templates: hero band now uses bgcolor="#f97316" + background-color fallback before the linear-gradient, so the orange band renders in Outlook desktop (which strips gradients) — also applied to the amber "why this matters" block.
- Contact Lists merged into Contacts. The standalone Marketing → Contact Lists sidebar entry is gone; lists now live as the second tab on /admin/contacts (URL-synced via ?tab=lists, so bookmarks and breadcrumbs still work). Old /admin/crm/lists redirects to /admin/contacts?tab=lists. CrmLists.jsx refactored to expose a Layout-less ContactListsView that the Contacts page embeds — same edit/picker/detail flow, fewer top-level destinations.
- CRM round of papercut fixes. Scan Business Card now opens a real live camera preview on desktop (getUserMedia) instead of silently falling back to a file picker — card-sized guide overlay, Flip front/back, single-click Capture. QR detection got better: pyzbar now runs on the original colour image first (autocontrast greyscale can wash out finder patterns), falling back to the OCR-prepped variant. Template starter library stays reachable forever: new Browse starters / Hide starters toggle in the page header, and the empty-state copy adapts after you save your first template. Editor backdrops no longer close on outside click — accidental misclicks while editing Contacts / Categories / Lists / Campaigns / Templates / Add-to-list won't drop your unsaved work. Country and dial-code dropdowns now pin Romania + EU + a few common targets at the top before the full alphabetical list. Two real bugs fixed by audit: ContactEditor now key-remounts per contact so the phone-prefix stops carrying over from the previous edit; "Save all starters" now tells you how many were skipped instead of swallowing 409s.
- CRM gets two power tools. (1) Custom categories: built-in 6 stay, but you can add / relabel / recolour / delete your own under Marketing → Categories. Built-ins can be relabelled but not deleted; custom slugs can't be deleted while contacts still use them. Dropdowns across the CRM pick up customs automatically. (2) Contact lists: hand-curate a reusable bundle (name, description, logo URL or colour-avatar fallback) at Marketing → Contact Lists, then target a campaign at a list instead of a filter combo. Bulk "Add to list" action on the Contacts page lets you populate a list from a multi-select. The campaign dialog gets an Audience toggle — Filter contacts or Send to list — with the live audience-count chip reflecting either choice. Lists are re-read at send time, so adding a contact to a list later means they get the next campaign.
- CRM polish round: Campaigns now editable (PATCH /campaigns/{id}) with a live audience-count chip in the dialog that updates as you tweak filters, an Edit button per row, and an Audience preview modal that shows the count, category breakdown, and a sample table of the first 50 recipients — same query the send loop will run, so what you see is what gets the email. Scan-card now also reads QR codes (libzbar0 + pyzbar): vCard / MECARD payloads override OCR for canonical fields; bare email / URL / tel QRs fill blanks; unknown QR text is appended to the raw-OCR block. Sidebar Contacts entry no longer co-highlights with Scan / Detail subroutes (exact match). Scan Replace button now actually replaces (was no-op'ing when the user picked the same filename twice). Breadcrumb "Crm" segment now redirects to Templates instead of 404'ing.
- New Marketing → Scan Business Card workflow. Upload a card photo (or shoot one from the device camera), the backend runs tesseract OCR (eng + ron data installed in the image) and pre-fills a draft contact — name, email, phone, company, country, job title, website. Operator reviews and edits inline, then one-click saves to Contacts. Raw OCR text is folded into the contact's notes for traceability. Reachable from the sidebar or from a "Scan card" button on the Contacts header.
- CRM sends are now compliance-ready and observable. Every email gets a unique tracking token: an embedded 1×1 pixel records first-open timestamp + open count per recipient, and a branded /u/<token> landing page lets recipients confirm-then-unsubscribe (two-step so email scanners that prefetch links can't opt people out). Templates can place {{unsubscribe_url}} wherever; if they don't, the send path auto-appends a compliance footer (HTML + plain) so a forgetful operator can't accidentally send a non-compliant blast. Campaigns page now shows "X opened (Y%)" and "Z unsubscribed" alongside sent/failed. Unsubscribed contacts are excluded from every audience automatically and stamped with unsubscribed_at + IP for audit.
- Aria chat inbox is now unread-aware. Pulsing amber bubble per session row counts visitor messages since you last looked; "Last visitor msg" column shows when the freshest message arrived. Unread sessions float to the top and the row tints amber. Sidebar "Chat Sessions" badge mirrors the platform-wide unread total, and a discrete toast fires when new visitor messages land on existing sessions. Opening a session auto-clears its bubble; the inbox icon next to Open marks read without opening. List auto-refreshes every 30s while open.
- Aria upgrade: visitors and operators can attach files (image / PDF / Office / zip · 5 MB cap), operator console shows the visitor's in-progress typing live, and chat sessions can be permanently deleted (gated by chat.delete_session). Fixed: clicking a follow-up question right after the intake form no longer re-opens the form.
- Aria chat human-takeover with SUPPORT_AGENT role (10 granular chat permissions).
- Operator-online indicator on Aria — visitors see "Agent online · typical reply within 5 min" when a human is on shift.
- Demo Requests: bulk archive/delete + Send-email-and-mark-contacted modal.
- Bulk approve / reject on Pending Approvals.
- Public /status page + live status pill on the landing trust strip + "X visitors browsing now" social-proof pill.
- Recent Activity tile on the in-app Dashboard — last 10 audit events, polls every 60s.
- CountUp animations on landing "By the numbers" band, section nav rail on landing, JS-driven AWS/Cloud marquee.
Marketing & ROI surface
May 20, 2026- Interactive ROI calculator under the landing-page ROI section.
- "Compare plans" feature matrix below the pricing tiers (5 sections, 27 rows).
- SEO pass: tighter robots.txt + sitemap.xml + JSON-LD FAQPage.
- Anonymous visitor tracking (consent-free ping) — see who arrives even when cookies declined.
- Per-route browser tab titles ("Visitor analytics · Farrenio" etc.).
- Branded 404 page with recovery search and quick links.
Pulse, security & RBAC
May 12, 2026- Security Pulse + Runtime Pulse — auth-attempt summary and p95 latency on the Dashboard.
- 69-permission RBAC matrix with per-role overrides editable via UI.
- MFA enforcement on code-server gate + audit retention to 365 days.
- PIN-revealable API tokens with rate-limited verification and account-level lockout.
Performance & observability
May 1, 2026- N+1 query fixes across agent-configs and global SLA — major latency drop on the dashboards.
- New unique MongoDB indexes on users.email, api_tokens.token, agents.agent_id (O(log n) lookups).
- Backend dependency upgrades to zero known CVEs per pip-audit; FastAPI 0.110 → 0.121.
- Login rate limiting (5/min) + fail2ban filter on /api/auth/*.
AI Predictive Ops
April 15, 2026- AI anomaly detection over SM50 / SM37 / ST22 metrics with explainable callouts.
- Capacity forecasting on disk / DB growth (90-day horizon).
- AI recommendations: cost-optimisation, idle-system reclaim, alert-noise reduction.
Like the trajectory?
We ship continuously and ship publicly. Talk to us about getting your SAP landscape on Farrenio — typical reply within one business day.