Project Structure
A summary of how HTML templates, CSS, and JavaScript interact in this project.
Overview
This is a Flask application with a single-page frontend. The server renders one HTML page via Jinja2 templates, loads all CSS globally, and boots a single ES module entry point. All subsequent UI interaction is handled in JavaScript without page reloads.
File Map
templates/ Jinja2 HTML templates
├── index.html Root page — loads CSS, loads main.js
├── sidebar.html Sidebar partial
├── workspace.html Workspace partial
├── start_page.html Empty-state page partial
├── components/
│ ├── server_panel.html Server connection UI
│ └── shortcut_legend.html Keyboard shortcut reference
├── workspace_panels/
│ ├── waveform_panel.html Audio player and waveform
│ ├── speakers_panel.html Speaker list and management
│ └── transcript_panel.html Transcript editor
└── documentation/
└── guide/ User guide pages (standalone)
static/css/
├── variables.css Global CSS custom properties (colors, dark + light theme)
├── main.css Base/global styles
├── sidebar.css Sidebar layout (includes folder breadcrumb styles)
├── start_page.css Empty-state page styles
├── components/
│ ├── confirm_dialog.css
│ ├── export_panel.css
│ ├── hue_picker.css
│ ├── info_widget.css
│ ├── segment_context_menu.css
│ ├── server_panel.css
│ ├── shortcut_legend.css
│ ├── split_popup.css
│ └── transcribe_dialog.css
├── workspace/
│ ├── workspace.css
│ ├── waveform_panel.css
│ ├── speakers_panel.css
│ └── transcript_panel.css
└── documentation/
└── guide.css
static/js/
├── main.js Entry point — App class, sidebar, project list, folder navigation
├── project.js Project, ProjectData, Transcript, Speaker classes
├── workspace.js Workspace — coordinates the three panels
├── server.js Server class — connection state, auth, and all server operations
├── start_page.js StartPage class
├── workspace_panels/
│ ├── waveform_panel.js WaveformPanel — audio playback, waveform, regions
│ ├── speakers_panel.js SpeakersPanel — speaker list and editing
│ └── transcript_panel.js TranscriptPanel — transcript display and editing
├── components/
│ ├── server_panel.js ServerPanel — login/logout UI logic
│ ├── segment_context_menu.js SegmentContextMenu — right-click menu on segments
│ ├── project_context_menu.js ProjectContextMenu — right-click menu on projects
│ ├── split_popup.js SplitPopup — word-level segment splitting
│ ├── confirm_dialog.js ConfirmDialog — modal confirmation
│ ├── export_panel.js ExportPanel — export format selection and download
│ ├── hue_picker.js HuePicker — speaker color selection
│ ├── info_widget.js InfoWidget — toast/notification display
│ └── transcribe_dialog.js TranscribeDialog — transcription options and progress
└── utilities/
├── tools.js General utility functions
├── audio.js Audio loading helpers
├── constants.js Shared constants (GAP_THRESHOLD, SPEAKER_COLORS, etc.)
├── export.js Export formatting helpers (PDF, DOCX, TXT, CSV, MD)
├── theme.js Theme switching (dark/light/auto) helpers
├── transcription_pricing.js Cloud transcription cost estimation
└── server_access.js Low-level fetch functions (raw API calls, no state)
application/ Flask server
├── __init__.py App factory
├── projects.py Project CRUD logic
├── auth.py Authentication
├── files.py Filesystem helpers
├── folders.py Folder hierarchy management
├── config.py Server configuration
├── stats.py Usage statistics tracking
├── tools.py Server-side utility functions (multipart upload)
└── transcription/
├── transcribe.py Local transcription (CPU, faster-whisper + pyannote)
└── modal_transcribe.py Cloud transcription (Modal AI, serverless GPU)
app.py Flask application and route definitions
serve.py Production WSGI launcher (Gunicorn/Waitress)
run.sh / run.bat Convenience startup scripts
Caddyfile Caddy reverse-proxy configuration
How Templates, CSS, and JS Interact
1. Template Composition
index.html is the single root template rendered by Flask. It composes the page using Jinja2 {% include %} tags:
index.html
├── {% include "sidebar.html" %}
│ ├── {% include "components/server_panel.html" %}
│ └── {% include "components/shortcut_legend.html" %}
└── {% include "workspace.html" %}
├── {% include "start_page.html" %}
├── {% include "workspace_panels/waveform_panel.html" %}
├── {% include "workspace_panels/speakers_panel.html" %}
└── {% include "workspace_panels/transcript_panel.html" %}
Templates define static HTML structure and element IDs only. No data is injected into templates at render time (aside from static asset URLs via url_for). All dynamic content is written by JavaScript after the page loads.
2. CSS Loading
All stylesheets are declared as <link> tags in index.html, referenced via Flask's url_for('static', filename=...). There is no CSS bundler or preprocessor.
Load order matters:
variables.css— defines CSS custom properties consumed by everything elsemain.css— base/reset styles- Layout stylesheets (
sidebar.css,start_page.css) - Component stylesheets (
components/*.css) - Workspace panel stylesheets (
workspace/*.css)
CSS files are scoped by directory: global styles live at the top level, component styles under components/, panel styles under workspace/.
3. JavaScript as ES Modules
index.html loads a single script:
<script src="{{ url_for('static', filename='js/main.js') }}" type="module"></script>
main.js imports all other JS files using native ES module import syntax. There is no bundler (Webpack, Vite, etc.) — the browser resolves modules directly from the filesystem via Flask's static file serving.
4. JS ↔ Template Binding
JavaScript classes bind to the pre-rendered DOM by querying element IDs defined in the templates:
// Example from main.js / Workspace
this.sidebar = document.querySelector("#sidebar");
this.sidebarList = document.querySelector("#sidebarList");
The templates establish the DOM contract (IDs, element types). The JS classes assume those IDs exist and will fail silently or throw if a template changes an ID without updating the corresponding JS.
5. Dynamic HTML Generation
Not all HTML comes from templates. Some elements are created entirely in JavaScript:
- Sidebar project items — built by
App.#buildProjectItem()inmain.js - Context menus —
ProjectContextMenu,SegmentContextMenucreate their own DOM trees - Confirm dialogs —
ConfirmDialogappends a modal todocument.body - Status badges —
Workspace.updateProjectServerStatus()writes badge elements into#projectServerStatus
These dynamic elements inherit styles from the globally-loaded CSS via shared class names (e.g., .ctx-item, .sidebar-item, .project-cloud-badge).
6. Cross-Panel Communication
The three workspace panels (WaveformPanel, TranscriptPanel, SpeakersPanel) do not reference each other directly. Workspace owns all three and wires them together at construction time via callbacks:
TranscriptPanel --onSegmentHover--> Workspace --calls--> WaveformPanel.setHoveredRegion()
WaveformPanel --onRegionSelect--> Workspace --calls--> TranscriptPanel.setSelectedSegment()
SpeakersPanel --onSpeakerHover--> Workspace --calls--> WaveformPanel.drawRegions()
This keeps panels decoupled: each panel exposes a callback API; Workspace provides the implementations.
7. State Management
State is split across two layers:
| Layer | Location | Contents |
|---|---|---|
| Server/connection | Server (server.js) | Server connection, auth token, connection status |
| Per-project | Project instances | Transcript, speakers, waveform data, dirty flags |
Project tracks dirty state per data category (transcriptDirty, speakersDirty, waveformDirty). When a category is marked dirty, the project fires registered callbacks that trigger UI re-renders in Workspace. This is the primary mechanism for keeping the three panels in sync after an edit.
8. Server Communication
Server communication is handled by two layers:
Serverclass (server.js) — stateful class owned byApp. Manages connection URL, auth token, and connection lifecycle. Exposes all server operations as methods. Fires callbacks (onStatusChanged,onConnect,onDisconnect) when connection state changes.server_access.js(utilities) — stateless module of rawfetch()wrappers. Called byServerwith the current base URL and token. All functions areasyncand throw on non-2xx responses.
The Flask server (application/) handles:
/— serves the main application page/docs,/docs/developer/— developer documentation/auth/login,/auth/logout— authentication/api/projects— list all projects (flat, withfolder_path); create project/api/projects/<id>— get, update, delete a project/api/projects/<id>/duplicate— duplicate a project/api/projects/<id>/audio— audio streaming (Range header support)/api/projects/<id>/audio-ready— check if background MP3 conversion is complete/api/projects/<id>/transcript— CSV transcript download / delete/api/projects/<id>/speakers— speaker metadata/api/projects/<id>/samples/<spkId>— voice sample files/api/projects/<id>/waveform— waveform data/api/projects/<id>/transcribe— SSE stream of transcription progress/api/projects/<id>/folder— move project to a different folder/api/folders— list folders and projects at a given path/api/folders/<path>— create, rename, or delete a folder/api/folders/<path>/count— count nested projects/folders/api/stats— usage statistics (logins, request counts)