Skip to content

feat: compress index.json output for reduced file size#9

Merged
newbe36524 merged 2 commits intomainfrom
feature/compress-index-json
Feb 8, 2026
Merged

feat: compress index.json output for reduced file size#9
newbe36524 merged 2 commits intomainfrom
feature/compress-index-json

Conversation

@newbe36524
Copy link
Contributor

@newbe36524 newbe36524 commented Feb 8, 2026

概述

  • sync-azure-storage.yml 工作流中添加 JSON 压缩
  • 使用 jq -c 参数生成紧凑格式的 index.json

变更内容

  • 修改 jq 输出参数,添加 -c 选项
  • 添加 JSON 有效性验证步骤

预期收益

  • 减少 index.json 文件体积 20-40%
  • 提升客户端加载性能

验证

  • 本地测试 jq -c 输出正确
  • JSON 格式验证通过

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added license management system with license key configuration and synchronization across installed versions
    • Introduced multi-source package management with support for local folders, GitHub releases, and custom HTTP index sources
    • Added package source selector UI with form-based configuration for each source type
    • Added license synchronization status notifications
  • UI/UX Improvements

    • Removed view transition animations for instantaneous page switching
    • Updated sidebar navigation to include license management
    • Added comprehensive package source configuration interface
  • Internationalization

    • Full Chinese and English translations for new license and package source features

- Remove AnimatePresence and motion.div wrapper from main content area
- Replace with standard div for instant view switching
- Preserve sidebar margin transition effect

This change improves perceived responsiveness when navigating between
views in the sidebar navigation.

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

coderabbitai bot commented Feb 8, 2026

📝 Walkthrough

Walkthrough

This PR introduces multi-source package management (local folders, GitHub Releases, HTTP indices) with an abstract PackageSource layer, adds license management with persistence and sync-to-version capabilities, and implements comprehensive Redux state management, UI components, and IPC integration across the full stack.

Changes

