# 13 — Frontend (Vue 3 SPA) Vue 3 + TypeScript + Vite + Pinia + Vue Router + Tailwind, pnpm, in `web/`. Styling authority is [14-design-system.md](14-design-system.md). All API access through one typed client; all live updates through one SSE module. ## Structure - [ ] `web/src/api/client.ts` — typed fetch wrapper (base `/api/v1`, JSON, error envelope → typed `ApiError`, cookie credentials) - [ ] `web/src/api/sse.ts` — EventSource wrapper: connect after login, auto-reconnect with backoff, dispatch frames to the `realtime` store, expose `onReconnect` hooks (pages refetch) - [ ] `web/src/router/index.ts` — routes below; global guard redirects unauthenticated → `/login` (bootstraps via `GET /me`) - [ ] Layout: `AppShell` (fixed sidebar + navbar + ``), auth pages bare - [ ] Vite dev proxy: `/api`, `/files`, `/webhooks` → Go server port ## Routes & pages - [ ] `/login` — email/password + OAuth buttons (hidden per config) - [ ] `/register` — sign-up (notes that first user becomes admin) - [ ] `/oauth/callback` — post-OAuth landing, bootstraps session, redirects - [ ] `/` — **Dashboard**: favorite + recent projects, "assigned to me" issue list, cross-project activity feed - [ ] `/inbox` — notification inbox (unread filter, mark read/all) - [ ] `/search` — full search page (type tabs) - [ ] `/projects` — project cards (favorites first, status/priority badges, favorite toggle) - [ ] `/projects/:id` — **Project overview**: description (markdown), members, links, attachments, dependencies (both directions), activity feed - [ ] `/projects/:id/issues` — **Issue table**: sortable columns, FilterBar, URL-synced filters/sort, row click → detail - [ ] `/projects/:id/board` — **Kanban**: 7 fixed status columns, drag-and-drop (optimistic), card = `#N`, title, labels, assignee avatar, points badge, blocked indicator, epic color strip - [ ] `/projects/:id/issues/:num` — **Issue detail**: editable title/description, sidebar (status, priority, points, assignee, epic, dates, labels, watchers, watch toggle), sub-issues list (+create), blockers list, attachments, links, activity feed ∪ comments with resolve, MarkdownEditor for comments - [ ] `/projects/:id/epics` — epic list with progress bars (counts + points), status filter - [ ] `/projects/:id/epics/:epicId` — epic detail: description, status, color, ordered issue list (drag to reorder, attach existing / create new) - [ ] `/projects/:id/wiki` — page list; `/projects/:id/wiki/:slug` — view; `/projects/:id/wiki/:slug/edit` + `/projects/:id/wiki/new` — edit/create with preview - [ ] `/projects/:id/settings` — tabs: general (fields, status, git URL, transfer, delete) | members | labels | webhooks (incoming secret, outgoing CRUD + delivery log) | export | danger zone — owner-gated tabs hidden for members - [ ] `/settings` — tabs: profile | security (password) | tokens (PAT create/reveal-once/revoke) | notifications (email_mode) - [ ] `/admin/users` — admin only: user list, admin toggle, delete - [ ] 404 page; project-scope 404s render "not found or no access" ## Pinia stores - [ ] `auth` — me, bootstrap, login/logout, guards - [ ] `projects` — list + current project (members, labels, dependencies) - [ ] `issues` — table query state (filters/sort/page, URL-synced), detail cache - [ ] `board` — per-status ordered lists, optimistic move (rank computed client-side between neighbors, PATCH sent, rollback on error) - [ ] `epics` — list + detail + ordering - [ ] `comments` — per-issue comments + resolve state - [ ] `wiki` — page list + current page - [ ] `notifications` — unread count, inbox, mark-read - [ ] `realtime` — SSE connection state; routes `data` frames: if the pointed-at resource is in an active store (current board/table/detail/feed), patch or refetch it; `notification` frames → `notifications` store + toast - [ ] `search` — palette state, recents (localStorage) - [ ] `ui` — toasts, modals, sidebar collapse ## Shared components - [ ] `KanbanBoard` / `KanbanColumn` / `IssueCard` — drag via vuedraggable or a small pointer-events implementation; column headers show count - [ ] `IssueTable` + `FilterBar` (status/assignee/label/priority/epic pickers + text search) + `SortHeader` - [ ] `MarkdownEditor` — textarea + write/preview tabs, `@` mention autocomplete (project members), `#` issue autocomplete, paste/drop image → attachment upload - [ ] `MarkdownView` — markdown-it + DOMPurify, mention chips, `#N` router-links, code highlight - [ ] `ActivityFeed` — renders audit entries (icon + actor + verb + target + relative time) - [ ] `NotificationBell` — unread badge, dropdown of latest 10, link to `/inbox` - [ ] `CommandPalette` — Cmd/Ctrl+K overlay ([10-search.md](10-search.md)) - [ ] Badges/pills: `StatusBadge` (per-status colors), `PriorityBadge`, `PointsBadge`, `LabelPill` (label color), `EpicPill` (epic color) - [ ] `UserAvatar` (initials fallback) + `AssigneePicker` + `MemberPicker` - [ ] `EpicProgressBar` (counts + points tooltip) - [ ] `WatchButton`, `FavoriteStar` - [ ] `AttachmentUploader` (drag-drop zone) + `AttachmentList` (image preview, download, delete) - [ ] `DatePicker`, `ConfirmDialog`, `ToastHost`, `EmptyState` (illustrated, per design system) ## Behaviors - [ ] Optimistic updates for board drags and inline status/assignee edits; rollback + toast on API error - [ ] URL is the source of truth for table filters/sort (shareable, refresh-safe) - [ ] SSE reconnect → refetch active page data + unread count ([09-notifications-realtime.md](09-notifications-realtime.md)) - [ ] Keyboard: Cmd/Ctrl+K palette; `c` new issue on board/table pages; Esc closes modals - [ ] Every list has loading skeletons + designed empty states; destructive actions use `ConfirmDialog` - [ ] Route titles set per page (`SoloPM — {project} — {page}`)