diff --git a/docs/contributing.md b/docs/contributing.md
index ad91fe25..327c5533 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -117,7 +117,7 @@ Licensing is now accomplished using the [REUSE](https://reuse.software/spec-3.3/
!!! tip "PR Scope"
If you're unsure where to stop the scope of your PR, ask yourself: _"If I broke this up, could any parts of it still be used by the project in the meantime?"_
-### Workflow Checks
+### :material-check-circle: Workflow Checks
When pushing your code, several automated [workflows](https://github.com/TagStudioDev/TagStudio/tree/main/.github/workflows) will check it against predefined tests and style checks. It's _highly recommended_ that you run these checks locally beforehand to avoid having to fight back-and-forth with the workflow checks inside your pull requests. These checks currently include:
@@ -126,7 +126,7 @@ When pushing your code, several automated [workflows](https://github.com/TagStud
- [Pytest](developing.md#pytest) tests
- REUSE [license compliance](#licenses)
-### Runtime Requirements
+## :material-timer-play: Runtime Requirements
Code must function on all of the supported operating systems and versions:
@@ -134,18 +134,8 @@ Code must function on all of the supported operating systems and versions:
- macOS 14.0+
- Common Linux distributions and versions
-## :material-file-document: Documentation Guidelines
+Final submitted code must **_NOT:_**
-Documentation contributions include anything inside of the `docs/` folder as well as the `README.md`. Documentation inside the `docs/` folder is built and hosted on our static documentation site, [docs.tagstud.io](https://docs.tagstud.io/).
-
-- Use "[dash-case / kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case)" for file and folder names
-- Follow the folder structure pattern
-- Don't add images or other media with excessively large file sizes
-- Provide alt text for embedded media
-- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
-
-## :material-translate: Translation Guidelines
-
-Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
-
-_Translation guidelines coming soon._
+- Contain superfluous or unnecessary logging statements
+- Cause unreasonable slowdowns to the program outside of a progress-indicated task
+- Cause undesirable visual glitches or artifacts on screen
diff --git a/docs/developing.md b/docs/developing.md
index 4b5252d4..9dba3bde 100644
--- a/docs/developing.md
+++ b/docs/developing.md
@@ -214,7 +214,7 @@ From there, Git will automatically run through the hooks during commit actions!
You can automatically enter this development shell, and keep your user shell, with a tool like [direnv](https://direnv.net/). Some reference `.envrc` files are provided in the repository at [`contrib`](https://github.com/TagStudioDev/TagStudio/tree/main/contrib).
-Two currently available are for [Nix](#nixos) and [uv](#installing-with-uv), to use one:
+Two currently available are for [Nix](#nix-nixos) and [uv](#installing-with-uv), to use one:
```sh
ln -s .envrc-$variant .envrc
diff --git a/docs/style.md b/docs/style.md
index ac5a7479..336ad05f 100644
--- a/docs/style.md
+++ b/docs/style.md
@@ -6,116 +6,206 @@ icon: material/sign-text
+
+!!! abstract "Prerequisite Reading"
+ This guide assumes you've read the [Developing](developing.md) and [Contributing](contributing.md) pages first.
+
# :material-sign-text: Style Guide
-## Formatting
+## :material-script-text: General Principles
-Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older code may not be adhering to all of these guidelines, in which case _"do as I say, not as I do"..._
+- Write **clear**, **concise**, and **modular** code.
+- If the purpose of a peice of code is not obvious, a **short comment** should help explain it.
+- Remember to follow the rest of the [contribution guidelines](contributing.md)!
-- Do your best to write clear, concise, and modular code.
- - This should include making methods private by default (e.g. `__method()`)
- - Methods should only be protected (e.g. `_method()`) or public (e.g. `method()`) when needed and warranted
-- Keep a maximum column width of no more than **100** characters.
-- Code comments should be used to help describe sections of code that can't speak for themselves.
-- Use [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings for any classes and functions you add.
- - If you're modifying an existing function that does _not_ have docstrings, you don't _have_ to add docstrings to it... but it would be pretty cool if you did ;)
-- Imports should be ordered alphabetically.
-- Lists of values should be ordered using their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).
- - Some files have their methods ordered alphabetically as well (i.e. [`thumb_renderer`](https://github.com/TagStudioDev/TagStudio/blob/main/src/tagstudio/qt/widgets/thumb_renderer.py)). If you're working in a file and notice this, please try and keep to the pattern.
-- When writing text for window titles or form titles, use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" capitalization. Your IDE may have a command to format this for you automatically, although some may incorrectly capitalize short prepositions. In a pinch you can use a website such as [capitalizemytitle.com](https://capitalizemytitle.com/) to check.
-- If it wasn't mentioned above, then stick to [**PEP-8**](https://peps.python.org/pep-0008/)!
+---
-### Modules & Implementations
+## :material-text-box-check: Formatting
-- **Do not** modify legacy library code in the `src/core/library/json/` directory
-- Avoid direct calls to `os`
- - Use `Pathlib` library instead of `os.path`
- - Use `platform.system()` instead of `os.name` and `sys.platform`
-- Don't prepend local imports with `tagstudio`, stick to `src`
-- Use the `logger` system instead of `print` statements
-- Avoid nested f-strings
-- Use HTML-like tags inside Qt widgets over stylesheets where possible
-
-Final submitted code must **_NOT:_**
-
-- Contain superfluous or unnecessary logging statements
-- Cause unreasonable slowdowns to the program outside of a progress-indicated task
-- Cause undesirable visual glitches or artifacts on screen
-
-### Formatter Configs
+Linting in Python files is mostly taken care of by [ruff](developing.md#ruff) and is based on the rules declared in `pyproject.toml` and `.editorconfig`.
TagStudio provides an [EditorConfig](https://editorconfig.org/#example-file) file ([`.editorconfig`](https://github.com/TagStudioDev/TagStudio/blob/main/.editorconfig)) along with a [Prettier](https://prettier.io/) config file ([`.prettierrc.toml`](https://github.com/TagStudioDev/TagStudio/blob/main/.prettierrc.toml)) for formatting files other than .py files (Markdown, JSON, YAML, HTML, CSS, etc.). If editing these types of files it's recommended that you use a formatter that supports EditorConfig or has its settings matched to the EditorConfig and Prettier configs. Lastly, please pay attention to the `prettier-ignore` flags in present in some files if you are not using Prettier, as formatting these sections will break formatting used elsewhere such as the [MkDocs site](https://docs.tagstud.io/).
-## Qt
+### :material-code-braces-box: Syntax Guidelines
-As of writing this section, the QT part of the code base is quite unstructured and the View and Controller parts are completely intermixed[^1]. This makes maintenance, fixes and general understanding of the code base quite challenging, because the interesting parts you are looking for are entangled in a bunch of repetitive UI setup code. To address this we are aiming to more strictly separate the view and controller aspects of the QT frontend.
+- Python files should always follow the [**PEP 8**]() style guide conventions, unless specifically allowed otherwise.
+ - The most notable exception in our project is the line length limit of **100** characters, which is enforced via ruff.
+ - Internal Qt methods also use `camelCase` instead of `snake_case`, so overrides of those are commonly seen in the codebase.
+- Classes and attributes considered to be "[private](https://docs.python.org/3/tutorial/classes.html#private-variables)" should be prepended with a **single underscore** (e.g. `_internal_method()`).
+ - If _functionally necessary_, an attribute name may be prepended with a double underscore to trigger "[name mangling](https://docs.python.org/3/reference/expressions.html#private-name-mangling)" (e.g. `__mangled_method()`).
+- Classes and methods should contain [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings _(this style is enforced via ruff)_.
+- Lists and JSON keys should be ordered by their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order) unless otherwise specified or readily indicated.
+- Some files have some or all of their attributes sorted. Please respect any established patterns like these in files you modify.
-The general structure of the QT code base should look like this:
+---
+## :material-filter-cog: Modules & Systems
+
+### :fontawesome-brands-python: Python Modules
+
+- Use `Pathlib` library instead of `os.path`
+- Use `platform.system()` instead of `os.name` or `sys.platform`
+- Avoid nested f-strings
+
+### :material-tag: TagStudio Systems
+
+- Translation keys can be accessed via bracket notation (e.g. `Translations["translation_key"]`) or with the `Translations.format()` method when a value needs to be passed to a placeholder in the translation.
+- Use HTML-like tags inside strings over explicit stylesheets where possible. The `Style` class provides several handy methods for formatting text with these.
+- Use the `format` method in the stylesheets class to format text headers.
+
+---
+
+## :material-folder-file: Project Layout
+
+### :material-engine: Core Backend
+
+Code that is integral to the core functionality of TagStudio and is UI-independent belongs under the `core/` directory. It's possible that some code that serves the UI can go here, as long as its purpose is to serve _any_ UI and is independent from Qt (e.g. file preview rendering).
+
+```yaml title="Core Backend Directory Example"
+core/
+│
+├── library/ # The TagStudio library system
+│ │
+│ ├── alchemy/ # Current SQLite backend w/ SQLAlchemy ORM
+│ │
+│ ├── json/ # Read-only legacy JSON library system, kept for migrations
+│ │
+│ ├── query_lang/ # The query parser
+│ │
+│ │ # Library files that do not involve the SQLAlchemy ORM
+│ │ # NOTE: Future Non-SQLAlchemy library files will be placed here
+│ ├── refresh.py
+│ └── ...
+│
+└── utils/ # Utility classes and functions for the core
```
-qt
-├── controllers
-│ ├── widgets
-│ │ └── preview_panel_controller.py
-│ └── main_window_controller.py
-├── views
-│ ├── widgets
-│ │ └── preview_panel_view.py
-│ └── main_window_view.py
-├── ts_qt.py
-└── mixed.py
-```
-
-In this structure there are the `views` and `controllers` sub-directories. They have the exact same structure and for every `_view.py` there is a `_controller.py` at the same location in the other subdirectory and vice versa.
-
-Typically the classes should look like this:
-
-```py
-# my_cool_widget_view.py
-class MyCoolWidgetView(QWidget):
- def __init__(self):
- super().__init__()
- self.__button = QPushButton()
- self.__color_dropdown = QComboBox()
- # ...
- self.__connect_callbacks()
-
- def __connect_callbacks(self):
- self.__button.clicked.connect(self._button_click_callback)
- self.__color_dropdown.currentIndexChanged.connect(
- lambda idx: self._color_dropdown_callback(self.__color_dropdown.itemData(idx))
- )
-
- def _button_click_callback(self):
- raise NotImplementedError()
-```
-
-```py
-# my_cool_widget_controller.py
-class MyCoolWidget(MyCoolWidgetView):
- def __init__(self):
- super().__init__()
-
- def _button_click_callback(self):
- print("Button was clicked!")
-
- def _color_dropdown_callback(self, color: Color):
- print(f"The selected color is now: {color}")
-```
-
-Observe the following key aspects of this example:
-
-- The Controller is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code
-- The UI elements are in private variables
- - This enforces that the controller shouldn't directly access UI elements
- - Instead the view should provide a protected API (e.g. `_get_color()`) for things like setting/getting the value of a dropdown, etc.
- - Instead of `_get_color()` there could also be a `_color` method marked with `@property`
-- The callback methods are already defined as protected methods with NotImplementedErrors
- - Defines the interface the callbacks
- - Enforces that UI events be handled
-!!! tip
- A good (non-exhaustive) rule of thumb is: If it requires a non-UI import, then it doesn't belong in the `*_view.py` file.
+!!! danger "Read-Only Legacy Code"
+ **Do not modify** legacy library code in the `src/core/library/json/` directory!
-[^1]: For an explanation of the Model-View-Controller (MVC) Model, checkout this article: [MVC Framework Introduction](https://www.geeksforgeeks.org/mvc-framework-introduction/).
+---
+
+### :material-button-cursor: App UI Frontend
+
+The application UI code is stored in the `qt/` directory, and contains all code specific to the Qt frontend. Qt widgets are built using an [MVC](https://www.geeksforgeeks.org/software-engineering/mvc-framework-introduction/) pattern, which is described in-depth below:
+
+#### MVC Pattern
+
+- **Models** are usually just objects from the [library](#core-backend).
+ - The **controller** interacts with these, the _view_ does **not**.
+- **Views** are Qt layout classes that **_only_** contain the **layout** and **styling** for one or more widgets.
+ - Class names are appended with `View`, and filenames appended with `_view`.
+ - Not to be used standalone, but as the layouts for one or more controllers.
+ - Some logic is acceptable in these classes if it serves to modularize the layout and allows controllers to influence how the layout is initialized.
+ - **Reusable Layouts**
+ - If a layout class is **_not meant_** to act as a view but instead be a generic layout, it belongs in the `views/layouts/` directory and the file should be appended with `_layout`.
+ - **Styling Classes**
+ - If a class is purely a source of reusable styling, it belongs in the `views/styles/` directory.
+
+- **Controllers** are complete widgets or base classes for complete widgets.
+ - Controller files simply take on the name of the final widget they create.
+ - This also creates naming parity with other widgets that simply extend existing Qt widgets with additional logic.
+
+```yaml title="Qt Frontend Directory Example"
+qt/
+│
+├── controllers/ # Widgets implementing views or extending other widgets
+│ ├── tag_suggest_box.py # Extends from `suggest_box.py`
+│ ├── suggest_box.py # Implements `suggest_box_view.py`
+│ ├── main_window.py
+│ └── ...
+│
+├── mixed/ # Files yet to be refactored into controllers and views
+│
+├── views/ # Everything related to widget layouts and appearances
+│ │
+│ ├── layouts/ # Layouts meant to be reused on their own inside other layouts
+│ │ ├── flow_layout.py
+│ │ └── ...
+│ │
+│ ├── styles/ # Classes specific for styling
+│ │ ├── palette.py
+│ │ ├── stylesheets.py
+│ │ └── ...
+│ │
+│ │ # Views (layouts) that get implemented by controllers (widgets)
+│ ├── main_window_view.py
+│ ├── suggest_box_view.py
+│ └── ...
+│
+│ # Frontend classes that aren't related to widgets, like managers
+├── resource_manager.py
+├── cache_manager.py
+├── ts_qt.py # Qt Driver
+└── ...
+```
+
+
+!!! warning "Pre-MVC UI Code"
+ **Do not add** new files to the `qt/mixed/` directory! These files have yet to to be refactored per the current MVC style guidelines and the directory will be **removed** once those migrations have concluded.
+
+Observe the following key aspects of the example below:
+
+- The **view** extends from a Qt layout class, and the **controller** simply extends from QWidget.
+- The **controller** is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code.
+- The **view's** widgets that are intended to be controlled by the controller are **public**, while the **controller's** methods are largely **private**.
+
+
+!!! example "MVC-Separated Widget Example"
+
+ ```py title="views/my_cool_widget_view.py"
+ class MyCoolWidgetView(QVBoxLayout):
+ def __init__(self):
+ super().__init__()
+ self.button = QPushButton()
+ self.color_dropdown = QComboBox()
+
+ self.addWidget(self.button)
+ self.addWidget(self.color_dropdown)
+ ```
+
+ ```py title="controllers/my_cool_widget.py"
+ class MyCoolWidget(QWidget):
+ def __init__(self):
+ super().__init__()
+ self.setLayout(MyCoolWidgetView())
+ self._connect_callbacks()
+
+ def _connect_callbacks(self):
+ self.layout().button.clicked.connect(self._button_click_callback)
+ self.layout().color_dropdown.currentIndexChanged.connect(
+ lambda idx: self._color_dropdown_callback(self.color_dropdown.itemData(idx)))
+
+ def _button_click_callback(self):
+ print("Button was clicked!")
+
+ def _color_dropdown_callback(self, color: Color):
+ print(f"The selected color is now: {color}")
+ ```
+
+
+!!! tip "Tip for Logic Placement"
+ A good rule of thumb is: If there's **conditional logic** after a widget has been created, it should probably go in a **controller**.
+
+---
+
+## :material-file-document: Documentation
+
+Documentation contributions include anything inside the `docs/` folder as well as the `README.md`. Documentation inside the `docs/` folder is built and hosted on our static documentation site, [docs.tagstud.io](https://docs.tagstud.io/). Some files such as the `CHANGELOG.md`, `CONTRIBUTING.md`, and `STYLE.md` are symlinked in the repo root from the `docs/` folder.
+
+- Use "[dash-case / kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case)" for file and folder names
+- Follow the folder structure pattern
+- Don't add images or other media with excessively large file sizes
+- Provide alt text for embedded media
+- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
+
+---
+
+## :material-translate: Translations
+
+Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
+
+- Do not change text inside placeholders
+- Do not change the style tags inside translations
+- Use the glossary for term definitions