Imports:
  - Types:
      - BoardRecord
      - BoardEntry
      - collect_topic_board
      - aggregate_topic_board
      - switch_topic
      - create_topic
      - update_topic
      - publish_existing_topic
      - resolve_propagation
      - execute_propagation
      - resolve_delete_targets
      - delete_topics
      - resolve_clear_targets
    Usages:
      - topic-board
      - switching
      - todo-entry
      - creating
      - publishing
      - deleting
      - registering-hooks
      - update
      - propagate
    From: goga/topics
  - Types:
      - load_project_config
      - TopicsConfig
    Usages:
      - project-configuration
    From: goga/config
  - Types:
      - ConfigHooks
      - ConfigOverlay
    Usages:
      - checkpoints
    From: goga/config/hooks

Usages:
  convention: .goga/usages/conventions.md
  click: .goga/usages/cooks/click.md
  beautiful_json: .goga/usages/cooks/beautiful_json.md

Annotations: |
  The `convention` practice is used for:
  - Working with the codebase
  - Organizing the REPL development cycle
  - Debugging and testing
  - Organizing the test infrastructure
  - Understanding the general principles and rules of development and testing in the project

  Use the `click` practice to build the topics command group: the group
  decorator with its own option, the subcommand registration, the
  flags, arguments, and options of each subcommand — the
  optional-value todo option included, its three states mapped in the
  callback — the repeatable host and topic filters, echo, exit-code
  propagation, the confirmation of the delete and clear subcommands,
  and the clean error rendering.

  Use the `creating` and `publishing` practices for the creation and
  publication contracts of the domain, the `switching` practice for
  the switching contract, the `todo-entry` practice for the todo entry
  behind the --todo flags, the `topic-board` practice for the board
  contract, and the `deleting` practice for the deletion and clear
  contracts of the domain. Use the `project-configuration` practice for the schema
  of the topics section.

  Use the `beautiful_json` practice for the JSON serialization of the
  board output.

  Use the `registering-hooks` practice for the
  lifecycle events this command group emits and the tool-package hooks
  subscribed to them.

  Use the `checkpoints` practice for the config amendment checkpoint at
  the project-configuration load moment — the command loads the authored
  configuration, delivers the checkpoint, consumes the effective
  configuration of the returned `ConfigOverlay`, and prints the amendment
  summary lines to stderr. A checkpoint failure — ValueError (the
  hard action) or ImportError (a broken tool package facade) — folds
  into the same clean-error wrapper as a failed load; a missing file
  counts as unset and offers no checkpoint.

  The creation inputs resolve their values at this layer: the base —
  a flag beats the topics section — `TopicsConfig` — of the effective
  configuration of the `ConfigOverlay` (the configuration load delivers
  the config amendment checkpoint), which beats the
  current HEAD requested explicitly; no base at all is a clean error
  naming the flag and the configuration line. The commit message
  template — a flag beats the topics section; the built-in default
  lives in the domain. The todo option maps its three states into the
  domain source declaration — a non-empty value is the todo, the
  value-less form declares stdin, an absent option or an empty value
  declares nothing; no resolution happens here. The switch flag
  passes through to the domain. The clear subcommand resolves its
  base the same way minus the current HEAD — a flag beats the topics
  section; no base at all is a clean error naming the flag and the
  configuration line. The configuration is read only for
  values no flag provided.

  This cell is the CLI surface of the topics domain: a thin wrapper
  that resolves inputs, delegates every computation to the domain
  routines, renders the board in its two views and its JSON form, and
  confirms the deletion and the clear. No inventory walking, no switch resolution,
  no git access, no editor session, no stdin reading live here — the
  value-less todo option maps into the domain's source declaration.
  Domain errors surface as clean CLI errors (stderr, non-zero exit,
  no traceback). Use relative imports.

  The update and propagate subcommands resolve their inputs at this
  layer: the base — a flag beats the topics section of the effective
  configuration, with no current-HEAD rung; the strategy and the
  message template pass through verbatim from the nested topics keys —
  the whitelists and the defaults live in the domain; update asks no
  confirmation, propagate asks exactly one — naming the target and the
  inherent push — with the --yes escape.

