Blurzy Documentation Style Guide
Overview
This document defines the modern documentation styling used throughout Blurzy, with emphasis on consistency, clarity, and developer experience. The codebase is transitioning to this standard incrementally.
API reference pages are generated from Python signatures and docstrings.
For beginner-friendly Architecture pages, diagram formats, asset locations,
and reproducible examples, follow Writing architecture guides.
Maintain API explanations in source and cross-component design notes in
docs/architecture/; see Reference authoring.
The Reference documentation article explains
generation, member selection, fenced examples, and contributor guidelines.
The renderer uses Google-style sections and MkDocs Markdown. Public API
docstrings should explain architectural ownership, behavior, side effects,
and contracts rather than merely repeat signatures. Use single backticks
for inline code and mkdocstrings cross-reference syntax for linked symbols.
Existing docstrings can be modernized incrementally; automation does not
verify their behavioral claims.
Phase 1 Engineering Conventions (Handlers / Services / Workers)
0. Responsibilities and Signal Ownership
- Handlers (
src/app/ui/handlers) - Own Qt slot orchestration and UI state transitions.
- Connect to worker signals and always handle success/failure/cancel cleanup.
- Never implement core business logic that belongs in services.
- Services (
src/app/application/services) - Own domain/application decisions and validation.
- May create/configure workers, but should stay UI-agnostic.
- Workers (
src/app/infrastructure/*worker.py) - Run heavy/background work only.
- Emit progress + completion/error signals; never access Qt widgets directly.
1. Progress Signal Pattern
For long operations (detect / track / export), expose:
progress_updated(current: int, total: int)when granular progress is availablefinished_processing()infinallyerror_occurred(msg: str)on failure- optional
cancelled()for user-initiated cancellation flows
UI handlers must:
- Set progress state visible/busy at start
- Update progress in slot callbacks
- Reset/hide progress on all terminal paths (success, error, cancellation)
2. Logging and Exception Pattern
- Log once at service/worker boundary with context (
session,operation). - Use
logger.opt(exception=exc)(or equivalent) for stack traces. - Show user-facing dialog errors in handlers, not in services/workers.
- Avoid swallowing exceptions silently; convert to signal/error message.
3. TODO/FIXME Policy
TODO: planned work with clear intent and target area.FIXME: known defect or incomplete behavior affecting correctness/UX.- Keep each TODO/FIXME actionable and short.
- Remove TODO/FIXME as soon as implemented; do not leave stale comments.
4. Docstring Standard
This is the project-wide standard for new and revised docstrings. Apply it incrementally; an existing file is not a model merely because its API page builds.
The documentation stack
MkDocs builds the site. mkdocstrings-python/Griffe statically collects annotations and parses Google-style API sections. Markdown extensions process embedded markup, and Material for MkDocs supplies the theme and interactive presentation. Material is not a replacement docstring parser.
Consult the Material reference
and mkdocs.yml together: features on the upstream website are not all enabled
here, and examples must work with the versions pinned in requirements-docs.txt.
Documentation builds must not import Qt/model providers or run inference.
Content and contracts
- Give public modules, classes, methods, and functions useful documentation.
Begin with a concrete purpose or behavior summary, not an
Attributes:block. Use imperative summaries for operations; describe module/class responsibilities. - Treat signature annotations as the source of truth for types. Mkdocstrings
infers parameter/return types;
frame_index: Zero-based frame index.is valid. Repeat a type only when needed, keeping it consistent with the annotation. - Use
Args:,Returns:,Yields:,Raises:,Attributes:, andExamples:when relevant, not as mandatory boilerplate. Do not documentself, trivialNonereturns, or the same constructor arguments on both class and constructor. Prefer constructor arguments on__init__so its rendered entry is useful. - Explain units, coordinate conventions, valid choices, and
None/empty meanings. Distinguish caller preconditions from validation the implementation performs. - Document copying versus live aliases, ownership, side effects, lock scope, worker signals/cancellation, and partial updates on failure where material. An internal lock does not make a returned mutable object safe after release.
- In
Raises:, name actionable exception types and their conditions verified in code. Describe provider/callback exception propagation in prose when no fixed type is promised. Do not invent exhaustive lists or guarantees of rollback. - Describe what code actually does. Avoid speculative prose, stale method names, unsupported complexity claims, and promises of immutability or fresh results without checking the implementation.
- Link related APIs with qualified mkdocstrings references, such as
[`VideoDataLayer`][app.domain.video.layer.VideoDataLayer]. Describe public contracts rather than inventorying private attributes. Keep cross-component architecture explanations indocs/architecture.
Sections, examples, and callouts
Keep the opening summary, closing quotes, and top-level prose at the same Python indentation. Indent Google section entries and Material block bodies an additional four spaces. Use blank lines to separate paragraphs, sections, and fenced blocks. Do not put article YAML front matter in a Python docstring.
Examples belong in Examples: with fenced, language-labelled code. Include
imports/setup or explicitly state prerequisites; mark illustrative snippets that
need media, a Qt application, hardware, or model weights. Builds do not execute
examples, so a successful render is not evidence that an example works.
def has_frame(self, frame_index: int) -> bool:
"""Check whether a frame entry exists, including an intentionally empty one.
Args:
frame_index: Zero-based frame index to look up.
Returns:
`True` for an initialized entry, even when it contains no boxes.
Examples:
Given an initialized `store`:
```python
initialized = store.has_frame(0)
```
"""
...
This is a template, not a method to call on SessionDataStore. Compare the vague
alternative """Instantly gets frame data; fully thread-safe.""": it supplies
neither the return contract nor evidence for its speed/threading claims.
Use callouts for important context, not decoration. The configured
admonition, details, and SuperFences extensions
support !!! warning "Title" for visible content, ??? info "Title" for
optional collapsed detail, and ???+ info "Title" for initially expanded detail.
Quote custom titles and use supported types such as note, warning, info,
or example, rather than inventing Google section names like Design:.
Keep safety, mutation, and ownership warnings visible, not hidden in details.
def boxes(self) -> list[Box]:
"""Return the stored list, not a snapshot.
Returns:
The live list owned by this object.
!!! warning "Shared mutable data"
The read lock is released before return. Coordinate subsequent access
with the owner's locking contract; do not mutate this list unprotected.
"""
...
Material authoring features
Use these features in articles or docstrings only when they improve the
explanation. Do not add every feature to every API. Article links are
page-relative; generated API assets use the existing api-asset: convention
described in Reference documentation.
| Feature | Configured support | Authoring rule |
|---|---|---|
| Code blocks | Highlight, InlineHilite, SuperFences, copy button | Label the language; optional titles/highlighting must clarify the example. |
| Content tabs | pymdownx.tabbed, alternate style |
Group genuine alternatives, not sequential required steps. |
| Images/captions | attr_list, md_in_html |
Supply useful alt text; use figure/figcaption for captions. |
| Tables | tables |
Compare structured facts; avoid oversized prose tables. |
| Code annotations | Per-block .annotate with attr_list |
Opt in only where numbered explanations help; global annotations are disabled. |
| Diagrams | Themed SVG pairs rendered from D2 sources (no Mermaid in architecture pages) | Verify readable equivalent content in both themes. |
For example, these tabs show alternative headless-test commands. Use the syntax of the current shell:
$env:QT_QPA_PLATFORM = "offscreen"
python -m pytest -q
QT_QPA_PLATFORM=offscreen python -m pytest -q
This caption uses Markdown in HTML, not an unconfigured caption extension:
Opt-in annotations use a language class first and an explanation after the fence:
from app.domain.video.layer import VideoDataLayer
editable_detections = VideoDataLayer.B # (1)
- Layer B contains reviewed/editable detections, distinct from the original output in A.
Do not impose a light-only palette: provide equivalent #only-light and
#only-dark images. Preserve editable diagram sources. See
Writing architecture guides for asset rules.
Content tabs are not globally linked, and annotations are not globally enabled;
do not assume optional features such as snippets or a lightbox are available.
Author and reviewer checklist
- Do descriptions agree with the implementation, including empty and failure paths?
- Are units, ownership/aliasing, mutation, lock scope, and partial failures clear?
- Do section names, annotations, references, and examples identify real APIs?
- Are required warnings visible and optional details genuinely optional?
- Do sections, fences, callouts, tabs, captions, and annotations render correctly?
- Are diagrams and text readable in both themes, without relying on color alone?
Run python -m mkdocs build --strict for documentation/source-docstring changes.
For configuration or documentation-tooling changes, also run
python -m unittest discover -s tests -p "test_docs_reference.py".
Inspect the rendered pages and interactive features in both palettes when
browser access is available; builds and HTML checks alone cannot prove contrast,
Mermaid execution, or interaction. Report any unavailable visual checks.
1. One-Liner Docstrings
Rule: Simple, Direct Purpose
Every public method needs a useful docstring. A concise summary is sufficient only when the method's behavior, side effects, and contract are obvious. Methods with parameters, meaningful return values, state changes, locking, or failure modes should document those details in Google-style sections.
Format
def method_name(self) -> ReturnType:
"""Clear description of what this method does."""
...
Variable Formatting
Use backticks (`variable`) for:
- Parameter names
- Variable names
- Return values
- Layer/class names
Use formatting directives for emphasis:
- **bold text** — for critical concepts (e.g., **ALWAYS**, **DO NOT**)
- *italic text* — for gentle emphasis
- >> or → — for flow/direction
Examples
GOOD:
``python
def get_layer_by_name(self, layer_name: DataLayer) -> BoxesByFrameIndex:
"""Fetch bounding boxes grouped by frame index for the specifiedlayer_name`."""
return self.data.get_all_boxes_for_layer_as_dict_of_lists(layer_name)
def has_boxes_for_layer(self, layer_name: DataLayer) -> bool:
"""Check whether layer_name contains any bounding boxes."""
return self.data.has_boxes_for_layer(layer_name)
```
BAD:
def get_layer_by_name(self, layer_name):
"""Get layer""" # Vague, no variable formatting
...
def has_boxes_for_layer(self, layer_name):
"""Returns true or false if boxes exist""" # Awkward grammar, no formatting
...
2. Flow Documentation with Section Blocks
Rule: Structured Step-by-Step Comments
For methods with multiple logical steps, use section dividers to organize flow. This pattern is used consistently in session.py and service.py.
Format
def complex_operation(self):
"""High-level summary of what this method does."""
# ============================================================================
# 1. STEP NAME — Brief description
# ============================================================================
# Additional context or rationale if needed
step_1_result = do_something()
# ============================================================================
# 2. NEXT STEP NAME — What happens here
# ============================================================================
if step_1_result is None:
logger.warning("Step 1 failed, proceeding with fallback")
# ============================================================================
# 3. FINAL STEP — Wrap up
# ============================================================================
return process_result(step_1_result)
Rules for Step Blocks
- Use ALL CAPS for step names:
# 1. FETCH SESSION INSTANCE - Precede with
# =============...(60+=chars) - Number steps sequentially:
# 1.,# 2., etc. - Add brief explanation after the dash:
# 1. STEP NAME — What it does - Include context comments above if needed — especially for non-obvious logic
- One blank line before and after the section block
Real Examples from Codebase
From session.py (lines 197–243):
def get_frame_by_index(self, frame_index: int) -> RGBFrame | None:
"""Fetches a specific frame directly from the O(1) Ring Buffer, or triggers a seek."""
# ====================================
# 1. Check if frame at index is cached
# ====================================
cached_frame = decode_worker.get_cached_frame_at_index(safe_idx)
if cached_frame is not None:
session_state.playback.current_frame_index = safe_idx
return cached_frame
# ====================================
# 2. Cache Miss
# ====================================
is_sequential_underrun = abs(safe_idx - session_state.playback.current_frame_index) <= 1
if not is_sequential_underrun:
decode_worker.request_seek(safe_idx)
# ====================================
# 3. Wait for worker to catch up
# ====================================
attempts = 0
while attempts < 200: # 2.0 second timeout
cached_frame = decode_worker.get_cached_frame_at_index(safe_idx)
if cached_frame is not None:
return cached_frame
time.sleep(0.01)
attempts += 1
From service.py (lines 57–117):
def detect_current_frame(self, s_id: SessionId) -> None:
"""
Detect objects in the current frame of the ``Session`` specified by the ``SessionId``
using the currently set detection model. If no model is set, skip detection and
clear any existing detection layers.
"""
# ============================================================================
# 1. FETCH SESSION INSTANCE
# ============================================================================
session = self._app.get_session_by_id(s_id)
# ============================================================================
# 2. CHECK MODEL NAME
# ============================================================================
if session.state.session_settings.model_name_is_null:
logger.info("Detect current frame skipped: model is None")
return
# ... (continues with steps 3–6)
3. Inline Comments for Non-Obvious Logic
Rule: Explain Why, Not What
The code shows WHAT happens; comments explain WHY.
Format
# [NOTE] Explanation of design decision or gotcha
# This bypass directly accesses the ring buffer through a new method
cached_frame = decode_worker.get_cached_frame_at_index(safe_idx)
# [FIXME] Known issue or debt item
# Currently, the progress is merely logged, wire it into a progress indicator within the UI
logger.info("Detection worker processed {}/{} frames.", processed_frames, total_frames)
# [TODO] Future work or enhancement
# Extract the timeout-loop out of `Session` and move it into `VideoDecodeWorker`
Tags
Use these prefixes for special comments:
| Tag | Usage | Example |
|---|---|---|
[NOTE] |
Design decision, architectural rationale | # [NOTE] Even though we start the worker here, it will only be active when session becomes active |
[FIXME] |
Known bug or incomplete feature | # [FIXME] Currently, progress is merely logged, wire it into a progress indicator |
[TODO] |
Future enhancement or refactoring | # [TODO] Extract timeout-loop into VideoDecodeWorker |
[NEW] |
Recently added or changed behavior | # [NEW] Use the new method to bypass direct buffer access |
[DEPRECATED] |
Old code to be removed | # [DEPRECATED] This approach is superseded by the streaming API |
3.1 IMPORTANT: Critical Warnings Block
For critical warnings about thread safety, memory management, or other gotchas that MUST be understood before modifying code, use the boxed IMPORTANT template:
# ┏━ IMPORTANT ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#
# The worker is parentless (required by moveToThread()), so
# Qt will NOT automatically delete it with the main class.
# Explicit, synchronized teardown is required to prevent
# memory leaks and thread crashes.
#
# REQUIRED SHUTDOWN ORDER
#
# 1. Stop Signal the worker to finish its current loop.
# 2. Quit Tell the QThread event loop to stop.
# 3. Wait Wait for the thread to finish.
# 4. Delete Safely release the parentless worker.
#
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Use IMPORTANT blocks for:
- Thread safety constraints
- Memory management gotchas
- Object lifetime issues
- Strict ordering requirements
- Cross-layer architectural rules
Real example from model_handler.py:
@Slot()
def _cleanup(self) -> None:
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ IMPORTANT ┃
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
# ┃ Do NOT call deleteLater() on active threads or workers. ┃
# ┃ Must call quit() → wait() before deleting memory. ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# 1. Stop and join the thread
if self._model_load_thread is not None and self._model_load_thread.isRunning():
self._model_load_thread.quit()
self._model_load_thread.wait()
# 2. Schedule worker deletion
if self._model_load_worker is not None:
self._model_load_worker.deleteLater()
4. Multi-Line Docstrings (For Complex Methods)
Rule: When a One-Liner Isn't Enough
Use triple-quoted docstrings for methods that need more explanation (especially in infrastructure/application layers).
Format (Google Style)
def complex_method(self, param1: str, param2: int) -> dict:
"""
Brief one-line summary.
Longer explanation of the method's behavior, including:
- Key preconditions
- Side effects
- Unusual return values
Args:
param1: Description of the input value.
param2: Another parameter description.
Returns:
A dictionary mapping frame indices to detection results.
Raises:
ValueError: If `param2` is negative.
"""
Example from Infrastructure
def start_detection_worker_for_session_id(self, s_id: SessionId) -> None:
"""
Starts a background ``DetectionWorker`` for the session specified by ``s_id``.
This method initializes the detection engine, creates a worker thread,
and begins processing frames in the background. **DO NOT** call this
if a worker is already running; check ``session.has_running_detection_worker`` first.
Args:
``s_id``: The ``SessionId`` identifying the target session.
Raises:
``WorkerAlreadyRunningException``: If a detection worker is already active.
``NullModelNameException``: If no detection model has been selected.
"""
5. Class-Level Documentation
Rule: Describe Purpose and Responsibilities
Every class should have a docstring explaining its role in the architecture.
Format
@final
class Session(QObject):
"""
Manages the view_state and lifecycle of a single video processing session.
Responsibilities:
- Holds ``SessionState`` (playback, settings, annotations)
- Manages ``VideoDecodeWorker`` for frame buffering
- Provides frame access methods (``get_current_frame``, ``get_frame_by_index``)
- Manages detection layer CRUD operations
- Coordinates with detection and tracking workers
"""
Example: Full Class Documentation
@final
class DetectionService:
"""
Orchestrates model-based object detection across sessions.
Responsibilities:
- Create and manage ``DetectionEngine`` instances per session
- Start background ``DetectionWorker`` threads
- Route detection batches back to session data stores
- Handle model switching and layer clearing
Design Notes:
- Although ``Session`` holds the ``DetectionEngine``, this service
creates and manages it to keep ``Session`` decoupled from detection logic.
"""
6. Documenting Signals and Slots in PySide6
Rule: Trace the Signal Cascade
Qt applications use signals and slots to communicate between objects. Documentation must clearly show: 1. Where the signal originates (top-level event or emit) 2. Which slot receives it (and where it lives) 3. What signal it emits next (cascade/chain) 4. The overall flow from user action to final state change
6.1 Top-Level Slots (Entry Points)
Info
Top-level slots are triggered by built-in Qt events (button clicks, menu actions, etc.) and usually emit signals that cascade downstream. These should include complete signal flow documentation.
Format
from PySide6.QtCore import pyqtSignal, Slot
class MyWidget(QWidget):
# ════════════════════════════════════════════════════════════════════════
# SIGNALS: Emitted when actions complete
# ════════════════════════════════════════════════════════════════════════
action_completed = pyqtSignal(str) # Emits result
@Slot()
def on_button_clicked(self) -> None:
"""
Handle button click and trigger downstream signal cascade.
**Signal Cascade:**
```
QButton.clicked (Qt built-in event)
└──> MyWidget.on_button_clicked() [this slot]
└──> Perform action
└──> Emit MyWidget.action_completed(result)
└──> Connected slot in caller (e.g., Controller.on_action_completed)
```
Signal Flow: ``QButton.clicked`` → ``on_button_clicked()``
→ ``action_completed`` → downstream handlers
"""
result = self._do_work()
self.action_completed.emit(result)
Real Example from Codebase
class TopRow(QWidget):
# ════════════════════════════════════════════════════════════════════════
# SIGNALS
# ════════════════════════════════════════════════════════════════════════
open_videos_requested = pyqtSignal(list) # Emits list[str] (file paths)
@Slot()
def choose_video_files(self) -> None:
"""
Open file browser and request video files to be opened.
**Signal Cascade (Complete Flow):**
```
TopRow.choose_button.clicked (Qt built-in)
└──> TopRow.choose_video_files() [this slot — entry point]
├── Launch QFileDialog.getOpenFileNames()
└──> Emit TopRow.open_videos_requested(paths: list[str])
└──> SessionHandler.on_open_videos(paths: list[str])
└──> SessionHandler processes and opens sessions
└──> Calls App.open_videos(paths)
```
Signal Flow: ``choose_button.clicked`` → ``choose_video_files()``
→ ``open_videos_requested`` → ``SessionHandler.on_open_videos()``
"""
paths, _ = QFileDialog.getOpenFileNames(
self._parent,
"Open Video Files",
"",
"Video Files (*.mp4 *.avi *.mov *.mkv *.wmv *.m4v);;All Files (*)",
)
if paths:
self.open_videos_requested.emit(paths)
6.2 Mid-Level Slots (Intermediate Handlers)
Mid-level slots receive signals from upstream and emit signals downstream. They should document: - Which signal triggered this slot (the "input") - Brief description of what it does - Which signal it emits next (the "output")
Format
@Slot(list)
def on_open_videos(self, paths: list[str]) -> None:
"""
Receive file paths and initiate session creation.
**Triggered By:**
``UIController.open_videos_requested``
**Flow:**
```
on_open_videos(paths) [this slot]
├── Validate paths
└──> Emit/Call App.open_videos(paths)
└──> SessionManager.create_session_from_video_path()
```
**Downstream:** Calls ``App.open_videos()`` which cascades to ``SessionManager``
"""
# ====================================================================
# 1. VALIDATE PATHS
# ====================================================================
if not paths:
logger.warning("No files selected")
return
# ====================================================================
# 2. OPEN VIDEOS IN APP
# ====================================================================
self._app.open_videos(paths)
6.3 Low-Level Slots (Leaf Nodes)
Low-level slots typically do not emit signals; they perform final actions (update UI, log, etc.). They should document: - Which signal triggered this slot - What it does - No downstream signals (or very few)
Format
@Slot(SessionId)
def on_session_created(self, s_id: SessionId) -> None:
"""
Update UI to reflect newly created session.
**Triggered By:**
``SessionManager.session_created``
**Action:** Refresh the sessions list in the UI and make the new session active.
**Downstream:** None (leaf node — updates UI view_state only)
"""
# ====================================================================
# 1. UPDATE UI
# ====================================================================
self._refresh_sessions_list()
self._set_active_session(s_id)
# ====================================================================
# 2. LOG
# ====================================================================
logger.info("Session {} created and set active", s_id)
6.4 Signal Declaration with Documentation
Every signal should have a docstring explaining what it emits and when:
class SessionManager(QObject):
# ════════════════════════════════════════════════════════════════════════
# SIGNALS
# ════════════════════════════════════════════════════════════════════════
session_created = pyqtSignal(SessionId)
"""
Emitted when a new ``Session`` is successfully created.
**Emitted by:** ``SessionManager.create_session_from_video_path()``
**Payload:** ``SessionId`` — the ID of the newly created session
**Downstream Handlers:**
- ``UIController.on_session_created()``
- ``App.handle_active_session_changed()``
"""
session_closed = pyqtSignal(SessionId)
"""
Emitted when a ``Session`` is closed and cleaned up.
**Emitted by:** ``SessionManager.close_session()``
**Payload:** ``SessionId`` — the ID of the closed session
**Downstream Handlers:** ``UIController.on_session_closed()``
"""
detection_completed = pyqtSignal(SessionId, dict)
"""
Emitted when background detection finishes for a frame.
**Emitted by:** ``DetectionService.on_detection_batch_ready()`` (via ``DetectionWorker``)
**Payload:** ``(SessionId, dict)`` — session ID and detection results {frame_index: [detections]}
**Downstream Handlers:** ``AnnotationHandler.on_detection_completed()``
"""
6.5 Complete Signal Chain Example
Here's a complete, documented signal chain showing the entire flow from user action to state update:
# ════════════════════════════════════════════════════════════════════════════════
# FILE: src/app/ui/handlers/session_handler.py
# ════════════════════════════════════════════════════════════════════════════════
@Slot(list)
def on_open_videos(self, paths: list[str]) -> None:
"""
Receive file paths and create new sessions for each video.
**Signal Cascade (Complete Flow):**
```
TopRow.choose_button.clicked [Qt built-in event]
└──> TopRow.choose_video_files() [top-level slot]
└──> Emit TopRow.open_videos_requested(paths)
└──> SessionHandler.on_open_videos(paths) [this slot — mid-level]
└──> Call App.open_videos(paths)
└──> SessionManager.create_session_from_video_path(path)
└──> Emit SessionManager.session_created(s_id)
└──> UIController.on_session_created(s_id) [leaf slot]
└──> Update UI
```
**Triggered By:**
``TopRow.open_videos_requested`` signal
**Downstream:** Indirectly triggers ``SessionManager.session_created`` → ``UIController.on_session_created()``
"""
# ====================================================================
# 1. VALIDATE PATHS
# ====================================================================
if not paths:
logger.warning("No video files selected")
return
# ====================================================================
# 2. DELEGATE TO APP TO OPEN VIDEOS
# ====================================================================
# [NOTE] App.open_videos() cascades to SessionManager, which emits signals
try:
self._app.open_videos(paths)
except Exception as e:
logger.opt(exception=e).error("Failed to open videos")
self._show_error_dialog("Could not open video files", str(e))
# ════════════════════════════════════════════════════════════════════════════════
# FILE: src/app/ui/uicontroller.py
# ════════════════════════════════════════════════════════════════════════════════
@Slot(SessionId)
def on_session_created(self, s_id: SessionId) -> None:
"""
Handle newly created session and update UI.
**Triggered By:**
``SessionManager.session_created`` signal
**Downstream:** None (leaf node — final UI update)
"""
# ====================================================================
# 1. SET ACTIVE SESSION
# ====================================================================
self.app.active_session_id = s_id
# ====================================================================
# 2. INITIALIZE PLAYBACK
# ====================================================================
self.playback_handler.initialize_playback(s_id)
# ====================================================================
# 3. REFRESH UI
# ====================================================================
self.ui_handler.render_frame(s_id)
logger.info("Session {} is now active", s_id)
6.6 Signal/Slot Documentation Checklist
When documenting signals and slots, verify:
- [ ] Top-level slots include complete signal cascade (from Qt event → all downstream handlers)
- [ ] Mid-level slots document origin signal (which signal triggered this?)
- [ ] Mid-level slots document downstream signals (what signal does this emit?)
- [ ] Low-level slots note they are "leaf nodes" (no further signal cascade)
- [ ] Signal declarations have docstrings explaining payload and downstream handlers
- [ ] Signal flow uses tree/arrow format (
→or├──,└──) for clarity - [ ] Backticks format signal names:
`signal_name`,`SlotName()` - [ ] Origin and destination are explicit (no guessing where signal comes from)
7. File Organization Rule
Every .py file should have this structure, with careful attention to __init__() methods:
"""Summarize purpose, architectural layer, responsibilities, and related APIs."""
from __future__ import annotations
# Standard library imports
import sys
from typing import TYPE_CHECKING, final, override
# Third-party imports
from PySide6.QtCore import QObject, pyqtSignal, Slot
# Local imports
if TYPE_CHECKING:
from app.domain.session import SessionId
from app.infrastructure.session.session_data_store import SessionDataStore
from app.shared.logging_cfg import get_logger
logger = get_logger("ModuleName")
# ============================================================================
# CLASSES
# ============================================================================
@final
class MyClass(QObject):
"""Class docstring with responsibilities and design notes."""
# ════════════════════════════════════════════════════════════════════════
# SIGNALS
# ════════════════════════════════════════════════════════════════════════
action_completed = pyqtSignal(str)
"""Emitted when action completes. Payload: result string."""
def __init__(self):
"""Initialize the class with required dependencies."""
super().__init__()
# [Rest of __init__ follows hierarchical ASCII detection pattern below]
7.1 ASCII Box-Drawing for __init__() — The Session.py Pattern
For complex __init__() methods with multiple attribute initialization sections, use hierarchical ASCII boxes to show grouping:
def __init__(self, s_id: SessionId) -> None:
"""Initialize the session with required workers and view_state."""
super().__init__()
# ┌─ SESSION ID AND DATA STORE ──────────────────────────────────┐
# Core identifiers and data persistence layer
self.s_id = s_id
self.data = SessionDataStore(s_id=self.s_id, parent=self)
# ├── VIDEO READER ──────────────────────────────────────────────────────┐
# Handles video file I/O. Used by DetectionService to read current frame.
# Also extracts metadata for SessionState initialization.
try:
self.video_reader = VideoReader(self.s_id.path)
except Exception as e:
logger.opt(exception=e).error("Failed to initialize VideoReader")
raise e
# ├─ SESSION STATE ──────────────────────────────────────────────────────┐
# Playback view_state, settings, and annotations. Initialized with metadata
# from VideoReader (not directly from VideoReader to maintain Clean Architecture).
self.state = SessionState(self.s_id, self.video_reader.metadata)
# ├─ VIDEO DECODE WORKER ────────────────────────────────────────────────┐
# Background worker that pre-buffers frames using a ring buffer.
# Started here but only truly active when session becomes active.
# [NOTE] Workers should ideally be injected rather than created here.
self.video_decode_worker = VideoDecodeWorker(
self.s_id.path,
self.state.playback,
parent=self
)
self.video_decode_worker.start()
# ├─ DETECTION AND TRACKING WORKERS ─────────────────────────────────────┐
# Lazily initialized — created when user starts detection/tracking.
# [NOTE] DetectionService creates/manages these, not Session directly.
self.detection_engine = None
self.detection_worker = None
self.tracking_worker = None
Benefits of this pattern:
- Visual hierarchy makes dependencies clear
- Comments explain why each attribute exists
- Reader can understand initialization order at a glance
- Easy to spot missing cleanup in close() method
ASCII Box Characters Reference
| Character | Name | Usage |
|---|---|---|
┌ |
Box corner (top-left) | Start of a new attribute group |
├ |
Box junction (left-mid) | Sub-attribute or nested level |
└ |
Box corner (bottom-left) | Last item in a section |
─ |
Box horizontal line | Connects labels |
┐ |
Box corner (top-right) | End delimiter (optional) |
┤ |
Box junction (right-mid) | Right-side connection (optional) |
┘ |
Box corner (bottom-right) | End of section (optional) |
Typical patterns:
# ┌─ FIRST GROUP ──────────────────┐
# Self-contained initialization
# ├── SUB-GROUP ─────────────────────────┐
# Nested or dependent initialization
# ├─ THIRD GROUP ──────────────────────────────────┐
# Another top-level group
# └─ FINAL CLEANUP ────────────────────────────────┐
# Last group or initialization step
8. Checklist for Documentation Compliance
When writing or reviewing code, verify:
- [ ] Every public method has an accurate docstring with behavioral detail
- [ ] Variables in docstrings use backticks:
`variable` - [ ] Complex methods use section blocks with
# ====dividers - [ ] Section blocks are numbered and ALL CAPS:
# 1. STEP NAME - [ ] Inline comments use tags (
[NOTE],[FIXME],[TODO], etc.) - [ ] Critical warnings use IMPORTANT blocks (for thread safety, memory management, etc.)
- [ ] Non-obvious logic has a "why" comment, not just a "what" comment
- [ ] Classes have docstrings describing responsibilities
- [ ] Docstrings use an imperative summary and Google-style sections where useful
- [ ] Public class docs describe responsibilities and public attributes
- [ ] All signals have docstrings (payload, emission point, downstream handlers)
- [ ] Top-level slots include signal cascade diagram
- [ ] Mid-level slots document origin and downstream signals
- [ ] Low-level slots note they are leaf nodes
- [ ]
__init__()methods use ASCII box grouping for multiple attributes - [ ] IMPORTANT blocks use box-drawing characters for critical sections
9. Refactoring with Copilot
Workflow
Step 1: Load the File
Ask Copilot
Analyze this Python file for documentation coverage.
Identify methods without docstrings or with incomplete documentation.
Step 2: Generate Docstrings
Ask Copilot
Generate useful docstrings for these methods using the Blurzy style guide:
- Explain behavior, side effects, and guarantees
- Use Google-style Args, Returns, Raises, and Attributes sections where useful
- Use MkDocs Markdown and single backticks for inline code
Here are the methods: [paste method signatures]
Step 3: Add Flow Sections
Ask Copilot
This method has 5 logical steps. Reformat it with section blocks using the style:
# ============================================================================
# 1. STEP NAME — Description
# ============================================================================
Here's the method: [paste code]
Step 4: Document Signals and Slots
Ask Copilot
This is a PySide6 UI file with signals and slots. Using the style guide,
document all signals and slots including:
- Signal cascade for top-level slots (entry points)
- Origin/downstream signals for mid-level slots
- Signal declarations with payload documentation
Here's the file: [paste code]
Step 5: Review and Iterate
Ask Copilot
Review this documentation against the Blurzy style guide.
Are there any inconsistencies or areas that could be clearer?
Suggest improvements.
10. Quick Reference: Backtick Usage
| Context | Example |
|---|---|
| Parameter | frame_index |
| Variable | current_frame |
| Enum/Layer | DataLayer.A |
| Class | DetectionEngine |
| Method | get_frame_by_index() |
| Constant | MAX_BUFFER_SIZE |
| Return type | Returns a dict of results |
| Signal | `action_completed` signal |
| Slot | `on_button_clicked()` slot |
11. Next Steps
- Review existing files — Compare against this guide
- Update older files incrementally — Start with core infrastructure files
- Document all UI signals/slots — Priority for handlers and controllers
- Use the IMPORTANT block for critical thread/memory warnings
- Use the ASCII box pattern in
__init__()methods for clarity - Use Copilot for bulk refactoring — Ask it to apply the style to entire modules
- Create PR templates — Include documentation checklist
- Add pre-commit hooks (optional) — Check for missing docstrings
Summary
The modern Blurzy documentation style emphasizes:
- !!! success " Clarity — One-liner docstrings with backtick-formatted variables
- !!! success " Structure — Numbered section blocks (
# 1. STEP NAME) for multi-step methods - !!! success " Context — Inline tags (
[NOTE],[FIXME],[TODO]) for design decisions - !!! success " Critical Warnings — Boxed IMPORTANT blocks for thread safety and memory management
- !!! success " Architecture — Class-level docstrings explaining responsibilities
- !!! success " Signal Tracing — Complete signal cascades showing data flow through slots
- !!! success " Initialization Clarity — ASCII boxes in
__init__()showing attribute grouping and dependencies - !!! success " Consistency — Uniform style across all layers (Domain, Application, Infrastructure, UI)
This is aspirational — files like session.py, model_handler.py, service.py, application.py, and session_handler.py demonstrate this standard. Use this guide to bring the rest of the codebase in line incrementally.