Opening a task the user has no permission for returned a 403, which
fell through the task detail load handler's 404-only catch. The empty
pre-initialized task model stayed visible, rendering a broken shell
(#0, no title, "Created 57 years ago"). Treat 403 like 404 and route
to the not-found page.
Reset/confirm/deletion tokens (kinds 1-3) are now stored as a SHA-256 hash and
looked up by hash, so the e2e factory must seed the hash while returning the
plaintext to the test. Kind 4 (caldav/bcrypt) is seeded verbatim.
role=toolbar with a roving tabindex: Arrow keys move between buttons,
one Tab stop, Tab leaves the toolbar, and the tab stop stays valid when
buttons become disabled. Covered by an e2e keyboard-navigation spec.
Also fixes the table-edit :disabled bindings, which referenced
editor.can().X as a property (always truthy) so those buttons never
actually disabled — surfaced by review while testing the roving focus.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP
The white hero heading sat directly on the photo, where its contrast
depends on the image content. A bottom-weighted gradient scrim keeps it
readable across the whole picture.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP
Opens the existing alt-text prompt right after an image is inserted
(upload, paste and URL flows) so every new image gets a description.
Cancelling leaves the image without alt text, and the node is deselected
afterwards so the next insert appends instead of replacing.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP
Each attachment row was a button containing the download, copy-URL,
delete and set-cover buttons — interactive elements nested inside an
interactive element, which is invalid and confuses assistive-technology
focus (axe: nested-interactive). The row is now a non-interactive
container: the filename (and thumbnail) is the trigger that opens or
downloads the attachment, and the action buttons are its siblings.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP
The fuzzy mirror test is ParadeDB-only and fails without the previous
commit (child returned as a second root). The multi-parent test pins the
NOT EXISTS semantics from #3197: one in-scope and one out-of-scope
parent must still nest the child.
Part of #2954
The parent root-membership mirror always used the ILIKE fallback, so on
ParadeDB a parent matching the search only via fuzzy/token matching kept
its child as a duplicate root, and a substring-only ILIKE match could
hide a child under a parent absent from the results. The assumed
limitation no longer holds: pg_search plans ||| against the aliased
parent_tasks inside the correlated NOT EXISTS as a regular BM25 scan
(verified on 0.24.1), and score-free predicates never triggered the
"unsupported query shape" error on older versions either.
Part of #2954
Add disclosure semantics to dropdown/popup triggers (aria-expanded, Escape to
close with focus return, drop the incorrect role=menu), aria-sort plus
per-column labels on the table view, a debounced polite live region announcing
quick-actions result counts, aria-pressed on editor toggle buttons, and an
aria-label on the rich-text editing region.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP
echo v5.3.0 reverted to v4 behavior where every group with middleware
implicitly registers a 404 route at its prefix. Our route setup creates
multiple groups with the same prefix (rate-limit subgroups of /api/v1),
which now panics with a duplicate-route error at startup.
Five PUT-routed "create" endpoints document @Success 200, but they all
dispatch through the shared CreateWeb handler (pkg/web/handler/create.go),
which always responds 201 Created. Every other CreateWeb-routed endpoint
(labels, teams, filters, task relations/comments/assignees/duplicate,
task-labels) already correctly documents 201 - these five are stragglers
with the same stale annotation.
Affected endpoints:
- PUT /tokens
- PUT /projects/{id}/views/{view}/buckets
- PUT /projects/{id}/webhooks
- PUT /{kind}/{id}/reactions
- PUT /projects/{project}/views
Doc-only change, no behavior change. Regenerated pkg/swagger/* via
`swag init -g ./pkg/routes/routes.go --parseDependency -d . -o ./pkg/swagger`
(the command mage generate:swagger-docs runs) so the generated docs match
the annotations.
Adds translated aria-labels to the 42 icon-only BaseButton/XButton
usages the new lint rule found — attachment actions, assignee/label
remove buttons, multiselect clear buttons, table sort triggers, the
TipTap bubble menu (which only had v-tooltip, which sets no accessible
name), datepicker toggles, and more. Labels reuse the existing tooltip
translation keys where present and interpolate item names where
available (e.g. "Remove {user} as assignee").
With zero remaining violations the rule is raised from warn to error.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP
BaseButton renders a bare button and does not enforce a name, so icon-only
usages without aria-label are announced as just "button" — the a11y audit
found this pattern regenerating across the codebase (42 current instances,
including the TipTap bubble menu where v-tooltip provides no accessible
name). The new vikunja/icon-button-accessible-name rule flags icon-only
BaseButton/XButton usages that have no aria-label, aria-labelledby, title,
visible text, or sr-only text; unknown child components are assumed to
render text to avoid false positives.
Set to 'warn' until the existing instances get aria-labels, then raise to
'error'.
Claude-Session: https://claude.ai/code/session_018prq5WqRL38EGhPNQKqZUP