Skip to content

Conversation

@shadowwwind
Copy link
Contributor

@shadowwwind shadowwwind commented Oct 21, 2025

grafik grafik

Summary by CodeRabbit

  • New Features

    • Added "Summary Style" setting in Appearance preferences allowing users to choose between document-categorized or unified list display modes for points.
    • Enhanced support for organizing and displaying points by associated documents, including handling documents without related points.
  • Style

    • Updated UI styling for document headers and improved list display formatting.

@shadowwwind
Copy link
Contributor Author

next i want to add an index of documents at the top, maybe with a count of points for each classification per doc

@ptgms
Copy link
Member

ptgms commented Oct 21, 2025

Can we perhaps make this a setting? I think most users would prefer the way we show the points right now - all in one place

@shadowwwind
Copy link
Contributor Author

Can we perhaps make this a setting? I think most users would prefer the way we show the points right now - all in one place

Yea, should we move it to a separate module for clarity?

@shadowwwind
Copy link
Contributor Author

IMO it improves the clarity and makes the list easier to read because you get that little bit more context.
I believe weither people like it or not it makes the summary easier to comprehend.

@shadowwwind
Copy link
Contributor Author

BTW I believe the settings button at the bottom broke for whatever reason ;D

@shadowwwind
Copy link
Contributor Author

grafik grafik

@shadowwwind shadowwwind self-assigned this Nov 4, 2025
@shadowwwind shadowwwind added enhancement An enhancement. Design The issue is regarding the UI of the extension. labels Nov 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This change adds a pointListStyle configuration setting that enables switching between two display modes for service points in the popup: document-categorized or unified list. The preference is persisted via local storage, controlled through settings UI, and the popup rendering branches accordingly.

Changes

