Files
VoiceStudio/docs/STRUCTURE.md
T
Palash Debnath 7640f42dce feat(install): one-command installer URL (.sh + .ps1) + 3-OS install smoke (#1627)
* feat(install): one-command installer URL (.sh + .ps1) + 3-OS install smoke

- scripts/install.ps1: Windows source installer (winget deps, uv, bun,
  clone, uv sync, frontend build); honors OMNIVOICE_PYTHON/OMNIVOICE_REGION
- infra/install-redirect: Cloudflare Worker serving /install with
  User-Agent sniffing (curl -> sh, PowerShell -> ps1, browser -> landing
  page); proxies live from main; /install.sh + /install.ps1 aliases
- scripts/install.sh: fix stale advertised URL (main/install.sh never
  existed) and repo-root resolution so a local run no longer clones a
  duplicate repo into ~/VoiceStudio (verified on macOS arm64)
- .github/workflows/install-smoke.yml: run both installers end-to-end on
  ubuntu/macos/windows when they change
- docs-sync: install one-liners lead each platform guide; STRUCTURE.md

(#1626)

* fix(install): don't let a failed bun download pass silently

curl | sh runs an empty script and exits 0 when the download fails, so
a bun.sh hiccup surfaced much later as 'bun: command not found' (seen
on the macos-latest smoke runner). Fetch to a temp file, verify, fall
back to npm -g bun when node exists, and die with the manual command.
Same post-install verification for uv.

* fix(install): UTF-8 BOM for install.ps1 + quiet-style changelog entry

- tests/scripts/test_uninstall_ping.py requires shipped PowerShell
  scripts with non-ASCII text to carry a UTF-8 BOM (Windows PowerShell
  5.1 mis-decodes otherwise); same treatment uninstall.ps1 already gets
- test_changelog_style caps entries at ~400 chars
2026-08-21 11:47:39 +00:00

8.9 KiB
Raw Blame History

Project Structure

Every folder has a single job. Every file at the root earns its place.

Layout

VoiceStudio/
│
├── README.md                    ⟵ user-facing overview
├── CHANGELOG.md                 ⟵ release history
├── LICENSE
│
├── pyproject.toml               ⟵ Python project manifest
├── uv.lock                      ⟵ Python lockfile
├── package.json                 ⟵ monorepo manifest (Bun workspaces + Turborepo)
├── bun.lock                     ⟵ JS lockfile
├── turbo.json                   ⟵ turborepo pipeline
│
├── .dockerignore                ⟵ Docker build context filter
├── backend.spec                 ⟵ pyinstaller spec (stays at root by pyinstaller convention)
├── alembic.ini                  ⟵ DB migration config (stays at root by alembic convention)
│
├── .env                         ⟵ user config; gitignored, .env.example is the template
├── .gitignore
│
├── backend/                     ⟵ FastAPI server
│   ├── main.py
│   ├── api/routers/             HTTP endpoints (thin)
│   ├── core/                    config, db, task queue, metrics
│   ├── services/                business logic
│   └── schemas/                 pydantic request/response shapes
│
├── frontend/                    ⟵ React 19 + Vite + Tauri desktop
│   ├── src/
│   │   ├── pages/               one file per top-level view
│   │   ├── components/          reusable UI
│   │   ├── api/                 typed API clients
│   │   ├── store/               Zustand slices
│   │   ├── hooks/               custom React hooks
│   │   └── utils/
│   ├── src-tauri/               Rust desktop shell
│   └── public/
│
├── omnivoice/                   ⟵ the underlying TTS model package
│   ├── models/
│   ├── cli/                     CLI entry points (omnivoice-infer, etc.)
│   ├── data/                    data utilities used by the model
│   ├── eval/                    evaluation scripts
│   ├── scripts/                 one-off utilities that ship with the package
│   ├── training/
│   └── utils/
│
├── tests/                       ⟵ all tests live here, no exceptions
│   ├── conftest.py
│   ├── test_api.py
│   ├── test_dub_*.py
│   ├── test_job_queue.py
│   ├── test_segmentation.py
│   └── frontend/                Node-based frontend tests
│
├── scripts/                     ⟵ dev / build / release shell + python scripts
│   ├── install.sh               universal installer (macOS/Linux/WSL)
│   ├── install.ps1              universal installer (Windows)
│   ├── run.sh                   universal launcher
│   ├── smoke-test.sh            end-to-end validation
│   └── desktop-prod.sh          production desktop build

├── infra/                       ⟵ edge/deploy workers (not the Docker deploy path)
│   └── install-redirect/        voicestudio.sh/install — UA-sniffing installer worker
│
├── deploy/                      ⟵ Docker deployment configs
│   ├── Dockerfile               single-stage CUDA image
│   └── docker-compose.yml       one-click local deployment
│
├── docs/                        ⟵ developer docs, screenshots, branding
│   ├── ROADMAP.md               where this project is going
│   ├── STRUCTURE.md             you are here
│   ├── mcp.json                 MCP config template
│   ├── preview.png              README hero image
│   ├── logo.png, logo.svg       branding assets
│   ├── screenshot-*.png         feature screenshots
│   ├── languages.md
│   ├── training.md
│   ├── data_preparation.md
│   ├── evaluation.md
│   └── voice-design.md
│
├── examples/                    ⟵ runnable demos + sample inputs
│
├── omnivoice_data/              ⟵ Docker bind-mount target (gitignored)
│                                   DB + HF cache live here when running via compose
│
└── .git/

Rules of the root

  1. Nothing at the root is a runtime artifact. Outputs, temp files, local DBs, crash logs — all go to ~/Library/Application Support/OmniVoice/ (or the OS equivalent), never into the repo. The one exception is omnivoice_data/, which exists as a bind-mount anchor for Docker.

  2. No ad-hoc scripts at the root. One-off debug scripts live in scripts/. Tests live in tests/. Benchmarks live in scripts/benchmarks/ (when we create them).

  3. Each subdirectory owns one concern. If you can't describe what goes in a directory in one sentence, it's wrong.

  4. Every package has a manifest. backend/, frontend/, omnivoice/ each have their own deps declared via pyproject.toml / package.json — they are independently testable.

What lives where

Kind of thing Goes in
User-facing product code backend/, frontend/
The TTS model (independent of the studio) omnivoice/
Everything executable but not user-facing scripts/
Tests tests/
Developer + user docs (Markdown) docs/
Architecture decision records (ADRs) docs/adr/
Runnable demos and sample data examples/
Runtime data (never committed) ~/Library/Application Support/OmniVoice/ on Mac

What doesn't live at the root anymore

Removed in the cleanup pass:

File Why it was there Where it went
test_crash.py, test_server.py, test_whisper.py, test_mock.py, test_pyannote.py One-off debug scripts from an April 14 crash investigation. Imported symbols that no longer exist after the router refactor. Deleted (already gitignored, referenced dead code).
benchmark.py Another stale debug script; imported backend.main._get_db which no longer exists. Deleted.
output.wav, test.wav Runtime artifacts. Deleted / moved out.
crash_log.txt Runtime log. Now written to $DATA_DIR/crash_log.txt. Deleted.
omnivoice.zip (148 MB) Offline reference archive of the project itself. Moved out of the repo to ../omnivoice.zip.bak.
data/ Only contained .DS_Store. Deleted.
legacy_gradio/ The pre-React Gradio UI. Kept for historical reference. Archived to research/legacy_gradio/, then removed in the 2026-07-12 cleanup (git history).
Scattered .DS_Store files macOS Finder droppings. Deleted from every non-ignored directory.

Removed in the 2026-07-12 cleanup pass (all preserved in git history):

Dir Why it was there Where it went
.planning/ (74 files) GSD-era planning archive: phases, quick plans, issue clusters. The GSD workflow was retired 2026-07-08. Deleted; the four load-bearing decision docs moved to docs/adr/.
specs/ spec-kit specs for features 001–007 — all shipped. Deleted.
design/ ASCII mockups of the pre-React target UX, superseded by the shipped app. Deleted.
research/ Archived legacy Gradio UI + April-2026 competitor notes. Deleted.
.agents/ Rules for a third-party agent tool no longer in use. Deleted.

Scaling path (proposed, not yet executed)

The current flat layout works fine for the current size. If the project grows to include additional apps (a mobile companion, a plugin SDK, multiple backends), migrate to a Turborepo-style monorepo:

VoiceStudio/
├── apps/
│   ├── api/                 ← was backend/
│   ├── web/                 ← was frontend/
│   └── desktop/             ← could extract src-tauri/ here later
├── packages/
│   ├── omnivoice-model/     ← was omnivoice/
│   └── tts-adapters/        ← new; the pluggable TTS interface from ROADMAP phase 3
├── config/
│   ├── docker/
│   └── pyinstaller/
├── tests/
└── docs/

Do not execute this migration without a dedicated PR. It breaks:

  • pyproject.toml [tool.hatch.build.targets.{sdist,wheel}] paths
  • package.json workspaces and scripts
  • turbo.json, Dockerfile, docker-compose.yml paths
  • backend.spec (['backend/main.py'], pathex=['.'])
  • frontend/src-tauri/tauri.*.conf.json sidecar paths
  • every import that reads from backend.main import … (tests, scripts)

Migrate when adding the second apps/* or the second packages/*. Not before.

Conventions

  • Filenames: snake_case for Python, kebab-case or PascalCase for JS/TS components, lowercase for Markdown.
  • Tests mirror source paths. backend/services/dub_pipeline.py → tests/services/test_dub_pipeline.py.
  • One-off scripts go into scripts/ with a descriptive name, not test_*.py at the root.
  • New top-level directories require a PR that updates this file.