# Clone and install
git clone <repository-url>
cd srtora
pnpm install
# Build all packages (required before first dev run)
pnpm build
# Start dev server with hot reload
pnpm devThe project uses pnpm workspaces with Turborepo for build orchestration.
Turborepo automatically builds packages in dependency order:
@srtora/types → @srtora/core, @srtora/adapters, @srtora/prompts → @srtora/pipeline → apps/web
| Command | Description |
|---|---|
pnpm build |
Build all packages |
pnpm dev |
Start dev server |
pnpm test |
Run all tests |
pnpm typecheck |
Type check all packages |
pnpm format |
Format with Prettier |
# Run tests for a specific package
cd packages/core
pnpm vitest run
# Watch mode
pnpm vitestTests use Vitest and are located in src/__tests__/ within each package.
| Package | Tests | Description |
|---|---|---|
packages/core |
114 | SRT/VTT parsing, assembly, chunking, token-budget chunking, validation, token estimation |
packages/adapters |
245 | Model registry matching, JSON repair, output strategy, retry logic |
packages/prompts |
15 | Prompt builders, strategies |
packages/pipeline |
103 | Profile resolver, progress tracking, quality modes, memory injector |
Follow the existing pattern:
import { describe, it, expect } from 'vitest'
describe('MyFeature', () => {
it('does the expected thing', () => {
const result = myFunction()
expect(result).toBe(expected)
})
})Subtitle test fixtures are in packages/core/src/__tests__/fixtures/:
basic.srt/basic.vtt— Simple files for basic parsing testsformatting-tags.srt/formatting-tags.vtt— Files with formatting tagsedge-cases.vtt— VTT with styles, regions, notes
- Create a new adapter class implementing
LLMAdapterinpackages/adapters/src/ - Implement
chat(),listModels(), andtestConnection() - Register it in
create-adapter.ts - Add the provider type to
ProviderTypeSchemainpackages/types/src/provider.ts - Add a UI option in
apps/web/src/components/config/provider-selector.tsx
- Create or edit the appropriate profile file in
packages/adapters/src/model-registry/profiles/(organized by provider:openai.ts,google.ts,anthropic.ts,ollama.ts) - Define a
ModelRegistryEntrywith all required fields:id,displayName,provider,tier,category,contextWindow,maxOutputTokens,executionProfile - For Ollama models, add
matchPatterns(regex) andollamaFamilyfor runtime discovery matching - The entry is automatically included in
registry-data.tsand available through the public API - No other code changes needed — the matcher, profile resolver, UI model selector, and pipeline all pick up new entries automatically
- Run
pnpm testandpnpm buildto verify
- Create a class implementing
PromptStrategyinpackages/prompts/src/strategies/ - Implement
formatMessages(system, user)to returnChatMessage[] - Add a new
PromptStyleIdvalue inpackages/types/src/model-registry.ts - Register it in
packages/prompts/src/strategies/strategy-factory.ts - Export from
packages/prompts/src/index.ts
- TypeScript strict mode across all packages
- Prettier for formatting (no semicolons, single quotes, trailing commas)
.jsextensions in imports within non-web packages (required for ESM)- Zod schemas for all domain types with co-located TypeScript type inference
State management uses a single Zustand store (apps/web/src/stores/translation-store.ts). Pipeline invocation uses a trigger-based pattern:
// Translate button increments trigger
requestTranslation: () => set(s => ({ translationTrigger: s.translationTrigger + 1 }))
// Pipeline runner hook reacts to trigger changes
useEffect(() => { runPipeline() }, [translationTrigger])All pipeline errors use PipelineException from @srtora/types, which includes:
- Error code (typed enum)
- Human-readable message
- Recoverable flag (determines retry behavior)
- Optional suggestion text
LLM outputs are often imperfect JSON. The parseJsonSafe() function:
- Tries direct
JSON.parse() - On failure, runs
repairJson()(strips markdown fences, fixes trailing commas, closes brackets) - Returns
{ data, repaired }ornullif unrepairable
Each supported model has an execution profile that controls translation behavior. The profile resolver merges three layers:
Model Profile (base) × Quality Mode (scaling) × User Config (overrides)
For details, see Supported Models.