Skip to content

Implement Highlight Words tab in Verse on Image - #1089

Open
TheNonPirate wants to merge 8 commits into
sillsdev:mainfrom
TheNonPirate:update/implement-single-word-color/1011
Open

Implement Highlight Words tab in Verse on Image#1089
TheNonPirate wants to merge 8 commits into
sillsdev:mainfrom
TheNonPirate:update/implement-single-word-color/1011

Conversation

@TheNonPirate

@TheNonPirate TheNonPirate commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Resolves #1011. This PR implements the Highlight Words tab in Verse on Image, which allows individual words to be colored and their case to be set. It also fixes an issue with Verse on Image where the carousel of tabs can be scrolled using the arrow keys, but it does not fully switch tabs.

Summary by CodeRabbit

  • New Features

    • Added word-level selection and editing for verse overlays, including per-word colors and uppercase/lowercase controls.
    • Introduced a dedicated Word Editor Pane and new word-selection and navigation icons.
  • Refinements

    • Improved editor tab alignment and pane scrolling for smoother navigation.
    • Updated download controls with a cleaner, borderless appearance and improved contrast.
  • Bug Fixes

    • Corrected editor pane labels and text alignment wording.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

VerseOnImage now splits verse text into selectable words, supports per-word color and case editing, adds a Word Editor Pane, and synchronizes editor-tab scrolling. New arrow and word-selection icons are added and exported through the icon registries. The download selector styling is also updated.

Changes

Verse word editing

