Concepts
========

This page covers the architecture and conventions behind a VIStk project. For a
hands-on walkthrough, start with :doc:`quickstart`.

Project Structure
-----------------

A VIStk project has the following folder layout:

.. code-block:: text

   MyProject/
   ├── .VIS/
   │   ├── project.json        <- project registry (screens, versions, metadata)
   │   ├── Host.py             <- Host entry point (generated by VIS new; not user-editable)
   │   ├── Templates/          <- screen and element templates used by the CLI
   │   └── project.spec        <- PyInstaller spec file (generated on release)
   ├── Screens/
   │   └── <screen>/           <- UI element files, prefixed f_ (e.g. f_header.py)
   ├── modules/
   │   └── <screen>/           <- logic files, prefixed m_ (e.g. m_header.py)
   ├── Icons/                  <- .ico (Windows) or .xbm (Linux) icon files
   ├── Images/                 <- image assets used by VIMG
   ├── <screen>.py             <- main script for each screen
   └── dist/                   <- compiled binaries (created on release)

``project.json`` is the source of truth for the project. It stores screen names, script
paths, icons, descriptions, version numbers, and release configuration. It is managed
automatically by the CLI and by ``VINFO``/``Project`` --- do not edit it by hand.

App Lifecycle
-------------

VIStk supports two runtime models: **standalone** (original) and **Host-based** (tabbed).

Standalone mode
~~~~~~~~~~~~~~~

Each screen is its own Python process. Switching screens replaces the current process via
``os.execl``. This is the original VIStk behaviour and still works for any screen where
``tabbed`` is ``false``.

.. code-block:: python

   from VIStk.Objects import Root

   root = Root()
   root.screenTitle("MyScreen")
   root.WindowGeometry.setGeometry(width=800, height=600, align="center")

   # ... build your UI ...

   if __name__ == "__main__":
       while root.Active:
           root.update()

Host mode
~~~~~~~~~

The ``Host`` is a persistent process that owns a hidden ``Tk()`` root. All visible windows
are ``DetachedWindow`` instances (Toplevels) that the Host manages. Screens marked
``tabbed: true`` open as tabs inside the active window; standalone screens open as new
``DetachedWindow`` instances.

On the first call to ``host.update()``, the Host automatically opens the project's default
screen (set in ``project.json``). This deferred open ensures that ``Host.py`` has time to
configure shared menus before any window is created.

.. code-block:: python

   from VIStk.Objects import Host
   from modules.menu import shared_menu_structure

   host = Host()
   host.default_menu_setup = lambda m: m.build_shared_menu(shared_menu_structure())

   while host.Active:
       host.tick_fps()
       host.update()

Screen navigation from anywhere in the app:

.. code-block:: python

   # Routes through Host if running, otherwise os.execl
   from VIStk.Structures._Project import Project
   Project().open("WorkOrders")

**Key rules:**

- Do not call ``root.mainloop()`` --- this bypasses the ``while`` loop and prevents process
  switching.
- Do not call ``root.destroy()`` to quit --- call ``host.quit_host()`` instead.
- Screen scripts must include ``if __name__ == "__main__":`` around the startup code so
  they can be imported as modules by the Host without executing the top-level loop.
- Use ``Project.open()`` instead of ``Project.load()`` when a Host may be running --- it
  routes correctly in both modes.

Screen Module Pattern
---------------------

Every tabbed screen must follow this pattern. Violating it will crash the Host on import:

.. code-block:: python

   # Module-level: only imports and pure data (no widget creation, no Tk calls)

   def loop():                  pass  # called every frame (standalone only)
   def configure_menu(menubar): pass  # contribute to HostMenu when tab is active
   def on_focused():            pass  # tab gained focus
   def on_unfocused():          pass  # tab lost focus

   def setup(parent):
       # ALL widget creation goes here
       from tkinter import ttk
       frame = ttk.Frame(parent)
       frame.place(relx=0, rely=0, relwidth=1, relheight=1)

   if __name__ == "__main__":
       from Screens.root import root, frame
       setup(frame)
       while root.Active:
           root.update()

The ``if __name__ == "__main__":`` guard is required for tabbed screens. When the Host
imports the module to call ``setup()``, the guard prevents the standalone loop from
running.

**Hook lookup priority:** If ``modules/<screen>/m_<screen>.py`` exists, the Host checks
it first for ``on_focused``, ``on_unfocused``, and ``configure_menu``. The screen script
is used as a fallback.

The f_element Pattern
---------------------

Elements are modular UI sections within a screen. Each element has a ``build(parent)``
function that receives the parent frame and places widgets into it.