Cohort / File(s) Change Summary
Configuration & Installation
src/scripts/background/install.ts
Adds pointListStyle: "docCategories" field to donation/settings object during extension installation.
State Management
src/scripts/views/popup/state.ts, src/scripts/views/settings/state.ts
Introduces pointListStyle: "docCategories" | "unified" to PopupPreferences interface, hydrates from local storage, exports getpointListStyle() getter, and collects pointListStyle from DOM in settings.
Service & Display Logic
src/scripts/views/popup/service.ts
Expands ServicePoint (case now required, adds document_id), introduces ServiceDocument interface, extends ServiceResponse with documents array, and refactors display logic to branch on pointListStyle: calls populateListDocCategories() for grouped-by-document rendering or populateListUnified() for flat list, with helper functions for filtering, sorting, and divider insertion.
Settings Handler
src/scripts/views/settings/handlers.ts
Adds handler for pointListStyle select element that normalizes value (defaulting to 'docCategories'), updates DOM if changed, and persists via setLocal().
Templates
src/views/popup.html, src/views/settings/settings.html
Replaces pointList container with documentList section and adds docsWithoutPoints area in popup; adds "Summary Style" select control with "Document categorized" and "Unified List" options in settings Appearance section; defers popup.js script loading.
Styling
src/views/style/popup.css
Updates font asset paths to relative (../fonts/), converts #pointList selectors to class-based .pointList, introduces .documentHeader styling for document titles with flex layout and hover effects, and updates dark-mode rules accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Settings
    participant LocalStorage
    participant Popup
    participant Service as Service (popup)

    User->>Settings: Open extension settings
    Settings->>LocalStorage: Read pointListStyle
    LocalStorage-->>Settings: Return stored preference
    Settings->>Settings: Display current style in dropdown

    User->>Settings: Change Summary Style
    Settings->>LocalStorage: Save new pointListStyle
    LocalStorage-->>Settings: Persisted

    User->>Popup: Open popup
    Popup->>LocalStorage: Hydrate pointListStyle
    LocalStorage-->>Popup: Return preference
    
    alt pointListStyle === "docCategories"
        Popup->>Service: Fetch points & documents
        Service-->>Popup: ServiceResponse with both
        Popup->>Service: populateListDocCategories(points, docs)
        Service->>Service: Group points by document
        Service->>Service: Render unlinked points section
        Service-->>Popup: Rendered DOM
    else pointListStyle === "unified"
        Popup->>Service: Fetch points
        Service-->>Popup: ServiceResponse
        Popup->>Service: populateListUnified(points)
        Service->>Service: Flat list rendering
        Service-->>Popup: Rendered DOM
    end

    Popup->>User: Display formatted point list
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas requiring attention:
    • Logic branching in service.ts for the two display modes (populateListDocCategories vs. populateListUnified) and the new helper functions (filterPoints, createSortetPoints, createPointList) to ensure correct point categorization and divider placement.
    • Interface changes to ServicePoint (case now required) and new ServiceDocument interface to confirm data flow consistency across fetching, storage, and rendering.
    • CSS selector migration from ID-based (#pointList) to class-based (.pointList) to verify specificity and styling correctness in both light and dark modes.
    • Settings handler normalization logic and state hydration to ensure pointListStyle defaults and persists correctly across sessions.

Poem

🐰 A tale of two lists, now split with flair,
Documents grouped, or points laid bare—
Choose your style and let it stay,
The popup dances in every way! ✨

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Split points by documents' directly aligns with the core change: implementing document-based categorization for displaying points, including UI updates, new state management, and settings options.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@shadowwwind
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/views/style/popup.css (1)

187-191: Fix the typo in the closing div tag.

Line 190 has a typo: /div> should be </div>.

Apply this diff:

         <div class="">
             <div id="pointList" class="pointList">
                 <a style="display: none">...</a>
-            /div>
+            </div>
         </div>`
src/scripts/views/popup/service.ts (1)

183-210: Critical: XSS vulnerability and syntax error in populateListUnified.

This function has two issues:

  1. Line 190: Syntax error - /div> should be </div>
  2. Line 192: Using innerHTML without sanitization is a potential XSS vector, though the template is static in this case.

Apply this diff to fix the syntax error:

     const temp = `
         <div class="">
             <div id="pointList" class="pointList">
                 <a style="display: none">...</a>
-            /div>
+            </div>
         </div>`

The innerHTML usage here is safe since temp contains only static HTML, but consider documenting this or using a safer approach for consistency.

🧹 Nitpick comments (6)
src/scripts/background/install.ts (1)

16-16: Consider adding type safety for the default value.

The string literal "docCategories" is not type-checked against the union type "docCategories" | "unified" defined in src/scripts/views/popup/state.ts. A typo here would only be caught at runtime.

Consider extracting this as a typed constant or adding a type assertion:

+const DEFAULT_POINT_LIST_STYLE: "docCategories" | "unified" = "docCategories";
+
 export async function handleExtensionInstalled(): Promise<void> {
     const donationAllowed = donationReminderAllowed(navigator.userAgent);
 
     await setLocal({
         themeHeader: true,
         sentry: false,
         displayDonationReminder: {
             active: false,
             allowedPlattform: donationAllowed,
         },
-        pointListStyle: "docCategories"
+        pointListStyle: DEFAULT_POINT_LIST_STYLE
     });
src/scripts/views/settings/state.ts (1)

60-62: Add validation when reading pointListStyle from storage.

The code uses String(result['pointListStyle']) which could produce invalid values if storage is corrupted or manually edited.

Consider validating the stored value:

 if (elements.pointListStyle) {
-    elements.pointListStyle.value = String(result['pointListStyle']);
+    const storedValue = result['pointListStyle'];
+    if (storedValue === "docCategories" || storedValue === "unified") {
+        elements.pointListStyle.value = storedValue;
+    } else {
+        elements.pointListStyle.value = "docCategories";
+    }
 }
src/scripts/views/popup/service.ts (4)

11-11: Consider making document_id nullable explicitly.

Using document_id?: number means the property can be missing or undefined, but line 219 and 258 check for null. Consider using document_id?: number | null for clarity.


216-216: Use for...of instead of for...in for arrays.

Line 216 uses for (let i of documents) which iterates over values, but the variable is named i (suggesting an index). This is confusing.

Apply this diff for clarity:

-    for (let i of documents) {
-        const element = i;
+    for (const element of documents) {

303-316: Simplify createSortedPoints logic.

The function has unnecessary if checks since the arrays will always exist (even if empty) after proper typing.

-function createSortedPoints(sortedPoints:any,pointsList:HTMLElement) {
-    if (sortedPoints.blocker) {
-        createPointList(sortedPoints.blocker, pointsList, false);
-    }    
-    if (sortedPoints.bad) {
-        createPointList(sortedPoints.bad, pointsList, false);
-    }
-    if (sortedPoints.good) {
-        createPointList(sortedPoints.good, pointsList, false);
-    }
-    if (sortedPoints.neutral) {
-        createPointList(sortedPoints.neutral, pointsList, true);
-    }
+function createSortedPoints(sortedPoints: FilteredPoints, pointsList: HTMLElement) {
+    createPointList(sortedPoints.blocker, pointsList, false);
+    createPointList(sortedPoints.bad, pointsList, false);
+    createPointList(sortedPoints.good, pointsList, false);
+    createPointList(sortedPoints.neutral, pointsList, true);
 }

322-322: Non-null assertion may be unsafe.

Line 322 uses pointsFiltered[i]! which assumes the array access is always valid. While the loop bounds ensure this, combining it with optional chaining for case suggests potential undefined values.

Consider defensive coding:

-        const rawTitle = pointsFiltered[i]!.case?.localized_title ?? pointsFiltered[i]!.title;
+        const point = pointsFiltered[i];
+        if (!point) continue;
+        const rawTitle = point.case?.localized_title ?? point.title;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b944b98 and b11a284.

⛔ Files ignored due to path filters (1)
  • src/views/settings/icons/FormatListBulleted.svg is excluded by !**/*.svg
📒 Files selected for processing (8)
  • src/scripts/background/install.ts (1 hunks)
  • src/scripts/views/popup/service.ts (4 hunks)
  • src/scripts/views/popup/state.ts (3 hunks)
  • src/scripts/views/settings/handlers.ts (2 hunks)
  • src/scripts/views/settings/state.ts (3 hunks)
  • src/views/popup.html (2 hunks)
  • src/views/settings/settings.html (1 hunks)
  • src/views/style/popup.css (3 hunks)
🧰 Additional context used
🪛 ast-grep (0.40.0)
src/scripts/views/popup/service.ts

[warning] 192-192: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 192-192: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 233-233: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 252-252: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 269-269: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 329-329: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: point.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 233-233: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 252-252: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 269-269: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 329-329: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: point.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🔇 Additional comments (11)
src/views/popup.html (2)

102-110: LGTM! Document-based structure aligns with new rendering modes.

The changes from pointList to documentList and the addition of a docsWithoutPoints section properly support the new document-categorized display mode.


120-120: Good addition of the defer attribute.

The defer attribute is appropriate for module scripts and ensures proper loading order.

src/views/style/popup.css (3)

4-5: Font path update looks correct.

The addition of ../ prefix to font URLs suggests proper relative path adjustment for the CSS file location.


273-292: Good refactor from ID to class selectors.

Changing from #pointList to .pointList enables multiple point list containers on the page, which is essential for the document-categorized display mode.


294-312: Document header styles look appropriate.

The flex layout and styling for document headers support the new document-categorized view effectively.

src/views/settings/settings.html (1)

78-95: LGTM! Settings UI is well-structured and consistent.

The new "Summary Style" control follows the existing pattern and provides clear options for users. The values match the types defined in the state module.

src/scripts/views/settings/handlers.ts (1)

11-11: LGTM! Element retrieval follows established pattern.

Consistent with other setting elements in the function.

src/scripts/views/settings/state.ts (2)

16-16: LGTM! Added to storage fetch list.

Consistent with other settings.


76-76: LGTM! Element collection updated.

Consistent with other form elements.

src/scripts/views/popup/service.ts (2)

63-69: LGTM! Clear branching logic based on display style.

The conditional logic properly routes to different rendering functions based on the selected style, with appropriate error logging for unsupported values.


7-12: No changes needed—case is correctly marked as required.

The type definition properly marks case as required. Evidence from the codebase shows consistent access to case without optional chaining across lines 289, 292, 295, 298, 325, and 326. Line 322's optional chaining on case (.case?.localized_title) is an outlier and appears to be unnecessary defensive coding. The property is accessed directly as required everywhere else, confirming the type definition is accurate and case is always provided in the API response.

Comment on lines 213 to 277
function populateListDocCategories(allPoints: ServicePoint[], documents: ServiceDocument[]) {
const documentList = document.getElementById('documentList');
// Split points by Document and display them seperatly
for (let i of documents) {
const element = i;

const docPoints = allPoints.filter((point:ServicePoint) => point.document_id === element.id)
const sortedPoints = filterPoints(docPoints)

if (sortedPoints.blocker.length + sortedPoints.bad.length + sortedPoints.neutral.length + sortedPoints.good.length > 0) {
const doc = document.createElement('div');
const temp = `
<div class="">
<div class="documentHeader">
<h3 class="documentTitle">${element.name}</h3>
<a href="${element.url}" target="_blank">Read Original></a>
</div>
<div id="pointList_${element.id}" class="pointList">
<a style="display: none">...</a>
</div>
</div>`;
doc.innerHTML = temp.trim();
documentList!.appendChild(doc.firstChild!);

const pointsList = document.getElementById(`pointList_${element.id}`)!

createSortetPoints(sortedPoints,pointsList)
} else { //documents without points
const docsWithoutPointsWraper = document.getElementById('docsWithoutPointsWraper')
const docsWithoutPoints = document.getElementById('docsWithoutPoints')

if (docsWithoutPoints?.style.display === "none") {
docsWithoutPoints.style.display = "block"
}
const doc = document.createElement('div');
const temp = `
<div class="documentHeader">
<h3 class="documentTitle">${element.name}</h3>
<a href="${element.url}" target="_blank">Read Original></a>
</div>`;
doc.innerHTML = temp.trim();
docsWithoutPointsWraper!.appendChild(doc.firstChild!);
}
}
//display points not liked to a document
const noDocPoints = allPoints.filter((point: ServicePoint) => point.document_id === null)
if (noDocPoints.length > 0) {
const doc = document.createElement('div');
const temp = `
<div class="">
<div class="documentHeader">
<h3 class="documentTitle">Points not linked to a Document</h3>
</div>
`.trim();
if (wrapper.firstChild) {
container.appendChild(wrapper.firstChild as HTMLElement);
<div id="pointList_unlinkedPoints" class="pointList">
<a style="display: none">...</a>
</div>
</div>`;
doc.innerHTML = temp.trim();
documentList!.appendChild(doc.firstChild!);
const sortedPoints = filterPoints(noDocPoints)
const pointsList = document.getElementById(`pointList_unlinkedPoints`)!
createSortetPoints(sortedPoints,pointsList)

}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Multiple XSS vulnerabilities in populateListDocCategories.

