typeof null === 'object', so null slipped past the type guards in
objectToCamelCase/objectToSnakeCase/prepareParams. The original
for...in loops silently iterated nothing on null; Object.keys(null)
throws. Also guard saveCollapsedBucketState where state[projectId]
may be undefined.
Replaces 33 for...in loops across 18 files with for...of + Object.keys/entries
or indexed for loops. for...in iterates enumerable string keys including
inherited ones, which is especially risky on reactive arrays (tasks, labels,
assignees, etc.) where polyfilled properties may appear.
Loops that mutate via splice during iteration now iterate backwards to avoid
index-shift bugs. Adds a no-restricted-syntax ESLint rule forbidding
ForInStatement to prevent regressions.
Closes#513
Allow users to skip the first N data rows when importing CSV files.
This is useful when the CSV contains metadata rows before the actual
task data begins. Adds skip_rows to ImportConfig (backend) and a
number input in the parsing options UI (frontend).
Add a new CSV migration module that allows users to import tasks from
any CSV file with custom column mapping and parsing options.
Backend changes:
- New CSV migrator module with detection, preview, and import endpoints
- Auto-detection of delimiter, quote character, and date format
- Suggested column mappings based on column name patterns
- Transactional import using InsertFromStructure
Frontend changes:
- New CSV migration UI with two-step flow (upload -> mapping -> import)
- Column mapping selectors for all task attributes
- Live preview showing first 5 tasks with current mapping
- Parsing option controls for delimiter and date format
The CSV migrator creates a parent "Imported from CSV" project with
child projects based on the project column if provided, or a default
"Tasks" project for tasks without a specified project.
Add a new settings page for managing user-level webhooks. Extract
webhook form into shared WebhookManager component used by both
project and user webhook settings. Add routing, translations,
and navigation entry.
- Session model, type interface, and API service
- Sessions settings page showing active sessions with device info,
IP address, last active time, and current session indicator
- Auth store updated to use cookie-based refresh tokens for user
sessions and JWT-based renewal for link shares
- refreshToken() uses Web Locks API to coordinate across browser
tabs — only one tab performs the refresh, others adopt the result
- 401 response interceptor with automatic retry: detects expired JWT
(error code 11), refreshes the token, and replays the request
- Interceptor gated to user JWTs only (link shares skip refresh)
- checkAuth() attempts cookie refresh when JWT is expired, allowing
seamless session resumption after short TTL expiry
- Proactive token refresh on page focus/visibility via composable
- renewToken() tolerates refresh failures when JWT is still valid
- Renames the `/tasks/all` endpoint to `/tasks` for consistency with
other collection endpoints like `/projects` and `/labels`
- Returns `[]` instead of `null` for empty pagination results across all
list endpoints
- Updates the frontend service to use the new endpoint path
- Updates API token tests to use the new endpoint path
Fixes#1984
BREAKING CHANGE: The bucket id of the task model is now only used internally and will not trigger a change in buckets when updating the task.
This resolves a problem where the task update routine needs to know the view context it is in. Because that's not really what it should be used for, the extra endpoint takes all required parameters and handles the complexity of actually updating the bucket.
This fixes a bug where it was impossible to move a task around between buckets of a saved filter view. In that case, the view of the bucket and the project the task was in would be different, hence the update failed.
This PR adds reactions for tasks and comments, similar to what you can do on Gitea, GitHub, Slack and plenty of other tools.
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2196
Co-authored-by: kolaente <k@knt.li>
Co-committed-by: kolaente <k@knt.li>