//! The user dashboard's Projects panel, described. //! //! Second of the tier-1 batch (wiki `mnw-server-conversion-plan`, "The S4 tab //! inventory"): 53 lines, four `hx-` attributes, no `data-action`, no //! `
`, no `{% include %}`, and nothing in `static/` or `frontend/src` //! reaches for any id it writes. //! //! It replaces `UserProjectsTabTemplate` and the template, and leaves //! `routes::pages::dashboard::tabs::user::build_projects` in place answering //! described markup instead of an Askama render. //! //! # A fill, not a mounted screen, and the ETag is why //! //! Every conversion before this one took its address off the Askama router and //! answered it through `super::mount`. This one must not: `dashboard_tab_projects` //! answers a conditional GET keyed on the user's cache generation //! (`helpers::check_etag` / `with_etag`), and `mount` has no way to say //! "304 if the generation has not moved". Mounting it would trade a working //! conditional GET for a tidier route table. //! //! So this follows [`super::project_content`] instead, which stayed on its //! Askama handler for the same reason (see `project_tabs::project_tab_content`, //! where the ETag is read before the panel is built). The handler and the route //! survive; what dies is the template. //! //! # It has two callers, which is why there are two entry points //! //! Projects is the tab the dashboard opens on, so it is rendered *inline* into //! the page as well as answering its own address when the strip fetches it. //! [`fill`] is the inline half and [`fragment`] the addressed half. The strip //! already draws a region carrying [`REGION`], so the inline markup must not //! wrap itself in a second one, and a route's answer must. //! //! # The empty state moves, and stops being reachable at the same time as the list //! //! The template renders the project loop and *then* asks whether the list was //! empty, so an account with no projects drew an empty `role="list"` followed by //! a Getting Started box. Described, the two are alternatives: a //! [`layout::Readiness::Empty`] stand-in carries the same four steps and the //! same call to action, and the list is simply not there. Nothing a reader can //! see changes, and the `
` with no rows in it does. //! //! # What the description says that the markup did not //! //! Every project card was an `
` holding an `

`, two `
`s of //! metadata, a badge and three controls, and the three controls were an anchor, //! an anchor and a `