Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
08db54f
feat(macos): refine workbench presentation
arkleselect Aug 29, 2026
23a5f73
feat(git): 完善源代码管理与引用工作流
mirakyux Aug 29, 2026
19a835b
feat(vcs): 完善源代码管理交互与推送流程
mirakyux Aug 31, 2026
5c926cf
fix(vcs): 门禁修复
mirakyux Aug 31, 2026
ca3c0c8
fix(vcs): 门禁修复
mirakyux Aug 31, 2026
244bc9c
fix(vcs): 修复选择提交与远程操作一致性
mirakyux Aug 31, 2026
4bdefaf
fix(macos): restore switcher popover integration
xiaoyumuxi Aug 31, 2026
07ddd3e
fix(vcs): 隔离并发推送测试目录
mirakyux Aug 31, 2026
86f8120
test(vcs): 覆盖部分暂存文件完整差异
mirakyux Aug 31, 2026
a3f1a97
UI优化
arkleselect Aug 31, 2026
2be39a1
优化文件树右键菜单样式与子菜单交互
arkleselect Aug 31, 2026
f45c5f2
“祝福pr检查错误‘’
arkleselect Aug 31, 2026
3778df9
feat(vcs): 完善推送预览校验与拉取恢复流程
mirakyux Sep 1, 2026
4cdc446
fix(macos): serialize run entry activation with module teardown
1lck Sep 1, 2026
1a6e82e
fix(vcs): 修复提交与远程操作审查问题
mirakyux Sep 1, 2026
b3695eb
Merge pull request #375 from 1lck/feat/fix-run-entrypoint-lifecycle
1lck Sep 1, 2026
b62facf
fix(ci): make Swift test watchdog stall-aware
1lck Sep 1, 2026
1cb782c
fix(ci): sample the full descendant tree on Swift runner stall
1lck Sep 1, 2026
e3e4719
Merge pull request #371 from 1lck/feat/ci-fix-swift-test-watchdog
1lck Sep 1, 2026
5698718
Merge branch 'preview' into codex/restore-switcher-ui-preview
1lck Sep 1, 2026
70140aa
Merge branch 'preview' into feature/vcs
1lck Sep 1, 2026
08d96bc
Merge branch 'preview' into codex/frontend
1lck Sep 1, 2026
e8d67e3
右键菜单样式添加(一期)
arkleselect Sep 1, 2026
6865337
Merge remote codex/frontend changes
arkleselect Sep 1, 2026
a6e9ae4
Merge pull request #368 from 1lck/codex/restore-switcher-ui-preview
1lck Sep 1, 2026
91b3a04
fix(vcs): 修复跨平台 Git 工作流一致性问题
mirakyux Sep 1, 2026
0ca373d
fix(vcs): 原子创建远程引用工作树
mirakyux Sep 1, 2026
79598ec
fix(vcs): 修正引用数量与分支同步状态
mirakyux Sep 1, 2026
e1cd660
fix(run): launch Maven test mains with test classpath
1lck Sep 1, 2026
678cc22
Merge branch 'preview' into feature/vcs
1lck Sep 1, 2026
ca54e09
Merge pull request #365 from mirakyux/feature/vcs
1lck Sep 1, 2026
6c9c87f
Merge branch 'preview' into feat/fix-maven-test-main-classpath
1lck Sep 1, 2026
fe58a18
Merge pull request #384 from 1lck/feat/fix-maven-test-main-classpath
1lck Sep 1, 2026
c7d2689
fix(windows): normalize verbatim paths for JDTLS changes
1lck Sep 1, 2026
bcf506a
fix(windows): handle case-insensitive UNC prefixes
1lck Sep 1, 2026
ad2bdb7
Merge branch 'preview' into feat/fix-jdtls-verbatim-path
1lck Sep 1, 2026
c564175
Merge pull request #389 from 1lck/feat/fix-jdtls-verbatim-path
1lck Sep 1, 2026
e7064c3
feat: automate issue claim comments
1lck Sep 1, 2026
8446912
Merge branch 'preview' into codex/frontend
1lck Sep 2, 2026
b9c2d04
Merge pull request #328 from 1lck/codex/frontend
1lck Sep 2, 2026
8583caa
docs: improve release guidance and macOS install help
1lck Sep 2, 2026
ca8f1dd
Merge branch 'preview' into feat/release-user-facing-guidance
1lck Sep 2, 2026
2e54493
Merge pull request #400 from 1lck/feat/release-user-facing-guidance
1lck Sep 2, 2026
ba3725b
Merge main into preview before promotion
1lck Sep 2, 2026
e696826
Merge pull request #402 from 1lck/feat/sync-main-into-preview-pr401
1lck Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 63 additions & 4 deletions .agents/skills/release-lithe/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,26 @@ keep their existing workflow unless the task explicitly includes them.
Do not invent features, compatibility claims, download assets, or fixes.
- Treat the release notes as a required release artifact. A missing or empty
file blocks the release; never fall back to GitHub-generated notes.
- Keep user-facing language focused on outcomes. Do not publish a raw commit or
pull-request list as the release description.
- Keep the release description short, plain-language, and focused on what users
can do or notice after updating. Write from the user's point of view: explain
the problem solved or workflow improved before naming the implementation.
- Include all meaningful user-facing changes, grouping related changes when that
makes the release easier to scan. Do not impose a fixed number of highlights.
Each highlight should answer “What does this change mean for me?” in one
sentence. Avoid internal names and jargon such as
implementation class names, protocol phases, rendering backends, or test
terminology. Mention a technical name only when a user must recognize it in
the UI or follow a setup step, and explain it in plain language.
- Do not publish a raw commit or pull-request list as the release description.
- Every stable release description must include the macOS Gatekeeper recovery
note below in both languages. Keep the command exactly as written and remind
users to use it only for an app downloaded from a source they trust:

