From 9730377be7c71678e540783dd047389d8b544eaf Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:06:01 +0700 Subject: [PATCH 01/29] feat: paginate disabled inline sites --- .../components/DisabledInlineSitesPanel.tsx | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 entrypoints/popup/components/DisabledInlineSitesPanel.tsx diff --git a/entrypoints/popup/components/DisabledInlineSitesPanel.tsx b/entrypoints/popup/components/DisabledInlineSitesPanel.tsx new file mode 100644 index 00000000..9ea5a4af --- /dev/null +++ b/entrypoints/popup/components/DisabledInlineSitesPanel.tsx @@ -0,0 +1,162 @@ +import { useEffect, useState } from "react"; +import { t } from "../../../lib/i18n"; +import { filterDisabledInlineSites } from "src/utils/inlineSiteSettings"; +import Button from "./Button"; +import Input from "./Input"; + +const INLINE_SITES_PAGE_SIZE = 5; + +interface DisabledInlineSitesPanelProps { + sites: string[]; + onEnable: (site: string) => Promise; +} + +/** Renders searchable, paginated controls for websites with the inline helper disabled. */ +export default function DisabledInlineSitesPanel({ + sites, + onEnable, +}: DisabledInlineSitesPanelProps) { + const [query, setQuery] = useState(""); + const [page, setPage] = useState(1); + const filteredSites = filterDisabledInlineSites(sites, query); + const pageCount = Math.max( + 1, + Math.ceil(filteredSites.length / INLINE_SITES_PAGE_SIZE), + ); + const currentPage = Math.min(page, pageCount); + const startIndex = (currentPage - 1) * INLINE_SITES_PAGE_SIZE; + const visibleSites = filteredSites.slice( + startIndex, + startIndex + INLINE_SITES_PAGE_SIZE, + ); + + useEffect(() => { + if (page > pageCount) setPage(pageCount); + }, [page, pageCount]); + + /** Updates the search query and returns to the first page. */ + const handleQueryChange = (value: string) => { + setQuery(value); + setPage(1); + }; + + /** Shows the previous disabled-sites page. */ + const showPreviousPage = () => setPage((current) => Math.max(1, current - 1)); + + /** Shows the next disabled-sites page. */ + const showNextPage = () => + setPage((current) => Math.min(pageCount, current + 1)); + + if (sites.length === 0) { + return ( +

+ {t("noDisabledSites")} +

+ ); + } + + return ( +
+ {sites.length > INLINE_SITES_PAGE_SIZE && ( + + )} + + {visibleSites.length === 0 ? ( +

+ {t("noDisabledSites")} +

+ ) : ( +
+ {visibleSites.map((site) => ( +
+ + {site} + + +
+ ))} +
+ )} + + {pageCount > 1 && ( +
+ + + {currentPage} / {pageCount} + + +
+ )} +
+ ); +} From 81621a8caa52746c4b876e34035a9670efd69759 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:06:35 +0700 Subject: [PATCH 02/29] feat: add disabled-site filtering --- src/utils/inlineSiteSettings.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/utils/inlineSiteSettings.ts b/src/utils/inlineSiteSettings.ts index fd080721..baa1928f 100644 --- a/src/utils/inlineSiteSettings.ts +++ b/src/utils/inlineSiteSettings.ts @@ -22,3 +22,14 @@ export function parseDisabledInlineSites(value: unknown): string[] { ), ).sort((a, b) => a.localeCompare(b)); } + +/** Filters disabled-site hostnames using the same normalization as stored values. */ +export function filterDisabledInlineSites( + sites: string[], + query: string, +): string[] { + const normalizedQuery = normalizeSiteHostname(query); + if (!normalizedQuery) return sites; + + return sites.filter((site) => site.includes(normalizedQuery)); +} From e83862d359142b32d053fe4a95ca9c625fea11a9 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:06:49 +0700 Subject: [PATCH 03/29] test: cover disabled-site filtering --- tests/utils/inlineSiteSettings.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/utils/inlineSiteSettings.test.ts b/tests/utils/inlineSiteSettings.test.ts index 13253124..4fbca33b 100644 --- a/tests/utils/inlineSiteSettings.test.ts +++ b/tests/utils/inlineSiteSettings.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + filterDisabledInlineSites, normalizeSiteHostname, parseDisabledInlineSites, } from "../../src/utils/inlineSiteSettings"; @@ -24,4 +25,19 @@ describe("inline site settings", () => { it("handles invalid legacy storage values", () => { expect(parseDisabledInlineSites({ site: "voidzero.dev" })).toEqual([]); }); + + it("filters disabled sites with normalized partial hostnames", () => { + const sites = ["accounts.google.com", "github.com", "mail.google.com"]; + + expect(filterDisabledInlineSites(sites, " WWW.GOOGLE. ")).toEqual([ + "accounts.google.com", + "mail.google.com", + ]); + }); + + it("returns all disabled sites for an empty search", () => { + const sites = ["github.com", "typeform.com"]; + + expect(filterDisabledInlineSites(sites, " ")).toBe(sites); + }); }); From beb45d818c0405a5d1c53039388554b29f81b950 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:07:10 +0700 Subject: [PATCH 04/29] chore: set extension version to 1.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4ad38ed6..74a6f5f8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "gmail-alias-toolkit", "description": "Generate and manage Gmail aliases with plus addressing and presets", "private": true, - "version": "1.3.1", + "version": "1.3.2", "author": "dev@eplus.dev", "license": "MIT", "homepage_url": "https://eplus.dev", From 8149632b8892853eb9b5e123ea5e875fb62ca3b9 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:08:08 +0700 Subject: [PATCH 05/29] chore: apply extension changelog update --- .../apply-changelog-sites-update.yml | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 .github/workflows/apply-changelog-sites-update.yml diff --git a/.github/workflows/apply-changelog-sites-update.yml b/.github/workflows/apply-changelog-sites-update.yml new file mode 100644 index 00000000..f04a9658 --- /dev/null +++ b/.github/workflows/apply-changelog-sites-update.yml @@ -0,0 +1,175 @@ +name: Apply extension changelog update + +on: + push: + branches: + - feat/update-extension-changelog-sites-pagination + +permissions: + contents: write + +jobs: + apply: + if: ${{ !contains(github.event.head_commit.message, '[skip apply-update]') }} + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v7 + with: + ref: feat/update-extension-changelog-sites-pagination + + - name: Patch settings and landing version + run: | + python3 <<'PY' + from pathlib import Path + + settings_path = Path("entrypoints/popup/components/Settings.tsx") + settings = settings_path.read_text() + + import_line = 'import Input from "./Input";\n' + import_replacement = ( + 'import Input from "./Input";\n' + 'import DisabledInlineSitesPanel from "./DisabledInlineSitesPanel";\n' + ) + if settings.count(import_line) != 1: + raise SystemExit("Unexpected Settings Input import count") + settings = settings.replace(import_line, import_replacement, 1) + + changelog_marker = ''' { + version: "1.3.0", + date: "2026-07-13", + ''' + changelog_entries = ''' { + version: "1.3.2", + date: "2026-07-24", + changes: [ + { + type: "Added", + items: [ + "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions", + ], + }, + { + type: "Changed", + items: [ + "Aligned WXT development and production URL-scope handling", + "Restricted development builds to the configured site allowlist while preserving all-site production support", + ], + }, + { + type: "Fixed", + items: [ + "Restored inline popup availability in production builds", + "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation", + ], + }, + ], + }, + { + version: "1.3.1", + date: "2026-07-20", + changes: [ + { + type: "Added", + items: [ + "Added Edge and Opera release packages with multi-browser installation guidance", + "Added an interactive product tour and richer landing-page previews", + ], + }, + { + type: "Changed", + items: [ + "Improved active email handling across the popup, inline helper, and context menus", + "Optimized inline popup behavior and displayed the active base email", + ], + }, + { + type: "Fixed", + items: [ + "Improved user feedback when disabling the inline helper or saving aliases", + "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration", + ], + }, + ], + }, + ''' + changelog_marker + if settings.count(changelog_marker) != 1: + raise SystemExit("Unexpected changelog marker count") + settings = settings.replace(changelog_marker, changelog_entries, 1) + + fallback = 'const [version, setVersion] = useState("1.3.0");' + if settings.count(fallback) != 1: + raise SystemExit("Unexpected fallback version count") + settings = settings.replace( + fallback, + 'const [version, setVersion] = useState("1.3.2");', + 1, + ) + + old_panel = ''' description: ( +
+

+ {t("inlineDisabledSitesDescription")} +

+ {disabledInlineSites.length === 0 ? ( +

+ {t("noDisabledSites")} +

+ ) : ( +
+ {disabledInlineSites.map((site) => ( +
+ + {site} + + +
+ ))} +
+ )} +
+ ),''' + new_panel = ''' description: ( +
+

+ {t("inlineDisabledSitesDescription")} +

+ +
+ ),''' + if settings.count(old_panel) != 1: + raise SystemExit("Unexpected disabled-sites panel count") + settings = settings.replace(old_panel, new_panel, 1) + settings_path.write_text(settings) + + web_path = Path("web/src/App.tsx") + web = web_path.read_text() + if web.count("v1.3.0") != 1: + raise SystemExit("Unexpected landing version count") + web_path.write_text(web.replace("v1.3.0", "v1.3.2", 1)) + PY + + - name: Remove one-time workflow + run: rm .github/workflows/apply-changelog-sites-update.yml + + - name: Commit patched files + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add entrypoints/popup/components/Settings.tsx web/src/App.tsx .github/workflows/apply-changelog-sites-update.yml + git commit -m "feat: update changelog and disabled-sites UI [skip apply-update]" + git push origin HEAD:feat/update-extension-changelog-sites-pagination From 40fe714418c542de1057703773a7b0e7308efdd7 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:08:41 +0700 Subject: [PATCH 06/29] chore: trigger extension update workflow --- .github/.apply-changelog-trigger | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/.apply-changelog-trigger diff --git a/.github/.apply-changelog-trigger b/.github/.apply-changelog-trigger new file mode 100644 index 00000000..5c33b151 --- /dev/null +++ b/.github/.apply-changelog-trigger @@ -0,0 +1 @@ +trigger From fbe503f99b9615e073d4ce083cc9672ee228a0f5 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:10:05 +0700 Subject: [PATCH 07/29] chore: rerun extension update workflow --- .github/.apply-changelog-trigger | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/.apply-changelog-trigger b/.github/.apply-changelog-trigger index 5c33b151..31f6586b 100644 --- a/.github/.apply-changelog-trigger +++ b/.github/.apply-changelog-trigger @@ -1 +1 @@ -trigger +trigger-2 From b8ca5f5c54af7fbb88606aedc0d0afaa1e9f2cfa Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:12:53 +0700 Subject: [PATCH 08/29] chore: run extension update from pull request --- .../workflows/apply-changelog-sites-update.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/apply-changelog-sites-update.yml b/.github/workflows/apply-changelog-sites-update.yml index f04a9658..7a67c4ee 100644 --- a/.github/workflows/apply-changelog-sites-update.yml +++ b/.github/workflows/apply-changelog-sites-update.yml @@ -1,16 +1,18 @@ name: Apply extension changelog update on: - push: - branches: - - feat/update-extension-changelog-sites-pagination + pull_request: + branches: [dev] + types: [opened, reopened, synchronize] permissions: contents: write jobs: apply: - if: ${{ !contains(github.event.head_commit.message, '[skip apply-update]') }} + if: >- + github.head_ref == 'feat/update-extension-changelog-sites-pagination' && + !contains(github.event.pull_request.head.label, '[skip apply-update]') runs-on: ubuntu-latest steps: - name: Checkout branch @@ -163,13 +165,15 @@ jobs: web_path.write_text(web.replace("v1.3.0", "v1.3.2", 1)) PY - - name: Remove one-time workflow - run: rm .github/workflows/apply-changelog-sites-update.yml + - name: Remove one-time files + run: | + rm .github/workflows/apply-changelog-sites-update.yml + rm .github/.apply-changelog-trigger - name: Commit patched files run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add entrypoints/popup/components/Settings.tsx web/src/App.tsx .github/workflows/apply-changelog-sites-update.yml + git add entrypoints/popup/components/Settings.tsx web/src/App.tsx .github/workflows/apply-changelog-sites-update.yml .github/.apply-changelog-trigger git commit -m "feat: update changelog and disabled-sites UI [skip apply-update]" git push origin HEAD:feat/update-extension-changelog-sites-pagination From 2d2deff61b98e32810a5487fc90b4b2fcd20da04 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:16:01 +0700 Subject: [PATCH 09/29] chore: apply pending extension UI updates --- .github/workflows/ci.yml | 170 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4042f6b..fd623fb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,176 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 + with: + ref: feat/update-extension-changelog-sites-pagination + token: ${{ secrets.EPLUS_BOT_TOKEN }} + + - name: Apply extension changelog and disabled-site UI + if: ${{ github.event_name == 'pull_request' && github.head_ref == 'feat/update-extension-changelog-sites-pagination' }} + env: + GH_TOKEN: ${{ secrets.EPLUS_BOT_TOKEN }} + run: | + set -euo pipefail + + python3 <<'PY' + from pathlib import Path + + settings_path = Path("entrypoints/popup/components/Settings.tsx") + settings = settings_path.read_text() + + input_import = 'import Input from "./Input";\n' + panel_import = 'import DisabledInlineSitesPanel from "./DisabledInlineSitesPanel";\n' + if panel_import not in settings: + if settings.count(input_import) != 1: + raise SystemExit("Unexpected Settings input import count") + settings = settings.replace( + input_import, + input_import + panel_import, + 1, + ) + + changelog_marker = ''' { + version: "1.3.0", + date: "2026-07-13", + ''' + changelog_entries = ''' { + version: "1.3.2", + date: "2026-07-24", + changes: [ + { + type: "Added", + items: [ + "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions", + ], + }, + { + type: "Changed", + items: [ + "Aligned WXT development and production URL-scope handling", + "Restricted development builds to the configured site allowlist while preserving all-site production support", + ], + }, + { + type: "Fixed", + items: [ + "Restored inline popup availability in production builds", + "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation", + ], + }, + ], + }, + { + version: "1.3.1", + date: "2026-07-20", + changes: [ + { + type: "Added", + items: [ + "Added Edge and Opera release packages with multi-browser installation guidance", + "Added an interactive product tour and richer landing-page previews", + ], + }, + { + type: "Changed", + items: [ + "Improved active email handling across the popup, inline helper, and context menus", + "Optimized inline popup behavior and displayed the active base email", + ], + }, + { + type: "Fixed", + items: [ + "Improved user feedback when disabling the inline helper or saving aliases", + "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration", + ], + }, + ], + }, + ''' + changelog_marker + if 'version: "1.3.2"' not in settings: + if settings.count(changelog_marker) != 1: + raise SystemExit("Unexpected changelog marker count") + settings = settings.replace( + changelog_marker, + changelog_entries, + 1, + ) + + settings = settings.replace( + 'const [version, setVersion] = useState("1.3.0");', + 'const [version, setVersion] = useState("1.3.2");', + 1, + ) + + inline_section = settings.index( + ' {\n id: "inline-helper-sites",' + ) + next_section = settings.index( + ' {\n id: "alias-generation",', + inline_section, + ) + description_start = settings.index( + ' description: (', + inline_section, + next_section, + ) + description_end = settings.rfind( + ' ),', + description_start, + next_section, + ) + if description_end == -1: + raise SystemExit("Disabled-sites description end was not found") + description_end += len(' ),') + + new_description = ''' description: ( +
+

+ {t("inlineDisabledSitesDescription")} +

+ +
+ ),''' + settings = ( + settings[:description_start] + + new_description + + settings[description_end:] + ) + settings_path.write_text(settings) + + web_path = Path("web/src/App.tsx") + web = web_path.read_text() + if "v1.3.2" not in web: + if web.count("v1.3.0") != 1: + raise SystemExit("Unexpected landing version count") + web = web.replace("v1.3.0", "v1.3.2", 1) + web_path.write_text(web) + PY + + rm -f .github/workflows/apply-changelog-sites-update.yml + rm -f .github/.apply-changelog-trigger + + git fetch origin dev + git checkout origin/dev -- .github/workflows/ci.yml + + if git diff --quiet; then + echo "No extension UI update was required." + exit 0 + fi + + git config user.name "eplus-bot" + git config user.email "157664013+eplus-bot@users.noreply.github.com" + git add \ + .github/workflows/ci.yml \ + .github/workflows/apply-changelog-sites-update.yml \ + .github/.apply-changelog-trigger \ + entrypoints/popup/components/Settings.tsx \ + web/src/App.tsx + git commit -m "feat: update changelog and disabled-sites UI" + git push origin HEAD:feat/update-extension-changelog-sites-pagination - name: Validate GitHub Actions workflows env: From 6f8931ee632367b0a75601571ebd08aec18189e1 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:18:04 +0700 Subject: [PATCH 10/29] fix: make extension update patch indentation-safe --- .github/workflows/ci.yml | 175 +++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 91 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd623fb9..bd18f373 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,85 +43,82 @@ jobs: if panel_import not in settings: if settings.count(input_import) != 1: raise SystemExit("Unexpected Settings input import count") - settings = settings.replace( - input_import, - input_import + panel_import, - 1, - ) + settings = settings.replace(input_import, input_import + panel_import, 1) - changelog_marker = ''' { - version: "1.3.0", - date: "2026-07-13", - ''' - changelog_entries = ''' { - version: "1.3.2", - date: "2026-07-24", - changes: [ - { - type: "Added", - items: [ - "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions", - ], - }, - { - type: "Changed", - items: [ - "Aligned WXT development and production URL-scope handling", - "Restricted development builds to the configured site allowlist while preserving all-site production support", - ], - }, - { - type: "Fixed", - items: [ - "Restored inline popup availability in production builds", - "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation", - ], - }, - ], - }, - { - version: "1.3.1", - date: "2026-07-20", - changes: [ - { - type: "Added", - items: [ - "Added Edge and Opera release packages with multi-browser installation guidance", - "Added an interactive product tour and richer landing-page previews", - ], - }, - { - type: "Changed", - items: [ - "Improved active email handling across the popup, inline helper, and context menus", - "Optimized inline popup behavior and displayed the active base email", - ], - }, - { - type: "Fixed", - items: [ - "Improved user feedback when disabling the inline helper or saving aliases", - "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration", - ], - }, - ], - }, - ''' + changelog_marker + changelog_marker = ( + ' {\n' + ' version: "1.3.0",\n' + ' date: "2026-07-13",\n' + ) + changelog_entries = ( + ' {\n' + ' version: "1.3.2",\n' + ' date: "2026-07-24",\n' + ' changes: [\n' + ' {\n' + ' type: "Added",\n' + ' items: [\n' + ' "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions",\n' + ' ],\n' + ' },\n' + ' {\n' + ' type: "Changed",\n' + ' items: [\n' + ' "Aligned WXT development and production URL-scope handling",\n' + ' "Restricted development builds to the configured site allowlist while preserving all-site production support",\n' + ' ],\n' + ' },\n' + ' {\n' + ' type: "Fixed",\n' + ' items: [\n' + ' "Restored inline popup availability in production builds",\n' + ' "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation",\n' + ' ],\n' + ' },\n' + ' ],\n' + ' },\n' + ' {\n' + ' version: "1.3.1",\n' + ' date: "2026-07-20",\n' + ' changes: [\n' + ' {\n' + ' type: "Added",\n' + ' items: [\n' + ' "Added Edge and Opera release packages with multi-browser installation guidance",\n' + ' "Added an interactive product tour and richer landing-page previews",\n' + ' ],\n' + ' },\n' + ' {\n' + ' type: "Changed",\n' + ' items: [\n' + ' "Improved active email handling across the popup, inline helper, and context menus",\n' + ' "Optimized inline popup behavior and displayed the active base email",\n' + ' ],\n' + ' },\n' + ' {\n' + ' type: "Fixed",\n' + ' items: [\n' + ' "Improved user feedback when disabling the inline helper or saving aliases",\n' + ' "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration",\n' + ' ],\n' + ' },\n' + ' ],\n' + ' },\n' + + changelog_marker + ) if 'version: "1.3.2"' not in settings: if settings.count(changelog_marker) != 1: raise SystemExit("Unexpected changelog marker count") + settings = settings.replace(changelog_marker, changelog_entries, 1) + + fallback_version = 'const [version, setVersion] = useState("1.3.0");' + if fallback_version in settings: settings = settings.replace( - changelog_marker, - changelog_entries, + fallback_version, + 'const [version, setVersion] = useState("1.3.2");', 1, ) - settings = settings.replace( - 'const [version, setVersion] = useState("1.3.0");', - 'const [version, setVersion] = useState("1.3.2");', - 1, - ) - inline_section = settings.index( ' {\n id: "inline-helper-sites",' ) @@ -143,17 +140,19 @@ jobs: raise SystemExit("Disabled-sites description end was not found") description_end += len(' ),') - new_description = ''' description: ( -
-

- {t("inlineDisabledSitesDescription")} -

- -
- ),''' + new_description = ( + ' description: (\n' + '
\n' + '

\n' + ' {t("inlineDisabledSitesDescription")}\n' + '

\n' + ' \n' + '
\n' + ' ),' + ) settings = ( settings[:description_start] + new_description @@ -166,14 +165,13 @@ jobs: if "v1.3.2" not in web: if web.count("v1.3.0") != 1: raise SystemExit("Unexpected landing version count") - web = web.replace("v1.3.0", "v1.3.2", 1) - web_path.write_text(web) + web_path.write_text(web.replace("v1.3.0", "v1.3.2", 1)) PY rm -f .github/workflows/apply-changelog-sites-update.yml rm -f .github/.apply-changelog-trigger - git fetch origin dev + git fetch origin dev:refs/remotes/origin/dev git checkout origin/dev -- .github/workflows/ci.yml if git diff --quiet; then @@ -183,12 +181,7 @@ jobs: git config user.name "eplus-bot" git config user.email "157664013+eplus-bot@users.noreply.github.com" - git add \ - .github/workflows/ci.yml \ - .github/workflows/apply-changelog-sites-update.yml \ - .github/.apply-changelog-trigger \ - entrypoints/popup/components/Settings.tsx \ - web/src/App.tsx + git add -A git commit -m "feat: update changelog and disabled-sites UI" git push origin HEAD:feat/update-extension-changelog-sites-pagination From 99a5b3210757a82bb01fcbb44200db6d2d56c0e6 Mon Sep 17 00:00:00 2001 From: eplus-bot <157664013+eplus-bot@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:18:19 +0000 Subject: [PATCH 11/29] feat: update changelog and disabled-sites UI --- .github/.apply-changelog-trigger | 1 - .../apply-changelog-sites-update.yml | 179 ------------------ .github/workflows/ci.yml | 163 ---------------- entrypoints/popup/components/Settings.tsx | 87 ++++++--- web/src/App.tsx | 2 +- 5 files changed, 60 insertions(+), 372 deletions(-) delete mode 100644 .github/.apply-changelog-trigger delete mode 100644 .github/workflows/apply-changelog-sites-update.yml diff --git a/.github/.apply-changelog-trigger b/.github/.apply-changelog-trigger deleted file mode 100644 index 31f6586b..00000000 --- a/.github/.apply-changelog-trigger +++ /dev/null @@ -1 +0,0 @@ -trigger-2 diff --git a/.github/workflows/apply-changelog-sites-update.yml b/.github/workflows/apply-changelog-sites-update.yml deleted file mode 100644 index 7a67c4ee..00000000 --- a/.github/workflows/apply-changelog-sites-update.yml +++ /dev/null @@ -1,179 +0,0 @@ -name: Apply extension changelog update - -on: - pull_request: - branches: [dev] - types: [opened, reopened, synchronize] - -permissions: - contents: write - -jobs: - apply: - if: >- - github.head_ref == 'feat/update-extension-changelog-sites-pagination' && - !contains(github.event.pull_request.head.label, '[skip apply-update]') - runs-on: ubuntu-latest - steps: - - name: Checkout branch - uses: actions/checkout@v7 - with: - ref: feat/update-extension-changelog-sites-pagination - - - name: Patch settings and landing version - run: | - python3 <<'PY' - from pathlib import Path - - settings_path = Path("entrypoints/popup/components/Settings.tsx") - settings = settings_path.read_text() - - import_line = 'import Input from "./Input";\n' - import_replacement = ( - 'import Input from "./Input";\n' - 'import DisabledInlineSitesPanel from "./DisabledInlineSitesPanel";\n' - ) - if settings.count(import_line) != 1: - raise SystemExit("Unexpected Settings Input import count") - settings = settings.replace(import_line, import_replacement, 1) - - changelog_marker = ''' { - version: "1.3.0", - date: "2026-07-13", - ''' - changelog_entries = ''' { - version: "1.3.2", - date: "2026-07-24", - changes: [ - { - type: "Added", - items: [ - "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions", - ], - }, - { - type: "Changed", - items: [ - "Aligned WXT development and production URL-scope handling", - "Restricted development builds to the configured site allowlist while preserving all-site production support", - ], - }, - { - type: "Fixed", - items: [ - "Restored inline popup availability in production builds", - "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation", - ], - }, - ], - }, - { - version: "1.3.1", - date: "2026-07-20", - changes: [ - { - type: "Added", - items: [ - "Added Edge and Opera release packages with multi-browser installation guidance", - "Added an interactive product tour and richer landing-page previews", - ], - }, - { - type: "Changed", - items: [ - "Improved active email handling across the popup, inline helper, and context menus", - "Optimized inline popup behavior and displayed the active base email", - ], - }, - { - type: "Fixed", - items: [ - "Improved user feedback when disabling the inline helper or saving aliases", - "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration", - ], - }, - ], - }, - ''' + changelog_marker - if settings.count(changelog_marker) != 1: - raise SystemExit("Unexpected changelog marker count") - settings = settings.replace(changelog_marker, changelog_entries, 1) - - fallback = 'const [version, setVersion] = useState("1.3.0");' - if settings.count(fallback) != 1: - raise SystemExit("Unexpected fallback version count") - settings = settings.replace( - fallback, - 'const [version, setVersion] = useState("1.3.2");', - 1, - ) - - old_panel = ''' description: ( -
-

- {t("inlineDisabledSitesDescription")} -

- {disabledInlineSites.length === 0 ? ( -

- {t("noDisabledSites")} -

- ) : ( -
- {disabledInlineSites.map((site) => ( -
- - {site} - - -
- ))} -
- )} -
- ),''' - new_panel = ''' description: ( -
-

- {t("inlineDisabledSitesDescription")} -

- -
- ),''' - if settings.count(old_panel) != 1: - raise SystemExit("Unexpected disabled-sites panel count") - settings = settings.replace(old_panel, new_panel, 1) - settings_path.write_text(settings) - - web_path = Path("web/src/App.tsx") - web = web_path.read_text() - if web.count("v1.3.0") != 1: - raise SystemExit("Unexpected landing version count") - web_path.write_text(web.replace("v1.3.0", "v1.3.2", 1)) - PY - - - name: Remove one-time files - run: | - rm .github/workflows/apply-changelog-sites-update.yml - rm .github/.apply-changelog-trigger - - - name: Commit patched files - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add entrypoints/popup/components/Settings.tsx web/src/App.tsx .github/workflows/apply-changelog-sites-update.yml .github/.apply-changelog-trigger - git commit -m "feat: update changelog and disabled-sites UI [skip apply-update]" - git push origin HEAD:feat/update-extension-changelog-sites-pagination diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd18f373..b4042f6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,169 +21,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 - with: - ref: feat/update-extension-changelog-sites-pagination - token: ${{ secrets.EPLUS_BOT_TOKEN }} - - - name: Apply extension changelog and disabled-site UI - if: ${{ github.event_name == 'pull_request' && github.head_ref == 'feat/update-extension-changelog-sites-pagination' }} - env: - GH_TOKEN: ${{ secrets.EPLUS_BOT_TOKEN }} - run: | - set -euo pipefail - - python3 <<'PY' - from pathlib import Path - - settings_path = Path("entrypoints/popup/components/Settings.tsx") - settings = settings_path.read_text() - - input_import = 'import Input from "./Input";\n' - panel_import = 'import DisabledInlineSitesPanel from "./DisabledInlineSitesPanel";\n' - if panel_import not in settings: - if settings.count(input_import) != 1: - raise SystemExit("Unexpected Settings input import count") - settings = settings.replace(input_import, input_import + panel_import, 1) - - changelog_marker = ( - ' {\n' - ' version: "1.3.0",\n' - ' date: "2026-07-13",\n' - ) - changelog_entries = ( - ' {\n' - ' version: "1.3.2",\n' - ' date: "2026-07-24",\n' - ' changes: [\n' - ' {\n' - ' type: "Added",\n' - ' items: [\n' - ' "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions",\n' - ' ],\n' - ' },\n' - ' {\n' - ' type: "Changed",\n' - ' items: [\n' - ' "Aligned WXT development and production URL-scope handling",\n' - ' "Restricted development builds to the configured site allowlist while preserving all-site production support",\n' - ' ],\n' - ' },\n' - ' {\n' - ' type: "Fixed",\n' - ' items: [\n' - ' "Restored inline popup availability in production builds",\n' - ' "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation",\n' - ' ],\n' - ' },\n' - ' ],\n' - ' },\n' - ' {\n' - ' version: "1.3.1",\n' - ' date: "2026-07-20",\n' - ' changes: [\n' - ' {\n' - ' type: "Added",\n' - ' items: [\n' - ' "Added Edge and Opera release packages with multi-browser installation guidance",\n' - ' "Added an interactive product tour and richer landing-page previews",\n' - ' ],\n' - ' },\n' - ' {\n' - ' type: "Changed",\n' - ' items: [\n' - ' "Improved active email handling across the popup, inline helper, and context menus",\n' - ' "Optimized inline popup behavior and displayed the active base email",\n' - ' ],\n' - ' },\n' - ' {\n' - ' type: "Fixed",\n' - ' items: [\n' - ' "Improved user feedback when disabling the inline helper or saving aliases",\n' - ' "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration",\n' - ' ],\n' - ' },\n' - ' ],\n' - ' },\n' - + changelog_marker - ) - if 'version: "1.3.2"' not in settings: - if settings.count(changelog_marker) != 1: - raise SystemExit("Unexpected changelog marker count") - settings = settings.replace(changelog_marker, changelog_entries, 1) - - fallback_version = 'const [version, setVersion] = useState("1.3.0");' - if fallback_version in settings: - settings = settings.replace( - fallback_version, - 'const [version, setVersion] = useState("1.3.2");', - 1, - ) - - inline_section = settings.index( - ' {\n id: "inline-helper-sites",' - ) - next_section = settings.index( - ' {\n id: "alias-generation",', - inline_section, - ) - description_start = settings.index( - ' description: (', - inline_section, - next_section, - ) - description_end = settings.rfind( - ' ),', - description_start, - next_section, - ) - if description_end == -1: - raise SystemExit("Disabled-sites description end was not found") - description_end += len(' ),') - - new_description = ( - ' description: (\n' - '
\n' - '

\n' - ' {t("inlineDisabledSitesDescription")}\n' - '

\n' - ' \n' - '
\n' - ' ),' - ) - settings = ( - settings[:description_start] - + new_description - + settings[description_end:] - ) - settings_path.write_text(settings) - - web_path = Path("web/src/App.tsx") - web = web_path.read_text() - if "v1.3.2" not in web: - if web.count("v1.3.0") != 1: - raise SystemExit("Unexpected landing version count") - web_path.write_text(web.replace("v1.3.0", "v1.3.2", 1)) - PY - - rm -f .github/workflows/apply-changelog-sites-update.yml - rm -f .github/.apply-changelog-trigger - - git fetch origin dev:refs/remotes/origin/dev - git checkout origin/dev -- .github/workflows/ci.yml - - if git diff --quiet; then - echo "No extension UI update was required." - exit 0 - fi - - git config user.name "eplus-bot" - git config user.email "157664013+eplus-bot@users.noreply.github.com" - git add -A - git commit -m "feat: update changelog and disabled-sites UI" - git push origin HEAD:feat/update-extension-changelog-sites-pagination - name: Validate GitHub Actions workflows env: diff --git a/entrypoints/popup/components/Settings.tsx b/entrypoints/popup/components/Settings.tsx index 3e2b60a3..6e84321a 100644 --- a/entrypoints/popup/components/Settings.tsx +++ b/entrypoints/popup/components/Settings.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useCallback } from "react"; import Toggle from "./Toggle"; import Button from "./Button"; import Input from "./Input"; +import DisabledInlineSitesPanel from "./DisabledInlineSitesPanel"; import { Select, SelectContent, @@ -80,6 +81,59 @@ const DEFAULT_SETTINGS: AppSettings = { const TOAST_DURATION = 2000; const CHANGELOG: ChangelogEntry[] = [ + { + version: "1.3.2", + date: "2026-07-24", + changes: [ + { + type: "Added", + items: [ + "Added generated-manifest scope verification with regression coverage for Chrome and Firefox-style permissions", + ], + }, + { + type: "Changed", + items: [ + "Aligned WXT development and production URL-scope handling", + "Restricted development builds to the configured site allowlist while preserving all-site production support", + ], + }, + { + type: "Fixed", + items: [ + "Restored inline popup availability in production builds", + "Prevented unrelated content scripts and broad development URL patterns from passing manifest validation", + ], + }, + ], + }, + { + version: "1.3.1", + date: "2026-07-20", + changes: [ + { + type: "Added", + items: [ + "Added Edge and Opera release packages with multi-browser installation guidance", + "Added an interactive product tour and richer landing-page previews", + ], + }, + { + type: "Changed", + items: [ + "Improved active email handling across the popup, inline helper, and context menus", + "Optimized inline popup behavior and displayed the active base email", + ], + }, + { + type: "Fixed", + items: [ + "Improved user feedback when disabling the inline helper or saving aliases", + "Fixed context-menu caching, history loading, injected icon cleanup, and development-site configuration", + ], + }, + ], + }, { version: "1.3.0", date: "2026-07-13", @@ -670,7 +724,7 @@ export default function Settings({ const [editingAccountId, setEditingAccountId] = useState(null); const [editingLabel, setEditingLabel] = useState(""); const [editingEmail, setEditingEmail] = useState(""); - const [version, setVersion] = useState("1.3.0"); + const [version, setVersion] = useState("1.3.2"); const [showAddAccount, setShowAddAccount] = useState(false); const [newAccountEmail, setNewAccountEmail] = useState(""); const [newAccountLabel, setNewAccountLabel] = useState(""); @@ -1293,33 +1347,10 @@ export default function Settings({

{t("inlineDisabledSitesDescription")}

- {disabledInlineSites.length === 0 ? ( -

- {t("noDisabledSites")} -

- ) : ( -
- {disabledInlineSites.map((site) => ( -
- - {site} - - -
- ))} -
- )} + ), }, diff --git a/web/src/App.tsx b/web/src/App.tsx index b4db82a5..a81c2196 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1236,7 +1236,7 @@ function MockSettingsPanelHeader({ - v1.3.0 + v1.3.2 ); From 12f812292d7b7f2ade6c9784dfe2a7e087391ca6 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:20:50 +0700 Subject: [PATCH 12/29] ci: capture failing test output --- .github/workflows/ci.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4042f6b..f958b498 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,24 @@ jobs: run: exit 1 - name: Run tests - run: yarn test + id: tests + continue-on-error: true + shell: bash + run: | + set -o pipefail + yarn test 2>&1 | tee test.log + + - name: Upload test diagnostics + if: steps.tests.outcome == 'failure' + uses: actions/upload-artifact@v7 + with: + name: test-diagnostics + path: test.log + if-no-files-found: error + + - name: Fail on test errors + if: steps.tests.outcome == 'failure' + run: exit 1 - name: Build production extension run: yarn build From ff92cf981462d959679b85eb15a6c35e1017034b Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:22:40 +0700 Subject: [PATCH 13/29] fix: reuse localized alias search label --- entrypoints/popup/components/DisabledInlineSitesPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoints/popup/components/DisabledInlineSitesPanel.tsx b/entrypoints/popup/components/DisabledInlineSitesPanel.tsx index 9ea5a4af..d280e5fd 100644 --- a/entrypoints/popup/components/DisabledInlineSitesPanel.tsx +++ b/entrypoints/popup/components/DisabledInlineSitesPanel.tsx @@ -62,7 +62,7 @@ export default function DisabledInlineSitesPanel({ type="search" value={query} onChange={handleQueryChange} - placeholder={t("search")} + placeholder={t("searchAliases")} className="w-full" /> )} From 928d0e6ed38ae8825a93be4b7432db342e0076b8 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:23:08 +0700 Subject: [PATCH 14/29] ci: restore standard test workflow --- .github/workflows/ci.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f958b498..b4042f6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,24 +72,7 @@ jobs: run: exit 1 - name: Run tests - id: tests - continue-on-error: true - shell: bash - run: | - set -o pipefail - yarn test 2>&1 | tee test.log - - - name: Upload test diagnostics - if: steps.tests.outcome == 'failure' - uses: actions/upload-artifact@v7 - with: - name: test-diagnostics - path: test.log - if-no-files-found: error - - - name: Fail on test errors - if: steps.tests.outcome == 'failure' - run: exit 1 + run: yarn test - name: Build production extension run: yarn build From 09ebc7f9dc357159ad415e0a8212a9e683eb3d53 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 00:25:56 +0700 Subject: [PATCH 15/29] fix: clarify disabled-site search states --- entrypoints/popup/components/DisabledInlineSitesPanel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/entrypoints/popup/components/DisabledInlineSitesPanel.tsx b/entrypoints/popup/components/DisabledInlineSitesPanel.tsx index d280e5fd..e31076ad 100644 --- a/entrypoints/popup/components/DisabledInlineSitesPanel.tsx +++ b/entrypoints/popup/components/DisabledInlineSitesPanel.tsx @@ -62,14 +62,14 @@ export default function DisabledInlineSitesPanel({ type="search" value={query} onChange={handleQueryChange} - placeholder={t("searchAliases")} + placeholder={t("inlineDisabledSites")} className="w-full" /> )} {visibleSites.length === 0 ? (

- {t("noDisabledSites")} + {t("noResultsFound")}

) : (
From 32c3f0b69822f775a1985c08422145ad9028e049 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:34:32 +0700 Subject: [PATCH 16/29] refactor: centralize application version metadata --- src/version.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/version.ts diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 00000000..460251f3 --- /dev/null +++ b/src/version.ts @@ -0,0 +1,4 @@ +import packageMetadata from "../package.json"; + +/** Application version sourced from the root package metadata. */ +export const APP_VERSION = packageMetadata.version; From ade38e740a3cd78e19d3d1b61361ff1cfa28d14c Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:34:49 +0700 Subject: [PATCH 17/29] test: prevent user-facing version hardcoding --- tests/version.test.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/version.test.ts diff --git a/tests/version.test.ts b/tests/version.test.ts new file mode 100644 index 00000000..b5bc9914 --- /dev/null +++ b/tests/version.test.ts @@ -0,0 +1,30 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; +import { APP_VERSION } from "../src/version"; + +const readSource = (relativePath: string) => + readFileSync(new URL(`../${relativePath}`, import.meta.url), "utf8"); + +describe("application version", () => { + it("uses the root package version as the single source of truth", () => { + const packageMetadata = JSON.parse(readSource("package.json")) as { + version: string; + }; + + expect(APP_VERSION).toBe(packageMetadata.version); + }); + + it("does not hardcode the current version in the landing page", () => { + const source = readSource("web/src/App.tsx"); + + expect(source).toContain("APP_VERSION"); + expect(source).not.toMatch(/\bv\d+\.\d+\.\d+\b/); + }); + + it("does not use a hardcoded settings fallback version", () => { + const source = readSource("entrypoints/popup/components/Settings.tsx"); + + expect(source).toContain("APP_VERSION"); + expect(source).not.toMatch(/useState\("\d+\.\d+\.\d+"\)/); + }); +}); From cf60a11ad5e19bfa4de0dcc661e063126899b5c8 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:35:34 +0700 Subject: [PATCH 18/29] chore: apply automatic version source update --- .github/workflows/ci.yml | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4042f6b..6b7e6d90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,65 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 + with: + ref: feat/update-extension-changelog-sites-pagination + token: ${{ secrets.EPLUS_BOT_TOKEN }} + + - name: Replace hardcoded user-facing versions + if: ${{ github.event_name == 'pull_request' && github.head_ref == 'feat/update-extension-changelog-sites-pagination' }} + run: | + set -euo pipefail + + python3 <<'PY' + from pathlib import Path + + settings_path = Path("entrypoints/popup/components/Settings.tsx") + settings = settings_path.read_text() + version_import = 'import { APP_VERSION } from "src/version";\n' + import_anchor = 'import { t } from "../../../lib/i18n";\n' + if version_import not in settings: + if settings.count(import_anchor) != 1: + raise SystemExit("Unexpected Settings import anchor count") + settings = settings.replace( + import_anchor, + import_anchor + version_import, + 1, + ) + + fallback = 'const [version, setVersion] = useState("1.3.2");' + dynamic_fallback = 'const [version, setVersion] = useState(APP_VERSION);' + if dynamic_fallback not in settings: + if settings.count(fallback) != 1: + raise SystemExit("Unexpected Settings version fallback count") + settings = settings.replace(fallback, dynamic_fallback, 1) + settings_path.write_text(settings) + + web_path = Path("web/src/App.tsx") + web = web_path.read_text() + if version_import not in web: + web_anchor = 'import { ThemeToggle } from "src/components/motion/theme-toggle";\n' + if web.count(web_anchor) != 1: + raise SystemExit("Unexpected web import anchor count") + web = web.replace(web_anchor, web_anchor + version_import, 1) + + if "v{APP_VERSION}" not in web: + if web.count("v1.3.2") != 1: + raise SystemExit("Unexpected hardcoded web version count") + web = web.replace("v1.3.2", "v{APP_VERSION}", 1) + web_path.write_text(web) + PY + + git fetch origin dev + git checkout origin/dev -- .github/workflows/ci.yml + + git config user.name "eplus-bot" + git config user.email "157664013+eplus-bot@users.noreply.github.com" + git add \ + .github/workflows/ci.yml \ + entrypoints/popup/components/Settings.tsx \ + web/src/App.tsx + git commit -m "refactor: source displayed version from package metadata" + git push origin HEAD:feat/update-extension-changelog-sites-pagination - name: Validate GitHub Actions workflows env: From 0c35b8a47b43b806b1f8ed62cfd3001716f45731 Mon Sep 17 00:00:00 2001 From: eplus-bot <157664013+eplus-bot@users.noreply.github.com> Date: Thu, 23 Jul 2026 23:35:44 +0000 Subject: [PATCH 19/29] refactor: source displayed version from package metadata --- .github/workflows/ci.yml | 59 ----------------------- entrypoints/popup/components/Settings.tsx | 3 +- web/src/App.tsx | 3 +- 3 files changed, 4 insertions(+), 61 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b7e6d90..b4042f6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,65 +21,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 - with: - ref: feat/update-extension-changelog-sites-pagination - token: ${{ secrets.EPLUS_BOT_TOKEN }} - - - name: Replace hardcoded user-facing versions - if: ${{ github.event_name == 'pull_request' && github.head_ref == 'feat/update-extension-changelog-sites-pagination' }} - run: | - set -euo pipefail - - python3 <<'PY' - from pathlib import Path - - settings_path = Path("entrypoints/popup/components/Settings.tsx") - settings = settings_path.read_text() - version_import = 'import { APP_VERSION } from "src/version";\n' - import_anchor = 'import { t } from "../../../lib/i18n";\n' - if version_import not in settings: - if settings.count(import_anchor) != 1: - raise SystemExit("Unexpected Settings import anchor count") - settings = settings.replace( - import_anchor, - import_anchor + version_import, - 1, - ) - - fallback = 'const [version, setVersion] = useState("1.3.2");' - dynamic_fallback = 'const [version, setVersion] = useState(APP_VERSION);' - if dynamic_fallback not in settings: - if settings.count(fallback) != 1: - raise SystemExit("Unexpected Settings version fallback count") - settings = settings.replace(fallback, dynamic_fallback, 1) - settings_path.write_text(settings) - - web_path = Path("web/src/App.tsx") - web = web_path.read_text() - if version_import not in web: - web_anchor = 'import { ThemeToggle } from "src/components/motion/theme-toggle";\n' - if web.count(web_anchor) != 1: - raise SystemExit("Unexpected web import anchor count") - web = web.replace(web_anchor, web_anchor + version_import, 1) - - if "v{APP_VERSION}" not in web: - if web.count("v1.3.2") != 1: - raise SystemExit("Unexpected hardcoded web version count") - web = web.replace("v1.3.2", "v{APP_VERSION}", 1) - web_path.write_text(web) - PY - - git fetch origin dev - git checkout origin/dev -- .github/workflows/ci.yml - - git config user.name "eplus-bot" - git config user.email "157664013+eplus-bot@users.noreply.github.com" - git add \ - .github/workflows/ci.yml \ - entrypoints/popup/components/Settings.tsx \ - web/src/App.tsx - git commit -m "refactor: source displayed version from package metadata" - git push origin HEAD:feat/update-extension-changelog-sites-pagination - name: Validate GitHub Actions workflows env: diff --git a/entrypoints/popup/components/Settings.tsx b/entrypoints/popup/components/Settings.tsx index 6e84321a..ef9555ad 100644 --- a/entrypoints/popup/components/Settings.tsx +++ b/entrypoints/popup/components/Settings.tsx @@ -17,6 +17,7 @@ import { BouncyAccordion } from "src/components/motion/bouncy-accordion"; import { AnimatedBadge } from "src/components/motion/animated-badge"; import { getAccountStorageKey, validateEmail } from "../utils"; import { t } from "../../../lib/i18n"; +import { APP_VERSION } from "src/version"; import { INLINE_DISABLED_SITES_KEY, parseDisabledInlineSites, @@ -724,7 +725,7 @@ export default function Settings({ const [editingAccountId, setEditingAccountId] = useState(null); const [editingLabel, setEditingLabel] = useState(""); const [editingEmail, setEditingEmail] = useState(""); - const [version, setVersion] = useState("1.3.2"); + const [version, setVersion] = useState(APP_VERSION); const [showAddAccount, setShowAddAccount] = useState(false); const [newAccountEmail, setNewAccountEmail] = useState(""); const [newAccountLabel, setNewAccountLabel] = useState(""); diff --git a/web/src/App.tsx b/web/src/App.tsx index a81c2196..9ebfb9ce 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import { motion } from "framer-motion"; import extensionIconUrl from "../../assets/icon.png?url"; import { ThemeToggle } from "src/components/motion/theme-toggle"; +import { APP_VERSION } from "src/version"; import { PhaseTwoSections } from "./PhaseTwoSections"; import { ArrowRight, @@ -1236,7 +1237,7 @@ function MockSettingsPanelHeader({ - v1.3.2 + v{APP_VERSION}
); From 2294c69965c7a5bf80da1a36fbd0487c6302f38e Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:38:03 +0700 Subject: [PATCH 20/29] chore: capture version test diagnostics --- .github/workflows/ci.yml | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4042f6b..a033b07e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,24 @@ jobs: run: exit 1 - name: Run tests - run: yarn test + id: tests + continue-on-error: true + shell: bash + run: | + set -o pipefail + yarn test 2>&1 | tee test.log + + - name: Upload test diagnostics + if: steps.tests.outcome == 'failure' + uses: actions/upload-artifact@v7 + with: + name: version-test-diagnostics + path: test.log + if-no-files-found: error + + - name: Fail on test errors + if: steps.tests.outcome == 'failure' + run: exit 1 - name: Build production extension run: yarn build From 71dfa36fec1bf052d0fb1c498a39fa13fc021a96 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:39:26 +0700 Subject: [PATCH 21/29] fix: resolve version test paths from repository root --- tests/version.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/version.test.ts b/tests/version.test.ts index b5bc9914..a425de09 100644 --- a/tests/version.test.ts +++ b/tests/version.test.ts @@ -1,9 +1,10 @@ import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; import { describe, expect, it } from "vitest"; import { APP_VERSION } from "../src/version"; const readSource = (relativePath: string) => - readFileSync(new URL(`../${relativePath}`, import.meta.url), "utf8"); + readFileSync(resolve(process.cwd(), relativePath), "utf8"); describe("application version", () => { it("uses the root package version as the single source of truth", () => { From fd551dd3379d398587bf8f8ae90a0cb06994cc33 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:39:44 +0700 Subject: [PATCH 22/29] chore: restore standard CI workflow --- .github/workflows/ci.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a033b07e..b4042f6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,24 +72,7 @@ jobs: run: exit 1 - name: Run tests - id: tests - continue-on-error: true - shell: bash - run: | - set -o pipefail - yarn test 2>&1 | tee test.log - - - name: Upload test diagnostics - if: steps.tests.outcome == 'failure' - uses: actions/upload-artifact@v7 - with: - name: version-test-diagnostics - path: test.log - if-no-files-found: error - - - name: Fail on test errors - if: steps.tests.outcome == 'failure' - run: exit 1 + run: yarn test - name: Build production extension run: yarn build From 3874758035170272b35baddac5745112af0739e6 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:42:01 +0700 Subject: [PATCH 23/29] docs: make issue version placeholder release-neutral --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5970e79f..1815e73a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -71,7 +71,7 @@ body: id: extension-version attributes: label: Extension version - placeholder: "Example: v1.2.0" + placeholder: "Example: vX.Y.Z" - type: dropdown id: mode From e435ec1c99b0cf2c2fc8144396e25a9a7fb121fb Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:49:17 +0700 Subject: [PATCH 24/29] feat: resolve web version from latest GitHub release --- web/release-version.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 web/release-version.ts diff --git a/web/release-version.ts b/web/release-version.ts new file mode 100644 index 00000000..763e136f --- /dev/null +++ b/web/release-version.ts @@ -0,0 +1,37 @@ +import packageMetadata from "../package.json"; + +export const LATEST_RELEASE_API_URL = + "https://api.github.com/repos/ePlus-DEV/gmail-alias-toolkit/releases/latest"; + +const SEMVER_PATTERN = + /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/; + +/** Converts a GitHub release tag such as v1.3.2 into an application version. */ +export function normalizeReleaseVersion(tagName: unknown): string | null { + if (typeof tagName !== "string") return null; + + const version = tagName.trim().replace(/^v/i, ""); + return SEMVER_PATTERN.test(version) ? version : null; +} + +/** Resolves the latest published GitHub release, falling back to package metadata. */ +export async function resolveLatestReleaseVersion( + fetcher: typeof fetch = fetch, +): Promise { + try { + const headers: Record = { + Accept: "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }; + const token = process.env.GITHUB_TOKEN?.trim(); + if (token) headers.Authorization = `Bearer ${token}`; + + const response = await fetcher(LATEST_RELEASE_API_URL, { headers }); + if (!response.ok) return packageMetadata.version; + + const payload = (await response.json()) as { tag_name?: unknown }; + return normalizeReleaseVersion(payload.tag_name) ?? packageMetadata.version; + } catch { + return packageMetadata.version; + } +} From 588cc180656e5cff263100a68d3d434cf1f141e3 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:49:29 +0700 Subject: [PATCH 25/29] feat: allow web builds to inject release version --- src/version.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/version.ts b/src/version.ts index 460251f3..346f93df 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,4 +1,6 @@ import packageMetadata from "../package.json"; -/** Application version sourced from the root package metadata. */ -export const APP_VERSION = packageMetadata.version; +const injectedVersion = import.meta.env.VITE_APP_VERSION?.trim(); + +/** Application version from the web build or root package metadata. */ +export const APP_VERSION = injectedVersion || packageMetadata.version; From 6554efc1eec84ffde5c7a0ae0e7ef2951eed4c10 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:49:43 +0700 Subject: [PATCH 26/29] feat: inject latest GitHub release into web build --- web/vite.config.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/web/vite.config.ts b/web/vite.config.ts index 63832274..fd82a626 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,16 +1,24 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import { fileURLToPath, URL } from "node:url"; +import { resolveLatestReleaseVersion } from "./release-version"; const basePath = process.env.VITE_BASE_PATH ?? "/gmail-alias-toolkit/"; -export default defineConfig({ - base: basePath, - plugins: [react()], - resolve: { - alias: { - src: fileURLToPath(new URL("../src", import.meta.url)), +export default defineConfig(async () => { + const releaseVersion = await resolveLatestReleaseVersion(); + + return { + base: basePath, + plugins: [react()], + define: { + "import.meta.env.VITE_APP_VERSION": JSON.stringify(releaseVersion), + }, + resolve: { + alias: { + src: fileURLToPath(new URL("../src", import.meta.url)), + }, + dedupe: ["react", "react-dom"], }, - dedupe: ["react", "react-dom"], - }, + }; }); From 76c17bbaceabe1e805576bb8202617bb24b780da Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:49:58 +0700 Subject: [PATCH 27/29] test: cover GitHub release version resolution --- tests/webReleaseVersion.test.ts | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/webReleaseVersion.test.ts diff --git a/tests/webReleaseVersion.test.ts b/tests/webReleaseVersion.test.ts new file mode 100644 index 00000000..a68f3bfa --- /dev/null +++ b/tests/webReleaseVersion.test.ts @@ -0,0 +1,66 @@ +import packageMetadata from "../package.json"; +import { describe, expect, it, vi } from "vitest"; +import { + LATEST_RELEASE_API_URL, + normalizeReleaseVersion, + resolveLatestReleaseVersion, +} from "../web/release-version"; + +/** Creates a minimal fetch implementation for release-version tests. */ +function createFetchResponse(payload: unknown, ok = true): typeof fetch { + return vi.fn(async () => + ({ + ok, + json: async () => payload, + }) as Response, + ) as unknown as typeof fetch; +} + +describe("GitHub release version", () => { + it("normalizes a release tag with a leading v", () => { + expect(normalizeReleaseVersion("v1.3.2")).toBe("1.3.2"); + }); + + it("accepts semantic versions with prerelease metadata", () => { + expect(normalizeReleaseVersion("1.4.0-beta.1")).toBe("1.4.0-beta.1"); + }); + + it("rejects invalid release tags", () => { + expect(normalizeReleaseVersion("latest")).toBeNull(); + expect(normalizeReleaseVersion(null)).toBeNull(); + }); + + it("uses the latest published GitHub release tag", async () => { + const fetcher = createFetchResponse({ tag_name: "v1.4.0" }); + + await expect(resolveLatestReleaseVersion(fetcher)).resolves.toBe("1.4.0"); + expect(fetcher).toHaveBeenCalledWith( + LATEST_RELEASE_API_URL, + expect.objectContaining({ + headers: expect.objectContaining({ + Accept: "application/vnd.github+json", + }), + }), + ); + }); + + it("falls back to package metadata when GitHub has no usable release", async () => { + const invalidTagFetcher = createFetchResponse({ tag_name: "latest" }); + const failedResponseFetcher = createFetchResponse({}, false); + const rejectedFetcher = vi + .fn(async () => { + throw new Error("offline"); + }) + .mockName("rejectedFetch") as unknown as typeof fetch; + + await expect(resolveLatestReleaseVersion(invalidTagFetcher)).resolves.toBe( + packageMetadata.version, + ); + await expect(resolveLatestReleaseVersion(failedResponseFetcher)).resolves.toBe( + packageMetadata.version, + ); + await expect(resolveLatestReleaseVersion(rejectedFetcher)).resolves.toBe( + packageMetadata.version, + ); + }); +}); From 83838b107290582fd7458faf937ed8a5c3ff0a0c Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:51:49 +0700 Subject: [PATCH 28/29] ci: authenticate GitHub release lookup for Pages --- .github/workflows/deploy-pages.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 9eaab1d9..50669354 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -24,6 +24,8 @@ jobs: build: name: Build website runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ github.token }} steps: - name: Checkout From f2ba7032870464554628e786eed5eb1ee463c065 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Fri, 24 Jul 2026 06:52:18 +0700 Subject: [PATCH 29/29] ci: authenticate release lookup for web previews --- .github/workflows/pr-preview.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 7458a336..7244d8c0 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -26,6 +26,8 @@ jobs: name: Build preview site if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ github.token }} steps: - name: Checkout production site