Layer / File(s) Summary
Word selection and editing flow
src/lib/components/VerseOnImage.svelte, src/lib/icons/image/*, src/lib/icons/index.ts
Verse text is represented as editable word objects, rendered as clickable spans, and modified through color and uppercase/lowercase controls in the new Word Editor Pane.
Editor tab carousel and pane scrolling
src/lib/components/VerseOnImage.svelte
Editor tab activation, carousel positioning, debounced scroll handling, and independent vertical scrolling for editor panes are updated. Pane label typos are corrected.

Download selector styling

Layer / File(s) Summary
Download selector appearance
src/lib/components/DownloadSelector.svelte
The modal and download buttons use the popup background, borderless styling, and gray text.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant VerseOnImage
  participant WordEditorPane
  participant WordState
  VerseOnImage->>WordState: Select rendered word
  VerseOnImage->>WordEditorPane: Show selected word controls
  WordEditorPane->>WordState: Update color or case
  WordState->>VerseOnImage: Apply word styling
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds word selection and color editing, but it does not show per-word size editing requested by issue #1011. Add per-word size controls and apply the selected word size in VerseOnImage.
Out of Scope Changes check ⚠️ Warning DownloadSelector styling changes are unrelated to issue #1011 and the Highlight Words tab objectives. Move the DownloadSelector styling changes to a separate pull request or link them to a relevant issue.
✅ 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 clearly identifies the main change: implementing the Highlight Words tab in Verse on Image.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@TheNonPirate
TheNonPirate requested a review from FyreByrd July 29, 2026 20:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/VerseOnImage.svelte`:
- Around line 1143-1160: The uppercase and lowercase controls in the
word-selection UI use fixed gray icon colors, preventing selected-state styling.
Update the ArrowUpIcon and ArrowDownIcon usages in the relevant button handlers
to receive SliderProgressColor when their corresponding wordSelected.uppercase
state is selected, while retaining `#6B7280` otherwise.
- Around line 841-854: Update the interactive span in the words each-block to be
keyboard-operable: prefer replacing it with a semantic button, or add tabindex
and Enter/Space key handling alongside the existing click behavior. Preserve the
active_editor_index check and word selection logic using wordSelected and
word.color.
- Around line 841-849: Update the word selection handler in the words loop so
selecting a word only assigns `wordSelected` and synchronizes the picker state
from that word’s existing color; remove the immediate `word.color` mutation
using the previous `wordColor`. Preserve color updates exclusively in the
picker-change flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f896b2b-10a3-4529-ab90-a730778d722b

📥 Commits

Reviewing files that changed from the base of the PR and between a1915fe and 4a519de.

📒 Files selected for processing (6)
  • src/lib/components/VerseOnImage.svelte
  • src/lib/icons/ArrowDownIcon.svelte
  • src/lib/icons/ArrowUpIcon.svelte
  • src/lib/icons/image/WordSelectIcon.svelte
  • src/lib/icons/image/index.ts
  • src/lib/icons/index.ts

Comment on lines +841 to +849
{#each words as word, i}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<span
onclick={() => {
if (active_editor_index === wordEditorIndex) {
word.color = `color:${wordColor};`;
wordSelected = word;
}
}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not change a word merely by selecting it.

Line 846 applies the previous selection’s wordColor before assigning wordSelected. Selecting a previously customized word therefore destroys its existing color. Selection should sync the picker from the word; only a picker change should update the word.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/VerseOnImage.svelte` around lines 841 - 849, Update the
word selection handler in the words loop so selecting a word only assigns
`wordSelected` and synchronizes the picker state from that word’s existing
color; remove the immediate `word.color` mutation using the previous
`wordColor`. Preserve color updates exclusively in the picker-change flow.

Comment thread src/lib/components/VerseOnImage.svelte
Comment thread src/lib/components/VerseOnImage.svelte Outdated

@FyreByrd FyreByrd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The functionality is all there and works just like the native app as far as I can tell. Well done.

However, there are a few UI issues...

Image Image

Also, there are tabs that should not need to be vertically scrollable (e.g. the alignment tab) but are vertically scrollable. I think this is because the container for the tabs content has its height set by the tallest element. It looks like this may be a limitation of the DaisyUI classes being used. If you can fix it without much effort, great. If not, it's probably not a big deal.

@TheNonPirate

Copy link
Copy Markdown
Contributor Author

How did you get those issues, particularly the vertical scrolling? I'm having trouble reproducing them.

@FyreByrd

Copy link
Copy Markdown
Collaborator

How did you get those issues, particularly the vertical scrolling? I'm having trouble reproducing them.

How did I get the issues? By running the code... I didn't do anything special to get those issues...

@TheNonPirate

Copy link
Copy Markdown
Contributor Author

What device/browser were you running it on? I can only get the first issue by making the width of the page really small, and I can't get the second issue.

@FyreByrd

Copy link
Copy Markdown
Collaborator

What device/browser were you running it on? I can only get the first issue by making the width of the page really small, and I can't get the second issue.

Brave Browser. I'll check the screen dimensions rq

@FyreByrd

Copy link
Copy Markdown
Collaborator

What device/browser were you running it on? I can only get the first issue by making the width of the page really small, and I can't get the second issue.

Brave Browser. I'll check the screen dimensions rq

Ok. So the max height of my browser window is 638 pixels. The width of the container depends on the width of the image. I am using SAB Test PWA. It shouldn't matter which verse. I am using the first image, which has a width of 375px. Also, another weird bug, the image seems to disappear when the width transitions across 450px and does not reappear until a new image is selected...

@TheNonPirate

Copy link
Copy Markdown
Contributor Author

Do you have a screenshot of the vertical scrolling issue?
Should I try to fix the image disappearing when the width gets too small, or is that not an issue because changing the screen width is mostly a development thing?

@FyreByrd

FyreByrd commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Do you have a screenshot of the vertical scrolling issue? Should I try to fix the image disappearing when the width gets too small, or is that not an issue because changing the screen width is mostly a development thing?

Screenshot 2026-07-30 at 11 29 24 AM Screenshot 2026-07-30 at 11 29 35 AM

I think the resize issue is mostly a development thing, so I honestly wouldn't worry about it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/lib/components/VerseOnImage.svelte (1)

326-331: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rebuild words when the verse source changes.

words is reconstructed only once on mount from selectedVerses.getCompositeText(). Since this can be reached by navigating in via /image, the selected verses can be swapped while the component remains mounted; add a reactively synced effect/reset path so the renderable word array and any attached editors stay in sync with the current verse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/VerseOnImage.svelte` around lines 326 - 331, Update the
VerseOnImage word-initialization flow so words is reactively rebuilt whenever
selectedVerses.getCompositeText() changes, not only during mount. Ensure the
reset also synchronizes or clears any attached editors so rendering and editing
reflect the current verse after navigation or verse swaps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/lib/components/VerseOnImage.svelte`:
- Around line 326-331: Update the VerseOnImage word-initialization flow so words
is reactively rebuilt whenever selectedVerses.getCompositeText() changes, not
only during mount. Ensure the reset also synchronizes or clears any attached
editors so rendering and editing reflect the current verse after navigation or
verse swaps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 731cbfc3-53b9-4b9e-a3c7-b2845de8ad4f

📥 Commits

Reviewing files that changed from the base of the PR and between 4a519de and 2cb7c75.

📒 Files selected for processing (1)
  • src/lib/components/VerseOnImage.svelte

@FyreByrd FyreByrd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think it would be better UX to have the case change buttons located above the color picker, but that may be just me...

Comment thread src/lib/components/VerseOnImage.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/VerseOnImage.svelte`:
- Around line 1128-1130: Update the controls wrapper div adjacent to ColorPicker
in VerseOnImage to use Tailwind v4’s grow utility instead of the obsolete
flex-grow class, preserving the existing flex expansion and centered button
layout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ccc5c94a-d58c-4abf-a6a9-b01db80b5588

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb7c75 and c663db0.

📒 Files selected for processing (1)
  • src/lib/components/VerseOnImage.svelte

Comment thread src/lib/components/VerseOnImage.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/components/DownloadSelector.svelte`:
- Around line 29-42: Update the download buttons in the DownloadSelector
component to use the theme-provided foreground/text color instead of the fixed
text-gray-700 class, ensuring labels and icons remain readable against
PopupBackgroundColor in dark and other supported themes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c0225f1-b653-45f8-84a0-154e81b87b0e

📥 Commits

Reviewing files that changed from the base of the PR and between c663db0 and 29187f1.

📒 Files selected for processing (2)
  • src/lib/components/DownloadSelector.svelte
  • src/lib/components/VerseOnImage.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/components/VerseOnImage.svelte

Comment on lines +29 to +42
styling="padding:0; background-color:{$themeColors['PopupBackgroundColor']}; {positioningCSS}"
>
<div class="grid gap-2 m-2">
<button
class="dy-btn dy-btn-sm flex items-center justify-center gap-2"
class="dy-btn dy-btn-sm flex items-center justify-center gap-2 border-none text-gray-700"
style="background-color:{$themeColors['PopupBackgroundColor']};"
onclick={() => downloadImage()}
>
<ImageIcon.Image />
{$t['Text_On_Image_Save_Image']}
</button>
<button
class="dy-btn dy-btn-sm flex items-center justify-center gap-2"
class="dy-btn dy-btn-sm flex items-center justify-center gap-2 border-none text-gray-700"
style="background-color:{$themeColors['PopupBackgroundColor']};"

@coderabbitai coderabbitai Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 '\bPopupBackgroundColor\b' src
rg -n -C 2 'text-gray-700' src/lib/components

Repository: sillsdev/appbuilder-pwa

Length of output: 5430


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Candidate files mentioning themeColors or PopupBackgroundColor:"
rg -n -C 3 'themeColors|PopupBackgroundColor|TextAppearance|Text_On_Image_Save_(Image|Video)' src/lib src 2>/dev/null || true

echo
echo "DownloadSelector outline and relevant section:"
if [ -f src/lib/components/DownloadSelector.svelte ]; then
  wc -l src/lib/components/DownloadSelector.svelte
  sed -n '1,80p' src/lib/components/DownloadSelector.svelte | cat -n
fi

echo
echo "TextAppearanceSelector relevant section:"
if [ -f src/lib/components/TextAppearanceSelector.svelte ]; then
  sed -n '1,220p' src/lib/components/TextAppearanceSelector.svelte | cat -n
fi

Repository: sillsdev/appbuilder-pwa

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate stores/data files:"
git ls-files 'src/lib/data/*' 'src/lib/data/**/*.svelte' 'src/lib/data/**/*.ts' 'src/lib/data/**/*.js' | sed -n '1,120p'

