Skip to content

Conversation

@zerob13
Copy link
Collaborator

@zerob13 zerob13 commented Aug 25, 2025

Pull Request Description

Is your feature request related to a problem? Please describe.
The existing rebranding functionality in DeepChat was incomplete, only handling about.json files in the i18n directory. Many other internationalization files contained hardcoded "DeepChat" references in titles and descriptions that were not being replaced during the rebranding process. Additionally, HTML files had title tags that also needed updating for complete brand customization.

Describe the solution you'd like
Enhanced the rebranding system to provide comprehensive brand customization support:

  • Extended brand-config.template.json to include welcome page customization options for all 9 supported languages
  • Enhanced the rebrand.js script to handle multiple file types and replacement scenarios
  • Added HTML title tag replacement functionality across all renderer process HTML files
  • Made brand assets (icons, logos) completely optional for users who only want text changes
  • Improved error handling and user feedback throughout the rebranding process

UI/UX changes for Desktop Application
This PR primarily affects the rebranding/white-labeling experience rather than the core application UI:

  • Welcome screen titles and descriptions can now be fully customized per language
  • Application window titles in all renderer processes reflect the custom brand name
  • Shell window maintains proper format: "CustomAppName - Shell"
  • No visual changes to the existing UI layout or design - only text content customization

Platform Compatibility Notes
The enhancement is cross-platform compatible:

  • File system operations work consistently across Windows, macOS, and Linux
  • HTML title replacements apply to all platform builds
  • Brand asset copying handles optional files gracefully on all platforms
  • No platform-specific code adjustments required
  • Testing completed on macOS, compatible with all Electron-supported platforms

Additional context
This enhancement completes the rebranding system by addressing the comprehensive i18n support that was previously missing. The solution follows the existing architecture patterns and maintains backward compatibility. Users can now perform complete brand customization with a single configuration file and script execution.

Key improvements:

  • Handles welcome.json, mcp.json, settings.json, update.json, and index.ts files
  • Replaces all "DeepChat" occurrences in unstructured content
  • Updates HTML title tags in index.html, shell/index.html, and floating/index.html
  • Provides clear success/failure feedback for each operation
  • Maintains the optional nature of brand asset replacement

Pull Request Description (中文)

你的功能请求是否与某个问题有关?请描述一下。
DeepChat 现有的品牌定制功能不够完整,仅处理 i18n 目录中的 about.json 文件。许多其他国际化文件在标题和描述中包含硬编码的 "DeepChat" 引用,在品牌化过程中没有被替换。此外,HTML 文件的标题标签也需要更新以实现完整的品牌定制。

请描述你希望的解决方案
增强了品牌化系统以提供全面的品牌定制支持:

  • 扩展了 brand-config.template.json,为所有 9 种支持的语言包含欢迎页面定制选项
  • 增强了 rebrand.js 脚本以处理多种文件类型和替换场景
  • 在所有渲染进程 HTML 文件中添加了 HTML 标题标签替换功能
  • 使品牌资源(图标、徽标)对于只想要文本更改的用户完全可选
  • 改进了整个品牌化过程中的错误处理和用户反馈

桌面应用程序的 UI/UX 更改
此 PR 主要影响品牌化/白标体验,而不是核心应用程序 UI:

  • 欢迎屏幕标题和描述现在可以按语言完全定制
  • 所有渲染进程中的应用程序窗口标题反映自定义品牌名称
  • Shell 窗口保持正确格式:"自定义应用名称 - Shell"
  • 现有 UI 布局或设计没有视觉变化 - 仅文本内容定制

平台兼容性注意事项
增强功能跨平台兼容:

  • 文件系统操作在 Windows、macOS 和 Linux 上一致工作
  • HTML 标题替换适用于所有平台构建
  • 品牌资源复制在所有平台上优雅处理可选文件
  • 不需要特定于平台的代码调整
  • 在 macOS 上完成测试,与所有 Electron 支持的平台兼容

附加背景
此增强通过解决之前缺失的全面 i18n 支持完成了品牌化系统。解决方案遵循现有架构模式并保持向后兼容性。用户现在可以通过单个配置文件和脚本执行进行完整的品牌定制。

主要改进:

  • 处理 welcome.jsonmcp.jsonsettings.jsonupdate.jsonindex.ts 文件
  • 替换非结构化内容中的所有 "DeepChat" 出现
  • 更新 index.htmlshell/index.htmlfloating/index.html 中的 HTML 标题标签
  • 为每个操作提供清晰的成功/失败反馈
  • 维护品牌资源替换的可选性质
dad88bcd8eb03bd1085908800e1a4dfe

Summary by CodeRabbit

  • New Features

    • Added configurable rebranding capability (name, product details, icons/logos) through a provided template.
    • Expanded localization scaffolding for app title, description, and website text across multiple languages.
    • Configurable update server URL integrated into app metadata.
  • Documentation

    • Introduced a comprehensive rebranding guide with step-by-step workflow, requirements, verification, and build instructions.
  • Chores

    • Added placeholder and guidelines for brand asset files to streamline customization.

zerob13 and others added 5 commits August 23, 2025 16:43
- Add support for welcome page title and setup description customization
- Extend updateI18nFiles() to handle both about.json and welcome.json files
- Add updateAllI18nDeepChatReferences() function to replace DeepChat references in:
  - mcp.json (MCP service descriptions)
  - settings.json (UI text and descriptions)
  - update.json (update notifications)
  - index.ts (search disclaimer)
