Skip to content

templates/family-root/scripts/siblings.py: the layout table pads each member's name by code points, not by the columns a terminal gives it, so a wide or combining name's state column stands out of line (the family-root twin of #1034) #1216

Open

Description

@brettheap

Goal: the layout table that make siblings prints pads each name by the columns a terminal gives it, so a wide or combining name's state column starts where every other row's does, and an ASCII name prints byte for byte as it does today.

Filed by lane openRepoShape-3, from RV1178's independent review of #1178 (#1034) at fa56168a (#1178 issuecomment-6351653129), its follow-up for templates/family-root/scripts/siblings.py:729; the delta review at c4beba19 (#1178 issuecomment-6353960809) is READY and takes the follow-up no further. It did not hold the landing. #1178 is open at c4beba19, and its diff against main ef619c74 leaves siblings.py, scripts/validate-repository-naming.py and scripts/repo_shape.py alone, so the defect is on main now. Line numbers are main's at ef619c74.

The row pads a name by code points

State. Sibling.row (templates/family-root/scripts/siblings.py:725) returns f" {kind:<9} {spelled(self.project):<18} {self.state:<22} " and then spelled(self.branch) (:729-730). A format width counts code points. row is every row of the layout: the holder, sibling and pinned rows (:1623, :1639, :1641) and the dispatch's rows (:1289). Each table's header is a literal (:1287 and :1635, {'what':<9} {'name':<18} {'state':<22} branch). spelled (scripts/repo_shape.py:3645) escapes controls, surrogates, line and paragraph separators and bidirectional controls and, its docstring says, prints every other character as it is, so a wide character or a combining mark reaches the pad whole. self.project is a family manifest's project: text.

What happens. Arithmetic from the code and the standard library's unicodedata, not a run of the tool: RV1178 measured adopt-project.py's table and listed this one as a follow-up without a measurement. Under the rule #1178 uses (no column for categories Mn, Me and Cf, two for East Asian width W or F, one otherwise):

  • a name of the three characters U+65E5 U+672C U+8A9E is 3 code points, so the pad adds 15 spaces and the field is 21 columns wide, 3 more than an ASCII name's 18: its state starts 3 columns to the right of every other row's;
  • e U+0301 cole is 6 code points and 5 columns, so its field is 17 columns wide and its state starts 1 column to the left.

On a stdout that cannot hold a character, escape_unencodable_stdout (called at :1743) makes the handler backslashreplace, so U+65E5 reaches the terminal as \u65e5: six columns for one code point that the pad counted once (by reading that function's docstring in scripts/repo_shape.py; not run).

Direction. Pad by display columns, as #1178 does for adopt-project.py's plan table (_columns at adopt-project.py:6437 and _padded at :6460 at c4beba19, with ZERO_COLUMN_CATEGORIES, TWO_COLUMN_WIDTHS and the two Hangul jamo sets), and measure the text as stdout will write it. An ASCII name keeps its width exactly; a name wider than the field is left as it is, with no truncation. No command and no flag.

  • Where the rule lives is the writer's choice, and the PR says which. This file is a pinned copy every holder carries. A holder that kept an older repo_shape.py (update-shape.py apply --accept-local) must still run it, and an import of a name that copy lacks would stop make siblings, park and resume (the comment at :1520-1524). So the simple form is the rule written in this file with unicodedata, which it can import as os and re are imported.

  • The change moves this file's bytes, so holders see an upstream-changed row for it, as bootstrap.py, and the family root's validate-family.py, bootstrap.py and siblings.py: a root whose own .git this user cannot read is a PermissionError traceback on 3.12, or not-a-git-repo where exists() answers False, not refused by name #1148 says of this file and three other pinned copies.

  • The test is tests/test_siblings_family_name.py. Its layout() (:111) collapses runs of spaces (" ".join(line.split())), so no row there can see a pad; the new rows read the raw line, with the member rows its fixtures already build (ROWS, with_rows, about :290-300).

  • A member whose project: is U+65E5 U+672C U+8A9E: the state word starts at the same display column as in an ASCII member's row, on a UTF-8 stdout. The row fails before and passes after.

  • The same for a name holding e U+0301.

  • The same two rows on an ASCII stdout (NARROW, tests/test_bootstrap_spelling.py:86): the escaped form is counted as stdout writes it.

  • An ASCII name prints byte for byte as before: the file's existing rows pass unchanged.

  • One mutant, the pad back to len, fails a new row; each mutant run is restored and checked with cmp.

Related: #1178, #1034 (adopt-project.py's plan table, the same defect), #1148 (names this file among four pinned copies), and #459 (the tracker for setup, scaffold, family and the shared helpers).

Size: S.

Lane: openreposhape-3 (openRepoShape-3)

Activity

  1. added this to the v1.2.1 milestone on Oct 23, 2026
  2. brettheap commented on Oct 23, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-3, session b30e8b49-f249-4668-adb1-1dbfa93c09e1@Eagle, 2026-10-23T18:37:56Z, for #1216

    Logged in lanes/log/openRepoShape-3.md at ff485dd578e248c17dbd9c8d1a85ea73a7909e39.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#1216:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#1216"`, kept where the row or its body names `#1216`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `1216` as a whole token:
       none
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions