Skip to content

Code Conventions

[[[appendices.code-conventions]]]

Overview

[[[appendices.code-conventions#overview]]]

Conventions for writing code and docs in this repo, for any contributor — not just Claude Code sessions. CLAUDE.md at the repo root covers how to work with the AI assistant; this page covers how the codebase itself is written.

1. Python

[[[appendices.code-conventions#python]]]

  • Imports: relative within a package's library code (from .field import Field); absolute only in tests and the app's entry file (from rehuco_agent.fields.field import Field). Relative keeps intra-package imports stable when a package is published or moved between depths. The codebase is mid-migration to this rule; the rehuco-core sweep is a follow-up.
  • Module filenames: match their main class (rehu_document.pyRehuDocument, field_registry.pyFieldRegistry), unless the file deliberately groups several related classes (properties.pyTypedProperty+SimpleProperty, fields/field.pyField+FieldBinding+FieldModel).
  • Visibility: public or private (__); no protected (_) unless the class is designed for inheritance. This holds at module level too — no _-prefixed module globals or classes. A helper used by only one class belongs inside that class (a __-private member, or a public classmethod if it's API — even the sentinel-in-signature case works, verified against 3.14's lazy annotations); anything that must stay at module level is a plain public name, kept out of the package API by what the package __init__ exports (name mangling doesn't exist outside a class body, so a module-level _ would be convention-only anyway).
  • Constants: Final without an explicit type when it can be inferred.
  • Naming: don't bake development-methodology/process labels into identifiers (e.g. a bare TRACER_ prefix naming a constant after "this was built during the tracer-bullet slice") — that context rots once the slice ends; put it in a docstring, not the name.
  • Overrides: @override on every method that overrides a base-class method.
  • Docstrings: Sphinx-style on all functions, including private ones — one-line summary + :param:/:returns:/:raises: as needed; no multi-paragraph docstrings for routine code. Closing """ on its own line for multi-line docstrings, on the same line for single-line ones. Constructor :param: entries go on the class docstring (IDE hover shows them); __init__ gets no docstring.
  • Comments: only when the why is non-obvious (hidden constraint, subtle invariant, bug workaround). No narration of what the code does.
  • Line length: 120 (ruff enforced).

2. PySide6 UI

[[[appendices.code-conventions#pyside-ui]]]

  • Window/widget layouts live in .ui files, not built up in Python. For a custom widget, prefer a .ui too, unless it's trivial (simple layout, or the class only overrides/adds behavior on an existing widget rather than composing a new layout).
  • Collocate a .ui with its controller class: foo_widget.ui, foo_widget.py next to each other; the generated foo_widget_ui.py is gitignored and rebuilt via make uis — never hand-edit it.
  • A custom widget used inside a .ui is placed as a base/QWidget placeholder and promoted to the real class, not embedded as literal custom XML.
  • Assets go through .qrc. Icons are authored in an Affinity Designer master file and exported as .svg; make qrcs regenerates the gitignored _rc.py. A .ui referencing a qrc-managed resource (e.g. windowIcon) must declare <resources><include location="....qrc"/></resources> so make uis emits the matching resource import — verify the generated file after adding one.
  • Ask before adding a new asset-conversion pipeline (e.g. deriving .ico from the .svg master) rather than improvising one inline.
  • Every property/attribute in a .ui file must be one Qt Designer's own Property Editor can display and edit. Don't hand-add XML that's schema-valid and works at runtime but isn't something Designer surfaces — a developer opening the file in Designer needs to be able to find and change it there. Anything Designer can't set belongs in the controller class's Python code after setupUi(), not in the .ui.
  • Name widgets in .ui files snake_case, not Qt Designer's default camelCase — this is a recommendation, not a hard rule; use camelCase where justified (e.g. overriding a name Qt itself expects). A widget name that's already a single dictionary word a spell checker recognizes (checkbox, toolbar) doesn't need splitting; a compound default that isn't a real single word (centralwidget, statusbar, menubar) becomes central_widget, status_bar, menu_bar.

3. Markdown

[[[appendices.code-conventions#markdown]]]

Docs under docs/ are markdownlint-checked (.markdownlint.json; MD013 line length 120, tables exempt). Also:

  • Blank lines around headings and lists (MD022/MD032); inside a blockquote the separator is an empty > line, not a truly blank line.
  • Spaced table delimiter rows — | --- | --- | — and single-space-padded cells.
  • No emphasis-as-heading (MD036) — use real ### headings.
  • Under a spec section heading, list its GitHub issue(s) as plain list items — - [#N: title](url) — regardless of whether the issue is open or closed. Deliberately not a - [ ]/- [x] task list: a checkbox has to be ticked in a commit every time an issue closes, which is churn in the specs for state GitHub already tracks authoritatively. The link records which issues a section belongs to; their status is read on GitHub.
  • Cross-renderer link rendering. Docs are read both on GitHub (blob view, PRs) and the published mkdocs site, which render some references differently:
  • A bare #123 issue/PR reference autolinks natively on GitHub already; pymdownx.magiclink (mkdocs.yml) gives it the same treatment on the published site. Its issue-shorthand pattern requires digits right after #, so it never collides with a [[doc#slug]] cross-reference token (slug segments are never all-digit) — see "Symbolic cross-references" in docs/specs/README.md.
  • A plain relative link from a doc into a repo file outside docs/ (e.g. a .py source file under packages/) resolves natively on GitHub, but 404s on the built mkdocs site, since only docs/ is ever published. Write such links as plain relative paths regardless — tools/mkdocs_relative_link_hook.py rewrites, at build time only, any link whose target falls outside docs_dir to an absolute {repo_url}/blob/{branch}/... link; GitHub needs no change.

4. Testing

[[[appendices.code-conventions#testing]]]

Each test's docstring ends with a **Test steps:** bullet list spelling out the steps and checks, so intent is readable without tracing the code — a project convention, not a pytest feature. The test stack and how to drive it are covered in [[appendices.testing#qa-gate]].