Multi-file Projects¶
Most real apps don't fit in one file. Whittl handles multi-file projects as a first-class case: the AI generates them with explicit file boundaries, the editor shows each file in its own tab, and smart routing keeps token costs bounded as your project grows.
How the AI marks multiple files¶
When the AI generates a multi-file project, it emits explicit file boundaries using a ### FILE: marker:
FILE: ui/window.py¶
FILE: managers/database.py¶
Whittl's parser scans the streamed output for these markers and routes each block to the corresponding tab in the preview panel. Tabs appear as they're written — the first file auto-focuses, then releases so you can click around while the rest finishes.
### Tab appearance during streaming
As the AI writes multi-file output:
1. First `### FILE:` marker hits → Whittl creates a tab named `main.py` and starts filling it
2. Second `### FILE:` marker → second tab appears, first tab stops streaming
3. Third, fourth, etc. → new tabs appear in order
4. Final `###` / end of stream → all tabs stop streaming
You can watch the AI work across all files in real time instead of waiting for everything to appear at once.
!!! tip "Duplicate-marker regression protection"
Some models (grok-4.1-fast notably) occasionally emit a stray second `### FILE: main.py` marker for a file they've already finished. Whittl's parser detects this and keeps the larger valid block rather than letting a 125-char orphan fragment overwrite a 5,900-char real file. Fixed in v2.3. If you were hit by IndentationErrors on older versions, upgrade.
## Creating a multi-file project
There's no special "create multi-file project" step. Just ask the AI for something modular:
Whittl generates the project with that structure. The AI picks the import paths, file names, and module boundaries based on your prompt and on [skills](skills.md) about Python project layout.
You can also **convert** an existing single-file project to multi-file by prompting:
That's a structural refactor — Whittl's planner recognizes it as such and uses full regeneration rather than surgical edits (surgical editing doesn't handle "reorganize everything" gracefully).
Smart file routing¶
Large projects (8+ files, or 2+ if you enable the "Smart Routing for All Projects" toggle) use smart routing automatically. Instead of sending all files to the AI on every request, Whittl:
- Pre-analyzes your request with a fast keyword + AI-assisted scan
- Identifies which files are relevant to what you're asking
- Sends only those files in the system prompt
- Protects imports — warns the AI which symbols are exported from the files you didn't send so it doesn't rename them
The status bar shows routing status: Smart routing: 3 of 28 files means 3 files were selected and 25 were excluded for that request.
Why this matters for cost¶
A 30-file project that would otherwise send ~100K tokens per request now sends ~10-15K. That's 4-15× fewer tokens per modification. On Claude Sonnet, that's the difference between $0.30 and $0.03 per edit.
When routing gets it wrong¶
Smart routing is a heuristic. Occasionally it misses a file that's actually needed. You'll see a symptom like:
- The AI modifies
views/login.pybut references a signal that lives inviews/main.pywhich wasn't in the prompt - The modification fails on run because of the missing context
Workarounds:
- Edit → Send Active File Only (max control — just the currently selected tab goes)
- Edit → Send All Files (send everything — slow + expensive, but works)
- Be more specific in your prompt — mention the specific files to include ("also include
views/main.py")
Per-file generation on Test Run¶
When you click Test Run on a multi-file project, Whittl:
- Writes every file in the project to the project folder (preserving directory structure —
ui/main.pygoes toui/main.py, notui_main.py) - Runs
main.pyfrom the project directory so relative imports work - Installs any pip dependencies detected from imports
Don't touch the data/ folder
If your project has a data/ subfolder, Whittl preserves it across generations and doesn't let the AI overwrite files there. This is protection for user-generated runtime data (settings, saved state, databases). If you need the AI to modify something, move it out of data/.
Imports across files¶
The AI is instructed to use explicit relative or package-rooted imports:
- ✅
from ui.window import MainWindow - ✅
from .window import MainWindow(relative) - ❌
from window import MainWindow(ambiguous, fails when run from elsewhere)
Whittl's auto-fix catches most bad imports before they hit disk, but the AI occasionally invents circular imports or forgets to add __init__.py to a new subfolder. Both get flagged on the first Test Run.
Local module protection¶
When the AI writes import database and there's a database.py in the project folder, Whittl recognizes it as a LOCAL import and doesn't try to pip-install the database package from PyPI. This prevents a whole class of "pip installed some random package that shadows my local module" bugs.
The list of folders Whittl treats as always-local: core/, ui/, views/, models/, utils/, helpers/, data/, assets/, handlers/, services/, managers/, api/, lib/, components/, widgets/. If your project uses a non-standard folder name, add it explicitly to project.json or just rename the folder.
Multi-file search (Ctrl+Shift+F)¶
Press Ctrl+Shift+F to open the multi-file search dialog. Search across all files in the current project — useful for:
- "Where is this signal emitted?"
- "Find every file that imports QTimer"
- "What files mention the old color token?"
Results show with line numbers and a file badge. Click a result to jump to that line.
File tabs with line counts¶
Each tab shows the filename and live line count: ui/main.py (147). The count updates on save. A colored dot (●) indicates unsaved changes (red = unsaved, green = saved).
Right-click a tab for:
- Rename — rename the file on disk (Whittl updates imports too, so
from ui.main import Xstill works aftermain.pybecomesmain_window.py) - Close Tab — removes from the open tabs but keeps the file on disk
- Delete File — removes the file from disk entirely (irreversible)
The files dropdown (☰)¶
The small hamburger button next to the tab bar opens a dropdown showing all files in the project — both open tabs and closed ones. Click a closed file to reopen it.
Particularly useful for projects with 10+ files where the tab bar scrolls horizontally.
Streaming indicator on tabs¶
During a multi-file generation, the active tab (the one currently being written) shows a subtle "Streaming..." indicator. Coming in v2.4 as part of the UX polish pass.
What's next¶
- Smart Routing — already covered above; Iterating on Generated Code has the follow-up-prompt pattern for multi-file edits
- Project Folder Structure — reference for what every file in a project does
- Auto-fix Rules — how Whittl handles imports, circular deps, and missing modules in generated multi-file code