Skip to content

Move Activity Tab state from cookie to localStorage#7

Open
kathrynalpert wants to merge 1 commit into
developfrom
bugfix/activities-cookie-localstorage
Open

Move Activity Tab state from cookie to localStorage#7
kathrynalpert wants to merge 1 commit into
developfrom
bugfix/activities-cookie-localstorage

Conversation

@kathrynalpert

@kathrynalpert kathrynalpert commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What this fixes

The Activity Tab (XNAT.app.activityTab) stored its task list in a per-user cookie that grew with normal use and never shrank on its own. Cookies go out on every request, so once that list got big enough, request headers blew past Tomcat's 8 KB maxHttpHeaderSize and Tomcat started returning 400 on every request.

This change moves the panel's state to localStorage, which does the same job (repopulate the panel and resume polling after a navigation) without ever riding the request headers. This fixes #6.

The change

Swap the four persistence calls to localStorage, keeping the same key and JSON shape:

XNAT.cookie.get(...)    ->  localStorage.getItem(...)
XNAT.cookie.set(...)    ->  localStorage.setItem(...)   (via a small saveActivities() helper)
XNAT.cookie.remove(...) ->  localStorage.removeItem(...)

Out of scope

  • No auto-pruning of finished tasks. They're kept so people can still review them after a reload, same as today. Can revisit if we want to cap it.
  • No cookie-to-localStorage migration. Users can close the activity tab once to start fresh.

Testing

  • node --check passes and no cookie references remain in the file (done).
  • Launched a few containers via Processing Dashboard:
    • Confirmed the state lands in Local Storage under activities<user> with no activities* cookie or request header
    • Navigated to new page and confirmed in-progress and finished items come back
    • Confirmed the X and close-all update or clear the localStorage entry

Risk

Low. Client-only change, no server or API impact, no data migration.

The Activities widget (XNAT.app.activityTab) persisted its state (the
in-progress/completed task list plus the minimized flag) in a per-user
cookie ("activities" + encoded username). Cookies are sent on every
request, and the list only shrank on manual dismissal: start() added an
entry but stopPoll() never removed finished ones, so the cookie grew with
normal use. Stacked on the ~6 KB ALB auth cookie, it pushed request
headers past Tomcat's maxHttpHeaderSize (default 8192), and Tomcat then
rejected every request with HTTP 400 ("Request header is too large",
thrown from Http11InputBuffer while parsing headers) until cookies were
cleared.

Nothing server-side reads this cookie; it exists only so the panel can
repopulate and resume polling across page navigations, which localStorage
does without riding the request headers. Swap the four persistence calls
(get/set/remove) to localStorage, keeping the same key and JSON shape.

Completed tasks are intentionally still kept so they remain reviewable
after a reload. No legacy-cookie migration is included: that cookie has
no expiry set, so it self-clears on browser close and stops growing as
soon as this ships.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 17, 2026 02:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Moves the Activity Tab’s persisted state (task list + minimized flag) from per-user cookies to localStorage to prevent oversized request headers triggering Tomcat maxHttpHeaderSize 400 errors.

Changes:

  • Replace XNAT.cookie.get/set/remove usage with localStorage.getItem/setItem/removeItem.
  • Introduce a small saveActivities() helper for persistence.
  • Rename the persisted key variable from cookieTag to storageTag while keeping the same key prefix (activities<user>).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 40 to +42
function getActivities() {
return JSON.parse(XNAT.cookie.get(cookieTag) || '{"' + minimized + '": false, "' + processes + '": {}}');
return JSON.parse(localStorage.getItem(storageTag) || '{"' + minimized + '": false, "' + processes + '": {}}');
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Going to leave this as-is. XNAT already assumes localStorage is available — storage.js writes to it on page load with no guard — so if storage were actually blocked, the app would break well before it got here. Adding a guard just in this spot would be inconsistent with the rest of the app.

Comment on lines +44 to 46
function saveActivities(activities) {
localStorage.setItem(storageTag, JSON.stringify(activities));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same thinking as the read above — we already use localStorage elsewhere without guarding, and the data here is tiny so quota isn't a realistic concern. Leaving it for consistency.

Comment on lines 112 to 116
}
}
if ($.isEmptyObject(activities[processes])) {
XNAT.cookie.remove(cookieTag);
localStorage.removeItem(storageTag);
const tab = $('#activity-tab');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think this one's a false alarm — for...in over undefined doesn't throw, it just returns true, so the close-all case works fine. It's also pre-existing behavior that this PR didn't change. Leaving as-is.

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.

Activity tab cookie grows without bound, eventually 400s the whole site

2 participants