Chinese:
`xattr -dr com.apple.quarantine /Applications/Lithe.app`

English:
`xattr -dr com.apple.quarantine /Applications/Lithe.app`

## Use the bilingual structure

Expand All @@ -34,17 +52,58 @@ this order:
6. English counterparts for any optional product-area groupings.
7. `### Upgrade instructions` and `### Compatibility and known issues`.
8. The equivalent English comparison link.
9. At the very bottom, `### 贡献者` followed by `### Contributors`, with
equivalent contributor names or GitHub profile links in both languages.

The download sections cover the project page, both macOS architectures,
Windows x64, and the complete Release Assets page. Use versioned asset URLs
that match the packaging workflows. Upgrade instructions cover macOS DMG,
Homebrew, and Windows. Compatibility sections include platform preview or
signing limitations only when they actually apply to that version.
Homebrew, and Windows. The compatibility section must include the Gatekeeper
note, plus platform preview or signing limitations only when they actually
apply to that version.

## User-facing writing checklist

- Lead with the result: “启动项目更快”“终端输出更流畅”“打开文件不再反复报错”.
- Replace implementation descriptions with the visible effect on editing,
running, debugging, Git, databases, or updates.
- Keep each bullet to one idea and one sentence; combine related fixes when the
user impact is the same.
- Use everyday words. If a technical term is unavoidable, add a short
explanation the first time it appears.
- Do not claim performance numbers, compatibility, security, or fixes unless
the target commit and release checks verify them.
- Include the Gatekeeper recovery instructions as a small, actionable note:

Chinese: “如果 macOS 提示无法打开 Lithe.app,请在‘应用程序’中按住 Control
点按应用并选择‘打开’;如果仍被阻止,可在终端执行
`xattr -dr com.apple.quarantine /Applications/Lithe.app`。仅对可信来源的应用使用。”

