This review records the Marketing Strategy Builder as it was on 22 April 2026. The wizard flow and visual design work well. Before client use, the build needed controls on AI spending, saved state, input validation and repairs to the PDF export. The findings below describe that version, rather than a fresh test of the current service.
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.
Fix Add server-side authorisation and per-user or per-session limits, with IP limits and a total spending cap as additional controls. Verify tokens on the server. An anonymous session is useful for metering but does not establish who the visitor is. Validate input types and set limits that fit real plans. Restrict CORS to the intended site, while treating it as browser policy rather than protection against direct API calls.
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.
Fix Encode every user value for the HTML context where it appears. A server-side PDF renderer needs the same protection, plus restricted access to external URLs and local files. Moving rendering to the server alone does not fix injection. Show 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 never reaches 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.
Fix Describe the current behaviour as suggestions from competitor URLs and the business overview, or add a fetch step. A crawler needs timeouts, response-size limits, caching and controls that block private-network destinations, including redirects.
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.
Compare models and prompting approaches on representative plans after supplying the missing context. Have a marketing reviewer score the advice and measure latency and cost before choosing a replacement.
The access password is hardcoded in React source and is available in the browser bundle. It cannot protect a private demo. Use server-side access control if the page or its functions should be restricted.
Before wider sharing Require server-side authentication or a properly validated invitation for restricted access. A noindex directive asks compliant search engines to exclude a page, but it does not make the page private or protect the AI endpoints.
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.
Fix Validate request bodies, add a timeout to gateway calls and return clear errors without internal details. Keep user text separate from system instructions, but do not treat that separation as a prompt-injection defence on its own. Validate model output before using it and keep any downstream actions behind code-enforced permissions.
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's the answer on each.
I found no shared application state in the reviewed request path. Each browser holds its own React state and the functions process individual requests. Saved plans will need explicit ownership checks and tests that one user cannot read another user's plan.
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 reviewed app does not write answers to a database. That does not establish that no data is retained: request logs, monitoring and AI providers need separate checks.
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 is visible in the browser bundle. Treat the page as publicly accessible until server-side access control is in place.
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.
Repair these issues, then retest the wizard, export and API permissions before inviting clients. A rebuild remains an option if it offers better ownership or maintenance for the business.
Add per-IP and per-session rate limits, 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.
Use server-side access control for a private demo. Add noindex for search visibility, while recognising that it is not an access restriction.
Encode every user value for its output context, including in any server-rendered PDF. Handle a blocked print window 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.
Explain which fields go to AI, who processes them, how long each part of the service retains data and who handles privacy questions. Verify these details before publishing the note.
Pick one package manager, regenerate the lockfile, strip Lovable placeholder metadata, replace the OG image, and trim the unused UI packages.
Validate and normalise sales figures, check competitor URL formats and explain any required fields. Validate on the server as well as in the browser.
The current tool is a useful basis for client testing once the fixes have been verified. Accounts, saved plans and controlled AI calls can be added to its existing stack. A Laravel rebuild is worth considering if you want Vu to own the code changes, hosting and maintenance.
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.
Laravel is the stack we operate regularly. It provides authentication, validation, queues and rate limiting, with integrations for PDF generation. Those components still need correct configuration and tests.
Compare the cost of repairing the existing build with a rebuild using the same acceptance criteria. The decision should follow the maintenance and ownership requirements.
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.
Route AI calls through OpenRouter, apply per-user budgets and queue longer work through Horizon. Model changes need regression checks against the plan-review examples.
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.
Hosting with deployment automation, TLS, backups and monitoring. We would agree recovery requirements and verify the setup before launch.
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 get an analysis of the actual sites.
No account, no history, no admin view.
Admin dashboard of users, plans, AI spend, and exports. You see what your tool is doing.