Catch up web/README.md and two stale comments to current code

web/README.md's Layout section hadn't been touched since early in the
session: it was missing ManageMenu.vue, CardManageMenu.vue,
StatusManager.vue, useDialog.ts, and four of the ten views
(ProjectExploreView, ProjectKanbanView, CardView, CardConfigureView)
entirely, still described CardRow as inline-editable-with-a-delete-
button, and still claimed the top-level <RouterView> was keyed by
plain route.path (true before this session's Explore/Kanban route
split, wrong after -- see App.vue's routeKey). The Inbox section also
still gated the post-drag cards refresh on route.name === 'project'
alone, from before Explore itself could send a card there.

Also fixed two lingering "all tasks" references (the tab's name before
it became "Explore") in a stores/cards.ts comment and a style.css one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 01:48:14 +01:00
co-authored by Claude Sonnet 5
parent fb67b57807
commit ef9669dc57
3 changed files with 48 additions and 29 deletions
+44 -25
View File
@@ -29,22 +29,39 @@ build 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)
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 (CRUD; no reordering -- see 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
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/components/AppSidebar.vue left nav: Dashboard link, project dropdown, Inbox + form
src/components/CardRow.vue editable text + status chip + delete, one card
src/components/KanbanCard.vue small draggable card for the board columns and the inbox
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/ProjectManageMenu.vue "Manage" dropdown + delete-project modal, shared by ProjectView and ProjectConfigureView
src/views/ DashboardView, ProjectView, ProjectConfigureView, LoginView,
ProfileView, VerifyEmailView
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:
@@ -54,9 +71,10 @@ divider, a project `<select>`, another divider, then the **Inbox** (see below).
The dropdown is a `v-model`-bound writable `computed` (`selectedProjectId`):
its getter reads the open project from `route.params.id`, so it tracks
whichever project is current; its setter `router.push`es to the chosen one, so
it also works as a project switcher from anywhere. `<RouterView
:key="route.path">` remounts the view on every path change so switching
projects always does a fresh load.
it also works as a project switcher from anywhere. `App.vue`'s top-level
`<RouterView>` is keyed so switching projects (or cards) always does a fresh
load -- see [Project detail](#project-detail) for why that key isn't simply
the path.
Below `768px` (`style.css`'s one layout breakpoint) the sidebar becomes an
off-canvas drawer instead of sitting beside the page: `position: fixed`,
@@ -117,13 +135,14 @@ handful of conventions worth knowing before adding to it:
A card with no project lives in the caller's inbox (`useInboxStore`), rendered
in the sidebar under the project list -- not per-project, and not tied to
whatever page is open. It's a `vuedraggable` list in the same `"kanban"` drag
group as every project's kanban columns (below), so a card can be dragged
straight out of the sidebar into any status column of whichever project is
currently open, or the other way. `AppSidebar`'s `onInboxChange` persists a
drop via `reorderColumn(null, null, ids)`, then reloads the inbox and, if a
project view is currently mounted (`route.name === 'project'`), that project's
cards too -- either side of a drag could have been the inbox. A small form
under the list adds a card straight to the inbox.
group as a project's Kanban columns and Explore list (below), so a card can
be dragged straight out of the sidebar into any status column of whichever
project is currently open (or Explore can send one the other way, though not
receive one -- see [Explore](#explore)). `AppSidebar`'s `onInboxChange`
persists a drop via `reorderColumn(null, null, ids)`, then reloads the inbox
and, if a project's Explore or Kanban route is currently open, that
project's cards too -- either side of a drag could have been the inbox. A
small form under the list adds a card straight to the inbox.
Empty (no cards) is shown as a subtle dashed drop-area rather than blank
space -- `.kanban__cards:empty` in `style.css`, so it's pure CSS keyed off the
+3 -3
View File
@@ -7,9 +7,9 @@ type CardPatch = Partial<Pick<Card, 'text' | 'complete'>>
export const useCardsStore = defineStore('cards', () => {
// One project's cards, grouped by status then position. Views re-sort as
// needed (the "all tasks" list is alphabetical). Moving a card in or out of
// this project (including via the inbox) goes through lib/cardOrder.ts,
// not this store -- callers re-load() afterwards.
// needed (Explore's list is alphabetical). Moving a card in or out of this
// project (including via the inbox) goes through lib/cardOrder.ts, not
// this store -- callers re-load() afterwards.
const cards = ref<Card[]>([])
const projectId = ref<number | null>(null)
const loading = ref(false)
+1 -1
View File
@@ -1036,7 +1036,7 @@ button[type="submit"]:disabled:not(.btn-icon),
/* A small square icon button, bordered like the compact field it sits
beside -- every "add to X" form (a kanban column, the inbox, the status
list, the "all tasks" new-card form) used a full "Add" label until there
list, Explore's own new-card form) used a full "Add" label until there
were enough of them on screen at once to feel cluttered, then a borderless
ghost icon on its own looked adrift next to that field. Excluded
(":not(.btn-icon)" above and on the .field-row/.kanban__new overrides)