English: “If macOS says it cannot open Lithe.app, Control-click it in
Applications and choose Open. If it is still blocked, run
`xattr -dr com.apple.quarantine /Applications/Lithe.app` in Terminal. Use
this only for an app from a source you trust.”

Use the most recent stable file under `docs/releases/` as the formatting
reference, but verify every statement and URL for the new version instead of
copying stale details.

## Contributors

- Add a contributor list at the bottom of every stable release description,
after the English comparison link.
- Build the list from the commits and merged pull requests between the
previous stable tag and the new tag. Verify names and profile links against
GitHub before publishing; do not infer identities from an email address.
- Include human contributors who made code, documentation, design, testing, or
release work relevant to the version. Exclude automation accounts such as
`github-actions[bot]` unless the release explicitly needs to credit them.
- Keep the list short and readable. Do not include a raw commit log or every
incidental merge author.
- Use the same people and links in the Chinese and English sections. If no
human contribution can be verified for a release, write a brief equivalent
sentence instead of leaving the section empty.

## Validate before publishing

- Confirm the version uses `MAJOR.MINOR.PATCH`, the filename is exactly
Expand Down
20 changes: 19 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 16 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ let package = Package(
.executable(name: "LitheOfficialPluginVerifier", targets: ["LitheOfficialPluginVerifier"])
],
dependencies: [
.package(url: "https://github.com/migueldeicaza/SwiftTerm.git", exact: "1.15.0")
.package(url: "https://github.com/migueldeicaza/SwiftTerm.git", exact: "1.15.0"),
.package(url: "https://github.com/swiftlang/swift-testing.git", exact: "6.2.4")
],
targets: [
.target(
Expand Down Expand Up @@ -118,7 +119,7 @@ let package = Package(
),
.testTarget(
name: "LitheTests",
dependencies: ["Lithe", "LitheModuleAPI", "LitheApplicationKernel", "LitheCoreContracts", "LitheGitModule", "LitheDatabaseModule", "LitheAIAssistanceModule", "LitheLanguageIntelligenceModule", "LitheGoSupportModule"],
dependencies: ["Lithe", "LitheModuleAPI", "LitheApplicationKernel", "LitheCoreContracts", "LitheGitModule", "LitheDatabaseModule", "LitheAIAssistanceModule", "LitheLanguageIntelligenceModule", "LitheGoSupportModule", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheTests",
resources: [
.copy("Fixtures")
Expand All @@ -129,69 +130,69 @@ let package = Package(
),
.testTarget(
name: "LitheApplicationKernelTests",
dependencies: ["LitheModuleAPI", "LitheApplicationKernel"],
dependencies: ["LitheModuleAPI", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheApplicationKernelTests",
swiftSettings: [
.swiftLanguageMode(.v6)
]
),
.testTarget(
name: "LitheTerminalModuleTests",
dependencies: ["LitheTerminalModule"],
dependencies: ["LitheTerminalModule", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheTerminalModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheAIAssistanceModuleTests",
dependencies: ["LitheAIAssistanceModule", "LitheApplicationKernel", "LitheCoreContracts"],
dependencies: ["LitheAIAssistanceModule", "LitheApplicationKernel", "LitheCoreContracts", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheAIAssistanceModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheSearchModuleTests",
dependencies: ["LitheSearchModule", "LitheApplicationKernel"],
dependencies: ["LitheSearchModule", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheSearchModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheLocalHistoryModuleTests",
dependencies: ["LitheLocalHistoryModule", "LitheApplicationKernel"],
dependencies: ["LitheLocalHistoryModule", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheLocalHistoryModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheGitModuleTests",
dependencies: ["LitheGitModule", "LitheApplicationKernel"],
dependencies: ["LitheGitModule", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheGitModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheDatabaseModuleTests",
dependencies: ["LitheDatabaseModule", "LitheApplicationKernel"],
dependencies: ["LitheDatabaseModule", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheDatabaseModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheLanguageIntelligenceModuleTests",
dependencies: ["LitheLanguageIntelligenceModule", "LitheApplicationKernel", "LitheCoreContracts"],
dependencies: ["LitheLanguageIntelligenceModule", "LitheApplicationKernel", "LitheCoreContracts", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheLanguageIntelligenceModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheDebugModuleTests",
dependencies: ["LitheDebugModule", "LitheApplicationKernel"],
dependencies: ["LitheDebugModule", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheDebugModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheExecutionModuleTests",
dependencies: ["LitheExecutionModule", "LitheApplicationKernel", "LitheCoreContracts"],
dependencies: ["LitheExecutionModule", "LitheApplicationKernel", "LitheCoreContracts", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheExecutionModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
.testTarget(
name: "LitheWorkspaceModuleTests",
dependencies: ["LitheWorkspaceModule", "LitheApplicationKernel"],
dependencies: ["LitheWorkspaceModule", "LitheApplicationKernel", .product(name: "Testing", package: "swift-testing")],
path: "macos/Tests/LitheWorkspaceModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
),
Expand All @@ -200,7 +201,8 @@ let package = Package(
dependencies: [
"LitheGoSupportModule",
"LitheApplicationKernel",
"LitheLanguageIntelligenceModule"
"LitheLanguageIntelligenceModule",
.product(name: "Testing", package: "swift-testing")
],
path: "Plugins/mac/Official/GoSupport/Tests/LitheGoSupportModuleTests",
swiftSettings: [.swiftLanguageMode(.v6)]
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ brew install --cask 1lck/lithe/lithe
brew upgrade --cask lithe
```

### If macOS says it cannot open `Lithe.app`

If macOS says that Apple cannot verify whether `Lithe.app` contains malware, the manually downloaded package may not yet be notarized by Apple. First confirm that the app came from the trusted [GitHub Releases](https://github.com/1lck/Lithe-IDEA/releases/latest), then use one of these methods:

1. In **Applications**, Control-click `Lithe.app`, choose **Open**, and choose **Open** again in the confirmation dialog.
2. If macOS still blocks it, open **System Settings > Privacy & Security**, click **Open Anyway** next to the security warning, and launch the app again.
3. You can also remove the quarantine attribute in Terminal:

```bash
xattr -dr com.apple.quarantine /Applications/Lithe.app
```

Only use these steps for an app whose source you trust. Homebrew installations usually do not require manual quarantine removal.

Java features require JDK 17 or newer. Release packages include Eclipse JDT Language Server, so JDTLS does not need to be installed separately.

## Architecture Overview
Expand Down
14 changes: 14 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@ brew install --cask 1lck/lithe/lithe
brew upgrade --cask lithe
```

### macOS 提示“无法打开 Lithe.app”

如果 macOS 提示“Apple 无法验证 Lithe.app 是否包含可能危害 Mac 安全或泄漏隐私的恶意软件”,通常是因为手动下载的安装包尚未经过 Apple 公证。请先确认应用来自可信的 [GitHub Releases](https://github.com/1lck/Lithe-IDEA/releases/latest),然后选择以下任一方式:

1. 在“应用程序”中按住 Control 键点按 `Lithe.app`,选择“打开”,再在确认对话框中选择“打开”。
2. 如果仍被阻止,打开“系统设置 > 隐私与安全性”,在安全性提示旁点按“仍要打开”,然后再次启动应用。
3. 也可以在终端中移除下载文件的隔离标记:

```bash
xattr -dr com.apple.quarantine /Applications/Lithe.app
```

上述命令只应对你确认来源可靠的应用使用;通过 Homebrew 安装通常不需要手动执行这些步骤。

Java 功能需要 JDK 17 或更高版本。正式安装包已包含 Eclipse JDT Language Server,无需单独安装 JDTLS。

## 架构概览
Expand Down
1 change: 1 addition & 0 deletions macos/Sources/Lithe/LitheApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ private struct SettingsWindowAccessor: NSViewRepresentable {
guard let window = view.window else { return }
reference.window = window
window.title = title
window.level = .floating
let windowAppearance = themePreference.windowAppearance
if window.appearance?.name != windowAppearance?.name {
window.appearance = windowAppearance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,64 @@
import Foundation

private enum WorkbenchNotificationTiming {
static let displayDuration: Duration = .seconds(4)
static let maximumVisibleCount = 3
}

extension AppModel {
func showNotification(_ message: String) {
let notification = WorkbenchNotification(message: message)
notifications.insert(notification, at: 0)
if notifications.count > 100 {
notifications.removeLast(notifications.count - 100)
}

activeNotifications.append(notification)
if activeNotifications.count > WorkbenchNotificationTiming.maximumVisibleCount {
let removed = activeNotifications.removeFirst()
cancelNotificationDismissal(for: removed.id)
}
notificationMessage = message
Task { @MainActor [weak self] in
try? await Task.sleep(for: .seconds(2))
if self?.notifications.first?.id == notification.id,
self?.notificationMessage == message {
self?.notificationMessage = nil
if areNotificationsHovered {
notificationRemainingDurations[notification.id] = WorkbenchNotificationTiming.displayDuration
} else {
scheduleNotificationDismissal(
for: notification,
after: WorkbenchNotificationTiming.displayDuration
)
}
}

func setNotificationStackHovered(_ isHovered: Bool) {
guard areNotificationsHovered != isHovered else { return }
areNotificationsHovered = isHovered

if isHovered {
let now = ContinuousClock().now
for notification in activeNotifications {
if let deadline = notificationDismissalDeadlines.removeValue(forKey: notification.id) {
notificationRemainingDurations[notification.id] = now < deadline
? now.duration(to: deadline)
: .zero
}
notificationDismissalTasks.removeValue(forKey: notification.id)?.cancel()
}
} else {
for notification in activeNotifications {
let remaining = notificationRemainingDurations.removeValue(forKey: notification.id)
?? WorkbenchNotificationTiming.displayDuration
scheduleNotificationDismissal(for: notification, after: remaining)
}
}
}

func dismissNotification(_ id: UUID) {
guard activeNotifications.contains(where: { $0.id == id }) else { return }
activeNotifications.removeAll { $0.id == id }
cancelNotificationDismissal(for: id)
notificationMessage = activeNotifications.last?.message
}

func markAllNotificationsRead() {
for index in notifications.indices {
notifications[index].isRead = true
Expand All @@ -25,6 +67,40 @@ extension AppModel {

func clearNotifications() {
notifications.removeAll()
activeNotifications.removeAll()
for task in notificationDismissalTasks.values {
task.cancel()
}
notificationDismissalTasks.removeAll()
notificationDismissalDeadlines.removeAll()
notificationRemainingDurations.removeAll()
areNotificationsHovered = false
notificationMessage = nil
}

private func scheduleNotificationDismissal(
for notification: WorkbenchNotification,
after duration: Duration
) {
notificationDismissalTasks[notification.id]?.cancel()
let deadline = ContinuousClock().now.advanced(by: duration)
notificationDismissalDeadlines[notification.id] = deadline
notificationDismissalTasks[notification.id] = Task { @MainActor [weak self] in
do {
try await Task.sleep(for: duration)
} catch {
return
}
guard let self,
!self.areNotificationsHovered,
self.notificationDismissalDeadlines[notification.id] == deadline else { return }
self.dismissNotification(notification.id)
}
}

private func cancelNotificationDismissal(for id: UUID) {
notificationDismissalTasks.removeValue(forKey: id)?.cancel()
notificationDismissalDeadlines.removeValue(forKey: id)
notificationRemainingDurations.removeValue(forKey: id)
}
}
Loading
Loading