Skip to content

fix: ActivitiesCard crashes — t() in a prop default has no scope - #231

Merged
meauxt merged 2 commits into
donetick:developfrom
everysingletear:fix/activities-card-scope
Aug 16, 2026
Merged

fix: ActivitiesCard crashes — t() in a prop default has no scope#231
meauxt merged 2 commits into
donetick:developfrom
everysingletear:fix/activities-card-scope

Conversation

@everysingletear

Copy link
Copy Markdown
Contributor

ActivitiesCard takes its title as ({ title = t('activity.title') }).
A default parameter is evaluated in the function's own scope, where t
does not exist: the only t in the file is bound inside ActivityItem,
a separate component. Sidepanel renders the card without a title, and
activities is enabled: true in DEFAULT_SIDEPANEL_CONFIG, so the
default is always evaluated on a desktop-width screen.

The hook moves into the component body and the title falls back there
instead. Same rendered output; three render sites now read displayTitle.

This is my regression: it arrived with #216, which described itself as
"no behaviour change". It was not caught because a bundler cannot flag it
— an unbound t is indistinguishable from a global — and my checks only
verified that keys and imports existed, not that t was in scope. It is
visible in the built bundle: ({title:e=t("activity.title")}) keeps the
literal t because the minifier cannot rename a free variable, while a
working call nearby minifies to q=e("common.confirm").

I have added an eslint no-undef pass to my own pipeline and run it
before sending anything from now on.

How to see it

Sidepanel renders the card without a title, and activities is
enabled: true in DEFAULT_SIDEPANEL_CONFIG, so any logged-in user on a
screen wider than lg hits it.

It is also visible without running anything. Build develop and grep the
bundle:

grep -o '.\{50\}activity\.title.\{15\}' dist/assets/Application-*.js
→ OAe=({title:e=t("activity.title")})=>{const[n,

The translator is the bare letter t — the minifier cannot rename a free
variable, so it leaves it as written. A working call a few lines away
minifies to q=e("common.confirm"), where e is the bound t from the
enclosing hook. That difference is the whole bug.

everysingletear added a commit to everysingletear/frontend that referenced this pull request Aug 15, 2026
Part of donetick#145. Narrowed version of this PR, as announced in donetick#145 and donetick#168.

The original diff covered twelve files; six were rewritten under me since —
the modal unification, donetick#186, and the AddTaskModal autocomplete work. This
reduces the PR to the seven files still untouched on `develop`:

  AssigneePickerField, AttachmentPickerField, DueDatePickerField,
  LabelsPickerField, ProjectPickerField, SmartTaskTitleInput, SubTask

The rewritten half goes back in the queue behind your work, to be
re-extracted against the current shape rather than replayed.

Rebased on current `develop` after donetick#210 landed — the conflict was in the
dictionaries only, and it shrank as a result: 20 keys to `en/chores.json`
and 1 to `en/common.json`, since donetick#210 already carried the rest.

AttachmentPickerField is re-extracted on top of your z-index refactor
(462af85) rather than carried over; my branch predates it and still
imported the removed `constants/zIndex`.

English only — no translations, no behaviour change; every t() value is
checked to appear character-for-character in the code it replaces
(23 call sites). Also run through `eslint --rule no-undef`, which is what
I should have been doing before donetick#216 and donetick#210 — see donetick#231.
`ActivitiesCard` takes its title as `({ title = t('activity.title') })`.
A default parameter is evaluated in the function's own scope, where `t`
does not exist: the only `t` in the file is bound inside `ActivityItem`,
a separate component. `Sidepanel` renders the card without a `title`, and
`activities` is `enabled: true` in `DEFAULT_SIDEPANEL_CONFIG`, so the
default is always evaluated on a desktop-width screen.

The hook moves into the component body and the title falls back there
instead. Same rendered output; three render sites now read `displayTitle`.

This is my regression: it arrived with donetick#216, which described itself as
"no behaviour change". It was not caught because a bundler cannot flag it
— an unbound `t` is indistinguishable from a global — and my checks only
verified that keys and imports existed, not that `t` was in scope. It is
visible in the built bundle: `({title:e=t("activity.title")})` keeps the
literal `t` because the minifier cannot rename a free variable, while a
working call nearby minifies to `q=e("common.confirm")`.

I have added an eslint `no-undef` pass to my own pipeline and run it
before sending anything from now on.
…eral

`CHORE_STATUS` is a module-level object literal, and a `useTranslation`
call ended up inside it:

    const CHORE_STATUS = {
      const { t } = useTranslation('chores')
      NO_DUE_DATE: 'No due date',

That is a syntax error, so the file cannot be parsed at all. It does not
break the build because nothing imports `ChoresOverview` — vite never
compiles it — but it breaks eslint, editors, and anything else that walks
the whole tree.

The hook moves into the component, which is where the file's `t()` calls
actually live.

This one arrived with donetick#210, the same way donetick#216 brought the ActivitiesCard
crash: my tooling wrote the hook in mechanically and nothing downstream
parsed the result. Both are now covered by the `no-undef` pass I described
in the other commit — it reports the parse error too.
@everysingletear
everysingletear force-pushed the fix/activities-card-scope branch from 2ae6802 to 64aca50 Compare August 15, 2026 09:02
@everysingletear

Copy link
Copy Markdown
Contributor Author

Added a second fix to this PR — same root cause, found while rebasing my
remaining branch onto develop after #210 landed.

ChoresOverview.jsx does not parse at all. #210 put a useTranslation
call inside a module-level object literal:

const CHORE_STATUS = {
  const { t } = useTranslation('chores')
  NO_DUE_DATE: 'No due date',

It doesn't break the build because nothing imports that file, so vite never
compiles it — which is exactly why neither of us noticed. It does break
eslint and editors, and it would break the moment the file is wired up
again.

So that's two defects from my extraction PRs: a runtime crash from #216 and
a syntax error from #210. Both share a cause — the hook was inserted
mechanically and nothing downstream ever parsed or ran the result — and both
are caught by the no-undef pass I now run, which reports the parse error
as well as the undefined t.

I'd rather you knew the count than found the second one yourself later.

@meauxt meauxt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you

@meauxt
meauxt merged commit 84f8603 into donetick:develop Aug 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants