Skip to content

Conversation

@leekahung
Copy link
Contributor

@leekahung leekahung commented Nov 20, 2025

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Infrastructure
  • Maintenance

Description

This PR updates the Letter page, including a new disclaimer, a pop-up box, and an updated prompt. The initial two messages in the Letter page will now be hidden (see clip).

For the reference link back to the referrer, I've omitted it for now to maintain flexibility for different organizations and refer to it as "your previous page."

Related Tickets & Documents

QA Instructions, Screenshots, Recordings

Screen.Recording.2025-11-20.at.2.45.03.PM.mov

Added/updated tests?

  • Yes
  • No, and this is why: please replace this line with details on why tests have not been included
  • I need help with writing tests

Documentation

  • If this PR changes the system architecture, Architecture.md has been updated

[optional] Are there any post deployment tasks we need to perform?

…ogic to hide initial 2 messages from Letter page; Create pop-up box for Letter page; Updated disclaimer for Letter page
@leekahung leekahung self-assigned this Nov 20, 2025
@leekahung leekahung added the frontend Frontend implementation (follows UX design) label Nov 20, 2025
@leekahung leekahung requested a review from apkostka November 20, 2025 22:59
@github-actions

This comment was marked as resolved.

@leekahung
Copy link
Contributor Author

Included @michaelzhang43 just to check the visual changes

@github-actions

This comment was marked as resolved.

}, [messages, startStreaming, addMessage, setMessages]);

useEffect(() => {
if (messages.length > 1 && messages[1].content !== "") {

Choose a reason for hiding this comment

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

Potential Bug: This condition checks messages.length > 1 && messages[1].content !== "", but what happens if:

  1. Only 1 message exists?
  2. The second message (index 1) is still streaming and hasn't received content yet?

This could cause the loading state to persist longer than expected in edge cases. Consider adding more robust checks or handling these edge cases explicitly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (messages.length > 1 && messages[1].content !== "") {
if (messages.length >= 1 && messages[1].content !== "") {

This seems reasonable, right?

@github-actions

This comment was marked as resolved.

…etter page; Refactor letterHelper userMessage for better maintainability; Fix formatting
@github-actions

This comment was marked as resolved.

…age; Include basic sanitation for org in letterHelper
@github-actions
Copy link

github-actions bot commented Nov 20, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

This comment was marked as resolved.

@github-actions

This comment was marked as resolved.

Copy link
Contributor

@yangm2 yangm2 left a comment

Choose a reason for hiding this comment

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

I'd like to see better test coverage before merging. I'm not sure if you're still gathering general feedback/reviews before investing in more tests or whether you're ready to merge.

Comment on lines 21 to 24
const sanitizedOrg = org
.replace(/[<>'"{}[\]]/g, "")
.trim()
.slice(0, 100);
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason why we're not using an Allow-list? For now there's only 1 client for this feature so there's a YAGNI argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could try and maintain a white list, but that would need to be updated each time we add a new organization, trade offs for both

`Draft a letter related to housing issues for my area${locationString ? ` (${locationString})` : ""} to my landlord.`,
`Use the information in this prompt to generate a letter to my landlord.`,
`The issue could be maintenance issues, unsafe conditions, or anything else affecting my home, use a broken faucet as an example.`,
`Update the letter as we discuss.`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
`Update the letter as we discuss.`,
`Update the letter as we discuss. Ask me questions to fill in placeholders in the letter.`,

Would this make the initial interactions more like an interview with a clinic lawyer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From testing it, the conversation seems the same as before

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, in the future, I'd like to get the chat bot to "interview"/prompt the user for the missing information.

`The issue could be maintenance issues, unsafe conditions, or anything else affecting my home, use a broken faucet as an example.`,
`Update the letter as we discuss.`,
`Update all placeholders for city and state in the letter with${locationString ? ` (${locationString})` : ""}`,
];
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
];
`When all but the signature placeholder have been replaced, please confirm that I have proof-read the letter for accuracy in content and tone, then provide instructions for how to copy and paste (formatted) the letter from the browser into a application of my choice, the necessary and optional notification/deliveries to the recipient(s), and retention/receipt best practices.`,
];

What do you think of this addition?

Copy link
Contributor Author

@leekahung leekahung Nov 21, 2025

Choose a reason for hiding this comment

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

Hm... need to play around with that new addition before I can tell

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've made some additional adjustments on top of the new addition by having it refer to the organization for further steps of action after the letter is completed. Sounds pretty reasonable from what I can see (see excerpt below).

Bot: I have added your contact information to the letter. All the necessary details are now included.

Please take a moment to proofread the letter below for accuracy in content and tone.

Once you have confirmed the letter is ready, here are the next steps:

**Copying the Letter:**
To copy the letter, you can highlight the entire text with your mouse, right-click, and select "Copy." Then, you can open a word processor (like Microsoft Word, Google Docs, or even a simple text editor) and paste the letter by right-clicking and selecting "Paste." This will preserve the formatting, including bolding, italics, and the clickable links.

**Sending the Letter (Delivery):**
It is very important to have proof that you sent this letter and that your landlord received it.

*   **Necessary:** Send the letter by certified mail with a return receipt requested. The post office will give you a tracking number and a green card that the landlord will sign upon receipt. This signed card will be mailed back to you and serves as legal proof of delivery. Keep this card in a safe place with your copy of the letter.
*   **Optional but Recommended:** You can also send a copy of the letter through regular mail and another copy via email, if you have an email address for your landlord. In the email, you can state, "For your convenience, I have attached a copy of the letter that was also sent to you via certified mail." This creates an additional record.

**Record Keeping (Retention/Receipt Best Practices):**
*   Keep a printed copy of the letter you sent for your own records.
*   Staple the certified mail receipt (the one you get at the post office) and the return receipt (the green card that comes back to you) to your copy of the letter.
*   Take a photo or make a digital scan of the signed letter, the post office receipt, and the signed return receipt for your digital files.
*   Keep all communication with your landlord about this issue, including emails, text messages, or notes from phone calls (be sure to write down the date, time, and what was discussed).

After you send the letter, your landlord has a "reasonable" amount of time to make the repairs. For something like a broken door that affects your security, a week or two would generally be considered reasonable. If your landlord does not respond or make the repairs, the steps you found on Oregon Law Help will guide you on what to do next, which may include options like terminating your lease or suing your landlord for the cost of repairs. Following these documentation steps is crucial if you need to take further action.

Comment on lines +17 to +22
<p id="letter-dialog-description">
You've been redirected here so we can help you create a letter to your
landlord. It'll take a few seconds to complete your initial letter.
You could instruct the tool to update the letter to your liking after
it's generated. Once your letter is complete, you should go back to
your previous page and follow the remaining steps.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we store this in a const?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When we start making more modals, think it'll be a good time to move this to a const

It should be fine for a one off

…ng; Add tests for Letter page and MessageWindow covering the new features
@github-actions

This comment was marked as outdated.

@github-actions
Copy link

github-actions bot commented Nov 22, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

… Chat components and utils; Refactor letter disclaimer as its own subcomponent
@github-actions

This comment was marked as outdated.

@leekahung leekahung requested a review from yangm2 November 22, 2025 02:01
@leekahung
Copy link
Contributor Author

@yangm2 I've included some additional test coverage and changes based on the feedback

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions
Copy link

github-actions bot commented Nov 22, 2025

Claude finished @leekahung's task —— View job


Code Review Complete ✅

I've completed a comprehensive review of PR #218. This PR successfully implements a modal dialog explaining letter generation and addresses all critical security concerns from previous reviews. Overall, this is solid, production-ready code with good security practices and thoughtful implementation.


Summary

Overall Assessment: ✅ Approved - Ready to merge

The author has made excellent progress addressing all feedback from previous automated reviews and yangm2's concerns about test coverage. The implementation demonstrates professional security practices, clean code patterns, and adequate test coverage for the new functionality.

Key Strengths:

  • ✅ XSS vulnerability properly resolved with comprehensive DOMPurify configuration
  • ✅ Memory leak fixed with proper useEffect cleanup
  • ✅ Magic numbers extracted to descriptive constants
  • ✅ Prompt injection risk mitigated with input sanitization
  • ✅ Dialog component properly extracted with ARIA attributes
  • ✅ Good test coverage added for new functionality
  • ✅ DOMPurify configuration has thorough test coverage
  • ✅ Disclaimer extracted to separate component for maintainability

Detailed Review by Focus Area

1. Code Quality ✅ Excellent

Strengths:

  • Clean React patterns with proper hooks usage and correct dependency arrays
  • Well-named constants improve readability:
    • LOADING_DISPLAY_DELAY_MS = 1000 (Letter.tsx:22)
    • LETTER_PAGE_HIDDEN_MESSAGES = 2 (MessageWindow.tsx:40)
  • Excellent separation of concerns:
    • Dialog extracted to LetterGenerationDialog component
    • Disclaimer extracted to LetterDisclaimer component
    • Letter helper logic in dedicated utility file
  • Helpful inline comments explain non-obvious logic (Letter.tsx:58, MessageWindow.tsx:39)
  • Array-based prompt construction (letterHelper.ts:25-36) is maintainable

Observations:

Letter.tsx:59 - The loading condition uses optional chaining:

if (messages.length > 1 && messages[1]?.content !== "")

This is good! It safely checks for the second message's content without risking undefined access. yangm2's suggestion about using >= wouldn't be correct here since we need at least 2 messages (indices 0 and 1).

Component Organization: The structure is excellent:

  • Dialog ref forwarding pattern (Letter.tsx:76) is clean and idiomatic
  • Disclaimer as separate component (Letter.tsx:122) improves maintainability
  • Conditional disclaimer text based on isOngoing prop is elegant

2. Security ✅ Very Strong

Excellent defense-in-depth implementation:

XSS Protection - Properly Implemented

The author's claim is correct: letterContent is sanitized in useLetterContent hook (useLetterContent.tsx:16) using DOMPurify with strict settings:

DOMPurify.sanitize(latestLetter?.content, SANITIZE_SETTINGS)

The sanitization configuration (dompurify.ts) is well-designed:

  • Allowed tags: Only safe tags (a, em, strong, p)
  • Forbidden tags: Dangerous tags explicitly blocked (script, iframe, object, embed, style)
  • Link security: Automatic target="_blank" and rel="noopener noreferrer" via afterSanitizeAttributes hook
  • Attribute filtering: Strict allowlist (href, title, target, rel)
  • Comprehensive test coverage: dompurify.test.ts validates:
    • Removal of dangerous tags (script, iframe, style, object, embed)
    • Preservation of allowed tags
    • Addition of security attributes to links
    • Stripping of disallowed attributes (onclick, etc.)

This layered security approach is excellent:

  1. URL parameter sanitization at input (letterHelper.ts:21-24)
  2. Content sanitization before rendering (useLetterContent.tsx:16)
  3. Strict HTML allowlist prevents dangerous tags
  4. Link security attributes automatically added

Prompt Injection Mitigation - Well Implemented

The org parameter sanitization (letterHelper.ts:21-24) effectively reduces injection risks:

const sanitizedOrg = org
  .replace(/[<>'"{}[\]]/g, "")
  .trim()
  .slice(0, 100);

This removes dangerous characters and enforces length limits.

Re: yangm2's allowlist suggestion: While an allowlist would be more secure, the current approach provides good flexibility for multiple organizations without code changes. The sanitization approach is a reasonable trade-off given the use case.


3. Performance ✅ Good

No performance concerns identified:

  • useEffect dependencies are correct, preventing unnecessary re-renders or infinite loops
  • Conditional rendering is efficient
  • The 1000ms delay (LOADING_DISPLAY_DELAY_MS) is intentional for UX smoothness, as documented inline (Letter.tsx:60)
  • No expensive computations or memory-intensive operations
  • Component structure prevents unnecessary re-renders

4. Testing ✅ Good

Current Test Coverage:

Letter.test.tsx:

  • Effect orchestration (first effect → second effect order) ✅
  • Loading state behavior before second message arrives ✅
  • Dialog close functionality ✅

MessageWindow.test.tsx:

  • Message slicing logic for letter page ✅
  • Message display on regular pages ✅

dompurify.test.ts:

  • Removal of dangerous tags (script, iframe, style, object, embed) ✅
  • Preservation of allowed tags ✅
  • Link security attributes automatically added ✅
  • Disallowed attribute stripping ✅

Assessment: The test coverage is good and directly addresses yangm2's concerns. The critical functionality is tested:

  • Dialog behavior
  • Loading state transitions
  • Message slicing logic
  • DOMPurify sanitization with edge cases

Minor Test Gaps (not blocking):

  • Edge case: Single message scenario when loading logic runs
  • Edge case: Dialog keyboard support (Escape key) - though native <dialog> should handle this
  • Integration: Full letter generation flow end-to-end

These can be addressed in follow-up work if needed.


5. Documentation ✅ Good

Strengths:

  • ✅ Inline comments explain key decisions (Letter.tsx:58, MessageWindow.tsx:39)
  • ✅ Constants are self-documenting with descriptive names
  • ✅ PR description includes video demonstration
  • ✅ ARIA labels provide semantic meaning (LetterGenerationDialog.tsx:9-16)
  • ✅ Updated disclaimer is comprehensive and legally appropriate (LetterDisclaimer.tsx)
  • ✅ Disclaimer text has been extracted to its own component as suggested

Specific Code Analysis

Letter.tsx ✅

  • Lines 59-65: Memory leak properly fixed with cleanup function: return () => clearTimeout(timeoutId);
  • Lines 92-94: dangerouslySetInnerHTML is safe - content sanitized in useLetterContent hook (verified at useLetterContent.tsx:16)
  • Line 76: Good use of ref forwarding to dialog component
  • Lines 69-70: Dialog properly initialized with showModal()
  • Line 122: Excellent extraction of disclaimer to its own component (addresses yangm2's feedback)

LetterGenerationDialog.tsx ✅

  • Lines 9-16: Excellent accessibility implementation:
    • aria-label for the dialog
    • aria-labelledby pointing to title
    • aria-describedby pointing to description
    • Screen-reader-only title with sr-only class
  • Line 26: Dialog close button uses ref correctly
  • Clean component: Single responsibility, reusable pattern

LetterDisclaimer.tsx ✅

  • Excellent extraction: Disclaimer text is now in its own component, improving maintainability (addresses yangm2's suggestion)
  • Conditional rendering: Different disclaimers for ongoing vs initial state
  • Link integration: Proper link to privacy policy with target="_blank"

letterHelper.ts ✅

  • Lines 21-24: Good sanitization removes dangerous characters and limits length to 100 chars
  • Lines 25-36: Array-based prompt construction is maintainable and easy to modify
  • Type safety: Proper return type definition and null checks
  • Lines 32-35: yangm2's suggestions about proofreading/delivery instructions have been incorporated

MessageWindow.tsx ✅

  • Lines 39-43: Clear constant naming (LETTER_PAGE_HIDDEN_MESSAGES) and helpful comment explain the message hiding logic
  • Lines 41-43: displayedMessages pattern is elegant and maintainable

Test Files ✅

  • Letter.test.tsx: Good test structure with proper mocking, waitFor usage, and act() for interactions
  • MessageWindow.test.tsx: Clean tests validating message slicing logic for letter vs regular pages
  • dompurify.test.ts: Comprehensive coverage of sanitization edge cases

Response to Human Reviewer (yangm2)

yangm2 requested better test coverage before merging and raised several specific points. Here's my assessment:

  1. ✅ Test Coverage - RESOLVED: The author has added meaningful tests covering:

    • Loading state transitions (Letter.test.tsx:137-151)
    • Dialog close behavior (Letter.test.tsx:153-166)
    • Message slicing logic (MessageWindow.test.tsx:30-52)
    • DOMPurify sanitization (dompurify.test.ts)

    This directly addresses the core concern about test coverage.

  2. 🟡 Allowlist vs Sanitization (letterHelper.ts:24):

    • Valid YAGNI argument acknowledged
    • Current approach provides flexibility for multiple organizations
    • Sanitization is a reasonable trade-off
  3. 🟡 Prompt Enhancement (letterHelper.ts:30):

    • "Ask me questions..." addition
    • Author indicates conversation seems the same
    • Reasonable to keep as-is
  4. ✅ Proofreading Prompt (letterHelper.ts:32-35):

    • The comprehensive addition about proofreading, copy/paste, and delivery has been incorporated
    • Lines 32-35 now include these instructions
    • Line 35 also references the organization for follow-up steps
  5. ✅ Extract Dialog Text (LetterGenerationDialog.tsx:23):

    • Component is focused and self-contained
    • Reasonable for one-off implementation
  6. ✅ Extract Disclaimer (Letter.tsx:122):

    • Addressed: Disclaimer has been extracted to its own component (LetterDisclaimer.tsx)
    • Improves maintainability as suggested

Recommendations

Optional Improvements (Not Blocking):

  1. The loading condition at Letter.tsx:59 already uses optional chaining (messages[1]?.content), which is excellent
  2. All of yangm2's main concerns have been addressed

Future Considerations:

  1. Add edge case tests for single-message scenarios if needed
  2. Consider A/B testing the prompt improvements to measure effectiveness
  3. Monitor user feedback on the new dialog and disclaimer

Comparison to Previous Reviews

The author has successfully addressed all critical feedback:

Issue Initial Status Current Status
XSS vulnerability 🔴 Critical Resolved
Memory leak 🔴 Critical Resolved
Magic numbers 🟡 Warning Resolved
Prompt injection 🟡 Warning Resolved
Code patterns 🟡 Warning Improved
Accessibility 🟡 Warning Improved
Test coverage 🟡 Warning Resolved
Disclaimer extraction 🟡 Suggestion Resolved

Final Recommendation

APPROVED - Ready to merge

This PR demonstrates:

  • ✅ Professional security practices with comprehensive XSS protection and defense-in-depth
  • ✅ Clean, maintainable code following React best practices
  • ✅ Proper resolution of all critical issues from previous reviews
  • ✅ Good UX improvement with clear user guidance via modal dialog
  • ✅ Adequate test coverage for new functionality (addresses yangm2's concern)
  • ✅ Responsive to reviewer feedback with thoughtful implementation choices
  • ✅ Professional patterns: correct use of React hooks, refs, effects, and ARIA attributes
  • ✅ Excellent component extraction for maintainability

The implementation is solid and production-ready. All of yangm2's main concerns have been addressed.


Positive Highlights 👏

  • Excellent security response: Comprehensive DOMPurify configuration with thorough tests demonstrates professional security implementation
  • Clean code: Well-structured, readable, and maintainable with excellent component extraction
  • Good UX: Modal dialog provides clear guidance to users about the process
  • Responsive to feedback: All critical issues properly addressed, including test coverage and component extraction
  • Professional patterns: Correct use of React hooks, refs, effects, and ARIA attributes
  • Thoughtful implementation: Defense-in-depth security, proper cleanup, meaningful constants
  • Test coverage: Addressed yangm2's primary concern with meaningful tests
  • Maintainability: Disclaimer and dialog properly extracted as separate components

Great work on this feature! The code quality, security practices, and test coverage are all excellent. This is ready for production.


Copy link
Contributor

@yangm2 yangm2 left a comment

Choose a reason for hiding this comment

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

Approving based on Claude's assessment and my own non-fluent understanding of frontend/JS/TSX. Thanks for adding tests.

@leekahung leekahung changed the title Adjust initial prompt for letter generation in Letter page; Include l… [Enhancement] - Update Landing Page UI Nov 30, 2025
@leekahung leekahung merged commit 50ac0e4 into main Nov 30, 2025
5 checks passed
@leekahung leekahung deleted the issue-215/update-letter-landing-page branch November 30, 2025 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend Frontend implementation (follows UX design)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Landing page for Oregon Law Help letter writing tool

3 participants