TalkSpark is a web app that generates short AI-written conversation starters — called "sparks" — for people who want to break the ice or take a discussion somewhere more interesting. You press one button and get three openers (a random topic, its opposite, and a weird/funny take), which you can copy, bookmark, or ask the app to expand with follow-up questions. Everything runs through a server-side AI pipeline that also grades its own output, while saved sparks live in your browser so the app works without an account.
TalkSpark
TalkSpark generates three AI-written conversation starters per request—a random topic, its opposite, and a humorous take—through a server-side pipeline that grades its own output, letting users copy, bookmark, and persist sparks locally without sign-up or a backend database.

TalkSpark
Live
Introduction

The Challenge
Build a zero-friction conversation-starter app that generates AI-powered prompts without requiring sign-up, while keeping API keys secure, AI output quality high through self-critique, and the codebase maintainable for future phases (multiplayer modes, prep-partner tools, and database persistence).
The Outcome
Shipped a complete generate-and-curate loop (one-button generation, depth/safety levers, self-critiquing output, on-demand follow-ups, persistent local collection) with no account, database, or client-side API key required.
Tech Stack
Frontend
- SvelteKit
- Svelte
- Valibot
- Zag.js
- SCSS
- CSS
- Vite
- Testing Library
- localStorage
- jsdom
- Sass
Language
- TypeScript
- Node.js
- JavaScript
- JSON
Backend
- OpenAI
DevOps
- Vercel
- Knip
- Husky
- lint-staged
- commitlint
- GitHub Actions
- pnpm
- Volta
Tooling
- Anthropic
- Vitest
- Playwright
- ESLint
- Prettier
- Stylelint
- SvelteKit 2 with Svelte 5 runes for reactive state, using SvelteKit's experimental remote functions (
generate.remote.ts,followups.remote.ts) so AI calls are typed RPC from component to server with no hand-rolled API routes. - TypeScript in strict mode (with
checkJs) across shared type definitions insrc/ts/for sparks, follow-ups, critiques, topics, and generation params. - OpenAI Responses API (
gpt-5.4-mini) with structured JSON output for generation, critique, and classification; the Anthropic SDK sits behind a shared provider abstraction (server/api/llm/provider.ts) so models can be swapped per task. - Valibot schemas validate every model response (
spark.schema.ts,followup.schema.ts,critique.schema.ts) before it reaches the client. - Zag.js headless state machines (
@zag-js/dialog,@zag-js/popover) power the accessible sheet, popover, and copy-confirmation UI without a component library. - SCSS component-scoped styles plus CSS custom properties in
variables.css, withanimations.csshandling reveal and page-transition motion. - Vite 8 build with
@poppanator/sveltekit-svgcompiling SVG icons to Svelte components; deployed via@sveltejs/adapter-vercelon Vercel. - Testing with Vitest + Testing Library (jsdom) for units and components, and Playwright for a production-build smoke run.
- Quality gates via ESLint, Prettier, Stylelint, Knip, Husky/lint-staged, commitlint, and GitHub Actions; pnpm and Node versions pinned with Volta.
Key Implementation Details
Server-only generation pipeline with a built-in critique pass
- Generation, critique, and classification all live under
src/lib/server/, so the API key never crosses the client boundary — enforced by a dedicatedserver-boundarylint rule and editor instruction file. resolve-params.tsturns the UI's lever selections into authoritative prompt constraints, thenspark-prompt.tsbuilds a structured request against a strict JSON schema.- Every generated spark and follow-up runs through a separate LLM critique prompt (
critique-prompt.ts,followup-critique-prompt.ts) scored against a clarity/answerability/context-fit/safety rubric, with results surfaced in the UI as aCritiqueBadge. enrich-spark.ts/enrich-followup.tsattach UUIDs, timestamps, indexes, and type metadata as flat typed objects, deliberately shaped so a future database migration is mechanical.
Generation levers and a depth/safety dial as first-class UI
- Four compact levers (people & topic, conversation goal, vibe, depth & safety) are defined once in
generation-options.tsalongsideDEFAULT_GENERATION_PARAMS, keeping the one-button default and the customization sheet in sync. spark-sentence.tscomposes a human-readable summary sentence of the current settings, muting values still at their default so users can see exactly what they've changed.- The customization surface is built as a Zag-driven
SheetwithLeverFieldmolecules, so keyboard and screen-reader behaviour comes from tested state machines rather than ad-hoc handlers.
Runes-based stores with local persistence
stores/*.svelte.tsuse Svelte 5 reactive state for generated sparks, saved sparks, follow-ups, and loading — synced tolocalStorageso the whole app works anonymously, with no login or database.- On-demand follow-ups call the engine with the parent spark as context and can be kept individually (
FollowupKeepButton), building a personal collection at/sparks. - Pure UI logic is factored out of components into testable modules —
result-navigation.tsfor moving between results andspark-reveal-geometry.tsfor the reveal animation math — each with its own unit tests.
Atomic design system enforced by tooling
- Components are organised strictly as atoms → molecules → organisms, backed by a documented design system (
src/styles/DESIGN_SYSTEM.md) and anatomic-componentsrule file. - Stylelint config includes a custom
declaration-property-value-disallowed-listthat flags any colour, background, fill, padding, margin, or gap value not expressed as avar(--token)— token discipline as a lint error rather than a code-review comment.
AI-assisted development workflow, versioned in the repo
- Machine-readable context is committed alongside the code:
AGENTS.mdfiles per directory,.agents/skills/for design-system, SvelteKit, generation, validation, and token-budget guidance, plus.cursor/rules/and.github/instructions/equivalents for other tools. - Product decisions live in a durable
docs/tree (philosophy, spark taxonomy, research, competitive analysis) with dated design specs and plans, so prompt and feature changes trace back to a written rule. - A single
pnpm validategate chains type-check, ESLint, Stylelint, format check, Knip dead-code detection, and Vitest; CI runs it on every push and PR, then builds with a dummy key to prove the bundle packages without secrets.
What I Contributed
- Architected a server-only AI pipeline using SvelteKit's experimental remote functions so generation, critique, and classification run as typed RPC with no hand-rolled API routes and the OpenAI key never crosses the client boundary.
- Designed a generation engine with a built-in LLM critique pass that scores every spark and follow-up against a clarity/answerability/safety rubric, surfacing results as CritiqueBadge components in the UI.
- Built the entire UI on Zag.js headless state machines (dialog, popover) and enforced atomic component structure with custom Stylelint rules that lint-error on raw color or spacing values not expressed as CSS custom properties.
- Established a machine-readable AI-assisted development workflow with versioned AGENTS.md context files, .agents/skills/ design-system and generation guidance, and a durable docs/ tree so product decisions trace to written rules.
Outcome
-
Automated quality enforcement via a single pnpm validate gate chaining type-check, ESLint, Stylelint, Prettier, Knip dead-code analysis, and Vitest, plus CI build verification with a dummy key on every push.
-
Delivered schema-validated structured output (valibot) for every model response and strict server boundary lint rules, keeping the app safe and consistent to extend into planned phases (Topics, prep-partner, Supabase Realtime multiplayer).
TalkSpark ships a complete generate-and-curate loop — one-button generation, tunable depth and safety levers, self-critiquing AI output, on-demand follow-ups, and a persistent local collection — with no account, database, or client-side API key required. The strict server boundary, schema-validated model responses, and token-enforced design system keep it safe and consistent to extend, and the committed docs and agent instructions make the product's reasoning as maintainable as its code.