Surface custom repo title across shelf, canvas, toolbar, and settings via static state
Replaces the per-leaf @Shared(.repositorySettings(rootURL))
subscriptions from the previous attempt with a reducer-managed
dictionary on RepositoriesFeature.State. The old approach created a
fresh @Shared wrapper instance per call inside hot-path methods
(orderedShelfBooks, toolbarNotificationGroups, the canvas card
loop), which on cache miss triggered a settingsFile write that
notified all settingsFile subscribers, re-ran view bodies, re-built
the wrappers, and looped — pegging CPU at 90%+ on launch.
Now the flow is:
- RepositoriesFeature.State gains
`repositoryCustomTitles: [Repository.ID: String]`.
- Four new actions manage the dict: refreshAllCustomTitles
(full batch reload, runs in a reducer effect that's safe to read
@Shared from), refreshCustomTitle(URL) (single-repo refresh),
customTitlesLoaded(dict), customTitleUpdated(id, title?).
- AppFeature wires the triggers: it forwards
`.repositories(.delegate(.repositoriesChanged))` to
`.repositories(.refreshAllCustomTitles)`, and forwards
`.settings(.repositorySettings(.delegate(.settingsChanged(url))))`
to `.repositories(.refreshCustomTitle(url))`.
- All display sites read the dict statically:
- RepoDisplayName is now a pure-props view (no @Shared).
- Sidebar (RepoHeaderRow/RepositorySectionView), settings list,
settings detail nav title, and RepositoryDetailView take the
resolved custom title as a String parameter.
- ShelfBook.orderedShelfBooks and
toolbarNotificationGroups accept a customTitles dict; the shelf
spine, toolbar notifications popover, and canvas card all pass
`state.repositoryCustomTitles`.
Tests cover the dict-mutation actions
(customTitlesLoaded/customTitleUpdated state guards) and
parameterized model methods (orderedShelfBooks +
toolbarNotificationGroups override + fallback). The previous attempt's
hot-path tests are dropped — the methods are now pure with no
@Shared dependency to mock.