echo
echo "themeColors definitions/usages near stores:"
rg -n -C 5 "export .*themeColors|let themeColors|const themeColors|PopupBackgroundColor" src/lib/data src 2>/dev/null | sed -n '1,240p'

echo
echo "Theme selector implementation:"
sed -n '1,220p' src/lib/components/TextAppearanceSelector.svelte | cat -n

echo
echo "Data/config theme candidates:"
rg -n -C 3 "PopupBackgroundColor|Theme|theme|DialogBackgroundColor|DialogTextColor|TextColor" src/lib src/routes src/assets --glob '!src/**/node_modules/**' | sed -n '1,240p'

Repository: sillsdev/appbuilder-pwa

Length of output: 35568


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "theme.ts:"
cat -n src/lib/data/stores/theme.ts | sed -n '1,120p'

echo
echo "Store index theme exports:"
cat -n src/lib/data/stores/index.ts | sed -n '1,180p'

echo
echo "Config/theme files only:"
git ls-files | rg '(^|/)(configuration|config|themes|style|styles|tailwind|vite-config|app\.config|theme)' | sed -n '1,200p'

echo
echo "TextAppearanceSelector remainder:"
sed -n '220,340p' src/lib/components/TextAppearanceSelector.svelte | cat -n

echo
echo "Style properties mentioning TextColor / Popup / Dialog:"
rg -n -C 2 "name: 'Text|PopupBackgroundColor|DialogTextColor|TextColor|TextAppear|DialogBackgroundColor|Dialog" src/lib/data/stores/theme.ts src 2>/dev/null | sed -n '1,240p'

