Make Explore and Kanban real routes, not tab state

/projects/:id (name "project") is now Explore; /projects/:id/kanban
(name "project-kanban") is Kanban. ProjectView.vue becomes a layout:
header + sub-nav, loading the project and its cards (both children
need the cards list) and rendering the active one via <RouterView>.
ProjectExploreView.vue and ProjectKanbanView.vue hold what used to be
each tab's own template/logic; Kanban additionally loads its own
statuses, since Explore has no use for them.

The sub-nav is now RouterLinks (active state matched on route.name),
not buttons toggling local state.

App.vue: the top-level <RouterView> was keyed by the full route path
to force a fresh instance per project/card id -- with Explore/Kanban
now separate paths under one layout, that would also remount the
layout (and re-fetch the project) on every tab switch. Keyed by the
matched route's top-level path + params instead, which is the same
value for both of a project's child routes.

AppSidebar: the project switcher and the inbox-drag refresh both used
to check route.name === 'project' for "this project is open" -- fixed
to cover both routes for the switcher, and narrowed to
'project-kanban' specifically for the inbox-drag refresh, since Kanban
is the only route with a draggable list a card could have moved
to/from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 00:46:17 +01:00
co-authored by Claude Sonnet 5
parent 81900f4d06
commit 8641c3d013
9 changed files with 329 additions and 242 deletions
+26 -13
View File
@@ -135,18 +135,27 @@ ghost. The same rule covers every kanban status column too (below).
## Project detail
`/projects/:id` shows one project. It renders on a **full-width** layout (the
route sets `meta.wide`, which widens `.app__main` in `App.vue`), so the header
spans the full width and the **Manage** menu sits top right. The title is a
plain heading, with an inline `.title-back` arrow to the dashboard right
before the text -- renaming lives on the configuration view (below). Manage
(`ProjectManageMenu.vue`) has a **Configure** link (to that view) and a
**Delete project** action that opens a confirmation modal; confirming calls
`DELETE /api/projects/:id` and returns to the dashboard.
`/projects/:id` shows one project. `ProjectView.vue` is a **layout**, not a
page of its own: it renders on a **full-width** layout (the parent route sets
`meta.wide`, inherited by its children, which widens `.app__main` in
`App.vue`), loads the project and its cards, and renders the header + a small
sub-nav — its two children (below) render into its `<RouterView>`.
Below the header are two tabs (local `activeTab` state, `v-show` so both stay
mounted). The tab order is fixed — **Explore** first, **Kanban** second — but
`activeTab` initialises to `'kanban'`, so a project opens on the board.
The header: a plain title heading, with an inline `.title-back` arrow to the
dashboard right before the text -- renaming lives on the configuration view
(below) -- and `ProjectManageMenu.vue` top right, with a **Configure** link
(to that view) and a **Delete project** action that opens a confirmation
modal; confirming calls `DELETE /api/projects/:id` and returns to the
dashboard.
The sub-nav (`RouterLink`s styled as tabs, active one matched on `route.name`)
is real navigation, not client-side tab state -- **Explore** is the project's
own route (`/projects/:id`, name `project`), **Kanban** a child beneath it
(`/projects/:id/kanban`, name `project-kanban`). Both read the `cards` store
the layout already loaded; App.vue's top-level `<RouterView>` key is derived
from the matched route's *top-level* path plus params rather than the full
path, so switching between them doesn't remount the layout (and re-fetch the
project) the way switching to a different project's id still does.
### Explore
@@ -160,8 +169,12 @@ sits outside that link.
One column per project status, in `position` order -- the inbox is *not* a
column here; it's in the sidebar (see above), though it's still a valid drag
source/target. `board` is derived from `cards.cards` + the project's statuses
and rebuilt by a `watch` whenever either changes.
source/target (the only view where that's true -- Explore has no draggable
list of its own, which the sidebar accounts for when deciding whether to
refresh a project's cards after an inbox drag). Unlike the cards, statuses
are this route's own fetch (`GET /api/projects/:id/statuses`) -- Explore has
no use for them. `board` is derived from `cards.cards` + those statuses and
rebuilt by a `watch` whenever either changes.
Every drop — whether reordering within a column (`moved`) or dragging in from
another column or the sidebar's inbox (`added`) — calls