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 home screen on a dark navy background, with a "Let's Talk" heading above a fill-in-the-blank sentence whose colored underlined words like thoughtful, anyone and spicy sit above Customize and Random Sparks buttons, plus a My Sparks link in the header.
Client

TalkSpark

Status

Live

Introduction

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 Sparks screen on a dark navy background, with the wordmark and gradient "My Sparks" button in the header above a "Sparks" heading and a pink-to-purple "More Random Sparks" button on an otherwise empty page

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 in src/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, with animations.css handling reveal and page-transition motion.
  • Vite 8 build with @poppanator/sveltekit-svg compiling SVG icons to Svelte components; deployed via @sveltejs/adapter-vercel on 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 dedicated server-boundary lint rule and editor instruction file.
  • resolve-params.ts turns the UI's lever selections into authoritative prompt constraints, then spark-prompt.ts builds 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 a CritiqueBadge.
  • enrich-spark.ts / enrich-followup.ts attach 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.ts alongside DEFAULT_GENERATION_PARAMS, keeping the one-button default and the customization sheet in sync.
  • spark-sentence.ts composes 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 Sheet with LeverField molecules, so keyboard and screen-reader behaviour comes from tested state machines rather than ad-hoc handlers.

Runes-based stores with local persistence

  • stores/*.svelte.ts use Svelte 5 reactive state for generated sparks, saved sparks, follow-ups, and loading — synced to localStorage so 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.ts for moving between results and spark-reveal-geometry.ts for 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 an atomic-components rule file.
  • Stylelint config includes a custom declaration-property-value-disallowed-list that flags any colour, background, fill, padding, margin, or gap value not expressed as a var(--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.md files 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 validate gate 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.