.. code-block:: python

   # Screens/MyScreen/f_header.py

   widget_ref = None  # module global -- set by build(), read by loop()

   def build(parent):
       global f_elem, widget_ref
       f_elem = ttk.Frame(parent)
       f_elem.place(parent.Layout.cell(row, col))  # 0-indexed
       widget_ref = ttk.Label(f_elem, text="Header")
       widget_ref.pack()

The ``#%`` Marker System
------------------------

VIStk templates use ``#%`` comment markers as structural anchors. The ``stitch`` command
uses these to locate and rewrite import blocks in screen scripts.

.. warning::

   Do not delete or rename ``#%`` comment lines. They are not standard comments --- they are
   structural anchors that VIStk searches for by text pattern.

The two critical blocks:

.. code-block:: python

   #%Screen Elements
   from Screens.MyScreen.f_header import f_header
   f_header.build(parent)
   from Screens.MyScreen.f_body import f_body
   f_body.build(parent)
   #%Screen Grid

   #%Screen Modules
   from modules import MyScreen
   #%Handle Arguments

``stitch`` replaces everything between ``#%Screen Elements`` and ``#%Screen Grid`` with
fresh imports and ``build()`` calls for each ``Screens/<screen>/f_*.py`` file. Everything
between ``#%Screen Modules`` and ``#%Handle Arguments`` is replaced with a single package
import: ``from modules import <ScreenName>``.

``stitch`` also generates ``modules/<screen>/__init__.py`` with three sections:

.. code-block:: python

   #%Modules (Auto-generated)
   from . import m_header
   from . import m_body

   #%Screen Variables
   # Hand-edited shared state (e.g. current_wo = None)

   #%Exports
   # Hand-edited public API (e.g. from .m_header import get_wo_num)

The ``#%Modules`` section is regenerated on every stitch. ``#%Screen Variables`` and
``#%Exports`` are preserved across stitches.

configure_menu Pattern
----------------------

Screens contribute menu items to the Host's menu bar via the ``configure_menu`` hook.
Items are added when the tab activates and automatically cleared when it deactivates.

.. code-block:: python

   def configure_menu(menubar):
       menubar.set_screen_items([
           {"label": "Export PDF", "command": export_pdf},
           {"label": "Print",      "command": print_fn},
       ], label="Work Orders")

       menubar.set_screen_items([
           {"label": "About", "command": show_about},
       ], label="Help")

Call ``set_screen_items`` multiple times to contribute multiple cascades.

Project-wide menus are configured in ``Host.py`` via ``default_menu_setup``. This callback
is called on every new ``DetachedWindow``'s ``HostMenu``, ensuring consistent menus across
all windows:

.. code-block:: python

   host = Host()
   host.default_menu_setup = lambda m: m.build_shared_menu({
       "File": [
           {"label": "New", "items": [...]},
           {"separator": True},
           {"label": "Exit", "command": host.quit_host},
       ],
       "Edit": [...],
       "View": [...],
       "Tools": [...],
   })

project.json
------------

``project.json`` is managed by the CLI and ``VINFO``. Its structure:

.. code-block:: json

   {
       "MyApp": {
           "Screens": {
               "Home": {
                   "script": "Home.py",
                   "release": true,
                   "icon": "home",
                   "desc": "Main dashboard",
                   "tabbed": true,
                   "single_instance": false,
                   "version": "1.0.0",
                   "current": null
               }
           },
           "defaults": {
               "icon": "app",
               "default_screen": "Home"
           },
           "metadata": {
               "company": "My Company",
               "copyright": "My Company",
               "version": "0.1.0"
           },
           "release_info": {
               "location": "./dist/",
               "hidden_imports": []
           },
           "host": {
               "script": ".VIS/Host.py"
           }
       }
   }

Per-screen fields:

.. list-table::
   :header-rows: 1
   :widths: 20 15 65

   * - Field
     - Type
     - Description
   * - ``script``
     - string
     - Python script filename in the project root
   * - ``release``
     - bool
     - Whether this screen is compiled to its own binary
   * - ``icon``
     - string/null
     - Icon name (without extension) from ``Icons/``
   * - ``desc``
     - string
     - Screen description
   * - ``tabbed``
     - bool
     - ``true`` = Host tab, ``false`` = standalone subprocess
   * - ``single_instance``
     - bool
     - Prevent duplicate tabs when ``true``
   * - ``version``
     - string
     - Screen-specific version (``major.minor.patch``)
   * - ``current``
     - string/null
     - Free-form current-state string