---

"topics(year: str | None = None)":
  location: topics.py
  annotations: |
    The goga topics command group — a click.Group container for the
    topics subcommands, exported via __all__ and registered in the
    root application group. The group carries the year scope every
    subcommand shares.

    `year`: the --year/-y group option — exactly one year, four digits
            the recognized form; None means the current year; a search
            across years does not exist

    Use the `click` practice for the group decorator, the group option,
    and the subcommand registration.

    Subcommand surfaces:
    - board — a --remote/-r flag, an --info/-i flag, a repeatable
      --host option, a repeatable --topic option, a --per-host flag,
      a --json flag
    - create — a NAME positional, a --todo/-t option with an optional
      value, a --publish/-p flag, a --base-ref option, a --from-current
      flag, a --commit/-c option, a --switch/-s flag
    - switch — an IDENTIFIER positional, a --todo flag
    - delete — IDENTIFIER positionals, a --yes/-y flag
    - clear — a --base-ref option, a --yes/-y flag
    - update — an optional IDENTIFIER positional, a --base-ref option,
      a --publish/-p flag
    - publish — an optional IDENTIFIER positional
    - propagate — an optional IDENTIFIER positional, a --base-ref
      option, a --yes/-y flag

    Apply the `convention` CLI command docstring rule for the --help
    text (rendered verbatim by Click; omit Args/Returns/Raises).
  methods:
    "board(remote: bool = False, info: bool = False, host: tuple[str, ...], per_host: bool = False, json_output: bool = False, topic: tuple[str, ...]) -> exit_code: int": |
      Subcommand goga topics board: print the board of the scoped
      year — by default one entry per topic as a four-column table,
      six with the todo and delivery columns under --info/-i; under
      --per-host one row per topic and hosting branch; under --json
      the machine-readable record array instead of a table. Every view
      shows only topics that still have their own branch.

      `remote`: the --remote/-r flag — read remote-tracking refs instead
                of local branches
      `info`: the --info/-i flag — add the todo and delivery columns to
              the table; the delivery column carries the topic's
              divergence marker against the configured base — base,
              up-to-date, propagated, or need-update
      `host`: the repeatable --host option — hosting-branch display
              names, exact match, union across values; empty — no
              filtering; an unknown name yields the empty board, never
              an error
      `per_host`: the --per-host flag — the expanded audit view, one
                  row per topic and hosting branch
      `json_output`: the --json flag — print the board as a
                     pretty-printed JSON array instead of a table
      `topic`: the repeatable --topic option — topic slugs, exact
               match, union across values, composed with `host`;
               empty — no filtering; an unknown slug yields the empty
               board, never an error
      `exit_code`: 0 on success (an empty board included), 1 on error

      Apply the `topic-board` practice for the board contract of the
      domain.
      Apply the `click` practice for the flags, the repeatable options,
      echo, and the color rules.
      Apply the `beautiful_json` practice for the JSON serialization.

      Algorithm:
      1. Load the configuration the way clear does —
         `load_project_config`; the configuration load delivers the
         config amendment checkpoint per the `checkpoints` practice;
         the amendment summary lines print to stderr, nothing when
         empty — and pass the resolved topics.base_ref of the
         effective configuration into `collect_topic_board` (no flag
         exists; the board reads the configuration only)
      2. `json_output` together with `info` -> clean error: the todo is
         always present in the JSON records
      3. The default view: collect the per-host records via
         `collect_topic_board` with the scoped year and `remote`, then
         project via `aggregate_topic_board` with `host` and `topic`
      4. The per-host view: collect via `collect_topic_board` with the
         scoped year, `remote`, and `host` and `topic` as the record
         filters
      5. The table output: measure the terminal width and render — the
         default view via `render_topic_board` with the entries, the
         per-host view via `render_topic_host_rows` with the records;
         both take `info`
      6. The JSON output: render via `render_board_json` with the
         entries or the records
      7. An empty board renders nothing as a table and [] as JSON —
         exit 0

      Requirements:
      - Read-only — nothing is created, written, or switched
      - The year scoping and `remote` apply to the JSON output exactly
        as to the table
      - A missing configuration file counts as an unset value — the
        divergence stays None and the board renders with empty Delivery
        cells, never an error

      Constraints:
      - Do not print the year or the artifacts, and no heading line
        outside the table or the JSON array — the table carries topic,
        branch, hosts, the todo and delivery columns under `info`, and
        statuses only
      - Do not compute the board here — the projection of the default
        view and both filter semantics belong to the domain
    "create(branch_name: str, todo: str | None = None, todo_from_stdin: bool = False, publish: bool = False, base_ref: str | None = None, from_current: bool = False, commit_message: str | None = None, switch: bool = False) -> exit_code: int": |
      Subcommand goga topics create: create fresh work — a branch off
      the resolved base with the name as entered, its topic of the
      scoped year, and an optional multi-line todo; by default the
      branch is planted at one commit carrying the topic's todo.md and
      the caller stays on their branch; under --switch the branch is
      checked out with its topic directory in the working copy; under
      --publish the work is created off the base and published to
      origin without switching.

      `branch_name`: NAME positional — the branch name as entered
      `todo`: the --todo/-t value — the todo text; an empty value
               counts as an absent option
      `todo_from_stdin`: True when --todo was given without a value —
                         the declared stdin source of the todo
      `publish`: the --publish/-p flag — the publication path without
                 the ask
      `base_ref`: the --base-ref value — the base of the branch; valid
                  with and without `publish`
      `from_current`: the --from-current flag — the current HEAD as
                      the base
      `commit_message`: the --commit/-c value — the message template;
                        publication-only
      `switch`: the --switch/-s flag — the checked-out creation; acts
                only without `publish`
      `exit_code`: 0 on success, 1 on error

      Apply the `creating` practice for the creation contract of the
      domain.
      Apply the `publishing` practice for the publication contract of
      the domain.
      Apply the `project-configuration` practice for the topics
      section schema.
      Apply the `click` practice for the options and exit-code
      propagation.

      Algorithm:
      1. `commit_message` without `publish` -> clean error: the option
         is publication-only
      2. `switch` together with `publish` -> clean error: the
         publication never switches
      3. Resolve the base — `base_ref`, otherwise the topics section of
         the effective configuration of the `ConfigOverlay` (loaded via
         `load_project_config`; the configuration load delivers the
         config amendment checkpoint per the `checkpoints` practice via
         the `ConfigHooks` checkpoint surface — amend_config(config=...)),
         otherwise the
         current HEAD under `from_current`; no base at all ->
         clean error naming the flag and the configuration line,
         before anything else
      4. Resolve the template — `commit_message`, otherwise the topics
         section, otherwise None (the built-in default lives in the
         domain)
      5. Map the optional-value todo option: a non-empty `todo` value is
         the todo and declares the source; the value-less form sets
         `todo_from_stdin` with no value; an absent option or an empty
         value declares nothing
      6. Delegate to `create_topic` with the name, the base, the todo
         value, the stdin declaration, `publish`, the template, the
         scoped year, and `switch`
      7. Echo the single result line
      8. Propagate the exit code

      Requirements:
      - The configuration is read only for values no flag provided; a
        missing configuration file counts as an unset value; a present
        but invalid one surfaces its own clean error
      - The amendment summary lines of the `ConfigOverlay` print to
        stderr (nothing when empty)

      Constraints:
      - Do not validate the name at the CLI layer — the domain and git
        own that
      - Do not open the editor or read stdin here — the entry and the
        stdin channel belong to the domain
      - Do not switch branches or render the board here — every
        computation belongs to the domain
    "switch(identifier: str, todo: bool = False) -> exit_code: int": |
      Subcommand goga topics switch: bring the repository onto the
      branch hosting the requested work; under --todo enter the todo
      of the switched topic after the switch.

      `identifier`: IDENTIFIER positional — a branch name, a topic
                    slug, or their prefix
      `todo`: the --todo flag — enter the todo of the switched topic
      `exit_code`: 0 on success, 1 on error

      Apply the `switching` practice for the switching contract of the
      domain.
      Apply the `click` practice for the flag and exit-code
      propagation.

      Algorithm:
      1. Delegate to `switch_topic` with the identifier, the flag, and
         the scoped year
      2. Echo the single result line
      3. Propagate the exit code

      Constraints:
      - Do not launch any pipeline — continuation is a separate
        command
    "delete(identifiers: tuple[str, ...], yes: bool = False) -> exit_code: int": |
      Subcommand goga topics delete: resolve and delete identified
      topics — the local branch, the origin twin, and the topic
      directory — under one confirmation for the whole list.

      `identifiers`: IDENTIFIER positionals — branch names, topic
                     slugs, or their prefixes
      `yes`: the --yes/-y flag — skip the confirmation
      `exit_code`: 0 on success (a declined confirmation included), 1
                   on error

      Apply the `deleting` practice for the deletion contract of the
      domain.
      Apply the `click` practice for the confirmation, echo, and
      exit-code propagation.

      Algorithm:
      1. Resolve the targets via `resolve_delete_targets` with the
         identifiers and the scoped year — a resolution error is clean
         and deletes nothing
      2. Without `yes`: no interactive terminal -> clean error;
         otherwise print the topic-to-branch pairs of the list and ask
         one confirmation for the whole list; a declined answer exits
         0 with nothing deleted
      3. Delegate to `delete_topics` with the targets and the scoped
         year
      4. Echo the single result line
      5. Propagate the exit code

      Requirements:
      - One confirmation for the whole list — never per topic
      - The -y short form collides with the group --year; the
        positions on the command line distinguish them

      Constraints:
      - Do not offer an interactive choice among ambiguous candidates —
        ambiguity is a domain error
      - Do not delete anything before the resolution and the
        confirmation are complete

    "clear(base_ref: str | None = None, yes: bool = False) -> exit_code: int": |
      Subcommand goga topics clear: resolve and delete the merged
      topics of the scoped year — every own-branched topic whose
      history the base ref carries — under one confirmation for the
      whole list.

      `base_ref`: the --base-ref value — the base whose tree defines
                  the clear scope
      `yes`: the --yes/-y flag — skip the confirmation
      `exit_code`: 0 on success (an empty scope and a declined
                   confirmation included), 1 on error

      Apply the `deleting` practice for the deletion and clear
      contracts of the domain.
      Apply the `project-configuration` practice for the topics
      section schema.
      Apply the `click` practice for the confirmation, echo, and
      exit-code propagation.

      Algorithm:
      1. Resolve the base — `base_ref`, otherwise the topics section of
         the effective configuration of the `ConfigOverlay` (loaded via
         `load_project_config`; the configuration load delivers the
         config amendment checkpoint per the `checkpoints` practice via
         the `ConfigHooks` checkpoint surface — amend_config(config=...));
         no base at all -> clean error naming the flag and the
         configuration line, before anything else
      2. Resolve the targets via `resolve_clear_targets` with the base
         and the scoped year; an empty scope echoes one line and
         exits 0
      3. Without `yes`: no interactive terminal -> clean error;
         otherwise print the topic-to-branch pairs of the list and ask
         one confirmation for the whole list; a declined answer exits
         0 with nothing deleted
      4. Delegate to `delete_topics` with the targets and the scoped
         year
      5. Echo the single result line
      6. Propagate the exit code

      Requirements:
      - The configuration is read only for values no flag provided; a
        missing configuration file counts as an unset value
      - The amendment summary lines of the `ConfigOverlay` print to
        stderr (nothing when empty)
      - One confirmation for the whole list — never per topic
      - The -y short form collides with the group --year; the
        positions on the command line distinguish them

      Constraints:
      - Do not compute the scope here — the scope resolution and the
        deletion belong to the domain
      - Do not delete anything before the resolution and the
        confirmation are complete
    "update(identifier: str | None = None, base_ref: str | None = None, publish: bool = False) -> exit_code: int": |
      Subcommand goga topics update: bring a topic up to its base —
      the current topic when IDENTIFIER is omitted, the identified one
      otherwise; --publish/-p pushes the refreshed branch after
      success.

      `identifier`: IDENTIFIER positional — a branch name, a topic
                    slug, or their prefix; omitted addresses the
                    current topic
      `base_ref`: the --base-ref value — the base of the update
      `publish`: the --publish/-p flag — push the refreshed branch
      `exit_code`: 0 on success (an already-current topic included), 1
                   on error

      Apply the `update` practice for the update contract of the
      domain.
      Apply the `project-configuration` practice for the topics
      section schema.
      Apply the `checkpoints` practice for the config amendment
      checkpoint at the configuration load moment.
      Apply the `click` practice for the options, echo, and exit-code
      propagation.

      Algorithm:
      1. Resolve the base — `base_ref`, otherwise topics.base_ref of
         the effective configuration of the `ConfigOverlay` (loaded
         via `load_project_config`; the configuration load delivers
         the config amendment checkpoint per the `checkpoints`
         practice); no base at all -> clean error naming the flag and
         the configuration line, before anything else
      2. Resolve the strategy source — topics.update.strategy of the
         effective configuration, verbatim; None stays None (the
         built-in default lives in the domain)
      3. Resolve the message template — topics.update.commit,
         verbatim
      4. Delegate to `update_topic` with the identifier, the base, the
         strategy source, the template, `publish`, and the scoped year
      5. Echo the single result line
      6. Propagate the exit code

      Requirements:
      - The configuration is read only for values no flag provided; a
         missing configuration file counts as an unset value
      - The amendment summary lines of the `ConfigOverlay` print to
         stderr (nothing when empty)
      - The --help text names the addressee rule explicitly

      Constraints:
      - Do not validate the strategy here — the domain owns the
        whitelist and its configuration error
      - Do not ask a confirmation
    "publish(identifier: str | None = None) -> exit_code: int": |
      Subcommand goga topics publish: deliver an existing topic branch
      to origin — the current topic when IDENTIFIER is omitted, the
      identified one otherwise.

      `identifier`: IDENTIFIER positional — a branch name, a topic
                    slug, or their prefix; omitted addresses the
                    current topic
      `exit_code`: 0 on success (the up-to-date and remote-ahead
                   outcomes included), 1 on error

      Apply the `publishing` practice for the publication contract of
      the domain.
      Apply the `click` practice for the positional, echo, and
      exit-code propagation.

      Algorithm:
      1. Delegate to `publish_existing_topic` with the identifier and
         the scoped year
      2. Echo the single result line
      3. Propagate the exit code

      Constraints:
      - Do not read the configuration — the publication resolves no
        configuration keys
      - Do not ask a confirmation — the push is the requested action
    "propagate(identifier: str | None = None, base_ref: str | None = None, yes: bool = False) -> exit_code: int": |
      Subcommand goga topics propagate: deliver a topic's finished
      work into its base — the push to origin is inherent, there is no
      publish flag; exactly one confirmation names the target and the
      push.

      `identifier`: IDENTIFIER positional — omitted addresses the
                    current topic
      `base_ref`: the --base-ref value — the target base
      `yes`: the --yes/-y flag — skip the confirmation
      `exit_code`: 0 on success (a nothing-to-do delivery and a
                   declined confirmation included), 1 on error

      Apply the `propagate` practice for the propagation contract of
      the domain.
      Apply the `project-configuration` practice for the topics
      section schema.
      Apply the `checkpoints` practice for the config amendment
      checkpoint at the configuration load moment.
      Apply the `click` practice for the confirmation, echo, and
      exit-code propagation.

      Algorithm:
      1. Resolve the base and the sources exactly as the update
         subcommand — `base_ref`, otherwise topics.base_ref; the
         strategy from topics.propagate.strategy and the template from
         topics.propagate.commit, verbatim
      2. Resolve the plan via `resolve_propagation` with the
         identifier, the base, the strategy source, the template, and
         the scoped year
      3. Without `yes`: no interactive terminal -> clean error;
         otherwise ask one confirmation naming the topic, the target
         base, and the push; a declined answer exits 0 with nothing
         done
      4. Delegate to `execute_propagation` with the plan
      5. Echo the single result line
      6. Propagate the exit code

      Requirements:
      - The -y short form collides with the group --year; the
        positions on the command line distinguish them
      - One confirmation — naming the target and the inherent push

      Constraints:
      - Do not compute the delivery here — the resolution and the
        execution belong to the domain