- Make brand assets (icons/logos) completely optional with friendly info messages
- Support all 9 languages: en-US, zh-CN, zh-TW, zh-HK, ja-JP, ko-KR, ru-RU, fr-FR, fa-IR
- Improve error handling and user feedback

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…e support

- Add HTML title replacement functionality for all renderer HTML files
- Update shell page title to maintain "App Name - Shell" format
- Preserve floating button functional title unchanged
- Integrate HTML title updates into main rebrand workflow
- Add proper error handling and success feedback

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Adds a rebranding system: a configurable template and example brand JSON, a Node.js script to apply branding across project files, a placeholder for brand assets, and a Chinese rebranding guide.

Changes

Cohort / File(s) Summary of Changes
Branding Config Samples
brand-config.template.json, brand-config.example-banana.json
Added brand configuration template and a BananaAI example with app metadata, update URL, i18n strings, GitHub repo, and MCP description suffixes.
Rebranding Script
scripts/rebrand.js
Added Node.js script to read brand config and update package metadata, Electron builder configs, main process ID, upgrade URL, i18n files, MCP suffixes, HTML titles, and copy brand assets; includes logging and error handling.
Documentation
docs/rebrand-guide.md
Added Chinese guide detailing end-to-end rebranding workflow, required fields, commands, and troubleshooting.
Asset Placeholder
scripts/brand-assets/.gitkeep
Added guidance for required brand assets and their specifications.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Dev as Developer
    participant CLI as node scripts/rebrand.js
    participant FS as File System
    participant Cfg as brand-config.*.json
    participant Pkg as package.json
    participant EB as electron-builder*.yml
    participant Main as src/main/index.ts
    participant Upg as .../upgradePresenter*
    participant I18N as i18n/.../*.json
    participant MCP as .../mcpConfHelper.ts
    participant HTML as .../index.html

    Dev->>CLI: Execute rebrand script
    CLI->>Cfg: Read brand-config.template.json
    alt Config loaded
        CLI->>Pkg: Update name, description, author
        CLI->>EB: Update appId, productName, executableName, URLs
        CLI->>Main: Set app user model ID
        CLI->>Upg: Set update baseUrl (if provided)
        CLI->>I18N: Update titles, descriptions, website text
        CLI->>MCP: Update server description suffixes
        CLI->>HTML: Update page titles
        CLI->>FS: Copy icons/logos from scripts/brand-assets/*
        CLI-->>Dev: Log success and next steps
    else Config missing/invalid
        CLI-->>Dev: Warn and exit with error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I nibble on configs, crisp as a leaf,
Swap names and icons—branding relief!
From logos that shine to titles that sing,
I hop through locales with a spring-spring-spring.
Banana or Demo, I tailor with cheer—
Ship it with style; the carrots are near! 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch exp/pack-multi-channel

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (5)
scripts/brand-assets/.gitkeep (1)

1-15: Consider moving guidance to README.md and keep .gitkeep empty

It’s unconventional to put user-facing docs into a .gitkeep. Suggest:

  • Create scripts/brand-assets/README.md with this guidance (bilingual if possible).
  • Keep .gitkeep empty to preserve the directory in Git.
brand-config.example-banana.json (1)

16-38: Include welcome screen i18n in the example to showcase new capability

Template supports welcomeTitle and welcomeSetupDescription, but the example lacks them. Adding sample values helps users verify the new welcome page customization end‑to‑end.

   "i18n": {
     "appTitle": {
@@
     },
     "appDescription": {
@@
     },
+    "welcomeTitle": {
+      "en-US": "Welcome to BananaAI",
+      "zh-CN": "欢迎使用 BananaAI",
+      "zh-TW": "歡迎使用 BananaAI",
+      "zh-HK": "歡迎使用 BananaAI",
+      "ja-JP": "BananaAI へようこそ",
+      "ko-KR": "BananaAI에 오신 것을 환영합니다",
+      "ru-RU": "Добро пожаловать в BananaAI",
+      "fr-FR": "Bienvenue sur BananaAI",
+      "fa-IR": "به BananaAI خوش آمدید"
+    },
+    "welcomeSetupDescription": {
+      "en-US": "Let's start setting up BananaAI",
+      "zh-CN": "让我们开始设置 BananaAI",
+      "zh-TW": "讓我們開始設定 BananaAI",
+      "zh-HK": "讓我們開始設置 BananaAI",
+      "ja-JP": "BananaAI の設定を始めましょう",
+      "ko-KR": "BananaAI 설정을 시작합시다",
+      "ru-RU": "Начнем настройку BananaAI",
+      "fr-FR": "Commençons la configuration de BananaAI",
+      "fa-IR": "بیایید تنظیم BananaAI را شروع کنیم"
+    }
   },
brand-config.template.json (2)

50-71: Consider using productName rather than “MyApp” in welcome defaults

Defaults like “Welcome to MyApp” can feel off-brand if users forget to change them. Using “DeepChat” or adding a comment hint reduces accidental leakage into branded builds.

-    "welcomeTitle": {
-      "en-US": "Welcome to MyApp",
-      "zh-CN": "欢迎使用 MyApp",
+    "welcomeTitle": {
+      "en-US": "Welcome to DeepChat", // Replace with your productName
+      "zh-CN": "欢迎使用 DeepChat",   // 替换为你的产品名
@@
-    "welcomeSetupDescription": {
-      "en-US": "Let's start setting up MyApp",
-      "zh-CN": "让我们开始设置 MyApp",
+    "welcomeSetupDescription": {
+      "en-US": "Let's start setting up DeepChat", // Replace with your productName
+      "zh-CN": "让我们开始设置 DeepChat",        // 替换为你的产品名

14-15: Keep a trailing slash in update.baseUrl

Your defaults include a trailing slash — good. Add a short inline comment to preserve this behavior for custom values.

   "update": {
-    "baseUrl": "https://cdn.deepchatai.cn/upgrade/"
+    "baseUrl": "https://cdn.deepchatai.cn/upgrade/" // Keep trailing slash
   },
scripts/rebrand.js (1)

468-499: Add lint step and translate end-of-run instructions into English

Verified that running pnpm dlx oxlint scripts/rebrand.js completes with no errors.

Apply the following changes at the end of scripts/rebrand.js:

  • Replace the Chinese checklist logs with English text.
  • Insert a lint invocation (pnpm dlx oxlint scripts/rebrand.js) as step 2.

Suggested diff:

   log('')
-  log('📋 接下来的步骤:')
-  log('1. 检查修改的文件是否符合预期')
-  log('2. 提交代码到您的仓库')
-  log('3. 构建应用: pnpm run build:mac:arm64 (或其他平台)')
+  log('📋 Next steps:')
+  log('1. Review and commit the changes')
+  log('2. Run lint: pnpm dlx oxlint scripts/rebrand.js')
+  log('3. Build your app: pnpm run build:mac:arm64 (or other targets)')

This aligns with the project’s guideline to use OxLint for JS/TS and to keep all logs and comments in English.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a421eb and 9fed8b9.

📒 Files selected for processing (5)
  • brand-config.example-banana.json (1 hunks)
  • brand-config.template.json (1 hunks)
  • docs/rebrand-guide.md (1 hunks)
  • scripts/brand-assets/.gitkeep (1 hunks)
  • scripts/rebrand.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for logs and comments

Files:

  • scripts/rebrand.js
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

**/*.{js,jsx,ts,tsx}: 使用 OxLint 进行代码检查
Log和注释使用英文书写

Files:

  • scripts/rebrand.js
🪛 LanguageTool
docs/rebrand-guide.md

[uncategorized] ~104-~104: "必需"后面不应接动词,您的意思是""必须""吗?
Context: ...ux:x64 # Linux x64 ``` ## 配置说明 ### 必需配置 - app.name - 应用包名,用于 package.json - ...

(ZH2)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (1)
brand-config.example-banana.json (1)

14-15: Trailing slash confirmed – no changes needed

I ran the provided check and verified that update.baseUrl in brand-config.example-banana.json ends with /, so the existing configuration already meets the requirement.

• File: brand-config.example-banana.json
• Field: update.baseUrl
• Value: https://updates.banana.co/ (ends with /)

All good here, so we can resolve this comment.

Comment on lines +15 to +23
### 1. 准备品牌资源

将以下文件放入 `scripts/brand-assets/` 目录:

- `icon.png` - 应用图标 (512x512 PNG)
- `icon.ico` - Windows 图标文件
- `logo.png` - 亮色主题 Logo (建议 256x256)
- `logo-dark.png` - 暗色主题 Logo (建议 256x256)

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Assets are optional — reflect this in the workflow section

Step 1 implies assets are strictly required. Make this explicitly optional to avoid blocking text-only rebrands.

-### 1. 准备品牌资源
+### 1.(可选)准备品牌资源
@@
-将以下文件放入 `scripts/brand-assets/` 目录:
+如需自定义图标/Logo,请将以下文件放入 `scripts/brand-assets/` 目录(均为可选):
📝 Committable suggestion

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

Suggested change
### 1. 准备品牌资源
将以下文件放入 `scripts/brand-assets/` 目录:
- `icon.png` - 应用图标 (512x512 PNG)
- `icon.ico` - Windows 图标文件
- `logo.png` - 亮色主题 Logo (建议 256x256)
- `logo-dark.png` - 暗色主题 Logo (建议 256x256)
### 1.(可选)准备品牌资源
如需自定义图标/Logo,请将以下文件放入 `scripts/brand-assets/` 目录(均为可选)
- `icon.png` - 应用图标 (512x512 PNG)
- `icon.ico` - Windows 图标文件
- `logo.png` - 亮色主题 Logo (建议 256x256)
- `logo-dark.png` - 暗色主题 Logo (建议 256x256)
🤖 Prompt for AI Agents
In docs/rebrand-guide.md around lines 15 to 23, Step 1 currently makes brand
assets sound mandatory; update the wording and workflow to mark these assets as
optional (e.g., add “(optional)” to the heading and each asset bullet), and add
a short note explaining that if no assets are provided the rebrand process will
proceed with text-only defaults or fallback placeholders; also adjust any
subsequent steps or scripts referenced in the guide to mention they should
handle missing assets gracefully or use defaults.

Comment on lines +66 to +77
这个脚本会自动替换以下文件中的品牌信息:

- `package.json` - 包配置
- `electron-builder.yml` - 构建配置
- `electron-builder-macx64.yml` - macOS x64 构建配置
- `src/main/index.ts` - 主进程配置
- `src/main/presenter/upgradePresenter/index.ts` - 更新服务配置
- `src/renderer/src/i18n/*/about.json` - 国际化文件
- `src/main/presenter/configPresenter/mcpConfHelper.ts` - MCP 服务描述

以及复制品牌资源文件到相应位置。

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Document HTML <title> replacements (index, shell, floating) to match behavior

The PR promises HTML title updates across index.html, shell/index.html, and floating/index.html, but this list omits them. Add them so users know to expect window title changes.

  这个脚本会自动替换以下文件中的品牌信息:
 
 - `package.json` - 包配置
 - `electron-builder.yml` - 构建配置
 - `electron-builder-macx64.yml` - macOS x64 构建配置
 - `src/main/index.ts` - 主进程配置
 - `src/main/presenter/upgradePresenter/index.ts` - 更新服务配置
 - `src/renderer/src/i18n/*/about.json` - 国际化文件
 - `src/main/presenter/configPresenter/mcpConfHelper.ts` - MCP 服务描述
+ - `src/renderer/index.html` - 页面标题 <title>
+ - `src/renderer/shell/index.html` - Shell 窗口标题 <title>(格式:Brand - Shell)
+ - `src/renderer/floating/index.html` - 悬浮窗标题 <title>
 
 以及复制品牌资源文件到相应位置。
📝 Committable suggestion

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

Suggested change
这个脚本会自动替换以下文件中的品牌信息:
- `package.json` - 包配置
- `electron-builder.yml` - 构建配置
- `electron-builder-macx64.yml` - macOS x64 构建配置
- `src/main/index.ts` - 主进程配置
- `src/main/presenter/upgradePresenter/index.ts` - 更新服务配置
- `src/renderer/src/i18n/*/about.json` - 国际化文件
- `src/main/presenter/configPresenter/mcpConfHelper.ts` - MCP 服务描述
以及复制品牌资源文件到相应位置。
这个脚本会自动替换以下文件中的品牌信息:
- `package.json` - 包配置
- `electron-builder.yml` - 构建配置
- `electron-builder-macx64.yml` - macOS x64 构建配置
- `src/main/index.ts` - 主进程配置
- `src/main/presenter/upgradePresenter/index.ts` - 更新服务配置
- `src/renderer/src/i18n/*/about.json` - 国际化文件
- `src/main/presenter/configPresenter/mcpConfHelper.ts` - MCP 服务描述
- `src/renderer/index.html` - 页面标题 <title>
- `src/renderer/shell/index.html` - Shell 窗口标题 <title>(格式:Brand - Shell)
- `src/renderer/floating/index.html` - 悬浮窗标题 <title>
以及复制品牌资源文件到相应位置。
🤖 Prompt for AI Agents
In docs/rebrand-guide.md around lines 66 to 77, the list of files that the
script updates omits the HTML files whose <title> tags are modified; update the
list to include index.html, shell/index.html, and floating/index.html so the
guide accurately documents that the script replaces the HTML <title> values for
those windows—add those three paths into the enumerated file list and mention
they control window titles.

Comment on lines +5 to +12
## 必需文件
- `icon.png` - 应用图标 (512x512 PNG)
- `icon.ico` - Windows 图标文件
- `logo.png` - 亮色主题 Logo (建议 256x256)
- `logo-dark.png` - 暗色主题 Logo (建议 256x256)

## 文件要求
- 图标文件应该是高质量的,符合各平台设计规范
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Docs say “required files” but PR goal makes assets optional — tighten wording

Your PR objective explicitly states brand assets are optional to support text‑only rebranding. This file currently lists them as “必需文件” (required), which contradicts that and may confuse users.

Apply this diff to align the guidance:

-## 必需文件
+## 可选文件(如需自定义图标/Logo)
+- 以下资源为可选项。若未提供,本工具将仅替换文本品牌信息:
 - `icon.png` - 应用图标 (512x512 PNG)
 - `icon.ico` - Windows 图标文件
 - `logo.png` - 亮色主题 Logo (建议 256x256)
 - `logo-dark.png` - 暗色主题 Logo (建议 256x256)
📝 Committable suggestion

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

Suggested change
## 必需文件
- `icon.png` - 应用图标 (512x512 PNG)
- `icon.ico` - Windows 图标文件
- `logo.png` - 亮色主题 Logo (建议 256x256)
- `logo-dark.png` - 暗色主题 Logo (建议 256x256)
## 文件要求
- 图标文件应该是高质量的,符合各平台设计规范
## 可选文件(如需自定义图标/Logo)
- 以下资源为可选项。若未提供,本工具将仅替换文本品牌信息:
- `icon.png` - 应用图标 (512x512 PNG)
- `icon.ico` - Windows 图标文件
- `logo.png` - 亮色主题 Logo (建议 256x256)
- `logo-dark.png` - 暗色主题 Logo (建议 256x256)
## 文件要求
- 图标文件应该是高质量的,符合各平台设计规范
🤖 Prompt for AI Agents
In scripts/brand-assets/.gitkeep around lines 5 to 12, the header and list label
these assets as “必需文件” which conflicts with the PR goal that assets are
optional; change the wording to indicate they are optional/recommended (e.g.,
“可选/建议文件” or “推荐文件”) and update any descriptive text to state these assets are
recommended for richer branding but not required, keeping the same file list and
size suggestions.

Comment on lines +3 to +12
/**
* DeepChat 品牌替换脚本
*
* 使用方法:
* 1. 修改 brand-config.template.json 中的配置
* 2. 将品牌资源文件放在 scripts/brand-assets/ 目录下
* 3. 运行 node scripts/rebrand.js
*
* 这将一次性替换整个项目的品牌信息
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Logs and comments must be in English per coding guidelines

Repository guideline for JS/TS requires English logs/comments. This file is entirely Chinese, which will fail compliance checks and hinder non‑Chinese contributors.

Apply this diff to translate the header comment; replicate the pattern for in‑line comments below:

-/**
- * DeepChat 品牌替换脚本
- *
- * 使用方法:
- * 1. 修改 brand-config.template.json 中的配置
- * 2. 将品牌资源文件放在 scripts/brand-assets/ 目录下
- * 3. 运行 node scripts/rebrand.js
- *
- * 这将一次性替换整个项目的品牌信息
- */
+/**
+ * DeepChat Rebranding Script
+ *
+ * Usage:
+ * 1. Edit branding in brand-config.template.json
+ * 2. (Optional) Put brand assets in scripts/brand-assets/
+ * 3. Run: node scripts/rebrand.js
+ *
+ * This performs a one‑time project‑wide brand replacement.
+ */

Also, please convert all user‑visible log messages passed to success/info/warning/error to English (or make them bilingual).

📝 Committable suggestion

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

Suggested change
/**
* DeepChat 品牌替换脚本
*
* 使用方法:
* 1. 修改 brand-config.template.json 中的配置
* 2. 将品牌资源文件放在 scripts/brand-assets/ 目录下
* 3. 运行 node scripts/rebrand.js
*
* 这将一次性替换整个项目的品牌信息
*/
/**
* DeepChat Rebranding Script
*
* Usage:
* 1. Edit branding in brand-config.template.json
* 2. (Optional) Put brand assets in scripts/brand-assets/
* 3. Run: node scripts/rebrand.js
*
* This performs a one-time project-wide brand replacement.
*/
🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 3 to 12, the file header comment and
subsequent comments/logs are in Chinese which violates repository guidelines;
update the header comment to English (or bilingual English+Chinese) and apply
the same translation pattern to all inline comments in the file, and change all
user-visible log messages passed to console/ logger.info/warn/error to English
(or bilingual) while preserving original meaning; ensure phrasing is clear,
concise, and consistent with other JS/TS files in the repo.

Comment on lines +88 to +116
// 更新 electron-builder.yml
function updateElectronBuilder(config) {
const builderPath = path.join(PROJECT_ROOT, 'electron-builder.yml')

try {
let content = fs.readFileSync(builderPath, 'utf8')

// 替换 appId
content = content.replace(/appId: .+/, `appId: ${config.app.appId}`)

// 替换 productName
content = content.replace(/productName: .+/, `productName: ${config.app.productName}`)

// 替换 executableName (Windows)
content = content.replace(/executableName: .+/, `executableName: ${config.app.executableName}`)

// 替换 shortcutName (Windows)
content = content.replace(/shortcutName: .+/, `shortcutName: ${config.app.productName}`)

// 替换 uninstallDisplayName (Windows)
content = content.replace(/uninstallDisplayName: .+/, `uninstallDisplayName: ${config.app.productName}`)

// 替换 maintainer (Linux)
content = content.replace(/maintainer: .+/, `maintainer: ${config.app.author}`)

// 替换 publish URL
if (config.update && config.update.baseUrl) {
content = content.replace(/url: https:\/\/cdn\.deepchatai\.cn\/upgrade\//, `url: ${config.update.baseUrl}`)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

YAML replacements are too brittle and only replace the first occurrence

Regex /appId: .+/ etc. will:

  • Match only once (no g flag)
  • Break on leading spaces/quotes/comments
  • Miss other occurrences across targets (win/linux/mac blocks)

Use anchored multiline and global patterns; normalize the publish URL.

-    // 替换 appId
-    content = content.replace(/appId: .+/, `appId: ${config.app.appId}`)
-
-    // 替换 productName
-    content = content.replace(/productName: .+/, `productName: ${config.app.productName}`)
-
-    // 替换 executableName (Windows)
-    content = content.replace(/executableName: .+/, `executableName: ${config.app.executableName}`)
-
-    // 替换 shortcutName (Windows)
-    content = content.replace(/shortcutName: .+/, `shortcutName: ${config.app.productName}`)
-
-    // 替换 uninstallDisplayName (Windows)
-    content = content.replace(/uninstallDisplayName: .+/, `uninstallDisplayName: ${config.app.productName}`)
-
-    // 替换 maintainer (Linux)
-    content = content.replace(/maintainer: .+/, `maintainer: ${config.app.author}`)
-
-    // 替换 publish URL
-    if (config.update && config.update.baseUrl) {
-      content = content.replace(/url: https:\/\/cdn\.deepchatai\.cn\/upgrade\//, `url: ${config.update.baseUrl}`)
-    }
+    // Replace common fields (all occurrences, preserve indentation)
+    content = content
+      .replace(/^(\s*appId:\s*).*/gm, `$1${config.app.appId}`)
+      .replace(/^(\s*productName:\s*).*/gm, `$1${config.app.productName}`)
+      .replace(/^(\s*executableName:\s*).*/gm, `$1${config.app.executableName}`)
+      .replace(/^(\s*shortcutName:\s*).*/gm, `$1${config.app.productName}`)
+      .replace(/^(\s*uninstallDisplayName:\s*).*/gm, `$1${config.app.productName}`)
+      .replace(/^(\s*maintainer:\s*).*/gm, `$1${config.app.author}`)
+
+    // Replace publish URL (match old default; keep trailing slash)
+    if (config.update?.baseUrl) {
+      const base = config.update.baseUrl.replace(/\/?$/, '/')
+      content = content.replace(
+        /^(\s*url:\s*)https:\/\/cdn\.deepchatai\.cn\/upgrade\/?/gm,
+        `$1${base}`
+      )
+    }
📝 Committable suggestion

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

Suggested change
// 更新 electron-builder.yml
function updateElectronBuilder(config) {
const builderPath = path.join(PROJECT_ROOT, 'electron-builder.yml')
try {
let content = fs.readFileSync(builderPath, 'utf8')
// 替换 appId
content = content.replace(/appId: .+/, `appId: ${config.app.appId}`)
// 替换 productName
content = content.replace(/productName: .+/, `productName: ${config.app.productName}`)
// 替换 executableName (Windows)
content = content.replace(/executableName: .+/, `executableName: ${config.app.executableName}`)
// 替换 shortcutName (Windows)
content = content.replace(/shortcutName: .+/, `shortcutName: ${config.app.productName}`)
// 替换 uninstallDisplayName (Windows)
content = content.replace(/uninstallDisplayName: .+/, `uninstallDisplayName: ${config.app.productName}`)
// 替换 maintainer (Linux)
content = content.replace(/maintainer: .+/, `maintainer: ${config.app.author}`)
// 替换 publish URL
if (config.update && config.update.baseUrl) {
content = content.replace(/url: https:\/\/cdn\.deepchatai\.cn\/upgrade\//, `url: ${config.update.baseUrl}`)
}
// 更新 electron-builder.yml
function updateElectronBuilder(config) {
const builderPath = path.join(PROJECT_ROOT, 'electron-builder.yml')
try {
let content = fs.readFileSync(builderPath, 'utf8')
// Replace common fields (all occurrences, preserve indentation)
content = content
.replace(/^(\s*appId:\s*).*/gm, `$1${config.app.appId}`)
.replace(/^(\s*productName:\s*).*/gm, `$1${config.app.productName}`)
.replace(/^(\s*executableName:\s*).*/gm, `$1${config.app.executableName}`)
.replace(/^(\s*shortcutName:\s*).*/gm, `$1${config.app.productName}`)
.replace(/^(\s*uninstallDisplayName:\s*).*/gm, `$1${config.app.productName}`)
.replace(/^(\s*maintainer:\s*).*/gm, `$1${config.app.author}`)
// Replace publish URL (match old default; keep trailing slash)
if (config.update?.baseUrl) {
const base = config.update.baseUrl.replace(/\/?$/, '/')
content = content.replace(
/^(\s*url:\s*)https:\/\/cdn\.deepchatai\.cn\/upgrade\/?/gm,
`$1${base}`
)
}
// ...rest of the function...
} catch (err) {
// handle error
}
}
🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 88 to 116, the current single-occurrence
regex replacements (e.g. /appId: .+/) are brittle and only replace the first
match; update them to robust, global, multiline-safe replacements by either
parsing the YAML (preferred) with a YAML library (js-yaml) and updating the keys
programmatically, or if sticking with regex use anchored, case-insensitive,
multiline/global patterns that tolerate leading whitespace and optional quotes
(e.g. match ^\s*appId:\s*['"]?.+?['"]?\s*$ with /gmi) for appId, productName,
executableName, shortcutName, uninstallDisplayName and maintainer so all
occurrences in win/linux/mac blocks are replaced; also normalize and replace the
publish URL using a pattern that matches http/https and optional trailing path
(e.g. ^\s*url:\s*https?:\/\/[^\s'"]+) with a global/multiline flag so every
publish entry is updated.

Comment on lines +136 to +146
// 替换 appId
content = content.replace(/appId: .+/, `appId: ${config.app.appId}`)

// 替换 productName
content = content.replace(/productName: .+/, `productName: ${config.app.productName}`)

// 替换 publish URL
if (config.update && config.update.baseUrl) {
content = content.replace(/url: https:\/\/cdn\.deepchatai\.cn\/upgrade\//, `url: ${config.update.baseUrl}`)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Same YAML robustness issue in electron-builder-macx64.yml

Mirror the anchored gm replacements and flexible URL handling here as well.

-    // 替换 appId
-    content = content.replace(/appId: .+/, `appId: ${config.app.appId}`)
-
-    // 替换 productName
-    content = content.replace(/productName: .+/, `productName: ${config.app.productName}`)
-
-    // 替换 publish URL
-    if (config.update && config.update.baseUrl) {
-      content = content.replace(/url: https:\/\/cdn\.deepchatai\.cn\/upgrade\//, `url: ${config.update.baseUrl}`)
-    }
+    content = content
+      .replace(/^(\s*appId:\s*).*/gm, `$1${config.app.appId}`)
+      .replace(/^(\s*productName:\s*).*/gm, `$1${config.app.productName}`)
+
+    if (config.update?.baseUrl) {
+      const base = config.update.baseUrl.replace(/\/?$/, '/')
+      content = content.replace(
+        /^(\s*url:\s*)https:\/\/cdn\.deepchatai\.cn\/upgrade\/?/gm,
+        `$1${base}`
+      )
+    }
📝 Committable suggestion

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

Suggested change
// 替换 appId
content = content.replace(/appId: .+/, `appId: ${config.app.appId}`)
// 替换 productName
content = content.replace(/productName: .+/, `productName: ${config.app.productName}`)
// 替换 publish URL
if (config.update && config.update.baseUrl) {
content = content.replace(/url: https:\/\/cdn\.deepchatai\.cn\/upgrade\//, `url: ${config.update.baseUrl}`)
}
content = content
.replace(/^(\s*appId:\s*).*/gm, `$1${config.app.appId}`)
.replace(/^(\s*productName:\s*).*/gm, `$1${config.app.productName}`)
if (config.update?.baseUrl) {
const base = config.update.baseUrl.replace(/\/?$/, '/')
content = content.replace(
/^(\s*url:\s*)https:\/\/cdn\.deepchatai\.cn\/upgrade\/?/gm,
`$1${base}`
)
}
🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 136 to 146, the current replacements for
appId, productName and the publish URL are brittle; update them to use anchored,
multi-line-safe regexes (use ^ and the gm flags) that match keys at the start of
a line and tolerate optional whitespace and quoting (e.g.
^\s*appId:\s*['"]?.+['"]?), similarly for productName, and replace with values
properly interpolated; for the publish URL, make the regex flexible (match the
existing url line regardless of the exact literal cdn path or trailing slash)
and replace it with config.update.baseUrl if present, otherwise skip/leave
existing value; ensure the replacements are done with global/multi-line flags
and do not rely on a single exact substring.

Comment on lines +161 to +165
// 替换 setAppUserModelId
content = content.replace(
/electronApp\.setAppUserModelId\('.*?'\)/,
`electronApp.setAppUserModelId('${config.app.appId}')`
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make setAppUserModelId replacement resilient to quote style

Current regex only matches single quotes. Support ", ', and ` to avoid missing updates.

-    content = content.replace(
-      /electronApp\.setAppUserModelId\('.*?'\)/,
-      `electronApp.setAppUserModelId('${config.app.appId}')`
-    )
+    content = content.replace(
+      /electronApp\.setAppUserModelId\((["'`]).*?\1\)/,
+      `electronApp.setAppUserModelId('${config.app.appId}')`
+    )
📝 Committable suggestion

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

Suggested change
// 替换 setAppUserModelId
content = content.replace(
/electronApp\.setAppUserModelId\('.*?'\)/,
`electronApp.setAppUserModelId('${config.app.appId}')`
)
// 替换 setAppUserModelId
content = content.replace(
/electronApp\.setAppUserModelId\((["'`]).*?\1\)/,
`electronApp.setAppUserModelId('${config.app.appId}')`
)
🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 161 to 165, the current regex only matches
single quotes; change it to match any quote style by using a capturing group and
backreference like /electronApp\.setAppUserModelId\((['"`]).*?\1\)/ so it
accepts ", ' or `, and make the replacement use the captured quote (e.g. use $1)
to wrap config.app.appId so the original quote style is preserved.

Comment on lines +186 to +189
content = content.replace(
/return 'https:\/\/cdn\.deepchatai\.cn\/upgrade'/,
`return '${config.update.baseUrl}'`
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Broaden upgrade URL match and normalize trailing slash

Match both quotes and optional trailing slash to avoid a no‑op on slight source changes.

-    content = content.replace(
-      /return 'https:\/\/cdn\.deepchatai\.cn\/upgrade'/,
-      `return '${config.update.baseUrl}'`
-    )
+    const base = config.update.baseUrl.replace(/\/?$/, '/')
+    content = content.replace(
+      /return\s+["']https:\/\/cdn\.deepchatai\.cn\/upgrade\/?["']/,
+      `return '${base}'`
+    )
📝 Committable suggestion

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

Suggested change
content = content.replace(
/return 'https:\/\/cdn\.deepchatai\.cn\/upgrade'/,
`return '${config.update.baseUrl}'`
)
const base = config.update.baseUrl.replace(/\/?$/, '/')
content = content.replace(
/return\s+["']https:\/\/cdn\.deepchatai\.cn\/upgrade\/?["']/,
`return '${base}'`
)
🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 186 to 189, the current regex only matches a
single-quote URL without a trailing slash so small source variations can skip
replacement; update the regex to match either single or double quotes and an
optional trailing slash (for example: /return
['"]https:\/\/cdn\.deepchatai\.cn\/upgrade\/?['"]/), and replace the match using
a properly quoted replacement (e.g. use JSON.stringify(config.update.baseUrl) or
otherwise escape quotes) so the replacement always inserts config.update.baseUrl
with correct quoting.

Comment on lines +371 to +380
const assetMappings = [
// 图标文件
{ src: 'icon.png', dest: 'resources/icon.png' },
{ src: 'icon.ico', dest: 'resources/icon.ico' },
{ src: 'icon.png', dest: 'build/icon.png' },

// Logo 文件
{ src: 'logo.png', dest: 'src/renderer/src/assets/logo.png' },
{ src: 'logo-dark.png', dest: 'src/renderer/src/assets/logo-dark.png' }
]
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify asset destinations are actually wired in electron-builder

The script copies to resources/* and build/icon.png. Ensure electron-builder.yml points to these paths; otherwise the copied assets won’t ship.

Run:

If not referenced, either adjust the builder config or copy into the configured locations.


🏁 Script executed:

#!/bin/bash
# Inspect builder config for icon paths (expect references to build/icon.png or resources/*)
rg -nP -C2 'icon|icon[ _-]?path|resources|logo' electron-builder*.yml

Length of output: 98


🏁 Script executed:

#!/bin/bash
# Search for key build config sections in all electron-builder YAMLs
rg -nP -C5 '^(files|extraResources|directories):' electron-builder*.yml

Length of output: 2099


Verify and include icon assets in the packaged build

Your current scripts/rebrand.js copies icons into both resources/ and build/, but your Electron Builder config only treats build/ (via directories.buildResources: build) as the source for platform icons. As a result, your .ico (Windows) and (likely) .icns (macOS) files aren’t being packaged.

• In scripts/rebrand.js (lines 371–380):

 const assetMappings = [
-  { src: 'icon.png',    dest: 'resources/icon.png' },
-  { src: 'icon.ico',    dest: 'resources/icon.ico' },
-  { src: 'icon.png',    dest: 'build/icon.png' },
+  { src: 'icon.png',    dest: 'resources/icon.png' },   // optional: runtime asset
+  { src: 'icon.ico',    dest: 'build/icon.ico' },      // Windows icon must live under buildResources
+  { src: 'icon.icns',   dest: 'build/icon.icns' },     // macOS icon must live under buildResources
+  { src: 'icon.png',    dest: 'build/icon.png' },      // keep PNG for Linux or fallback

• In electron-builder.yml (uses directories.buildResources: build):
– No references to resources/ or other icon overrides.
extraResources only includes ./runtime/ (no resources/).

Next steps

  1. Update your asset mappings as shown above so that .ico and .icns land in build/ (your buildResources).
  2. If you must ship files from resources/, add an extraResources entry in electron-builder.yml:
extraResources:
  - from: resources/
    to: resources/
  1. Alternatively, change directories.buildResources to resources if you prefer keeping icons there.

This will ensure all your icon assets are actually picked up by Electron Builder.

🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 371–380 update the assetMappings so platform
icon files are placed into the buildResources directory (build/) instead of only
resources/ — specifically add destination entries for the .ico (Windows) and
.icns (macOS) files that point to build/ (e.g., copy icon.ico and icon.icns into
build/), or alternatively modify electron-builder.yml to include resources/ in
packaging (either set directories.buildResources to resources or add an
extraResources entry that copies resources/ into the packaged app); implement
one of these two fixes so Electron Builder actually picks up the .ico/.icns
files during packaging.

Comment on lines +435 to +449
if (filePath.includes('shell/index.html')) {
// shell 页面的标题格式:AppName - Shell
content = content.replace(
/<title>DeepChat - Shell<\/title>/,
`<title>${config.app.productName} - Shell</title>`
)
} else if (filePath.includes('floating/index.html')) {
// floating 页面保持 "Floating Button" 不变
// 这个页面的标题是功能性的,不需要改为品牌名称
} else {
// 主页面和其他页面使用应用名称
content = content.replace(
/<title>DeepChat<\/title>/,
`<title>${config.app.productName}</title>`
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Floating window title isn’t updated; index/shell matches are too strict

  • floating/index.html is intentionally skipped, but PR scope says it should include brand name.
  • Title replacements assume exact DeepChat strings and will fail if whitespace/other text differs.

Use generic <title>…</title> matching; set “Brand - Shell” and “Brand - Floating”.

-      // 替换 title 标签中的内容
-      if (filePath.includes('shell/index.html')) {
-        // shell 页面的标题格式:AppName - Shell
-        content = content.replace(
-          /<title>DeepChat - Shell<\/title>/,
-          `<title>${config.app.productName} - Shell</title>`
-        )
-      } else if (filePath.includes('floating/index.html')) {
-        // floating 页面保持 "Floating Button" 不变
-        // 这个页面的标题是功能性的,不需要改为品牌名称
-      } else {
-        // 主页面和其他页面使用应用名称
-        content = content.replace(
-          /<title>DeepChat<\/title>/,
-          `<title>${config.app.productName}</title>`
-        )
-      }
+      // Replace <title>...</title> generically
+      if (filePath.includes('shell/index.html')) {
+        // Shell window title: Brand - Shell
+        content = content.replace(
+          /<title>.*?\s*-\s*Shell<\/title>/i,
+          `<title>${config.app.productName} - Shell</title>`
+        )
+      } else if (filePath.includes('floating/index.html')) {
+        // Floating window title: Brand - Floating
+        content = content.replace(
+          /<title>.*?<\/title>/i,
+          `<title>${config.app.productName} - Floating</title>`
+        )
+      } else {
+        // Main page and others: Brand
+        content = content.replace(
+          /<title>.*?<\/title>/i,
+          `<title>${config.app.productName}</title>`
+        )
+      }

Also applies to: 442-444

🤖 Prompt for AI Agents
In scripts/rebrand.js around lines 435 to 449 (and also apply the same change
for lines 442-444), the current logic skips floating/index.html and matches
exact "<title>DeepChat</title>" strings which is too strict; update it to always
replace titles using a generic pattern that matches any content between <title>
and </title>, set shell pages to "<title>{brand} - Shell</title>", floating
pages to "<title>{brand} - Floating</title>", and all other pages to
"<title>{brand}</title>"; implement safe regex matching that tolerates
surrounding whitespace and different existing text, and ensure the floating
branch is no longer skipped so the brand is applied.

@zerob13 zerob13 merged commit bd6977a into dev Aug 25, 2025
2 checks passed
@zerob13 zerob13 deleted the exp/pack-multi-channel branch September 21, 2025 15:16
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.

2 participants