[{"data":1,"prerenderedAt":573},["ShallowReactive",2],{"repo-tree":3,"repo-\u002Fpreview-environment-plan":283},[4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,69,72,75,78,81,84,86,88,90,93,96,99,102,105,108,111,114,117,120,123,125,127,129,131,133,135,138,141,143,146,149,152,155,158,161,164,167,169,172,175,178,180,183,186,189,192,195,198,201,203,206,209,212,215,218,221,224,227,230,233,236,239,242,245,248,251,254,257,260,263,266,269,272,275,278,281],{"path":5,"title":6},"\u002Fagents\u002Fbackend-code-style","Backend Conventions",{"path":8,"title":9},"\u002Fagents\u002Fdatabase","Database",{"path":11,"title":12},"\u002Fagents\u002Fportal-code-style","Portal Conventions",{"path":14,"title":15},"\u002Fagents\u002Ftranslation","Translation",{"path":17,"title":18},"\u002Fconventions\u002Fbackend-coding","Backend coding conventions",{"path":20,"title":21},"\u002Fconventions\u002Ffrontend-coding","Frontend coding conventions",{"path":23,"title":24},"\u002Fdevelopment-process","Development process",{"path":26,"title":27},"\u002Flearning-api-preview-hetzner-setup","Learning API Preview on Hetzner + Cloudflare",{"path":29,"title":30},"\u002Flearning-api-preview-vm-plan","Learning API Preview VM Plan",{"path":32,"title":33},"\u002Fmonorepo-structure","Monorepo structure",{"path":35,"title":36},"\u002Foperations","Operations — bugs and support",{"path":38,"title":39},"\u002Fpostmortems\u002F2026-03-16_onboarding-currency-regression","Onboarding Zod transform silently broken — web signups assigned wrong checkout currency",{"path":41,"title":42},"\u002Fpostmortems\u002Freadme","Postmortems",{"path":44,"title":45},"\u002Fpostmortems\u002F_template","TEMPLATE",{"path":47,"title":48},"\u002Fpostmortems\u002Fposthog-comparison","Postmortem practice — comparison with PostHog",{"path":50,"title":51},"\u002Fpreview-environment-plan","Preview Environment Plan",{"path":53,"title":54},"\u002Fprinciples","Engineering principles",{"path":56,"title":57},"\u002Fworking-with-ai","Working with AI",{"path":59,"title":60},"\u002F.claude\u002Fskills\u002Feval-playground\u002Fskill","Eval Playground — Co-development Skill",{"path":62,"title":63},"\u002F.claude\u002Fskills\u002Ffigma-diff-section\u002Fskill","Figma Diff Section Pipeline",{"path":65,"title":66},"\u002Fagents","AGENTS.md",{"path":68,"title":66},"\u002Fclaude",{"path":70,"title":71},"\u002Freadme","Studyflash",{"path":73,"title":74},"\u002Fapps\u002Fcore-api\u002Fagents","Core API (apps\u002Fcore-api)",{"path":76,"title":77},"\u002Fapps\u002Fcore-api\u002Freadme","README",{"path":79,"title":80},"\u002Fapps\u002Femail-previews\u002Fagents","Email Previews (apps\u002Femail-previews)",{"path":82,"title":83},"\u002Fapps\u002Flanding-page\u002Fagents","Landing Page (apps\u002Flanding-page)",{"path":85,"title":83},"\u002Fapps\u002Flanding-page\u002Fclaude",{"path":87,"title":66},"\u002Fapps\u002Flearning-api\u002Fagents",{"path":89,"title":77},"\u002Fapps\u002Flearning-api\u002Freadme",{"path":91,"title":92},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Feval_metrics_design","Surface-Specific Eval Metrics Design",{"path":94,"title":95},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Ftest_set","Quiz Eval Test Set",{"path":97,"title":98},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Ffrontend\u002Freadme","React + TypeScript + Vite",{"path":100,"title":101},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Fknown-issues\u002Fcontent-pillar-shallow-coverage\u002Freadme","Content pillar misses subtopics in dense documents",{"path":103,"title":104},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Fknown-issues\u002Fdocling-empty-section-headers\u002Freadme","Empty section headers dropped by docling chunker",{"path":106,"title":107},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Fknown-issues\u002Fdocling-table-reading-order\u002Freadme","Table\u002Fbox layout causes wrong reading order",{"path":109,"title":110},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Fmetrics\u002Freadme","Quiz eval metrics — canonical rubrics",{"path":112,"title":113},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Freports\u002F2026-04-12-quiz-summary-feedback-current-state","Quiz and Summary Feedback Current State",{"path":115,"title":116},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Freports\u002F2026-04-24-quiz-eval-metrics","Quiz Evaluation Metrics",{"path":118,"title":119},"\u002Fapps\u002Flearning-api\u002Fevals-playground\u002Freports\u002F2026-05-01-quiz-eval-current-state","Quiz Eval Current State",{"path":121,"title":122},"\u002Fapps\u002Flearning-api\u002Fmonitoring\u002Freadme","Monitoring Stack",{"path":124,"title":77},"\u002Fapps\u002Flearning-api\u002Fshared\u002Freadme",{"path":126,"title":77},"\u002Fapps\u002Flearning-api\u002Fworkers\u002Flearning_agents\u002Fflashcard_agent\u002Freadme",{"path":128,"title":77},"\u002Fapps\u002Flearning-api\u002Fworkers\u002Flearning_agents\u002Fingestion_agent\u002Freadme",{"path":130,"title":77},"\u002Fapps\u002Flearning-api\u002Fworkers\u002Flearning_agents\u002Fquiz_agent\u002Freadme",{"path":132,"title":77},"\u002Fapps\u002Flearning-api\u002Fworkers\u002Flearning_agents\u002Fsummary_agent\u002Freadme",{"path":134,"title":77},"\u002Fapps\u002Flearning-api\u002Fworkers\u002Fparser\u002Freadme",{"path":136,"title":137},"\u002Fapps\u002Fmarketing-emails-preview\u002Fagents","Marketing Emails Preview (apps\u002Fmarketing-emails-preview)",{"path":139,"title":140},"\u002Fapps\u002Fmobile-app\u002Fagents","StudyFlash Mobile App - Claude Code Configuration",{"path":142,"title":140},"\u002Fapps\u002Fmobile-app\u002Fclaude",{"path":144,"title":145},"\u002Fapps\u002Fmountain-max\u002Fagents","Mountain Max (apps\u002Fmountain-max)",{"path":147,"title":148},"\u002Fapps\u002Fmountain-max\u002Fgame\u002Freadme","Mountain Max Game",{"path":150,"title":151},"\u002Fapps\u002Fportal\u002Fagents","Portal (apps\u002Fportal)",{"path":153,"title":154},"\u002Fapps\u002Fportal\u002Freadme","Nuxt Minimal Starter",{"path":156,"title":157},"\u002Fapps\u002Fportal\u002Fapp\u002Fcomposables\u002Ffiles\u002Freadme","File Upload Composables",{"path":159,"title":160},"\u002Fapps\u002Fportal\u002Fdocs\u002Flibrary-routing","Library Routing Documentation",{"path":162,"title":163},"\u002Fapps\u002Fsupabase\u002Fagents","Supabase (apps\u002Fsupabase)",{"path":165,"title":166},"\u002Fapps\u002Fwrapped\u002Fagents","Wrapped (apps\u002Fwrapped)",{"path":168,"title":98},"\u002Fapps\u002Fwrapped\u002Freadme",{"path":170,"title":171},"\u002Finfra\u002Freadme","infra\u002F",{"path":173,"title":174},"\u002Finfra\u002Fdns\u002Freadme","DNS Infrastructure",{"path":176,"title":177},"\u002Finfra\u002Fdokploy\u002Freadme","studyflash-dokploy",{"path":179,"title":77},"\u002Finfra\u002Fdokploy\u002Fsdk\u002Fnodejs\u002Freadme",{"path":181,"title":182},"\u002Finfra\u002Finfisical\u002Freadme","Infisical Infrastructure",{"path":184,"title":185},"\u002Finfra\u002Flearning-api\u002Freadme","Pulumi GCP TypeScript Template",{"path":187,"title":188},"\u002Finfra\u002Fopenreplay\u002Freadme","OpenReplay on Hetzner",{"path":190,"title":191},"\u002Finfra\u002Fscripts\u002Freadme","infra\u002Fscripts\u002F",{"path":193,"title":194},"\u002Finfra\u002Fturborepo-cache\u002Freadme","Turborepo Remote Cache Infrastructure",{"path":196,"title":197},"\u002Finternal\u002Fchatwoot\u002Freadme","Chatwoot Infrastructure",{"path":199,"title":200},"\u002Finternal\u002Fchatwoot\u002Fprovider\u002Freadme","studyflash-chatwoot-provider",{"path":202,"title":77},"\u002Finternal\u002Fchatwoot\u002Fprovider\u002Fsdk\u002Fnodejs\u002Freadme",{"path":204,"title":205},"\u002Finternal\u002Fdocs\u002Freadme","internal\u002Fdocs",{"path":207,"title":208},"\u002Finternal\u002Fsupport-bot\u002Fclaude","Support Bot (Maximilian)",{"path":210,"title":211},"\u002Finternal\u002Fsupport-bot\u002Freadme","Studyflash Customer Support Bot (Maximilian)",{"path":213,"title":214},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Faccount_issues","Account Issues",{"path":216,"title":217},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fbilling_invoice","Billing Invoice",{"path":219,"title":220},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fcontent_upload","Content Upload",{"path":222,"title":223},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fdata_loss","Data Loss",{"path":225,"title":226},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fflashcard_issues","Flashcard Issues",{"path":228,"title":229},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fgarbage","Garbage",{"path":231,"title":232},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fgeneral_how_to","General How To",{"path":234,"title":235},"\u002Finternal\u002Fsupport-bot\u002Fkb","Knowledge Base Index",{"path":237,"title":238},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Flanguage_issues","Language Issues",{"path":240,"title":241},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fmindmap_issues","Mindmap Issues",{"path":243,"title":244},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fmisunderstanding","Misunderstanding",{"path":246,"title":247},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fmock_exam_issues","Mock Exam Issues",{"path":249,"title":250},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fpodcast_issues","Podcast Issues",{"path":252,"title":253},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fquiz_issues","Quiz Issues",{"path":255,"title":256},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Frefund_request","Refund Request",{"path":258,"title":259},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fsubscription_cancellation","Subscription Cancellation",{"path":261,"title":262},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fsubscription_info","Subscription Info",{"path":264,"title":265},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fsummary_issues","Summary Issues",{"path":267,"title":268},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Ftechnical_errors","Technical Errors",{"path":270,"title":271},"\u002Finternal\u002Fsupport-bot\u002Fkb\u002Fvideo_issues","Video Issues",{"path":273,"title":274},"\u002Fpackages\u002Fcommon\u002Fdocs\u002Fearly-access-features","Declarative Early Access Features",{"path":276,"title":277},"\u002Fpackages\u002Fcommon\u002Fscripts\u002Freadme","Common Package Scripts",{"path":279,"title":280},"\u002Fpackages\u002Fdevtools\u002Ffigma-plugins\u002Freadme","Figma plugins",{"path":282,"title":77},"\u002Fpackages\u002Fpulumi-infisical\u002Freadme",{"id":284,"title":51,"body":285,"description":558,"extension":567,"lastReviewed":568,"meta":569,"navigation":570,"owner":568,"path":50,"seo":571,"status":568,"stem":291,"tags":568,"__hash__":572},"handbook\u002Fpreview-environment-plan.md",{"type":286,"value":287,"toc":557},"minimark",[288,292,297,301,325,329,347,351,380,384,414,418,542,546],[289,290,51],"h1",{"id":291},"preview-environment-plan",[293,294,296],"h2",{"id":295},"goal","Goal",[298,299,300],"p",{},"Provide pragmatic per-PR previews with dependency-aware deploys:",[302,303,304,312,322],"ul",{},[305,306,307,311],"li",{},[308,309,310],"code",{},"portal"," always previewed",[305,313,314,317,318,321],{},[308,315,316],{},"core-api"," and ",[308,319,320],{},"learning-api"," previewed only when needed",[305,323,324],{},"fall back to UAT resources when previews are unnecessary",[293,326,328],{"id":327},"keep","Keep",[302,330,331,334,337,344],{},[305,332,333],{},"Keep Supabase GitHub integration as the source of truth for preview branch lifecycle.",[305,335,336],{},"Keep Supabase integration responsible for preview branch migrations and baseline seeding.",[305,338,339,340,343],{},"Keep ",[308,341,342],{},".github\u002Fworkflows\u002Fdeploy_preview_env.yaml"," as the app preview orchestrator.",[305,345,346],{},"Keep preview app deploys stateless and environment-driven.",[293,348,350],{"id":349},"clean-up","Clean Up",[302,352,353,356,371],{},[305,354,355],{},"Remove implicit fallback from preview deploys to staging Supabase credentials.",[305,357,358,359],{},"Standardize Supabase preview secret names in GitHub workflows:\n",[302,360,361,366],{},[305,362,363],{},[308,364,365],{},"SUPABASE_CLI_ACCESS_TOKEN",[305,367,368],{},[308,369,370],{},"UAT_SUPABASE_PROJECT_ID",[305,372,339,373,317,376,379],{},[308,374,375],{},"apps\u002Fsupabase\u002Fscripts\u002Fdeploy-preview.sh",[308,377,378],{},"apps\u002Fsupabase\u002Fscripts\u002Fpostdeploy-preview.sh"," as no-ops for now, but treat them as legacy paths and avoid building new behavior there.",[293,381,383],{"id":382},"seeding-policy","Seeding Policy",[302,385,386,389,396,403],{},[305,387,388],{},"Seed only the Supabase preview branch, never app previews directly.",[305,390,391,392,395],{},"Keep baseline preview seed data in Supabase-managed seed flow (",[308,393,394],{},"seed.sql"," \u002F integration flow).",[305,397,398,399,402],{},"Do not run destructive custom seed scripts on every push (",[308,400,401],{},"synchronize",").",[305,404,405,406,409,410,413],{},"If custom seeding is needed later, run it only on branch creation events (",[308,407,408],{},"opened"," \u002F ",[308,411,412],{},"reopened",") and make it idempotent.",[293,415,417],{"id":416},"rollout-plan","Rollout Plan",[419,420,421,449,464,470,480,487,490,493,517],"ol",{},[305,422,423,424,427,428],{},"Detect changed scopes in ",[308,425,426],{},"deploy_preview_env",":\n",[302,429,430,435,440],{},[305,431,432],{},[308,433,434],{},"apps\u002Fcore-api\u002F**",[305,436,437],{},[308,438,439],{},"apps\u002Flearning-api\u002F**",[305,441,442,445,446],{},[308,443,444],{},"apps\u002Fsupabase\u002Fmigrations\u002F**"," or ",[308,447,448],{},"apps\u002Fsupabase\u002Fschemas\u002F**",[305,450,451,452],{},"Compute deploy chain:\n",[302,453,454,459],{},[305,455,456],{},[308,457,458],{},"deploy_learning_preview = learning_changed OR supabase_changed",[305,460,461],{},[308,462,463],{},"deploy_core_preview = core_changed OR deploy_learning_preview",[305,465,466,467,469],{},"Always deploy ",[308,468,310],{}," preview.",[305,471,472,473,475,476,479],{},"Deploy ",[308,474,320],{}," preview only when ",[308,477,478],{},"deploy_learning_preview=true",".",[305,481,472,482,475,484,479],{},[308,483,316],{},[308,485,486],{},"deploy_core_preview=true",[305,488,489],{},"Reuse UAT URLs when preview deploy is skipped.",[305,491,492],{},"Use Supabase preview branch only when learning\u002Fsupabase-dependent preview chain is active.",[305,494,495,496,499,500],{},"Export Infisical secrets from ",[308,497,498],{},"staging"," for both services:\n",[302,501,502,510],{},[305,503,504,507,508,479],{},[308,505,506],{},"\u002Flearning-api\u002F"," uses ",[308,509,498],{},[305,511,512,507,515,479],{},[308,513,514],{},"\u002Fbackend\u002F",[308,516,498],{},[305,518,519,520],{},"Force internal learning-api service wiring in preview runtime:\n",[302,521,522,527,532,537],{},[305,523,524],{},[308,525,526],{},"API_URL=http:\u002F\u002Fapi:8000",[305,528,529],{},[308,530,531],{},"REDIS_URL=redis:\u002F\u002Fredis:6379\u002F0",[305,533,534],{},[308,535,536],{},"CELERY_BROKER_URL=redis:\u002F\u002Fredis:6379\u002F0",[305,538,539],{},[308,540,541],{},"CELERY_RESULT_BACKEND=redis:\u002F\u002Fredis:6379\u002F0",[293,543,545],{"id":544},"out-of-scope-current-pass","Out of Scope (Current Pass)",[302,547,548,551,554],{},[305,549,550],{},"Marketing\u002Femail preview fixes.",[305,552,553],{},"Dependency\u002Fversion mismatch cleanup.",[305,555,556],{},"Custom GitHub Action seeding pipeline.",{"title":558,"searchDepth":559,"depth":559,"links":560},"",2,[561,562,563,564,565,566],{"id":295,"depth":559,"text":296},{"id":327,"depth":559,"text":328},{"id":349,"depth":559,"text":350},{"id":382,"depth":559,"text":383},{"id":416,"depth":559,"text":417},{"id":544,"depth":559,"text":545},"md",null,{},true,{"title":51,"description":558},"oTak_joOhdPJO5VnhHmdbxu7OWWQA0jBnqErzr-AXUA",1779007962944]