Checklist skill invoked before editing models in pkg/models/. Covers Can*
method placement (on the model, never in route handlers), the four
permission methods, required positive+negative test coverage, and the
anti-patterns most frequently flagged in review.
The api translation scanner only looked at literal arguments to i18n.T /
i18n.TP, so keys passed via a variable (e.g. the time.since_* keys stored
in a struct slice in pkg/utils/humanize_duration.go and looked up via
chunk.key) were invisible and had to be hard-coded in an allowlist of
dynamic prefixes.
Mirror the frontend scanner: collect every dotted string literal in the
Go source as a "usage hint" and treat any literal that matches a known
translation key as used. This automatically picks up the time.since_*
case and removes the need for the apiDynamicKeyPrefixes allowlist.
Previously the PR introduced a separate `check:frontendTranslations` mage
task and a second CI job. Merge both into the existing `check:translations`
task and a single CI job. Also rename internal references from "backend" to
"api" to match the project convention (Vikunja's Go server is the api, not
the backend).
Add a frontend-check-translations job that runs the new
check:frontendTranslations mage task. Like the existing
api-check-translations job, failures hard-fail CI. This makes
reviewers catch dead keys and missing $t() wiring up front instead of
having to flag them manually in pull request review.
Remove 47 keys from frontend/src/i18n/lang/en.json that are not
referenced by any $t / t / i18n.t / i18n.global.t / tc / <i18n-t>
call site, nor by any stored-as-literal dynamic lookup pattern.
The keys fall into a few broad groups: leftover attribute labels on
filter and label models, dropped editor toolbar entries, unused
password/password-confirmation copy, and a handful of stale admin and
migration strings. The sibling translation files will be reconciled on
the next Crowdin sync.
Remove five keys from pkg/i18n/lang/en.json that are no longer
referenced by any i18n.T / i18n.TP call. These surfaced once the
translation check started reporting dead keys. The sibling translation
files will be reconciled on the next Crowdin sync.
Removed keys:
- notifications.task.comment.mentioned_message
- notifications.task.mentioned.message
- notifications.common.actions.assigned_you
- notifications.common.actions.assigned_themselves
- notifications.common.actions.assigned_user
The call to i18n.T for notifications.task.overdue.overdue was missing
its first positional argument, so the translation key was being passed
as the language code. This surfaced as a "dead key" once the
translation check learned to look for unused entries. Fix the call so
the reminder line is properly localised.
Extend the existing check:translations task so it now also reports
"dead" keys - entries present in pkg/i18n/lang/en.json but never
referenced by any i18n.T / i18n.TP call. Dynamic references (where
the key is a runtime value, e.g. from a struct field) are handled via
an allowlist of prefixes so they don't false-positive.
Add a new check:frontend-translations task that performs the same
bidirectional check against frontend/src/i18n/lang/en.json by scanning
.vue / .ts / .js files for $t, t, i18n.t, i18n.global.t, tc, $tc calls
and <i18n-t keypath="...">. Template literals with ${...} interpolation
contribute a usage prefix instead of a single key. String literals that
exactly match a known translation key (or template-literal prefixes
assigned to a variable) are also treated as usage hints, so keys stored
in arrays or built up programmatically aren't flagged as dead.
Register the new task in Check.All so `mage check` covers both.
Chromium already caps zoom levels internally, so the manual [-7, +7]
clamp was redundant. Removes the constants and clamping logic while
keeping the before-input-event approach intact for persistence support.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Electron does not register zoom shortcuts by default, so the desktop app
had no way to scale the UI. Register Ctrl+Plus, Ctrl+Minus and Ctrl+0
via webContents.before-input-event, and Ctrl+scroll via the zoom-changed
event.
The zoom level is clamped to [-7, +7] (Chromium's range, roughly 28% to
358%) and persisted to zoom.json in app.getPath('userData'), so the
chosen level survives restarts. Restored zoom is re-applied on every
did-finish-load, since Electron resets zoom across page reloads.
Fixes#2623
Wire aria-invalid, aria-describedby and role=alert on the form
primitive components so errors raised directly on FormInput or
FormSelect are announced by assistive tech and programmatically
linked to the control.
Adds aria-label='Mark {task} as done' to task checkboxes so
screen readers can distinguish between them. Passes ariaLabel
prop through FancyCheckbox → BaseCheckbox → input.
Fixes WCAG 2.4.6 (Headings and Labels).
Adds aria-invalid, aria-describedby, and role='alert' to error
messages in FormField and Password components so screen readers
announce validation errors.
Fixes WCAG 3.3.1 (Error Identification).
Passes aria-label to the <dialog> element via attribute inheritance
so screen readers announce the dialog's purpose.
Fixes WCAG 4.1.2 (Name, Role, Value).
Adds role='status' and aria-live='polite' to the notification
container so screen readers announce status messages.
Fixes WCAG 4.1.3 (Status Messages).
Adds a visually-hidden skip-to-content link as the first focusable
element. Adds id='main-content' to the <main> element. Changes
<section> to <main> on auth pages for proper landmark navigation.
Fixes WCAG 2.4.1 (Bypass Blocks).
CI shard 4 hit a ~996ms skew between the JS-constructed originalDue and
the backend's advanced due date, enough to bust the <500ms precision
bound. Bump precision to -4 (<5s) — still tight enough to confirm the
regeneration advanced by ~1 day, loose enough to absorb sub-second
round-tripping through Date → ISO → Go time.Time → JSON.