feat: SynthesisStrategy interface + freeStrategy (#1739)#1765
Merged
Conversation
Phase 3.2 of epic #1725. The strategy abstraction lets the screen render the right synthesis experience without knowing the user's tier or the feature-flag state. This card delivers the interface, the free implementation, the chooseStrategy factory, and stubs for the two premium implementations (filled in by #1740 and #1745). - services/guidedStudy/synthesis/strategy.ts: SynthesisStrategyKind, SynthesisRunContext, SynthesisOutputBlock (recap_section / cta_button / streaming_placeholder / amicus_text / footer_note), ReviewArtifact, CitationRef, SynthesisStrategy interface, chooseStrategy factory (non-premium -> free; premium + flag + amicusCanUse -> amicus; otherwise -> structured). - services/guidedStudy/synthesis/freeStrategy.ts: implements run() — builds mode-shaped recap + clipboard/share/upgrade CTAs + flag-aware footer note. Exposes recapTitleFor(mode) so the screen can title the rendered card. Returns artifact:null (free path doesn't persist). - services/guidedStudy/synthesis/premiumStructuredStrategy.ts: stub throwing 'not implemented' (filled by #1740). - services/guidedStudy/synthesis/premiumAmicusStrategy.ts: stub throwing 'not implemented' (filled by #1745). - components/guidedStudy/SynthesisFreeRecap.tsx: refactored to consume SynthesisOutputBlock[] rather than computing recap shape inline. Renders each block type; copy/share own their clipboard/ expo-sharing logic; footer fires onUpgradeNudgePress. - screens/StudySessionScreen.tsx: useAmicusAccess + isFlagEnabled feed chooseStrategy; effect runs the strategy when its kind is 'free' (premium paths land in #1740/#1745) and stores the result; blocks pass into SynthesisFreeRecap with title from recapTitleFor. New strategy.test.ts: 13 tests covering chooseStrategy routing (non-premium / amicus path / fallback to structured when flag off / fallback when amicusCanUse=false), freeStrategy.run shape, per-mode recap titles, teaching multi-bucket section labels, premium stubs throw + report kinds. SynthesisFreeRecap.test.tsx rewritten for the new block-driven API. tsc / lint / 3864 full-suite tests green.
Test Results✅ All tests passed
Coverage
⏱️ Duration: 88.1s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1739. Phase 3.2 of epic #1725.
Why
The synthesize step needs to render different things for free / premium-structured / premium-Amicus users without the screen having to branch on tier or flag state. This card lays down the strategy abstraction, ships the free implementation, and stubs the two premium ones so #1740 / #1745 can fill them in by replacing a single file.
What
services/guidedStudy/synthesis/strategy.ts— interface + factory:SynthesisStrategyKind,SynthesisRunContext,SynthesisRunResult,SynthesisOutputBlock(recap_section|cta_button|streaming_placeholder|amicus_text|footer_note),ReviewArtifact,CitationRef,SynthesisStrategyCallbacks,SynthesisStrategy.chooseStrategy({ isPremium, amicusFlagEnabled, amicusCanUse }):!isPremium→freeStrategyamicusCanUse→premiumAmicusStrategypremiumStructuredStrategyfreeStrategy.ts—run()builds mode-shaped recap + clipboard/share CTAs + flag-aware footer note. Returnsartifact: null(free path doesn't persist). ExposesrecapTitleFor(mode)so the screen can title the rendered card.premiumStructuredStrategy.ts/premiumAmicusStrategy.ts— stubs that thrownot implementedand report theirkind. Filled in by #1740 and #1745.SynthesisFreeRecap.tsx— refactored to consumeSynthesisOutputBlock[](per spec: "unify the two paths"). The component now renders generically:recap_section→ labeled body,cta_button→ owned clipboard/share/upgrade handler,footer_note→ tappable upgrade nudge.streaming_placeholderandamicus_textare accepted-but-skipped here; #1745 wires the renderer for those.StudySessionScreen.tsx—useAmicusAccess+isFlagEnabled('GUIDED_STUDY_AMICUS_SYNTHESIS')feedchooseStrategy. An effect runs the strategy when itskind === 'free'(premium paths plug in starting #1740) and stores the result; blocks flow into<SynthesisFreeRecap blocks={...} title={recapTitleFor(mode)} />.Tests
synthesis/__tests__/strategy.test.ts— 13 tests:chooseStrategyrouting across all four cases.freeStrategy.runshape:kind=free,artifact=null, empty when nothing captured, expected block order when captured (recap_section × N → cta_button × 2 → footer_note), hides empty recap_sections.recapTitleForper mode; teaching's six recap labels.not implementedand report theirkind.SynthesisFreeRecap.test.tsxrewritten for the block-driven API.Acceptance criteria
freeStrategyproduces correctSynthesisOutputBlock[]for each mode (verified instrategy.test.ts)SynthesisFreeRecapfrom Phase 2.7 — Free synthesis recap (SynthesisFreeRecap component) + free NextChapterNudge #1736 refactored to consume blockschooseStrategyreturnsfreeStrategyfor non-premium; throws "not implemented" for premium paths until Phase 3.3 — premiumStructuredStrategy + persistent save + review queue wiring #1740 / Phase 4.2 — premiumAmicusStrategy + SynthesisPremiumDraft component (streaming output) #1745 landRollback
Revert the PR. The strategy module + freeStrategy are leaf imports; the screen falls back to its previous render path (still functional via the legacy
synthesisDraftform).Notes for Craig
premiumStructured(Phase 3.3 — premiumStructuredStrategy + persistent save + review queue wiring #1740) andpremiumAmicus(Phase 4.2 — premiumAmicusStrategy + SynthesisPremiumDraft component (streaming output) #1745).SynthesisInputfields below the strategy-driven recap. Phase 3.3 (Phase 3.3 — premiumStructuredStrategy + persistent save + review queue wiring #1740) will rationalize this duplication when the structured premium form lands.Generated by Claude Code