Lines 227-228 and 250-251 insert unsanitized data from the API (element.name and element.url) directly into HTML via innerHTML. This is a critical XSS vulnerability if the API response is compromised or contains malicious content.

The static analysis correctly flags this as CWE-79. You must sanitize or escape the content:

+function escapeHtml(text: string): string {
+    const div = document.createElement('div');
+    div.textContent = text;
+    return div.innerHTML;
+}
+
+function escapeUrl(url: string): string {
+    const a = document.createElement('a');
+    a.href = url;
+    return a.href; // Browser normalizes and validates
+}

 function populateListDocCategories(allPoints: ServicePoint[], documents: ServiceDocument[]) {
     // ... existing code ...
     
         if (sortedPoints.blocker.length + sortedPoints.bad.length + sortedPoints.neutral.length + sortedPoints.good.length > 0) {
             const doc = document.createElement('div');
             const temp = `
             <div class="">
                 <div class="documentHeader">
-                    <h3 class="documentTitle">${element.name}</h3>
-                    <a href="${element.url}" target="_blank">Read Original></a>
+                    <h3 class="documentTitle">${escapeHtml(element.name)}</h3>
+                    <a href="${escapeUrl(element.url)}" target="_blank">Read Original></a>
                 </div>

Apply the same escaping to lines 250-251 in the documents-without-points section.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 233-233: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 252-252: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 269-269: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 233-233: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 252-252: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 269-269: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: doc.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🤖 Prompt for AI Agents
In src/scripts/views/popup/service.ts around lines 213-277, the code injects
unsanitized API values (element.name and element.url) directly into innerHTML
causing XSS; fix by building DOM nodes without innerHTML: create elements (div,
h3, a) with document.createElement, set the title text via textContent (or safe
escaping) and assign the link via element.setAttribute('href', safeUrl) after
validating/normalizing the URL (allow only http/https, or prepend https if
missing), add rel="noopener noreferrer" and keep target="_blank", then append
those nodes; apply the exact same change to the documents-without-points block
(lines ~250-251) so both sections use textContent and safe href assignment
instead of string interpolation into innerHTML.

return filteredPoints
}

function createSortetPoints(sortedPoints:any,pointsList:HTMLElement) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in function name.

createSortetPoints should be createSortedPoints.

Apply this diff:

-function createSortetPoints(sortedPoints:any,pointsList:HTMLElement) {
+function createSortedPoints(sortedPoints: FilteredPoints, pointsList: HTMLElement) {

Update all call sites (lines 239 and 274) as well.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function createSortetPoints(sortedPoints:any,pointsList:HTMLElement) {
function createSortedPoints(sortedPoints: FilteredPoints, pointsList: HTMLElement) {
🤖 Prompt for AI Agents
In src/scripts/views/popup/service.ts around line 303 rename the mis-typed
function createSortetPoints to createSortedPoints and update its declaration
accordingly; then update all call sites that reference the old name (notably at
lines ~239 and ~274) to call createSortedPoints instead so the symbol name is
consistent across file.

Comment on lines +318 to 343
function createPointList(pointsFiltered: ServicePoint[], pointsList: HTMLElement, last: boolean) {
let added = 0;
for (let i = 0; i < pointsFiltered.length; i++) {
const point = document.createElement('div');
const pointTitle = pointsFiltered[i]!.case?.localized_title ?? pointsFiltered[i]!.title;

let temp = `
<div class="point ${pointsFiltered[i]!.case.classification}">
<img src="icons/${pointsFiltered[i]!.case.classification}.svg">
<p>${pointTitle}</p>
${renderCuratorTag(pointsFiltered[i]!.status)}
</div>`;
point.innerHTML = temp.trim();
pointsList.appendChild(point.firstChild!);
added++;
if (i !== pointsFiltered.length - 1) {
const divider = document.createElement('hr');
container.appendChild(divider);
pointsList.appendChild(divider);
}
});

if (added > 0 && !isLastGroup) {
}
if (added !== 0 && !last) {
const divider = document.createElement('hr');
divider.classList.add('group');
container.appendChild(divider);
pointsList.appendChild(divider);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: XSS vulnerability in createPointList.

Line 327 directly inserts pointTitle (derived from API data) into HTML via innerHTML on line 330. This is a critical XSS vulnerability.

Even though pointTitle uses a fallback to localized_title or title, both come from the API and must be sanitized:

 function createPointList(pointsFiltered: ServicePoint[], pointsList: HTMLElement, last: boolean) {
     let added = 0;
     for (let i = 0; i < pointsFiltered.length; i++) {
         const point = document.createElement('div');
-        const pointTitle = pointsFiltered[i]!.case?.localized_title ?? pointsFiltered[i]!.title;
+        const rawTitle = pointsFiltered[i]!.case?.localized_title ?? pointsFiltered[i]!.title;
+        const pointTitle = escapeHtml(rawTitle);

         let temp = `
         <div class="point ${pointsFiltered[i]!.case.classification}">

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 329-329: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: point.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 329-329: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: point.innerHTML = temp.trim()
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


const darkmode = Boolean(result['darkmode']);
const storedCuratorMode = Boolean(result['curatorMode']);
pointListStyle = result['pointListStyle'] as "docCategories" | "unified"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add validation for type-asserted storage value.

The type assertion as "docCategories" | "unified" is unsafe because storage could contain any value. This could lead to runtime errors if the stored value is corrupted.

Apply this diff:

-    pointListStyle = result['pointListStyle'] as "docCategories" | "unified"
+    const storedStyle = result['pointListStyle'];
+    pointListStyle = (storedStyle === "docCategories" || storedStyle === "unified") 
+        ? storedStyle 
+        : "docCategories";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pointListStyle = result['pointListStyle'] as "docCategories" | "unified"
const storedStyle = result['pointListStyle'];
pointListStyle = (storedStyle === "docCategories" || storedStyle === "unified")
? storedStyle
: "docCategories";
🤖 Prompt for AI Agents
In src/scripts/views/popup/state.ts around line 45, the code unsafely
type-asserts a storage value to "docCategories" | "unified"; validate the value
at runtime instead of using a direct type assertion. Read the raw value, check
it against an allowed set (e.g., ["docCategories","unified"]), and if it matches
assign it, otherwise assign a safe default (or remove the key) and optionally
log/debug the unexpected value; ensure the variable ends up typed correctly
without relying on an unchecked "as" cast.

Comment on lines +58 to +66
if (pointListStyleSelect) {
pointListStyleSelect.addEventListener('change', () => {
const normalized = pointListStyleSelect.value ?? 'docCategories';
if (pointListStyleSelect.value !== normalized) {
pointListStyleSelect.value = normalized;
}
void setLocal({ pointListStyle: normalized });
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Improve type safety and validation for pointListStyle.

The current implementation has two issues:

  1. The normalization logic (pointListStyleSelect.value ?? 'docCategories') won't catch invalid values already in the select element, unlike the language handler which uses normalizeLanguage().
  2. The normalized variable and the persisted value lack type safety.

Consider adding proper validation:

+function normalizePointListStyle(value: string): "docCategories" | "unified" {
+    if (value === "docCategories" || value === "unified") {
+        return value;
+    }
+    return "docCategories";
+}
+
 if (pointListStyleSelect) {
     pointListStyleSelect.addEventListener('change', () => {
-        const normalized = pointListStyleSelect.value ?? 'docCategories';
+        const normalized = normalizePointListStyle(pointListStyleSelect.value);
         if (pointListStyleSelect.value !== normalized) {
             pointListStyleSelect.value = normalized;
         }
         void setLocal({ pointListStyle: normalized });
     });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (pointListStyleSelect) {
pointListStyleSelect.addEventListener('change', () => {
const normalized = pointListStyleSelect.value ?? 'docCategories';
if (pointListStyleSelect.value !== normalized) {
pointListStyleSelect.value = normalized;
}
void setLocal({ pointListStyle: normalized });
});
}
function normalizePointListStyle(value: string): "docCategories" | "unified" {
if (value === "docCategories" || value === "unified") {
return value;
}
return "docCategories";
}
if (pointListStyleSelect) {
pointListStyleSelect.addEventListener('change', () => {
const normalized = normalizePointListStyle(pointListStyleSelect.value);
if (pointListStyleSelect.value !== normalized) {
pointListStyleSelect.value = normalized;
}
void setLocal({ pointListStyle: normalized });
});
}
🤖 Prompt for AI Agents
In src/scripts/views/settings/handlers.ts around lines 58 to 66, the change
handler uses pointListStyleSelect.value ?? 'docCategories' which neither
validates against allowed values nor preserves type safety; replace this with a
validation/normalization step that checks pointListStyleSelect.value against the
known allowed pointListStyle values (e.g., an enum or const array), map invalid
or missing values to the default 'docCategories', and narrow the type with a
type guard or typed cast before calling setLocal; ensure the persisted value is
of the declared pointListStyle type and update the handler to use that validated
value only.

@ptgms
Copy link
Member

ptgms commented Nov 27, 2025

@shadowwwind want me to take over and finish the implementation?

@shadowwwind shadowwwind changed the base branch from modules to master November 27, 2025 16:52
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Design The issue is regarding the UI of the extension. enhancement An enhancement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants