Show a project with a vertical timeline of assignments. Supports inline status changes, duration editing, dependency management, and tracks who completed each task.
List / Timeline / Calendar tabs
The page has three views — the vertical task list, the embedded
ProjectGanttLive Timeline, and the embedded ProjectCalendarLive month
calendar — toggled by tabs under the shared header. Switching is instant (an
assign flip) and each nested LV is lazily mounted on first open, then kept
(so the gantt's zoom/expand and the calendar's month navigation survive
switching back). The tabs render in every mount context, embedded
live_render renders included (only templates stay list-only). Note this
means the Timeline/Calendar tabs are nested live_renders, which are
themselves nested LVs when the show page is embedded — deliberate,
server-rendered, so both views show even before any JS loads.
Keeping the URL in sync (the trailing /gantt / /calendar segment) is
optional and off by default: it's only on when @tab_url_sync? is true,
which the router-mounted standalone admin page sets (so its deep-linking
keeps working) and an embed can opt into via session["tab_url_sync"]. When
on, the ProjectTabsUrl JS hook REPLACES the current URL on each switch —
deep links / copy / reload land on the right tab, while back/forward return
to the previous page. Deliberately no per-tab history entries: they'd need
this LV to export handle_params/3 (which would block live_render
embedding), and LiveView's popstate handler treats foreign pushState entries
as live navigation — remounting and crashing on the missing callback. With
sync off the tabs still switch fully — they just never touch the host page's
URL (the right default for an embed, which must not rewrite the host's
address bar).