Remove email, name, emailRemindersEnabled, and isLocalUser from user JWT
claims, and isLocalUser from link share JWT claims. These fields are never
used from the token - the backend always fetches the full user from the
database by ID, and the frontend fetches user data from the /user API
endpoint immediately after login.
Also simplify GetUserFromClaims to only extract id and username, and
remove the now-unnecessary email override in the frontend's
refreshUserInfo.
route.name can be undefined during initial route resolution or for
unnamed routes. Without this guard, AUTH_ROUTE_NAMES.has() would
return false and the authenticated layout could flash briefly.
Move the list of authentication route names (login, register, password
reset, openid, link-share) into a shared constant in
src/constants/authRouteNames.ts. Use it in both App.vue (layout gate)
and router/index.ts (auth redirect guard) to keep them in sync.
The v-if in App.vue switches to the authenticated layout (navbar +
sidebar) as soon as authStore.authUser becomes truthy. But Vue's
reactivity flush runs before the await continuation in submit(), so
the layout swaps while the route is still /login, causing the login
form to flash inside the authenticated shell for ~250ms.
Fix by adding a route check: don't show the authenticated layout while
the current route is an auth page (login, register, password reset,
openid). The NoAuthWrapper stays visible until redirectIfSaved()
navigates away, then the authenticated layout renders cleanly.
When Login.vue re-mounts inside the authenticated layout after a
successful login, its onBeforeMount hook fires again. If it calls
redirectIfSaved(), it clears the saved route from localStorage before
the submit() handler's redirectIfSaved() can use it, causing a redirect
to home instead of the saved route. Use router.push({name: 'home'})
directly since the only purpose here is to redirect already-authenticated
users away from the login page.
When the first regex match is a rejected middle-of-text date, continue
searching for subsequent matches instead of returning null. This fixes
cases like "The 9/11 Report due 10/12" where 9/11 is rejected but
10/12 at the end should still be parsed.
Reworked matchDateAtBoundary() to use a single regex pass instead of
two-pass start/end anchoring. Middle-of-text matches are now accepted
when followed by a time expression (at/@ prefix), so inputs like
"meeting 9/11 at 10:00" still parse correctly while "The 9/11 Report"
is rejected.
Adds user-select, touch-action, and webkit-touch-callout CSS to the list
view's draggable task items, matching what KanbanCard.vue already has.
Without these properties, the browser's native long-press text selection
fires before SortableJS's 1-second touch delay expires, preventing drag
from ever starting on mobile devices.
Ref: https://community.vikunja.io/t/missing-positioning-option-in-list-view/4278
Use v-show instead of v-if for EditorToolbar and BubbleMenu to avoid
a race condition between Vue's DOM reconciliation and tiptap's internal
DOM manipulation during unmount. This fixes the "Cannot read properties
of null (reading 'insertBefore')" error that occurred when saving a
task description.
Adds regression test to verify the fix.
Upstream issue: https://github.com/ueberdosis/tiptap/issues/7342
Fixes: https://github.com/go-vikunja/vikunja/issues/1770
This allows external integrations to link directly to the API token creation page with pre-selected title and permission scopes. URLs can now use `?title=Name&scopes=group:perm,group:perm` format to pre-populate the form.
Example URL:
```
/user/settings/api-tokens?title=My%20Integration&scopes=tasks:create,tasks:delete,projects:read_all
```
Adds a `viewFilters` Pinia store that stores query params per view ID to sync Gantt filters to the store whenever they change. This persists the custom date ranges when switching between views.
Fixes#2124
- Fixed date parser incorrectly extracting date components from within
words
- "Renovation - 2nd Floor Bath" no longer becomes "Reation - Floor Bath"
with a due date of November 2nd
## Changes
- `getMonthFromText` now requires word boundaries, preventing "nov" from
matching inside "Renovation" or "mar" inside "Remark"
- `getDayFromText` now only matches ordinals when followed by
end-of-string, time expressions, or month names, preventing "2nd Floor"
from being parsed as a date
Resolves
https://community.vikunja.io/t/quick-add-magic-unintended-date-parsing/4259
Filter expressions with multi-word values (such as project names with
spaces) are now automatically quoted to preserve them correctly as
single tokens in the filter syntax.
Fixes#2010🐰 A filter's journey, refined with care,
Multi-word values now wear quotes fair,
Offsets aligned by the rabbit's precision,
Autocomplete flows with mathematical vision,
From comma to space, each boundary divine! ✨
Mount multiple FormField components within the same Vue app to properly
test that useId() generates unique IDs for each instance. This validates
that labels correctly link to their respective inputs.
Verify that FormField forwards $attrs event listeners (onKeyup, onFocusout)
to its inner input element. This ensures migrated templates using
@keyup.enter and @focusout continue to work correctly.
When modelValue is a number, emit the value as a number instead of
coercing to string. This prevents subtle type bugs when using
FormField with numeric v-model bindings.
When using FormField with custom slot content, the slotted element
needs access to the generated inputId to ensure the label's for
attribute matches the input's id. This exposes the id via the
default slot: <template #default="{ id }">
Components using FormField with a ref need to call .focus() on it.
Without exposing the method, these calls would fail since the ref
points to the component instance, not the underlying input element.