Cohort / File(s) Summary
Workflow Optimization
.github/workflows/sync-azure-storage.yml
Updated index.json generation to use compact JSON format (jq -c) and added JSON validation step to catch malformed output before upload.
Package Source Abstraction
src/main/package-sources/package-source.ts, src/main/package-sources/index.ts
Introduced PackageSource interface supporting three types (local-folder, github-release, http-index) with validation, version listing, and download callbacks; created factory function for instantiation.
Local Folder Source
src/main/package-sources/local-folder-source.ts
Implemented LocalFolderPackageSource with directory scanning, platform filtering, version parsing, and file copying with progress callbacks.
GitHub Release Source
src/main/package-sources/github-release-source.ts
Implemented GitHubReleasePackageSource with API integration, release asset parsing, version caching (TTL-based), platform-specific filtering, and auth token support.
HTTP Index Source
src/main/package-sources/http-index-source.ts
Implemented HttpIndexPackageSource fetching remote index JSON, validating structure, filtering by platform, caching versions, and supporting asset downloads with error handling.
Package Source Configuration Manager
src/main/package-source-config-manager.ts
Introduced PackageSourceConfigManager for persisting source configs via Electron Store; supports multi-source setup with active/default source tracking, initialization with environment-based defaults.
License Management Backend
src/main/license-manager.ts, src/main/config.ts
Added LicenseManager singleton for license retrieval/persistence, LicenseConfig interface, and ConfigManager methods; includes default license initialization and sync-to-AppSettings functionality with status callbacks.
Version Manager Integration
src/main/version-manager.ts
Extended VersionManager to accept PackageSourceConfigManager, refactored to use PackageSource abstraction for version listing and downloading, added source management methods (getCurrentSourceConfig, setSourceConfig, switchSource, validateSourceConfig).
Main Process IPC & Lifecycle
src/main/main.ts
Wired new IPC handlers for package source operations (get/set/switch/validate config, scan/fetch from sources), license management (get/save), integrated LicenseManager lifecycle with sync status forwarding, updated VersionManager initialization.
Package Source Redux State
src/renderer/store/slices/packageSourceSlice.ts
Created Redux slice managing package source configs, form fields, available versions, loading/validation/fetching states, scan results; includes reducers for all mutations and comprehensive selectors.
License Redux State
src/renderer/store/slices/licenseSlice.ts, src/renderer/store/slices/viewSlice.ts
Added licenseSlice for license data, loading, and error state; extended ViewType union to include 'license' view.
Package Source Sagas
src/renderer/store/sagas/packageSourceSaga.ts
Implemented sagas for loading/setting/switching sources, validating configs, scanning folders, fetching GitHub releases and HTTP indices; includes toast notifications and initialization saga.
License Sagas
src/renderer/store/sagas/licenseSaga.ts
Implemented sagas for fetching and saving licenses via IPC with success/error toast feedback and reload-on-save flow.
Store Integration
src/renderer/store/index.ts
Integrated packageSource and license reducers and sagas into root store configuration with startup initialization.
Package Source UI Components
src/renderer/components/PackageSourceSelector.tsx, src/renderer/components/FolderConfigForm.tsx, src/renderer/components/GitHubConfigForm.tsx, src/renderer/components/SourceConfigForm.tsx
Created UI components for source selection, folder/GitHub/HTTP-index configuration forms with validation, error handling, and Redux integration.
License Management UI
src/renderer/components/LicenseManagementPage.tsx
Implemented dedicated license management page with sync status display, current license info, update form, input masking, and sync status listener integration.
Navigation & View Updates
src/renderer/components/SidebarNavigation.tsx, src/renderer/App.tsx, src/renderer/components/VersionManagementPage.tsx
Added license sidebar navigation item, integrated LicenseManagementPage rendering, updated view switching to support 'license', added PackageSourceSelector to VersionManagementPage, removed view transition animations, added onVersionListChanged listener.
IPC & Preload Exposure
src/preload/index.ts
Extended electronAPI with packageSource namespace (getConfig, setConfig, switchSource, validateConfig, scanFolder, fetchGithub, fetchHttpIndex, onConfigChange) and license namespace (get, save, onSyncStatus); updated switchView signature to include 'license'.
Type Definitions
src/types/license.ts
Added LicenseData, LicenseSyncStatus interfaces and DEFAULT_LICENSE_KEY constant.
Internationalization
src/renderer/i18n/locales/en-US/components.json, src/renderer/i18n/locales/en-US/common.json, src/renderer/i18n/locales/zh-CN/components.json, src/renderer/i18n/locales/zh-CN/common.json
Added comprehensive translations for package source UI (folder, GitHub, HTTP index source types, validation, errors) and license management (sync status, current status, update form, actions).
Package Configuration
package.json
Updated main entry point from dist/main.js to dist/main/main.js and corresponding dev script.
Documentation & Specifications
openspec/changes/archive/*
Added comprehensive proposal, specification, and task documents for multi-source package management, license management, HTTP index source, and workflow compression optimizations.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (UI)
    participant Redux as Redux Store
    participant Saga as Package Source Saga
    participant IPC as Electron IPC
    participant Main as Main Process
    participant Source as Package Source
    
    User->>Redux: Dispatch VALIDATE_CONFIG
    Redux->>Saga: Trigger Validation
    Saga->>IPC: Call validateConfig(config)
    IPC->>Main: IPC Handler
    Main->>Source: Create source & validate
    Source->>Source: Check URL/Path/Auth
    Source-->>Main: Validation Result
    Main-->>IPC: Return result
    IPC-->>Saga: Validation complete
    Saga->>Redux: Set validation status
    Redux-->>User: Display result
    
    User->>Redux: Dispatch SCAN_FOLDER
    Redux->>Saga: Trigger scan
    Saga->>IPC: Call scanFolder(path)
    IPC->>Main: IPC Handler
    Main->>Source: List available versions
    Source->>Source: Scan directory/API
    Source-->>Main: Version list
    Main-->>IPC: Return versions
    IPC-->>Saga: Scan complete
    Saga->>Redux: Set available versions
    Redux-->>User: Display versions
Loading
sequenceDiagram
    participant User as User (License UI)
    participant Redux as Redux Store
    participant Saga as License Saga
    participant IPC as Electron IPC
    participant Main as Main Process
    participant Manager as License Manager
    participant Config as ConfigManager
    
    User->>Redux: Dispatch FETCH_LICENSE
    Redux->>Saga: Load license
    Saga->>IPC: Call license.get()
    IPC->>Main: IPC Handler
    Main->>Manager: getLicense()
    Manager->>Config: Retrieve license
    Config-->>Manager: License data
    Manager-->>Main: Return license
    Main-->>IPC: Send to renderer
    IPC-->>Saga: License received
    Saga->>Redux: Set license & clear loading
    Redux-->>User: Display license
    
    User->>Redux: Input new license key
    User->>Redux: Dispatch SAVE_LICENSE
    Redux->>Saga: Save license
    Saga->>IPC: Call license.save(key)
    IPC->>Main: IPC Handler
    Main->>Manager: saveLicense(key)
    Manager->>Manager: syncToAppSettings()
    Manager->>Config: setLicense(key)
    Config-->>Manager: Persisted
    Manager-->>Main: Sync status
    Main-->>IPC: Return success
    IPC-->>Saga: Saved & synced
    Saga->>Redux: Set license, show toast
    Redux-->>User: Display success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Multi-source package management enhancement: This PR fully implements the feature roadmap for supporting local folders, GitHub releases, and HTTP index sources with configurable switching and validation, addressing the core requirements of enhanced package source management.

Possibly related PRs

  • Workflow sync Azure storage PR: Both PRs modify .github/workflows/sync-azure-storage.yml to optimize index.json handling; main PR adds compact JSON generation and validation while related PR handles trigger/input configurations.

Poem

🐰 Bouncing through packages from three sources bright,
Local folders, GitHub stars, HTTP indices in sight,
A license keeper guards the gate with keys secure,
Redux state flows smooth, each transition pure,
Configuration persists, and sources dance between,
The finest multi-source ballet ever seen! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: compress index.json output for reduced file size' directly and clearly describes the primary change in the changeset—adding JSON compression to the workflow output.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/compress-index-json

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/components/VersionManagementPage.tsx (1)

66-83: ⚠️ Potential issue | 🟡 Minor

Add missing onVersionListChanged method to the Window.electronAPI interface.

The window.electronAPI.onVersionListChanged method is used at line 129 but is not declared in the local Window.electronAPI interface. Add the following line to the interface (after onActiveVersionChanged):

onVersionListChanged: (callback: () => void) => void;

The method is implemented in src/preload/index.ts:132 and needs to be declared in the local type definition to avoid TypeScript errors.

🤖 Fix all issues with AI agents
In `@src/main/config.ts`:
- Around line 94-100: In setLicense(licenseKey: string) remove the unused local
variable current (the assignment from this.getLicense()) since it’s not
referenced; update the setLicense method to simply call this.set('license', {
licenseKey, isConfigured: !!licenseKey, updatedAt: new Date().toISOString() })
without declaring current so no unused variable remains.

In `@src/main/main.ts`:
- Around line 1444-1448: The VersionManager is constructed synchronously but
kicks off async work in initializePackageSource(), causing callers (e.g., the
immediate IIFE that uses versionManager) to race with initialization; change the
pattern so construction is synchronous-only and expose an explicit async
initializer (e.g., VersionManager.initializePackageSource or a static async
factory like VersionManager.create(...)) and then await that initializer right
after creating packageSourceConfigManager and before any use of versionManager
(replace the current new VersionManager(...) call with await
VersionManager.create(...) or call await
versionManager.initializePackageSource()). Ensure initializePackageSource() is
moved out of the constructor and returns a Promise so callers can reliably await
completion.

In `@src/main/package-source-config-manager.ts`:
- Around line 278-283: The code in the addSource call inside
package-source-config-manager.ts hardcodes a developer-specific path for
defaultSource; update that call (the defaultSource initialization that invokes
this.addSource) to read the path from an environment/config variable (e.g.
process.env.DEFAULT_LOCAL_PACKAGE_PATH) with a sensible fallback (or omit
fallback and fail-fast) instead of using "/home/newbe36524/…"; ensure you only
change the path property passed to this.addSource and keep the same type/name
values so defaultSource creation logic remains intact.

In `@src/main/package-sources/github-release-source.ts`:
- Around line 329-334: The regex in extractVersionFromFilename only allows
single-digit version parts, so update the pattern(s) to use [0-9]+ for each
numeric component (e.g., change [0-9]\.[0-9]\.[0-9] to [0-9]+(?:\.[0-9]+){2} or
similar) to support multi-digit segments and keep the rest of the capture group
intact; apply the same change to the other occurrence around lines 339-344 so
both extractors accept versions like 10.20.30 while preserving the existing
optional prerelease capture and platform suffix handling in
extractVersionFromFilename.
- Around line 388-406: The current compareVersions implementation in
compareVersions(v1: string, v2: string) strips prerelease info and misorders
semver pre-releases; replace its body to use the semver library by calling
semver.compare(v1, v2) (ensure semver is imported at the top of the file),
removing the manual parseVersion logic so prerelease and build metadata are
handled correctly.

In `@src/main/package-sources/http-index-source.ts`:
- Around line 341-353: The version part of extractPlatformFromFilename only
allows single-digit components; update the filename regex in
extractPlatformFromFilename to accept multi-digit version components (use one or
more digits for each version segment and allow one or two dot-separated
segments), keep the optional prerelease suffix handling (e.g., -beta.1), and
preserve the platform capture and optional -nort suffix logic so filenames like
hagicode-0.10.0-beta.1-linux-x64-nort.zip and hagicode-1.2.10-windows-x64.zip
are matched and normalized (win -> windows).

In `@src/main/package-sources/local-folder-source.ts`:
- Around line 188-214: The three regexes in extractPlatformFromFilename (the
patterns used in the first match, the fallback-old-format match, and the final
no-arch match) currently constrain each version component to a single digit
([0-9]\.[0-9]\.[0-9]) and therefore fail for multi-digit versions; update each
pattern to allow multi-digit version components (e.g., replace
[0-9]\.[0-9]\.[0-9] with \d+\.\d+\.\d+ or [0-9]+\.[0-9]+\.[0-9]+), remembering
to escape backslashes appropriately in the TypeScript string literals, so all
three regex declarations inside extractPlatformFromFilename accept versions like
10.11.123.
- Around line 179-183: In extractVersionFromFilename, the version regex only
allows single-digit components; update the pattern used in filename.match(...)
to accept multi-digit semver components (e.g., use \d+ or [0-9]+ for each
numeric segment) while preserving the optional pre-release part and the platform
group (linux|windows|osx) and the .zip suffix so filenames like
hagicode-0.10.0-linux.zip and hagicode-12.0.0-windows.zip match and return the
captured version instead of 'unknown'.

In `@src/main/package-sources/package-source.ts`:
- Around line 36-39: The HttpIndexConfig interface currently only defines
indexUrl but StoredPackageSourceConfig (in package-source-config-manager.ts)
includes baseUrl and httpAuthToken; update HttpIndexConfig to include baseUrl?:
string and httpAuthToken?: string (or the same required/optional semantics as
StoredPackageSourceConfig) so runtime config matches stored config and no fields
are lost during conversion between StoredPackageSourceConfig and HttpIndexConfig
(ensure any conversion functions that create HttpIndexConfig from
StoredPackageSourceConfig also copy these fields).

In `@src/main/version-manager.ts`:
- Around line 65-79: The constructor currently calls initializePackageSource()
without awaiting it, causing a race on currentPackageSource for methods like
listVersions() and installVersion(); remove the async call from the constructor
and add a static async factory (e.g., VersionManager.create(dependencyManager,
packageSourceConfigManager)) that constructs the VersionManager, then awaits
instance.initializePackageSource() before returning the instance; update any
call sites to use VersionManager.create(...) instead of new VersionManager(...).
Ensure existing ensurePackageSource() stays as a fallback but the factory
guarantees initialization so listVersions(), installVersion(), and other methods
can rely on currentPackageSource being set.
- Around line 101-121: The storedToConfig function currently substitutes empty
strings for missing required fields which can hide errors; update storedToConfig
to validate required properties and throw a descriptive Error when a required
field is missing or empty (e.g., for 'local-folder' ensure stored.path is
defined and non-empty, for 'github-release' ensure stored.owner and stored.repo
are defined and non-empty while token can remain optional, and for 'http-index'
ensure stored.indexUrl is defined and non-empty), using trimmed checks to catch
whitespace-only values and include the offending type/name in the thrown message
to fail fast and avoid silent downstream failures.

In `@src/preload/index.ts`:
- Around line 54-70: The Window.electronAPI type is missing several grouped IPC
objects and listeners; update the central electron API declaration (the
Window.electronAPI interface) to include a packageSource object with the eight
methods getConfig, getAllConfigs, setConfig, switchSource, validateConfig,
scanFolder, fetchGithub, fetchHttpIndex and the onConfigChange(callback)
listener, a license object with get, save and onSyncStatus methods/listener, the
onVersionListChanged listener, and any missing manifest dependency APIs used
across the renderer (e.g., the methods referenced in webServiceSaga.ts); add
these symbols to the shared type declaration so all components import the same
complete electronAPI shape rather than declaring local/fragmented types.

In `@src/renderer/components/PackageSourceSelector.tsx`:
- Around line 102-118: Replace hard-coded Chinese strings used as UI labels in
PackageSourceSelector (e.g., the name property values for the local folder,
GitHub release and HTTP index branches and the Save button text referenced
around lines ~289-291) with translation keys or locale‑agnostic IDs, then render
them via the app's i18n function (or a localized label resolver) before display;
specifically update the objects created in the ternary branches where name:
'本地文件夹' / name: 'HagiCode Releases' / name: 'HTTP 索引源' to use translation keys
(e.g., t('packageSource.localFolder')) and change the Save button text to call
t('common.save') or the equivalent i18n helper used in this codebase so all
labels are localized at render time.
- Around line 56-63: The sync effect leaves a stale folder value because it only
calls dispatch(setFolderPath(...)) when currentConfig.path is truthy; update the
logic in the PackageSourceSelector sync to always set folder state for the
'local-folder' branch (e.g. dispatch(setFolderPath(currentConfig.path || ''))
inside the if (currentConfig.type === 'local-folder') block) and optionally
clear the folder state in the other branches (call dispatch(setFolderPath(''))
when type !== 'local-folder') so the input cannot retain a previous value.
- Around line 99-105: The ternary precedence in PackageSourceSelector.tsx causes
folderPath to be ignored: change the expression used for the path property so it
evaluates folderPath first and only falls back to the dev path when folderPath
is falsy; specifically update the path assignment (the object created when value
=== 'local-folder') to use a clear conditional like folderPath ? folderPath :
(process.env.NODE_ENV === 'development' ? '<devPath>' : '') so the path property
truly uses folderPath when present.

In `@src/renderer/i18n/locales/zh-CN/components.json`:
- Line 265: The translation entry "nortOnlyNotice" appears to contain a possible
typo in its value ("nort"); verify the intended term by checking the matching
key in other locale files and any UI usage of the key "nortOnlyNotice", then
correct the Chinese string to the accurate term and ensure the same key/value
spelling is used consistently across all locale files and in code references
(search for "nortOnlyNotice" to update any mismatches).

In `@src/renderer/store/sagas/licenseSaga.ts`:
- Around line 55-63: Replace the hard-coded mixed CN/EN toast messages in
licenseSaga.ts with i18n translation lookups: call the app's translation
function (e.g., i18n.t or the translator used elsewhere) for both the toast
title and description where toast.success and toast.error are invoked (including
the other occurrences noted at lines ~70-72), and also use translated text when
dispatching setError(result.error || '...') so fallback messages come from
translation keys rather than literal strings; update the
toast.success/toast.error calls and the setError fallback to use the appropriate
translation keys (e.g., 'license.updated.success' / 'license.updated.failure')
so messages follow the app i18n pattern.

In `@src/renderer/store/sagas/packageSourceSaga.ts`:
- Around line 32-48: The loading flag races because loadSourceConfig and
loadAllSourceConfigs both dispatch setLoading(true/false); change coordination
so initializePackageSourceSaga doesn't get a false from the first completer:
either (A) add a counter-based loading API (e.g., dispatch startLoading()
increments a reducer counter and finishLoading() decrements it, keep setLoading
usage replaced by startLoading()/finishLoading() inside loadSourceConfig and
loadAllSourceConfigs), or (B) add two distinct flags (e.g., setConfigLoading and
setAllConfigsLoading) and have loadSourceConfig call
setConfigLoading(true/false) and loadAllSourceConfigs call
setAllConfigsLoading(true/false); update the reducer to derive overall loading
if needed and update initializePackageSourceSaga to remove its own setLoading
toggles so loading remains true until both sagas finish. Ensure the change
references functions loadSourceConfig, loadAllSourceConfigs,
initializePackageSourceSaga, and the existing setLoading action so you update
all call sites consistently.

In `@src/renderer/store/slices/packageSourceSlice.ts`:
- Around line 52-54: The hardcoded developer-specific path assigned to
folderPath in packageSourceSlice (the ternary using process.env.NODE_ENV ===
'development') is duplicated elsewhere; extract that literal into a shared
constant or environment variable and import/use it in both packageSourceSlice
and package-source-config-manager instead of repeating the string. Create a
single exported symbol (e.g., RELEASE_PACKAGES_PATH or RELEASE_PACKAGES_DIR) in
a shared config/module and replace the inline string in the folderPath ternary
and the other occurrence so both modules read the same value (or read from
process.env.RELEASE_PACKAGES_DIR) to avoid duplication and maintenance drift.
- Line 3: Update the import so PackageSourceConfig is imported from the correct
module: replace the current combined import that pulls PackageSourceConfig from
package-source-config-manager with two imports — import type {
PackageSourceConfig } from '../../../main/package-sources/package-source' and
import type { StoredPackageSourceConfig } from
'../../../main/package-source-config-manager' — ensuring the symbols
PackageSourceConfig and StoredPackageSourceConfig are referenced from their
proper files (package-source.ts for PackageSourceConfig,
package-source-config-manager for StoredPackageSourceConfig).
🧹 Nitpick comments (40)
.github/workflows/sync-azure-storage.yml (1)

181-187: Good defensive validation; consider using English for comment consistency.

The jq empty validation is a good safety check before uploading. However, the comment on line 181 is in Chinese while the rest of the workflow uses English comments.

Suggested fix for comment consistency
-          # 验证生成的 JSON 格式有效
+          # Validate the generated JSON format
           echo "Validating index.json..."
openspec/changes/archive/2026-02-08-license-management-feature/license-management-feature/proposal.md (1)

29-44: Consider adding language specifier to fenced code blocks.

The ASCII art diagrams at lines 29-44 and 48-73 trigger markdownlint warnings (MD040). While these are visual diagrams rather than code, adding a text or plaintext language specifier would suppress the warnings and improve markdown consistency.

📝 Example fix for line 29
-```
+```text
 ┌─────────────────────────────────────────┐
openspec/changes/archive/2026-02-08-multi-source-package-management-enhancement/multi-source-package-management-enhancement/tasks.md (1)

206-211: Inconsistency: HTTP package source is mentioned as future work but appears to be implemented.

Line 208 suggests "考虑添加 HTTP 包源类型作为未来的扩展" (consider adding HTTP package source as future extension), but based on the PR summary and related spec file (spec.md for HTTP index package source), the HTTP index source is being implemented in this PR.

Consider updating this section to reflect the current implementation status or remove it from the "技术债务和改进建议" list.

openspec/changes/archive/2026-02-08-multi-source-package-management-enhancement/multi-source-package-management-enhancement/design.md (3)

104-111: Replace developer-specific hardcoded path with a generic example.

The default configuration example contains a developer-specific path (/home/newbe36524/repos/...) which could confuse other developers or accidentally end up in production configuration. Consider using a placeholder or generic example path.

📝 Suggested change
 const defaultConfig: PackageSourceConfig = {
   type: 'local-folder',
   localFolderPath: process.env.NODE_ENV === 'development'
-    ? '/home/newbe36524/repos/newbe36524/pcode/Release/release-packages/'
+    ? '/path/to/your/local/release-packages/'  // Configure via environment or settings
     : path.join(app.getPath('userData'), 'packages')
 };

633-644: Same hardcoded path appears in migration example.

The migration code also references the developer-specific path. Update for consistency with the above suggestion.

📝 Suggested change
 async function migrateConfig() {
-  const oldPath = '/home/newbe36524/repos/newbe36524/pcode/Release/release-packages/';
+  const oldPath = process.env.OLD_PACKAGE_PATH || '/path/to/old/release-packages/';
   const newConfig: PackageSourceConfig = {
     type: 'local-folder',
     localFolderPath: oldPath
   };

   await configManager.setPackageSourceConfig(newConfig);
 }

119-152: Consider adding language specifier to ASCII art blocks.

Similar to the proposal document, the UI mockups at lines 119-152, 156-200, and 302-348 use fenced code blocks without language specifiers, triggering MD040 warnings. Adding text would improve consistency.

openspec/changes/remove-view-transition-animation/tasks.md (3)

9-122: Tasks should be marked as complete if implementation is done.

Based on the commit history ("Remove view transition fade animation"), this feature appears to have been implemented. Per coding guidelines, all completed tasks should be updated to - [x] after implementation work is confirmed.

Please update the task checkboxes to reflect the actual implementation status.

As per coding guidelines: "Update every task in tasks.md to - [x] after all implementation work is complete and confirmed"


102-111: Add language identifier to fenced code block.

The commit message example block should specify a language for proper rendering and linting compliance.

📝 Proposed fix
 - [ ] 创建提交:
-  ```
+  ```bash
   git commit -m "feat: remove view transition fade animation

162-196: Add language identifier to ASCII diagram code block.

Consider using text or plaintext as the language identifier for the dependency diagram to satisfy markdown linting.

📝 Proposed fix
-```
+```text
 ┌─────────────────┐
 │ 1. 代码准备      │
src/types/license.ts (1)

17-24: Duplicate LicenseSyncStatus interface definition.

This interface is also defined in src/main/license-manager.ts (lines 12-19). To avoid drift and maintain DRY principles, consider having a single source of truth for this type and importing it where needed.

♻️ Suggested approach

Either:

  1. Remove the duplicate from src/main/license-manager.ts and import from this shared types file, or
  2. Keep the definition in src/main/license-manager.ts and import it here for re-export

Option 1 is recommended since src/types/license.ts appears to be the canonical location for shared license types.

src/renderer/store/index.ts (1)

13-14: Unused imports: initializePackageSourceSaga and initializeLicenseSaga.

These are imported but never used. The code dispatches raw action types instead (lines 60-61, 65).

Either remove the unused imports or use the imported action creators for consistency with other sagas (e.g., initializeI18nSaga on line 48).

♻️ Option 1: Remove unused imports
-import { packageSourceSaga, initializePackageSourceSaga } from './sagas/packageSourceSaga';
-import { licenseSaga, initializeLicenseSaga } from './sagas/licenseSaga';
+import { packageSourceSaga } from './sagas/packageSourceSaga';
+import { licenseSaga } from './sagas/licenseSaga';
src/renderer/components/FolderConfigForm.tsx (2)

7-18: Remove unused imports.

Select, SelectContent, SelectItem, SelectTrigger, SelectValue, and setSelectedSourceType are imported but never used in this component.

♻️ Proposed fix
 import { Label } from './ui/label';
 import { Input } from './ui/input';
 import { Button } from './ui/button';
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from './ui/select';
 import type { RootState } from '../store';
 import {
   setFolderPath,
-  setSelectedSourceType,
 } from '../store/slices/packageSourceSlice';

40-44: Consider surfacing the error to the user.

The error is only logged to console. If the directory picker fails, the user receives no feedback. Consider dispatching an error action or using a toast notification.

src/main/package-sources/github-release-source.ts (1)

94-96: Unreachable code: axios throws on non-2xx status by default.

This status check will never be reached because axios throws an error for non-2xx responses. The error handling in lines 157-185 correctly catches these cases.

♻️ Remove unreachable code
       const response = await axios.get<GitHubRelease[]>(
         `https://api.github.com/repos/${this.config.owner}/${this.config.repo}/releases`,
         {
           headers,
           params: {
             // Get all releases including drafts and pre-releases
             per_page: 100, // Maximum per page
           }
         }
       );

-      if (response.status !== 200) {
-        throw new Error(`GitHub API returned status ${response.status}`);
-      }
-
       const releases = response.data;
src/renderer/components/SourceConfigForm.tsx (2)

14-18: Unused import: setValidationError.

This action creator is imported but never used in the component.

♻️ Proposed fix
 import {
   setSelectedSourceType,
-  setValidationError,
   clearErrors,
 } from '../store/slices/packageSourceSlice';

73-73: Consider using the Label component for consistency.

Other forms in this PR (FolderConfigForm, GitHubConfigForm) use the Label component from ./ui/label. Using a plain <label> here creates inconsistency.

♻️ Proposed fix
+import { Label } from './ui/label';
...
       <div className="space-y-2">
-        <label className="text-sm font-medium">{t('packageSource.sourceType.label')}</label>
+        <Label>{t('packageSource.sourceType.label')}</Label>
src/main/package-sources/index.ts (1)

11-24: Consider an exhaustive switch to avoid as any in the default branch.
Tightening typing here improves safety when adding new source types.

♻️ Example tightening
 export function createPackageSource(config: PackageSourceConfig): PackageSource {
   switch (config.type) {
     case 'local-folder':
       return new LocalFolderPackageSource(config as LocalFolderConfig);
@@
     case 'http-index':
       return new HttpIndexPackageSource(config as HttpIndexConfig);
 
     default:
-      throw new Error(`Unknown package source type: ${(config as any).type}`);
+      // Exhaustiveness check for future types
+      const _exhaustive: never = config;
+      throw new Error(`Unknown package source type: ${(config as any).type}`);
   }
 }
src/renderer/components/LicenseManagementPage.tsx (4)

59-70: Hard-coded Chinese strings should use i18n.

The toast notification messages use hard-coded Chinese text while the rest of the component uses useTranslation. This creates inconsistent localization behavior.

♻️ Suggested refactor to use i18n
       if (status.synced) {
-        const sourceText = status.source === 'default' ? '默认许可证' :
-                          status.source === 'existing' ? '已有许可证' : '手动设置';
-        const isDefaultText = status.isDefault ? '(默认公测许可证)' : '';
-        const versionsText = status.syncedVersions !== undefined
-          ? `\n已同步到 ${status.syncedVersions} 个版本`
-          : '';
-
-        toast.success(`许可证已同步: ${sourceText}${isDefaultText}${versionsText}`, {
-          description: `时间: ${new Date(status.timestamp).toLocaleString('zh-CN')}`,
+        const sourceText = t(`licenseManagement.syncStatus.source.${status.source}`);
+        const isDefaultText = status.isDefault ? t('licenseManagement.syncStatus.defaultBeta') : '';
+        const versionsText = status.syncedVersions !== undefined
+          ? t('licenseManagement.syncStatus.syncedVersions', { count: status.syncedVersions })
+          : '';
+
+        toast.success(t('licenseManagement.syncStatus.synced', { source: sourceText, default: isDefaultText, versions: versionsText }), {
+          description: t('licenseManagement.syncStatus.timestamp', { time: new Date(status.timestamp).toLocaleString() }),
         });

80-86: Hard-coded error message in handleSaveLicense.

Similar to the sync status toast, this error message is hard-coded in Chinese while the component uses i18n elsewhere.

♻️ Suggested fix
   const handleSaveLicense = () => {
     if (!licenseKey.trim()) {
-      toast.error('许可证密钥不能为空', {
-        description: 'License key cannot be empty',
-      });
+      toast.error(t('licenseManagement.errors.emptyLicenseKey'));
       return;
     }

45-78: useEffect has potential stale closure and re-subscription issue.

The effect depends on [license], which means every time license changes, it will unsubscribe and re-subscribe to onSyncStatus. Additionally, Lines 47-51 set licenseKey state inside the effect that depends on license, which could cause unnecessary re-renders.

Consider separating the initialization logic from the subscription logic.

♻️ Suggested refactor to separate concerns
+  // Initialize license key from current license or default
+  useEffect(() => {
+    if (license?.licenseKey) {
+      setLicenseKey(license.licenseKey);
+    } else {
+      setLicenseKey(DEFAULT_LICENSE_KEY);
+    }
+  }, [license?.licenseKey]);
+
+  // Subscribe to license sync status changes (runs once on mount)
   useEffect(() => {
-    // Pre-fill the input with current license or default
-    if (license?.licenseKey) {
-      setLicenseKey(license.licenseKey);
-    } else {
-      setLicenseKey(DEFAULT_LICENSE_KEY);
-    }
-
-    // Listen for license sync status changes
     const unsubscribe = window.electronAPI.license.onSyncStatus((status) => {
       console.log('[LicenseManagementPage] License sync status:', status);
       setSyncStatus(status);
-
-      // Show toast notification for sync status
-      if (status.synced) {
-        // ... toast logic
-      }
+      // ... toast logic
     });
 
     return () => {
       if (typeof unsubscribe === 'function') {
         unsubscribe();
       }
     };
-  }, [license]);
+  }, []);

156-199: Hard-coded Chinese strings in Sync Status Card UI.

The sync status card contains multiple hard-coded Chinese strings (lines 162, 168, 170, 174, 176-179, 183, 190, 192). These should use i18n for consistency.

src/main/license-manager.ts (4)

13-20: Duplicate interface definition.

LicenseSyncStatus is defined here but also exists in src/types/license.ts (lines 16-23). This duplication can lead to drift if one is updated without the other.

♻️ Suggested fix to import instead of duplicate
 import type { ConfigManager } from './config.js';
-import type { LicenseData } from '../types/license.js';
-import { DEFAULT_LICENSE_KEY } from '../types/license.js';
+import type { LicenseData, LicenseSyncStatus } from '../types/license.js';
+import { DEFAULT_LICENSE_KEY } from '../types/license.js';
 import { PathManager } from './path-manager.js';
-
-/**
- * License sync status information
- */
-export interface LicenseSyncStatus {
-  synced: boolean;
-  licenseKey?: string;
-  isDefault?: boolean;
-  source: 'existing' | 'default' | 'manual';
-  timestamp: string;
-  syncedVersions?: number; // Number of versions that were synced
-}
+
+// Re-export for consumers that import from this module
+export type { LicenseSyncStatus } from '../types/license.js';

