Changelog
Was sich in den letzten Patches geändert hat. Neuester Eintrag oben.
RSSvor 2 WochenThree tiers · 30-day Pro trial for every signup · Founders Program
4Neu3Geändert
Three tiers · 30-day Pro trial for every signup · Founders Program
- Neu New Pro tier at $99/€99/£84/CA$134/MX$1849 per month. Same single-user feature set as Solo with 4× the Bob token allowance (8M/month). Sits between Solo and Team for operators who run Bob as their main surface.
- Geändert Sign-up now starts a 30-day Pro trial automatically — no card, full Pro feature access including Bob. After 30 days you pick Solo, Pro, or Team. The "free for the first 100" cohort is closed to new signups; existing Founding-100 members keep their permanent free access.
- Neu Founders Program at /founders. Apply if you're still building your MSP — get a 90-day Pro trial (instead of 30) and an optional "free until your first paying customer" arrangement. Every application reviewed by hand within 5 business days.
- Neu Trial-end emails. We send a heads-up 7 days before, 1 day before, and on the day your trial expires — each with the link to pick a plan. The workspace flips to locked-status automatically once the trial is over; your data stays intact.
- Geändert /pricing rebuilt as a three-card grid (Solo / Pro / Team) with Founders Program and Founding Members (50% lifetime discount for the first 100 paid signups) cards below. Currency switcher above repricing all three with one click.
- Geändert /billing upgrade picker shows three cards instead of two; active-tier banner now recognises Pro alongside Solo and Team.
- Neu Landing page got two new sections — three short "Bob does X" use-case cards (quick invoice, customer lookup, assessment-to-quote) and a three-step "how it works" walkthrough above the feature list.
vor 3 WochenTeam time-sheet · /assistant is one page now
1Neu1Geändert
Team time-sheet · /assistant is one page now
- Neu Team time-sheet at /time. Log billable hours per project — type them in yourself, or ask Bob ("log 2h on Acme for cabling"). Each entry carries date, hours, hourly rate, and an optional note; totals roll up per customer for invoicing. (DE/EN/ES)
- Geändert /assistant is one page now. Pick a conversation from the sidebar and it opens in the right rail — no more full-page reload between conversation switches. Old /assistant/<id> links keep working (they redirect).
vor 3 WochenInvoices, workspace timezone, Network → Pro · big quality pass
2Neu2Geändert3Fix1Security
Invoices, workspace timezone, Network → Pro · big quality pass
- Neu Invoices. Accept a quote, hit "Create invoice", get a numbered invoice ready to send. Gap-free numbering (RE-YYYY-NNNN), DIN-5008 layout, per-period VAT block, reverse-charge handling — all inherited from the quote PDF. Send as PDF in one click, mark paid; sent invoices lock against further edits. Structured e-invoice / ZUGFeRD output is the next step. (DE/EN/ES)
- Neu Workspace timezone setting. Workspace owners can pick the org's IANA zone in Settings → Workspace; the date on outgoing quote PDFs now pins to it, so a quote issued at 23:30 local doesn't print yesterday's date. Leave it on "Automatic" to derive from your Country. (DE/EN/ES)
- Geändert Partner Network is a Pro feature now. A Free workspace clicking "Network" lands on /billing with a short explanation; Solo and Team get the directory + lead-forward flow as before.
- Geändert Free tier is now capped at 3 quotes per month — Solo and Team stay unlimited. A small counter on the quote list shows what you've used, and the wall enforces at every create path (manual, from a Checkup, from the wizard auto-quote).
- Fix Big sweep — roughly 70 fixes across the app. Highlights: the customer portal no longer crashes on a project with task comments; the iOS app no longer hits a redirect loop after the trial ends; the embed and self-check questionnaires finally show their conditional follow-up questions (they were silently dropped); the email-send modal now surfaces a real error when delivery fails instead of pretending it worked; the AI summary overlay no longer hangs on a failed generation.
- Fix Checkup-derived quotes (the ones without a linked project) now show up everywhere they should — operator quote list, customer portal, share link, PDF and CSV export. They used to disappear silently when a project was deleted or when the quote came from a project-less Checkup.
- Fix PDF and portal dates render the right calendar day across timezones now. Audit dates on Checkup reports stay anchored to the day you picked in the date input, regardless of where you or your customer sit.
- Security Hardened the partner-network's lead-forwarding flow and tightened a few authenticated-session checks.
vor 3 WochenFixes - square app icon, cleaner changelog feed
1Geändert1Fix
Fixes - square app icon, cleaner changelog feed
- Fix The PWA / home-screen / taskbar icon showed the wide MSP banner shrunk onto a white square, so it read as a tiny floating logo. It is now a proper square app icon — a green tile with the MSP mark centred. If your device still shows the old one, remove and re-add the app (OS icon caches are stubborn).
- Geändert Each change in the /changelog RSS feed is now its own line with a kind marker (added / changed / fixed …) instead of one run-on paragraph — so Discord feed embeds and desktop RSS readers render it as a scannable list.
vor 3 WochenSettings rebuilt - collapsible sections, mobile & iOS-app polish
1Neu1Geändert
Settings rebuilt - collapsible sections, mobile & iOS-app polish
- Neu Settings is now organised into collapsible sections — tap one to open it instead of scrolling a single long page. On desktop the sidebar still jumps straight to any section.
- Geändert Settings got a mobile and iOS-app pass: bigger tap targets, no zoom-jump when you focus a field, safe-area spacing, a haptic tap when a change saves, and fields that scroll clear of the on-screen keyboard.
vor 3 WochenDark-mode color-safety audit · automated check + token rules
2Neu1Fix
Dark-mode color-safety audit · automated check + token rules
- Neu Dark-mode color-safety audit script (npm run check:darkmode). Scans every .astro/.tsx/.ts file for stock Tailwind palette uses (stone-, gray-, emerald-, rose- etc.) that have no paired dark: variant. Strict mode (check:darkmode:strict) exits non-zero, ready to wire into CI as a pre-build gate once the existing offender count drops.
- Neu Color-token rules documented as a tall comment block at the top of src/styles/global.css — the six rules covering surfaces, text, borders, inverted buttons, accent, and pills. Every developer (and AI assistant) touching styles sees the rules at the source-of-truth file.
- Fix Ticket system pages (/superadmin/tickets list + detail, /help/tickets list + thread, support FAB dialog) render correctly in dark mode — conversation bubbles, status pills, reply composers, and modal backdrops no longer leak white-on-white or black-on-black text.
vor 3 WochenCustomer support overhaul · ticket system v1 · in-app chat widget · /help hub
6Neu1Geändert
Customer support overhaul · ticket system v1 · in-app chat widget · /help hub
- Neu Help & Support hub at /help — single discoverable entry point for docs, changelog, feedback, Discord community, and direct support. Replaces four separate AppNav entries with one card grid. Shows a count badge when you have open tickets.
- Neu Sticky support-chat widget in the bottom-right corner of every authenticated page. Click the bubble, write a subject + message, pick a category (Support / Billing / Privacy). Submits land instantly in /superadmin/tickets and trigger an operator notification email — no IMAP roundtrip, no page reload.
- Neu Customer-facing ticket views at /help/tickets (list of your own threads, sorted by last activity) and /help/tickets/[id] (full conversation with inline reply composer + "mark as resolved" option). Strictly isolated per user by from_email; cross-tenant access redirects to the list without leaking existence.
- Neu Built-in support ticket system in /superadmin/tickets. A background worker polls support@, billing@ and privacy@ inboxes every 60 seconds, ingests new mail as tickets, auto-tags by destination mailbox, and auto-links to a tenant when the sender email matches a known workspace.
- Neu Operator reply path supports both web and Outlook: the inline composer on /superadmin/tickets/[id] sends a threaded reply through noreply@mspercury.com (RFC 5322 In-Reply-To + References headers). Replying from the operator personal mailbox (l.flores@it-flores.de) is also recognised by the IMAP ingest and forwarded onward to the customer transparently.
- Geändert Customer-facing email Reply-To moved from the operator personal inbox to support@mspercury.com. The mailbox forwards onward to the operator at the provider level, so the routing change is transparent to senders. Mentions in the blog and one-off operator replies updated to match.
- Neu Reply-blocklist for send-only / loop-prone addresses (noreply@, support@, billing@, privacy@). The reply UI shows a warning banner instead of accepting a reply that would silently bounce at the destination MTA. Prevents the most common operator foot-gun: replying to a smoke-test ticket whose from_email is noreply@.
vor 3 WochenPublic-CheckUp asks for phone too · stale items pruned from roadmap + known-issues
2Geändert1Entfernt
Public-CheckUp asks for phone too · stale items pruned from roadmap + known-issues
- Geändert Public-CheckUp contact form (DE/EN/ES) now requires a phone number alongside email. Operator feedback was that email-only leads were too easy to ignore on the prospect side; a number lets the MSP call back the moment a high-score lead lands. Applies to both /check/{slug}/questions and the iframe embed at /check/{slug}/embed/questions.
- Entfernt Stale entries pruned from /docs known-issues (DE/EN/ES): "5 MB iOS photo cap" (actual cap is 8 MB), "custom footer not in PDF yet" (renders since the May 2026 quote-PDF refresh), "no bulk customer import" (CSV import lives at /customers/import), "SVG logo breaks branding" (SVG is in the allowed-mime list), "AI ChatGPT path untested" (both Claude + OpenAI providers ship at parity).
- Geändert Roadmap (DE/EN/ES) refreshed: Stripe direct-key integration moved from "Now (in flight)" to "Already shipped" — that one already lives at /settings/integrations. Public Read API + Webhooks slipped from Q2 to Q3 2026 to match reality. Native iOS app entry kept.
vor 3 WochenTiered pricing · Solo €29/mo · Team €49/mo · Mexican peso support
3Neu4Geändert
Tiered pricing · Solo €29/mo · Team €49/mo · Mexican peso support
- Neu New "Solo" tier at €29/$29/CA$39/MX$549 per month, unlocking every Pro feature for single-operator MSPs (CRM, marketplace, branding, advanced reporting, AI with BYO key, multiple Public-CheckUp URLs, Stripe direct-key integration, API). Existing Pro features now start at Solo.
- Neu "Team" tier at €49/$49/CA$65/MX$929 per month — everything in Solo plus multi-user workspace (additional admin + member seats). Existing Pro subscribers at €49 are grandfathered onto Team via the migration.
- Neu Mexican peso (MXN) added as a supported billing currency across /pricing, /billing, and the Stripe checkout flow. Locale + CF-IPCountry pick MXN by default for Mexican workspaces.
- Geändert /pricing rebuilt as a three-card layout (Free / Solo / Team) with a central currency switcher above the cards so a single click re-prices both paid tiers. FAQ updated to explain Solo vs Team and the Stripe Customer Portal tier-switch path.
- Geändert /billing rebuilt: shows the active tier (Free / Solo / Team) with a status pill, two side-by-side upgrade cards for unsubscribed workspaces, and a "Manage subscription" link for active subscriptions (where the Stripe Customer Portal handles tier changes pro-rata).
- Geändert Plan-gating helpers (`isPro` / `getPlan` / `getLimits` / `requirePro`) updated to recognise three tiers. `isPro` stays true for Solo + Team (so every pre-existing call-site keeps working). New `requireTeam` gate added for future multi-user features.
- Geändert Stripe webhook now decodes the active price ID into a tier (Solo / Team) on every subscription event and writes it to `organizations.subscription_tier`. Tier changes via the Stripe Customer Portal are reflected within seconds of the event arriving.
vor 1 MonatSignup is open · branded error pages · welcome tour · multi-admin lead alerts · per-invite question set
2Neu4Fix2Entfernt
Signup is open · branded error pages · welcome tour · multi-admin lead alerts · per-invite question set
- Entfernt Workspace-signup invite codes are gone. Anyone can create a workspace at /signup with email + PIN, no code required. The old gate was broken anyway — codes were never marked used, the beta-flag never propagated, and the gate was bypassable by POSTing the action URL directly. /superadmin/invites and /settings/invites pages were deleted.
- Neu Welcome modal on first signup now shows a 4-card feature tour (Public CheckUp, CheckUp wizard, Service catalog, Quotes + PDFs) above the setup form, each linking to the surface so you can click around before filling anything in. Listens for the canonical ?welcome=signup cue from the post-signup redirect (was ?welcome=1, which also triggers a fresh Google Ads conversion event).
- Neu Branded English error pages for /check/{slug}/* and /self-check/{token} when the link is dead or revoked. Replaces the bare-text "Diese Seite ist nicht verfügbar." 404 that prospects in EN/ES locales used to see. Inline-CSS, MSPercury logo, noindex.
- Fix Lead-alert emails (DE/EN/ES) now fan out to every admin in a multi-admin workspace, not just one inbox. Each admin's subject + body is rendered in their own preferredLanguage, matching the per-admin push-notification fan-out behaviour. Same for the new-lead push title and body — those were hard-coded German.
- Fix Customer self-check is now pinned to a specific question set at invite-time (DE/EN/ES). If you flip your default catalog after sending an invitation, the customer still sees the catalog you originally meant — no surprise mid-flight changes. Existing pending invitations keep the previous behaviour (fall back to the org default).
- Fix Operator flash messages + AI quote-draft titles localised across the leads, customers, customer-contacts, and AI surfaces (DE/EN/ES). Removed the leftover German strings that flashed on EN/ES workspaces ("Ansprechpartner aktualisiert.", "Bereits konvertiert.", "Lead im Marketplace veröffentlicht.", quote titles starting with "Angebots-Entwurf aus CheckUp", etc.).
- Fix Post-signup redirect chain collapsed: /signup/complete now lands directly on /dashboard?welcome=signup instead of bouncing through /onboarding?welcome=1. One redirect, correct cue, no wasted hops.
- Entfernt Dead defensive code around i18n key lookups: stripped the `t("nav.leads" as any) ?? "Leads"` fallback pattern in AppNav + the settings sidebar. The keys exist in every locale; the dead fallbacks had German strings ("Netzwerk", "CheckUp-Einstellungen") that would never activate but represented a latent silent-German risk.
vor 1 MonatPublic pages default to English · redesigned changelog layout
2Geändert
Public pages default to English · redesigned changelog layout
- Geändert Every public surface (landing, /imprint, /legal/*, /pricing, /blog, /docs, /changelog, /feedback) now defaults to English when the visitor isn't signed in. The Accept-Language browser-locale auto-detect path was removed — a visitor who actively wants a different language uses the picker in the footer (English · Deutsch · Español) or appends `?lang=de` / `?lang=es` to the URL. The explicit pick persists across navigation via a dedicated marker cookie. Signed-in users keep their saved language unchanged.
- Geändert /changelog layout reworked for both desktop and mobile. Desktop gets a wider canvas (~1152 px), a sticky year-jump rail on the left, and a year-header rhythm above the first entry of each year. Mobile stacks date + title with a short-date label and bigger tap targets. New sticky toolbar offers per-kind filter chips (Added / Changed / Fixed / Removed / Security with live counts), expand-all / collapse-all buttons, and a copy-link affordance per entry that writes the deep-link URL to the clipboard. Hash navigation (e.g. `#2026-05-12` from the RSS feed) now force-opens the linked entry and scrolls it into view.
vor 1 MonatInline-expand forms moved into modals — no more layout-sprenger when adding contacts, catalogs, tasks
1Neu4Geändert
Inline-expand forms moved into modals — no more layout-sprenger when adding contacts, catalogs, tasks
- Neu New reusable `<FormModal>` component (same `<dialog>`-based pattern as the existing EmailSendModal / OnboardingWizardModal / PdfModal). Trigger button + native modal, ESC + backdrop close, auto-reopen with an error banner if the previous submit returned a validation error, optional URL cancel-href for unwinding edit-state-in-URL.
- Geändert Customer detail (/customers/[id]): "Add contact" + per-row "Edit contact" no longer expand inline and rip the card header apart. Both now open a proper modal. Same field set, same validation; the card layout stays put.
- Geändert Settings → CheckUps (/settings/checkups): "New question set", "New landing", "Edit landing", "New question" (per category), "Edit question" (per row), "Rename set", "Reset set to defaults" all moved into modals. The question-editor list is now a clean read-only stream with hover-reveal Edit/Delete actions; the giant multi-locale form only takes up screen space when you actually need it.
- Geändert Project detail (/projects/[id]): "Add task", "New service report", "Edit service report (draft)", "Propose maintenance window", "Edit maintenance window" all moved into modals. The Maintenance tab is now full-width with a single composer card above the list instead of a cramped left-column form that kept the right-column list narrow.
- Geändert Settings → Integrations: Stripe key rotation moved into a modal.
vor 1 MonatIndustry templates for the public CheckUp — law, tax, finance, healthcare
2Neu1Fix
Industry templates for the public CheckUp — law, tax, finance, healthcare
- Neu Four ready-to-use question sets when you create a new public-CheckUp catalog: Healthcare (medical practice / MVZ), Law firm, Tax / Accounting firm, Financial services. Each ships 29 questions in all three locales (DE/EN/ES) — the standard cross-industry set plus four sector-specific add-ons per template (retention rules, regulator / court-filing portals, secure client portals, confidentiality training). The Settings dropdown localises the template's display name to the operator's UI language; the underlying question text is trilingual.
- Neu Pick the workspace language on signup. The email step now has a DE / EN / ES flag picker; whichever you choose sets the workspace language and seeds the default CheckUp questions in that locale (DE/EN/ES). Changeable later in settings.
- Fix Creating a second question set silently 403'd for early-access workspaces — the form redirected to the Landings tab and the catalog never appeared. Plan-cap check now recognises superadmin + early-access correctly. Error banners surface real failures instead of silent redirects.
vor 1 MonatHotfix: industry-template questions showed up as raw keys in the lead transcript + PDF
3Fix
Hotfix: industry-template questions showed up as raw keys in the lead transcript + PDF
- Fix Questions from the Healthcare / Law / Tax / Finance industry templates (DE/EN/ES) rendered as their internal keys — e.g. `patient_record_access_hc`, `bea_active_law` — in three places: the operator's lead-detail transcript, the prospect's emailed PDF report, and the AI quote-drafter context. Three read-side renderers were resolving question text against the Standard seed only; anything outside the Standard catalog fell back to the raw identifier. Resolution now walks the slug's assigned question set → any other set the workspace owns → the static seed union of all five catalogs. Prospect-facing PDFs are the most visible win.
- Fix Fix applies to all three locales (DE/EN/ES) at once — the resolver returns the full translation triple for every key and the call-site picks the active locale. English and Spanish workspaces that run the law / tax / finance / healthcare templates get the same correct rendering as German ones; no separate per-locale patch was needed.
- Fix Prospect-facing surfaces verified clean: the /check/{slug}/questions form (both standalone and embed variants) and the customer self-check at /self-check/{token} were already reading translations directly from the DB row, so they never hit the bug. The patch above only touches the three back-office / PDF / AI render paths that had been hard-coded against the Standard seed array.
vor 1 MonatCheckUp settings unified, formal Sie in German, multiple question sets per workspace
4Neu2Geändert1Fix1Security
CheckUp settings unified, formal Sie in German, multiple question sets per workspace
- Neu One home for all CheckUp settings at /settings/checkups with three tabs: Public landings, Question sets, Findings library. Replaces the scattered surfaces that used to live under public-checkup, questions, and findings separately. The old Findings entry was removed from the sidebar nav.
- Neu Multiple question sets per workspace (Pro). Name a catalog ("Healthcare campaign", "Retail audit"), assign it to one or more public-CheckUp URLs. Each URL can run its own question list — /check/your-msp-medical with 12 healthcare questions, /check/your-msp-retail with PCI-flavoured ones. Free stays at one catalog.
- Neu Clone a public-CheckUp landing in one click. The landings table now has a copy action that duplicates the slug + intro text + audience label + question-set assignment; you get a new draft URL ready to rename.
- Neu Leads count in the sidebar. The Leads nav item now shows the same red badge as Customers / Quotes, so unread leads are visible without opening the page.
- Geändert Public-CheckUp wording (DE only) switched from informal "du" to formal "Sie" across all 63 default questions and tooltips. Existing prospect-facing copy on /check/[slug] now matches the tone most German MSPs use with their customers. English and Spanish translations were unaffected — they already used the formal address ("you" / "usted").
- Geändert Self-CheckUp result page polish: the score number and the maturity badge no longer crowd each other on narrow screens (they stack on mobile, sit side-by-side on desktop). The "Back to home" button now goes to the MSP's own landing page if one is configured, instead of always returning to mspercury.com.
- Fix Active MSPs counter on the admin dashboard was over-counting — ghost organisations that never finished signup were being tallied. Counter now matches the actual paying-or-trialing workspaces. (Visible only to superadmin; reported by an operator who noticed the math didn't add up.)
- Security Hardened the password, passkey and 2FA login paths. No user action required.
vor 1 MonatSignup friction crushed: email + PIN, done — workspace/password/2FA move into the wizard. Plus PIN login, passkey nudge, PIN-gated account deletion, tenant-branded public CheckUp, OG image + SEO overhaul
6Neu3Geändert1Fix
Signup friction crushed: email + PIN, done — workspace/password/2FA move into the wizard. Plus PIN login, passkey nudge, PIN-gated account deletion, tenant-branded public CheckUp, OG image + SEO overhaul
- Geändert Signup collapsed from three pages to two. Was: /signup (email) → /signup/verify (PIN) → /signup/complete (workspace + name + password + password-confirm + 2 consent checkboxes) → /dashboard. Now: /signup with inline consent ("By clicking Send code you agree to Terms + Privacy") → /signup/verify → /onboarding wizard. The `confirmEmailVerification` action creates the org + admin user the moment the PIN matches: workspace name derived from the email prefix (renamable in the wizard), passwordHash=NULL, consent timestamps stamped, session cookie minted. The friction layer that was killing Reddit-traffic conversion is gone.
- Neu Passwordless login on /login: new "Email me a sign-in code — no password needed" button next to the classic password form. The button posts to `startEmailVerification` and bounces to /signup/verify; `confirmEmailVerification` detects the existing user, mints a fresh session and lands on /dashboard. That keeps the PIN-only cohort (anyone who signed up post 2026-05-09 in the friction-reduced flow and hasn't set a password yet) able to get back in. The login action now returns a readable error for `passwordHash=null` accounts instead of "Invalid credentials".
- Geändert Onboarding wizard is now a popup modal on the dashboard instead of a hard middleware redirect. The `enforceOnboarding` middleware is neutered (no-op); new `OnboardingWizardModal.astro` renders the full setup form (workspace + finance + branding + about) as a centred `<dialog>` with a backdrop, closeable via X / ESC / backdrop click. Auto-opens on first dashboard paint when `onboardingCompletedAt` is null; stays dismissible (soft nag) — ESC + Close don't change server state, the footer's "Maybe later" link explicitly sets `onboardingCompletedAt`. /onboarding standalone URL becomes a legacy redirect to /dashboard.
- Neu Password card at the top of /settings/security — single form, branched server-side on hasPassword. With `passwordHash=null` the UI shows "Set a password" with just New password + Confirm fields; the `changePassword` action now accepts `currentPassword` as optional and only verifies it when a hash already exists. On initial set the success notice reads "Password set — next login can use email + password OR keep using the email code". Plus a tooltip noting that a passkey is the phishing-resistant option vs a password. The existing /settings/password page is untouched (slated for consolidation into /settings/security).
- Neu Passkey-recommendation banner on the dashboard — small accent-tinted card pitching Touch ID / Windows Hello / Yubikey. Renders only when the user has 0 passkeys AND onboarding is done AND the `mspc_passkey_nudge_dismissed` cookie isn't set. Setup button links to /settings/security#passkeys; X sets a 365-day dismiss cookie. PIN-only signups now have an immediate path to hardware-bound passwordless sign-in once they finish (or skip) the wizard.
- Neu PIN-gated account deletion for passwordless accounts. /settings/account/delete now detects whether the user has a password set. If not, the password field is replaced with an "Email me a delete code" button; click fires the new `requestDeleteAccountCode` action, which sends a 6-digit PIN to the account email (15-minute TTL, 5 attempts, IP-rate-limited like the signup PIN flow). Once sent, the page reveals the PIN input plus the email-verbatim confirm. The `deleteAccount` action now accepts either `password` OR `pin`, branches on `passwordHash` state and verifies the PIN against the `email_verifications` table (same infra as signup, reused).
- Geändert Public CheckUp /check/{slug}/* (standalone variant) now shows ONLY the MSP's branding, not MSPercury's. Before, the three pages (index/questions/result) wrapped themselves in `MarketingLayout` — leads saw the MSPercury logo, Pricing/Partners/Blog nav and the full MSPercury footer with Imprint/Terms/Privacy/DPA/Cookies links. New minimal `TenantPublicLayout.astro` shows only the org name (or uploaded logo) in the header and a tiny "Powered by MSPercury" footer link. Plus `noindex,nofollow,noarchive` so per-MSP slug URLs don't rank on the MSPercury domain. The embed routes were already clean.
- Neu Open-Graph image (1200×630) plus square variant (1200×1200) generated via Sharp from `public/og.svg`. Full meta-tag overhaul in `MarketingLayout.astro`: og:image:secure_url + type + alt, twitter:domain + url + image:alt, application-name, apple-mobile-web-app-* suite, format-detection (no auto-tel linking), referrer policy strict-origin, DNS-prefetch + preconnect for googletagmanager, theme-color split light/dark. The English landing SEO is decoupled from DE/ES: title + description rewritten around the US/Canada Google Ads messaging (MSP CRM, quoting, lead tracking, no spreadsheets, free tier), GDPR / EU-hosted language stripped. JSON-LD now carries alternateName + dual applicationCategory + a curated featureList + locale-aware priceCurrency (USD for EN).
- Fix Multiple DE-string leaks in the EN UI fixed. Dashboard MRR chart showed "Projekte (einmalig)" hardcoded across every locale; now three i18n keys (`chartOneTime`, `chartLblOneTime`, `chartRangeMonths`) DE/EN/ES. Range toggle buttons ("3 / 6 / 12 Monate") now go through the template-i18n. AiOverlay component (used everywhere): aria-label "Schließen", Cancel + Error-Close strings switched to locale-aware via SSR front-matter; inline-JS error strings for the local-AI path via an injected `window.__mspcAiI18n` bag (DE/EN/ES). Plus: dashboard 500 ReferenceError fixed (`isDe`/`isEs` were never declared at the top of the page), Checkup AI-tasks modal localised, Quotes AI-reply-draft overlay localised via define:vars. Settings/admin customer-edit had a DE-example address placeholder (`Hauptstraße 1`), now US-shaped (`1500 Market St`).
- Neu New patchnote RSS feed at `/changelog/rss.xml` — pre-rendered, locale-aware (defaults English), newest entry first. Up to seven bullets per entry summarised into the `description` field. Mirrors the same `@astrojs/rss` pattern the blog feed at /blog/rss.xml uses.
vor 1 MonatMehrere Ansprechpartner pro Kunde + Self-Check-Einladung per Token an einzelne Kontakte
6Neu
Mehrere Ansprechpartner pro Kunde + Self-Check-Einladung per Token an einzelne Kontakte
- Neu Mehrere Ansprechpartner pro Kunde. Statt einem Single-Contact-Feld auf der customers-Row (contactPerson + email) gibt es jetzt eine eigene customer_contacts-Tabelle mit beliebig vielen Kontakten je Kunde — jeder mit Name, E-Mail, Telefon, Rolle (IT-Leitung / Geschäftsführung / Buchhaltung …) und Notizen. Auf der /customers/[id]-Seite eingebettet als kollabierbare Liste mit Add/Edit/Delete-Forms; ein Eintrag kann als Hauptkontakt markiert werden. Lazy-Backfill: beim ersten Öffnen wird der bestehende customers.contactPerson + customers.email als erste Kontakt-Row angelegt — keine Daten gehen verloren, alte Reads funktionieren weiter.
- Neu Token-gegate Self-Check-Einladung. Pro Ansprechpartner mit E-Mail-Adresse zeigt die Kontakt-Liste einen Self-Check-senden-Button. Klick erzeugt einen 30-Tage-gültigen Token, persistiert eine customer_self_assessment_invites-Row und schickt eine E-Mail an den Kontakt mit dem Subject: customerName lädt Sie zum IT-Selbstcheck ein. Die Mail ist im Branding des MSP gerendert (Wordmark + Footer = workspace name, nicht MSPercury). Optionale Operator-Opener-Nachricht wird oberhalb des CTA als italic-Block eingefügt.
- Neu Public-Page /self-check/{token} drei-stufig: Welcome → Questions → Done. Vor-pop-up keine E-Mail-Re-Erfassung, da Customer schon bekannt. Locale-aware aus invite.locale. Mit ?step=questions / ?step=done query-state. Submit erzeugt eine checkups-Row mit status=draft, notes=JSON mit Score und Antworten, plus Push-Notification an alle Workspace-Admins.
- Neu Submit-Action submitSelfAssessment validiert Token (existence + nicht-completed + nicht-expired), parst Antworten, computet Score, legt CheckUp-Row an, markiert Invite als completed mit completed_checkup_id. Idempotent: zweiter Submit auf dieselbe Invite returnt success-shape ohne Duplikat-CheckUp.
- Neu Invite-History-Block auf der Kunden-Detailseite. Pro Eintrag: E-Mail, Versanddatum, Status (offen / abgelaufen / abgeschlossen). Offene Invites haben Link-kopieren + Widerrufen-Buttons. Completed-Invites haben einen Link auf den resultierenden CheckUp.
- Neu Drizzle-Migration 0005 legt zwei neue Tabellen an: customer_contacts und customer_self_assessment_invites. FK-Cascades sauber: Customer-Delete cascadet auf Contacts und Invites; Contact-Delete setzt invite.contact_id auf NULL; CheckUp-Delete setzt invite.completed_checkup_id auf NULL.
vor 1 MonatMehrere maßgeschneiderte Public-CheckUp-Landingpages pro Workspace (Pro-Feature) mit per-Slug Frage-Filter und Lead-Attribution
5Neu2Geändert
Mehrere maßgeschneiderte Public-CheckUp-Landingpages pro Workspace (Pro-Feature) mit per-Slug Frage-Filter und Lead-Attribution
- Neu Pro-User können jetzt mehrere /check/-URLs pro Workspace betreiben — eine pro Zielgruppe. Schema: .unique() auf public_checkup_slugs.organization_id gedroppt, drei neue per-Slug-Spalten ergänzt (name für den internen Operator-Label wie "Healthcare-Kampagne", audience_label für sichtbares "Speziell für Arztpraxen", question_keys_json als JSON-Array zum Einschränken des Frage-Sets pro Slug). Plus prospect_leads.source_slug_id zur Lead-Attribution. Migration 0004_multi_slug_public_checkup.sql ist additiv-only mit IF-NOT-EXISTS-Guards plus Backfill der bestehenden Single-Slug-Rows auf name='Default'.
- Neu Plan-Limits: Free behält den Cap auf 1 Slug pro Workspace; Pro hat unlimited. Ein neues publicCheckupCustomQuestionSet-Flag erlaubt Pro zusätzlich pro Slug ein eingeschränktes Frage-Set (z.B. nur die 12 Healthcare-relevanten Fragen für die /check/dein-shop-medical-Variante statt aller 30). Validation server-side stripped die Filter-Daten bei Free-Submits stillschweigend, kein 403.
- Neu Settings → Public-CheckUp komplett umgebaut: statt einem Single-Slug-Editor jetzt eine Liste aller Slugs als Cards mit Link, iframe-Snippet und kollabierbarem Edit-Form pro Slug. Plus "Neue Landingpage anlegen"-Block am Ende, Pro-gated wenn Limit erreicht — zeigt sonst einen Upgrade-Pitch. Frage-Set-Filter ist als Checkbox-Picker pro Slug eingebaut: scrollbare Liste aller Workspace-Fragen, Häkchen setzen, Hidden-JSON-Input wird beim Submit autosynchronisiert.
- Neu Lead-Attribution: jeder neue Lead trägt jetzt die source_slug_id der Landingpage, über die er reingekommen ist. Damit kannst du im /leads-Inbox nach Kampagne filtern. Bei Slug-Löschung bleibt die ID erhalten als toter Verweis (kein CASCADE), die Lead-Karte rendert dann "(deleted slug)" statt des Kampagnennamens — historische Daten gehen nie verloren.
- Neu Question-Set-Sicherheit: bei einem aktiven Filter pro Slug werden Submit-Antworten server-side auf die im Filter erlaubten Keys reduziert + die Score-Berechnung läuft nur auf der gefilterten Subset. Damit kann ein Angreifer nicht via tampered Form-Payload Antworten zu Fragen einreichen, die der Visitor real nie zu sehen bekam — sonst wäre der Score über die Filter-Schwelle manipulierbar gewesen.
- Geändert Pricing-Page Pro-Feature-Liste erweitert um Mehrere Public-CheckUp-URLs pro Workspace plus Stripe-Integration auf eigenem Konto. FAQ-Eintrag in DE/EN/ES ergänzt mit Use-Case-Beispielen.
- Geändert Features-Doku auf docs.mspercury.com (DE/EN/ES) bekommt einen neuen Abschnitt Mehrere Public-CheckUp-URLs pro Workspace direkt unter dem Standard-Public-CheckUp-Block, mit Beispiel-URLs für Healthcare/Retail/Legal und Erklärung des Frage-Filters.
vor 1 MonatStripe-KB-Artikel + AGB komplett überarbeitet (monatlich kündbar, kein Mindestlauf, Beta-Schutzklauseln) + Datenschutz mit allen Sub-Auftragsverarbeitern (Hetzner / Stripe / NinjaOne / BYO-AI / Apple / Google), alles trilingual
2Neu2Geändert
Stripe-KB-Artikel + AGB komplett überarbeitet (monatlich kündbar, kein Mindestlauf, Beta-Schutzklauseln) + Datenschutz mit allen Sub-Auftragsverarbeitern (Hetzner / Stripe / NinjaOne / BYO-AI / Apple / Google), alles trilingual
- Neu Neuer Knowledgebase-Artikel „Stripe-Integration & Kundenabrechnung" auf docs.mspercury.com (DE/EN/ES, order=3.7). Zehn Abschnitte mit Hints + Tooltips: Stripe-Konto vorbereiten + Stripe-Tax aktivieren + Restricted-Key statt Secret-Key, Workspace- und Kunden-Steuerstammdaten, Schritt-für-Schritt Quote→Stripe-Push, Mapping-Tabelle der Periode-Typen, Reverse-Charge bei EU-B2B mit Beispielen, Tropezones-Tabelle (häufige Fehler + Behebung), klare Abgrenzung „was MSPercury macht / nicht macht", Roadmap. Markdown mit Frontmatter-Order (3.7), wird automatisch in der Docs-Nav zwischen Public-CheckUp und Known-Issues einsortiert.
- Geändert AGB komplett überarbeitet auf das aktuelle Free+Pro-Pricing-Modell. Pro €49 / $49 / CA$65 monatlich — explizit „keine Mindestlaufzeit, monatlich kündbar zum Ende des laufenden Abrechnungsmonats" (Ziffer 5). 14-tägige kostenfreie Probezeit ohne Card on signup. Founding-100-Cohort dauerhaft Free, mit Garantie dass Preisänderungen am Pro-Plan diese Plätze nicht zwangsweise deaktivieren (Ziffer 4.1+4.4). Beta-Status-Disclaimer als eigene Ziffer 3 mit Hinweisen auf Funktionsänderungen, Bug-Fix-Priorität und Backup-Verantwortung. Neue Acceptable-Use-Klausel (Ziffer 12) gegen Spam, Phishing, Malware-Distribution, Reverse-Engineering. BYO-API-Key-Verantwortlichkeit klar geregelt: Operator haftet für Stripe/Anthropic/OpenAI-Konfigurationen + Kosten + Compliance. Haftungsbegrenzung verschärft: dreifacher Monatsbetrag pro Schaden, mind. 100 €; ausdrücklich ausgeschlossen sind Datenverluste bei nicht-erfüllter Backup-Pflicht, KI-Output-Schäden, indirekte Schäden, Beta-Phase-Beeinträchtigungen ohne Vorsatz/grobe Fahrlässigkeit. Spanische Fassung neu ergänzt (vorher fehlend, fiel auf EN zurück).
- Geändert Datenschutzerklärung um alle aktuellen Sub-Auftragsverarbeiter erweitert: Hetzner mit detailliertem Zweck (VPS + Postgres + SMTP-Relay über mail.your-server.de + Storage-Box-Off-site-Backup), NinjaOne (Server-Monitoring + Patch-Management, EU-US-DPF-zertifiziert), Stripe in zwei separaten Rollen (a) MSPercury-Subscription-Billing (b) MSP-eigene Stripe-Integration für Kundenrechnungen — wichtige Klarstellung dass der MSP in Fall (b) selbst Verantwortlicher ggü. Stripe ist und wir nur transportieren. Anthropic / OpenAI / Ollama / OpenAI-kompatibel als BYO-Key-Provider mit explizitem Datenfluss pro AI-Funktion (was wann an wen geht: Executive-Summary, Quote-Drafter, Lead-Outreach, Quote-Reply, Service-Report u.a.). Apple bleibt für iOS-Distribution. Google Ads nur auf Marketing-Landing nach Cookie-Consent. Drittland-Übermittlung (Section 6) erweitert. Spanische Fassung neu ergänzt (vorher fehlend).
- Neu Neuer Sub-Abschnitt 5a „Datenfluss bei BYO-AI-Funktionen" in der Datenschutzerklärung, der pro AI-Feature transparent macht welche Felder an den konfigurierten AI-Provider geschickt werden. Bei Self-hosted-Endpoints (Ollama auf eigenem Server) verlässt nichts das Operator-Netz; bei Cloud-Providern gelten deren AVV-Bestimmungen. AI-Funktionen sind komplett deaktivierbar via Einstellungen → AI → Provider auf „—".
vor 1 MonatCohorts-500 gefixt + Country-aware VAT/USt-IdNr/EIN/GST-HST in Angeboten, Rechnungen und Stripe-Push
4Neu1Fix
Cohorts-500 gefixt + Country-aware VAT/USt-IdNr/EIN/GST-HST in Angeboten, Rechnungen und Stripe-Push
- Fix /superadmin/cohorts warf 500. Ursache: postgres.js kann ein JS-Date nicht direkt durch ein raw sql\\`...\\`-Template-Literal serialisieren — der Drizzle-typed gt()-Operator löst das in .where(), raw-SQL-Strings nicht. Fix: earliestCohort.toISOString() mit explizitem ::timestamptz-Cast in den beiden raw-SQL-Blöcken (CTE-Self-Join für Aktivitäts-Grid + signup-Aggregat). Selbe Bug-Klasse hatte uns bei der Postgres-Migration mehrfach erwischt; Inline-Code-Comment dokumentiert den Trade-off.
- Neu Country-aware Tax-ID-Handling für Angebote + Rechnungen. Kunde + Workspace haben jetzt je country (ISO-3166-1 alpha-2) und tax_id_type (Stripe-aligned: eu_vat/us_ein/ca_gst_hst/ca_qst/gb_vat/ch_vat/au_abn/ae_trn/...). Bisher rendete die Quote-PDF immer ein hartcodiertes USt-ID — jetzt wechselt das Label automatisch auf den Begriff den die Behörde im jeweiligen Land erwartet (USt-IdNr für DE, VAT für andere EU-Länder, EIN für USA, GST/HST für Kanada-Bund, QST für Quebec, …). Per-locale-i18n: deutsche Operatoren sehen USt-IdNr., englische VAT ID, spanische NIF/IVA. Auto-Default vom Land: pickst du DE, springt der Type-Picker automatisch auf eu_vat; pickst du US, auf us_ein. Mehrere kanadische PSTs sind als Override verfügbar (BC/MB/SK).
- Neu Quote-PDF zeigt die Kunden-Steuer-ID jetzt in der Bezugszeichenzeile (DIN 5008-konform), die Workspace-Steuer-ID weiterhin im Page-Footer — beides mit dem gleichen country-aware Label. Bei Empfängern in einem anderen EU-Land wird oben über den Posten ein gelb umrandeter Reverse-Charge-Banner gerendert mit Verweis auf Art. 196 MwSt-RL / §13b UStG, in der Kundensprache. Wird automatisch unterdrückt bei innerdeutscher (gleiches Land) oder Drittland-Lieferung.
- Neu Stripe-Push (createStripeInvoiceFromQuote) hängt Adresse + tax_id_data an die Stripe-Customer-Erstellung. Damit kann Stripe-Tax die richtige Jurisdiktion auflösen (DE → 19% MwSt, EU-cross-border-B2B → 0% mit Reverse-Charge-Annotation, US → state-level sales tax). Bei bereits existierendem Stripe-Customer wird die Adresse synchronisiert und neue Tax-IDs nachgereicht via customers.createTaxId(), ohne Duplikate zu erzeugen. Strukturierte Adresse (line1/city/postal_code/country) statt der freitext-address-Spalte, damit Stripe-Tax sauber arbeitet.
- Neu Customer-Edit + -Create-Form bekommen Country-Dropdown (DACH + US/CA + UK gepinnt oben, Rest A-Z) und Tax-ID-Type-Dropdown. JS auto-fillt den Type wenn das Land geändert wird und der Type leer oder vom alten Standard war. Settings → Workspace genauso für die MSP-eigenen Daten. Drizzle-Migration 0003_add_country_tax_id_type.sql (4 ALTERs, additiv-only).
vor 1 MonatStripe-Architektur korrigiert: direkter API-Key statt „on-behalf-of" Connect, end-to-end Quote→Invoice
2Neu1Geändert1Entfernt
Stripe-Architektur korrigiert: direkter API-Key statt „on-behalf-of" Connect, end-to-end Quote→Invoice
- Geändert Stripe-Integration komplett neu gedacht. Statt Stripe Connect Express („MSPercury rechnet on-behalf des MSP ab") läuft jetzt der saubere Direct-API-Key-Pfad: der MSP trägt unter Einstellungen → Integrationen seinen eigenen Stripe-Secret- oder Restricted-Key (sk_*/rk_*) ein, MSPercury verschlüsselt ihn AES-256-GCM at rest (gleiche Helper wie für TOTP- und AI-Keys) und ruft stripe.invoices.create() direkt gegen das MSP-Konto auf. Geld fließt zwischen MSP und Endkunde — MSPercury sieht nie Geld, weder als Treuhänder noch als „Plattform". Kein Stripe-Connect-Onboarding-Flow, keine Express-Account-Erstellung, kein „on-behalf"-Header mehr. Konzeptionell wesentlich sauberer und in der Praxis flexibler: jeder MSP, der bereits Stripe nutzt, bringt seinen Account direkt mit.
- Neu End-to-End Quote→Stripe-Invoice live. Auf jedem akzeptierten Angebot taucht jetzt — sofern Stripe verbunden ist — ein violetter Block „Stripe-Rechnung erstellen" auf, mit Zahlungsfrist (default 14 Tage) und „Sofort schicken"-Checkbox. Klick erzeugt: 1) Stripe-Customer auf dem MSP-Konto (find-by-metadata oder neu anlegen, getaggt mit mspercury_customer_id), 2) ein InvoiceItem pro MSPercury-Quote-Line-Item mit Beschreibung + Stückpreis + Menge, 3) eine Stripe-Invoice mit collection_method=send_invoice und einem Footer „MSPercury reference: Q-2026-XXXX". Bei aktiver Sofort-Versand-Checkbox wird zusätzlich finalizeInvoice() + sendInvoice() gerufen; Stripe schickt die Hosted-Invoice-Mail aus der eigenen Stripe-Adresse. Tenant-Isolation strikt: Quote, Customer und Line-Items werden alle auf ctx.locals.organization.id re-geladen, der Stripe-Key wird pro Request frisch entschlüsselt (kein Modul-Cache).
- Neu Verbindungsverwaltung in /settings/integrations: Restricted-Key-Empfehlung mit Scope-Hinweis (Customers Write + Invoices Write), automatische Erkennung von Test-vs-Live-Modus aus dem Key-Prefix, Account-Display-Name + Account-ID werden via accounts.retrieve() direkt nach dem Paste gefetcht und gecached. Test-Connection-Button ruft erneut accounts.retrieve() und aktualisiert das „last verified"-Timestamp; Disconnect-Button löscht alle Felder. Test-Mode-Warnung wird prominent angezeigt, damit niemand versehentlich Test-Rechnungen an echte Kunden schickt. Pro-only, Admin-only, alle Felder verschlüsselt.
- Entfernt Alte Stripe-Connect-Foundation entfernt: src/actions/stripe-connect.ts, /api/stripe/connect/return.ts, der account.updated-Webhook-Handler und die Connect-Account-Onboarding-UI sind weg. Die Drizzle-Spalten stripe_connect_account_id + stripe_connect_charges_enabled aus Migration 0001 bleiben dormant in der DB (additive-only Migration-Disziplin) und werden in einer späteren Cleanup-Migration gedroppt. Roadmap (DE/EN/ES) auf docs.mspercury.com angepasst — „Stripe Connect" durch „Stripe-Integration (eigener Account, kein on-behalf)" ersetzt.
vor 1 Monati18n-Audit: Findings-Library, CheckUp-Wizard, PDF-Report — keine deutschen Enum-Werte mehr für EN/ES-Tenants
5Fix
i18n-Audit: Findings-Library, CheckUp-Wizard, PDF-Report — keine deutschen Enum-Werte mehr für EN/ES-Tenants
- Fix Findings-Library-Editor zeigte für EN- und ES-Workspaces die Priority/Effort-Dropdowns mit deutschen Werten („hoch / mittel / niedrig") an, obwohl alle Labels und Buttons sonst lokalisiert waren. Ursache: die Werte sind in der DB kanonisch deutsch (FINDING_PRIORITIES = [„hoch",„mittel",„niedrig"]) und wurden 1:1 als Option-Labels gerendert. Fix: drei neue Helper localizePriority / localizeEffort / localizeTaskPriority neben dem bestehenden localizeCategory in lib/checkups/wizard.ts, plus i18n-Schlüssel checkup.priorities.*, checkup.efforts.*, checkup.taskPriorities.* in DE/EN/ES.
- Fix Gleicher Bug auf der Findings-Library-Listenseite — Filter-Pills und Item-Chips zeigten rohe deutsche Kategorie-Strings („Sicherheit", „Berechtigungen", „Sonstiges") und Priority-Badges („hoch", „mittel", „niedrig") für nicht-deutsche Operatoren. Im CheckUp-Wizard-Review (/checkups/wizard/.../review) ebenso: Priority-Chip + Effort-Inline-Text + Add-Finding-Dropdowns. Alles auf die neuen Helper umgestellt.
- Fix CheckUp-PDF-Report (kundenseitiges PDF!) leakte deutsche Priority/Effort-Werte in den Findings-by-Category-Listen, Risiko-Matrix-Achsenbeschriftungen und im Anhangs-Tabelle. Ist ein WeasyPrint-Worker, hat keinen Zugriff auf die Request-t()-Funktion — Lösung: per-Locale-Maps direkt im PDF-Template (PRIORITY_LABELS, EFFORT_LABELS, PDF_CATEGORY_LABELS), gespiegelt zur i18n-Datei. Plus Spanisch-Übersetzungen für alle Cover-Page-, TOC-, Methodology-, Appendix-Strings, die vorher beim ES-Locale auf Englisch zurückfielen (CheckUp-Bericht / Audit-Datum / Inhalt / Risiko-Matrix / Anhang etc.).
- Fix Inline-CheckUp-Findings-Form (CheckupFindingsForm.astro) — Vanilla-JS-Island, das Card-für-Card die Findings rendert — hatte die gleichen rohen deutschen Werte in allen Dropdowns + Priority-Chips. Server-side label-Maps werden jetzt via define:vars ins Script injiziert; priorityChip(...) + categorySelect + priSel + effSel benutzen sie.
- Fix AI-generierte Tasks (Tier-1G-CheckUp-zu-Tasks) zeigten ihre priority-Property im Operator-UI als rohe englische Werte „high / medium / low" — auch im DE-Workspace. Eigene Lokalisierungsschicht via checkup.taskPriorities.*, da das Enum unabhängig vom Findings-Schema-Enum (DE) ist (LLM-Output).
vor 1 MonatStripe Connect-Foundation, Roadmap-Refresh, Lexware/Polar/sevDesk-Pipeline
3Neu1Geändert
Stripe Connect-Foundation, Roadmap-Refresh, Lexware/Polar/sevDesk-Pipeline
- Neu Stripe Connect (Phase 1) — Schema, Actions, Webhook-Hook und Settings-Seite stehen. Unter Einstellungen → Integrationen können Pro-Workspaces ihr eigenes Stripe-Konto via Express-Onboarding verbinden, MSPercury stellt danach Kundenrechnungen und -Subscriptions on-behalf aus. Auszahlungen gehen direkt aufs MSP-Konto, Stripe übernimmt die komplette USt-Logik (DE 19 %, EU-Reverse-Charge mit USt-IdNr., 0 % Drittland). Tenant-Isolation strikt: Connect-Account-ID lebt pro Org-Row, Webhook-Handler und Return-Callback gleichen sie bei jedem Event gegen die signed-in-Workspace ab — kein cross-tenant-Leak möglich. Onboarding-Status-Pill (Bereit / Stripe prüft / Onboarding offen / Nicht verbunden) plus Express-Dashboard-Login-Link plus Disconnect-Knopf.
- Neu Roadmap auf docs.mspercury.com (DE/EN/ES) komplett refresht. „Jetzt" ist jetzt Public Read API + API-Keys, Stripe Connect, Webhooks, native iOS App. „Nächst" ist Per-Tenant-DB-Snapshot+Restore, Cohort-Heatmap-Drilldown, AEO/GEO-SEO-Push. „Später" listet Lexware Office, sevDesk, Polar.sh, DATEV-Schnittstelle, FastBill/Billbee/Faktura+ und ein paar weitere Long-Tail-Wünsche. „Bereits umgesetzt" enthält das volle Mai-2026-Inventar — Pricing-Modell, Partner-Netzwerk + Marktplatz, KI-Tier 1A–1G, Lead-Gen + Marketing, Customer-Branding, Workspace/Quotes/CheckUps, Auth/Security, Operations.
- Geändert Settings-Sidebar hat einen neuen Eintrag „Integrationen" zwischen „Specialty-Profil" und Superadmin-Block. Sub-Label „Stripe Connect · API" macht klar wo es hingeht. Aktuell nur Stripe Connect; Public Read API + Webhooks landen in derselben Page sobald sie shippen.
- Neu Drizzle-Migration 0001_add_stripe_connect.sql committed: zwei neue Spalten auf organizations (stripe_connect_account_id, stripe_connect_charges_enabled). Konflikt-frei mit dem bestehenden Stripe-Customer-/Subscription-Setup für die MSPercury-Pro-Subscription — Connect bezahlt der KUNDE des MSPs, die Pro-Subscription bezahlt der MSP.
vor 1 MonatPublic-CheckUp ohne PIN-Gate, AI-Features mit BYO-Key, Beta-Hinweis, Cohort-Heatmap
4Neu2Geändert1Entfernt
Public-CheckUp ohne PIN-Gate, AI-Features mit BYO-Key, Beta-Hinweis, Cohort-Heatmap
- Geändert Public-CheckUp-Funnel: das PIN-Eingabe-Schritt nach der E-Mail ist weg. Lead tippt Adresse, klickt „CheckUp starten“, landet direkt bei den Fragen — keine 6-Stellen-aus-dem-Postfach-tippen-Friction mehr. Anti-Spam-Floor weiterhin in Place: 3 Starts/h pro E-Mail, 10 Starts/h pro IP, plus die natürliche Friction der 20 Fragen + Privacy-Consent am Submit. Alte /pin-URLs in gecachten E-Mails forwarden sauber zur richtigen Stelle.
- Neu AI-Features dokumentieren ihren Bring-your-own-Key-Ansatz jetzt prominent auf der Landingpage. Anthropic, OpenAI oder beliebiger OpenAI-kompatibler Endpoint (Ollama, vLLM, LiteLLM, Self-Hosted-LLM-Proxy) — kein Token-Markup auf MSPercury-Seite, kein Vendor-Lock-in. Wer kein Modell hinterlegt, läuft im Klassik-Modus ohne KI.
- Neu Beta-Hinweis-Banner auf /login und Dashboard: erinnert dezent daran, dass wir während der Beta fast täglich kleine Fixes shippen — kurze Unterbrechungen können passieren. Per-Device dismissable, Default sichtbar bis zum ersten Klick auf X.
- Geändert Partner-Netzwerk und Lead-Marktplatz sind jetzt klar als künftige Pro-Features markiert (Sidebar-Tag „Bald · Soon“, Banner oben in /network/*, Tag in der Marktplatz-Card auf /leads/[id]). Während der Beta für alle freigeschaltet — der Pro-Gate kommt mit dem GA-Pricing.
- Neu Superadmin → Cohorts ist nicht mehr „Coming Soon“: Retention-Heatmap pro Signup-Kohorte (12 Wochen × 12 Offset-Wochen) mit Week-0-Activation und Week-4-Retention KPIs oben drüber. CSV-Export an Bord für Board-Decks.
- Entfernt Pricing-Modell-Migration ist fertig: PWYW („pay what you want“)-Reste sind aus dem User-facing Surface raus. /superadmin/pwyw bleibt für historische Pledges erreichbar, ist aber nicht mehr in der Sidebar.
- Neu Multi-Region-Hreflang: Marketing-Layout liefert jetzt zusätzlich zu en/de/es auch en-US, en-CA, de-DE, es-ES und es-MX an die Suchmaschinen aus — eigene SERPs für die Märkte, die wir aktiv targeten, derselbe Content-Pfad pro Sprache.
vor 1 MonatPublic-CheckUp-Embed-Bugfixes, Bell auch auf Mobile + Lead-Stream, neue Push-Surface „newLead", iframe-Snippet auf /leads, Embed-Quelle automatisch getaggt
6Neu3Geändert6Fix
Public-CheckUp-Embed-Bugfixes, Bell auch auf Mobile + Lead-Stream, neue Push-Surface „newLead", iframe-Snippet auf /leads, Embed-Quelle automatisch getaggt
- Fix Public-CheckUp-Iframe lief in einer Endlosschleife: nach Email + PIN landete der Besucher wieder auf dem Email-Step. Ursache war doppelt: (1) der signierte PIN-Cookie hatte `SameSite=Lax`, also wurde er von modernen Browsern in iframe-Kontext (= Drittanbieter) gar nicht mehr mitgeschickt — die `/embed/questions`-Seite sah kein Cookie und redirectete zurück. (2) Selbst nach erfolgreichem Submit wurde der User wieder auf Schritt 1 geworfen, weil die `submitPublicCheckup`-Action den Cookie am Ende absichtlich löscht (Single-Use), aber der Page-Handler den Cookie-Check VOR dem Submit-Result-Check laufen ließ — der Erfolgs-Redirect zum Result wurde nie erreicht. Beide Fixes: (1) Cookie auf `SameSite=None; Secure` in Prod (Lax bleibt als Dev-Fallback, weil `None` über HTTP silent dropped wird), (2) Page-Handler-Reihenfolge gedreht: Submit-Result wird zuerst geprüft, Cookie-Gate erst danach. Gilt analog für UTM-Cookie und Lead-Detail-Seite (Delete-Action löscht den Lead-Row → 404 statt Redirect-zur-Liste, gleicher Reihenfolge-Bug).
- Fix PIN-Eingabe im Iframe-Embed wurde mit „Bitte halten Sie sich an das vorgegebene Format" abgewiesen, sobald iOS-Autofill oder Copy-Paste ein Leerzeichen mitbrachte. Das Eingabefeld hatte `pattern="\d{6}"` als Native-Browser-Validierung, der Server akzeptiert aber bereits 4-8 Ziffern. Pattern entfernt, stattdessen ein `oninput`-Stripper der nicht-Ziffern direkt aus dem Wert raushaut — Paste mit Leerzeichen, Bindestrichen, etc. funktioniert jetzt. Server-Validierung bleibt unverändert als zweite Linie.
- Fix Notification-Bell auf iOS-PWA war nicht antippbar. Drei kombinierte Ursachen: (1) der `<script is:inline>`-Block in `NotificationBell.astro` wurde pro Bell-Instanz dupliziert (Mobile + Desktop = 2 Skripts) und hat doppelt Listener auf dieselben Buttons attached — Klick toggelte 2× und endete bei „geschlossen". Auf hoisted `<script>` umgestellt, Astro emittet jetzt genau einmal pro Page. (2) Das Badge-Span auf dem Button war absolut positioniert ohne `pointer-events:none` — Tap auf den Counter wurde von iOS nicht zum Button durchgereicht. (3) `touch-action: manipulation` auf dem Bell-Container deaktiviert iOS' 300ms-double-tap-zoom-Verzögerung.
- Fix Push-Aktivierungs-Button in `/settings/notifications` hatte keine Funktion — der zentrale `<Button>`-Component reichte `data-*`-Attribute nicht ans `<button>`-Element durch, also fand das Wiring-Skript `document.querySelector("[data-push-enable]")` nichts und der Early-Return brach das gesamte Push-Setup ab. Button.astro spreaded jetzt `Astro.props` per `...rest` aufs Element — alle data-/aria-/hidden-Attribute kommen durch. Effekt: Push-Subscribe-Flow funktioniert ab sofort auf jeder Plattform inkl. iOS-PWA.
- Fix Notification-Center-Panel rutschte auf Mobile komplett aus dem sichtbaren Bereich nach links. Ursache: der Bell-Button steht auf Mobile in der Mitte der Button-Reihe (Search → Bell → Theme → Menu), nicht am rechten Rand. Mit `position: absolute; right: 0` ankerte das Panel an der Bell-Wrapper-Rechtskante, die 360px-Panel-Breite zog dann ~50–100px past the viewport. Behoben durch JS-basierte Positionierung beim Öffnen: `position: fixed`, `right: 8px` (immer 8px vom Viewport-Rand), `top: bell-bottom + 8px`. Plus `max-width: calc(100vw - 2rem)` clampt das Panel auf engen Geräten (320px → 288px Breite, 414px → 360px Breite).
- Fix Lead-Karte zeigte „(direkt)" als Quelle, sobald der Besucher den Iframe auf einer Site mit `Referrer-Policy: no-referrer` gesehen hat — dann fehlt sowohl Referer-Header als auch URL-UTM. Embed-Endpoint setzt jetzt automatisch einen Fallback `utm_source=embed_iframe`, `utm_medium=iframe`, sobald nichts anderes erfasst wird. Echte UTMs in der Iframe-URL und ein vorhandener Referer überschreiben den Fallback weiterhin. In der Lead-Liste taucht das als „Embed (Website)" auf statt im Direct-Bucket. Tipp: für saubere Per-Landing-Auswertung trotzdem eigene UTMs an die iframe-`src` hängen.
- Neu Notification-Bell ist jetzt auch in der mobilen Top-Bar verfügbar (vorher nur auf md+ sichtbar) — der Bell-Aggregator wurde in `lib/notifications/bell-feed.ts` extrahiert, beide Top-Bars (Desktop `TopHeader`, Mobile `AppNav`) ziehen die identischen 5 Streams: Quote-Messages, Task-Comments, Service-Bericht-Comments, Status-Update-Comments, neue Public-CheckUp-Leads.
- Neu Neuer Bell-Stream + Push-Surface „Neuer Lead". Sobald ein Public-CheckUp-Lead in der Pipeline `new` liegt, taucht er in der Glocke oben rechts mit Score, Branche und Größe auf — verschwindet automatisch, sobald der Lead auf „Kontaktiert" gezogen wird. Parallel feuert die submitPublicCheckup-Action einen Web-Push an alle Workspace-Admins mit `surface: "newLead"` — opt-in/-out wie alle anderen Push-Kategorien unter `/settings/notifications`. iOS-PWA muss einmalig installed sein + im Settings-Notifications die Subscription registrieren, Voraussetzung wie bei jedem Web-Push auf Apple.
- Neu Public-CheckUp-Iframe trägt jetzt einen subtilen „Powered by MSPercury"-Footer (border-top, kleine graue Schrift, klickbarer Link auf mspercury.com). Nur auf den Embed-Pages (alle 4 Steps), nicht auf den standalone-Landings — die nutzen die normale Marketing-Layout-Footer-Chrome. Plus auf dem Email-Step ein dezentes Datenschutz-Hinweisband: „Mit ‚Code anfordern' akzeptierst du unsere Datenschutzerklärung" mit Link nach `/legal/privacy`. Kein gating Checkbox — die hard binding consent bleibt am Submit-Schritt am Flow-Ende.
- Neu Spam-Folder-Hint im Email-Step („Bitte ggf. auch im Spam-Ordner nachsehen") in DE/EN/ES — half users finden den 6-stelligen Code, wenn er von strenger Mailfilter-Heuristik aussortiert wird. Erscheint sowohl auf der Iframe-Variante als auch auf der standalone Landing.
- Neu Auf `/leads` ist der Iframe-Embed-Snippet jetzt auch außerhalb der Settings copy-fertig zu sehen — als einklappbarer „Als Widget auf eigener Website einbetten"-Block direkt unter dem Public-Link. Mit eigenem Copy-Button und einem UTM-Beispiel als Tipp. Wer sich neu in den Lead-Funnel einarbeitet sieht beide Varianten (direkter Link + Iframe) in einem Blick.
- Neu Drei neue Doku-Seiten auf docs.mspercury.com (DE/EN/ES): „Public-CheckUp & Widget einrichten". 8-stufige End-to-End-Anleitung von leerem Workspace bis zur Iframe-Integration auf der eigenen Marketing-Site, inkl. UTM-Tracking-Beispiele, RGPD-Notes, häufige Stolpersteine. Verlinkt im Docs-Index auf Position 3.5.
- Geändert „Abgelehnt"-Spalte im Lead-Kanban hat eine semantisch passendere rosé-rote Farbe (vorher unauffälliges Grau, das visuell mit „Neu" konkurriert hat). Sortiert sich klar in die Pipeline-Farbpalette ein: Violett (Neu) → Sky (Kontaktiert) → Amber (Angebot) → Emerald (Kunde) → Rose (Abgelehnt).
- Geändert Side-Nav auf der `/settings`-Seite ist jetzt auch auf Mobile sichtbar (vorher `hidden lg:block`, also nur auf Desktop ≥1024px). Damit erreicht man von der iOS-PWA die Sub-Pages „Sicherheit", „Push-Benachrichtigungen", „Vereinbarungs-Vorlagen" usw., die vorher de facto unauffindbar waren. Auf Desktop bleibt's bei der gewohnten Sidebar-Position links.
- Geändert Service-Worker-Cache von `mspercury-v4` auf `v5` gebumpt — sorgt dafür dass installierte PWAs (Desktop + iOS) nach diesem Deploy einmalig die neuen Assets ziehen statt am alten JavaScript zu hängen. Wer eine PWA hat: einmalig aus dem App-Switcher killen und neu öffnen, dann ist alles gleich aktuell.
vor 1 MonatKI-Tier-1C/1D/1E, Lead-Source-Tracking + UI, Stundensatz, MRR pro Kunde, Embed-Snippet, Web Push live, PDF DIN 5008 fix
10Neu4Geändert2Fix1Security
KI-Tier-1C/1D/1E, Lead-Source-Tracking + UI, Stundensatz, MRR pro Kunde, Embed-Snippet, Web Push live, PDF DIN 5008 fix
- Neu KI-Meilenstein-Generator (Tier-1C) — auf jedem Projekt im Status-Tab aufklappbar als „✨ KI-Meilenstein-Entwurf". Sie tippen das Projektziel in 1–2 Sätzen ein („50 Endpoints auf Win11 migrieren, fertig bis Ende Q3") plus optional eine Gesamtdauer in Tagen, die KI liefert 3–7 geordnete Meilensteine mit groben ETAs zurück. Sie sehen die Vorschläge als Liste mit Checkboxen + editierbaren Labels und Datumsfeldern, deselektieren was nicht passt, korrigieren wo nötig, und „Ausgewählte einfügen" schreibt alle in einer atomaren Bulk-Insert-Aktion. Der Meilenstein-Tracker im Kundenportal zeigt sie sofort als Fortschrittsbalken.
- Neu Meilenstein-Reorder per Tastendruck — bei Hover über einen Meilenstein erscheinen rechts ↑/↓-Pfeile, die ihn um eine Position nach oben oder unten schieben. Die Reihenfolge im Kundenportal-Fortschrittsbalken folgt sofort. Beim ersten Eintrag ist ↑ deaktiviert, beim letzten ↓ — die Pfeile springen visuell aus, wenn das Ende erreicht ist. Hilfreich nach KI-generierter Reihenfolge oder wenn ein neuer Meilenstein nachträglich eingeschoben wird.
- Neu KI-Antwortvorschlag im Quote-Thread (Tier-1D) — direkt unter dem Antwort-Textfeld auf jeder Angebots-Detailseite ein neuer Button „✨ Antwort entwerfen". Klick liest die letzten ~10 Nachrichten zwischen Ihnen und der Kundschaft und liefert einen höflichen, sachlichen Antwort-Entwurf in der Sprache der Kundschaft (DE/EN/ES — folgt `customer.preferredLanguage`). Tonalität: kein Anrede/Verabschiedung (Chat-Bubble-Format), max. 4 Sätze, geht konkret auf die letzte Kundennachricht ein. Operator prüft + sendet immer manuell — nichts geht autonom raus, der Audit-Trail bleibt sauber. Wenn Sie schon was getippt haben, fragt der Button vor dem Überschreiben.
- Neu Service-Bericht → Status-Post Brücke (Tier-1E). Auf jedem versendeten oder bestätigten Service-Bericht steht jetzt ein „✨ Auch als Status posten"-Button. Klick destilliert die KI den Bericht zu einem 1-Zeilen-Status-Post mit der passenden Kategorie (meist „erledigt"), wechselt automatisch in den Status-Tab, befüllt den Komposer (Textarea + Kategorie-Radio) mit kurzem violetten Outline-Flash, und Sie senden ihn nach Review mit einem Klick raus. Sie müssen denselben Einsatz nicht mehr zweimal formulieren — einmal als ausführlichen Bericht, einmal als knappen Statusfeed-Eintrag.
- Neu Stundensatz wird pro Workspace gespeichert. Unter Einstellungen → Finanzen gibt es ein neues Feld „Standard-Stundensatz" — einmal setzen, dann bei jedem KI-Aufwand-Angebot aus einem CheckUp vorbefüllt. Leer lassen heißt: jeder Operator gibt den Satz pro Angebot manuell ein, wie bisher. Praktisch für MSPs mit einem festen Tarif (z. B. 120 €/h Standard, vereinzelt überschrieben für Großkunden) — kein Doppel-Tippen mehr.
- Neu Kunden-MRR jetzt direkt sichtbar in der Kundenliste UND auf der Detailseite. Auf der Listenseite eine neue Spalte „MRR" rechts mit dem effektiven Wert pro Kunde + einem ●-Badge wenn ein manueller Override gesetzt ist. Auf der Detailseite eine neue MRR-Karte mit dem Headline-Betrag, der Quelle (manueller Override oder berechnet aus N akzeptierten Angeboten) UND einer Zeilen-Aufstellung der beitragenden Angebote inkl. monatlichem Anteil pro Angebot. Jede Zeile ist klickbar zum Angebot oder direkt zur Bearbeitung — wenn die Kundschaft 5× RMM angenommen hat und Sie wollen das auf 7× hochziehen, klicken Sie „Bearbeiten", passen die Menge an, und der MRR auf der Kundenseite + im Dashboard reflektiert es sofort. Override für Vertragspauschalen bleibt verfügbar (jetzt collapsed unter eigenem Toggle).
- Neu Lead-Quellen-Attribution für den öffentlichen CheckUp. Jeder neue Lead trägt jetzt die volle First-Touch-Attribution: Source-URL (mit Query-String), Referrer-Host und alle fünf UTM-Parameter (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`). Erfassung läuft über ein signiertes Cookie auf der ersten Seite des Flows — überlebt den Multi-Step (Email → PIN → Fragen → Submit), wird beim Lead-Insert in die DB geschrieben, dann gelöscht. First-touch gewinnt: ein wiederkehrender Besucher mit anderer UTM überschreibt nicht den Original-Kanal. Kein Setup nötig außer `?utm_source=google&utm_medium=cpc&...` an die iframe-/Landing-URL anhängen.
- Neu Lead-Quellen-Operator-UI. Auf `/leads` über dem Kanban gibt es jetzt einen „Top-Quellen"-Strip mit den 5 wichtigsten Channels (Lead-Anzahl + Prozentanteil), und jede Lead-Karte trägt eine kompakte 📈-Source-Badge wenn UTM oder Referrer-Daten da sind. Die Pills im Strip sind klickbar — filtern den Kanban auf einen einzigen Channel; „Alle" rechts zum Reset. URL-driven (`?source=google · cpc`), also bookmarkbar/teilbar. Auf der Lead-Detailseite eine neue „Quelle"-Karte mit allen UTM-Feldern, Referrer-Host und der vollen Landing-URL als klickbarer Link, plus expliziter Erklärtext wenn (direkt).
- Neu Iframe-Embed-Snippet jetzt copy-fertig direkt im Settings-Block. Unter Einstellungen → Public-CheckUp wird der vollständige `<iframe src="…/embed" …>`-Snippet inline im grünen Link-Block angezeigt mit eigenem „Kopieren"-Button — vorher steckte er hinter einem `<details>`-Aufklapper und wurde übersehen. Der Embed-Endpoint (`/check/{slug}/embed`) ist iframe-optimiert (CSP `frame-ancestors *`, X-Frame-Options ALLOWALL), trägt den Multi-Step-Flow inkl. PIN-Eingabe komplett innerhalb des Iframes, und passt sich responsive an die Container-Breite an.
- Neu Web Push aktiviert. VAPID-Schlüssel sind in die Server-Konfiguration eingebaut, der `/api/push/key`-Endpoint antwortet jetzt 200 mit dem Public Key, und der Service-Worker kann Subscriptions registrieren. In `/settings/notifications` lässt sich pro Event-Typ (Kundennachricht-empfangen, Quote-Entscheidung, Vereinbarung-signiert, Service-Bericht-Kommentar, Status-Update-Kommentar, Task-Kommentar, Wartungsfenster-Antwort, Billing) opt-in. Browser-natives Push, kein Drittanbieter-Gateway, VAPID-Keys workspace-scoped.
- Geändert Hilfe-Section in der Sidebar startet jetzt by default eingeklappt — egal welcher Viewport. Die meisten Operator-Aufrufe gehen an Kunden / Angebote / Projekte; Docs / Changelog / Feedback nutzt man unregelmäßig. Der Aufklapp-Zustand wird weiter pro Browser gespeichert: einmal geöffnet bleibt geöffnet, bis Sie ihn wieder zumachen.
- Geändert Conversion-Funnel auf dem Dashboard: pro Stufe steht jetzt der Drop-off-Hinweis IMMER sichtbar als Mini-Zeile unter dem Balken — vorher musste der Operator mit dem Cursor draufstehenbleiben und auf den nativen Browser-Tooltip warten (mit ~1s Verzögerung, je nach OS auch gar nicht angezeigt). Ohne Drop-off ist der Hinweis grau („Kein Drop-off"), bei tatsächlichem Verlust rot-italic („↓ 12 verloren ggü. Engaged (-30%)"). Der Operator sieht beim Reinkommen aufs Dashboard sofort wo's hakt, ohne zu hovern.
- Geändert Komplette Localization-Audit: 241 i18n-Keys in DE/EN/ES verifiziert, 0 fehlend. Alle vorher noch hartcodierten deutschen Strings auf dem Dashboard (KPI-Tooltips, MRR-Override-Hinweis, Chart-Hover-Delta-Phrasen, Funnel-Texte), in den Quote-Detail- und Edit-Seiten (Post-Accept-Banner und -Hinweis) und im Projekt-Status-Inline-JS (KI-Strukturierer- und KI-Bericht-Helper-Meldungen) wurden auf `t()`-Aufrufe umgestellt. Die spanischsprachigen Operatoren bekommen jetzt die kompletten KI-Helper- und Dashboard-Texte auf Spanisch — vorher rutschten an einigen Stellen deutsche Originalstrings durch.
- Geändert Doku-Seite (docs.mspercury.com) komplett aktualisiert in DE/EN/ES. Die Funktionsliste erweitert von 12 auf 24+ Blöcke und reflektiert den jetzigen Stand inkl. KI-Tier-1A bis 1D, Sprint-4-Dashboard, Public-CheckUp-Lead-Gen, Iframe-Embed, UTM-Attribution, Manual MRR Override, Quote-Post-Accept-Editing, Vereinbarungs-Vorlagen, Web-Push, Branchen-Routung. Roadmap restrukturiert in „Jetzt / Nächst / Später / Bereits umgesetzt" mit Mai-2026-Daten für die neuen Features. Getting-Started erweitert von 7 auf 8 Schritte (explizites Onboarding, KI-Aufwand-Pfad neben Katalog-Pfad, optionaler Public-CheckUp-Schritt mit UTM-Beispiel). FAQ-Cancel-Frage spiegelt das tatsächlich shipped Self-Service-Löschen statt der Roadmap-Versprechung.
- Fix Angebots-PDF-Folgeseiten (Seite 2 und folgende) hatten nahezu keinen Top-Margin — Inhalt klebte am oberen Seitenrand und brach DIN 5008. Ursache: die @page-Margin-Regel hatte `top: 0` als Default mit einer `@page :left, :right`-Override-Gruppe, deren Cascade in WeasyPrint nicht greift. Behoben: Default ist jetzt 25mm Top, nur die `:first`-Page (mit eigener `.din-header`-Padding-Logik) überschreibt auf 0. Bestehende, bereits exportierte PDFs sind nicht rückwirkend betroffen — neu generierte Angebots-PDFs respektieren ab sofort die DIN-konforme Top-Margin.
- Fix Manueller MRR-Override + Quote-Post-Accept-Editing-Audit (eigentlich schon im Code seit 2026-05-03) waren ohne die zugehörigen DB-Migrationen ausgerollt — die Spalten `customers.manual_monthly_revenue` + `_note` und `quotes.last_edited_at_post_accept` + `last_edited_by_user_id` existieren jetzt tatsächlich auf prod und der Override + die Audit-Spalten funktionieren. Plus die heute neuen Migrationen für `organizations.default_hourly_rate` und sieben neue Spalten auf `prospect_leads` für UTM-/Source-Capture.
- Security SSH-Server-Endpoint ist von Port 22 auf Port 2222 migriert worden, um Hetzner-Edge-Anti-DDoS-Filter dauerhaft zu umgehen. Der Filter ziehlt auf `(src_ip, dst_port=22)`-Tupel und hat in der Vergangenheit den Deploy-Pfad mehrfach für Stunden lahmgelegt. Phase 1 (Port 2222 zusätzlich aufnehmen) ist live und verifiziert; Phase 2 (Port 22 entfernen) folgt nach 24h-Soak. Für Sie als Kunde keine Auswirkung — nur ein Operator-/Deploy-Pfad-Detail.
vor 1 MonatCustomer-Communication-Hub: Service-Berichte, Wartungsfenster, Status-Stream + Diskussion
9Neu1Geändert1Fix1Security
Customer-Communication-Hub: Service-Berichte, Wartungsfenster, Status-Stream + Diskussion
- Neu Status-Stream pro Projekt — kurze Updates an Ihre Kunden ohne den Umweg über E-Mail-Texte. Auf jedem Projekt gibt es einen neuen Reiter „Status & Meilensteine". Sie wählen eine Kategorie (Info, Bestellt, Geliefert, Geplant, In Arbeit, Wartet auf Kunde, Erledigt — jede mit eigenem Icon und Farbe) und schreiben einen Satz wie „18× Lenovo M70q bestellt, geplante Lieferung 15.05.". Ihr Kunde bekommt sofort eine E-Mail im MSPercury-Branding mit direktem Link ins Portal und sieht den Eintrag dort als chronologischen Verlauf zu seinem Projekt. Über einen Toggle „Im Kundenportal sichtbar" können Sie auch interne Notizen für Ihr Team posten, die nie nach außen gehen.
- Neu Meilenstein-Tracker pro Projekt — die „Pizza-Tracker"-Variante für IT-Projekte. Sie definieren die linearen Stufen Ihres Projekts (z. B. „Vertrag unterzeichnet → Hardware bestellt → Hardware geliefert → Installation → Übergabe") mit optionalem Soll-Datum, hakeln sie mit einem Klick ab, sobald sie erledigt sind. Im Kundenportal wird das als horizontaler Fortschrittsbalken mit nummerierten Stufen sichtbar — der Kunde fragt nicht mehr nach „wie weit sind wir denn jetzt?", er sieht es. Manuelles Umsortieren per Drag funktioniert; ein freier Sortier-Index lässt Sie auch nachträglich Stufen einschieben, ohne alles neu nummerieren zu müssen.
- Neu Service-Berichte — der digitale Servicebericht ersetzt den Zettel nach dem Vor-Ort-Termin. Sie tippen nach einem Einsatz Titel, Einsatzdatum, Aufwand in Stunden und einen Beschreibungstext (was wurde gemacht, welche Geräte, Auffälligkeiten, Empfehlungen). Erst als Entwurf speichern, dann mit einem Klick „An Kunde senden" — der Kunde bekommt eine E-Mail und liest den Bericht im Portal. Auf seiner Seite kann er „Erhalten bestätigen" (für die Soft-Quittung, dass er den Bericht gesehen hat) oder mit einem getippten Namen rechtsverbindlich unterzeichnen. Status-Linie: Entwurf → Gesendet → Erhalten → Unterzeichnet, mit IP-Audit beim Signieren. Drafts können Sie noch beliebig bearbeiten oder löschen; gesendete Berichte bleiben als Audit-Trail erhalten und sind nicht mehr nachträglich änderbar.
- Neu Diskussion direkt am Service-Bericht. Wenn Ihr Kunde nach dem Lesen eine Rückfrage hat („War die SSD im Server 1 oder 2?", „Können wir die nächsten Reboots auf abends 19:00 legen?"), schreibt er das als Kommentar direkt am Bericht — kein Mail-Tennis mehr, alles bleibt dokumentiert am richtigen Objekt. Sie sehen die Antwort als ungelesene Markierung in der Glocke oben rechts und antworten ebenfalls inline. Auf der Kunden-Seite ist der Verlauf als Chat dargestellt (Ihre Antworten links grau, seine Nachrichten rechts in Ihrer Markenfarbe). Lese-Markierungen automatisch beim Öffnen — kein Klick auf „als gelesen" nötig.
- Neu Wartungsfenster-Workflow — geplante Wartung mit Kunden-Bestätigung. Auf jedem Projekt gibt es einen neuen Reiter „Wartung". Sie schlagen einen Termin vor (Titel, Start- und Endzeit über datetime-Picker, optionale Beschreibung wer erreichbar ist während der Wartung) und entscheiden, ob mit Service-Unterbrechung zu rechnen ist (das blendet beim Kunden ein Warn-Icon ein). Der Kunde bekommt eine E-Mail mit Termin und Detailinfo, kann im Portal entweder bestätigen oder einen anderen Termin vorschlagen — letzterer mit einem freien Begründungstext, den Sie dann auf Ihrer Seite als „Kunde wünscht Verschiebung" eingeblendet sehen. Wenn Sie das Datum nachträglich verschieben, springt der Status automatisch zurück auf „Vorgeschlagen", damit der Kunde erneut bestätigt — Metadaten-Änderungen am gleichen Slot triggern keine erneute Bestätigung. Vier Lifecycle-Mails: Vorschlag, Verschoben, Aktualisiert, Abgesagt — alle in der Sprache Ihres Kunden (DE/EN/ES).
- Neu Diskussion direkt am Status-Update. Genauso wie bei Service-Berichten kann Ihr Kunde jetzt auch unter jedem Status-Stream-Eintrag eine Rückfrage hinterlegen — kein extra Telefonat, kein eigener Ticket-Loop. Inline-`details`-Aufklappbereich pro Eintrag mit Composer und chronologischem Verlauf. Interne (nicht-kundensichtbare) Status-Posts haben naturgemäß keinen Diskussions-Faden; die Funktion erscheint dort nicht. Beidseitige Lese-Markierungen wie bei Berichten.
- Neu Persönlicher Ansprechpartner pro Kunde. Auf der Kunden-Detail-Seite gibt es ein neues Dropdown unter den Kontaktdaten („Ansprechpartner (MSP-Seite)"), in dem Sie aus den Mitgliedern Ihres Workspaces einen für diesen Kunden zuweisen können. Im Kundenportal erscheint daraufhin oben auf der Übersichtsseite eine „Ihr Ansprechpartner"-Karte mit dem Namen Ihres Mitarbeiters statt des allgemeinen Firmennamens — der Kunde sieht namentlich, wer für ihn zuständig ist, mit direktem Mailto-Link. Ohne Zuweisung bleibt es bei den allgemeinen Workspace-Kontaktdaten.
- Neu Glocke oben rechts zeigt jetzt vier Quellen statt zwei: Quote-Nachrichten, Task-Kommentare, Service-Bericht-Diskussionen (grün) und Status-Update-Diskussionen (hellblau) — alles in einem Badge mit der Gesamtzahl. Klick auf einen Eintrag landet direkt am richtigen Surface (Bericht aufgeklappt, Status-Post aufgeklappt) und markiert den Eintrag automatisch als gelesen, damit Sie ihn nicht zweimal sehen. Der Operator hat damit eine zentrale Inbox für jede Kunden-Reaktion in jedem Kanal — kein Suchen mehr durch Tabs.
- Neu „Aktuelles" auf der Kundenportal-Übersicht erweitert. Die chronologische Activity-Liste zeigt zusätzlich zu Vereinbarungen, Angeboten und CheckUp-Berichten jetzt auch Service-Berichte (mit „neu — bitte bestätigen"-Badge wenn ungelesen) und Wartungsfenster (orange-amber wenn Bestätigung ausstehend, grün wenn bestätigt) — sortiert nach Aktualität, max. 8 Einträge. Der Kunde sieht beim Reinkommen ins Portal auf einen Blick, was es Neues gibt und was er noch tun soll.
- Geändert Polish im Kundenportal-Status-Feed: jeder Eintrag hat jetzt eine farbige Emoji-Icon-Spalte links (kategorie-getönt, halbtransparenter Hintergrund), das Kategorie-Label oben in CAPS-Letterspacing — das Layout liest sich als Timeline, die Kategorie ist auf einen Blick erkennbar.
- Fix Info-Kategorie-Chip im Status-Stream-Composer war im Dark-Mode unleserlich (heller Hintergrund mit hellem Text). Ursache: das Tone-Token `dark:bg-ink-800` war versehentlich gesetzt — das semantische Token `ink-800` wird im Dark-Mode automatisch invertiert, der `dark:`-Override hat das nochmal umgekehrt und so einen weißen Hintergrund produziert. Die `dark:`-Modifier auf der `info`-Tone wurden entfernt; die anderen Kategorien (Amber, Sky, Violet, Rose, Emerald) nutzen echte Tailwind-Farben und brauchen ihre `dark:`-Varianten weiterhin.
- Security Portal-Token-Sperre wirkt jetzt sofort. Beim Audit der neuen Customer-Portal-Endpoints (Acknowledge, Sign, Maintenance-Accept/Reschedule, Comment-Posts) ist aufgefallen, dass alle fünf Token-Validierungen einen Lifecycle-Check auf eine Spalte `expiresAt` machten — die es auf `customer_portal_tokens` aber gar nicht gibt (das Schema kennt nur `revokedAt`, gesetzt beim Operator-seitigen Rotate / manuellen Widerruf). Der Check war damit toter Code und hat nie ausgelöst, gerollte oder widerrufene Tokens blieben technisch nutzbar. Tenant-Isolation durch `customerId`-Binding war davon nicht betroffen — kein Cross-Customer-Leak möglich. Aber ein Operator, der einen Token rotiert hat, konnte sich nicht darauf verlassen, dass der alte Link sofort tot ist. Behoben: alle fünf Endpoints (inkl. der älteren task-comments-Aktion mit derselben Bug-Quelle) prüfen jetzt `revokedAt` und werfen 401, sobald gesetzt. Zusätzlich wurden in derselben Runde defense-in-depth `organizationId`-Doppel-Bindings in alle UPDATE/DELETE-Statements der neuen Actions eingezogen — falls ein zukünftiger Schema-Drift jemals Customer/Org-IDs entkoppelt, fehlschlagen die Writes laut statt leise.
vor 1 MonatSign-flow + signed-PDF fixes, customer status auto-promotion, maintenance banner
2Neu7Fix
Sign-flow + signed-PDF fixes, customer status auto-promotion, maintenance banner
- Neu Customer pipeline status now updates itself when reality moves the deal forward. First quote that goes out to a customer with no classification yet → Warm prospect. First accepted quote (operator-side flip OR customer accepts via the share link) or signed agreement → Active customer. The funnel-stage promotions (cold/warm/hot → active) only happen when there's a clear conversion signal — manually-set values otherwise stick, so a deliberate "cold" or "hot" doesn't get clobbered by an unrelated quote going out. Archived customers are never auto-revived; if an archived customer signs a one-off addendum, that's the operator's call to reactivate, not the system's. Promotion runs as fire-and-forget — failures are logged to journalctl but never block the action that triggered them, so a customer signing an agreement always succeeds end-to-end even if the promotion update hits a transient lock.
- Neu Maintenance banner. Configurable via three env vars on the systemd unit — MAINTENANCE_BANNER_TEXT for the message, MAINTENANCE_BANNER_UNTIL for an optional ISO timestamp that renders a server-computed countdown alongside the text, MAINTENANCE_BANNER_TONE for info / warning / danger colour. Visible at the top of every page including the login screen and marketing pages, so an unauthenticated visitor also sees "Wartung in 15 Minuten" before they try to sign in. Activate by adding the vars to mspercury.service and `systemctl restart mspercury`; deactivate by clearing the text var and restarting again. No DB row, no client JS — countdown is fresh on each page load by re-reading the timestamp at request time.
- Fix Customers were getting "Link not found" after successfully signing an agreement. The DB row was correctly flipped to status=signed and the signed-PDF email actually went out, but the page they landed on couldn't see any of that because Astro's default form-action redirect uses the Referer header — and modern browsers (Strict-Origin-When-Cross-Origin is the default) strip the query string from Referer on POSTs. The page reloaded as `/agreements/<id>/sign` with no `?token=…`, the token-based lookup returned nothing, and we rendered the not-found state. Fixed: the action now returns the token in its success payload so the page handler can do an explicit Astro.redirect with the query string preserved. After the redirect the page sees status=signed and renders the green confirmation panel + Download CTA. On error paths the token is also recovered from `result.input` so "Already signed" / "Token expired" messages render properly instead of the generic not-found.
- Fix Submit button on the public agreement-sign page was rendering white-on-white and effectively invisible. Cause: the button used the workspace's brand colour as background — a near-white brand colour combined with white button text turns the only call-to-action on the page into nothing. The branded header banner above the document can stay accent-coloured, but the actionable buttons — Sign and Download signed PDF — are now hard-coded to a dark slate so they're always readable regardless of brand-colour choice.
- Fix Signed-agreement PDF metadata block was laying out wrong: "Datum 30. April 2026 um 21:49" was wrapping into a tall column, "Kunde ACME GmbH" was running into "IP-Adresse 209.198.153.96", and the document hash was splitting into 4 stacked chunks. Cause: the meta `<dl>` used `display: table` with `display: table-cell` on each `<dt>` and `<dd>` — without explicit row wrappers all eight cells were crammed onto a single CSS-table row, getting ~1/8th of the page width each. Fixed by wrapping each label/value pair in a row container with `display: table-row`, plus `white-space: nowrap` on the labels so they can't wrap into multi-line columns. Existing already-signed PDFs are baked to disk and won't change retroactively; only newly-signed agreements pick up the fix.
- Fix Signing email's sign-link was pointing at http://127.0.0.1:4327. The detail page was building the URL from `Astro.url.origin`, which behind nginx is the upstream Node socket — useless to the recipient. Now uses the publicBaseUrl helper (PUBLIC_APP_URL → X-Forwarded-Host → request URL fallback chain), and the sendAgreement action also rebuilds the canonical URL server-side from the freshly minted token, so even a stale URL the operator pasted in can't ship a broken host.
- Fix Agreement emails arrived as plain text in some inboxes. Cause: the body was wrapped in raw `<p>` tags but had no full HTML document — Outlook (and a few others) fell back to text/plain. Now both the sign invite and the signed-confirmation mail go through the existing branded renderTransactional shell (full <!doctype>, MSPercury wordmark, IT Systeme Flores UG legal footer, dark CTA button), with a localised "Jetzt unterzeichnen" / "Sign now" / "Firmar ahora" button pointing at the canonical sign URL on the invite. URL auto-linkification in operator-authored email bodies was also added in the same pass, so any URL typed into a quote / agreement / customer-thread reply renders as a clickable `<a>` regardless of which client the recipient uses.
- Fix Native <details> disclosure marker was leaking outside the styled card on the /changelog page in some browsers, showing a stray triangle next to one of the rows. Hidden globally via summary::-webkit-details-marker + summary::marker, so the only chevron is the SVG one rendered inside the summary.
- Fix Filter pills on /customers were unreadable when selected in dark mode — the active state used bg-ink-900 text-white, but ink-900 flips to a near-white token in dark mode while text-white doesn't, producing white-on-white. Switched the active pill to the brand accent (bg-accent-700 text-white), which has fixed contrast in both themes.
vor 1 MonatNotification dismiss, customer pipeline status, internal team notes, branded agreement emails
5Neu
Notification dismiss, customer pipeline status, internal team notes, branded agreement emails
- Neu Notification bell can be cleared without opening every quote. Each item in the dropdown has a small × button on hover that marks just that one as read; the header gained a "Mark all read" link that drains the entire unread queue in one click. Both actions are idempotent — clicking dismiss on something that's already read is a no-op, so a stale tab can't undo a fresh action. Localised in DE/EN/ES ("Alle als gelesen" / "Mark all read" / "Marcar todo leído").
- Neu Customer pipeline status. Customers now carry an optional classification — Cold prospect, Warm prospect, Hot prospect, Active customer, Archived — surfaced as a coloured pill on the customer detail page and as a new column on the customer list. The new/edit form has a dedicated picker with an empty default of "Unclassified" so fresh imports don't get a fake "active" status. The list page has a pill-row filter (All · each status · Unclassified) above the table that composes with the existing search field, so you can answer "which warm prospects haven't I followed up on?" in two clicks instead of scrolling. DB column is nullable + additive — existing customers default to Unclassified until you pick something.
- Neu Internal notes thread on the customer detail page. Operator-only — no public surface, no email dispatch — meant for the team's running conversation about the relationship: who's the champion, why the deal stalled, when the renewal date hits, who you spoke to last. Multi-author, timestamped, with a pin-on-post checkbox so the high-signal stuff ("renewal 2026-08", "champion left, talk to Jana now") floats to the top. Pinned/unpinned by anyone in the workspace; only the original author can delete their own notes. Author name is captured both as a foreign key (so future filters work) and as a snapshot string, so a note still renders the original author's name even after they leave the workspace. DE/EN/ES throughout.
- Neu MSPercury-branded HTML template for agreement emails. The DPA-sign invite + the signed-PDF confirmation now go through the same renderTransactional shell quote-thread mails already use: full <!doctype> document, white card on a warm grey background, MSPercury wordmark on top, IT Systeme Flores UG legal footer at the bottom. Sign invitations get a prominent black CTA button — "Jetzt unterzeichnen" / "Sign now" / "Firmar ahora" — pointing at the canonical sign URL.
- Neu Auto-linkification in transactional email bodies. The plain-text-to-HTML helper (used by every operator-authored email) now detects http/https URLs in the body and wraps them in clickable <a> tags with proper styling and target="_blank". Without this, Outlook in particular leaves bare URLs un-clickable; now the link works regardless of which client the recipient uses. Three duplicate copies of the helper were consolidated into one src/lib/mail/format.ts.
vor 1 MonatCheckUp wizard, customer language, AI summary controls, sender branding, PDF page breaks
10Neu3Geändert5Fix1Entfernt1Security
CheckUp wizard, customer language, AI summary controls, sender branding, PDF page breaks
- Neu "Nicht zutreffend" on every CheckUp wizard question. Skip is no longer gated by a per-question flag — any question (Firewall on a solo-self-employed customer, server hardening on a 0-server site, you get the idea) can be marked irrelevant. Skipped questions create no findings, so they're score-neutral, and they're listed under "— nicht zutreffend" on the review screen + PDF report so the audit trail stays honest.
- Neu Score badge in the /checkups list. Finalised CheckUps now show a coloured score pill (0–100 %) right next to the status badge, using the same emerald → amber → rose palette as the report. Drafts stay unchanged because their finding set isn't frozen yet.
- Neu Customer-level preferred language. Customer create/edit form has a new "Preferred language" picker (Org default · Deutsch · English · Español). When set, it overrides the workspace default for every customer-facing artefact: the email-draft default, the share-link CTA copy, the rendered PDF locale on quote and CheckUp report sends. Empty value ⇒ falls back to the workspace's brand language.
- Neu Language picker in the email-send modal. Above subject + body, a small DE/EN/ES tab row lets you swap the entire draft to a different language with one click. Per-language edits are remembered while the modal is open, so flipping back to the original tab restores what you wrote there.
- Neu Sender branding: "<Your name> via MSPercury". Customer-facing emails (quote PDF, quote share link, CheckUp report) now arrive with the From-display-name set to the operator's name (or workspace name if the user has no display name on file) — so the recipient sees who the email is from immediately, not a generic "MSPercury". The mailbox stays at noreply@mspercury.com so DKIM/DMARC keep aligning. System mails (password reset, verify, invites, Stripe webhooks) are unchanged on principle.
- Neu AI executive summary modal with language + detail-level controls. The old one-click button is gone, replaced by a proper modal: pick the output language (defaults to your brand language but freely overridable per generation), pick a detail level — Brief (~150 words, scannable for the C-suite), Standard (~300 words, the v1 default), or Detailed (~700 words, structured with numbered recommendations and a 30/90/180-day plan). On submit the modal shows a foreground progress overlay with elapsed-time counter and rotating stage labels ("Calling Anthropic" → "Streaming response" → "Polishing" → "Saving") so you actually see that the AI is working, instead of just the browser-tab spinner.
- Neu Standalone executive-summary PDF — accompanies the full CheckUp report. New endpoint at /checkups/{id}/summary.pdf renders a single-page A4 with letterhead, recipient block, audit-date + score pill, and the AI-generated prose. Download button on the CheckUp detail page next to the saved-summary preview, plus a small DE/EN/ES locale pill so you see at a glance which language the saved text is in.
- Neu "Also attach summary as PDF" toggle in the CheckUp send-to-customer modal. When the CheckUp has a saved AI summary, the modal grows a checkbox (default-on) that ships the standalone summary PDF as a second attachment alongside the main report. Renderer fails hard rather than silently dropping the attachment — you opted in, so a half-delivered envelope would be more confusing than a clear error you can retry.
- Neu AI integrations card redesign. Settings → AI integrations now shows a structured connection panel (provider, model, masked API key, encryption note, scope-is-this-tenant-only note) with a dedicated red "Disconnect" button that wipes provider, model and the encrypted key from the workspace row in one click. The free-text "Currently connected to anthropic." line that used to sit under the description is gone — it's the panel now.
- Neu Settings → Language now syncs to workspace brand language for admins. Until today, picking Deutsch in Settings only updated your personal UI language (users.preferredLanguage); the workspace brand language (organizations.preferredLanguage, used for outgoing letters and AI summaries) stayed wherever it was. They drifted. Now an admin language change updates both at once, so "my workspace is in German" and "emails go out in German" stop disagreeing. Non-admins still only flip their own UI.
- Fix AI executive summary was English even when the workspace was set to German. Two bugs piled on each other: the action read ctx.locals.locale (which a stale `mspercury_locale=en` cookie could pin to English even after you'd set Deutsch in Settings), and admin language changes never propagated to the workspace row. Fixed both. AI generation now follows organizations.preferredLanguage explicitly, with a per-call override exposed via the new modal — and the tag is baked into the saved summary marker (`--- AI-SUMMARY:de ---`) so the embedded section in the main report and the standalone summary PDF render labels in the same language the AI wrote in.
- Fix AI executive summary errors used to vanish — the action threw ActionError but the page never read the result, so a click did nothing visible. The detail page now reads getActionResult and renders a red banner with the actual Anthropic response (model name typo, revoked key, rate limit) under the button. Server-side, the catch path also writes to journalctl for debugging without re-running.
- Fix AI executive summary was blocked on finalised CheckUps. Lifted: the summary lives on `notes` and only feeds the executive-summary section of the PDF — it doesn't change findings, scores, or any audit-bearing data, so the freeze rule that protects findings doesn't apply. Generating one on a frozen CheckUp now works.
- Fix PDF reports cut content off at the page edge. The CheckUp report cover used a hard-set 297mm height that clipped overflow, and the findings sections forced page-break-inside: avoid on whole categories — categories with many findings then bled past the bottom edge. Same family of issues on the quote PDF (line-group avoid-break) and the internal monthly report (kpi/chart/health cards). All three templates now use targeted break-inside: avoid on the smallest meaningful units (cover stat row, individual finding card, single line row, kpi card, chart frame, health card), repeat <thead> on continuation pages via display: table-header-group, and let categories / line groups break naturally across pages with the heading break-after: avoid'd to its first row.
- Fix ecosystem.config.cjs in the repo implied PM2 was the prod process supervisor. It isn't — production runs the Astro build under a systemd unit (mspercury.service), which is what restarts now go through. The pm2 path on the server returns "command not found". The repo file is left intact for the moment to avoid breaking any unknown dev-side workflow but should be considered cosmetic.
- Geändert Email-draft default language now follows the tenant's effective system language. Previously every workspace got drafts in the org's preferredLanguage regardless of which language the operator was reading the UI in. Now the cascade is: customer.preferredLanguage (if set as an explicit override) → user.preferredLanguage → org.preferredLanguage → en. Net effect: the modal opens in whatever language you're currently using, with one click to switch per-customer.
- Geändert Email-drafts code consolidated. The 4 inline switch-by-locale blocks (PDF / share-link / CheckUp-report) that lived in two detail pages — ~120 lines of nearly-identical copy-paste — moved to lib/mail/drafts.ts with one helper per delivery mode. Each page now does six lines instead of fifty, and tweaking the greeting copy is a one-place change.
- Geändert Customer language picker extracted to a shared component. The select markup was duplicated between customers/new and customers/[id]/edit; now it's one CustomerLanguageField.astro that both pages consume.
- Entfernt Unused `skip` translation key from de/en/es locale files. The wizard switched to the always-available `notApplicable` button last cycle, so the old key was just sitting there.
- Security AI tenant isolation audit. The action path was already structurally clean — every read goes through ctx.locals.organization (session-bound), providerForOrg() builds the HTTP client only from that one org row, AES-256-GCM encryption at rest with a SESSION_SECRET-derived master key, random per-message IV, auth tag included. The Settings card now visibly says it: provider, model, masked key with "encrypted at rest, AES-GCM" hint, scope line "This workspace only — tenant-isolated". One Disconnect click wipes the row.
vor 1 MonatAccount security overhaul: 2FA, passkeys, sessions, trusted devices, email + password change, account delete, GDPR export
12Neu4Fix5Security
Account security overhaul: 2FA, passkeys, sessions, trusted devices, email + password change, account delete, GDPR export
- Neu Forgot password? — finally. /login has a 'Forgot password?' link that sends a single-use reset link by email. After you choose a new password, every existing session and trusted device on your account is wiped, so a leaked password can't keep being used. Link is valid for 60 minutes.
- Neu Active sessions panel on Settings → Security. Every device that's currently signed in to your account is listed with the browser, IP and last-active time. Sign out a single session, or 'Sign out everywhere else' to nuke every cookie except the one you're holding.
- Neu Trusted devices for 2FA. On the /login/2fa prompt you can tick 'Trust this browser for 30 days' — that browser then skips the code on every sign-in for the next month. Manage and revoke individual trusts on Settings → Security. Trusts are wiped automatically when you reset your password or disable 2FA. Backup-code logins never set trust on principle (you're on an emergency device by definition).
- Neu New-device sign-in alert email. Whenever someone signs in to your account from a browser/IP combination we've never seen, we send you an email with the device, IP, time and a one-click 'Reset my password now' button. Browser auto-updates don't trigger false alerts (we strip version numbers from the fingerprint), and the very first device on a fresh signup is silently enrolled.
- Neu Recent activity feed on Settings → Security shows the last 10 security events on your account: sign-ins, 2FA changes, passkey changes, password resets, support resets. If something happened that you didn't do, you'll see it here.
- Neu Support-reset hatch for lost 2FA. If a customer loses both their authenticator app AND all backup codes, we (operator) can now reset their 2FA from /superadmin/users after out-of-band identity verification — the action requires a ticket reference and writes a permanent audit trail. Previously the only recovery was direct DB editing.
- Neu Settings → Security link in the /settings sidebar. The page existed but wasn't reachable from anywhere — fixed.
- Neu Change email address. Settings → Profile → Change. Type your new address + current password, we send a 6-digit code to the new inbox; once you confirm it, the address is updated and the old one gets a security notice (so a hijacker who learned your password can't silently swap the email out from under you).
- Neu Change password while signed in. Settings → Profile → Change password. Verify current password, set a new one. Other sessions on your account are signed out automatically; this device stays signed in. Trusted-device cookies are preserved (re-trusting your laptop on every quarterly rotation would be hostile UX). Distinct from forgot-password, which wipes everything as a recovery measure.
- Neu Self-service account deletion. Settings → Profile → Delete account…. Two-factor confirmation (current password + typing your email verbatim), with a blast-radius preview that calls out the exact counts (X customers, Y projects, Z quotes) for sole-user workspaces, or 'data stays with the other N members' for multi-user. Single users delete their workspace alongside; otherwise just the user goes. Notice email + audit event fire BEFORE the data is wiped (the only window we have).
- Neu Download my personal data. Settings → Profile → Download .json. Single-file dump of every byte we hold for your account: profile, sessions with UA/IP, 2FA + passkey state (without the secrets), trusted + known devices, email-change + password-reset history, full security event log. Excludes workspace data (separate export for that), encrypted secrets, and other users' info. GDPR Article 15.
- Neu Workspace-level 2FA enforcement (admins only). Settings → Workspace → Require two-factor for everyone. When on, every member without TOTP is bounced to /settings/security on every page until they enrol. Lock-out-guard refuses to enable unless the toggling admin already has 2FA on their own account; the action requires the admin's password as defence in depth.
- Fix Adding a passkey was failing with 'Could not verify the credential. Try again.' The server was passing only the inner attestation response to the WebAuthn verifier instead of the full credential envelope. Adding a passkey now works end-to-end on Touch ID, Windows Hello, YubiKeys, etc.
- Fix Buttons on Settings → Security were inconsistent: some were ghosted/transparent in dark mode, others used a hard-coded green that ignored your workspace's accent color. All eight buttons now go through the unified Button component, which means they pick up the brand color you set in Settings → Branding automatically.
- Fix Backup codes after enabling 2FA were unreadable in dark mode — the code chips had a hard-coded white background that turned them into invisible white-on-light text. Now use the same surface tokens as the rest of the app and adapt to both themes. Same fix applied to the TOTP setup-key chip.
- Security Rate-limiting on every auth endpoint. Login (form + JSON), TOTP completion, passkey assertion, signup PIN issuance + confirmation, and password-reset start + complete all have per-IP and (where applicable) per-email/per-uid throttles. Bcrypt is rejected before the slow check runs, so an attacker can't burn CPU. Successful logins reset the bucket so a typo or two doesn't lock anyone out.
- Security Successful password reset wipes EVERY session on the account, not just the cookie that triggered the reset. Same for trusted devices. This closes the 'attacker had a stale session, victim resets password to recover, attacker session keeps working' gap.
- Security Audit events for every security-relevant change: 2FA enable/disable, backup-code regeneration, passkey added/removed/renamed, password reset + change, support reset, new-device sign-in, email change, account deletion, workspace-2FA enforcement toggle, personal-data export. Surfaced both in the user's own Recent activity feed and in the operator's superadmin event log.
- Security Migration safety rail: deploys now go through scripts/safe-migrate.ts, which captures drizzle-kit's planned SQL and refuses to apply it if it contains DROP TABLE, DROP COLUMN, RENAME COLUMN, DELETE FROM <table>, or TRUNCATE — unless the operator explicitly opts in with ALLOW_DESTRUCTIVE=1. Replaces the prior `drizzle-kit push --force` deploy step.
- Security PRAGMA foreign_keys = ON now runs on every DB connection. SQLite ships with FK enforcement OFF by default; without this, a stray DELETE FROM <parent> would silently leave orphan rows in every dependent table. Now cascades work correctly for runtime ops; the safe-migrate rail prevents accidental destructive ops at the migration layer.
- Fix Honesty note from this morning: a drizzle-kit migration on the new workspace-2FA-enforcement column emitted an unexpected `delete from organizations;` before the ALTER, which wiped the org metadata for both production workspaces (workspace name, branding, AI provider config, contact details, billing fields). Recovered within ~10 minutes by reconstructing the org rows from the surviving orphan data (users, customers, projects, quotes, sessions, events all kept their original org_id pointers because FKs were OFF). The migration safety rail and FK pragma above are the structural fixes that prevent a repeat. If your workspace contact info, branding color, AI provider key, or logo upload reference looks blank in Settings — please re-enter; the actual files (logo, customer data) are intact.
vor 1 MonatAngebote per Link teilen
5Neu2Geändert
Angebote per Link teilen
- Neu Angebote lassen sich jetzt per Link mit Kunden teilen. Der Kunde klickt in der E-Mail auf den Button und sieht das Angebot direkt im Browser — mobilfreundlich, druckbar, als PDF speicherbar. Funktioniert ohne MSPercury-Account.
- Neu Optionaler PLZ-Schutz: ist in der Kundenkartei eine Postleitzahl hinterlegt, fragt die Share-Seite diese ab, bevor das Angebot sichtbar wird. So wird der Link nutzlos, falls er an falsche Adressaten weitergeleitet wird.
- Neu Neuer "Copy link"-Button auf der Angebots-Detailseite: erzeugt den Share-Link und legt ihn bereit, damit ihr ihn in euer eigenes Mailprogramm einfügen könnt, statt über MSPercury zu versenden.
- Neu Öffnet der Kunde den Link, seht ihr das — Zeitpunkt des ersten Öffnens und die Anzahl der Aufrufe werden mitgezählt. Links laufen nach 30 Tagen automatisch ab und können jederzeit manuell entzogen werden.
- Neu Neues PLZ-Feld im Kundenformular — dient als Verifikations-Code für die Share-Link-Freigabe.
- Geändert Angebots-PDF kommt jetzt im DIN-5008-konformen Briefkopf (korrekte Position für Fensterkuverts, Bezugszeichenzeile, Betreffzeile).
- Geändert Steuern im Angebot jetzt klar nach §14 UStG aufgeschlüsselt: pro Periode Netto, Rabatt, Netto nach Rabatt, MwSt-Satz und -Betrag, Brutto-Gesamt.
vor 1 MonatSecurity audit + hardening, docs site, SEO pass
4Neu6Security
Security audit + hardening, docs site, SEO pass
- Security Fixed an open-redirect on login. A crafted link like /login?next=https://evil.example would send the user to the attacker's site after they signed in. Only same-origin paths are accepted now; anything else falls back to /dashboard. Same fix applied to the Microsoft SSO callback.
- Security Signup no longer leaks which emails are customers. Trying to register an existing email used to return a visible '409: account exists' error; now the response looks identical to a new signup and the real account holder gets a 'someone just tried to create an account with your email' notification instead.
- Security Login now rotates the session cookie. Any leftover session token is invalidated before a fresh one is issued, neutralising a narrow 'pre-plant a cookie in the victim's browser' attack.
- Security Superadmin DPA flow could be abused, in the worst case, to delete arbitrary files on the server via a malicious path value. The field now only accepts a safe filename pattern.
- Security SQLite database file permissions tightened from 644 to 600 (owner-only). Legacy TLS 1.0/1.1 lines removed from the main nginx config (the stricter TLS 1.2/1.3-only policy was already in effect at the vhost level).
- Security Independent code-level security audit run across the whole app: session handling, multi-tenant isolation, Stripe webhook signature validation, PDF template XSS escaping, secret handling, CSP, middleware bypass guards — all came back clean. Findings above were the complete actionable set.
- Neu NinjaOne monitoring and patch-management agent installed on the VPS. Reports to the EU datacenter; no application data leaves the server.
- Neu Documentation subsite launched at docs.mspercury.com. Seven pages in English, German and Spanish (What it is / Getting started / Features / Known issues / Status / Roadmap / FAQ). The Status page has a live health widget, the header has coloured shortcut pills for the three high-traffic pages, and each page has its own collapsible table of contents in the sidebar.
- Neu Landing-page SEO + AI-search pass: hreflang alternates for all three languages, richer OpenGraph + Twitter Card metadata, JSON-LD structured data with SoftwareApplication + Organization + WebSite + FAQ nodes optimised for AI answer engines. New /robots.txt explicitly allows GPTBot, ChatGPT-User, ClaudeBot, PerplexityBot, etc. and a dynamic /sitemap.xml lists the public pages.
- Neu App nav reshuffle: Docs / Changelog / Feedback moved to a dedicated 'Help' section pinned to the bottom of the sidebar, always one click from the bottom regardless of how much scrolls above. Docs opens the external subdomain in a new tab.
vor 1 MonatFeedback form, superadmin overview, cookie notice, signup consent
5Neu3Geändert3Fix
Feedback form, superadmin overview, cookie notice, signup consent
- Neu In-app feedback form at /feedback. Pick Bug / Feature request / Other, write a message, send. It lands in the superadmin dashboard and fires an email to the operator, with Reply-To set to the submitter so replies go directly to the user.
- Neu Superadmin dashboard at /settings/admin with a cross-tenant overview: stat cards for Tenants / Users / CheckUps / Quotes / Pay-what-you-want / Feedback, per-workspace counts (users, customers, projects, services, findings, quotes), DPA signing status, and a 'Seed sample data' button for workspaces that haven't loaded the starter catalog yet. Recent-users and recent-feedback tables below.
- Neu Cookie notice banner on every page. MSPercury only sets strictly-necessary cookies (session + language + banner acknowledgement) — the banner is transparency, not a consent gate. The Cookie Policy page lists every cookie by name, purpose and lifetime.
- Neu GDPR-required explicit consent on signup: two separate checkboxes for Terms of Service and Privacy Policy, both mandatory, both open the policy in a new tab. Acceptance timestamps are stored on the user record for audit purposes.
- Neu Starter CheckUp findings library: 43 ready-made finding templates covering Security / Updates / Backup / Network / Compliance / Hardware / Permissions / Other. Each entry includes a description and suggested remediation in English, German and Spanish, a default severity + effort, and where applicable a link to the matching catalog service so a finding flows straight into a quote.
- Geändert Catalog seed is now locale-aware. When you click 'Load sample data', service names, descriptions and the default package name are picked in your workspace's language.
- Geändert English is now the default language for unauthenticated visitors, regardless of their browser's Accept-Language header. Logged-in users still get their saved language preference.
- Geändert Landing-page CTAs changed from 'Request invite' to 'Start free' — signup is open, no invite code needed.
- Fix PDF cover page had the company logo overlapping the company name (a WeasyPrint rendering bug). Fixed with a table-based header layout; the logo, name and address now sit cleanly next to each other.
- Fix Every POST form hit a CSRF false-positive under nginx-terminated TLS. Root cause: Astro's built-in CSRF check compared an https Origin header to an http request URL. The session cookie's SameSite=Lax attribute already blocks the underlying attack, so the Astro check has been disabled with a note so it's not silently re-enabled later.
- Fix Email delivery was broken for a week because Hetzner Cloud blocks outbound SMTP on port 465 for new accounts, and our mailer was pinned to that port. Switched to port 587 with STARTTLS; welcome, feedback and invite emails now go through.
vor 1 MonatProduction deployment package + codebase audit
6Neu1Geändert
Production deployment package + codebase audit
- Neu deploy/ folder with complete go-live package: README, DEPLOY master guide, ENVIRONMENT reference (20+ env vars), M365_SSO walkthrough (Entra app registration + multi-tenant), HETZNER (VPS + SMTP + Storage Box + DNS), STRIPE (products + tax + webhook), SSL_DNS (certbot + CAA + HSTS), PRE_LAUNCH_HARDENING (5 gap-closing features), RUNBOOK (day-2 ops + incident playbook)
- Neu deploy/nginx/mspercury.conf — reverse proxy with security headers, HSTS, gzip, long-cache for fonts + _astro assets, upstream timeouts tuned for WeasyPrint PDF rendering
- Neu deploy/systemd/mspercury.service — sandboxed unit running dist/server/entry.mjs as the mspercury user, strict NoNewPrivileges + ProtectSystem + ReadWritePaths limited to data/ + /tmp, restart-on-failure with backoff
- Neu deploy/scripts: deploy.sh (pull/build/migrate/restart with health check), backup-sqlite.sh (nightly SQLite .backup + tgz of uploads/attachments/dpa-signed → Hetzner Storage Box, 30-day retention), firewall.sh (UFW + fail2ban)
- Neu deploy/.env.production.template — sanitised env file with every variable documented inline, grouped by required / optional / third-party
- Geändert package.json: added start:prod running node ./dist/server/entry.mjs for production (existing start is dev-only)
- Neu Full codebase audit: no dead links, no orphan actions, all action imports verified in src/actions/index.ts, every href traced to an existing page, API endpoints cross-referenced with their callers, translation keys sample-verified
vor 1 MonatDSGVO launch-readiness: self-hosted fonts, rewritten privacy policy, Art. 28 DPA + download flow
9Neu1Entfernt
DSGVO launch-readiness: self-hosted fonts, rewritten privacy policy, Art. 28 DPA + download flow
- Entfernt Google Fonts / Gstatic completely eliminated. Every <link rel='preconnect|stylesheet'> that pointed at fonts.googleapis.com or fonts.gstatic.com is gone — BaseLayout, MarketingLayout, AuthLayout, the onboarding page and the standalone CheckUp report. Source tree `grep googleapis` returns 0 hits
- Neu Self-hosted Geist variable woff2 under /public/fonts/ (plus a Mono variant for <code>) with one @font-face declaration in global.css and a font-display: swap. Each layout now <link rel='preload'> the font and ships a CSP meta tag locking font-src to 'self' data:
- Neu Privacy policy fully rewritten (DE + EN, same-file bilingual). 11 numbered sections with stable #anchor ids — Verantwortlicher, Datenschutzbeauftragter, Zwecke + Rechtsgrundlagen, Datenkategorien, Empfänger (Hetzner / Stripe / Microsoft / Apple), Drittlandübermittlung, Speicherdauer, Betroffenenrechte, Cookies (only technisch notwendig), PWA / iOS, Stand. Every company fact pulled from src/lib/legal/company.ts — no hardcoded placeholders
- Neu Auftragsverarbeitungsvertrag (AVV) / DPA at /legal/dpa — full Art. 28 Abs. 3 DSGVO coverage: Parteien, Gegenstand + Dauer, Weisungsrecht, Pflichten, Unterauftragsverarbeiter-Liste (Hetzner / Stripe / Microsoft), Kontrollrechte, TOMs (Zutritt / Zugang / Zugriff / Weitergabe / Eingabe / Verfügbarkeit / Trennung), Meldepflicht, Drittland, Rückgabe/Löschung, Unterschriftenfeld
- Neu GET /legal/dpa.pdf?lang=de|en — WeasyPrint-rendered PDF via the same pipeline as CheckUp / quote reports, with ?controller=<tenant> pre-filling the controller line on the signing page. Every download writes a dpa_records row so Settings / Superadmin can show status
- Neu Settings card 'Datenschutz & Auftragsverarbeitung' — two download buttons (AVV Deutsch / DPA English), Upload-signed-PDF dropzone that stores the countersigned file under data/dpa-signed/{orgId}/dpa-signed.pdf and advances the record to status='signed', plus a reset button for re-signing after TOM changes
- Neu Superadmin-only /settings/dpa-admin — table of every tenant with DPA status (not_signed / sent / signed), download + signing timestamps, and a 'Mark signed' shortcut for the common case where the customer emails the signed copy back
- Neu dpa_records schema + dpaActions (uploadSignedDpa, resetDpa, markDpaSignedForOrg) + upsertDpaRow helper with monotonic status progression (a re-download never demotes a 'signed' back to 'sent')
- Neu README §8 rewritten as a proper DSGVO go-live checklist: attorney review hard-gate, Hetzner / Stripe / Microsoft AVV checklist with archive paths, fonts/CSP verification, customer AVV workflow documentation, DNS CAA reminder
- Neu Smoke test: 13 new DSGVO assertions — no googleapis / gstatic strings on /, /fonts/geist-variable.woff2 200 + font/woff2 content-type, /legal/privacy?lang=de contains Verantwortlicher / Hetzner / Art. 6, /legal/privacy?lang=en contains controller / Article 6 variants, /legal/dpa.pdf?lang=de returns 200 (WeasyPrint) or 503 (graceful), /settings shows 'AVV herunterladen' + links to /legal/dpa.pdf
vor 1 MonatHealth score, infrastructure wizard step, PDF photos, OS-scoped quote engine
6Neu2Fix
Health score, infrastructure wizard step, PDF photos, OS-scoped quote engine
- Neu 0–100 % health score on every CheckUp — shared helper drives an SVG ring hero on both the HTML report and the WeasyPrint PDF. Weights: −8 per high, −4 per medium, −2 per low finding, floored at 0; five color bands (excellent → critical) with locale-aware labels (EN/DE/ES)
- Neu Wizard infrastructure step at /checkups/wizard/{id}/infrastructure — captures per-OS workstation counts (Windows / macOS / Linux / ChromeOS / Other), server breakdown (Windows / Linux / Other), total users, infra notes and on-site photos before question 1. Runs first for every new wizard CheckUp; creates or updates the linked project transactionally
- Neu OS-scoped quantity types on the quote engine: per_windows_ws, per_macos_ws, per_linux_ws, per_chromeos_ws, per_windows_srv, per_linux_srv. buildQuoteFromCheckup resolves each finding's linked service against the OS counts captured in the infrastructure step, so service items whose unit says "Windows workstation" generate exactly the right line-item quantity
- Neu PDF report now embeds all attached photos as base64 data URLs in a dedicated "Photo appendix" section — 2-up grid, captioned with the question scope (infrastructure vs. question-id), printed at up to 95 mm height on A4 so they stay crisp
- Neu Report HTML redesigned mobile-first: class-based responsive CSS, meta grid collapses 4→2 columns under 640 px, action bar stacks on mobile and sits row-wise on desktop, title no longer breaks at em-dash (text-wrap: balance). Print CSS forces desktop layout back so PDFs stay compact
- Neu Wizard desktop layout: sticky top progress row, question content, and fixed bottom action bar share a consistent max-w-3xl inner column; fixed bottom bar gets md:left-60 so it starts past the 240 px sidebar instead of centering on the viewport
- Fix Image upload 400 error — null-tolerant schemas (questionId / caption) let multipart photo uploads succeed again. Verified end-to-end with a 631-byte test JPEG
- Fix enforceOnboarding middleware now skips beta accounts so demo / seed data always reaches the app without getting bounced to /onboarding
vor 1 MonatGuided CheckUp wizard with adaptive questions, photos, EN/DE/ES i18n
8Neu
Guided CheckUp wizard with adaptive questions, photos, EN/DE/ES i18n
- Neu Prominent 'Start a new CheckUp' CTA card at the top of the dashboard — one tap kicks off the guided wizard
- Neu /checkups/wizard/{id}/step/{n} — mobile-first step page with sticky progress bar, type-aware inputs (yesno / single / multi / scale / text), keyboard shortcuts (Enter, J/N), optional note field, and camera-aware photo uploader with client-side canvas compression + EXIF strip
- Neu Adaptive conditional routing: questions with unmet `showIf` clauses are skipped server-side, so step numbers always point to the next visible question
- Neu Auto finding-rule engine: answers trigger deterministic findings (description + priority + effort + optional linked service code), tagged with sourceQuestionId so re-answering regenerates them in place without touching manual findings
- Neu /checkups/wizard/{id}/review — collapsible category groups, editable executive summary (auto-drafted from top-3 findings), add/remove findings, one-click Finalize, and Finalize+Quote when the CheckUp is on a project with service-mapped findings
- Neu 30 default questions across all 7 categories (Sicherheit / Updates / Backup / Netzwerk / Compliance / Hardware / Berechtigungen) — every prompt, context, option label and generated finding text in EN / DE / ES, stored as { en, de, es } JSON and resolved at render time via pickI18n()
- Neu /api/checkup-attachments/{id} streams uploaded photos with auth + org-ownership checks; uploads land under data/checkup-attachments/{orgId}/ and keep EXIF-stripped copies only
- Neu Smoke test: 12 new wizard + i18n assertions (catalog seeded with { en, de, es } JSON, pickI18n resolves across locales, conditional hiding, finding rules fire, EN/DE/ES wizard pages render locale-appropriate copy)
vor 1 MonatPay-what-you-want, setup wizard, breadcrumbs, backup disclosure
5Neu1Geändert3Fix
Pay-what-you-want, setup wizard, breadcrumbs, backup disclosure
- Geändert Rebranded away from 'closed beta': product is now Free + Pay-what-you-want, forever. No trial, no gated plans, no upgrade nags — voluntary contributions only
- Neu Pay-what-you-want form (Settings → Billing + public /pay): set a net amount, pick one-time / monthly / yearly, live 19 % VAT calc + gross total, collapsible invoicing-details block (legal name, address, country, VAT ID, tax no.) so Lucas can issue a §14 UStG-compliant invoice without back-and-forth
- Neu Two PWYW submit paths: email inquiry (from noreply@mspercury.com → info@it-flores.de for manual arrangement) + Stripe Checkout with custom-amount price_data + Stripe Tax when configured
- Neu Setup wizard at /onboarding — new workspaces are gated here until they confirm workspace name, language, currency, VAT rate and optional brand color
- Neu Breadcrumb navigation on every menu-driven page (Dashboard → Customers → Acme GmbH etc.) — auto-derived from route, with per-page tail overrides for detail entities; never leaks raw IDs
- Neu Terms of Service & DPA now explicitly disclose the backup policy: daily full-server backups DO happen; per-tenant snapshots / workspace restore points DO NOT EXIST YET. Users are urged to use the built-in ZIP export
- Fix PWYW form: amount input and interval select are now baseline-aligned on desktop (long label no longer wraps and pushes the input below its neighbour)
- Fix "Save project" / "Save CheckUp" / "New quote" forms no longer silently fail when a required select had no preselected value — forms now default to the first available record
- Fix Stale service worker cleanup in dev mode now does ONE forced reload per tab so in-flight SW handlers stop intercepting form POSTs after the purge
vor 1 MonatDocs + Changelog nav, SW dev-unregister, Post-Redirect-Get polish
2Neu3Fix
Docs + Changelog nav, SW dev-unregister, Post-Redirect-Get polish
- Neu Sidebar links to /docs and /changelog under a new Help section
- Neu /changelog page with date-grouped collapsible entries
- Fix Service worker is now disabled in dev mode and actively unregistered — stale caches no longer hide form-submit redirects
- Fix Settings actions (language, finance, branding, profile, AI) self-redirect after save so the page re-renders with fresh middleware locals, not stale ones
- Fix Primary buttons (New quote / New customer / New project / New CheckUp) get a dark-mode-aware accent variant so they're readable after theme inversion
vor 1 MonatDark mode, Cmd+K search, AI integration, CSV + ZIP exports, /docs
10Neu
Dark mode, Cmd+K search, AI integration, CSV + ZIP exports, /docs
- Neu Three-state theme toggle (system / light / dark) in the top header, no-flash-on-load
- Neu Global command palette via Cmd/Ctrl+K — searches customers, projects, CheckUps, quotes, finding templates, services, packages
- Neu Desktop top header with search trigger + theme toggle; mobile top bar gets matching icons
- Neu AI integration: Anthropic (Claude) + OpenAI (GPT) providers, AES-GCM-encrypted API keys, "Generate executive summary" button on CheckUp editor, auto-embedded into PDF reports
- Neu CSV export per entity (/api/export/<entity>.csv) for customers, projects, quotes, CheckUps, findings, services, packages
- Neu Workspace ZIP export (GDPR Art. 20) — full workspace or per-customer, streamed via archiver, contains JSON + CSV + uploaded logo
- Neu /docs page with 12-section feature walkthrough in English
- Neu Logo upload + primary-color picker in Settings → Branding; both flow through to PDF templates
- Neu Spanish locale (es) in addition to English and German
- Neu Settings → Finance: workspace-level currency picker and default VAT rate
vor 1 MonatCheckUp module, WeasyPrint PDF reports, invite-only beta
5Neu1Geändert
CheckUp module, WeasyPrint PDF reports, invite-only beta
- Neu CheckUp audit module: finding templates library, priority + effort grading, quote generation from mapped findings, immutable snapshots on finalize
- Neu WeasyPrint-rendered PDF reports for CheckUps (cover + TOC + risk matrix + findings by category + methodology + appendix) and quotes (letterhead + line items per billing period + VAT breakdown + terms)
- Neu Invite-only beta signup gate: superadmin issues codes, signup requires ?invite=CODE, invited workspaces are flagged is_beta_account and exempt from Stripe lockout
- Neu Custom confirm modal replacing browser confirm() dialogs (mobile-first bottom-sheet, safe-area-insets)
- Neu PWA manifest + service worker + offline fallback + installable icons
- Geändert Pricing model paused during closed beta — free for beta participants, Stripe code retained for later activation
vor 2 MonatenMobile-first redesign, i18n, marketing + legal pages
7Neu
Mobile-first redesign, i18n, marketing + legal pages
- Neu Mobile-first nav: desktop sidebar + mobile top bar with drawer + bottom tab bar
- Neu EN/DE translation system with per-user preferredLanguage and Accept-Language fallback
- Neu Public marketing pages: landing, pricing, imprint, terms, privacy, DPA, cookies (EN + DE)
- Neu Design tokens: Geist font, warm stone-neutral ink scale, emerald accent, motion easing, shadow scale
- Neu Microsoft 365 OAuth login flow (multi-tenant, optional Graph organization read for auto-named workspace)
- Neu Hetzner SMTP mailer abstraction with EN/DE transactional templates (welcome, trial-ending, seat invite, payment receipt)
- Neu Stripe billing scaffolding: seat-based subscription, Customer Portal, Tax, webhook handler, trial lifecycle