The logo block stopped wherever its text happened to end (~140px) while the
rail below it is 208px, so the left edge was a step rather than a column.
Both now read `asideWidth()` from src/layout.ts, so they cannot drift apart,
and collapsing the rail narrows the logo block with it — the wordmark is
dropped and the mark centres in the 56px strip. The block also carries the
rail's separator up through the header, so the column reads as one piece.
The header's own 20px padding had to go for this to work: the logo block has
to start at x=0 to be flush with the rail, so the nav and the right-hand mark
now own their insets. The mark's inset comes from the same `ASIDE_INSET` as
the rail's section title, so the two line up.
A side effect worth keeping: the rail is 208px and the top nav's first item
and .el-main are each inset a further 20px by their own Element Plus defaults,
so the nav text, the page body and the page header all start on one vertical
line.
The widths live in a module rather than CSS custom properties because el-aside
takes its width through a prop and would otherwise fight an inline style.
The rail was on the right. It now leads the row instead, so the section menu
reads as a conventional left-hand navigation column.
Which side it lands on was never a stylesheet decision: `.app-body` is a flex
row and the aside is simply its first child now, with `.app-main` after it.
The only style change that had to follow is the rail's separator, which moves
from its left edge to its right, plus the collapse tooltip's placement.
The global shell contract in base.css now says this explicitly, so the next
person to move the rail knows the side comes from DOM order rather than from a
rule in that file.
Records the decisions that are not visible from the code alone:
- why section_field has no parent_id, and what reuse that buys;
- that template_field.sort is the single source of display order, and
that repeats and ties are deliberately legal;
- that templates reference library fields rather than copying them, which
is what makes a rename propagate;
- that TiDB parses FOREIGN KEY and ignores it, so the integrity lives in
the application layer.
Also adds the API table, the frontend route table, and the seed script.