40-45: Singleton getInstance requires configManager on every call but ignores it after first instantiation.

Once the singleton is created, subsequent calls to getInstance(configManager) will ignore the provided configManager argument. This could lead to confusion if different ConfigManager instances are passed.

♻️ Consider adding a warning or assertion
   static getInstance(configManager: ConfigManager): LicenseManager {
     if (!LicenseManager.instance) {
       LicenseManager.instance = new LicenseManager(configManager);
+    } else if (process.env.NODE_ENV === 'development') {
+      log.warn('[LicenseManager] getInstance called with configManager after instance already created - argument ignored');
     }
     return LicenseManager.instance;
   }

106-120: saveLicense reports success even if sync to AppSettings partially fails.

syncToAppSettings catches errors per-version and continues (line 279-281), returning only successfully updated versions. The saveLicense method then reports success: true regardless of how many versions were synced. Consider indicating partial failure if not all versions were updated.


236-244: Silent catch when reading YAML config is acceptable but consider logging at debug level.

The empty catch block (lines 241-243) silently creates a new config if the file doesn't exist. While this fallback is appropriate, a debug-level log would aid troubleshooting.

♻️ Suggested improvement
           try {
             const content = await fs.readFile(configPath, 'utf-8');
             config = yaml.load(content) as Record<string, any> || {};
-          } catch {
+          } catch (readError) {
+            log.debug(`[LicenseManager] Config file not found for ${versionId}, will create new one`);
             // Config doesn't exist, will create new one
           }
