# Project Manager — web Vue 3 + TypeScript + Vite PWA. Talks to the REST API in the parent directory. ## Develop ```bash npm install npm run dev # http://localhost:5173 ``` The dev server proxies `/api` to `http://localhost:8080` (the Dockerised API — run `docker compose up -d` in the parent directory first). Override the target with `VITE_PROXY_TARGET`, or point the app at a different API entirely with `VITE_API_BASE_URL` (see [.env.example](.env.example)). ## Build ```bash npm run build # type-checks, then emits dist/ npm run preview ``` The parent `Dockerfile` runs this build in a Node stage and copies `dist/` into the PHP image's `public/`, so the `app` container serves the compiled SPA at `/`. There is no separate frontend container — a production image is `docker composebuild app` from the parent directory. ## Layout ``` src/main.ts App bootstrap; resolves the stored session before mount src/router/index.ts Routes + guard (redirects to /login when unauthenticated) -- a project's Explore/Kanban are nested children of ProjectView src/stores/auth.ts Pinia store: token in localStorage, magic-link + fetchMe src/stores/projects.ts Pinia store: the user's projects (fetch + create) src/stores/cards.ts Pinia store: one project's cards -- load/add/patch-in-place; delete and reordering happen elsewhere (ManageMenu.vue, lib/cardOrder.ts) src/stores/inbox.ts Pinia store: the caller's global inbox (fetch + create) src/lib/api.ts fetch wrapper, bearer token, typed ApiError, notFoundOr() (a caught error -> a 404-aware user-facing message) src/lib/cardOrder.ts reorderColumn() -- PUT /api/cards/order, shared by the sidebar and kanban board src/lib/webauthn.ts base64url <-> ArrayBuffer + the register/login passkey ceremonies src/composables/useDialog.ts Escape-to-close + focus-on-open for a dialog or menu -- shared by ManageMenu.vue and StatusManager's reassign modal src/components/AppSidebar.vue left nav: Dashboard link, project dropdown, Inbox + form src/components/CardRow.vue one row in the Explore list: links to the card's own view + a status chip -- no inline editing, no delete (both live on that view now) src/components/KanbanCard.vue small draggable card, itself a link to the card's own view; used by the kanban board and the inbox src/components/PasskeyNotice.vue dismissible "add a passkey" banner across the top of the page src/components/ManageMenu.vue generic "Manage" dropdown + delete-confirmation modal, shared by both entity pairs below src/components/ProjectManageMenu.vue / CardManageMenu.vue thin wrappers over ManageMenu.vue supplying what's project-/card-specific: routes, labels, and what deleting actually does src/components/StatusManager.vue a project's status list -- drag to reorder, add, delete with reassignment; used by ProjectConfigureView src/views/ DashboardView, LoginView, ProfileView, VerifyEmailView, ProjectView (layout) + ProjectExploreView + ProjectKanbanView, ProjectConfigureView, CardView, CardConfigureView ``` Signed-in "app" routes (`meta.requiresAuth`) render inside a persistent shell: the top bar, then a left **sidebar** (`AppSidebar.vue`) beside the routed view. The sidebar stays mounted across navigation — it holds a **Dashboard** link, a divider, a project ``/`