Skip to content

Conversation

@carylwyatt
Copy link
Member

@carylwyatt carylwyatt commented May 30, 2025

Firebird has been successfully migrated to svelte 5 with two exceptions: the componentsSearchBar and Navbar, which are related via Header.

I wrote a whole novel about my attempts to migrate these components in my notes, but maybe I can sum it up: no matter how far I whittle down this code, VSCode will not let me migrate these two components to svelte 5 or use runes in any way. I keep getting a migration error telling me it cannot set properties of undefined (setting 'next'), but when I add a ton of log statements, everything is defined... and I'm never calling next(). It could be a third-party thing? I like to keep components separated by functionality when possible, but one possible solution is to combine all three of these components into a single Header component if necessary.

While this is mostly ready to go, unfortunately PageTurner can't use this version of the code. I talked with @aelkiss about this a bit, and we have some options:

  1. Migrate PageTurner to svelte 5. This needs to be done eventually, and maybe I should tackle it while this is fresh in my mind.
  2. Pin PageTurner's dependency in package.json to a branch of firebird that hasn't been upgraded (maybe create a new branch called svelte-4 and use that for PageTurner until it can be migrated). The issue with this is that any updates to firebird would either never trickle down to PageTurner or we'd have to udpate the svelte-4 branch with any style/template updates that are applied to the main branch.
  3. A middle ground option: upgrade PageTurner to svelte 5 and migrate the components that reference HT.loginStatus and any other state that trickles down into PageTurner, but leave most of it in svelte 4 syntax for now.

I started working on option 2 on dev-3 and ran into some errors. Since there are some issues, this isn't quite ready for primetime, but a draft PR will do for now.

hathitrust/babel#112 is a draft PR for pinning the firebird dependency in pt to the svelte4 branch of firebird-common.

Testing

Staged on dev-3 (vpn required):

Do some searches, add items to collections, open the feedback modals, log in and out... All of the components are tested in storybook, but maybe some "user flow" testing would be good.

Page Turner is not included in this PR, it will need additional work.

@aelkiss
Copy link
Member

aelkiss commented Jun 2, 2025

IMO starting with option 2 followed by option 3 as time permits makes sense to me, but I don't have a sense of how much work option 1 is likely to be.

let loginStatusState = $state(emptyLoginStatus);