"render_topic_board(entries: list[BoardEntry], width: int, info: bool = False)":
  location: render.py
  annotations: |
    Render the default board as a table: topic, branch, and hosts —
    under `info` the todo and delivery columns sit between hosts and
    statuses.

    `entries`: the aggregated board entries — already sorted by the domain
    `width`: the measured terminal width in columns
    `info`: True adds the todo and delivery columns and switches to
            the six-column width rule

    Apply the `click` practice for echo.

    Algorithm:
    1. Compute the column widths from `width` alone per the width rule of
       the requirements — the four-column rule without `info`, the
       six-column rule with it; the grid is fixed and independent of the
       record content
    2. Print one header row and one separator row with column and row
       dividers — the column order is topic, branch, hosts, todo,
       delivery, statuses under `info`, and topic, branch, hosts,
       statuses otherwise
    3. Print each entry: every text column truncated with an ellipsis when
       it exceeds its column, every host name on its own grid line of the
       hosts column, and the statuses wrapped onto continuation lines
       without affecting the column widths
    4. Print one row divider after every record — the same dash run as the
       header separator — the last record included; the wrapped
       continuation lines of one record stay undivided
    5. Mark the entry hosting the current branch with an asterisk; keep
       the remote prefix of a remote own branch visible in the branch
       column
    6. An empty `entries` prints nothing

    Requirements:
    - Four-column widths: topic, branch, and hosts get an equal share
      first, statuses take the remainder — each of topic, branch, and
      hosts is capped at one quarter of `width` minus the dividers,
      statuses receives what is left, and every column keeps a minimum
      of 8 columns before truncation applies
    - Six-column widths under `info`: topic, branch, hosts, todo, and
      delivery get an equal share — each capped at one sixth of `width`
      minus the dividers, statuses receives the non-negative
      remainder, and every column keeps a minimum of 8 columns before
      truncation applies
    - The hosts column header is the word Hosts; the todo column header
      is the word Todo; the delivery column header is the word Delivery
    - The delivery column carries the divergence marker — base,
      up-to-date, propagated, or need-update; an empty cell when the
      divergence is None
    - Every host name prints on its own grid line of the hosts column
    - A todo of None or an empty string renders an empty cell
    - The truncation marker is a single ellipsis character
    - An overlong status or host name is truncated like the other
      columns
    - A row divider — identical to the header separator row — closes every
      record: it prints after the wrapped statuses of each record, the
      last record included, and never between the continuation lines of
      one record
    - The table never exceeds `width`, with one documented exception: when
      the minimum columns no longer fit — below the narrow threshold of the
      active column rule — every column keeps its minimum of 8 and the
      table may exceed `width`; minimum readability wins over the width cap
      on ultra-narrow terminals

    Constraints:
    - Read-only on `entries` — do not mutate, do not re-sort, do not filter
    - Do not print the year or the artifacts

