An independent review of the Lovable-built Marketing Strategy Builder. The wizard flow is well structured and the visual design is clean. Before you put it in front of real clients, a handful of items need fixing first: AI cost exposure, data persistence, input validation, and the PDF export. This report walks through the findings, answers your questions about multi-user safety, and closes with a recommendation on when to move it off Lovable onto a production stack.
The eight-step wizard is logically sequenced. Framer Motion transitions, Tailwind styling, and the orange-on-cream palette feel deliberate. The two Supabase edge functions do call real AI via Lovable's gateway, and the tool-calling setup for audience segments is the right pattern.
The project builds cleanly once dependencies are installed, and the routing structure (Vite + React Router + a single wizard page) is tidy.
The AI endpoints will accept calls from any origin with no rate limit, no per-user authorisation, and no input caps. All wizard state is in memory only, so a refresh wipes the plan. The export writes unescaped user input into a new window. The "analyse competitors" step does not actually fetch any competitor website. The build project is carrying a large amount of unused code and dependency weight.
None of this undermines the value of the concept. It does matter before the first real client reaches the page.
The review was based on the source code, local build tooling, and the main user journey through the wizard and export flow.
React/Vite wizard components, shared state, export code, Supabase client setup, generated Supabase types, and both Supabase edge functions.
npm run build, npm run lint, npm test, npx playwright test --list, npm ci --dry-run, and npm audit --omit=dev.
Both edge functions set Access-Control-Allow-Origin: *, accept calls made with the public Supabase frontend credentials, and have no per-user authorisation, rate limiting, origin allowlist, or request-size cap. Depending on which AI button is clicked, the business overview, competitor URLs, sales figures, and objective text are sent to Lovable's AI gateway. With the Supabase anon key and project URL (both shipped in the frontend bundle, which is normal for Supabase), anyone can script calls straight at the AI gateway and burn through credits.
FixPer-IP and per-session rate limits are non-negotiable: without them, a public anon-JWT or access token is effectively the same as no check at all. Add both rate limits and server-side token verification (signed access token or Supabase anonymous-session JWT). Cap input sizes (e.g. business overview max 2,000 chars, per-URL max 500 chars). Restrict CORS origin to the live domain as useful hygiene, but do not rely on CORS as the security boundary.
The export builds a raw HTML string using template literals and injects every user field directly (state.businessOverview, state.competitors, state.objectives, customer journey, etc.) into a newly opened window using printWindow.document.write(). If a value contains HTML markup or a script tag, the export window can interpret it as HTML. In the current app this is mainly a self-XSS and reliability issue because plans are not saved or shared, but it still matters before client use: exported PDFs can be corrupted, and future saved/shared plans would make the same bug broader.
FixEither escape every value before interpolation (HTML-encode &, <, >, ", ') or generate the PDF server-side via a proper renderer (Browsershot, Puppeteer, or an API). Surface an error when the print window is blocked.
The tool collects rich context across eight steps, but the AI calls use only a narrow slice of it.
Sent to the AI: the business overview sentence, the competitor URL strings, the sales figures, and the specific objective being reviewed.
Never sent to the AI: the marketing mission statement (step 1), the 6Ws framework (step 2), the audience segments after the user edits them, the chosen audiences and value propositions, the selected tactics, the customer journey map, and the media plan.
This means when a user clicks "AI Review" on an objective, the AI doesn't know their mission, their audiences, or their tactics. It's reviewing each objective in isolation against a one-sentence business description. Users fill in detailed context hoping for useful AI feedback, but most of that effort is only used later in the PDF export.
FixPass the full plan context into each AI call. The "AI Review" of an objective should include the mission statement, 6Ws, chosen audiences, and selected tactics so the review is relevant to the user's stated strategy. The audience-generation step should include the mission and 6Ws. This is the largest AI-quality improvement I found.
Every wizard answer lives in a single React useState. There is no autosave, no localStorage fallback, no draft recovery, no database write. A browser refresh, accidental close, sleep-wake crash, or misclick on "back" from the activation table will lose the whole plan with no warning.
FixAt minimum, mirror state to localStorage on every change and restore on mount. For multi-user use, persist to Supabase on a debounced save so users can come back later and continue. Add an "Export draft as JSON" escape hatch.
In the Tactics step, selections are stored as { "LinkedIn Organic": true, ... }. In ExportSummary, the code does k.split("|") expecting stage|channel|platform. The split returns the channel name in stage and undefined for the others, so the exported "Selected Channels" table will show the channel name in the wrong column and render undefined in the next two. This is a functional bug in the deliverable.
FixEither store the key as the composite value it's being split as (and populate all three parts when toggling), or simplify the export to render the key as a single "Channel" column. Align the writer and the reader.
Step 1 asks the user for a Marketing Mission Statement. It is stored on state.missionStatement. The PDF export has sections 1 to 6, but none of them include the mission. Users fill it in and it quietly disappears from the deliverable.
FixAdd a "Mission Statement" block at the top of the export before "About Your Business".
The on-page growth percentage uses parseFloat(value.replace(/,/g, "")). If the user types a currency symbol like £500,000, parseFloat returns NaN. If the user types 0 for 2025, the division returns Infinity. The edge function is slightly safer and falls back to "unknown" when the 2025 value is not greater than zero, but the raw invalid sales values are still sent into the AI prompt.
FixStrip any non-digit prefix (currency symbols, spaces) before parsing. Guard against zero and NaN. Use a numeric input with inputMode="numeric". Hide the growth row until both values are valid.
The system prompt says "Analyse the competitor websites to understand what B2B audiences they serve." In reality, only the URL strings are sent to Gemini 3 Flash. The model does not have browsing tools enabled here. It infers audience guesses from the domain names alone and its training data. This is fine as a prototype, but it is not what the copy promises the user.
FixEither change the copy to reflect what actually happens ("from competitor URLs and your business overview"), or add a real fetch step that crawls each URL, extracts visible text, and includes a summary in the prompt. The second option is a non-trivial build and needs its own timeout and caching layer.
The AI layer is real, but it is currently a light helper rather than a strategist using the full plan.
The audience function uses Gemini 3 Flash through Lovable's AI gateway and forces a structured tool call. That is a sensible way to get predictable JSON back for the UI.
The objective reviewer gets one objective, the business overview, and sales figures. It does not receive the mission statement, 6Ws, audience choices, or tactics, so its advice cannot be properly grounded in the plan the user has built.
Gemini 3 Flash is fine for quick segment suggestions. For strategic review, a stronger reasoning model plus a two-step "draft, critique, refine" prompt would produce noticeably better marketing advice.
The access password is hardcoded in the React source, which means it ends up in the JavaScript bundle every visitor downloads. This is a soft demo gate, not real access control - fine for now while it's pre-launch, but worth noting. The Lovable page is also not set to noindex. Before sharing the URL more widely, either replace the gate with a real auth step or accept that it's a light cosmetic barrier.
Before public launchAdd noindex, nofollow meta tags now to keep the URL out of Google. At launch, either drop the gate entirely or replace it with a server-side check (e.g. signed invitation link) so the AI endpoints are protected server-side.
npm ci --dry-run fails because package-lock.json is out of sync with package.json. Both bun.lockb and package-lock.json are checked in, implying two package managers. The production JavaScript bundle is ~700 KB minified, and the repo also carries 27 Radix UI packages and 49 Shadcn UI components even though the wizard imports only a small handful. That adds install weight, audit noise, and maintenance surface. Because the lockfile is stale, the exact audit count may change after regeneration, but the current npm audit --omit=dev report shows 9 vulnerabilities, 7 high, including React Router / @remix-run/router and transitive packages such as glob, lodash, minimatch, picomatch, and yaml.
FixPick one package manager and commit only that lockfile. Regenerate the lockfile. Trim unused dependencies. Run npm audit fix.
README.md says "TODO: Document your project here." index.html has <meta name="author" content="Lovable">, the Open Graph image points at lovable.dev/opengraph-image-p98pqg.png, and the Twitter site handle is @Lovable. If a staff member shares the link internally, the preview says "Lovable".
FixReplace author, OG image, and Twitter handle with MFF Marketing values. Host the OG image yourself. Remove the TODO comments.
The only unit test asserts true === true. Playwright imports lovable-agent-playwright-config, which is not declared as a dependency, so npx playwright test fails with a module-resolution error before any test is attempted. The project has no coverage story at all.
FixEither delete the unused test scaffolding (honest) or add real smoke tests for the password gate, the wizard happy path, and the export. Pin whichever Playwright config package is actually intended.
strict: false, noImplicitAny: false, strictNullChecks: false, noUnusedLocals: false. Type safety is effectively off. npm run lint currently reports 7 errors and 8 warnings, including any casts in AboutBusiness and TacticsSection where the types should have flowed naturally.
FixEnable strict TypeScript one flag at a time (start with strictNullChecks, then noImplicitAny) and fix the types as you go. Run lint in CI and gate deploys on it.
No input validation on the request bodies. No length caps on businessOverview, objective, or the competitors array. No timeout on the fetch() to the AI gateway (it hangs as long as the gateway takes). Internal error messages are returned verbatim in the response body (e.g. "LOVABLE_API_KEY not configured"). The review-objective function builds the system prompt by interpolating businessOverview, sales2025, and sales2026 straight into a template literal: a malicious value could attempt prompt injection. The generate-audiences function returns { segments: [] } silently when the AI fails to tool-call, so the UI shows the spinner stop and nothing happens.
FixValidate inputs with a schema (Zod or hand-written). Add AbortSignal.timeout(30_000) to the fetch. Return generic error messages. Escape user input in system prompts (or move user content into the user role only). Show a friendly "AI could not generate segments" message when the response is empty.
Competitor URLs are plain text inputs. There is no URL validation, no type="url", no trimming. The user can type not a url at all and it will be sent to the AI. Sales figures are text inputs accepting any string. Nothing stops a user from clicking "Next" on an empty mission statement and proceeding through the entire wizard with a blank plan, reaching the export.
FixUse type="url" on competitor fields and a numeric input on sales. Disable "Next" until the current step meets a minimum-viable threshold (or at least warn).
The Supabase client is configured with persistSession: true and autoRefreshToken: true although the app has no authentication flow. The generated types.ts shows the Supabase project has zero tables. Supabase here is just an AI proxy. That is fine, but leaving auth-related config in place implies features that don't exist.
FixRemove the unused auth config, or (better) add anonymous-session auth so the edge functions can verify a caller.
Array(5).fill({ segment: "", ... }) creates five references to the same object. The form code updates via .map which creates new objects on each edit, so there is no concrete bug today. But any future contributor who refactors the update path to mutate will introduce a subtle cross-row bug. It is an avoidable trap.
FixUse Array.from({length: 5}, () => ({...})) to create independent objects.
The header nav lets the user click any of the eight steps at any time via goToStep. A user can click "Export" from step 1 and will see an empty PDF. This is confusing rather than broken, but it means the UI makes no attempt to guide the user through a complete flow.
FixEither mark later steps as visually "locked" until earlier ones have minimum input, or show a progress indicator on the step buttons so the user knows how far they've gone.
Several form inputs rely on visual labels without htmlFor/id pairing, so screen readers lose the association. Colour contrast on text-muted-foreground hints sits near the lower edge of WCAG AA on the cream background. The activation table is 1,100px wide minimum and is only mitigated by a horizontal scroll on mobile, which is easy to miss. The access-code dialog is built on Radix Dialog, which is a good accessibility choice.
FixPair labels with inputs properly, tighten contrast, and consider a card-based mobile layout for the activation schedule.
You asked three questions before launch: is it safe to let multiple users in at once, is any of their information retained or shared, and is anything sensitive exposed in the code. Here is the honest answer on each.
One user's answers won't leak into another user's session. Each visitor gets their own React state in their own browser, and the edge functions are stateless.
The real multi-user risk is cost and abuse. Because the AI endpoints have no rate limit, a single abusive user or a few simultaneous real ones can burn AI credits fast or trip Lovable's rate limits, causing 429 errors for everyone. Before announcing the tool to a list, add rate limiting.
The app itself does not save answers to any database. The Supabase project has zero tables, so nothing is persisted on your side.
The fields sent to Lovable's AI gateway when the user clicks the AI buttons are: the business overview sentence, the competitor URL list, the sales figures, and the specific objective being reviewed. They are not all sent on every AI request, but those are the categories that can leave the browser. Everything else the user types (mission statement, 6Ws, audiences, tactics, journey, media plan) stays in their browser and is only used for the PDF. Lovable's and the underlying model provider's terms govern what happens to the data that does get sent. Before you say "we do not retain information" in public copy, confirm the AI gateway retention policy and add a short privacy note on the landing page.
The demo access code ships in the browser bundle, so it should be treated as a light access prompt rather than security. That is acceptable for a private demo if everyone understands the limit.
The Supabase URL and publishable anon key are also in the bundle, which is the normal Supabase pattern but only safe when the backend assumes they are public (the edge functions currently do not). The private LOVABLE_API_KEY is correctly kept on the server side only.
These are the pre-launch items to fix in the existing Lovable build. They keep the current tool safe and usable for a first round of real clients. The rebuild recommendation below is the longer-term path off Lovable, when the tool is earning its keep.
Add per-IP and per-session rate limits (non-negotiable), server-side token verification, input size caps, and a CORS allowlist on both edge functions. Add a timeout on the gateway fetch. Return generic error messages.
The front-end password is cosmetic, not security. Rename it to "access code" in the UI, and add noindex, nofollow to the page so the demo URL stays out of Google.
HTML-encode every user value before interpolation into the export string, or render the PDF server-side. Handle the popup-blocked case explicitly.
Save wizard state to localStorage on every change and restore on load. Add an "Export as JSON" so users can save a copy off the page.
Correct the tactics export columns so the chosen channels show in the right fields, and include the step 1 mission statement in the PDF.
State clearly: what data is sent to AI for processing, that answers are not stored on your servers, and who to contact with questions. One paragraph is enough.
Pick one package manager, regenerate the lockfile, strip Lovable placeholder metadata, replace the OG image, and trim the unused UI packages.
Strip currency symbols on sales, enforce URL format on competitors, minimum length on the mission statement. A few defensive checks stop the AI responses from going sideways.
Lovable has done the useful first job: it has got the tool to the point where real clients can start using it. Patch the pre-launch checklist and the current build is safe to launch. The longer-term path, once the tool is earning its keep, is to move off Lovable onto a stack with proper accounts, saved plans, server-rendered PDFs, and room to grow. That is the rebuild Vu can handle.
Most of the logic is straightforward: a wizard, two AI calls, and an HTML export. The production work is making it durable: accounts, saved plans, rate-limited AI calls, server-side PDF generation, and a normal deploy path.
A Laravel rebuild would reuse the validated flow and put it on infrastructure we already operate every day. The point is not Laravel for its own sake. It is that auth, validation, queues, rate limits, database persistence, PDF rendering, and deploys are native parts of the stack rather than one-off patches around a prototype.
The current build is good enough to get the tool in front of real clients once the pre-launch checklist is worked through in Lovable. The rebuild is what you reach for when the tool is earning its keep and you want to stop patching around Lovable's edges.
Sessions, auth, rate limiting, validation, queues, and CSRF built in. Inertia + React so the front end keeps the Lovable look without losing the React developer experience.
Wizard state autosaves per user, per plan. Users can log back in and pick up where they left off. Multiple plans per account. Clean audit trail.
One gateway for every model. Swap Gemini for Claude or GPT in a line. Rate limiting per user baked in. Long-running generations queue via Horizon, so the UI stays responsive.
Server-rendered PDFs from templated Blade views. Proper fonts, print-quality layout, and no popup-blocker issues. Brandable per client.
Real accounts instead of front-end-only access gates. Optional "invitation-only" mode using signed URLs. Admin view of who has used the tool.
Zero-downtime deploys, auto SSL, WAF, backups, monitoring. Same setup that runs Raq.com and every live Vu project.
Demo access code with no user accounts.
Real accounts, signed invitations, optional SSO.
Refresh wipes the plan.
Autosaved to Postgres. Multiple named plans per user.
Open AI endpoints, no limits.
Authenticated, rate-limited, budgeted per user.
Browser-print PDF with XSS risk.
Server-rendered, branded, escaped, signed download link.
Competitor URLs passed to AI as strings.
Real crawl with cached summaries, fed into the prompt. Users see a genuine analysis.
No account, no history, no admin view.
Admin dashboard of users, plans, AI spend, and exports. You see what your tool is doing.