Skip to content
This repository was archived by the owner on Sep 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ kururu opens only a few kinds of connection, and each one belongs to a visible f

6. **Command bar destinations and scripts you configure.** Opening a saved website or web search hands its expanded URL to macOS. The destination can include the query, clipboard text or selected text when you put those placeholders in its configuration; the receiving website can receive those values. Saved scripts execute the chosen local file with the matched query argument and can make their own network requests. Scripts produce live results while you type a matching query after a brief pause, so execution does not always wait for Return. Local search and ranking do not make these external destinations private or prevent a configured script from communicating.

7. **Environment update checks, only when you ask.** Local diagnostics always identify recognized installation sources, including macOS and Apple developer tools, without a release request. “Check for updates” requests stable release metadata from `api.github.com` for Bun and uv found at recognized standalone locations. Requests contain the repository name, not local paths or installed versions. Recognized Homebrew tools use local `brew info` and `brew outdated` results. Homebrew metadata may need a separate refresh in Packages. Upgrade results are shown only for supported user installations; bundled tools, version-manager installations, wrappers, unconfirmed sources, pinned versions and Homebrew dependencies receive no upgrade prompts. This page can invoke the existing Homebrew upgrade flow for eligible packages after confirmation. Standalone tools link to upgrade instructions. It does not change version pins or inspect project compatibility. The configuration tab checks known user and system file locations and, when selected, a project directory. It lists paths without reading file contents or claiming that the files are active.

### Independent storage

Release and development builds use separate application identities. Existing upstream preferences, clipboard history, notes and shelf files are not read automatically. Explicit settings-file import accepts portable preferences and excludes machine restoration records and login registration. macOS permissions are granted separately to the new identity.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Adjust volume per app, switch audio outputs and mute microphones. Access display

### Apps and maintenance

Find cleanup candidates, inspect app leftovers with the uninstaller, and review installed Homebrew packages. Upgrade packages you explicitly installed, and inspect local development tools in the environment page. Homebrew may also upgrade dependencies as part of a requested package upgrade.
Find cleanup candidates, inspect app leftovers with the uninstaller, and review installed Homebrew packages. Upgrade packages you explicitly installed, and inspect local development tools in the environment page. It distinguishes bundled tools from recognized user installations. Optional update hints cover standalone Bun and uv and explicitly installed Homebrew tools, excluding pinned versions and dependencies. The environment page uses compact source groups and expandable paths, and lists discovered configuration files with their full locations. Eligible Homebrew tools use the existing package upgrade flow directly; standalone tools link to upgrade instructions. Homebrew may also upgrade dependencies as part of a requested package upgrade.

### Your choice of entry point

Expand All @@ -77,7 +77,7 @@ Feature availability depends on your selections, macOS permissions and hardware.

Preferences, clipboard history, notes and shelf data stay in kururu's own local storage. Screen text recognition uses Apple's on-device Vision framework. There is no account, subscription or automatic telemetry.

Network access belongs to specific actions: an internet speed test, Homebrew operations, or websites and scripts you configure in the command bar. Feedback is prepared locally for you to review and copy; opening the issue page does not submit it.
Network access belongs to specific actions: an internet speed test, Homebrew operations, checking tool releases on GitHub, or websites and scripts you configure in the command bar. Feedback is prepared locally for you to review and copy; opening the issue page does not submit it.