"render_topic_host_rows(records: list[BoardRecord], width: int, info: bool = False)":
  location: render.py
  annotations: |
    Render the per-host audit view as a table — one row per topic and
    hosting branch: topic, branch, and statuses, under `info` the todo
    and delivery columns sit between branch and statuses.

    `records`: the collected per-host records — already sorted by the domain
    `width`: the measured terminal width in columns
    `info`: True adds the todo and delivery columns and switches to
            the five-column width rule

    Apply the `click` practice for echo.

    Algorithm:
    1. Compute the column widths from `width` alone per the width rule of
       the requirements — the three-column rule without `info`, the
       five-column rule with it; the grid is fixed and independent of the
       record content
    2. Print one header row and one separator row with column and row
       dividers — the column order is topic, branch, todo, delivery,
       statuses under `info`
    3. Print each record: every text column truncated with an ellipsis when
       it exceeds its column, the statuses wrapped onto continuation lines
       without affecting the column widths
    4. Print one row divider after every record — the same dash run as the
       header separator — the last record included; the wrapped
       continuation lines of one record stay undivided
    5. Mark the record hosting the current branch with an asterisk; keep
       the remote prefix of a remote host visible in the branch column
    6. An empty `records` prints nothing

    Requirements:
    - Three-column widths: topic and branch get an equal share first,
      statuses take the remainder — each of topic and branch is capped at
      one third of `width` minus the dividers, statuses receives what is
      left, and every column keeps a minimum of 8 columns before
      truncation applies
    - Five-column widths under `info`: topic, branch, todo, and
      delivery get an equal share — each capped at one fifth of `width`
      minus the dividers, statuses receives the non-negative remainder,
      and every column keeps a minimum of 8 columns before truncation
      applies
    - The todo column header is the word Todo; the delivery column
      header is the word Delivery
    - The delivery column carries the divergence marker — base,
      up-to-date, propagated, or need-update; an empty cell when the
      divergence is None
    - A todo of None or an empty string renders an empty cell
    - The truncation marker is a single ellipsis character
    - An overlong status is truncated like the other columns
    - A row divider — identical to the header separator row — closes every
      record: it prints after the wrapped statuses of each record, the
      last record included, and never between the continuation lines of
      one record
    - The table never exceeds `width`, with one documented exception: when
      the minimum columns no longer fit — below the narrow threshold of the
      active column rule — every column keeps its minimum of 8 and the
      table may exceed `width`; minimum readability wins over the width cap
      on ultra-narrow terminals

    Constraints:
    - Read-only on `records` — do not mutate, do not re-sort, do not filter
    - Do not print the year or the artifacts

