IdeaMachina Development Guide
App Location
apps/idea-machina/src/
├── ai/
│ ├── prompts/ # AI prompt templates (*.md)
│ └── runner/ # AI module executor (runModule, buildModuleInput, types)
├── components/
│ ├── evolution/ # Evolution system UI
│ │ ├── stages/ # CoreStage, DirectionStage, ForceStage, SparkStage, SparkCard
│ │ └── sidebar/ # AIContextPanel, ContextPanel, StageChecklist, TimelinePanel
│ ├── ideas/ # Legacy ideas UI (IMIdeaCard, IdeasStatsBar, etc.)
│ ├── pricing/ # Pricing discovery UI
│ └── checklist/ # Project setup checklist
├── hooks/
│ └── evolution/ # Evolution hooks (Provider, useEvolution, mutations)
├── lib/ # Business logic, routing, health checks
├── pages/ # All pages
├── stores/__tests__/ # Evolution store tests
├── test-utils/ # Test factories & adapters
├── types/ # TypeScript types
└── App.tsx # Routes + providers
Architecture: Two Systems
1. Evolution System (Primary, Active)
4-stage progressive pipeline: KIPINÄ → YDIN → SOIHTU → VALO
| Stage | Finnish | Meaning | Icon | Color | DB Table | Key Type |
|-------|---------|---------|------|-------|----------|----------|
| KIPINÄ (Spark) | ideat | Raw ideas, AI-developed proposals | Flame | orange | pm_sparks | Spark |
| YDIN (Core) | strategia | Distilled problem/target/outcome | CircleDot | blue | pm_cores | Core |
| SOIHTU (Torch) | toteutus | Execution modules (pricing, validation, GTM, roadmap, CTA slogan, SOME marketing) | Zap | violet | pm_force_modules | ForceModule |
| VALO (Light) | vaikutus | Measured impact, proven value | Sun | yellow | — | — |
Note: The SUUNTA (Direction) stage has been removed from the UI.
pm_directionstable and hooks still exist at DB level but there is noDirectionStagecomponent. Direction data (segments, customer_personas) persists but is accessed differently. Theoutcomefield now lives directly onCore(replacing the oldvalue_shiftfield).
Stage Status: empty | locked | in-progress | done | skipped
Stages unlock sequentially. Activation modes can skip stages.
Landing page model section (IdeaMachinaLandingPage.tsx): Shows all 4 stages in a grid with Finnish names + subtitle (e.g., "KIPINÄ" / "ideat"). i18n keys: landing.stages.*Fi, landing.model.*Subtitle, landing.model.*.
Spark Zones (AHJO / HAUTOMO / SOIHTU)
Sparks are split into three zones controlled by pm_sparks.shelved and pm_sparks.usage_state:
| Zone | Finnish | Filter | Default State | Icon | Color |
|------|---------|--------|---------------|------|-------|
| AHJO (Forge) | Aktiivisesti kehitettävät | !shelved && usage_state != 'implemented' | Expanded, open | Flame | Orange |
| HAUTOMO (Incubator) | Hautumassa olevat | shelved && usage_state != 'implemented' | Collapsed | Egg | Amber |
| SOIHTU (Torch) | Toteutetut kipinät | usage_state == 'implemented' | Collapsed | CircleCheckBig | Green |
SparksSectionacceptszone?: "ahjo" | "hautomo" | "soihtu"prop to filter sparksuseSparkMutations.setShelved(id, shelved)toggles AHJO ↔ HAUTOMOuseSparkMutations.setImplemented(id, implemented)toggles SOIHTU ↔ AHJOLandingSparkCardshows shelve/unshelve + implemented toggle buttons- SOIHTU sparks show green "Toteutettu" badge in collapsed header
EvolutionPagerenders threeCollapsibleSectionwrappers: AHJO (orange) + HAUTOMO (amber) + SOIHTU (green)- HAUTOMO + SOIHTU sparks auto-collapse on load
AI Context Priority: SOIHTU → AHJO → HAUTOMO (implemented sparks are confirmed core features)
Source Spark / LÄHDEKIPINÄ
Sparks track their origin via source_spark_id. When a core is created from a spark, the core stores source_spark_id → that spark is the "YDINKIPINÄ" (core spark).
LandingSparkCardacceptsisSourceSpark?: booleanprop- When
isSourceSpark, all action buttons are hidden (read-only display with rating) CoreSummaryCardshows YDINKIPINÄ section: rendersLandingSparkCardwithisSourceSparkfor the core's source spark- Lookup:
sparks.find(s => s.id === core.source_spark_id)
Kipinäsuihku (Bulk Spark Import)
BulkSparkImport component: paste text → AI parses into explicit + implicit sparks.
- 2-pass client-side flow:
normalize_bulk_text→parse_bulk_sparks(split to avoid edge function timeout) - Typed outputs:
NormalizeBulkTextOutput,ParseBulkSparksOutput,GenerateImplicitSparksOutput - Distinguishes
explicit_sparks(directly mentioned) andimplicit_sparks(inferred) - Hierarchical parent-child relationships with per-spark accept/reject
- Shows source excerpts, reasoning, and 2-line pass status bar
- Persistent error banner on timeout/error
2. Legacy Ideas System
Classic CRUD with status flow: new → nurturing → ready → converted | archived
- Tables:
pm_ideas,pm_idea_tags,pm_idea_ratings,pm_projects - "Continue to..." actions convert ideas to projects/goals/workflows/prompts
Routes
| URL | Page | Notes |
|-----|------|-------|
| /idea-machina | LandingPage | Hero spark input |
| /idea-machina/evolve | EvolutionPage | Main workflow |
| /idea-machina/evolve?stage=spark | SparkStage | Loose sparks |
| /idea-machina/evolve?coreId=X | Core view | Specific core |
| /idea-machina/core | ProjectsPage | Core management |
| /idea-machina/plans | PlansPage | Subscription plans |
| /ideas | IdeasPage | Legacy ideas list |
| /ideas/:id | IdeaDetailPage | Legacy idea detail |
Evolution Data Flow
EvolutionProvider (auto-creates evolution per user)
→ useEvolutionData (React Query ↔ Supabase ai_prompt schema)
→ useEvolution (facade hook)
→ useSparkMutations, useCoreMutations, useDirectionMutations, useForceMutations
→ Optimistic updates + toast feedback
Key Tables: pm_sparks, pm_cores, pm_force_modules, pm_evolutions (also pm_directions at DB level, but UI removed)
Provider Pattern:
useEvolutionId()— throws if no evolution (authenticated only)useEvolutionIdOptional()— returns null for guests
AI Module System
11 runnable modules via ai/runner/runModule.ts → Edge Function ai-run-module:
| Module | Purpose |
|--------|---------|
| ai_develop | Analyze + generate proposals (refine/split/derive/reframe/upgrade) |
| generate_new_ideas | Create 5+ sparks from context |
| brainstorm_idea | Brainstorm angles, next steps, wild cards |
| clarification_mode | Ask follow-up questions |
| instant_activation | Direct 1-phase execution plan |
| structured_activation | Multi-phase execution plan |
| attach_to_core | Recommend core attachment |
| upgrade_entity_context | Suggest context improvements |
| parse_bulk_sparks | Kipinäsuihku: parse text into explicit + implicit sparks |
| normalize_bulk_text | Kipinäsuihku pass 1: normalize raw text |
| generate_implicit_sparks | Kipinäsuihku: infer implicit sparks from text |
Persona Generation (not a prompt module — client-side AI function):
generateCustomerPersonaCandidates()inlib/evolution-ai.tsGeneratePersonasDialogcomponent for accept/reject flow- Personas stored in
pm_customer_personastable (linked to direction)
Concurrency guard in runModule: inFlight Set prevents duplicate calls for the same module.
AI Context Modes (LEAN / FULL)
Evolution AI uses buildProjectContext(data, mode) to control context sent to AI:
| Mode | Sparks | Core | Force | reference_urls | |------|--------|------|-------|----------------| | LEAN | Titles only | name + one_liner | null | stripped | | FULL | Full content | All fields | Full | stripped → url_summaries |
Key rules:
- Raw
reference_urlsare never sent to AI — stripped inbuildProjectContext(full mode) anddevelopCore - Premium users get
url_summaries: AI-summarized URL content (max ~600 chars/URL) viasummarize-urlsedge function UrlSummary { url, summary, chars, fetched_at }— stored inpm_cores.url_summariesjsonbIMContextPreview(wrapper around sharedAIContextPreview) shows FULL/LEAN badge + "URL-lähteet" section with domain tags- Core section in context bar shows only
core.name(no one_liner) - All AI dialogs (GenerateSparks, ClarifyCore) use
mode="full" LandingSparkCardshowsIMContextPreviewduring AI processing
Files: ai/runner/buildProjectContext.ts (mode logic), lib/evolution-ai.ts (summarizeReferenceUrl), types/project-evolution.ts (UrlSummary), components/evolution/AIContextPreview.tsx (IMContextPreview)
Key Business Logic
| File | Purpose |
|------|---------|
| lib/activation-router.ts | Route UI actions → AI modules (safety gates, confidence thresholds) |
| lib/intent-router.ts | Pure routing from classified intent |
| lib/activation-mode.ts | Recommend activation mode (instant/structured/persistent) |
| lib/coreHealthCheck.ts | Derive actionable improvements from core state |
| lib/evolution-ai.ts | AI develop, classify intent, promote spark, summarize URLs, classifyAndRun() (classify→route→run pipeline) |
| lib/contextTiers.ts | Context layer matrix for legacy AI features |
| lib/pipelineStatus.ts | Pipeline state: idea → goals → roadmap → app |
Key Components
| Component | Purpose |
|-----------|---------|
| LandingSparkCard | Full spark interaction: AI dev, proposals, core picker, shelve toggle |
| SparksSection | Filtered spark list with zone support (ahjo/hautomo/soihtu), toolbar, collapse/expand |
| CoreFormSection | Core editing: name, one_liner, problem, target, outcome, non_goals, URL digests (outcome replaced old value_shift) |
| GeneratePersonasDialog | AI persona generation with accept/reject candidates |
| CoreSummaryCard | Core summary with status badge, collapsible chip sections, YDINKIPINÄ source spark |
| KirkastaPanel | AI health check actions panel |
| CreateCoreDialog | Blank core creation |
| CoresPageHeader | Core management page header |
| GenerateSparksDialog | Batch spark generation |
| BulkSparkImport | Kipinäsuihku: paste text → AI parses into explicit + implicit sparks |
| CoreActionsPanel | Contextual action suggestions |
| SparkActivityLog | Spark change history |
| IMContextPreview | AI context bar showing sparks/core/direction/force with mode badge |
| ModuleResultCard | AI module results + ClarificationPanel with free-text option |
| CollapsibleSection | Reusable collapsible wrapper with chevron, used for AHJO/HAUTOMO |
| ExpandableText | Reusable text truncation with "Lue lisää"/"Näytä vähemmän" toggle |
| StageNavBar | 4-stage navigation bar |
UI Patterns
Ref-based Focus on Edit Mode Entry (pendingAiPromptFocus)
When a button needs to open edit mode AND focus a specific field:
const targetRef = useRef<HTMLTextAreaElement>(null);
const pendingFocus = useRef(false);
// In button onClick:
pendingFocus.current = true;
setEditing(true);
// useEffect to apply focus after render:
useEffect(() => {
if (editing && pendingFocus.current) {
pendingFocus.current = false;
requestAnimationFrame(() => targetRef.current?.focus());
}
}, [editing]);
Used by: AI-ohje button in LandingSparkCard.
ExpandableText Component
Reusable overflow detection + toggle for long text:
<ExpandableText text={content} lineClamp="line-clamp-3" />
- Auto-detects overflow via
scrollHeight > clientHeight + 1 - i18n keys:
evolution.spark.showAll,evolution.spark.showLess - Used in:
CoreSummaryCardforcore.one_liner
CollapsibleChipSection (inline in CoreSummaryCard)
Core chip arrays (problem/target/outcome) rendered as collapsible badge groups with chevron toggle. Defined inline in CoreSummaryCard — not a standalone component.
LandingSparkCard isSourceSpark Mode
Read-only spark display with rating. Hides all action buttons. Used when rendering source sparks inside CoreSummaryCard.
References
- Architecture Details — Component hierarchy, data flow, query keys, URL routing
- DB Schema — Table structures, RLS policies
Common Tasks
Add New Spark Field
- Migration:
ALTER TABLE ai_prompt.pm_sparks ADD COLUMN ... - Types: Update
Sparkintypes/project-evolution.ts - Data: Update
useEvolutionData.tsselect query - Mutations: Update
useSparkMutations.ts - UI: Update
LandingSparkCard.tsxorSparkCard.tsx
Add New Core Field
- Migration:
ALTER TABLE ai_prompt.pm_cores ADD COLUMN ... - Types: Update
Coreintypes/project-evolution.ts - Data: Update
useEvolutionData.tsselect query - Mutations: Update
useCoreMutations.ts - UI: Update
CoreFormSection.tsx - i18n: Add key to
evolution.core.*in both locale files
Add New AI Module
- Create prompt:
ai/prompts/module_name.md - Register in
ai/prompts/index.ts - Add output type to
ModuleOutputMapinai/runner/types.ts - Add input builder in
ai/runner/buildModuleInput.ts - Wire into activation router if needed
Add New Evolution Stage Feature
- Reference
useEvolutionComputed.tsfor stage unlocking logic - Create stage component in
components/evolution/stages/ - Add to
StageContent.tsxswitch - Update
stageConfig.tsmetadata - Add mutation hook in
hooks/evolution/
Add New Legacy Idea Field
- Migration:
ALTER TABLE ai_prompt.pm_ideas ADD COLUMN ...(+_fi/_en) - Types: Update
PmIdeaandIdeaFormDataintypes/ideas.ts - Form: Add
LanguageFieldTabsinIdeaForm.tsx - Card: Display in
IMIdeaCard.tsx - Translate: Add to
TranslationResultinlib/ideas.ts