Grant macOS permissions for the features you use and review them in **System Settings → Privacy & Security**. See the [data and privacy documentation](.github/CONTRIBUTING.md#privacy) for storage details and the scope of network access.

Expand Down
6 changes: 4 additions & 2 deletions README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ kururu 分叉自 [Vorssaint](https://github.com/vorssaint/vorssaint-utils),目

### 应用与维护

查找可清理的项目,用卸载器检查应用残留,并查看已安装的 Homebrew 软件包。升级自己主动安装的软件包,在环境页检查本地开发工具。升级指定软件包时,Homebrew 也可能连带升级其依赖。
查找可清理的项目,用卸载器检查应用残留,并查看已安装的 Homebrew 软件包。升级自己主动安装的软件包,在环境页区分系统附带工具与已识别的后装工具。手动检查更新只提示独立安装的 Bun、uv 和主动安装的 Homebrew 工具,跳过固定版本和依赖包。升级指定软件包时,Homebrew 也可能连带升级其依赖。

本地环境页支持在工具列表与配置文件列表之间切换。工具按安装来源分组,路径详情按需展开;配置文件显示完整位置,可在 Finder 中定位。Homebrew 更新可直接沿用软件包页的升级流程,独立安装的 Bun、uv 提供明确标注的升级说明入口。

### 选择适合你的入口

Expand All @@ -77,7 +79,7 @@ kururu 分叉自 [Vorssaint](https://github.com/vorssaint/vorssaint-utils),目

偏好设置、剪贴板历史、便条和暂存架数据保存在 kururu 自己的本地存储中。屏幕文字识别使用 Apple 的设备端 Vision 框架。无需账号,没有订阅,也不自动收集遥测数据。

网络访问对应具体操作:网络测速、Homebrew 操作,或你在命令栏中配置的网站和脚本。反馈内容先在本地生成,供你检查和复制;打开 issue 页面不会提交反馈。
网络访问对应具体操作:网络测速、Homebrew 操作、查询 GitHub 上的工具版本,或你在命令栏中配置的网站和脚本。反馈内容先在本地生成,供你检查和复制;打开 issue 页面不会提交反馈。

按所用功能授予 macOS 权限,并在**系统设置 → 隐私与安全性**中查看和调整。存储细节与网络访问范围见开发文档中的[数据与隐私说明](.github/CONTRIBUTING.md#privacy)。

Expand Down
10 changes: 2 additions & 8 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,9 @@ Dock 保留英文,和 App 同一条规则

**总开关与信息排布已完成首轮源码整理(2026-09-10,自动验证通过,UI待验收)。** 34 个设置路由已逐页静态审计。功能库管理整页可用性,页内只保留具体持续行为的开关。六个多行为组和四个按需捕获工具移除重复外层门禁,旧关闭状态迁移到具体动作或快捷键,参数与名单保留。开关放到所属选项旁,暂停后仍可准备配置。实际界面验收与缺口见 [交互审计](../kururu-note/验收记录/页面与流程.md)。

### 外观主题:导入 VS Code 配色
### 外观

**已实现并有代表性UI实测。** 第60轮主题链接错误、预览、应用及恢复默认通过,第64轮六语窗口通过;其他主题、完整页面及无障碍组合仍待。 支持本地 JSON / JSONC 主题,限制 256 KiB,将背景、卡片、主文字、次文字、边框、强调色映射到现有语义表面。导入先局部预览,点击应用才保存,可恢复默认灰阶。监控曲线与核心图的填充、轨道和边界已接配色,预览直接复用生产绘图并传固定数据,不启动采样或修改已应用主题。浅深、缺字段、低对比主题的离屏11项检查通过,四张渲染已查看;完整窗口、原生控件、实际悬停和菜单栏仍待实机验收。

- **T1 · 配色范围。** 覆盖面板与设置页的背景、卡片、文字、边框、强调色,以及监视曲线和核心负载图。只更换颜色,保留章鱼标记的形状、布局、字体与交互。菜单栏图标需继续适应 macOS 的明暗背景。
- **T2 · 导入与映射。** 先核对 VS Code 官方主题格式及实际主题样本,再确定首版支持的文件类型与字段。把可用颜色映射到 kururu 的语义角色,缺失颜色回落到内置主题。代码语法高亮色不直接当成界面颜色,也不承诺完整复刻编辑器外观。只读取主题配色,不安装或运行扩展。
- **T3 · 预览与验收。** 应用前预览监视页和设置页,可恢复默认。检查浅色、深色、低对比度及字段不完整的主题。热压力、警告等状态保留明确的语义与文字标识,避免换色后正常和危急难以区分。导入解析失败时说明原因,保留当前主题。

首版解析与持久化专项已验证,正式完成状态以完整回归及实际界面验收记录为准。跨明暗模式和无障碍组合仍需继续验收。
2026-09-16 按用户要求移除 Liquid Glass 开关及专用渲染分支,移除 VS Code 主题导入、预览、持久化及配色覆盖。保留系统/浅色/深色选择和当前 baseline 的信息组织。旧备份里的两项外观字段按已退役设置忽略,不影响其他设置导入。首轮原生外观调整使用系统按钮与强调色,统一正文和小号开关比例;以中性底色、浅色分组卡片和细边界保留分区。菜单栏左侧导航改为固定圆角容器内滚动,溢出内容使用边缘渐隐。功能名称后保留权限图标,能耗与补充解释放入问号弹窗;部分设置说明改为按需帮助,保持唤醒的菜单栏外观选项可折叠。权限缺失、失败状态和操作后果继续直接显示。

### 监视

Expand Down
8 changes: 0 additions & 8 deletions Sources/Vorssaint/App/AppAppearanceController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,10 @@ final class AppAppearanceController: ObservableObject {
}
}

@Published var liquidGlassEnabled: Bool {
didSet {
guard liquidGlassEnabled != oldValue else { return }
UserDefaults.standard.set(liquidGlassEnabled, forKey: DefaultsKey.liquidGlassEnabled)
}
}

private init() {
appearance = AppAppearance.sanitized(
UserDefaults.standard.string(forKey: DefaultsKey.appearance)
)
liquidGlassEnabled = UserDefaults.standard.bool(forKey: DefaultsKey.liquidGlassEnabled)
}

/// The panel is a popover anchored to the menu bar item, so it takes its
Expand Down
50 changes: 3 additions & 47 deletions Sources/Vorssaint/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate, NSW
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { _ in
FeatureRuntime.shared.sync([
.scrollInverter, .focusFollowsMouse, .smoothScroll, .mouseNavigation, .switcher,
.dockPreview, .finderCutPaste, .finderRename, .autoQuit, .dockClick,
.middleClick, .windowMaximizer, .keyboardDebounce,
.textSnippets, .brightness, .radialMenu, .mouseButtonShortcuts,
.mouseClickDebounce, .superKey, .quitWindowProtection, .mixer,
])
FeatureRuntime.shared.permissionChanged(.accessibility)
}
.store(in: &cancellables)

Permissions.shared.$screenRecording
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { _ in
FeatureRuntime.shared.sync([.dockPreview, .screenRecorder])
FeatureRuntime.shared.permissionChanged(.screenRecording)
}
.store(in: &cancellables)

Expand Down Expand Up @@ -252,46 +246,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate, NSW
isTerminating = true
// Quitting properly means the start worked, whenever it happened.
endStartupWatch()
if AppFeature.brightness.isAvailable {
BrightnessService.shared.restoreDisplaysBeforeTermination()
}
ProcessUsageService.shared.stopNetworkMonitoring(force: true)
URLCleanerService.shared.stop()
FocusFollowsMouseService.shared.stop()
WindowMaximizer.shared.stop()
KeyboardDebounceService.shared.suspend()
MouseClickDebounceService.shared.suspend()
TextSnippetService.shared.suspend()
// Takes the Super key mapping back out before the process goes away.
SuperKeyService.shared.suspend()
// Dock's app and window switcher hotkeys persist after quit.
AppSwitcher.shared.suspend()
MouseButtonShortcutService.shared.suspend()
MiddleClickService.shared.suspend()
ScrollInverter.shared.suspend()
SmoothScrollService.shared.suspend()
if AppFeature.mouseAcceleration.isAvailable
|| MouseAccelerationRecovery.hasPendingEntries() {
MouseAccelerationService.shared.stop()
}
MouseNavigationService.shared.suspend()
DockPreviewService.shared.stop()
SoundOutputSwitcher.shared.stop()
PreciseVolumeRollerService.shared.stop()
AppVolumeMixer.shared.stopAll()
FanControlService.restoreBeforeTerminationIfNeeded()
// Puts the system input back if a microphone was chosen here: the
// app's audio settings must not outlive the app.
AudioInputDeviceManager.shared.stop()
// Flushes any scratchpad edit still inside the save debounce.
ScratchpadService.shared.suspend()
// The clipboard history persists through an async pipeline; the last
// mutation (often a Clear) must land before the process dies.
if AppFeature.clipboardHistory.isAvailable {
ClipboardHistoryService.shared.flushBeforeTermination()
}
ShelfService.shared.flushBeforeTermination()
KeepAwakeManager.shared.deactivate(reason: .quit)
FeatureRuntime.shared.terminate()
}

/// The lifeline when the menu bar icon goes missing. Opening the app again
Expand Down
56 changes: 56 additions & 0 deletions Sources/Vorssaint/App/FeatureLifecycle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-3.0-or-later

/// Main-thread lifecycle routing. Registrations describe resource owners, so a
/// service shared by several features is synchronized once per change.
enum FeaturePermission: Hashable {
case accessibility, screenRecording
}

final class FeatureLifecycle<Feature: Hashable> {
struct Registration {
let members: Set<Feature>
var permissions: Set<FeaturePermission> = []
let synchronize: () -> Void
var terminate: () -> Void = {}
}

private let registrations: [Registration]
private var active = Set<Int>()
private var loaded = Set<Int>()
private var terminated = false

init(_ registrations: [Registration]) { self.registrations = registrations }
func sync(changed: Set<Feature>, available: Set<Feature>) {
guard !terminated else { return }
for (index, registration) in registrations.enumerated()
where !registration.members.isDisjoint(with: changed) {
let enabled = !registration.members.isDisjoint(with: available)
guard enabled || active.contains(index) else { continue }
if enabled {
active.insert(index)
loaded.insert(index)
} else {
active.remove(index)
}
registration.synchronize()
}
}
func permissionChanged(_ permission: FeaturePermission, available: Set<Feature>) {
guard !terminated else { return }
for (index, registration) in registrations.enumerated()
where registration.permissions.contains(permission)
&& !registration.members.isDisjoint(with: available) {
active.insert(index)
loaded.insert(index)
registration.synchronize()
}
}
func terminate() {
guard !terminated else { return }
terminated = true
for index in registrations.indices where loaded.contains(index) {
registrations[index].terminate()
}
active.removeAll()
}
}
Loading