Conversation
BrowserContext had no consumers: nothing imported it or called addToCompared, addToViewed, getPreviouslyCompared or getPreviouslyViewed. The live path is Redux — the compare and viewed widgets dispatch into browserInfo, and the search listings page reads it with useSelector. Deleting it also removes a second writer of the ngStorage-previouslyCompared and ngStorage-previouslyViewed keys. useStorage writes its state back on mount, and because BrowserWrapper was an ancestor of the widget that dispatched, React ran the widget's effect first: the reducer wrote the new list, then BrowserWrapper wrote back the list it had read during its own render, dropping the entry just added. Redux kept it in memory, so only the persisted copy was wrong — the most recently viewed or compared listing was lost on reload. [#OCD-5460] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The slice read ngStorage-previouslyCompared and ngStorage-previouslyViewed in initialState and wrote them back from its reducers, which made the reducers impure and duplicated what chplState already persists. Those keys were only ever a cross-bundle sync hack, and the shared store retired the need for it. Move the one-time carry-over into store.js alongside the existing legacy-key migration, and add both keys to the sweep. Note that preloadedState replaces a slice rather than merging into its initialState, so the migration has to spread the slice defaults - otherwise api and apiKey would come back undefined. For the same reason, overlay api and apiKey from the current build over whatever chplState holds: neither has a reducer, so a persisted copy would pin a returning user to configuration from an earlier deploy. [#OCD-5460] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
[#OCD-5460] The tag is mandatory in every commit message and must appear exactly once, so the doc now names the three mechanisms that can supply it - a prepare-commit-msg hook, these instructions when an agent drafts the message, or typing it by hand - and says to establish which one is live before relying on it. It also records the two ways the hook can fail to fire, and where to put the tag when you are the one writing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…file-upload The four upload components used the AngularJS Upload service, which meant building Authorization and API-Key headers by hand and fetching an access token first. Posting FormData through the shared axios instance lets the interceptors supply all of that, so the token plumbing, the header construction and the browserInfo selectors all go away, and ng-file-upload can be dropped entirely. The instance defaults to Content-Type: application/json, and axios serialises a FormData body to JSON when that header is set, so the request interceptor now clears it for FormData and lets the browser set multipart/form-data with its own boundary. Two response shapes differed and are corrected: the uploaded filename now comes from the local file rather than response.config.data.file.name, which axios does not populate, and error details are read from error.response.data instead of error.data. [#OCD-5460] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Components reached into ui-router directly via getAngularService, so $state, $location, $rootScope, $transitions and $stateParams were scattered across eight files. Concentrate them in services/navigation.service.js and have the call sites depend on that instead. getAngularService now appears in exactly one place, so replacing ui-router later is a change to this module rather than a hunt across components. goToUrl absorbs the $rootScope.$digest() that a $location change from a React handler needs, and onRouteChange returns one deregister function in place of the three that chpl-route-loading was tracking. No behaviour changes. [#OCD-5460] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
complaint-edit was the last consumer of the AngularJS utilService, using its sortCertActual comparator. That comparator's sort table is stale - it is missing 170.315 (b)(10), (d)(12), (d)(13), (g)(10), (g)(31) through (g)(33), (j)(20) and (j)(21) - and anything absent from the table is given Number.MAX_VALUE, so those criteria were all collapsing to the end of the dropdown in input order rather than sorting into sequence. criteria.service's sortCriteria already has the current table and is already used by surveillance.service, so use it here too. Also copy the array before sorting: criteriaData comes from the react-query cache and sort mutates in place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> [#OCD-5460]
Analysing 600 commits from production showed the documented shape was wrong: 392 of 409 non-merge commits are a subject line plus the tag with no body, so the example now shows that form and treats a body as the exception. Records the subject prefixes in use, including the non-standard ui and <type>-flag families, notes that ! marks a user-visible change for release notes rather than a breaking change, and exempts release commits from the tag requirement. Adds commits made through the GitHub web UI to the list of ways the hook does not fire, which accounts for untagged commits in the history. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> [#OCD-5460]
kekey1
approved these changes
Sep 11, 2026
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.
What
Next slice of removing AngularJS. This branch retires every remaining
getAngularServicecall outside a single deliberate boundary, drops one Angulardependency, and finishes the
ngStorage-*migration. Routing is deliberatelyout of scope and is the next ticket.
getAngularServicewent from 15 call sites across 9 files to 2 files: the helperthat defines it, and
services/navigation.service.js, which is now the onlyplace that talks to ui-router.
services/navigation.service.js(new)Components reached into the router directly, so
$state,$location,$rootScope,$transitionsand$stateParamswere scattered across eightfiles. They are now behind six functions:
goToState$state.goreloadState$state.reloadgetCurrentUrl$location.url()goToUrl$location.url(x)+$digest()getRouteParams$stateParamsonRouteChange$transitions.on*ChplLinkalone funnels 65router={...}props across 52 files through one$state.go, so replacing the router next ticket is a change to this one modulerather than a hunt through components.
Two design notes:
goToUrlabsorbs the$rootScope.$digest()that a$locationchange from a React handler needs, because that is an Angularimplementation detail; and
onRouteChangereturns a single deregister functionin place of the three
chpl-route-loadingwas tracking.Uploads through the shared axios client
The four upload components used the Angular
Uploadservice, which meanthand-building
AuthorizationandAPI-Keyheaders and fetching an access tokenfirst. They now post
FormDatathrough the shared axios instance, so theinterceptors supply all of it — the token plumbing, the header construction and
the
browserInfoselectors all go away, andng-file-uploadis removedentirely.
One trap worth knowing about, verified against the installed axios 1.7.7: the
shared instance defaults to
Content-Type: application/json, and axios'transformRequestserialises aFormDatabody to JSON when that header is set,so uploads would have silently posted JSON. The request interceptor now clears
the header for
FormDataand lets the browser setmultipart/form-datawithits own boundary.
Two response shapes also differed and are corrected: the uploaded filename comes
from the local file rather than
response.config.data.file.name, which axiosdoes not populate, and error details are read from
error.response.datainsteadof
error.data.ngStorage-*retiredBrowserWrapperandBrowserContextdeleted. Nothing consumed thecontext — no file imported it or called
addToCompared,addToViewed,getPreviouslyComparedorgetPreviouslyViewed. The live path was alwaysRedux.
useStoragewrites its stateback on mount, and
BrowserWrapperwas an ancestor of the widget thatdispatched, so React ran the child effect first: the reducer wrote the new
list, then
BrowserWrapperwrote back the list it had read during its ownrender, dropping the entry just added. Redux kept it in memory, so only the
persisted copy was wrong — the most recently viewed or compared listing was
lost on reload.
browserInforeducers are pure again. The one-time carry-over moved intostore.jsalongside the existing legacy-key migration. Note thatpreloadedStatereplaces a slice rather than merging into itsinitialState,so the migration spreads the slice defaults — otherwise
apiandapiKeywould come back
undefined. For the same reasonapi/apiKeyare nowoverlaid from the current build over whatever
chplStateholds; neither has areducer, so a persisted copy would pin a returning user to configuration from
an earlier deploy.
Complaint criteria sorting (
fix!)complaint-editwas the last consumer of the AngularutilService, via itssortCertActualcomparator. That comparator's sort table is stale — it ismissing
170.315 (b)(10),(d)(12),(d)(13),(g)(10),(g)(31)through(g)(33),(j)(20)and(j)(21)— and anything absent from the table is givenNumber.MAX_VALUE, so those criteria were all collapsing to the end of thedropdown in input order instead of sorting into sequence:
criteria.service'ssortCriteriaalready has the current table and is alreadyused by
surveillance.service, so it is used here too. The array is also copiedbefore sorting:
criteriaDatacomes from the react-query cache andsortmutates in place.
CLAUDE.md
Documents the branch-name commit tag requirement. Analysing 600 commits from
productionshowed the guidance needed to match reality: 392 of 409 non-mergecommits are a subject line plus the tag with no body, so that is the documented
shape. Also records the subject prefixes actually in use (including the
non-standard
uiand<type>-flagfamilies), that!marks a user-visiblechange for release notes rather than a breaking change, that
release:commitsare exempt from the tag, and the three ways the
prepare-commit-msghook failsto fire.
Base branch
This stacks on
OCD-5430, which is not yet instaging. Point the PR atOCD-5430or it will show all 36 commits instead of these 7.Not in this PR
*.state.js, the remainingangular.module()calls,reactToAngularComponentandnavigation.service.jsitself. Next ticket.ui-viewshell components (*.component.js), the four.htmltemplates and
src/app/templates.js— deleting these would also remove awebpack entry. Sized and ready, but it is routing scaffolding, so it belongs
with the routing ticket.
angular-zxcvbnhas zero references, andngCytoscapeplus the vendoredlib/ngCytoscape.min.jsare superseded byreact-cytoscapejs. Several others look dead but each needs verifying.chplReportsProductsis referenced byreports.state.js:21andregistered nowhere, so
/reports/productsrenders nothing. Found whileinventorying; needs its own ticket to decide whether the route is dead or
broken.
Verification
yarn buildcompilesagainst the real modules with throwaway harnesses, including negative controls
🤖 Generated with Claude Code