* add: basic ui replacement menu
* feat(replacement): modified ViewSettings interface and added Replacement type
* add: frontend menu ui to annotation settings
- create replacementoptions file for 4 fix options: fix once, fix in library, fix in book, fix in library
-integrate with annotator.tsx
only frontend changes, but initialzied in backend
* add: delete global option and click gear option to get rid of menu
* docs: add test cases for replacementoptions file
* edits to enable readest to build
* basic changes for rule types
* replacement transformer file added
* additional support code added
* interim updates to replacement.ts file
* adding console log statements to confirm functionality without frontend
* adding more console logs for debugging; i think i got my replacement working, will clean console logs and add actual tests now.
* figured out how to get my transformer to work. replacement doesnt actually work yet. figuring that out rn. committing before i destroy something, lol
* replcement logic working with hard coded tests. code is cleaned up with minimal console logs. actual replacement logic + testing is next :)
* test suite built, and fully passing. made consle log edits too.
* added more replacement rules, but figuring out why they arent being implemented by my code.
* cleaning up test suite to not break when there are 0 rules; test is commited with 1 local rule. not sure if that rule is going to copy over when i merge.
* feat(replacement): Add text field, case sensitivity checkbox, and confirmation dialog to ReplacementOptions
- Add text input field for replacement text with placeholder
- Add 'Case Sensitive' checkbox (default: unchecked/case-insensitive)
- Implement two-step confirmation flow with Back/Confirm buttons
- Show preview of original text, replacement text, scope, and case sensitivity
- Disable scope buttons until replacement text is entered
- Display truncated preview for long selected text (>50 chars)
- Export ReplacementConfig type for use in parent components
* feat(replacement): Add 30-word limit and integrate new ReplacementOptions component
- Add MAX_REPLACEMENT_WORDS constant (30 words)
- Add getWordCount() utility function for word counting
- Show warning toast when word limit exceeded on Text Replacement click
- Replace old fix handlers with single handleReplacementConfirm()
- Integrate with new ReplacementConfig (replacementText, caseSensitive, scope)
- Display success toast with scope and case sensitivity info on confirm
* fix(build): Add ReplacementMenu placeholder component
- Create placeholder component to fix missing import error in reader/page.tsx
- Component returns null for now, to be implemented with global replacement rules
* test(replacement): Add comprehensive tests for ReplacementOptions and word limit
ReplacementOptions.test.tsx:
- Test rendering of text input, checkbox, and scope buttons
- Test case sensitivity checkbox toggle and state
- Test disabled buttons when no replacement text entered
- Test confirmation dialog flow and Back/Confirm buttons
- Test click outside and Cancel button behavior
- Test full replacement flow with all options
wordLimit.test.ts:
- Test word counting with various inputs (spaces, newlines, unicode)
- Test 30-word limit boundary conditions
- Test case-sensitive vs case-insensitive matching logic
- Test edge cases (empty string, long words, punctuation)
* refactor: removed unused initial definition of Replacement
* feat: added replacement rules window in bookmenu
* test: added tests to verify the replacement rules window renders book and global replacement rules, and it opens when bookmenu item is clicked
* feat: added Replacement tab in SettingsDialog, displays global rules
* feat(replacement): connected front-end to functions. todo: fix the automatic reload functionality.
* fix(replacement): simplified re-rendering logic, doesn't fail on epubs anymore.
* test: add integration tests for text replacement functionality
* fix: added single rules section to ReplacementRulesWindow
* fix(replacement): added null checks to some unsafe calls in integration tests
* fix(replacements): added non-null assertion operator for a previously initialized variable
* refactor: created ReplacementPanel and edited style of inputs
* feat: disable the edit feature for selected phrase
* refactor: use toast instead of banner for confirmation msg
* feat: automatically reload the page to apply changes
* feat: disable global rule for book if deleted in book view
* fix(replacement): Improve popup positioning and eliminate ghost animation
- Add viewport boundary detection to keep popup within visible area
- Calculate position only once on mount to prevent jumping when other UI appears
- Use visibility: hidden until position is calculated to eliminate ghost animation
- Add max-height with overflow-y: auto for scrollable content
- Popup now appears directly in correct position without two-step animation
* fix: implement single-instance replacement with persistence
- Add sectionHref to TransformContext for section tracking
- Add singleInstance, sectionHref, occurrenceIndex fields to ReplacementRule
- Pass section name from FoliateViewer to transformer context
- Switch transformer from DOM-based to string-based replacement
- Handle single-instance rules with section matching and occurrence tracking
- Update Annotator to track occurrence index and apply direct DOM changes
- Persist single-instance rules for refresh survival
Single-instance replacements now:
1. Apply immediately via direct DOM modification
2. Store occurrence index and section for precise targeting
3. Persist across page refreshes
* fix: allow multiple single-instance replacements for same word
Single-instance rules now always create new entries instead of merging.
This fixes the issue where replacing multiple occurrences of the same
word would overwrite previous rules.
The transformer applies rules in sequence, so each rule targets
occurrence index 0 of the current (modified) string, allowing
cascading replacements to work correctly after refresh.
* fix: prevent cascading replacements and add wholeWord support
- Add wholeWord field to ReplacementRule for word boundary matching
- Track replaced regions to prevent replacement text from being re-matched
- Fix cascading replacement issue where replacement text was matched again
- Apply replacements from right to left to preserve positions
- Support whole word matching with \b boundaries for both single-instance and regular rules
* Fix whole-word matching for replacement rules
- Auto-enforce whole-word matching for simple word patterns (letters only)
- Add HTML tag boundary checks to prevent matching across tags
- Add double-check validation for whole-word matches
- Prevent matching 'and' inside words like 'England', 'stand', 'understand'
- Add comprehensive logging for debugging replacement issues
* test: added rAF in setup to for ReplacementOptions tests
* fix: only allow replacement for epubs, remove replacement rendering for non-epubs, add test cases
* refactor: refactored replacement logic for case sensitivity and word boundaries
* test: added tests for scope precedence and case sensitivity across scopes
* refactor: removed unnecessary code from testing
* feat: able to display, edit, and delete single-instance rules in book settings
* fix: connected case sensitive checkbox to backend, fixed merge and delete logic
* test: updated test cases to reflect changes on case sensitivity and rules rendering
* test: modified ReplacementOptions test to remove unnecessary case sensitive check from merge
* fix: add logic for grayed out button for non-epubs
* chore: update foliate-js submodule from upstream merge
* fix: resolve all TypeScript/ESLint linting errors
- Fix prefer-const error in ReplacementOptions.tsx
- Fix set-state-in-effect error in ReplacementRulesWindow.tsx (use lazy initializer)
- Replace all @typescript-eslint/no-explicit-any with proper types (ReplacementRule, unknown, etc.)
- Fix unused error variables in replacement.ts (prefix with _)
- Remove unused eslint-disable directives
- Add missing ReplacementRule import in ReplacementPanel.tsx
* fix: add localStorage mock to vitest setup
- Fixes test failures in ReplacementRulesWindow and SettingsDialog tests
- localStorage mock ensures all Storage API methods are available in test environment
* fix: resolve ESLint and TypeScript build errors
- Fix all remaining @typescript-eslint/no-explicit-any errors in test files
- Fix unused error variables in replacement.ts (prefix with _)
- Fix TypeScript error in ReplacementRulesWindow.tsx (move @ts-ignore to correct location)
- All ESLint checks now pass
- Web and Tauri builds compile successfully
* fix: remove lookbehind regex for browser compatibility
- Replace lookbehind assertions (?<!...) with manual boundary checking
- Add isUnicodeWordChar helper function for manual Unicode word boundary detection
- Apply manual boundary checks in applyMultiReplacement and applySingleInstance
- Fixes build_web_app check failures by avoiding lookbehind in compiled output
- Maintains whole-word matching functionality for both ASCII and Unicode patterns
* fix: update tauri-utils version to 2.8.1 to resolve duplicate symbol error
- Update local tauri-utils version from 2.8.0 to 2.8.1 to match crates.io version
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linker error
- Ensures all dependencies use the same tauri-utils version
* fix: use local tauri path directly to resolve version conflicts
- Change tauri dependency to use local path instead of version requirement
- This ensures all dependencies use the same local tauri version (2.9.3)
- Fixes 'links = Tauri' conflict error in Rust linting
- The patch.crates-io should still work for transitive dependencies
* fix: use version requirement with patch for tauri dependency
- Revert to using version requirement '2' instead of direct path
- Rely on [patch.crates-io] to use local tauri version
- Remove Cargo.lock to force fresh dependency resolution
- This should resolve the 'links = Tauri' conflict by ensuring
all tauri dependencies (direct and transitive) use the patched version
* fix: remove plugin patches that cause resolution errors
- Remove all tauri-plugin git patches from [patch.crates-io]
- Keep only tauri, tauri-utils, and tauri-build patches
- Plugins from crates.io will use the patched tauri via transitive dependencies
- Fixes error: patch for tauri-plugin-oauth failed to resolve
* fix: update tauri submodule with tauri-utils version fixes
* fix: revert tauri submodule and update tauri-utils to 2.8.0
- Revert submodule changes that can't be pushed to remote
- Update local tauri-utils version to 2.8.0 to match other packages
- This avoids the need to modify the submodule
* fix: add tauri-plugin to workspace and patch to resolve duplicate symbol error
- Add packages/tauri/crates/tauri-plugin to workspace members
- Add tauri-plugin patch to [patch.crates-io]
- This ensures all tauri dependencies use local versions
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linking error
* chore: restore Cargo.lock from upstream
- Restore the original Cargo.lock from readest/readest main branch
- This ensures reproducible builds and matches upstream
- The lock file will be updated by cargo when dependencies change
* fix: resolve TypeScript errors in test files
- Fix ReplacementOptions.test.tsx: add optional chaining for possibly undefined values
- Fix ReplacementRulesWindow.test.tsx: use proper type assertions for store setState calls
- Use (store.setState as unknown as (state: unknown) => void) pattern for partial state updates
* fix: prevent race condition when deleting replacement rules rapidly
- Add isReloading state to track ongoing delete/edit operations
- Prevent multiple rapid deletions that cause runtime errors during page reload
- Show warning toast when user tries to delete while reload is in progress
- Add finally blocks to ensure isReloading is always reset
- This prevents the 'book doesn't finish rerendering' error
* fix: allow phrases and lines with quotes for single-instance replacements
- Updated isWholeWord() to allow phrases (text with spaces or punctuation)
- Phrases are always allowed for single-instance replacements
- Only single words are checked for partial word matches
- Fixes issue where lines with quotes couldn't be replaced
- Added detailed logging for debugging phrase detection
* fix: allow selections with boundary punctuation and fix pattern matching for punctuation
- Updated isWholeWord() to explicitly allow selections that start or end with punctuation (e.g., 'tis, off;, look,)
- Fixed normalizePattern() to handle patterns with leading/trailing punctuation correctly
- Word boundaries are now only added around the word part, not the punctuation
- Fixes issue where replacements like 'scholar;' were not matching correctly
* fix: escape HTML entities in replacement text to preserve angle brackets
- Added escapeHtmlEntities() function to escape HTML special characters
- Apply HTML escaping to replacement text in both multi and single-instance replacements
- Fixes issue where replacement text like '<<AND>>' was being interpreted as HTML tags
- Angle brackets and other HTML entities are now properly escaped and displayed correctly
* fix: revert Tauri backend changes and resolve package.json conflict
- Revert Cargo.toml and src-tauri/Cargo.toml to match upstream/main
- Resolve @tauri-apps/cli version conflict (2.9.5 -> 2.9.6)
- These changes are not related to the replacement feature implementation
* fix: update pnpm-lock.yaml to match @tauri-apps/cli 2.9.6
* removed useless tests and backend tests from ReplacementOptions integration testing suite
* chore: revert foliate-js submodule to match readest/readest main
* fix: refactored wordLimit logic into a separate util file
* fix: removed additional pr description
* refactor: rewrite replacement transformer to use DOM-based approach
replace string manipulation with DOMParser and TreeWalker
follow pattern from simpleecc transformer
* style: format code with prettier
* fix: remove unused string-manipulation functions
* fix: refactored display dialog logic to match other dialogs
* fix: enabled global rule deletion in book menu
* fix: removed ReplacementPanel from library settings
* fix: deleted SettingsDialog.replacement.test.tsx since we no longer need to display replacements in library settings
* fix: removed text replacement tab from settings dialog
* fix: applied prettier code formatter to replacement rules window
* chore: fix formatting and remove unused file listed by chrox
* chore: format all changed files from pr 2693 and revert pnpm-lock
* rebased Cargo.lock, package.json, pnpm-lock.yaml to upstream main
edits to enable readest to build
* basic changes for rule types
* replacement transformer file added
* additional support code added
* interim updates to replacement.ts file
* adding console log statements to confirm functionality without frontend
* adding more console logs for debugging; i think i got my replacement working, will clean console logs and add actual tests now.
* figured out how to get my transformer to work. replacement doesnt actually work yet. figuring that out rn. committing before i destroy something, lol
* replcement logic working with hard coded tests. code is cleaned up with minimal console logs. actual replacement logic + testing is next :)
* test suite built, and fully passing. made consle log edits too.
* added more replacement rules, but figuring out why they arent being implemented by my code.
* cleaning up test suite to not break when there are 0 rules; test is commited with 1 local rule. not sure if that rule is going to copy over when i merge.
* add: basic ui replacement menu
* add: frontend menu ui to annotation settings
- create replacementoptions file for 4 fix options: fix once, fix in library, fix in book, fix in library
-integrate with annotator.tsx
only frontend changes, but initialzied in backend
* add: delete global option and click gear option to get rid of menu
* docs: add test cases for replacementoptions file
* feat(replacement): modified ViewSettings interface and added Replacement type
feat(replacement): modified viewsettings interface and added ReplacementRulesConfig
* feat(replacement): Add text field, case sensitivity checkbox, and confirmation dialog to ReplacementOptions
- Add text input field for replacement text with placeholder
- Add 'Case Sensitive' checkbox (default: unchecked/case-insensitive)
- Implement two-step confirmation flow with Back/Confirm buttons
- Show preview of original text, replacement text, scope, and case sensitivity
- Disable scope buttons until replacement text is entered
- Display truncated preview for long selected text (>50 chars)
- Export ReplacementConfig type for use in parent components
* feat(replacement): Add 30-word limit and integrate new ReplacementOptions component
- Add MAX_REPLACEMENT_WORDS constant (30 words)
- Add getWordCount() utility function for word counting
- Show warning toast when word limit exceeded on Text Replacement click
- Replace old fix handlers with single handleReplacementConfirm()
- Integrate with new ReplacementConfig (replacementText, caseSensitive, scope)
- Display success toast with scope and case sensitivity info on confirm
* fix(build): Add ReplacementMenu placeholder component
- Create placeholder component to fix missing import error in reader/page.tsx
- Component returns null for now, to be implemented with global replacement rules
* test(replacement): Add comprehensive tests for ReplacementOptions and word limit
ReplacementOptions.test.tsx:
- Test rendering of text input, checkbox, and scope buttons
- Test case sensitivity checkbox toggle and state
- Test disabled buttons when no replacement text entered
- Test confirmation dialog flow and Back/Confirm buttons
- Test click outside and Cancel button behavior
- Test full replacement flow with all options
wordLimit.test.ts:
- Test word counting with various inputs (spaces, newlines, unicode)
- Test 30-word limit boundary conditions
- Test case-sensitive vs case-insensitive matching logic
- Test edge cases (empty string, long words, punctuation)
* refactor: removed unused initial definition of Replacement
* feat: added replacement rules window in bookmenu
* test: added tests to verify the replacement rules window renders book and global replacement rules, and it opens when bookmenu item is clicked
* feat: added Replacement tab in SettingsDialog, displays global rules
* fix: added single rules section to ReplacementRulesWindow
* refactor: created ReplacementPanel and edited style of inputs
* feat(replacement): connected front-end to functions. todo: fix the automatic reload functionality.
* fix(replacement): simplified re-rendering logic, doesn't fail on epubs anymore.
* test: add integration tests for text replacement functionality
* fix(replacement): added null checks to some unsafe calls in integration tests
* fix(replacements): added non-null assertion operator for a previously initialized variable
* feat: disable the edit feature for selected phrase
* refactor: use toast instead of banner for confirmation msg
* feat: automatically reload the page to apply changes
* feat: disable global rule for book if deleted in book view
* fix(replacement): Improve popup positioning and eliminate ghost animation
- Add viewport boundary detection to keep popup within visible area
- Calculate position only once on mount to prevent jumping when other UI appears
- Use visibility: hidden until position is calculated to eliminate ghost animation
- Add max-height with overflow-y: auto for scrollable content
- Popup now appears directly in correct position without two-step animation
* fix: only allow replacement for epubs, remove replacement rendering for non-epubs, add test cases
* fix: add logic for grayed out button for non-epubs
* fix: resolve all TypeScript/ESLint linting errors
- Fix prefer-const error in ReplacementOptions.tsx
- Fix set-state-in-effect error in ReplacementRulesWindow.tsx (use lazy initializer)
- Replace all @typescript-eslint/no-explicit-any with proper types (ReplacementRule, unknown, etc.)
- Fix unused error variables in replacement.ts (prefix with _)
- Remove unused eslint-disable directives
- Add missing ReplacementRule import in ReplacementPanel.tsx
* fix: add localStorage mock to vitest setup
- Fixes test failures in ReplacementRulesWindow and SettingsDialog tests
- localStorage mock ensures all Storage API methods are available in test environment
* fix: implement single-instance replacement with persistence
- Add sectionHref to TransformContext for section tracking
- Add singleInstance, sectionHref, occurrenceIndex fields to ReplacementRule
- Pass section name from FoliateViewer to transformer context
- Switch transformer from DOM-based to string-based replacement
- Handle single-instance rules with section matching and occurrence tracking
- Update Annotator to track occurrence index and apply direct DOM changes
- Persist single-instance rules for refresh survival
Single-instance replacements now:
1. Apply immediately via direct DOM modification
2. Store occurrence index and section for precise targeting
3. Persist across page refreshes
* fix: allow multiple single-instance replacements for same word
Single-instance rules now always create new entries instead of merging.
This fixes the issue where replacing multiple occurrences of the same
word would overwrite previous rules.
The transformer applies rules in sequence, so each rule targets
occurrence index 0 of the current (modified) string, allowing
cascading replacements to work correctly after refresh.
* fix: prevent cascading replacements and add wholeWord support
- Add wholeWord field to ReplacementRule for word boundary matching
- Track replaced regions to prevent replacement text from being re-matched
- Fix cascading replacement issue where replacement text was matched again
- Apply replacements from right to left to preserve positions
- Support whole word matching with \b boundaries for both single-instance and regular rules
* Fix whole-word matching for replacement rules
- Auto-enforce whole-word matching for simple word patterns (letters only)
- Add HTML tag boundary checks to prevent matching across tags
- Add double-check validation for whole-word matches
- Prevent matching 'and' inside words like 'England', 'stand', 'understand'
- Add comprehensive logging for debugging replacement issues
* refactor: refactored replacement logic for case sensitivity and word boundaries
* test: added tests for scope precedence and case sensitivity across scopes
* refactor: removed unnecessary code from testing
* feat: able to display, edit, and delete single-instance rules in book settings
* fix: connected case sensitive checkbox to backend, fixed merge and delete logic
* test: updated test cases to reflect changes on case sensitivity and rules rendering
* test: modified ReplacementOptions test to remove unnecessary case sensitive check from merge
* fix: resolve ESLint and TypeScript build errors
- Fix all remaining @typescript-eslint/no-explicit-any errors in test files
- Fix unused error variables in replacement.ts (prefix with _)
- Fix TypeScript error in ReplacementRulesWindow.tsx (move @ts-ignore to correct location)
- All ESLint checks now pass
- Web and Tauri builds compile successfully
* fix: update tauri-utils version to 2.8.1 to resolve duplicate symbol error
- Update local tauri-utils version from 2.8.0 to 2.8.1 to match crates.io version
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linker error
- Ensures all dependencies use the same tauri-utils version
* fix: use local tauri path directly to resolve version conflicts
- Change tauri dependency to use local path instead of version requirement
- This ensures all dependencies use the same local tauri version (2.9.3)
- Fixes 'links = Tauri' conflict error in Rust linting
- The patch.crates-io should still work for transitive dependencies
* fix: use version requirement with patch for tauri dependency
- Revert to using version requirement '2' instead of direct path
- Rely on [patch.crates-io] to use local tauri version
- Remove Cargo.lock to force fresh dependency resolution
- This should resolve the 'links = Tauri' conflict by ensuring
all tauri dependencies (direct and transitive) use the patched version
* fix: remove plugin patches that cause resolution errors
- Remove all tauri-plugin git patches from [patch.crates-io]
- Keep only tauri, tauri-utils, and tauri-build patches
- Plugins from crates.io will use the patched tauri via transitive dependencies
- Fixes error: patch for tauri-plugin-oauth failed to resolve
* fix: add tauri-plugin to workspace and patch to resolve duplicate symbol error
- Add packages/tauri/crates/tauri-plugin to workspace members
- Add tauri-plugin patch to [patch.crates-io]
- This ensures all tauri dependencies use local versions
- Fixes duplicate symbol __TAURI_BUNDLE_TYPE linking error
* chore: restore Cargo.lock from upstream
- Restore the original Cargo.lock from readest/readest main branch
- This ensures reproducible builds and matches upstream
- The lock file will be updated by cargo when dependencies change
* fix: resolve TypeScript errors in test files
- Fix ReplacementOptions.test.tsx: add optional chaining for possibly undefined values
- Fix ReplacementRulesWindow.test.tsx: use proper type assertions for store setState calls
- Use (store.setState as unknown as (state: unknown) => void) pattern for partial state updates
* fix: allow selections with boundary punctuation and fix pattern matching for punctuation
- Updated isWholeWord() to explicitly allow selections that start or end with punctuation (e.g., 'tis, off;, look,)
- Fixed normalizePattern() to handle patterns with leading/trailing punctuation correctly
- Word boundaries are now only added around the word part, not the punctuation
- Fixes issue where replacements like 'scholar;' were not matching correctly
* fix: prevent race condition when deleting replacement rules rapidly
- Add isReloading state to track ongoing delete/edit operations
- Prevent multiple rapid deletions that cause runtime errors during page reload
- Show warning toast when user tries to delete while reload is in progress
- Add finally blocks to ensure isReloading is always reset
- This prevents the 'book doesn't finish rerendering' error
* fix: escape HTML entities in replacement text to preserve angle brackets
- Added escapeHtmlEntities() function to escape HTML special characters
- Apply HTML escaping to replacement text in both multi and single-instance replacements
- Fixes issue where replacement text like '<<AND>>' was being interpreted as HTML tags
- Angle brackets and other HTML entities are now properly escaped and displayed correctly
* fix: revert Tauri backend changes and resolve package.json conflict
- Revert Cargo.toml and src-tauri/Cargo.toml to match upstream/main
- Resolve @tauri-apps/cli version conflict (2.9.5 -> 2.9.6)
- These changes are not related to the replacement feature implementation
* fix: update pnpm-lock.yaml to match @tauri-apps/cli 2.9.6
* removed useless tests and backend tests from ReplacementOptions integration testing suite
* chore: revert foliate-js submodule to match readest/readest main
* fix: refactored display dialog logic to match other dialogs
* fix: enabled global rule deletion in book menu
* fix: removed ReplacementPanel from library settings
* fix: deleted SettingsDialog.replacement.test.tsx since we no longer need to display replacements in library settings
* fix: removed text replacement tab from settings dialog
* fix: applied prettier code formatter to replacement rules window
* fix: refactored wordLimit logic into a separate util file
* fix: removed additional pr description
* style: format code with prettier
* chore: fix formatting and remove unused file listed by chrox
* chore: format all changed files from pr 2693 and revert pnpm-lock
* fix: fixed inconsistencies from rebase
* refactor: removed unused code
* refactor: removed unintentional formatting changes
* fix: set upstream for packages/tauri-plugins to the readest branch
* fix: used original Cargo.lock file
* fix: got Cargo.lock from upstream
* fix: fetched SettingsDialog from upstream main
* fix: pointed tauri-plugins to the same commit as upstream
* chore: remove unnecssary comments from replacement.ts
* chore: fixed more unnecessary comments
---------
Co-authored-by: fatbiscuit247 <fatbiscuit247@github.com>
Co-authored-by: joon <your.email@example.com>
Co-authored-by: jarchenn <jerryc2@andrew.cmu.edu>
Co-authored-by: joon0429 <68578999+joon0429@users.noreply.github.com>
Co-authored-by: Jerry Chen <50bmg@Jerrys-MacBook-Pro-9.local>
Co-authored-by: Alicia Chen <aliciach@andrew.cmu.edu>
Co-authored-by: Jerry Chen <50bmg@MacBook-Pro-7.local>
Co-authored-by: Jerry Chen <50bmg@macbook-pro-158.wifi.local.cmu.edu>
Co-authored-by: fatbiscuit247 <136537548+fatbiscuit247@users.noreply.github.com>