Repository: sillsdev/appbuilder-pwa

Length of output: 22839


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import json, re

# Read only configuration/theme data files as text and extract JSON-like objects where safe.
files = list(Path("test_data/data").glob("config.js")) if Path("test_data/data").exists() else []
print("config.js files:", files)
for p in files:
    txt = p.read_text()
    matches = re.findall(r'var\s+config\s*=\s*({[\s\S]*?})\s*;', txt)
    for i, body in enumerate(matches):
        print(f"\n--- {p} match {i+1} ---")
        try:
            obj = json.loads(body)
            print("themes:", len(obj.get("themes", [])))
            for t in obj.get("themes", []):
                cs = next((c for c in t.get("colorSets", []) if c.get("type") == "main"), {})
                bgs = cs.get("colors", {}).get("PopupBackgroundColor", "")
                dialog = cs.get("colors", {}).get("DialogBackgroundColor", "")
                bg = cs.get("colors", {}).get("BackgroundColor", "")
                bg2 = t.get("backgroundColor", "")
                print({
                    "name": t.get("name"),
                    "enabled": t.get("enabled"),
                    "PopupBackgroundColor": bgs,
                    "DialogBackgroundColor": dialog,
                    "BackgroundColor": bg,
                    "backgroundColor": bg2
                })
        except Exception as e:
            print("parse error:", e)
            print(body[:500])
PY

echo
echo "text-based PopupBackgroundColor assignments in test config:"
rg -n -C 4 "PopupBackgroundColor|PopupBackgroundColor" test_data/data src test 2>/dev/null | sed -n '1,220p'

Repository: sillsdev/appbuilder-pwa

Length of output: 6647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant config.theme definitions:"
rg -n -C 8 '"name": "(Normal|Sage|Rose|Sepia|Charcoal|Dark)"|Popu|Dialog|TextColor' test_data/data/config.js 2>/dev/null | sed -n '1,260p'

echo
echo "Search for TextColor definitions in all tracked files:"
rg -n -C 5 '"TextColor": "text"|TextColor|TextAppearance|colors:' test_data/data/config.js 2>/dev/null | sed -n '1,300p'

Repository: sillsdev/appbuilder-pwa

Length of output: 25492


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, re
from pathlib import Path

# Extract JSON from test_data/data/config.js using the config.js parser from the repo if available.
# The repository has src/lib/scripts/config-repository.ts; run it only if its parser is already usable.
text = Path("test_data/data/config.js").read_text()
m = re.search(r'var config = ({[\s\S]*?});', text)
if not m:
    print("could not find config literal")
    raise SystemExit(0)