src/main/package-sources/local-folder-source.ts (2)

99-119: Duplicate fs.stat calls for progress reporting.

The code calls fs.stat on the source file before copy (line 101) and on the destination file after copy (line 113). The second stat is redundant since the copied file will have the same size. Store the size from the first call.

♻️ Suggested optimization
+      let totalSize: number | undefined;
+
       // Get file size for progress reporting
       if (onProgress) {
         const stats = await fs.stat(sourcePath);
-        const totalSize = stats.size;
-
+        totalSize = stats.size;
         // Report progress in chunks for large files
         onProgress({ current: 0, total: totalSize, percentage: 0 });
       }

       // Copy file to cache
       await fs.copyFile(sourcePath, cachePath);

       // Report completion
-      if (onProgress) {
-        const stats = await fs.stat(cachePath);
+      if (onProgress && totalSize !== undefined) {
         onProgress({
-          current: stats.size,
-          total: stats.size,
+          current: totalSize,
+          total: totalSize,
           percentage: 100,
         });
       }

252-288: Code duplication with HttpIndexPackageSource.

getCurrentPlatform() and compareVersions() methods are identical to those in http-index-source.ts. Consider extracting these to a shared utility module.

openspec/changes/archive/2026-02-08-custom-http-index-package-source/tasks.md (1)

81-101: Testing tasks remain incomplete.

Sections 6.1, 6.2, and 6.3 are marked incomplete. If this PR is intended to be merged before tests are written, consider tracking these as follow-up work items or issues.

Would you like me to help create GitHub issues to track these testing tasks for follow-up?

openspec/changes/archive/2026-02-08-custom-http-index-package-source/proposal.md (1)

19-53: Add language specifiers to fenced code blocks.

The ASCII art code blocks at lines 19-53 and 82-93 don't have a language specifier, triggering markdownlint warnings. Consider adding a specifier like text or plaintext.

♻️ Suggested fix
-```
+```text
 ┌─────────────────────────────────────────────────────────┐
 │ 📦 包源管理                                    │

Also applies to: 82-93

src/main/package-sources/http-index-source.ts (2)

189-204: Downloading entire file into memory before writing may cause issues with large packages.

Using responseType: 'arraybuffer' loads the complete file into memory. For large packages, this could exhaust memory. Consider using stream-based downloading.

♻️ Suggested stream-based approach
+import { createWriteStream } from 'node:fs';
+import { pipeline } from 'node:stream/promises';
+
   async downloadPackage(
     version: Version,
     cachePath: string,
     onProgress?: DownloadProgressCallback
   ): Promise<void> {
     try {
       if (!version.downloadUrl) {
         throw new Error(`No download URL available for version: ${version.id}`);
       }

       log.info('[HttpIndexSource] Downloading package:', version.id);

-      // Download with progress tracking
-      const response = await axios.get<ArrayBuffer>(version.downloadUrl, {
-        responseType: 'arraybuffer',
+      // Download with streaming and progress tracking
+      const response = await axios.get(version.downloadUrl, {
+        responseType: 'stream',
         onDownloadProgress: (progressEvent) => {
           if (onProgress && progressEvent.total) {
             const current = progressEvent.loaded;
             const total = progressEvent.total;
             const percentage = Math.round((current / total) * 100);
             onProgress({ current, total, percentage });
           }
         },
       });

-      // Write to cache file
-      const buffer = Buffer.from(response.data);
-      await fs.writeFile(cachePath, buffer);
+      // Stream to cache file
+      const writer = createWriteStream(cachePath);
+      await pipeline(response.data, writer);

358-394: Code duplication with LocalFolderPackageSource.

getCurrentPlatform() and compareVersions() are duplicated from local-folder-source.ts. Extract these to a shared utility module to improve maintainability.

♻️ Suggested approach

Create a new file src/main/package-sources/utils.ts:

// src/main/package-sources/utils.ts
export function getCurrentPlatform(): string {
  const currentPlatform = process.platform;
  switch (currentPlatform) {
    case 'win32':
      return 'windows';
    case 'darwin':
      return 'osx';
    case 'linux':
      return 'linux';
    default:
      return 'unknown';
  }
}

export function compareVersions(v1: string, v2: string): number {
  const parseVersion = (v: string) => {
    const parts = v.split('-')[0].split('.').map(Number);
    return parts;
  };

  const p1 = parseVersion(v1);
  const p2 = parseVersion(v2);

  for (let i = 0; i < Math.max(p1.length, p2.length); i++) {
    const n1 = p1[i] || 0;
    const n2 = p2[i] || 0;
    if (n1 > n2) return 1;
    if (n1 < n2) return -1;
  }
  return 0;
}

Then import in both source files.

src/renderer/store/sagas/packageSourceSaga.ts (3)

27-27: Unused action type constant.

CLEAR_VERSIONS is defined but has no corresponding watcher saga. Either add a watcher for it or remove the unused constant to reduce dead code.


92-94: Inconsistent toast message languages.

Toast titles are in Chinese while descriptions are in English. This creates an inconsistent user experience. Consider using i18n keys for both title and description to maintain language consistency.

-      toast.success('包源配置已保存', {
-        description: 'Package source configuration saved successfully',
+      toast.success(t('packageSource.saveSuccess'), {
+        description: t('packageSource.saveSuccessDesc'),
       });

Also applies to: 98-100, 107-109, 136-138, 142-144, 151-153


15-16: Cross-boundary type imports from main process.

Importing types directly from ../../../main/version-manager and ../../../main/package-source-config-manager creates coupling between renderer and main process code. While this works in Electron, consider creating shared type definitions in a common location (e.g., src/shared/types/) to improve maintainability.

src/main/version-manager.ts (1)

163-171: Repeated unsafe type casts.

Multiple (config as any) casts are used to access type-specific properties. This bypasses TypeScript's type checking and could lead to runtime errors if the config type doesn't match.

Use type guards or discriminated union narrowing instead:

♻️ Suggested improvement
-        if (config.type === 'local-folder') {
-          updates.path = (config as any).path;
-        } else if (config.type === 'github-release') {
-          updates.owner = (config as any).owner;
+        if (config.type === 'local-folder') {
+          updates.path = (config as LocalFolderConfig).path;
+        } else if (config.type === 'github-release') {
+          const ghConfig = config as GitHubReleaseConfig;
+          updates.owner = ghConfig.owner;
+          updates.repo = ghConfig.repo;
+          updates.token = ghConfig.token;
src/main/package-source-config-manager.ts (2)

300-322: Default source methods are not idempotent.

getDefaultGitHubSource() and getDefaultHttpIndexSource() create a new createdAt timestamp on each call. If these are used to compare or deduplicate sources, the varying timestamps will cause issues.

Consider using a fixed timestamp or lazy initialization:

♻️ Suggested fix
+  private static readonly DEFAULT_CREATED_AT = '1970-01-01T00:00:00.000Z';
+
   getDefaultGitHubSource(): StoredPackageSourceConfig {
     return {
       id: 'github-default',
       type: 'github-release',
       name: 'HagiCode Releases',
       owner: 'HagiCode-org',
       repo: 'releases',
-      createdAt: new Date().toISOString(),
+      createdAt: PackageSourceConfigManager.DEFAULT_CREATED_AT,
     };
   }

46-49: Unsafe double type assertion.

The as unknown as Store<PackageSourceStoreSchema> cast bypasses type checking entirely. If the provided store has an incompatible schema, this will cause runtime errors.

Consider adding runtime validation or using a type guard:

     if (store) {
-      this.store = store as unknown as Store<PackageSourceStoreSchema>;
+      // Validate store has expected structure
+      this.store = store as Store<PackageSourceStoreSchema>;
+      // Ensure defaults exist
+      if (!this.store.has('sources')) {
+        this.store.set('sources', []);
+      }
     }
src/renderer/store/slices/packageSourceSlice.ts (1)

8-40: State slice mixes domain and UI concerns.

The state combines domain data (currentConfig, availableVersions) with UI form state (folderPath, githubOwner, githubToken, etc.). Consider separating these into distinct slices or namespaced sub-states for better maintainability and testability.

src/main/main.ts (1)

35-44: Duplicate preload path resolution logic.

The same preload path calculation appears twice (lines 35-44 and 130-135). Extract to a helper function to maintain DRY principle:

♻️ Suggested refactor
+function getPreloadPath(): string {
+  const isDev = process.env.NODE_ENV === 'development';
+  return isDev
+    ? path.join(__dirname, '../../dist/preload/index.mjs')
+    : path.join(__dirname, 'preload/index.mjs');
+}
+
 function createWindow(): void {
   console.log('[Hagicode] Creating window...');
-
-  const isDev = process.env.NODE_ENV === 'development';
-  const preloadPath = isDev
-    ? path.join(__dirname, '../../dist/preload/index.mjs')
-    : path.join(__dirname, 'preload/index.mjs');
+  const preloadPath = getPreloadPath();

Also applies to: 130-135

src/renderer/i18n/locales/en-US/components.json (1)

265-265: Unclear translation key.

The key nortOnlyNotice with value "Only nort packages (hagicode-*) are supported" uses the term "nort" which may be unclear to users. If "nort" is an internal project name or acronym, consider using a more descriptive term or adding clarification.

Comment on lines +94 to +100
setLicense(licenseKey: string): void {
const current = this.getLicense();
this.set('license', {
licenseKey,
isConfigured: !!licenseKey,
updatedAt: new Date().toISOString(),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused variable current.

The current variable is assigned but never used in the setLicense method. This appears to be leftover from a potential merge operation that was not implemented.

🧹 Proposed fix
   setLicense(licenseKey: string): void {
-    const current = this.getLicense();
     this.set('license', {
       licenseKey,
       isConfigured: !!licenseKey,
       updatedAt: new Date().toISOString(),
     });
   }
📝 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
setLicense(licenseKey: string): void {
const current = this.getLicense();
this.set('license', {
licenseKey,
isConfigured: !!licenseKey,
updatedAt: new Date().toISOString(),
});
setLicense(licenseKey: string): void {
this.set('license', {
licenseKey,
isConfigured: !!licenseKey,
updatedAt: new Date().toISOString(),
});
}
🤖 Prompt for AI Agents
In `@src/main/config.ts` around lines 94 - 100, In setLicense(licenseKey: string)
remove the unused local variable current (the assignment from this.getLicense())
since it’s not referenced; update the setLicense method to simply call
this.set('license', { licenseKey, isConfigured: !!licenseKey, updatedAt: new
Date().toISOString() }) without declaring current so no unused variable remains.

Comment on lines +1444 to +1448
// Initialize Package Source Configuration Manager
packageSourceConfigManager = new PackageSourceConfigManager(configManager.getStore() as unknown as Store);

// Initialize Version Manager with package source config manager
versionManager = new VersionManager(dependencyManager, packageSourceConfigManager);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Initialization order may cause race conditions.

VersionManager is constructed synchronously but its internal initializePackageSource() is async. The immediately following code that uses versionManager (like the IIFE at line 1466) could encounter an uninitialized package source.

This ties back to the constructor issue in version-manager.ts. Consider awaiting initialization:

♻️ Suggested approach
-  versionManager = new VersionManager(dependencyManager, packageSourceConfigManager);
+  versionManager = new VersionManager(dependencyManager, packageSourceConfigManager);
+  // Ensure package source is initialized before using versionManager
+  await versionManager.ensureInitialized(); // Add this method
🤖 Prompt for AI Agents
In `@src/main/main.ts` around lines 1444 - 1448, The VersionManager is constructed
synchronously but kicks off async work in initializePackageSource(), causing
callers (e.g., the immediate IIFE that uses versionManager) to race with
initialization; change the pattern so construction is synchronous-only and
expose an explicit async initializer (e.g.,
VersionManager.initializePackageSource or a static async factory like
VersionManager.create(...)) and then await that initializer right after creating
packageSourceConfigManager and before any use of versionManager (replace the
current new VersionManager(...) call with await VersionManager.create(...) or
call await versionManager.initializePackageSource()). Ensure
initializePackageSource() is moved out of the constructor and returns a Promise
so callers can reliably await completion.

Comment on lines +278 to +283
const defaultSource = process.env.NODE_ENV === 'development'
? this.addSource({
type: 'local-folder',
name: '本地文件夹',
path: '/home/newbe36524/repos/newbe36524/pcode/Release/release-packages/',
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded developer-specific path should be configurable.

The hardcoded path /home/newbe36524/repos/newbe36524/pcode/Release/release-packages/ is specific to one developer's machine and will fail on other development environments.

Consider using an environment variable:

♻️ Suggested fix
         const defaultSource = process.env.NODE_ENV === 'development'
           ? this.addSource({
               type: 'local-folder',
               name: '本地文件夹',
-              path: '/home/newbe36524/repos/newbe36524/pcode/Release/release-packages/',
+              path: process.env.HAGICODE_DEV_PACKAGES_PATH || path.join(app.getPath('userData'), 'dev-packages'),
             })
📝 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
const defaultSource = process.env.NODE_ENV === 'development'
? this.addSource({
type: 'local-folder',
name: '本地文件夹',
path: '/home/newbe36524/repos/newbe36524/pcode/Release/release-packages/',
})
const defaultSource = process.env.NODE_ENV === 'development'
? this.addSource({
type: 'local-folder',
name: '本地文件夹',
path: process.env.HAGICODE_DEV_PACKAGES_PATH || path.join(app.getPath('userData'), 'dev-packages'),
})
🤖 Prompt for AI Agents
In `@src/main/package-source-config-manager.ts` around lines 278 - 283, The code
in the addSource call inside package-source-config-manager.ts hardcodes a
developer-specific path for defaultSource; update that call (the defaultSource
initialization that invokes this.addSource) to read the path from an
environment/config variable (e.g. process.env.DEFAULT_LOCAL_PACKAGE_PATH) with a
sensible fallback (or omit fallback and fail-fast) instead of using
"/home/newbe36524/…"; ensure you only change the path property passed to
this.addSource and keep the same type/name values so defaultSource creation
logic remains intact.

Comment on lines +329 to +334
private extractVersionFromFilename(filename: string): string {
// Try to extract from tag name (remove 'v' prefix if present)
// Format: hagicode-{version}-{platform}.zip
const match = filename.match(/^hagicode-([0-9]\.[0-9]\.[0-9](?:-[a-zA-Z0-9\.]+)?)-(linux|windows|osx)\.zip$/);
return match ? match[1] : 'unknown';
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Version regex only matches single-digit version components.

The pattern [0-9]\.[0-9]\.[0-9] only matches versions like 1.2.3 but not 10.20.30. Use [0-9]+ for multi-digit support.

🐛 Proposed fix
   private extractVersionFromFilename(filename: string): string {
-    const match = filename.match(/^hagicode-([0-9]\.[0-9]\.[0-9](?:-[a-zA-Z0-9\.]+)?)-(linux|windows|osx)\.zip$/);
+    const match = filename.match(/^hagicode-([0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9\.]+)?)-(linux|windows|osx)\.zip$/);
     return match ? match[1] : 'unknown';
   }

   private extractPlatformFromFilename(filename: string): string {
-    let match = filename.match(/^hagicode-([0-9]\.[0-9]\.[0-9](?:-[a-zA-Z0-9\.]+)?)-(linux|windows|osx)\.zip$/);
+    let match = filename.match(/^hagicode-([0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9\.]+)?)-(linux|windows|osx)\.zip$/);
     if (match) {
       return match[2];
     }

-    match = filename.match(/^hagicode-([0-9]\.[0-9]\.[0-9](?:-[a-zA-Z0-9\.]+)?)-([a-zA-Z]+)-x64\.zip$/);
+    match = filename.match(/^hagicode-([0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9\.]+)?)-([a-zA-Z]+)-x64\.zip$/);

Also applies to: 339-344

🤖 Prompt for AI Agents
In `@src/main/package-sources/github-release-source.ts` around lines 329 - 334,
The regex in extractVersionFromFilename only allows single-digit version parts,
so update the pattern(s) to use [0-9]+ for each numeric component (e.g., change
[0-9]\.[0-9]\.[0-9] to [0-9]+(?:\.[0-9]+){2} or similar) to support multi-digit
segments and keep the rest of the capture group intact; apply the same change to
the other occurrence around lines 339-344 so both extractors accept versions
like 10.20.30 while preserving the existing optional prerelease capture and
platform suffix handling in extractVersionFromFilename.

Comment on lines +388 to +406
private compareVersions(v1: string, v2: string): number {
const parseVersion = (v: string) => {
const parts = v.split('-')[0].split('.').map(Number);
return parts;
};

const p1 = parseVersion(v1);
const p2 = parseVersion(v2);

for (let i = 0; i < Math.max(p1.length, p2.length); i++) {
const n1 = p1[i] || 0;
const n2 = p2[i] || 0;

if (n1 > n2) return 1;
if (n1 < n2) return -1;
}

return 0;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the file exists and examine the compareVersions method
if [ -f "src/main/package-sources/github-release-source.ts" ]; then
  echo "=== File found, checking lines 388-406 ==="
  sed -n '388,406p' "src/main/package-sources/github-release-source.ts"
else
  echo "File not found at expected path"
  find . -name "github-release-source.ts" -type f 2>/dev/null | head -5
fi

Repository: HagiCode-org/desktop

Length of output: 569


🏁 Script executed:

#!/bin/bash
# Check package.json for semver dependency
if [ -f "package.json" ]; then
  echo "=== Checking package.json for semver dependency ==="
  cat package.json | jq '.dependencies.semver // .devDependencies.semver // "Not found"'
fi

Repository: HagiCode-org/desktop

Length of output: 124


🌐 Web query:

latest version of semver npm package

💡 Result:

The latest published version of the semver npm package is 7.7.3. [1][2]


Use semver library for accurate prerelease version comparison.

compareVersions strips prerelease suffixes with split('-')[0], causing 1.0.0-alpha.1 and 1.0.0-alpha.2 to be treated as equal and sort inconsistently. Since semver is already a dependency, replace the implementation with semver.compare(v1, v2):

🛠️ Suggested fix
  private compareVersions(v1: string, v2: string): number {
-   const parseVersion = (v: string) => {
-     const parts = v.split('-')[0].split('.').map(Number);
-     return parts;
-   };
-
-   const p1 = parseVersion(v1);
-   const p2 = parseVersion(v2);
-
-   for (let i = 0; i < Math.max(p1.length, p2.length); i++) {
-     const n1 = p1[i] || 0;
-     const n2 = p2[i] || 0;
-
-     if (n1 > n2) return 1;
-     if (n1 < n2) return -1;
-   }
-
-   return 0;
+   return semver.compare(v1, v2);
  }

Ensure semver is imported at the top of the file.

🤖 Prompt for AI Agents
In `@src/main/package-sources/github-release-source.ts` around lines 388 - 406,
The current compareVersions implementation in compareVersions(v1: string, v2:
string) strips prerelease info and misorders semver pre-releases; replace its
body to use the semver library by calling semver.compare(v1, v2) (ensure semver
is imported at the top of the file), removing the manual parseVersion logic so
prerelease and build metadata are handled correctly.

"valid": "配置有效",
"fetchingVersions": "获取版本中...",
"nortOnlyNotice": "仅支持 nort 包(hagicode-*)"
"nortOnlyNotice": "仅支持 nort 包(hagicode-*)",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Possible typo in nortOnlyNotice key/value.
If the intended term isn’t “nort,” this may cause mismatches across locales or confusing UI text. Please confirm spelling and alignment with other locale keys.

🤖 Prompt for AI Agents
In `@src/renderer/i18n/locales/zh-CN/components.json` at line 265, The translation
entry "nortOnlyNotice" appears to contain a possible typo in its value ("nort");
verify the intended term by checking the matching key in other locale files and
any UI usage of the key "nortOnlyNotice", then correct the Chinese string to the
accurate term and ensure the same key/value spelling is used consistently across
all locale files and in code references (search for "nortOnlyNotice" to update
any mismatches).

Comment on lines +55 to +63
toast.success('许可证已更新', {
description: 'License updated successfully',
});
} else {
yield put(setError(result.error || 'Failed to save license'));

toast.error('许可证更新失败', {
description: result.error || 'Failed to save license',
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize toast strings instead of hard‑coded CN/EN mix.
These messages bypass i18n and will show Chinese even in English UI. Please route through translation keys.

Also applies to: 70-72

🤖 Prompt for AI Agents
In `@src/renderer/store/sagas/licenseSaga.ts` around lines 55 - 63, Replace the
hard-coded mixed CN/EN toast messages in licenseSaga.ts with i18n translation
lookups: call the app's translation function (e.g., i18n.t or the translator
used elsewhere) for both the toast title and description where toast.success and
toast.error are invoked (including the other occurrences noted at lines ~70-72),
and also use translated text when dispatching setError(result.error || '...') so
fallback messages come from translation keys rather than literal strings; update
the toast.success/toast.error calls and the setError fallback to use the
appropriate translation keys (e.g., 'license.updated.success' /
'license.updated.failure') so messages follow the app i18n pattern.

Comment on lines +32 to +48
function* loadSourceConfig() {
try {
yield put(setLoading(true));

const config: StoredPackageSourceConfig | null = yield call(
window.electronAPI.packageSource.getConfig
);

yield put(setCurrentConfig(config));
} catch (error) {
yield put(
setError(error instanceof Error ? error.message : 'Failed to load package source configuration')
);
} finally {
yield put(setLoading(false));
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential loading state race condition.

Both loadSourceConfig and loadAllSourceConfigs independently set loading to true/false. When called concurrently in initializePackageSourceSaga (lines 393-397), the first saga to complete will set loading: false while the other is still in progress.

Consider using separate loading flags or a counter-based approach:

♻️ Suggested fix using distinct flags
-    yield put(setLoading(true));
+    // Use a dedicated flag or track concurrent operations
+    yield put(setLoadingConfig(true));
     // ... saga logic ...
-    yield put(setLoading(false));
+    yield put(setLoadingConfig(false));

Alternatively, keep loading true until both complete by restructuring initializePackageSourceSaga.

Also applies to: 53-69

🤖 Prompt for AI Agents
In `@src/renderer/store/sagas/packageSourceSaga.ts` around lines 32 - 48, The
loading flag races because loadSourceConfig and loadAllSourceConfigs both
dispatch setLoading(true/false); change coordination so
initializePackageSourceSaga doesn't get a false from the first completer: either
(A) add a counter-based loading API (e.g., dispatch startLoading() increments a
reducer counter and finishLoading() decrements it, keep setLoading usage
replaced by startLoading()/finishLoading() inside loadSourceConfig and
loadAllSourceConfigs), or (B) add two distinct flags (e.g., setConfigLoading and
setAllConfigsLoading) and have loadSourceConfig call
setConfigLoading(true/false) and loadAllSourceConfigs call
setAllConfigsLoading(true/false); update the reducer to derive overall loading
if needed and update initializePackageSourceSaga to remove its own setLoading
toggles so loading remains true until both sagas finish. Ensure the change
references functions loadSourceConfig, loadAllSourceConfigs,
initializePackageSourceSaga, and the existing setLoading action so you update
all call sites consistently.

@@ -0,0 +1,223 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { Version } from '../../../main/version-manager';
import type { PackageSourceConfig, StoredPackageSourceConfig } from '../../../main/package-source-config-manager';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check where PackageSourceConfig is actually exported from
rg -n "export.*PackageSourceConfig" --type=ts

Repository: HagiCode-org/desktop

Length of output: 776


🏁 Script executed:

#!/bin/bash
# Check the contents of package-source-config-manager.ts to see all imports/exports
cat -n src/main/package-source-config-manager.ts

Repository: HagiCode-org/desktop

Length of output: 12461


🏁 Script executed:

#!/bin/bash
# Check the actual import statement in packageSourceSlice.ts
head -20 src/renderer/store/slices/packageSourceSlice.ts

Repository: HagiCode-org/desktop

Length of output: 666


Fix incorrect import path for PackageSourceConfig.

PackageSourceConfig is exported from src/main/package-sources/package-source.ts, not package-source-config-manager.ts. Update the import to:

import type { PackageSourceConfig } from '../../../main/package-sources/package-source';
import type { StoredPackageSourceConfig } from '../../../main/package-source-config-manager';

The current import statement will fail at compile time because package-source-config-manager.ts only exports StoredPackageSourceConfig and PackageSourceConfigManager.

🤖 Prompt for AI Agents
In `@src/renderer/store/slices/packageSourceSlice.ts` at line 3, Update the import
so PackageSourceConfig is imported from the correct module: replace the current
combined import that pulls PackageSourceConfig from
package-source-config-manager with two imports — import type {
PackageSourceConfig } from '../../../main/package-sources/package-source' and
import type { StoredPackageSourceConfig } from
'../../../main/package-source-config-manager' — ensuring the symbols
PackageSourceConfig and StoredPackageSourceConfig are referenced from their
proper files (package-source.ts for PackageSourceConfig,
package-source-config-manager for StoredPackageSourceConfig).

Comment on lines +52 to +54
folderPath: process.env.NODE_ENV === 'development'
? '/home/newbe36524/repos/newbe36524/pcode/Release/release-packages/'
: '',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Duplicate hardcoded developer-specific path.

This is the same hardcoded path as in package-source-config-manager.ts (line 282). Having this path in two places increases maintenance burden and will cause inconsistencies.

Consider extracting to a shared constant or using environment variables consistently.

🤖 Prompt for AI Agents
In `@src/renderer/store/slices/packageSourceSlice.ts` around lines 52 - 54, The
hardcoded developer-specific path assigned to folderPath in packageSourceSlice
(the ternary using process.env.NODE_ENV === 'development') is duplicated
elsewhere; extract that literal into a shared constant or environment variable
and import/use it in both packageSourceSlice and package-source-config-manager
instead of repeating the string. Create a single exported symbol (e.g.,
RELEASE_PACKAGES_PATH or RELEASE_PACKAGES_DIR) in a shared config/module and
replace the inline string in the folderPath ternary and the other occurrence so
both modules read the same value (or read from process.env.RELEASE_PACKAGES_DIR)
to avoid duplication and maintenance drift.

- Add jq -c parameter to generate compressed JSON format
- Add JSON validation step to ensure output validity
- Expected 20-40% file size reduction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@newbe36524 newbe36524 force-pushed the feature/compress-index-json branch from d7ba661 to 6fd956a Compare February 8, 2026 15:02
@newbe36524 newbe36524 merged commit 4577853 into main Feb 8, 2026
1 of 2 checks passed
@newbe36524 newbe36524 deleted the feature/compress-index-json branch February 8, 2026 15:03
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@openspec/changes/archive/2026-02-08-compress-client-index-json/proposal.md`:
- Line 88: Update the incorrect jq manual link referenced as "jq 手册 - compact
output" to use the current domain and correct anchor: replace the URL
"https://stedolan.github.io/jq/manual/#v4" with
"https://jqlang.org/manual/v1.5/#invoking-jq" so the `-c` / `--compact-output`
option points to the "Invoking jq" section on jqlang.org.

In
`@openspec/changes/archive/2026-02-08-remove-view-transition-animation/tasks.md`:
- Around line 1-200: Update the task checklist to reflect the completed
implementation: mark each step in the tasks list (1. 代码准备, 2. 修改 App.tsx 移除视图动画,
3. 清理未使用的导入, 4. 本地功能测试, 5. 代码审查与优化, 6. 提交变更, 7. 合并与清理) as completed (- [x]) and
tick off all acceptance criteria (功能完整性, 视觉一致性, 代码质量, 性能) after confirming the
changes described in the PR commit ("Remove view transition fade animation" —
removal of AnimatePresence and motion.div and replacement with a standard div
preserving sidebar margin transition); ensure the note about keeping
project-level dependency on motion/react remains and update the overall status
and estimated work/time fields accordingly.
🧹 Nitpick comments (5)
openspec/changes/archive/2026-02-08-remove-view-transition-animation/proposal.md (2)

1-60: Add UI mockups and user interaction flow diagrams.

This proposal describes a UI change (removing view transition animations), but lacks visual design documentation. Based on learnings, proposal.md files for UI changes should include:

  • UI mockups showing before/after states
  • Mermaid sequence diagrams illustrating user interaction flows
  • Reference to PROPOSAL_DESIGN_GUIDELINES.md

Consider adding:

  1. A simple before/after comparison (even text-based timeline showing animation vs. instant transition)
  2. A Mermaid sequence diagram showing: User clicks sidebar → View switch → Content display
  3. A reference section pointing to PROPOSAL_DESIGN_GUIDELINES.md
📊 Example Mermaid diagram structure

Add after line 60 in the "解决方案" section:

### 用户交互流程

```mermaid
sequenceDiagram
    participant User
    participant Sidebar
    participant App
    participant View
    
    User->>Sidebar: 点击导航项
    Sidebar->>App: 触发视图切换
    Note over App: 无动画延迟
    App->>View: 立即渲染新视图
    View-->>User: 内容即时显示

```

设计参考

详细的 UI 设计指南请参考 PROPOSAL_DESIGN_GUIDELINES.md

</details>

Based on learnings: Structure proposal.md with Why, What Changes, UI Design Changes (if applicable), Code Flow Changes (if applicable), and Impact sections; Include UI mockups, Mermaid sequence diagrams for user interaction flows, and reference PROPOSAL_DESIGN_GUIDELINES.md in proposal.md for UI changes.

---

`102-107`: **Fix code block formatting.**

The fenced code block on line 102 is missing a language specifier, causing markdown linter warnings. Since this appears to be a bulleted list rather than code, consider using standard markdown list formatting instead.


<details>
<summary>📝 Proposed fix</summary>

```diff
 ### 依赖清理
 
 `motion/react` 导入在移除视图动画后可能仍然被 `SidebarNavigation.tsx` 使用(该组件使用了大量 motion 动画)。需要检查是否仍有其他组件使用该库:
 
--
-- `SidebarNavigation.tsx` - 仍使用 `motion` 和 `AnimatePresence`(用于侧边栏折叠动画、导航项动画等)
--
+- `SidebarNavigation.tsx` - 仍使用 `motion` 和 `AnimatePresence`(用于侧边栏折叠动画、导航项动画等)
 
 **结论**:不应从 `App.tsx` 移除 `motion/react` 导入,因为该项目仍在其他组件中使用。
.github/workflows/sync-azure-storage.yml (1)

179-187: Compression and validation logic looks good.

The use of jq -c . for compact JSON output and jq empty for validation are both correct approaches. The error handling properly uses GitHub Actions error annotations and exits with code 1 on failure.

One minor inconsistency: Line 181 has a Chinese comment ("验证生成的 JSON 格式有效") while all other comments in the workflow are in English. Consider using English for consistency:

🔧 Suggested fix for comment consistency
-          # 验证生成的 JSON 格式有效
+          # Validate the generated JSON format
           echo "Validating index.json..."
openspec/changes/archive/2026-02-08-compress-client-index-json/tasks.md (2)

22-24: Update completed task checkboxes.

Based on learnings, tasks should be marked [x] as they are completed. Since the PR is already open with the workflow changes implemented, tasks 1 through 8 (at minimum) appear to be complete and their checkboxes should be updated to reflect progress.

For example, Task 1's verification items should be checked:

 **验证**:
-- [ ] 修改后的命令语法正确
-- [ ] YAML 缩进保持一致
+- [x] 修改后的命令语法正确
+- [x] YAML 缩进保持一致

58-66: Minor inconsistency in validation message.

The success message in Task 3 (line 65) shows "JSON validation passed", but Task 4 (line 87) and the actual workflow implementation use "index.json validation passed". Consider aligning these for consistency.

🔧 Suggested fix
 if ! jq empty index.json; then
   echo "::error::Generated index.json is not valid JSON"
   exit 1
 fi
-echo "JSON validation passed"
+echo "index.json validation passed"


## 参考资料

- [jq 手册 - compact output](https://stedolan.github.io/jq/manual/#v4)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

jq manual compact output -c option anchor link

💡 Result:

jq’s compact output option is -c / --compact-output (“put each JSON object on a single line”, i.e., no pretty-print whitespace). [1]

A stable section link in the official manual where -c is documented (under Invoking jq) is:

https://jqlang.org/manual/v1.5/#invoking-jq

[1]

(There doesn’t appear to be a dedicated per-option anchor for --compact-output on that page; it’s listed as a bullet within the “Invoking jq” section.) [1]

Example:

jq -c '.'

[2]

Sources: jq manual v1.5 [1], jq(1) man page (Arch Linux) [2].


Fix the jq manual link anchor and domain.

The link uses an incorrect anchor (#v4) that does not exist in the jq manual. The correct reference should point to https://jqlang.org/manual/v1.5/#invoking-jq, where the -c / --compact-output option is documented under the "Invoking jq" section. Additionally, the domain appears to have moved from stedolan.github.io to jqlang.org.

🤖 Prompt for AI Agents
In `@openspec/changes/archive/2026-02-08-compress-client-index-json/proposal.md`
at line 88, Update the incorrect jq manual link referenced as "jq 手册 - compact
output" to use the current domain and correct anchor: replace the URL
"https://stedolan.github.io/jq/manual/#v4" with
"https://jqlang.org/manual/v1.5/#invoking-jq" so the `-c` / `--compact-output`
option points to the "Invoking jq" section on jqlang.org.

Comment on lines +1 to +200
# 实施任务清单

移除视图切换淡入淡出动画的有序任务列表。

---

## 任务列表

### 1. 代码准备
**状态:** `pending` | **预计工作量:** 5 分钟

- [ ] 创建功能分支:`git checkout -b feature/remove-view-transition-animation`
- [ ] 验证当前分支:确保在 `feature/multiplerelease` 基础上创建

---

### 2. 修改 App.tsx 移除视图动画
**状态:** `pending` | **预计工作量:** 10 分钟
**文件:** `src/renderer/App.tsx`

- [ ] 定位主内容区域的 `AnimatePresence` 和 `motion.div` 组件(约第 73-91 行)
- [ ] 移除 `AnimatePresence` 包装标签
- [ ] 将 `motion.div` 替换为普通 `div` 元素
- [ ] 删除以下动画属性:
- `key={currentView}`
- `initial={{ opacity: 0, y: 20, scale: 0.98 }}`
- `animate={{ opacity: 1, y: 0, scale: 1 }}`
- `exit={{ opacity: 0, y: -20, scale: 0.98 }}`
- `transition={{ duration: 0.4, ease: [0.25, 0.1, 0.25, 1] }}`
- [ ] 保留容器样式类:`className="container mx-auto px-4 py-8 min-h-screen"`
- [ ] 保留外层 div 的边距过渡:`className="ml-64 transition-all duration-500 ease-out"`

**预期变更:**
```tsx
<!-- 之前 -->
<AnimatePresence mode="wait">
<motion.div
key={currentView}
initial={{ opacity: 0, y: 20, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -20, scale: 0.98 }}
transition={{ duration: 0.4, ease: [0.25, 0.1, 0.25, 1] }}
className="container mx-auto px-4 py-8 min-h-screen"
>
{currentView === 'system' && <SystemManagementView />}
...
</motion.div>
</AnimatePresence>

<!-- 之后 -->
<div className="container mx-auto px-4 py-8 min-h-screen">
{currentView === 'system' && <SystemManagementView />}
...
</div>
```

---

### 3. 清理未使用的导入(可选)
**状态:** `pending` | **预计工作量:** 5 分钟
**文件:** `src/renderer/App.tsx`

- [ ] 检查 `AnimatePresence` 和 `motion` 是否在文件中还有其他用途
- [ ] 如无其他用途,从导入语句中移除:
```diff
- import { AnimatePresence, motion } from 'motion/react';
+ import { AnimatePresence, motion } from 'motion/react'; // 暂时保留,SidebarNavigation 仍在使用
```
- [ ] **注意**:由于 `SidebarNavigation.tsx` 仍使用 `motion/react`,项目级别不应移除依赖,仅清理当前文件的导入即可

---

### 4. 本地功能测试
**状态:** `pending` | **预计工作量:** 15 分钟

- [ ] 启动开发服务器:`npm run dev` 或 `pnpm dev`
- [ ] 测试所有视图切换:
- [ ] System Management → Version Management
- [ ] Version Management → License Management
- [ ] License Management → System Management
- [ ] 所有其他视图组合
- [ ] 验证视图内容立即显示(无淡入淡出延迟)
- [ ] 验证无视觉闪烁或布局跳动
- [ ] 验证侧边栏折叠/展开时的边距过渡仍然正常

---

### 5. 代码审查与优化
**状态:** `pending` | **预计工作量:** 5 分钟

- [ ] 检查代码格式是否符合项目规范
- [ ] 确认无 console 错误或警告
- [ ] 验证 TypeScript 类型检查通过:`npm run type-check` 或类似命令

---

### 6. 提交变更
**状态:** `pending` | **预计工作量:** 5 分钟

- [ ] 添加变更到暂存区:`git add src/renderer/App.tsx`
- [ ] 创建提交:
```
git commit -m "feat: remove view transition fade animation

- Remove AnimatePresence and motion.div wrapper from main content area
- Replace with standard div for instant view switching
- Preserve sidebar margin transition effect

This change improves perceived responsiveness when navigating between
views in the sidebar navigation."
```

---

### 7. 合并与清理
**状态:** `pending` | **预计工作量:** 5 分钟

- [ ] 推送功能分支:`git push origin feature/remove-view-transition-animation`
- [ ] 创建 Pull Request 到 `feature/multiplerelease` 分支
- [ ] 在 PR 描述中引用此 OpenSpec 提案
- [ ] 等待代码审查和合并
- [ ] 合并后删除功能分支

---

## 验收检查清单

在标记任务完成前,确认以下所有项目:

### 功能完整性
- [ ] 所有四个视图(system、web、version、license)可正常切换
- [ ] 视图切换时内容立即显示
- [ ] 无 0.4 秒动画延迟

### 视觉一致性
- [ ] 侧边栏宽度变化时的过渡效果保持正常
- [ ] 侧边栏导航项活跃状态指示器正常
- [ ] 无页面闪烁或布局错位

### 代码质量
- [ ] 无 TypeScript 类型错误
- [ ] 无运行时控制台错误
- [ ] 代码格式符合项目规范

### 性能
- [ ] 视图切换响应主观感觉即时(< 100ms)

---

## 回滚计划

如果需要回滚此变更:

1. **紧急回滚**:使用 `git revert <commit-hash>` 撤销提交
2. **手动回滚**:恢复 `AnimatePresence` 和 `motion.div` 代码结构
3. **验证**:确认动画效果恢复正常后重新部署

---

## 依赖关系

```
┌─────────────────┐
│ 1. 代码准备 │
└────────┬────────┘
┌─────────────────┐
│ 2. 修改 App.tsx │
└────────┬────────┘
┌─────────────────┐
│ 3. 清理导入 │ (可选)
└────────┬────────┘
┌─────────────────┐
│ 4. 本地测试 │
└────────┬────────┘
┌─────────────────┐
│ 5. 代码审查 │
└────────┬────────┘
┌─────────────────┐
│ 6. 提交变更 │
└────────┬────────┘
┌─────────────────┐
│ 7. 合并与清理 │
└─────────────────┘
```

**总预计工作量:** 45-50 分钟
**风险等级:** 低
**可并行任务:** 无(任务需顺序执行)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update task completion status to reflect implementation progress.

According to the PR commit message, the view transition animation removal has been completed ("Remove view transition fade animation — removed AnimatePresence and motion.div wrapper from main content area, replaced with a standard div; preserved sidebar margin transition."). However, all tasks in this file remain unchecked with "pending" status.

Based on learnings, tasks should be checked off sequentially as implementation progresses, and all tasks should be marked - [x] after implementation work is complete and confirmed. Please update the checkboxes to accurately reflect the current state of implementation.

Based on learnings: "Track implementation progress sequentially in tasks.md with checkboxes, completing one task at a time before moving to the next" and "Update every task in tasks.md to - [x] after all implementation work is complete and confirmed."

🤖 Prompt for AI Agents
In
`@openspec/changes/archive/2026-02-08-remove-view-transition-animation/tasks.md`
around lines 1 - 200, Update the task checklist to reflect the completed
implementation: mark each step in the tasks list (1. 代码准备, 2. 修改 App.tsx 移除视图动画,
3. 清理未使用的导入, 4. 本地功能测试, 5. 代码审查与优化, 6. 提交变更, 7. 合并与清理) as completed (- [x]) and
tick off all acceptance criteria (功能完整性, 视觉一致性, 代码质量, 性能) after confirming the
changes described in the PR commit ("Remove view transition fade animation" —
removal of AnimatePresence and motion.div and replacement with a standard div
preserving sidebar margin transition); ensure the note about keeping
project-level dependency on motion/react remains and update the overall status
and estimated work/time fields accordingly.

newbe36524 added a commit that referenced this pull request Feb 8, 2026
## 概述
- 在 `sync-azure-storage.yml` 工作流中添加 JSON 压缩
- 使用 `jq -c` 参数生成紧凑格式的 `index.json`

## 变更内容
- 修改 jq 输出参数,添加 `-c` 选项
- 添加 JSON 有效性验证步骤

## 预期收益
- 减少 index.json 文件体积 20-40%
- 提升客户端加载性能

## 验证
- [x] 本地测试 jq -c 输出正确
- [x] JSON 格式验证通过

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added HTTP index-based package source support for discovering and
downloading packages from custom servers.
* Introduced global license management with configuration persistence
and sync status tracking.
* Enabled multi-source package management, supporting local folders,
GitHub Releases, and HTTP index sources.

* **Improvements**
  * Removed animated view transitions for instantaneous page switching.
* Enhanced data integrity with JSON validation in configuration
workflows.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant