Repository navigation
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
Description
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+8A9Eis 3 code points, so the pad adds 15 spaces and the field is 21 columns wide, 3 more than an ASCII name's 18: itsstatestarts 3 columns to the right of every other row's; e U+0301 coleis 6 code points and 5 columns, so its field is 17 columns wide and itsstatestarts 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 stopmake siblings,parkandresume(the comment at:1520-1524). So the simple form is the rule written in this file withunicodedata, which it can import asosandreare imported. -
The change moves this file's bytes, so holders see an
upstream-changedrow for it, as bootstrap.py, and the family root's validate-family.py, bootstrap.py and siblings.py: a root whose own.gitthis user cannot read is a PermissionError traceback on 3.12, ornot-a-git-repowhereexists()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. Itslayout()(: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:isU+65E5 U+672C U+8A9E: thestateword 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 withcmp.
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
brettheap commentedon Oct 23, 2026
CLAIMED — lane openRepoShape-3, session b30e8b49-f249-4668-adb1-1dbfa93c09e1@Eagle, 2026-10-23T18:37:56Z, for #1216
Logged in
lanes/log/openRepoShape-3.mdatff485dd578e248c17dbd9c8d1a85ea73a7909e39.The three reads (lane-collision-protocol Rule 1):