Object.defineProperty(HT, 'loginStatus', {
get() {
Copy link
Member

Choose a reason for hiding this comment

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

I would like to see where else loginStatus is being used just to get a sense of this.

Copy link
Member

Choose a reason for hiding this comment

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

It doesn't look like loginStatus.get or loginStatus.set is being called anywhere (unless somehow that's happening implicitly), so maybe we don't actually need the defineProperty?

Copy link
Member

Choose a reason for hiding this comment

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

In different places, we have loginStatus, HT.loginStatus, and HT.login_status... This is pretty confusing, and I also suspect the usage of HT.login_status in utils.js and CollectionEditModal may not be correct insofar as HT.login_status never seems to get updated except via PingCallbackDecorator which is only being used for storybook?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. I went back and looked at my notes about this, and I had to write the get/set methods because in Svelte 5 you can't directly assign $state() to object properties like HT.loginStatus. I could've used a class wrapper, but then I'd have to call .value() or something to get that value, whereas the getter/setter updates the properties of the reactive object so I could leave the code as-is.
  2. The difference between HT.loginStatus and HT.login_status is for handling reactivity. HT.login_status is an object and HT.loginStatus is a store (which is also an object, but with special get/set/subscribe functions). The HT.login_status object is created by ping, I think: https://github.com/hathitrust/babel/blob/2aed2b1fbd567c9817d4973adc504febcbd69a7f/ping/cgi/ping#L131-L148. Then firebird is creating the store HT.loginStatus using that object for reactivity.
image

Since the postPingCallback (in main.js) is executed on every page, it's gathering that object and updating the store on every pageload. There's a separate but identical postPingCallback function in page turner's main.js that does the same thing.

With Svelte 5, HT.loginStatus is now deeply-reactive state instead of a store. I think we still need these two different properties since they do different things.

  1. loginStatus vs HT.loginStatus is a bit of a mess, but there's a reason for it. HT.loginStatus is a store, but you can't subscribe to just the property loginStatus of that HT object (perhaps HT.$loginStatus ?) without creating a derived store from that store. Using the $: to follow updates to the store is a workaround, and, as mentioned in the svelte 5 announcement post, a footgun. Now that we're using runes, I think this whole thing could be refactored, I just need to think about the best way to handle this state that gets passed around/subscribed to in messy ways.
    I think now that HT.loginStatus is using a $state() rune instead of a store, that loginStatusState has "deep state" and I can use HT.loginStatus in place of loginStatus in all the components that use the $: loginStatus = HT.loginStatus trick.
    Edit to add: yes, I updated all the loginStatus variables to use HT.loginStatus directly, and I need to test it on dev-3, but the storybook components are working.

Copy link
Member Author

Choose a reason for hiding this comment

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

Tested on dev-3 and the login logic still works as expected.

Copy link
Member

@aelkiss aelkiss Jun 17, 2025

Choose a reason for hiding this comment

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

While I would love to rename HT.login_status, since it's coming from ping, that's probably more work than is worthwhile.

_paq.push(['setCookieConsentGiven']);
}
})
if (consent.trackingConsent !== 'true') {
Copy link
Member

Choose a reason for hiding this comment

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

The subscribe method doesn't exist anymore; because this is using reactive state then if there's a change in the value then Svelte will magically cause this method to run.

el.component = mount(apps[slug], {
target: el,
props: props,
intro: false,
Copy link
Member

Choose a reason for hiding this comment

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

Adding intro: false disables whimsical animations that we did not want.

Copy link
Member

@aelkiss aelkiss left a comment

Choose a reason for hiding this comment

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

Reviewed with @moseshll and @carylwyatt. I think there are a just a couple issues regarding the rebase and one component to check with respect to migration.

@carylwyatt carylwyatt marked this pull request as ready for review June 16, 2025 19:28
@carylwyatt
Copy link
Member Author

@aelkiss ready for re-review, thanks!

Copy link
Member

@aelkiss aelkiss left a comment

Choose a reason for hiding this comment

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

All the comments from the code review were addressed, so I think this looks good!

- use migrate self-closing-tags to fix icon markup
- add svelte prettier plugin, add minify false to babel script
- convert store to runes
- update svelte preprocess
- migrate main.js to main.svelte.js
- convert writable store to $state() proxy
- update index with new file name for vite
- update loginStatus logic in login modal components
- remove unused components and references to them
@carylwyatt
Copy link
Member Author

carylwyatt commented Sep 3, 2025

Updates since the previous code review:

  • Migrated Navbar and SearchBar to svelte 5 syntax
  • Rebased against main to get RoleSwitchModal component in this branch
    • migrated this component to svelte 5 syntax
    • refactored this into smaller/modular components
  • Removed unused CSS/added :global modifier to silence console warnings

Edit to add: I checked the storybook and realized something weird was happening with the navbar stories. There were some bugs related to state handling in the navbar directly related to the login modal and notification modal. Those have now been updated the the stories are back to the regular behavior.

@carylwyatt carylwyatt requested a review from aelkiss September 9, 2025 20:00
Copy link
Member

@aelkiss aelkiss left a comment

Choose a reason for hiding this comment

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

Reviewed with @carylwyatt ; all looks fine.

props: { loggedIn: false, notificationData: null },
}),
],
// decorators: [
Copy link
Member

Choose a reason for hiding this comment

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

@carylwyatt says this change is because props weren't getting overridden in each individual story; duplicating this in each of them isn't ideal but this is all likely to change with storybook 9 anyway.

</span>
<div class="option--help">
{#if switchableRole === 'resourceSharing'}
<p>
Copy link
Member

Choose a reason for hiding this comment

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

This looks great!

color: var(--color-primary-700);
&:hover {
color: var(--color-primary-800);
.switch-roles :global {
Copy link
Member

Choose a reason for hiding this comment

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

:global avoids warnings about unused css

@carylwyatt carylwyatt merged commit 78b6688 into main Sep 10, 2025
5 checks passed
@carylwyatt carylwyatt deleted the ETT-266-svelte5 branch September 10, 2025 18:10
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.

3 participants