data = json.loads(m.group(1))

theme_order = ['Normal', 'Sage', 'Rose', 'Sepia', 'Charcoal', 'Dark']
for theme in data.get("themes", []):
    if theme.get("name") in theme_order:
        cs = next((c for c in theme.get("colorSets", []) if c.get("type") == "main"), {})
        colors = cs.get("colors", {})
        bg = colors.get("PopupBackgroundColor")
        text_color = colors.get("TextColor")
        print({
            "theme": theme["name"],
            "PopupBackgroundColor": bg,
            "TextColor": text_color
        })

# Check TextAppearanceSelector code for text color override outside button body.
component = Path("src/lib/components/TextAppearanceSelector.svelte").read_text()
print("\nTextAppearanceSelector contains explicit button text-color style:", bool(re.search(r'style:color\s*=', component)))
print("TextAppearanceSelector contains monoIconColor:", "monoIconColor" in component)
PY

Repository: sillsdev/appbuilder-pwa

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "config.js header/footer:"
wc -l test_data/data/config.js
sed -n '1,120p' test_data/data/config.js | cat -n
sed -n '1820,1920p' test_data/data/config.js | cat -n

echo
echo "Enable fields around all themes:"
rg -n -C 4 '"name": "(Normal|Sage|Rose|Sepia|Charcoal|Dark)"|enabled|PopupBackgroundColor|TextColor' test_data/data/config.js | sed -n '1,260p'

Repository: sillsdev/appbuilder-pwa

Length of output: 21118


🌐 Web query:

Tailwind CSS text-gray-700 hex color RGB dark gray

💡 Result:

In Tailwind CSS, the color value for the gray-700 utility class depends on the version you are using, as Tailwind CSS transitioned to OKLCH color values in version 4 [1]. For Tailwind CSS v3 and earlier, the gray-700 color corresponds to the following values: - HEX: #374151 [2][3][4] - RGB: rgb(55, 65, 81) [2][3][4] For Tailwind CSS v4 and later, which uses the OKLCH color space by default for its color palette, the color definition is: - OKLCH: oklch(0.373 0.034 259.733) [5][1] When using Tailwind CSS, these values are typically handled automatically by the utility classes (e.g., text-gray-700), but if you need to reference them directly in your own CSS or when creating arbitrary values (like text-[#374151]), these are the standard identifiers [3][1]. Note that Tailwind offers multiple "gray" scales (such as Slate, Zinc, Neutral, and Stone), each with slightly different undertones, but the "gray" scale is the standard neutral option [4][6].

Citations:


🌐 Web query:

Dark V8 theme enabled PopupBackgroundColor TextColor config.js test_data/data/config.js

💡 Result:

In technical applications, configuring dark mode themes through a config.js file typically involves defining color variables, often utilizing CSS custom properties (variables) to maintain consistency [1][2]. While specific implementation details vary by codebase, the general pattern for setting popup colors in a configuration object is shown below [1][3][4]. For applications that use a central config.js or theme configuration object, you define the popupBackgroundColor and textColor within your theme export or configuration block [1][3]. Example configuration pattern: // Example configuration in config.js or a theme definition file export const theme = { //... other theme settings popupBackgroundColor: "#121212", // Dark background color textColor: "#FFFFFF", // Light text color for contrast }; // If using CSS variables, it is often structured as::root { --popup-background-color: #121212; --text-color: #ffffff; } When applying these settings, ensure your UI components reference these values. For instance, in React or similar frameworks, you might pass these styles to a component's prop (e.g., overlayInnerStyle) [3][5]. If you are working with a specific framework or library, it is standard practice to: 1. Identify the central configuration file (e.g., config.js, theme.js) [6][7]. 2. Add or override the popupBackgroundColor and textColor properties [1][4]. 3. Ensure the CSS/styling engine is configured to consume these variables (often injected via CSS variables or a ThemeProvider) [1][2][8]. Note that for some advanced dark theme implementations, libraries may use auto-detection or color-mixing algorithms based on your primary colors rather than requiring explicit manual overrides for every component [8]. Always check the documentation specific to your project or library for how it registers theme changes [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