"render_board_json(board: list[BoardEntry] | list[BoardRecord])":
  location: render.py
  annotations: |
    Render the board as a pretty-printed JSON array on stdout — the
    machine-readable projection of either board view.

    `board`: the board to print — the aggregated entries of the default
             view or the per-host records of the audit view

    Apply the `beautiful_json` practice for the serialization format.
    Apply the `click` practice for echo.

    Algorithm:
    1. Shape every `BoardEntry` into the record with exactly the keys
       topic, branch, hosts, statuses, current, remote, todo,
       divergence; every `BoardRecord` into the same record without
       hosts; the todo key carries the summary string or None
    2. Serialize the array through the `beautiful_json` practice and
       echo it with one trailing newline
    3. An empty `board` prints []

    Requirements:
    - The record fields and shapes are a stable consumer contract —
      later changes stay additive; the key order is not part of it
    - The todo key is a string or null — never omitted
    - Every record carries the divergence key — base, up-to-date,
      propagated, need-update, or null when unresolved; always present,
      never omitted — an additive change to the stable record contract
    - Read-only on `board` — do not mutate, re-sort, or filter

    Constraints:
    - Do not render a table, a heading, or any line outside the JSON
      array

---

Author: Goga
CreatedAt: 29/08/26
Description: |
  The goga topics command group — board in its two views and its
  JSON form, create with the optional-value todo option, switch,
  delete, clear, update, publish, and propagate — over the topics
  domain.
