# Package Layout

Background and reasoning behind design decisions in splam. This page won't tell you how to do something. See the How-To Guides for that.


# Package layout

The app is packaged with a `src/splam/` layout rather than flat scripts, which was necessary for `great-docs` (and any other tool doing static analysis) to treat it as an importable package rather than loose files.

    src/splam/
    ├── __init__.py
    ├── app.py
    ├── auth.py
    ├── audit.py
    ├── chat.py
    ├── highlight.py
    ├── manage_users.py
    ├── paths.py
    ├── service_info.py
    └── styles.css

The module split follows single responsibility:

- `app.py`: Shiny UI/server wiring, plus the `systemctl`/`journalctl` wrapper functions.
- `auth.py`: password hashing, verification, and login attempt/lockout state.
- `audit.py`: the append-only action log.
- `service_info.py`: the per-service guidance data (`SERVICE_INFO`) and its overrides.
- `highlight.py`: keyword highlighting for the Status/Logs/Audit Trail output (`_PATTERNS`), and its overrides.
- `chat.py`: chatbot client construction, system prompt, and the read-only tool set ([build_client](../reference/build_client.md#splam.build_client), [chat_available](../reference/chat_available.md#splam.chat_available), [read_only_tools](../reference/read_only_tools.md#splam.read_only_tools), [system_prompt](../reference/system_prompt.md#splam.system_prompt)). See [Explanation: Chatbot Scope](02.04-explanation-chatbot-scope.md).
- `manage_users.py`: the `splam-users` CLI entry point (`add`/`list` subcommands) for managing accounts in `credentials.json` outside the running app.
- `paths.py`: resolves the data directory (`data_dir()`: `SPLAM_DATA_DIR`, else `XDG_DATA_HOME`, else `~/.local/share/splam`) and enforces owner-only permissions on it (`ensure_parent()`).


# The [highlight](../reference/highlight.md#splam.highlight) name shadowing quirk

`__init__.py` re-exports a function called [highlight](../reference/highlight.md#splam.highlight) from `highlight.py`:

``` python
from .highlight import add_highlight_keyword, highlight, highlight_term
```

Because the re-exported name is identical to the submodule's own name, this line rebinds the [splam.highlight](../reference/highlight.md#splam.highlight) attribute from the submodule to the function. In other words, after `import splam`, [splam.highlight](../reference/highlight.md#splam.highlight) is `<function highlight>`, not the module. The module is still reachable (e.g. via `importlib.import_module("splam.highlight")`, `sys.modules`, or `from splam import highlight as _; import splam.highlight as highlight_mod`), but plain attribute access no longer finds it.

This is intentional/accepted, not a bug to fix. [highlight](../reference/highlight.md#splam.highlight) is the natural public name for the function, and the tests already work around it (`tests/conftest.py`, `tests/test_highlight.py` both import the submodule via `importlib.import_module("splam.highlight")` with a comment explaining why). Just be aware of it if you add new tests or tooling that touches [splam.highlight](../reference/highlight.md#splam.highlight).

It also affects `great-docs`: when `great-docs.yml`'s API reference config resolves the bare name [highlight](../reference/highlight.md#splam.highlight), dynamic introspection resolves it to the *submodule* (not the function) and, under `dynamic: true`, fails to filter out the module's own imports (`data_dir`, `ensure_parent` from `paths.py`) as aliases. Left alone, this leaks spurious pages into the API reference (`highlight.data_dir`, `highlight.ensure_parent`, plus the module's constants) and prints a "Dynamic introspection failed" warning during `great-docs build`. The fix lives in `great-docs.yml`, which overrides `dynamic: false` for just that one reference entry so static analysis (which correctly identifies the imports as aliases) is used instead:

``` yaml
reference:
  - title: Functions
    contents:
      - ...
      - name: highlight
        dynamic: false
      - highlight_term
      - ...
```

If you rename or restructure `highlight.py`, or add another submodule whose name collides with one of its own re-exported members, check whether this override is still needed (or needs to move).