path = Path("test_data/data/config.js")
text = path.read_text()

# Find theme definitions by name and capture enabled=true enabled:false blocks until the next theme or the styles array.
def find_theme(name):
    start = re.search(r'\{\s*\n\s*"name":\s*"{}"'.format(re.escape(name)), text)
    if not start:
        return None
    search = text[start.start():]
    end = re.search(r'\n\s*\{\s*\n\s*"name":\s*"|^\s*"styles":\s*\[\s*\{', search, flags=re.M)
    if end:
        block = search[:end.start()]
    else:
        block = search
    m = re.search(r'"PopupBackgroundColor":\s*"([^"]+)"\s*,', block)
    return {
        "enabled": re.search(r'"enabled":\s*(true|false)\s*,', block),
        "PopupBackgroundColor": m.group(1) if m else None,
        "textContent": bool(re.search(r'\bPopupBackgroundColor\b|TextColor\b', block[:3000]))
    }

print("enabled normal text:", find_theme("Normal"))
print("enabled sepia text:", find_theme("Sepia"))
print("enabled dark text:", find_theme("Dark"))

# Extract theme order from theme.ts and compare against enabled text.
tstxt = Path("src/lib/data/stores/theme.ts").read_text()
m = re.search(r"const themeOrder: string\[\] = (\[[^\]]+\])", tstxt)
print("themeOrder source:", m.group(1) if m else None)
print("disabled text includes Dark:", '"Dark"' in text[re.search(r'"name":\s*"Dark"', text).span()[1] if re.search(r'"name":\s*"Dark"', text) else 0:])
PY

echo
echo "All Dark theme header with enabled:"
m=$(rg -n '"name":\s*"Dark"' test_data/data/config.js | head -1)
line=${m%%:*}
sed -n "$((line-3)),$((line+25))p" test_data/data/config.js | cat -n

Repository: sillsdev/appbuilder-pwa

Length of output: 316


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

path = Path("test_data/data/config.js")
text = path.read_text()

def extract_enabled_popup(theme_name):
    idx = text.find(f'"name": "{theme_name}"')
    if idx == -1:
        return None
    block = text[idx:text.find('"name": "', idx + 1) if text.find('"name": "', idx + 1) != -1 else idx + 4000]
    enabled_match = re.search(r'"enabled":\s*(true|false)\s*,', block[:500])
    popup_match = re.search(r'"PopupBackgroundColor":\s*"([^"]+)"', block[:5000])
    return {
        "enabled": enabled_match.group(1) if enabled_match else "missing",
        "PopupBackgroundColor": popup_match.group(1) if popup_match else "missing"
    }

for theme_name in ["Normal", "Sepia", "Dark", "Charcoal", "Rose", "Sage"]:
    print(extract_enabled_popup(theme_name))

tstxt = Path("src/lib/data/stores/theme.ts").read_text()
m = re.search(r"const themeOrder: string\[\] = (\[[^\]]+\])", tstxt)
print("themeOrder source:", m.group(1))
PY

echo
sed -n '412,422p' test_data/data/config.js | cat -n

Repository: sillsdev/appbuilder-pwa

Length of output: 688


Check Dark theme contrast before keeping text-gray-700.

The Dark theme sets PopupBackgroundColor to #202020 and has TextColor: white, but these download buttons still inherit Tailwind’s gray text. Use the theme-provided foreground color for the button labels/icons, or document that this contrast is intentional for all supported themes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/DownloadSelector.svelte` around lines 29 - 42, Update the
download buttons in the DownloadSelector component to use the theme-provided
foreground/text color instead of the fixed text-gray-700 class, ensuring labels
and icons remain readable against PopupBackgroundColor in dark and other
supported themes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The native app seems to just have a hardcoded text color, so it does not have good contrast in the dark theme. I don't know whether this is something we want to change for the PWA.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@TheNonPirate, understood. The contrast issue is valid in the PWA, but changing it would create behavior that differs from the native app.

I will leave this finding unresolved until the team decides whether the PWA should prioritize theme contrast or native-app visual parity.

You are interacting with an AI system.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Verse on Image: Edit color and size of single words by selection

2 participants