Skip to content

feat: SynthesisStrategy interface + freeStrategy (#1739)#1765

Merged
CraigBuckmaster merged 1 commit into
masterfrom
feature/issue-1739-synthesis-strategy
Apr 26, 2026
Merged

feat: SynthesisStrategy interface + freeStrategy (#1739)#1765
CraigBuckmaster merged 1 commit into
masterfrom
feature/issue-1739-synthesis-strategy

Conversation

@CraigBuckmaster
Copy link
Copy Markdown
Owner

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:

  • Types: SynthesisStrategyKind, SynthesisRunContext, SynthesisRunResult, SynthesisOutputBlock (recap_section | cta_button | streaming_placeholder | amicus_text | footer_note), ReviewArtifact, CitationRef, SynthesisStrategyCallbacks, SynthesisStrategy.
  • chooseStrategy({ isPremium, amicusFlagEnabled, amicusCanUse }):
    • !isPremiumfreeStrategy
    • premium + flag on + amicusCanUsepremiumAmicusStrategy
    • otherwise → premiumStructuredStrategy

freeStrategy.tsrun() builds mode-shaped recap + clipboard/share CTAs + flag-aware footer note. Returns artifact: null (free path doesn't persist). Exposes recapTitleFor(mode) so the screen can title the rendered card.

premiumStructuredStrategy.ts / premiumAmicusStrategy.ts — stubs that throw not implemented and report their kind. Filled in by #1740 and #1745.

SynthesisFreeRecap.tsx — refactored to consume SynthesisOutputBlock[] (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_placeholder and amicus_text are accepted-but-skipped here; #1745 wires the renderer for those.

StudySessionScreen.tsxuseAmicusAccess + isFlagEnabled('GUIDED_STUDY_AMICUS_SYNTHESIS') feed chooseStrategy. An effect runs the strategy when its kind === 'free' (premium paths plug in starting #1740) and stores the result; blocks flow into <SynthesisFreeRecap blocks={...} title={recapTitleFor(mode)} />.

Tests

  • New synthesis/__tests__/strategy.test.ts — 13 tests:
    • chooseStrategy routing across all four cases.
    • freeStrategy.run shape: 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.
    • recapTitleFor per mode; teaching's six recap labels.
    • Premium stubs throw not implemented and report their kind.
  • Existing SynthesisFreeRecap.test.tsx rewritten for the block-driven API.

Acceptance criteria

Rollback

Revert the PR. The strategy module + freeStrategy are leaf imports; the screen falls back to its previous render path (still functional via the legacy synthesisDraft form).

Notes for Craig


Generated by Claude Code

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.
@github-actions
Copy link
Copy Markdown

Test Results

✅ All tests passed

Passed Failed Total
Tests ✅ 3864 ❌ 0 3864
Suites ✅ 519 ❌ 0 519

Coverage

Statements Branches Functions Lines

⏱️ Duration: 88.1s

@CraigBuckmaster CraigBuckmaster merged commit 3385089 into master Apr 26, 2026
6 checks passed
@CraigBuckmaster CraigBuckmaster deleted the feature/issue-1739-synthesis-strategy branch April 26, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3.2 — SynthesisStrategy interface + freeStrategy implementation

1 participant