The admin header's breadcrumb trail for this module's pages — one place for the rules, so every LiveView reads the same way (panel round with codex/grok/zai, 2026-09-05):
Admin Panel / Projects / … every page of the module
Admin Panel / Projects / Tasks / New task subtab pages: subtab label as the crumb
Admin Panel / Projects / Test / Files sub-pages are crumbs, not "Test · Files"
Admin Panel / Projects / Parent / Child sub-projects show their parent chain
Admin Panel / Projects / Test / Add task "Add" attaches to the project crumb…
Admin Panel / Projects / Tasks / New task …"New" creates a standalone record
Admin Panel / Projects / Tasks / Edit <name> edit names its object (core's Users convention)The trail is core's contract: page_section (+ page_section_path) is
the module tab, page_crumbs the linked middle, page_title the plain
leaf. The project's List/Board/Timeline/Calendar tabs are views of one
place and never appear. Crumb labels reuse the subtab labels verbatim
so the trail mirrors the sidebar.
Known limit of the core contract, not fixed here: page_title is also
the browser tab title, so a short leaf ("Files", "Add task") makes a
weak tab title. Every panel seat flagged it — the fix is a separate
browser-title assign in core.
Summary
Functions
Section + the project's crumbs MINUS the project itself (for the project page, whose title is the name).
A project (or template) as crumbs: the ancestors the viewer may see,
root first, then the project itself — every one linked. lang picks
the localized name. scope is the viewer: an ancestor the viewer holds
no :view on is left out of the trail (its name is information about a
project this reader was refused — the module answers such a project as
"not found", and the trail must not say otherwise; the sweep, 2026-09-05).
A nil scope sees no ancestors.
The assigns every page of the module starts from: the Projects section.
The Tasks subtab as a crumb.
The Templates subtab as a crumb.
Section + one subtab crumb (:tasks | :templates).
Section + the project's crumbs, ready to assign/2 before page_title.
Functions
@spec above_project(PhoenixKitProjects.Schemas.Project.t(), term()) :: keyword()
Section + the project's crumbs MINUS the project itself (for the project page, whose title is the name).
@spec project(PhoenixKitProjects.Schemas.Project.t(), String.t() | nil, term()) :: [ map() ]
A project (or template) as crumbs: the ancestors the viewer may see,
root first, then the project itself — every one linked. lang picks
the localized name. scope is the viewer: an ancestor the viewer holds
no :view on is left out of the trail (its name is information about a
project this reader was refused — the module answers such a project as
"not found", and the trail must not say otherwise; the sweep, 2026-09-05).
A nil scope sees no ancestors.
@spec section() :: keyword()
The assigns every page of the module starts from: the Projects section.
@spec tasks() :: map()
The Tasks subtab as a crumb.
@spec templates() :: map()
The Templates subtab as a crumb.
@spec under(:tasks | :templates) :: keyword()
Section + one subtab crumb (:tasks | :templates).
@spec under_project(PhoenixKitProjects.Schemas.Project.t(), term()) :: keyword()
Section + the project's crumbs, ready to assign/2 before page_title.