Repository navigation
refactor: convert the access-expiration alert hooks and useAlert to TypeScript - #2152
Merged
Merged
Conversation
brian-smith-tcril
added this pull request to stack #2141
October 2, 2026 09:23
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2152 +/- ##
==========================================
+ Coverage 95.25% 95.59% +0.33%
==========================================
Files 372 374 +2
Lines 6053 6081 +28
Branches 1499 1498 -1
==========================================
+ Hits 5766 5813 +47
+ Misses 275 258 -17
+ Partials 12 10 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
brian-smith-tcril
force-pushed
the
bsmith/access-expiration-alert-ts
branch
from
October 2, 2026 10:20
96b9c7e to
b0c318c
Compare
brian-smith-tcril
marked this pull request as ready for review
October 2, 2026 10:35
10 tasks done
…ypeScript
`alerts/access-expiration-alert/hooks.js` and `index.js` become `.ts`: both
hooks' parameters are typed, and the `useModel(tab, courseId)` destructure
is annotated `{ accessExpiration?: CourseHomeAccessExpiration | null }` so
the banner's reads are checked against the declared shape. The learner-facing
`useAccessExpirationAlert` is typed on its six parameters and otherwise
untouched.
That makes `hooks.ts` the first TypeScript caller of `useAlert`, whose
destructured options parameter TypeScript inferred from the JavaScript as six
required fields. So `generic/user-messages/hooks.js` and
`UserMessagesContext.js` become `.ts` as well: a `UserMessage` type for what a
caller registers, a `UserMessagesContextValue` for what the provider already
exposes, and `createContext<UserMessagesContextValue | null>(null)` with
`useAlert` throwing a named error outside the provider, the shape of
`ToastContext`, `SidebarContext` and `CoursewareSearchContext`. Every app and
test render path is under `UserMessagesProvider`.
No runtime change for any render under the provider; no data-layer change;
the `.jsx` components, the provider and `AlertList` stay as they are. A peel
below #1999, so that layer shows only its read swap on a typed file.
Closes #2151. Part of #1946.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
brian-smith-tcril
force-pushed
the
bsmith/access-expiration-alert-ts
branch
from
October 5, 2026 17:33
b0c318c to
247e060
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
alerts/access-expiration-alert/hooks.jsandindex.jsbecome TypeScript, with both hooks' parameters typed and theuseModel(tab, courseId)read annotated withCourseHomeAccessExpiration, so that #1999 — which replaces that read with reads of the outline and progress queries — lands as a two-line swap on a typed file. That madehooks.tsthe first TypeScript caller ofuseAlert, whose destructured options parameter TypeScript inferred from the JavaScript as six required fields, sogeneric/user-messages/hooks.jsandUserMessagesContext.jsbecome TypeScript too: aUserMessagetype for what a caller registers, aUserMessagesContextValuefor what the provider already exposes, and anullcontext default withuseAlertthrowing a named error outside the provider, the shape ofToastContext,SidebarContextandCoursewareSearchContext. No runtime change for any render underUserMessagesProvider, which is every app and test render path; no data-layer change; the.jsxcomponents, the provider andAlertListstay as they are. Part of the Redux → React Query migration (#1946, Stage 1); a peel below #1999, stacked above #2150. Closes #2151.What changed
alerts/access-expiration-alert/hooks.ts(decisions 3–4):useAccessExpirationMasqueradeBanner(courseId: string, tab: string); theuseModeldestructure annotated{ accessExpiration?: CourseHomeAccessExpiration | null }, soisVisibleandexpirationDateare checked against the declared shape.useAccessExpirationAlerttyped on its six parameters and otherwise untouched. Lazy imports, memoised payloads, alert codes and topics unchanged.alerts/access-expiration-alert/index.ts: content unchanged;@src/alerts/access-expiration-alertkeeps resolving forInstructorToolbarand for downstream toolbar overrides that import the banner hook from it.generic/user-messages/UserMessagesContext.ts(decisions 1–2):UserMessage(code: string;text,topic,type,payload,dismissibleoptional) andUserMessagesContextValue(add,addFlash,remove,clear,messages, thevalueobjectUserMessagesProvider.jsxalready builds);createContext<UserMessagesContextValue | null>(null).generic/user-messages/hooks.ts(decisions 1–2, 4):useAlert(isVisible: boolean | null | undefined, options: UserMessage); throwsuseAlert must be used within a UserMessagesProviderwhen the context isnull, asuseToastdoes. The TNL-7400 memoisation comment and the effect are as they were.user-messages/hooks.test.tsxfor the guard, in the shape of the sibling context suites;access-expiration-alert/hooks.test.tsxfor the learner-facing hook, registering underUserMessagesProviderand reading itsmessages, uncovered in the JavaScript too but patch lines once the file moved.Testing
npm run typesandnpm run lintclean; full suite 122 suites, 1193 passed, 0 skipped; both converted hook files at 100% lines. The banner's existing positive tests run through the renamed files unchanged:OutlineTab.test.jsx› Access Expiration Alert andProgressTab.test.jsx› Access expiration masquerade banner. The render-path check behind the context default:src/index.jsxwraps the app inUserMessagesProvider,setupTest.js'srenderwraps every test in it, and the ten test files that build their ownAppProvidertree without it render search, tab-nav, breadcrumb and sidebar components, none of which calluseAlert. No manual test: renames and types only.Decisions
Full decision log
Decisions — convert the access-expiration alert hooks and
useAlertto TypeScript (#2151)Peel below #1999 (layer E of #1977), the #2125 shape: renames and types, no
runtime change for any render under
UserMessagesProvider. Layer of therunning stack #2141 above #2150. Settled with the user 2026-10-02.
useAlertand its context convert too, in this layer. Typing theaccess-expiration hooks made
hooks.tsthe first TypeScript caller ofuseAlert, and TypeScript infers that JavaScript hook's destructuredoptions parameter as six required
anyfields, so both calls failed ontext,typeanddismissiblebeing absent. Of the three ways out, aJSDoc
@paramon the JavaScript file (thegeneric/hooks.jsprecedent),passing the three keys as
undefinedat the call site, or convertinguser-messages/hooks.jsandUserMessagesContext.jsto TypeScript, theuser chose the conversion: 41 lines in two files, one
UserMessagetypefor what a caller registers and one
UserMessagesContextValuefor whatthe provider already exposes (
add,addFlash,remove,clear,messages).UserMessagesProvider.jsx,AlertList.jsxandAlert.jsxstay JavaScript; the eleven JavaScript
useAlertcallers are unaffected.Convert the access-expiration alert hooks and useAlert to TypeScript (peel below #1999) #2151's title and body widen accordingly.
The context default is
nullanduseAlertthrows outside theprovider.
createContext({})typed honestly would makeaddandremoveoptional at every read. The repo's typed contexts(
ToastContext,SidebarContext,CoursewareSearchContext) all usecreateContext<Value | null>(null)with the consumer hook throwing a namederror, so
UserMessagesContextmatches. The only behaviour this changes isa render with no
UserMessagesProvider, which used to fail later with anundefined
addand now fails at the hook with its own message. Checkedbefore adopting it:
src/index.jsxwraps the app in the provider,setupTest.js'srenderwraps every test in it, and the ten test filesthat build their own
AppProvidertree without it render search, tab-nav,breadcrumb and sidebar components, none of which call
useAlert.The
useModelread stays a destructure, annotated. The property-accessform (
useModel(tab, courseId).accessExpiration) was tried first andeslint's
prefer-destructuringrewrote it back, so the read isconst { accessExpiration }: { accessExpiration?: CourseHomeAccessExpiration | null } = useModel(tab, courseId).Source the access-expiration masquerade banner from the tab query, not useModel(tab) #1999 replaces the line either way.
isVisibleisboolean | null | undefined. Every caller computes itas
x && yover an object or boolean and a boolean, which is that union;useAlertonly tests it for truthiness. The learner-facinguseAccessExpirationAlertis typed on its six parameters and otherwiseuntouched, per Source the access-expiration masquerade banner from the tab query, not useModel(tab) #1999's decision 4.
Two cases for codecov's patch check. It flagged one line in each
converted hook file: the new
throwinuser-messages/hooks.ts, which norender reaches because every render is under the provider, and the body
of the learner-facing
useAccessExpirationAlert, unreachable from theapp since 2021 and uncovered in the JavaScript too, counted as patch
lines once the file moved (the situation Type the course-home API module on the functions that produce its results (convert course-home/data/api.js to TypeScript) #2130's decision 15 met).
user-messages/hooks.test.tsxcovers the guard the way the five siblingcontext suites cover theirs (
renderHookoutside the provider,toThrowthe message).access-expiration-alert/hooks.test.tsxrendersthe learner alert hook under
UserMessagesProviderand reads theprovider's
messages: one registered alert with the hook'scode,topicand payload when the learner has an expiration and is notmasquerading, none when
masqueradingExpiredCourseis set. Leaving themisses and noting them in the PR was the alternative; the user chose the
tests, matching Type the course-home API module on the functions that produce its results (convert course-home/data/api.js to TypeScript) #2130.
🤖 Generated with Claude Code