diff --git a/.github/workflows/build-windows-manual.yml b/.github/workflows/build-windows-manual.yml index 04ba8e5..31500c0 100644 --- a/.github/workflows/build-windows-manual.yml +++ b/.github/workflows/build-windows-manual.yml @@ -1,33 +1,27 @@ -name: Build Windows Installer +name: Build Windows GLS Team Foundation on: + push: + branches: + - main + paths: + - ".github/workflows/build-windows-manual.yml" workflow_dispatch: inputs: version: - description: "Installer version to write into the Tauri bundle" + description: "GLS team installer version" required: true - default: "0.1.5" - artifact_name: - description: "Uploaded artifact name" - required: true - default: "HoloLake-Era-Team-Foundation-Windows-x64" - distribution: - description: "Product surface to package" - required: true - type: choice - default: "team-foundation" - options: - - "team-foundation" - - "standard" + default: "0.1.7" + type: string env: NODE_OPTIONS: --max-old-space-size=4096 - RUST_TARGET_CACHE_VERSION: v2026-04-14-hololake-era-windows-manual + RUST_TARGET_CACHE_VERSION: v2026-07-19-hololake-team-windows-xwin jobs: build-windows: - name: Build Windows x64 installer - runs-on: windows-latest + name: Build Windows x64 GLS team installer + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -43,77 +37,77 @@ jobs: node-version: "22" cache: "pnpm" + - name: Install Linux cross-build dependencies + run: | + sudo apt-get update + sudo apt-get install -y nsis lld llvm clang + - name: Setup Rust uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 - with: - targets: x86_64-pc-windows-msvc - name: Cache Rust dependencies uses: actions/cache@v4 with: path: | - ~\.cargo\registry - ~\.cargo\git - src-tauri\target - key: ${{ runner.os }}-manual-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}-${{ hashFiles('src-tauri/Cargo.lock') }} + ~/.cargo/registry + ~/.cargo/git + src-tauri/target + .xwin-cache + key: ${{ runner.os }}-team-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}-${{ hashFiles('src-tauri/Cargo.lock') }} restore-keys: | - ${{ runner.os }}-manual-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}- + ${{ runner.os }}-team-windows-cargo-x86_64-pc-windows-msvc-${{ env.RUST_TARGET_CACHE_VERSION }}- - - name: Cache Tauri Windows tools - uses: actions/cache@v4 - with: - path: ~\AppData\Local\tauri - key: ${{ runner.os }}-tauri-tools-nsis-3.11-nsis-tauri-utils-0.5.3 - - - name: Prefetch Tauri NSIS toolchain - shell: pwsh - run: ./.github/scripts/prefetch-tauri-nsis.ps1 + - name: Install cargo-xwin + run: cargo install --locked cargo-xwin - name: Install dependencies run: pnpm install --frozen-lockfile - name: Set app version - shell: pwsh run: | - $version = "${{ inputs.version }}" - $tauri = Get-Content "src-tauri/tauri.conf.json" | ConvertFrom-Json - $tauri.version = $version - $tauri | ConvertTo-Json -Depth 100 | Set-Content "src-tauri/tauri.conf.json" - (Get-Content "src-tauri/Cargo.toml") -replace '^version = ".*"$', "version = `"$version`"" | Set-Content "src-tauri/Cargo.toml" + version="${{ inputs.version }}" + if [ -z "$version" ]; then version="0.1.7"; fi + jq --arg version "$version" '.version = $version' src-tauri/tauri.conf.json > src-tauri/tauri.conf.json.tmp + mv src-tauri/tauri.conf.json.tmp src-tauri/tauri.conf.json + sed -i "s/^version = \".*\"/version = \"$version\"/" src-tauri/Cargo.toml - - name: Build NSIS installer - shell: pwsh + - name: Build unsigned NSIS installer for GLS team testing env: VITE_SENTRY_DSN: "" SENTRY_DSN: "" VITE_POSTHOG_KEY: "" VITE_POSTHOG_HOST: "" + XWIN_CACHE_DIR: ${{ github.workspace }}/.xwin-cache run: | - if ("${{ inputs.distribution }}" -eq "team-foundation") { - pnpm tauri build --target x86_64-pc-windows-msvc --bundles nsis --config src-tauri/tauri.team.conf.json - } else { - pnpm tauri build --target x86_64-pc-windows-msvc --bundles nsis - } + pnpm tauri build \ + --runner cargo-xwin \ + --target x86_64-pc-windows-msvc \ + --bundles nsis \ + --config src-tauri/tauri.team.conf.json - name: Validate installer - shell: pwsh run: | - $installers = @(Get-ChildItem -Path "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" -Filter "*-setup.exe" -File -ErrorAction SilentlyContinue) - if ($installers.Count -eq 0) { - throw "Windows build produced no NSIS setup executable." - } - foreach ($installer in $installers) { - if ($installer.Name -notlike "*${{ inputs.version }}*") { - throw "Windows installer has unexpected version in filename: $($installer.Name)" + shopt -s nullglob + installers=(src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*-setup.exe) + if [ ${#installers[@]} -eq 0 ]; then + echo "::error::Windows build produced no NSIS setup executable." + exit 1 + fi + for installer in "${installers[@]}"; do + echo "Built Windows GLS team installer: $installer" + file "$installer" | grep -q 'PE32' || { + echo "::error::Unexpected installer format: $installer" + exit 1 } - Write-Host "Built Windows installer: $($installer.FullName)" - } + sha256sum "$installer" > "$installer.sha256" + done - name: Upload Windows installer uses: actions/upload-artifact@v4 with: - name: ${{ inputs.artifact_name }} + name: HoloLake-Era-Team-Foundation-Windows-x64 path: | src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*-setup.exe + src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*-setup.exe.sha256 if-no-files-found: error retention-days: 7 diff --git a/docs/IOS-TESTFLIGHT.md b/docs/IOS-TESTFLIGHT.md index b63f93a..946276e 100644 --- a/docs/IOS-TESTFLIGHT.md +++ b/docs/IOS-TESTFLIGHT.md @@ -1,6 +1,6 @@ # HoloLake Era iPhone / TestFlight 承接记录 -状态日期:2026-07-18 +状态日期:2026-07-19 当前版本:0.1.7 Bundle ID:`com.guanghulab.hololake` @@ -15,32 +15,33 @@ Bundle ID:`com.guanghulab.hololake` - 手机导航回归测试:`src/components/HoloLakeHome.test.tsx` - iOS 专用 Tauri 配置:`src-tauri/tauri.ios.conf.json` - XcodeGen 工程源:`src-tauri/gen/apple/project.yml` -- 已生成 Xcode 工程:`src-tauri/gen/apple/HoloLake.xcodeproj` +- 已生成 Xcode 工程:`src-tauri/gen/apple/hololake.xcodeproj` - iOS 权限基线:`src-tauri/capabilities/mobile.json` -旧的 `src-tauri/gen/apple/laputa.xcodeproj` 和 `laputa_iOS` 目录来自 Tolaria 基座,仅作为历史生成结构保留;TestFlight 的新承接入口是 `HoloLake.xcodeproj` / `HoloLake_iOS`,不得再用旧包名 `club.refactoring.tolaria` 发布。 +旧的 `laputa.xcodeproj`、旧 `HoloLake.xcodeproj`、`laputa_iOS` 与第一次生成的 `tolaria.xcodeproj` 是相互冲突的历史结构,已从当前工程移除并备份到外接盘。Rust 包、Xcode project 与 scheme 已统一去基座品牌化,当前唯一承接入口是 `hololake.xcodeproj` / `hololake_iOS`;产品显示名为 `HoloLake Era`,发布身份只能使用 `com.guanghulab.hololake`。 ## 已验证 - Web/TypeScript 生产构建通过:`npm run build` - 第五域与手机频道导航测试通过:`src/components/HoloLakeHome.test.tsx`,8 项通过 - `aarch64-apple-ios` 与 `aarch64-apple-ios-sim` Rust 目标已安装 -- Xcode 工程已经以 HoloLake 名称、0.1.7 版本和独立 Bundle ID 生成 +- Xcode 工程已按 0.1.7(build 20)、独立 Bundle ID 和 `hololake_iOS` scheme 重新生成 +- Apple 付费个人开发者团队 `825A9L3G7Q` 已在 Xcode 生效 +- Apple Development 与 Apple Distribution 证书已由 Xcode 创建 -## 尚未闭环的外部条件 +## 当前闭环状态 -1. Apple Developer 订阅已付款,但 Xcode 当前仍只显示 `Personal Team`;正式付费团队同步后才能建立发行签名并上传 TestFlight。 -2. 本机 Xcode 的 iOS 26.5.1 + iOS 26.5 Simulator 组件未安装,官方界面显示占用 8.52 GB;应在冰朔确认磁盘占用后下载。 -3. iOS Rust 原生检查已进入 Tauri Swift 层,但 SwiftPM 从 GitHub 拉取 `Brendonovich/swift-rs` 时网络连接失败。这是依赖下载阻断,不是当前代码编译错误;网络恢复后重跑原生检查。 -4. 创建 Apple Development / Distribution 证书属于持久凭据操作,必须在正式团队出现后由冰朔当场确认,再在 Xcode 的 `Manage Certificates…` 中执行。 +2026-07-19 已完成付费团队同步、证书创建、iOS 26.5 平台导入、真机登记、前端生产构建和 Xcode scheme 对齐。私人设备标识不得写入仓库。 + +App Store Connect 应用记录已创建:名称 `HoloLake Era`,主语言简体中文,Bundle ID `com.guanghulab.hololake`。0.1.7 build 20 已由 Xcode 返回 `App upload complete`。build 19 曾因 `libapp.a` 被错误复制到 App 根目录而被 Apple 以 90171 拒绝;根因是 `project.yml` 把 `Externals` 同时声明为 sources,现已移除,build 20 包内预检确认不再包含该静态库。 + +外接盘留档:`/Volumes/JZAO/HoloLake/artifacts/ios-testflight/0.1.7-build20/`。IPA SHA-256:`c813d8fd6f639d5e91366e95e1b57d04ea6eec46ac9433261746b0c8cd18dc76`。 ## 下一实例最短继续路径 -1. 打开 Xcode → Settings → Apple Accounts,确认是否出现非 `Personal Team` 的正式团队。 -2. 经冰朔确认后,安装 Xcode → Settings → Components 中的 iOS 26.5.1 + Simulator。 -3. 网络可访问 GitHub 后运行: - `cargo check --manifest-path src-tauri/Cargo.toml --target aarch64-apple-ios` -4. 用 `src-tauri/gen/apple/HoloLake.xcodeproj` 的 `HoloLake_iOS` scheme 做真机归档。 -5. 正式团队出现且冰朔确认创建证书后,创建 App Store Connect 应用记录并上传 TestFlight 内测构建。 +1. 在 App Store Connect / TestFlight 查看 build 20 的 Apple 服务器处理状态。 +2. 处理完成后设置内部测试信息并邀请测试者;若出现新的合规问题,记录 Apple 原始错误后再修,不重复上传同一 build number。 +3. 真机从 TestFlight 安装后验收启动、五入口导航、安全区、频道切换和知识库入口。 +4. 后续上传必须递增 build number,并保留归档、IPA、SHA-256 与 Apple 接收状态。 -TestFlight 上线前不得把“前端构建通过”“Xcode 工程已生成”写成“手机版已发布”。 +`App upload complete` 证明 Apple 已接收上传,不等于 TestFlight 已处理完成、已分配测试者或 App Store 已发布。 diff --git a/docs/TEAM-FOUNDATION-HANDOFF.md b/docs/TEAM-FOUNDATION-HANDOFF.md index 0ca7e19..c8e177f 100644 --- a/docs/TEAM-FOUNDATION-HANDOFF.md +++ b/docs/TEAM-FOUNDATION-HANDOFF.md @@ -1,7 +1,7 @@ # 光湖团队 GLS 基座与企业四域开发承接 > 文档编号:HLP-DOC-TEAM-0001 -> 状态:2026-07-18 实现基线 +> 状态:2026-07-19 跨平台承接基线 > 产品仓库:`REPO-008 bingshuo/hololake-platform` ## 1. 已确认的产品顺序 @@ -76,20 +76,19 @@ Mac 团队基座包: ## 6. Windows CI 与未闭环项 -Windows 安装包沿用仓库已有的 Windows CI,不在 Mac 上伪造。手动触发: +Windows 安装包不在 Mac 上伪造。当前工作流已改为在 `ubuntu-latest` 上使用 `cargo-xwin + NSIS` 交叉构建,不依赖未配置完成的 Windows 自托管 runner;推送工作流文件到 `main` 会自动触发,也可手动触发: ```text .github/workflows/build-windows-manual.yml -distribution = team-foundation version = 0.1.7 artifact_name = HoloLake-Era-Team-Foundation-Windows-x64 ``` 输出目标名:`HoloLake-Era-0.1.7-Team-Foundation-Windows-x64-setup.exe`。 -该工作流历史上成功生成过 Windows 包;本次已增加 `distribution` 选择,默认 `team-foundation`,通过 `src-tauri/tauri.team.conf.json` 只构建 GLS 团队入口。CI 成功只能证明 NSIS 产物生成;能否安装、启动和显示正确 GLS 页面仍须 Windows 实机验收。当前团队版 Windows 包尚未生成,不得写成双平台已经交付。 +该工作流固定通过 `src-tauri/tauri.team.conf.json` 构建 GLS 团队入口,并对 NSIS 文件做 PE32 格式检查与 SHA-256 输出。CI 成功只能证明 NSIS 产物生成;能否安装、启动和显示正确 GLS 页面仍须 Windows 实机验收。当前新版工作流尚未在远端 runner 上跑出本次团队包,不得写成双平台已经交付。 -2026-07-18 功能提交为 `a6d83b0`,Windows CI 与本文修正提交为 `ab283b5`。第一次推送完整执行了前端测试、Rust lint、1074 项 Rust 测试与覆盖率;Playwright 冒烟测试因本机缺少对应 headless-shell 运行器未执行成功。远端曾因 `repo_push_grant_expired` 拒绝;冰朔完成新邮件工单批准后,已按导航地图读取、确认和仓库授权路径成功推送至远端 `main`。下一步是触发上述 Windows CI,并在 Windows 实机验证安装和启动。 +2026-07-18 功能提交为 `a6d83b0`,Windows CI 与本文修正提交为 `ab283b5`。第一次推送完整执行了前端测试、Rust lint、1074 项 Rust 测试与覆盖率;Playwright 冒烟测试因本机缺少对应 headless-shell 运行器未执行成功。远端曾因 `repo_push_grant_expired` 拒绝;冰朔完成新邮件工单批准后,已按导航地图读取、确认和仓库授权路径成功推送至远端 `main`。2026-07-19 的 iOS/TestFlight 与 Windows 工作流提交推送后会自动触发新版 Windows CI;必须以远端 job 与产物页面为准,并在 Windows 实机验证安装和启动。 ## 7. 外接盘构建约束 diff --git a/package.json b/package.json index 51e9bd3..927c7fa 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "tolaria", + "name": "hololake-era", "private": true, "license": "AGPL-3.0-or-later", "version": "0.1.7", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 784c47c..bbddbdf 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1863,6 +1863,45 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hololake" +version = "0.1.7" +dependencies = [ + "base64 0.22.1", + "chrono", + "csv", + "dirs 5.0.1", + "futures-util", + "gray_matter", + "ironcalc_base", + "log", + "notify", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-web-kit", + "regex", + "reqwest 0.12.28", + "sentry", + "serde", + "serde_json", + "serde_yaml", + "tauri", + "tauri-build", + "tauri-plugin-deep-link", + "tauri-plugin-dialog", + "tauri-plugin-log", + "tauri-plugin-opener", + "tauri-plugin-prevent-default", + "tauri-plugin-process", + "tauri-plugin-single-instance", + "tauri-plugin-updater", + "tempfile", + "tokio", + "uuid", + "walkdir", +] + [[package]] name = "hostname" version = "0.4.2" @@ -5545,45 +5584,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tolaria" -version = "0.1.7" -dependencies = [ - "base64 0.22.1", - "chrono", - "csv", - "dirs 5.0.1", - "futures-util", - "gray_matter", - "ironcalc_base", - "log", - "notify", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-web-kit", - "regex", - "reqwest 0.12.28", - "sentry", - "serde", - "serde_json", - "serde_yaml", - "tauri", - "tauri-build", - "tauri-plugin-deep-link", - "tauri-plugin-dialog", - "tauri-plugin-log", - "tauri-plugin-opener", - "tauri-plugin-prevent-default", - "tauri-plugin-process", - "tauri-plugin-single-instance", - "tauri-plugin-updater", - "tempfile", - "tokio", - "uuid", - "walkdir", -] - [[package]] name = "toml" version = "0.5.11" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index feb301f..0cf7287 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tolaria" +name = "hololake" version = "0.1.7" description = "Personal knowledge and life management app" authors = ["Luca Rossi"] @@ -26,7 +26,7 @@ serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" log = "0.4" notify = "6.1" -tauri = { version = "2.10.0", features = ["macos-private-api", "protocol-asset", "devtools", "image-png"] } +tauri = { version = "2.10.0", features = [ "protocol-asset", "devtools", "image-png", "macos-private-api"] } tauri-plugin-log = "2" gray_matter = "0.2" walkdir = "2" diff --git a/src-tauri/gen/apple/HoloLake.xcodeproj/project.pbxproj b/src-tauri/gen/apple/HoloLake.xcodeproj/project.pbxproj deleted file mode 100644 index 341a674..0000000 --- a/src-tauri/gen/apple/HoloLake.xcodeproj/project.pbxproj +++ /dev/null @@ -1,798 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 10BC764F3DECE7B9FDC8F1EB /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12F6AA9DA589FC51C9D843C3 /* WebKit.framework */; }; - 10CF0B2A75125AB882A4104B /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60D77C95CDA4878AC31E06FC /* Metal.framework */; }; - 1E3F1D473076F96C5929CEE7 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E5CBD70FB2815D19C3F0190 /* CoreGraphics.framework */; }; - 30810FE969AAA1DFE091637C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3BBE6E79A4BF3832CE315919 /* Assets.xcassets */; }; - 574E9FC1DB877392B7BF5918 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 0F60A50A996D2D783A5FD25F /* assets */; }; - 6BA90420617661093E6C004A /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 476D4C25E501B2DB819B83EF /* Security.framework */; }; - 888E277D0583DEC4A5F817FA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F8BF20C12EF3B0D970C4BE /* QuartzCore.framework */; }; - 890AF7B1E9D69D4FBA033F99 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26BB1CFE5D89468C0954F3D7 /* UIKit.framework */; }; - 9E4FC2FF2D11E16C85D0CA22 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6A8D7ABE8518DC962AEE83D2 /* main.mm */; }; - A22C9A35CAAB2DF45F783F3B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A9D939D7C6842A013F9E9D2D /* LaunchScreen.storyboard */; }; - BFA08714B1F1FFA160554E4E /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C68619FE2409BD0403F0F822 /* MetalKit.framework */; }; - E52656C9AA3CE79F8BD9C340 /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 907D2515583F8E3DAA94C653 /* libapp.a */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 0050B284AED1F6607547DDDF /* cache.rs */ = {isa = PBXFileReference; path = cache.rs; sourceTree = ""; }; - 0075CBC9552C222333830937 /* menu.rs */ = {isa = PBXFileReference; path = menu.rs; sourceTree = ""; }; - 010158A81A8DD44ABBAA4BCC /* codex_cli.rs */ = {isa = PBXFileReference; path = codex_cli.rs; sourceTree = ""; }; - 015F375350B0DCCF932CD855 /* path_identity.rs */ = {isa = PBXFileReference; path = path_identity.rs; sourceTree = ""; }; - 01A36EEB3D914E2049B80F8C /* folder_and_file_kind.rs */ = {isa = PBXFileReference; path = folder_and_file_kind.rs; sourceTree = ""; }; - 057242D51175B2DA743FFEEC /* rename_cmds.rs */ = {isa = PBXFileReference; path = rename_cmds.rs; sourceTree = ""; }; - 057E964B8FFDE93C6C0DE7F0 /* paths.rs */ = {isa = PBXFileReference; path = paths.rs; sourceTree = ""; }; - 06D4D353ACDD7DA2F35B49E8 /* delete.rs */ = {isa = PBXFileReference; path = delete.rs; sourceTree = ""; }; - 082E515A19172B9A0992BA4F /* conflict.rs */ = {isa = PBXFileReference; path = conflict.rs; sourceTree = ""; }; - 09754A5CDE571D89F3563080 /* migration.rs */ = {isa = PBXFileReference; path = migration.rs; sourceTree = ""; }; - 09D44DF00534FAA275529BF9 /* remote_config.rs */ = {isa = PBXFileReference; path = remote_config.rs; sourceTree = ""; }; - 0BF29A2DBD08245184C62509 /* view_value_conversions.rs */ = {isa = PBXFileReference; path = view_value_conversions.rs; sourceTree = ""; }; - 0F60A50A996D2D783A5FD25F /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; - 0FCCFC9E2A1E037C3491E7F5 /* pi_events.rs */ = {isa = PBXFileReference; path = pi_events.rs; sourceTree = ""; }; - 1179CD8FF58077DDF7E99299 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - 121AED7D3C84479EC638C2E8 /* antigravity_cli.rs */ = {isa = PBXFileReference; path = antigravity_cli.rs; sourceTree = ""; }; - 128C9606725F612692B8438E /* getting_started.rs */ = {isa = PBXFileReference; path = getting_started.rs; sourceTree = ""; }; - 12F6AA9DA589FC51C9D843C3 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - 1315706C702DC2397F3B802E /* scan_cmds.rs */ = {isa = PBXFileReference; path = scan_cmds.rs; sourceTree = ""; }; - 14962FC08F5BBCA3DFF34DD4 /* copilot_cli.rs */ = {isa = PBXFileReference; path = copilot_cli.rs; sourceTree = ""; }; - 14C532D36F6C1157D9C129D1 /* upstream.rs */ = {isa = PBXFileReference; path = upstream.rs; sourceTree = ""; }; - 1BB054E84CD7A1962922A3E2 /* provider.rs */ = {isa = PBXFileReference; path = provider.rs; sourceTree = ""; }; - 1C41778F8CEEEBAC8FCC5F13 /* ai_models.rs */ = {isa = PBXFileReference; path = ai_models.rs; sourceTree = ""; }; - 1CBDAF21C861C3AB97AC482D /* memory.rs */ = {isa = PBXFileReference; path = memory.rs; sourceTree = ""; }; - 20AE80661DFE787E09398F4F /* antigravity_discovery.rs */ = {isa = PBXFileReference; path = antigravity_discovery.rs; sourceTree = ""; }; - 22092BA85067CC737DBBAD05 /* commit.rs */ = {isa = PBXFileReference; path = commit.rs; sourceTree = ""; }; - 22EBF553582EACF370284B42 /* display_metadata.rs */ = {isa = PBXFileReference; path = display_metadata.rs; sourceTree = ""; }; - 26948CCB253A8A9438E1DFF6 /* folders.rs */ = {isa = PBXFileReference; path = folders.rs; sourceTree = ""; }; - 26BB1CFE5D89468C0954F3D7 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - 2869AD5AF9EBC3A2F6F89A96 /* system_metadata_tests.rs */ = {isa = PBXFileReference; path = system_metadata_tests.rs; sourceTree = ""; }; - 28F8BF20C12EF3B0D970C4BE /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 2E55F0E500944FFBEDF36439 /* credentials.rs */ = {isa = PBXFileReference; path = credentials.rs; sourceTree = ""; }; - 2F38683CB597F0CC57478E4A /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; - 2FC6224DED146A056DBEFAFA /* title_sync.rs */ = {isa = PBXFileReference; path = title_sync.rs; sourceTree = ""; }; - 3202961ACB715D8D472F795C /* pi_config.rs */ = {isa = PBXFileReference; path = pi_config.rs; sourceTree = ""; }; - 348A425343E36A5B9DC950F4 /* sheet.rs */ = {isa = PBXFileReference; path = sheet.rs; sourceTree = ""; }; - 354F27756754D7E66226E8C8 /* view_cmds.rs */ = {isa = PBXFileReference; path = view_cmds.rs; sourceTree = ""; }; - 363304D4E0297987B24BB926 /* status.rs */ = {isa = PBXFileReference; path = status.rs; sourceTree = ""; }; - 3BBE6E79A4BF3832CE315919 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 3CD4014D86632C50AF20DE59 /* ai_agents.rs */ = {isa = PBXFileReference; path = ai_agents.rs; sourceTree = ""; }; - 3D266F3E587C8CAFD43897AB /* window_state.rs */ = {isa = PBXFileReference; path = window_state.rs; sourceTree = ""; }; - 3EF2534081FDE3316E028081 /* dates.rs */ = {isa = PBXFileReference; path = dates.rs; sourceTree = ""; }; - 3FE3C66FF30B84195FA98C1A /* opencode_events.rs */ = {isa = PBXFileReference; path = opencode_events.rs; sourceTree = ""; }; - 41B8445BDA2EEFFB636C2F27 /* lifecycle_cmds.rs */ = {isa = PBXFileReference; path = lifecycle_cmds.rs; sourceTree = ""; }; - 4207C0C689A9DEF15CA8888C /* vault.rs */ = {isa = PBXFileReference; path = vault.rs; sourceTree = ""; }; - 42D0C09784FF734A5CC92E40 /* entry.rs */ = {isa = PBXFileReference; path = entry.rs; sourceTree = ""; }; - 458437264D37E162C02F7E01 /* vault_watcher.rs */ = {isa = PBXFileReference; path = vault_watcher.rs; sourceTree = ""; }; - 463BC00E27FDD63E84BFDACD /* parsing.rs */ = {isa = PBXFileReference; path = parsing.rs; sourceTree = ""; }; - 476D4C25E501B2DB819B83EF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - 4A46898BA1C969B2646B4B80 /* pdf_export.rs */ = {isa = PBXFileReference; path = pdf_export.rs; sourceTree = ""; }; - 4C1FDAEA4662D9A6260A58DE /* hermes_discovery.rs */ = {isa = PBXFileReference; path = hermes_discovery.rs; sourceTree = ""; }; - 4D52F451E32D5D9CA7AFB9AA /* author.rs */ = {isa = PBXFileReference; path = author.rs; sourceTree = ""; }; - 4E947F33478FAB67227CFCCB /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - 4F0D35BE7398C3602AB06629 /* command.rs */ = {isa = PBXFileReference; path = command.rs; sourceTree = ""; }; - 5450814D3827CCD69F01A241 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; - 5766FC68AFF52E5D601A2201 /* image.rs */ = {isa = PBXFileReference; path = image.rs; sourceTree = ""; }; - 5986643AE3444A64F2EC3699 /* HoloLake_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = HoloLake_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 5A06AA9064684BD5F26156B3 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; - 5B0390DD26F6B3080CFE1531 /* opencode.rs */ = {isa = PBXFileReference; path = opencode.rs; sourceTree = ""; }; - 5BFC39CB9F1243B4474C3BB6 /* app_updater.rs */ = {isa = PBXFileReference; path = app_updater.rs; sourceTree = ""; }; - 5D6A7764044526D11B69B748 /* pulse.rs */ = {isa = PBXFileReference; path = pulse.rs; sourceTree = ""; }; - 60D77C95CDA4878AC31E06FC /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - 66E7FE04B14503C592B73CD0 /* view_relationships.rs */ = {isa = PBXFileReference; path = view_relationships.rs; sourceTree = ""; }; - 675DE5C585D832EC23F4BFF3 /* archival_metadata.rs */ = {isa = PBXFileReference; path = archival_metadata.rs; sourceTree = ""; }; - 68EDAECA09ECC7BC74B1088C /* subprocess.rs */ = {isa = PBXFileReference; path = subprocess.rs; sourceTree = ""; }; - 6A8D7ABE8518DC962AEE83D2 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; - 6B7EB67FFE128AB20A135E6E /* folders.rs */ = {isa = PBXFileReference; path = folders.rs; sourceTree = ""; }; - 6D03B0E75CD5CDAFA20644BC /* connect.rs */ = {isa = PBXFileReference; path = connect.rs; sourceTree = ""; }; - 6E98CFB59596D798EB7DCB34 /* type_templates.rs */ = {isa = PBXFileReference; path = type_templates.rs; sourceTree = ""; }; - 6F0DA82A2114B791EDAB7300 /* real_vault_consistency.rs */ = {isa = PBXFileReference; path = real_vault_consistency.rs; sourceTree = ""; }; - 6F4D5A90038E5C644B0B92A5 /* hermes_cli.rs */ = {isa = PBXFileReference; path = hermes_cli.rs; sourceTree = ""; }; - 700C8B5C02E5F70B26055FFD /* remote_branch_tests.rs */ = {isa = PBXFileReference; path = remote_branch_tests.rs; sourceTree = ""; }; - 73CCD2E1120A28AD85F99137 /* type_and_links.rs */ = {isa = PBXFileReference; path = type_and_links.rs; sourceTree = ""; }; - 74958B90904CF6F4DD914C06 /* frontmatter_regression_tests.rs */ = {isa = PBXFileReference; path = frontmatter_regression_tests.rs; sourceTree = ""; }; - 76DEFD594E4794AA24544225 /* parsing_tests.rs */ = {isa = PBXFileReference; path = parsing_tests.rs; sourceTree = ""; }; - 7A12A53788441DDEC467ADF9 /* complex_frontmatter.rs */ = {isa = PBXFileReference; path = complex_frontmatter.rs; sourceTree = ""; }; - 7B0BC7CD8DCDBD326123B1E1 /* trash.rs */ = {isa = PBXFileReference; path = trash.rs; sourceTree = ""; }; - 7BF95AB601E1EA43C76FEE79 /* telemetry.rs */ = {isa = PBXFileReference; path = telemetry.rs; sourceTree = ""; }; - 7D2B082E58B3456C21A81C48 /* config_seed.rs */ = {isa = PBXFileReference; path = config_seed.rs; sourceTree = ""; }; - 7D8F18C847A1FF6B2BDB0356 /* ai.rs */ = {isa = PBXFileReference; path = ai.rs; sourceTree = ""; }; - 816915173E861F05D186A727 /* pi_discovery.rs */ = {isa = PBXFileReference; path = pi_discovery.rs; sourceTree = ""; }; - 818CA203C2C91ABABF95A8CB /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - 81F8B518EC2593A40DAA5503 /* remote.rs */ = {isa = PBXFileReference; path = remote.rs; sourceTree = ""; }; - 85A5FC54DAD705312260A21A /* scan_and_file_access.rs */ = {isa = PBXFileReference; path = scan_and_file_access.rs; sourceTree = ""; }; - 87147D77664A5051BBA28592 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - 8961178AD8EA4CC56A023659 /* view_date_filters.rs */ = {isa = PBXFileReference; path = view_date_filters.rs; sourceTree = ""; }; - 8D2E06DCBF77C0D6AC0FB24C /* mod_tests.rs */ = {isa = PBXFileReference; path = mod_tests.rs; sourceTree = ""; }; - 8E5CBD70FB2815D19C3F0190 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - 8FD8EFD959EE4C4185BD712D /* windows_cmd_shim.rs */ = {isa = PBXFileReference; path = windows_cmd_shim.rs; sourceTree = ""; }; - 9049C59DDCC9763ACB36C5F3 /* opencode_discovery.rs */ = {isa = PBXFileReference; path = opencode_discovery.rs; sourceTree = ""; }; - 907D2515583F8E3DAA94C653 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; - 96BDADEAB5BE019394D4DF4C /* opencode_cli.rs */ = {isa = PBXFileReference; path = opencode_cli.rs; sourceTree = ""; }; - 9992DE2C71B8768B3E090D80 /* clone.rs */ = {isa = PBXFileReference; path = clone.rs; sourceTree = ""; }; - 9A57A197EBB4AEAC23ED6F36 /* git.rs */ = {isa = PBXFileReference; path = git.rs; sourceTree = ""; }; - 9A611472FDB8AC605F500C3F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - 9B5BEB3EE5C7932B4E065849 /* clipboard.rs */ = {isa = PBXFileReference; path = clipboard.rs; sourceTree = ""; }; - 9C68630C9CBDDB447BC5E879 /* app_icon.rs */ = {isa = PBXFileReference; path = app_icon.rs; sourceTree = ""; }; - 9D7F16C81C610385B6CA9B46 /* search.rs */ = {isa = PBXFileReference; path = search.rs; sourceTree = ""; }; - 9EAFBB5DB3A9DC0A7B2E8FF6 /* frontmatter.rs */ = {isa = PBXFileReference; path = frontmatter.rs; sourceTree = ""; }; - A01712BAB3D6830A0BC75710 /* remote_status.rs */ = {isa = PBXFileReference; path = remote_status.rs; sourceTree = ""; }; - A0FB232A6B19885BD9BDCA62 /* boundary.rs */ = {isa = PBXFileReference; path = boundary.rs; sourceTree = ""; }; - A16DE2463D72666355CA347B /* antigravity_config.rs */ = {isa = PBXFileReference; path = antigravity_config.rs; sourceTree = ""; }; - A1AE659A16F1D92E77E9E687 /* views.rs */ = {isa = PBXFileReference; path = views.rs; sourceTree = ""; }; - A64C1E3DB12B7D51417FBED4 /* vault_list.rs */ = {isa = PBXFileReference; path = vault_list.rs; sourceTree = ""; }; - A8647B6883D11614B6E97DB3 /* copilot_discovery.rs */ = {isa = PBXFileReference; path = copilot_discovery.rs; sourceTree = ""; }; - A8F3EABFCD65C8663D7D2FDC /* rename.rs */ = {isa = PBXFileReference; path = rename.rs; sourceTree = ""; }; - A8F4939901A70030BD872898 /* rename_transaction.rs */ = {isa = PBXFileReference; path = rename_transaction.rs; sourceTree = ""; }; - A9D939D7C6842A013F9E9D2D /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - AE2B2E577D7A7ABD77E37F57 /* relationship_key_tests.rs */ = {isa = PBXFileReference; path = relationship_key_tests.rs; sourceTree = ""; }; - B0488C703D2C6B46C91C47DA /* ai_agent_processes.rs */ = {isa = PBXFileReference; path = ai_agent_processes.rs; sourceTree = ""; }; - B2ABF84048E93D7CD435A4FA /* opencode_config.rs */ = {isa = PBXFileReference; path = opencode_config.rs; sourceTree = ""; }; - B43462DD1247187BEC7B2867 /* mcp_config.rs */ = {isa = PBXFileReference; path = mcp_config.rs; sourceTree = ""; }; - B87EE0F054D9C201C322C9B1 /* opencode_events_tests.rs */ = {isa = PBXFileReference; path = opencode_events_tests.rs; sourceTree = ""; }; - B88D960D36A57345E09B5B80 /* keys.rs */ = {isa = PBXFileReference; path = keys.rs; sourceTree = ""; }; - B9F7A500BB678F7C6C5EFA99 /* history.rs */ = {isa = PBXFileReference; path = history.rs; sourceTree = ""; }; - BA4747AF028A869AA1A1A6DC /* basics.rs */ = {isa = PBXFileReference; path = basics.rs; sourceTree = ""; }; - BBE04C1F55BC29695FEE4539 /* runtime.rs */ = {isa = PBXFileReference; path = runtime.rs; sourceTree = ""; }; - BC1398337782B4F13550069B /* modified_dates_tests.rs */ = {isa = PBXFileReference; path = modified_dates_tests.rs; sourceTree = ""; }; - BD49C0127DD47B9259DDB6DC /* relationships.rs */ = {isa = PBXFileReference; path = relationships.rs; sourceTree = ""; }; - C0126E76F91A13BBDBC5904C /* view_migration.rs */ = {isa = PBXFileReference; path = view_migration.rs; sourceTree = ""; }; - C2AD8E13B962ECF439B7E1E5 /* yaml.rs */ = {isa = PBXFileReference; path = yaml.rs; sourceTree = ""; }; - C3ED1D8403428C88C5F63503 /* kiro_discovery.rs */ = {isa = PBXFileReference; path = kiro_discovery.rs; sourceTree = ""; }; - C55B8EDBBEB11312B57B7591 /* ops.rs */ = {isa = PBXFileReference; path = ops.rs; sourceTree = ""; }; - C66F99524B45C69B7210BDEE /* version.rs */ = {isa = PBXFileReference; path = version.rs; sourceTree = ""; }; - C68619FE2409BD0403F0F822 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; - C6CD6117887727F3A4D96F57 /* cli_agent_runtime.rs */ = {isa = PBXFileReference; path = cli_agent_runtime.rs; sourceTree = ""; }; - C7F71AD744A1B18D2DACDC62 /* ai_model_tools.rs */ = {isa = PBXFileReference; path = ai_model_tools.rs; sourceTree = ""; }; - CAA8F8764EF165BAD372E0AB /* file_cmds.rs */ = {isa = PBXFileReference; path = file_cmds.rs; sourceTree = ""; }; - CBE02C8F152E451278EA827C /* view_tests.rs */ = {isa = PBXFileReference; path = view_tests.rs; sourceTree = ""; }; - CECEC916A37B4A001FEEFF9A /* file.rs */ = {isa = PBXFileReference; path = file.rs; sourceTree = ""; }; - D1CBEA64917CE1F1C3A5589D /* file_url.rs */ = {isa = PBXFileReference; path = file_url.rs; sourceTree = ""; }; - D3757B9773F2FA1B992C7EB1 /* shell_env.rs */ = {isa = PBXFileReference; path = shell_env.rs; sourceTree = ""; }; - D860F78F8B5272FDB3AE6247 /* git_clone.rs */ = {isa = PBXFileReference; path = git_clone.rs; sourceTree = ""; }; - D8B35D0B4002E064A68C5918 /* ops_update_tests.rs */ = {isa = PBXFileReference; path = ops_update_tests.rs; sourceTree = ""; }; - DB739EBCB89E3A26BD0494C8 /* extraction.rs */ = {isa = PBXFileReference; path = extraction.rs; sourceTree = ""; }; - DC2E71B56C45C04CAE6565F0 /* git_connect.rs */ = {isa = PBXFileReference; path = git_connect.rs; sourceTree = ""; }; - DC4FA3AE9642BBE51F638B89 /* runtime.rs */ = {isa = PBXFileReference; path = runtime.rs; sourceTree = ""; }; - DC6FADA70D52DCF194FF7D6C /* system.rs */ = {isa = PBXFileReference; path = system.rs; sourceTree = ""; }; - E479065A9C78E592C84F4944 /* remote_url.rs */ = {isa = PBXFileReference; path = remote_url.rs; sourceTree = ""; }; - E4FA358B012D1EDF0267B63F /* claude_invocation.rs */ = {isa = PBXFileReference; path = claude_invocation.rs; sourceTree = ""; }; - EA0CD2EF0B8E39E98114B8B1 /* laputa_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = laputa_iOS.entitlements; sourceTree = ""; }; - EBBF2E18D884BEAE0BB5EABE /* pi_events_tests.rs */ = {isa = PBXFileReference; path = pi_events_tests.rs; sourceTree = ""; }; - EC2E3E3FD74DE9987F1F326B /* mcp.rs */ = {isa = PBXFileReference; path = mcp.rs; sourceTree = ""; }; - ECE47DEFF64DBD80238E7C96 /* kiro_cli.rs */ = {isa = PBXFileReference; path = kiro_cli.rs; sourceTree = ""; }; - F2C168663A35EAABC3AC0E00 /* filename_rules.rs */ = {isa = PBXFileReference; path = filename_rules.rs; sourceTree = ""; }; - F304454433962A99CF538071 /* line_stream.rs */ = {isa = PBXFileReference; path = line_stream.rs; sourceTree = ""; }; - F4EDF4DE2E778C5A03B94435 /* linux_appimage.rs */ = {isa = PBXFileReference; path = linux_appimage.rs; sourceTree = ""; }; - F4F90594F8CD2DB418876246 /* settings.rs */ = {isa = PBXFileReference; path = settings.rs; sourceTree = ""; }; - F6671256B6EF31BA8EEB7622 /* app_config.rs */ = {isa = PBXFileReference; path = app_config.rs; sourceTree = ""; }; - F6CCA1B5F651CD7BE868E102 /* claude_cli.rs */ = {isa = PBXFileReference; path = claude_cli.rs; sourceTree = ""; }; - F77DDF68DB111E6E824780D4 /* frontmatter_cmds.rs */ = {isa = PBXFileReference; path = frontmatter_cmds.rs; sourceTree = ""; }; - F7833823F42B1D5F9031B534 /* app_icon.rs */ = {isa = PBXFileReference; path = app_icon.rs; sourceTree = ""; }; - FB24334BDF17362ADB6AFAF8 /* journal_type_visibility.rs */ = {isa = PBXFileReference; path = journal_type_visibility.rs; sourceTree = ""; }; - FB6CC358F33CD2DDB4DFB1FE /* ignored.rs */ = {isa = PBXFileReference; path = ignored.rs; sourceTree = ""; }; - FCC12D491DEEA8F9048BEB3C /* pi_cli.rs */ = {isa = PBXFileReference; path = pi_cli.rs; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - C5C4481C06CDA35095034A53 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - E52656C9AA3CE79F8BD9C340 /* libapp.a in Frameworks */, - 1E3F1D473076F96C5929CEE7 /* CoreGraphics.framework in Frameworks */, - 10CF0B2A75125AB882A4104B /* Metal.framework in Frameworks */, - BFA08714B1F1FFA160554E4E /* MetalKit.framework in Frameworks */, - 888E277D0583DEC4A5F817FA /* QuartzCore.framework in Frameworks */, - 6BA90420617661093E6C004A /* Security.framework in Frameworks */, - 890AF7B1E9D69D4FBA033F99 /* UIKit.framework in Frameworks */, - 10BC764F3DECE7B9FDC8F1EB /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 0EEEEBEC725F0B590CDC8EA7 /* git */ = { - isa = PBXGroup; - children = ( - 4D52F451E32D5D9CA7AFB9AA /* author.rs */, - 9992DE2C71B8768B3E090D80 /* clone.rs */, - 4F0D35BE7398C3602AB06629 /* command.rs */, - 22092BA85067CC737DBBAD05 /* commit.rs */, - 082E515A19172B9A0992BA4F /* conflict.rs */, - 6D03B0E75CD5CDAFA20644BC /* connect.rs */, - 2E55F0E500944FFBEDF36439 /* credentials.rs */, - 3EF2534081FDE3316E028081 /* dates.rs */, - D1CBEA64917CE1F1C3A5589D /* file_url.rs */, - B9F7A500BB678F7C6C5EFA99 /* history.rs */, - 87147D77664A5051BBA28592 /* mod.rs */, - 1BB054E84CD7A1962922A3E2 /* provider.rs */, - 5D6A7764044526D11B69B748 /* pulse.rs */, - 700C8B5C02E5F70B26055FFD /* remote_branch_tests.rs */, - 09D44DF00534FAA275529BF9 /* remote_config.rs */, - A01712BAB3D6830A0BC75710 /* remote_status.rs */, - E479065A9C78E592C84F4944 /* remote_url.rs */, - 81F8B518EC2593A40DAA5503 /* remote.rs */, - 363304D4E0297987B24BB926 /* status.rs */, - 14C532D36F6C1157D9C129D1 /* upstream.rs */, - ); - path = git; - sourceTree = ""; - }; - 24D40E45930F3723C3E550D0 /* commands */ = { - isa = PBXGroup; - children = ( - 7D8F18C847A1FF6B2BDB0356 /* ai.rs */, - F7833823F42B1D5F9031B534 /* app_icon.rs */, - 9B5BEB3EE5C7932B4E065849 /* clipboard.rs */, - 06D4D353ACDD7DA2F35B49E8 /* delete.rs */, - 6B7EB67FFE128AB20A135E6E /* folders.rs */, - D860F78F8B5272FDB3AE6247 /* git_clone.rs */, - DC2E71B56C45C04CAE6565F0 /* git_connect.rs */, - 9A57A197EBB4AEAC23ED6F36 /* git.rs */, - 1CBDAF21C861C3AB97AC482D /* memory.rs */, - 4E947F33478FAB67227CFCCB /* mod.rs */, - 4A46898BA1C969B2646B4B80 /* pdf_export.rs */, - DC4FA3AE9642BBE51F638B89 /* runtime.rs */, - 348A425343E36A5B9DC950F4 /* sheet.rs */, - DC6FADA70D52DCF194FF7D6C /* system.rs */, - 4207C0C689A9DEF15CA8888C /* vault.rs */, - C66F99524B45C69B7210BDEE /* version.rs */, - F195675449CD313E2231565D /* vault */, - ); - path = commands; - sourceTree = ""; - }; - 25D8109B94A4D5FA1ADBFFAC /* mcp */ = { - isa = PBXGroup; - children = ( - DB739EBCB89E3A26BD0494C8 /* extraction.rs */, - 5B0390DD26F6B3080CFE1531 /* opencode.rs */, - 057E964B8FFDE93C6C0DE7F0 /* paths.rs */, - BBE04C1F55BC29695FEE4539 /* runtime.rs */, - 68EDAECA09ECC7BC74B1088C /* subprocess.rs */, - ); - path = mcp; - sourceTree = ""; - }; - 375B38CFEED2D8C01FF6B250 /* src */ = { - isa = PBXGroup; - children = ( - B0488C703D2C6B46C91C47DA /* ai_agent_processes.rs */, - 3CD4014D86632C50AF20DE59 /* ai_agents.rs */, - C7F71AD744A1B18D2DACDC62 /* ai_model_tools.rs */, - 1C41778F8CEEEBAC8FCC5F13 /* ai_models.rs */, - 121AED7D3C84479EC638C2E8 /* antigravity_cli.rs */, - A16DE2463D72666355CA347B /* antigravity_config.rs */, - 20AE80661DFE787E09398F4F /* antigravity_discovery.rs */, - F6671256B6EF31BA8EEB7622 /* app_config.rs */, - 9C68630C9CBDDB447BC5E879 /* app_icon.rs */, - 5BFC39CB9F1243B4474C3BB6 /* app_updater.rs */, - F6CCA1B5F651CD7BE868E102 /* claude_cli.rs */, - E4FA358B012D1EDF0267B63F /* claude_invocation.rs */, - C6CD6117887727F3A4D96F57 /* cli_agent_runtime.rs */, - 010158A81A8DD44ABBAA4BCC /* codex_cli.rs */, - 14962FC08F5BBCA3DFF34DD4 /* copilot_cli.rs */, - A8647B6883D11614B6E97DB3 /* copilot_discovery.rs */, - 6F4D5A90038E5C644B0B92A5 /* hermes_cli.rs */, - 4C1FDAEA4662D9A6260A58DE /* hermes_discovery.rs */, - ECE47DEFF64DBD80238E7C96 /* kiro_cli.rs */, - C3ED1D8403428C88C5F63503 /* kiro_discovery.rs */, - 5A06AA9064684BD5F26156B3 /* lib.rs */, - F4EDF4DE2E778C5A03B94435 /* linux_appimage.rs */, - 2F38683CB597F0CC57478E4A /* main.rs */, - EC2E3E3FD74DE9987F1F326B /* mcp.rs */, - 0075CBC9552C222333830937 /* menu.rs */, - 96BDADEAB5BE019394D4DF4C /* opencode_cli.rs */, - B2ABF84048E93D7CD435A4FA /* opencode_config.rs */, - 9049C59DDCC9763ACB36C5F3 /* opencode_discovery.rs */, - B87EE0F054D9C201C322C9B1 /* opencode_events_tests.rs */, - 3FE3C66FF30B84195FA98C1A /* opencode_events.rs */, - FCC12D491DEEA8F9048BEB3C /* pi_cli.rs */, - 3202961ACB715D8D472F795C /* pi_config.rs */, - 816915173E861F05D186A727 /* pi_discovery.rs */, - EBBF2E18D884BEAE0BB5EABE /* pi_events_tests.rs */, - 0FCCFC9E2A1E037C3491E7F5 /* pi_events.rs */, - 9D7F16C81C610385B6CA9B46 /* search.rs */, - F4F90594F8CD2DB418876246 /* settings.rs */, - 7BF95AB601E1EA43C76FEE79 /* telemetry.rs */, - A64C1E3DB12B7D51417FBED4 /* vault_list.rs */, - 458437264D37E162C02F7E01 /* vault_watcher.rs */, - 3D266F3E587C8CAFD43897AB /* window_state.rs */, - 70C8904AC6DCA135C23A111F /* cli_agent_runtime */, - 24D40E45930F3723C3E550D0 /* commands */, - DC70A0AE2754CAB63FF390CC /* frontmatter */, - 0EEEEBEC725F0B590CDC8EA7 /* git */, - 25D8109B94A4D5FA1ADBFFAC /* mcp */, - ED344194F7B84C233B93DD09 /* vault */, - ); - name = src; - path = ../../src; - sourceTree = ""; - }; - 53685D32BD42A5AD34D93205 /* bindings */ = { - isa = PBXGroup; - children = ( - 5450814D3827CCD69F01A241 /* bindings.h */, - ); - path = bindings; - sourceTree = ""; - }; - 62A4B7D340F2686BFC21EEA1 /* Externals */ = { - isa = PBXGroup; - children = ( - ); - path = Externals; - sourceTree = ""; - }; - 70C8904AC6DCA135C23A111F /* cli_agent_runtime */ = { - isa = PBXGroup; - children = ( - F304454433962A99CF538071 /* line_stream.rs */, - B43462DD1247187BEC7B2867 /* mcp_config.rs */, - D3757B9773F2FA1B992C7EB1 /* shell_env.rs */, - 8FD8EFD959EE4C4185BD712D /* windows_cmd_shim.rs */, - ); - path = cli_agent_runtime; - sourceTree = ""; - }; - 8E68112B2BC77FD0176FC686 /* laputa */ = { - isa = PBXGroup; - children = ( - 6A8D7ABE8518DC962AEE83D2 /* main.mm */, - 53685D32BD42A5AD34D93205 /* bindings */, - ); - path = laputa; - sourceTree = ""; - }; - 9E882C91B5A089FA290CC95F /* mod_tests */ = { - isa = PBXGroup; - children = ( - 675DE5C585D832EC23F4BFF3 /* archival_metadata.rs */, - BA4747AF028A869AA1A1A6DC /* basics.rs */, - 7A12A53788441DDEC467ADF9 /* complex_frontmatter.rs */, - 22EBF553582EACF370284B42 /* display_metadata.rs */, - 01A36EEB3D914E2049B80F8C /* folder_and_file_kind.rs */, - FB24334BDF17362ADB6AFAF8 /* journal_type_visibility.rs */, - 6F0DA82A2114B791EDAB7300 /* real_vault_consistency.rs */, - BD49C0127DD47B9259DDB6DC /* relationships.rs */, - 85A5FC54DAD705312260A21A /* scan_and_file_access.rs */, - 73CCD2E1120A28AD85F99137 /* type_and_links.rs */, - ); - path = mod_tests; - sourceTree = ""; - }; - ADB8E0CEBBD4C6248828DC1E /* laputa_iOS */ = { - isa = PBXGroup; - children = ( - 9A611472FDB8AC605F500C3F /* Info.plist */, - EA0CD2EF0B8E39E98114B8B1 /* laputa_iOS.entitlements */, - ); - path = laputa_iOS; - sourceTree = ""; - }; - BCC76595F04AD2075FB85DBD = { - isa = PBXGroup; - children = ( - 0F60A50A996D2D783A5FD25F /* assets */, - 3BBE6E79A4BF3832CE315919 /* Assets.xcassets */, - A9D939D7C6842A013F9E9D2D /* LaunchScreen.storyboard */, - 62A4B7D340F2686BFC21EEA1 /* Externals */, - ADB8E0CEBBD4C6248828DC1E /* laputa_iOS */, - D07B2BBD7D85A1140E7B1325 /* Sources */, - 375B38CFEED2D8C01FF6B250 /* src */, - F8799DD23A051DB64ABA21E1 /* Frameworks */, - BDD7263C80436D6C14C3E2BB /* Products */, - ); - sourceTree = ""; - }; - BDD7263C80436D6C14C3E2BB /* Products */ = { - isa = PBXGroup; - children = ( - 5986643AE3444A64F2EC3699 /* HoloLake_iOS.app */, - ); - name = Products; - sourceTree = ""; - }; - D07B2BBD7D85A1140E7B1325 /* Sources */ = { - isa = PBXGroup; - children = ( - 8E68112B2BC77FD0176FC686 /* laputa */, - ); - path = Sources; - sourceTree = ""; - }; - DC70A0AE2754CAB63FF390CC /* frontmatter */ = { - isa = PBXGroup; - children = ( - B88D960D36A57345E09B5B80 /* keys.rs */, - 1179CD8FF58077DDF7E99299 /* mod.rs */, - D8B35D0B4002E064A68C5918 /* ops_update_tests.rs */, - C55B8EDBBEB11312B57B7591 /* ops.rs */, - C2AD8E13B962ECF439B7E1E5 /* yaml.rs */, - ); - path = frontmatter; - sourceTree = ""; - }; - ED344194F7B84C233B93DD09 /* vault */ = { - isa = PBXGroup; - children = ( - 0050B284AED1F6607547DDDF /* cache.rs */, - 7D2B082E58B3456C21A81C48 /* config_seed.rs */, - 42D0C09784FF734A5CC92E40 /* entry.rs */, - CECEC916A37B4A001FEEFF9A /* file.rs */, - F2C168663A35EAABC3AC0E00 /* filename_rules.rs */, - 26948CCB253A8A9438E1DFF6 /* folders.rs */, - 74958B90904CF6F4DD914C06 /* frontmatter_regression_tests.rs */, - 9EAFBB5DB3A9DC0A7B2E8FF6 /* frontmatter.rs */, - 128C9606725F612692B8438E /* getting_started.rs */, - FB6CC358F33CD2DDB4DFB1FE /* ignored.rs */, - 5766FC68AFF52E5D601A2201 /* image.rs */, - 09754A5CDE571D89F3563080 /* migration.rs */, - 8D2E06DCBF77C0D6AC0FB24C /* mod_tests.rs */, - 818CA203C2C91ABABF95A8CB /* mod.rs */, - BC1398337782B4F13550069B /* modified_dates_tests.rs */, - 76DEFD594E4794AA24544225 /* parsing_tests.rs */, - 463BC00E27FDD63E84BFDACD /* parsing.rs */, - 015F375350B0DCCF932CD855 /* path_identity.rs */, - AE2B2E577D7A7ABD77E37F57 /* relationship_key_tests.rs */, - A8F4939901A70030BD872898 /* rename_transaction.rs */, - A8F3EABFCD65C8663D7D2FDC /* rename.rs */, - 2869AD5AF9EBC3A2F6F89A96 /* system_metadata_tests.rs */, - 2FC6224DED146A056DBEFAFA /* title_sync.rs */, - 7B0BC7CD8DCDBD326123B1E1 /* trash.rs */, - 6E98CFB59596D798EB7DCB34 /* type_templates.rs */, - 8961178AD8EA4CC56A023659 /* view_date_filters.rs */, - C0126E76F91A13BBDBC5904C /* view_migration.rs */, - 66E7FE04B14503C592B73CD0 /* view_relationships.rs */, - CBE02C8F152E451278EA827C /* view_tests.rs */, - 0BF29A2DBD08245184C62509 /* view_value_conversions.rs */, - A1AE659A16F1D92E77E9E687 /* views.rs */, - 9E882C91B5A089FA290CC95F /* mod_tests */, - ); - path = vault; - sourceTree = ""; - }; - F195675449CD313E2231565D /* vault */ = { - isa = PBXGroup; - children = ( - A0FB232A6B19885BD9BDCA62 /* boundary.rs */, - CAA8F8764EF165BAD372E0AB /* file_cmds.rs */, - F77DDF68DB111E6E824780D4 /* frontmatter_cmds.rs */, - 41B8445BDA2EEFFB636C2F27 /* lifecycle_cmds.rs */, - 057242D51175B2DA743FFEEC /* rename_cmds.rs */, - 1315706C702DC2397F3B802E /* scan_cmds.rs */, - 354F27756754D7E66226E8C8 /* view_cmds.rs */, - ); - path = vault; - sourceTree = ""; - }; - F8799DD23A051DB64ABA21E1 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 8E5CBD70FB2815D19C3F0190 /* CoreGraphics.framework */, - 907D2515583F8E3DAA94C653 /* libapp.a */, - 60D77C95CDA4878AC31E06FC /* Metal.framework */, - C68619FE2409BD0403F0F822 /* MetalKit.framework */, - 28F8BF20C12EF3B0D970C4BE /* QuartzCore.framework */, - 476D4C25E501B2DB819B83EF /* Security.framework */, - 26BB1CFE5D89468C0954F3D7 /* UIKit.framework */, - 12F6AA9DA589FC51C9D843C3 /* WebKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 7C405E12F28A63ECDF863C30 /* HoloLake_iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1BC66CB9C55FA203BDD14661 /* Build configuration list for PBXNativeTarget "HoloLake_iOS" */; - buildPhases = ( - 0AD8A0E8790089BF8E7F3851 /* Build Rust Code */, - FDE5DF4B6ABD5475F59F7853 /* Sources */, - 3AC97F5F4F4789DB85510F50 /* Resources */, - C5C4481C06CDA35095034A53 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = HoloLake_iOS; - packageProductDependencies = ( - ); - productName = HoloLake_iOS; - productReference = 5986643AE3444A64F2EC3699 /* HoloLake_iOS.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - D0324676016167B5308CE31A /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - TargetAttributes = { - }; - }; - buildConfigurationList = 5536EFA81EFA6631A115A48E /* Build configuration list for PBXProject "HoloLake" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = BCC76595F04AD2075FB85DBD; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = BDD7263C80436D6C14C3E2BB /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 7C405E12F28A63ECDF863C30 /* HoloLake_iOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 3AC97F5F4F4789DB85510F50 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 30810FE969AAA1DFE091637C /* Assets.xcassets in Resources */, - A22C9A35CAAB2DF45F783F3B /* LaunchScreen.storyboard in Resources */, - 574E9FC1DB877392B7BF5918 /* assets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 0AD8A0E8790089BF8E7F3851 /* Build Rust Code */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Build Rust Code"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", - "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "npm run -- tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - FDE5DF4B6ABD5475F59F7853 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9E4FC2FF2D11E16C85D0CA22 /* main.mm in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 59094C00BE8A55D183856EE8 /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = laputa_iOS/laputa_iOS.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = laputa_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - PRODUCT_BUNDLE_IDENTIFIER = com.guanghulab.hololake; - PRODUCT_NAME = "HoloLake Era"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = arm64; - }; - name = debug; - }; - 6AA0389A83C50D9AFAFB7CBD /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - }; - name = release; - }; - 8D6F3195A1CD4CA412A8E96A /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = debug; - }; - CEA1A776C8E7A07EB2E0981B /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = laputa_iOS/laputa_iOS.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = laputa_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - PRODUCT_BUNDLE_IDENTIFIER = com.guanghulab.hololake; - PRODUCT_NAME = "HoloLake Era"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = arm64; - }; - name = release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1BC66CB9C55FA203BDD14661 /* Build configuration list for PBXNativeTarget "HoloLake_iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 59094C00BE8A55D183856EE8 /* debug */, - CEA1A776C8E7A07EB2E0981B /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; - 5536EFA81EFA6631A115A48E /* Build configuration list for PBXProject "HoloLake" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8D6F3195A1CD4CA412A8E96A /* debug */, - 6AA0389A83C50D9AFAFB7CBD /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; -/* End XCConfigurationList section */ - }; - rootObject = D0324676016167B5308CE31A /* Project object */; -} diff --git a/src-tauri/gen/apple/Podfile b/src-tauri/gen/apple/Podfile index cad1985..bf19b5f 100644 --- a/src-tauri/gen/apple/Podfile +++ b/src-tauri/gen/apple/Podfile @@ -5,9 +5,9 @@ platform :ios, '15.0' # Pods for HoloLake_iOS end -target 'laputa_macOS' do +target 'tolaria_macOS' do platform :osx, '11.0' - # Pods for laputa_macOS + # Pods for tolaria_macOS end # Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile diff --git a/src-tauri/gen/apple/Sources/laputa/bindings/bindings.h b/src-tauri/gen/apple/Sources/hololake/bindings/bindings.h similarity index 100% rename from src-tauri/gen/apple/Sources/laputa/bindings/bindings.h rename to src-tauri/gen/apple/Sources/hololake/bindings/bindings.h diff --git a/src-tauri/gen/apple/Sources/laputa/main.mm b/src-tauri/gen/apple/Sources/hololake/main.mm similarity index 100% rename from src-tauri/gen/apple/Sources/laputa/main.mm rename to src-tauri/gen/apple/Sources/hololake/main.mm diff --git a/src-tauri/gen/apple/assets/agent-docs/AGENTS.md b/src-tauri/gen/apple/assets/agent-docs/AGENTS.md new file mode 100644 index 0000000..a090776 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/AGENTS.md @@ -0,0 +1,15 @@ +# AGENTS.md - Tolaria Docs Bundle + +This folder contains local, generated Tolaria product docs for AI agents. + +Use these docs when a user asks how Tolaria works, when you need product behavior, or before making Tolaria-specific assumptions. + +Portent is the default best-practice model for structuring Tolaria knowledge bases. When a user asks how to improve a knowledge base, make it better organized, choose better types, model relationships, or make the vault easier for humans and agents to use, read `pages/templates/portent.md` and combine it with Tolaria's docs for types, relationships, properties, Inbox, archive, and custom views. + +Recommended lookup flow: + +1. Read the active vault's AGENTS.md for vault-specific conventions. +2. Read this folder's index.md for the docs map. +3. Use `rg` over this folder for advanced concepts, workflows, shortcuts, Git, AutoGit, AI, Portent, types, properties, relationships, and troubleshooting. + +Vault-specific AGENTS.md wins for local conventions. These bundled docs win for Tolaria product behavior. diff --git a/src-tauri/gen/apple/assets/agent-docs/ai-agent-not-found.md b/src-tauri/gen/apple/assets/agent-docs/ai-agent-not-found.md new file mode 100644 index 0000000..613407e --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/ai-agent-not-found.md @@ -0,0 +1,27 @@ +# AI Agent Not Found + +Source: troubleshooting/ai-agent-not-found.md +URL: /troubleshooting/ai-agent-not-found + +# AI Agent Not Found + +Tolaria can only launch local CLI agents that are installed and discoverable. + +## Symptoms + +- The AI panel says no supported agent is available. +- Claude Code or another agent works in one shell but not in Tolaria. + +## Checks + +Open a terminal and run the agent command directly. For Claude Code: + +```bash +claude --version +``` + +If the command fails, install or repair the agent first. + +## Path Issues + +Desktop apps can inherit a different `PATH` from your interactive shell. Tolaria checks common install locations, but shell setup can still vary. Prefer installing CLI tools in standard locations or making them available from your login shell. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/ai.md b/src-tauri/gen/apple/assets/agent-docs/ai.md new file mode 100644 index 0000000..28953a3 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/ai.md @@ -0,0 +1,37 @@ +# AI + +Source: concepts/ai.md +URL: /concepts/ai + +# AI + +Tolaria has two AI paths: coding agents that can use tools to inspect and edit a vault, and direct model targets that answer in chat mode from note context. + +## Coding Agents + +The AI panel can stream supported local CLI agents through Tolaria's normalized event layer. Current targets include Claude Code, Codex, OpenCode, Pi, and Antigravity CLI when they are installed on the machine. + +Coding agents can run in: + +- **Vault Safe** mode, limited to file, search, and edit tools. +- **Power User** mode, which can allow local shell commands scoped to the active vault for agents that support shell access. + +## Direct Models + +Direct model targets run in chat mode. They receive the active note, linked context, and conversation history, but they do not receive vault-write tools or shell access. + +Supported provider shapes include: + +- Local models through Ollama or LM Studio. +- Hosted providers such as OpenAI, Anthropic, Gemini, and OpenRouter. +- Custom OpenAI-compatible endpoints. + +## External MCP Setup + +Tolaria exposes an MCP server for external tools. The setup flow can write Tolaria's MCP entry into Claude Code, Antigravity CLI, Cursor, and a generic MCP config path, and it can also copy the exact JSON snippet for manual setup. + +MCP setup is explicit. Closing the dialog leaves third-party config files untouched. + +## Why Git Matters For AI + +AI-generated changes should be inspectable. Git gives you diffs, history, rollback, and a clear boundary between suggestions and committed work. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/all.md b/src-tauri/gen/apple/assets/agent-docs/all.md new file mode 100644 index 0000000..d8ee4db --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/all.md @@ -0,0 +1,2254 @@ +# Index + +Source: index.md +URL: / + + + +--- + +# First Launch + +Source: start/first-launch.md +URL: /start/first-launch + +# First Launch + +The first launch flow is designed to get you into a real vault quickly without hiding the local-first model. + +## What You Choose + +Tolaria asks whether you want to: + +- Create or clone the Getting Started vault. +- Open an existing local vault. +- Create a new empty vault. + +The Getting Started vault is cloned locally and then disconnected from its remote. That keeps the sample safe to edit without accidentally pushing tutorial changes. + +## What Tolaria Creates + +Tolaria stores app-level settings on the local machine. Your notes stay in the vault folder you choose. + +| Data | Stored in | +| --- | --- | +| Notes and attachments | Your vault folder | +| Type definitions and saved views | Your vault folder | +| Window size, zoom, recent vaults | Local app settings | +| Cache data | Rebuildable local cache | + +## First Commands To Try + +- `Cmd+K` / `Ctrl+K`: open the command palette. +- `New Note`: create a note in the current vault. +- `Open Getting Started Vault`: clone the public sample vault. +- `Reload Vault`: rescan files after external edits. + +## AI Setup Prompt + +Tolaria can show an optional AI agents prompt after a vault is open. It checks common local install locations for supported coding agents and gives you setup paths, but you can dismiss it and use Tolaria without AI. + +--- + +# Getting Started Vault + +Source: start/getting-started-vault.md +URL: /start/getting-started-vault + +# Getting Started Vault + +The Getting Started vault is a small public sample vault hosted at [refactoringhq/tolaria-getting-started](https://github.com/refactoringhq/tolaria-getting-started). + +It exists to show Tolaria's conventions without requiring you to restructure your own notes first. + +## What It Demonstrates + +- Markdown notes with YAML frontmatter. +- Types such as Project, Person, Topic, and Procedure. +- Wikilinks in note bodies. +- Relationship fields in frontmatter. +- A local Git repository that can be connected to a remote later. +- Vault guidance files for AI agents. + +## Local-Only By Default + +When Tolaria clones the sample, it removes the remote from the local copy. This makes the sample vault disposable. You can edit it freely, commit locally, and delete it later. + +To connect a vault to your own remote, use the bottom status bar remote chip or run `Add Remote` from the command palette. + +Tolaria also repairs starter-vault guidance files when needed. `AGENTS.md` is the canonical guidance file, `CLAUDE.md` is kept as a compatibility shim, and `GEMINI.md` is only created when you explicitly restore Antigravity/Gemini guidance. + +## Use It Alongside Your Own Vaults + +You can keep the Getting Started vault open while working in your own notes. Enable `Settings` -> `Vaults` -> `Use multiple vaults at the same time`, then use the bottom-left vault menu to include both the sample vault and your real vault in the unified graph. + +This lets search, quick open, note lists, backlinks, and wikilink navigation span both vaults. Git actions still stay scoped to each vault's own repository, and new notes go to the default vault you choose in `Manage vaults`. + +## When To Move On + +After you understand the sample, open your own vault. Tolaria does not require a special folder structure: a folder of Markdown files is enough to start. You can remove the sample from Tolaria's vault list later without deleting its files from disk. + +--- + +# Install Tolaria + +Source: start/install.md +URL: /start/install + +# Install Tolaria + +Tolaria publishes desktop builds for macOS, Windows, and Linux. macOS is the primary day-to-day development target, with Windows and Linux builds supported through the release pipeline and fixed as platform issues are found. + +## Download + +Use the latest stable release unless you are intentionally testing pre-release builds: + +- Download the latest stable build +- [Browse all GitHub releases](https://github.com/refactoringhq/tolaria/releases) +- Read the release notes + +## Homebrew + +On macOS you can install the cask: + +```bash +brew install --cask tolaria +``` + +## Platform Status + +| Platform | Status | Notes | +| --- | --- | --- | +| macOS | Primary | Apple Silicon and Intel builds are published. Homebrew is available. | +| Windows | Supported, early | NSIS installers and updater bundles are Tauri-signed. Authenticode publisher signing will be added after Windows certificate provisioning; company-managed SmartScreen, Defender, or WDAC policies can still require IT approval before install. | +| Linux | Supported, early | AppImage, deb, and RPM artifacts are published. Desktop behavior depends on distribution WebKitGTK and input-method integration. | + +See [Supported Platforms](/reference/supported-platforms) for the current support policy. + +## Managed Windows Devices + +Do not disable SmartScreen or Windows Security to install Tolaria. On a managed Windows device, install Tolaria through your normal software approval path if policy blocks unsigned or unknown-publisher installers. After Authenticode provisioning is complete, validate that the downloaded installer has a valid Tolaria publisher signature before installing. + +## After Installing + +1. Open Tolaria. +2. Choose the Getting Started vault if you want a guided sample. +3. Or open an existing folder of Markdown files as a vault. +4. Use the command palette with `Cmd+K` on macOS or `Ctrl+K` on Linux and Windows. + +--- + +# Open Or Create A Vault + +Source: start/open-or-create-vault.md +URL: /start/open-or-create-vault + +# Open Or Create A Vault + +A Tolaria vault is a folder on disk. The folder can contain Markdown notes, attachments, type definitions, saved views, and Git metadata. + +## Open An Existing Folder + +Choose an existing folder if you already have Markdown notes. Tolaria scans `.md` files and uses frontmatter when it exists. + +Good starting points: + +- A folder of plain Markdown files. +- An Obsidian-style vault. +- A Git repository containing notes. +- A copy of the Getting Started vault. + +## Create A New Vault + +Choose a new empty folder if you want Tolaria conventions from the start. New notes and optional type definitions are created as Markdown files. + +## Use More Than One Vault + +You do not have to merge everything into one folder. Register each local folder as its own vault, then turn on `Use multiple vaults at the same time` in `Settings` -> `Vaults`. + +Once enabled, the bottom-left vault menu lets you include vaults in the unified graph. Search, quick open, wikilinks, and note lists can span the included vaults, while Git sync and commits remain tied to each vault's own repository. + +## Git Is Recommended, Not Required + +Tolaria works well with a plain folder of Markdown files. You can open, edit, organize, and search notes without making the vault a Git repository. + +Git is recommended when you want local history, diff views, recovery, pull, push, and remote sync without a proprietary backend. If a vault is not already a repository, Tolaria can initialize one when you explicitly ask it to. + +--- + +# AI + +Source: concepts/ai.md +URL: /concepts/ai + +# AI + +Tolaria has two AI paths: coding agents that can use tools to inspect and edit a vault, and direct model targets that answer in chat mode from note context. + +## Coding Agents + +The AI panel can stream supported local CLI agents through Tolaria's normalized event layer. Current targets include Claude Code, Codex, OpenCode, Pi, and Antigravity CLI when they are installed on the machine. + +Coding agents can run in: + +- **Vault Safe** mode, limited to file, search, and edit tools. +- **Power User** mode, which can allow local shell commands scoped to the active vault for agents that support shell access. + +## Direct Models + +Direct model targets run in chat mode. They receive the active note, linked context, and conversation history, but they do not receive vault-write tools or shell access. + +Supported provider shapes include: + +- Local models through Ollama or LM Studio. +- Hosted providers such as OpenAI, Anthropic, Gemini, and OpenRouter. +- Custom OpenAI-compatible endpoints. + +## External MCP Setup + +Tolaria exposes an MCP server for external tools. The setup flow can write Tolaria's MCP entry into Claude Code, Antigravity CLI, Cursor, and a generic MCP config path, and it can also copy the exact JSON snippet for manual setup. + +MCP setup is explicit. Closing the dialog leaves third-party config files untouched. + +## Why Git Matters For AI + +AI-generated changes should be inspectable. Git gives you diffs, history, rollback, and a clear boundary between suggestions and committed work. + +--- + +# Editor + +Source: concepts/editor.md +URL: /concepts/editor + +# Editor + +Tolaria offers a rich editor for daily writing and a raw Markdown mode for exact file control. Both modes write back to the same Markdown file. + +## Rich Editing + +The rich editor supports blocks, slash commands, wikilinks, tables, code blocks, images, Mermaid diagrams, LaTeX-style math, and markdown-backed whiteboards. + +Use it when you want to write and reorganize quickly without thinking about Markdown syntax. + +## Raw Mode + +Raw mode shows the Markdown source directly. Use it when you need to edit YAML frontmatter, repair unusual Markdown, or make an exact text change. + +Toggle raw mode with `Cmd+\` on macOS or `Ctrl+\` on Windows and Linux. + +## Table Of Contents + +The table of contents panel builds an outline from headings in the current note. It is useful for long notes, procedures, research files, and generated documents. Toggle it with `Cmd+Shift+T` on macOS or `Ctrl+Shift+T` on Windows and Linux. + +## Width + +Notes can use normal or wide editor width. Set the default in Settings, or override an individual note from the editor toolbar. + +--- + +# Files And Media + +Source: concepts/files-and-media.md +URL: /concepts/files-and-media + +# Files And Media + +Tolaria starts with Markdown notes, but a vault can also contain images, PDFs, media files, whiteboards, and other local files. + +## Mermaid Diagrams + +Use Mermaid code blocks when a note needs a diagram that should stay plain text and versionable. + +````md +```mermaid +flowchart LR + Idea --> Draft --> Review --> Publish +``` +```` + +Tolaria renders Mermaid diagrams in the editor while keeping the source in Markdown. + +## Attachments + +Images pasted into the editor are saved into the vault as normal files. They remain portable and can be opened by other tools. + +## Previews + +Tolaria can preview common image files, PDFs, and supported media files in the app. Files without an in-app preview can still be opened in the default system app. + +Settings control whether PDFs, images, and unsupported files appear in All Notes. Folder browsing still shows files in their folders. + +## Whiteboards + +Whiteboards use tldraw in the editor, but their durable representation stays in Markdown. That keeps them inside the vault and versioned by Git with the rest of your notes. + +## Git Boundary + +If generated or local-only files are ignored by Git, Tolaria can hide them from notes, search, quick open, and folders. Use this when build artifacts or private local files should not behave like vault content. + +--- + +# Git + +Source: concepts/git.md +URL: /concepts/git + +# Git + +Git is Tolaria's recommended history and sync layer. Tolaria can work with plain Markdown folders, and Git unlocks local history, recovery, remote backup, and multi-device workflows when you want them. + +Tolaria acts as a lightweight Git client for your vault. You can review changes, commit, pull, push, and inspect history without leaving the app. + +## What Tolaria Uses Git For + +- Whole-vault commit history. +- Current diff for the vault. +- Per-note history. +- Current diff for an individual note. +- Pull and push. +- Conflict detection and resolution. +- Remote connection for local-only vaults. + +## History And Diffs + +Each note can show its own history and current diff, so you can understand how that file changed over time or what is unsaved relative to Git. + +Tolaria also shows a history of the whole vault. Use it when you want to review broader changes across multiple notes before committing or syncing. + +## Local Commits + +You can commit changes inside Tolaria without leaving the app. This gives you useful restore points even before a remote is configured. + +## Remotes + +Connect a compatible Git remote when you want sync or backup. Tolaria relies on your system Git authentication, so GitHub CLI, SSH keys, credential helpers, and existing Git configuration can continue to work. + +--- + +# Inbox + +Source: concepts/inbox.md +URL: /concepts/inbox + +# Inbox + +The Inbox is for notes that have been captured but not yet organized. + +## Why It Exists + +Fast capture should not require perfect structure. The Inbox gives you a place to put incomplete notes, then process them later. + +The Inbox workflow is optional. Turn it off in Settings > Workflow if you prefer every note to appear organized by default. + +## Organizing Inbox Notes + +When reviewing the Inbox: + +1. Give the note a clear H1. +2. Set its `type`. +3. Add status, dates, or URL if useful. +4. Add relationships with wikilinks or frontmatter fields. +5. Move it into a folder only if the folder adds value. + +## Healthy Inbox Habit + +Keep the Inbox small enough that it can be reviewed in one focused pass. Tolaria works best when capture is fast and organization is deliberate. + +--- + +# Notes + +Source: concepts/notes.md +URL: /concepts/notes + +# Notes + +A note is a Markdown file with optional YAML frontmatter. Tolaria reads the first H1 as the primary title and keeps the file on disk as the durable representation. + +## Anatomy + +```md +--- +type: Project +status: Active +belongs_to: + - "[[workspace]]" +--- + +# Launch Documentation + +Draft the public Tolaria docs and keep them close to code changes. +``` + +## Titles + +The first H1 is the note title. Tolaria uses that title wherever the note is displayed: note lists, search results, wikilink suggestions, relationship pickers, tabs, and window titles. + +The title is separate from the filename. The filename stays visible in the breadcrumb so you can see the file on disk, and you can rename it independently when needed. + +Use the breadcrumb action to rename the file to match the title. New untitled notes can also auto-rename from the first H1 the first time they get a real title. Turn this behavior off in Settings > Vault Content > Titles & Filenames if you prefer filenames to stay unchanged until you rename them manually. + +## Body Links + +Use `[[wikilinks]]` to connect notes from the body. Tolaria shows autocomplete suggestions while you type, and links can resolve by filename or title. + +## Frontmatter + +Use frontmatter for structured fields such as type, status, date, URL, and relationships. Keep free-form thinking in the body. + +Some notes can be displayed with specialized editors while keeping the same file-first model. A note with `_display: sheet` opens as a spreadsheet and stores its cells in a CSV-like body, while `type` remains available for organization. See [Spreadsheets](/concepts/spreadsheets). + +--- + +# Properties + +Source: concepts/properties.md +URL: /concepts/properties + +# Properties + +Properties are frontmatter fields that Tolaria can display, filter, and edit. + +## Suggested Properties + +Suggested properties are the fields Tolaria knows how to create quickly from the Properties panel. When a suggested property is missing, the panel shows a shortcut to add it with the right editor. + +| Field | Purpose | +| --- | --- | +| `type` | Groups the note into a type such as Project, Person, or Topic. | +| `status` | Tracks lifecycle state such as Active, Done, or Blocked. | +| `url` | Stores a canonical external link. | +| `date` | Represents a single date. | + +## System Properties + +Fields that start with `_` are system properties. They remain in plain text but are hidden from normal property editing. + +Examples include `_icon`, `_color`, `_order`, `_sidebar_label`, `_width`, and `_pinned_properties` on type documents or notes. + +## Property Editing + +The Properties panel is the safest place to edit structured properties. Toggle it with `Cmd+Shift+I` on macOS or `Ctrl+Shift+I` on Windows and Linux. + +Date fields use Tolaria's picker, relationship fields can use wikilinks, and raw Markdown mode is available when you need direct control over YAML. + +--- + +# Relationships + +Source: concepts/relationships.md +URL: /concepts/relationships + +# Relationships + +Relationships make a vault feel like a graph instead of a pile of documents. + +## Relationship Fields + +Any frontmatter field containing wikilinks can become a relationship. Relationship fields can point to one note or to an array of notes. + +```yaml +belongs_to: + - "[[product-work]]" +related_to: + - "[[documentation]]" + - "[[editor-research]]" +blocked_by: + - "[[release-process]]" + - "[[sync-conflicts]]" +``` + +Tolaria supports default relationship fields out of the box: `belongs_to`, `has`, and `related_to`. It also detects custom relationship fields dynamically when they contain wikilinks. + +Default relationships have automatically computed inverses. If a note says it `belongs_to` a project, the project can show that note under its inverse `has` relationship without you writing the reverse link by hand. `related_to` works as a lateral relationship in both directions. + +These outgoing and inverse relationships appear in the Properties panel and in Neighborhood mode, where the note list becomes a graph view around the selected note. + +## Body Links Versus Relationship Fields + +Use body links when the relationship appears naturally in writing. Use frontmatter relationships when the connection is important enough to show in navigation, filters, Neighborhood mode, or the Properties panel. + +## Backlinks + +Tolaria can show incoming links and inverse relationships, making it easier to navigate from a note to the rest of its context. + +--- + +# Spreadsheets + +Source: concepts/spreadsheets.md +URL: /concepts/spreadsheets + +# Spreadsheets + +Tolaria sheets are spreadsheet notes. They keep the same file-first model as other notes, but a note with `_display: sheet` opens in a spreadsheet editor instead of the rich text editor. The note's `type` remains available for organization. + +The durable file is still Markdown with YAML frontmatter. The body is CSV-like text containing cell inputs and formulas, and spreadsheet presentation state is stored as plain YAML under `_sheet`. + +## Read Next + +- [Use Spreadsheets](/guides/use-spreadsheets) for the editing workflow. +- [Spreadsheet File Format](/reference/spreadsheet-format) for the plain-text storage contract. +- [Spreadsheet Formulas](/reference/spreadsheet-functions) for formula syntax, autocomplete, and IronCalc function families. + +## Why Sheet Notes + +Sheets are useful when information is better modeled as rows, columns, and formulas than as prose. Examples include budgets, revenue models, inventories, editorial calendars, lightweight trackers, and analytical scratchpads. + +Tolaria does not store sheets as opaque workbook binaries. A sheet should remain: + +- readable in a text editor +- diffable in Git +- editable by humans and AI agents +- available offline +- connected to the rest of the vault through types, properties, relationships, and wikilinks + +## One Note, One Sheet + +A sheet note is a single sheet. Tolaria does not expose multiple tabs inside one note. + +When a model needs more than one table, create more than one sheet note and connect them with wikilinks or cross-sheet formulas. This keeps each file small, legible, and aligned with Tolaria's graph model. + +For example: + +- `newsletter-revenue.md` +- `sponsorship-pipeline.md` +- `refactoring-business-plan.md` + +Each can be a normal `_display: sheet` note, and formulas can reference cells in another sheet note with Tolaria's wikilink cell syntax. + +## Editing + +The interactive sheet editor is backed by IronCalc. Tolaria uses IronCalc for spreadsheet behavior and formula evaluation, then adapts the workbook back to Tolaria's plain-text note format. + +In the sheet editor: + +- cell inputs that start with `=` are formulas +- non-formula cells can contain normal text, numbers, dates, and `[[wikilinks]]` +- typing `[[` in a cell opens the same note autocomplete concept used elsewhere in Tolaria +- typing a formula function name opens inline formula autocomplete for the bundled IronCalc function catalog +- right-clicking a selection exposes core formatting controls such as number formats, decimal precision, bold, italic, and clear formatting + +Keyboard basics follow spreadsheet conventions: + +- arrow keys move the active cell +- `Shift` with arrows extends the selection +- `Enter` starts editing the active cell +- `Escape` exits cell editing while keeping focus in the sheet +- `Delete` or `Backspace` clears the selected range +- copy and paste should preserve formulas, including Tolaria cross-sheet references + +## Wikilinks In Cells + +Wikilinks in non-formula cells are stored as normal Tolaria wikilinks: + +```csv +Project,Owner,Status +[[website-redesign]],[[person/alice]],Active +[[sponsorship-pipeline]],[[person/matteo]],Review +``` + +The cell still behaves like a spreadsheet cell, but the value remains a vault link that Tolaria can understand. + +## Note Reference Formulas + +Tolaria adds a sheet-note reference syntax on top of IronCalc formulas: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=[[refactoring-business-plan]].$C$18 +``` + +The target before the dot is a normal Tolaria wikilink target. For another sheet note, the part after the dot is an A1-style cell address. + +Relative and absolute references work like spreadsheet references when copied: + +- `[[revenue]].B5` can shift when pasted to another cell +- `[[revenue]].$B$5` stays fixed +- `[[revenue]].B$5` fixes the row +- `[[revenue]].$B5` fixes the column + +This is not the same as an IronCalc workbook tab reference. It is Tolaria-specific syntax for referencing another sheet note in the vault. + +Current cross-sheet formulas resolve single cells. Ranges across sheet notes are not a stable file-format feature yet, so prefer composing them from explicit cell references or keeping range formulas inside the same sheet note. + +Sheet formulas can also read scalar frontmatter properties from a note: + +```txt +=[[device]].power.watts +=[[project-alpha]].status +``` + +This keeps sheet models connected to ordinary Tolaria metadata without requiring a saved view or query. Unresolved, ambiguous, or non-scalar property references show spreadsheet errors. + +## Storage + +A minimal sheet note looks like this: + +```md +--- +type: Project +_display: sheet +status: Draft +belongs_to: + - "[[business-plan]]" +_sheet: + frozen_rows: 1 + columns: + A: + width: 180 + cells: + E6: + num_fmt: "0.00%" +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Services,800,900,750,=SUM(B3:D3) +Expenses,650,700,760,=SUM(B4:D4) +Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5) +Growth,,=(C5-B5)/B5,=(D5-C5)/C5,=(E5-B5)/B5 +``` + +Normal frontmatter stays normal Tolaria metadata. `_sheet` is system metadata for the spreadsheet editor and is hidden from normal property editing. + +For the full storage contract, see [Spreadsheet File Format](/reference/spreadsheet-format). + +## Formulas + +Tolaria delegates formula calculation to IronCalc. IronCalc aims for Excel-compatible formulas, while its project documentation still describes it as work in progress. For Tolaria-specific formula behavior and the autocomplete function catalog, see [Spreadsheet Formulas](/reference/spreadsheet-functions). + +--- + +# Types + +Source: concepts/types.md +URL: /concepts/types + +# Types + +Types describe what kind of thing a note represents: Project, Person, Topic, Procedure, Event, or any category you create. + +## Type Field + +The `type:` field assigns a note to a type. + +```yaml +type: Project +``` + +Tolaria does not infer type from folder location. Moving a file into another folder does not change its type. + +## Prefer Types Over Folders + +Types are the preferred way to group notes in Tolaria. Folders are supported for existing vaults and fallback organization, but Tolaria is built around types and relationships because they carry stronger meaning than file paths. + +Use types for semantic groups such as Projects, People, Topics, Procedures, Events, and Essays. Use relationships to connect notes across those groups. This gives Tolaria better structure for navigation, filtering, properties, templates, and future automation than folder location alone. + +## Type Documents + +Type documents are Markdown notes with `type: Type` in frontmatter. They describe how a type should appear and what new notes of that type should start with. + +```yaml +--- +type: Type +_icon: folder +_color: blue +_sidebar_label: Projects +_order: 10 +--- + +# Project +``` + +Type templates can live in the Type document's `template` frontmatter field. When a hand-edited Type body contains template-like structure after its own `# TypeName` heading, Tolaria also uses that body content as the new-note template. Plain descriptive body text stays documentation-only. + +## What Types Control + +- Sidebar grouping. +- Type icon and color. +- Sidebar order and label. +- Pinned properties. +- New-note templates. + +## New Note Defaults + +Type documents can define empty properties and relationships. When you create a new note of that type, Tolaria shows placeholders for those fields so you can fill them in from the Properties panel. + +If a type document gives a property a value, that value becomes the default for new notes of that type. For example, a Project type can define `status: Active` so every new project starts active until you change it. + +--- + +# Vaults + +Source: concepts/vaults.md +URL: /concepts/vaults + +# Vaults + +A vault is the folder Tolaria reads and writes. The filesystem is the source of truth; the app state and cache are derived from files. + +## Core Rules + +- Notes are Markdown files. +- YAML frontmatter provides structure. +- Attachments are normal files inside the vault. +- Type definitions and saved views are also files. +- Git can track history and support remote sync. + +## Why Local Files Matter + +Local files keep your notes inspectable. You can open them in another editor, search with command-line tools, back them up with your own system, and version them with Git. + +Tolaria should never become the only way to read your data. + +## Git Is A Capability + +A plain folder of Markdown files can open as a vault. Git-backed vaults unlock history, changes, commits, pull, push, conflict handling, and remote setup. + +If a folder is not a Git repository, Tolaria can initialize Git when you explicitly ask it to. It avoids initializing broad personal folders such as Desktop, Documents, or Downloads unless they are clearly dedicated vault folders. + +## Multiple Vaults At The Same Time + +Tolaria can load multiple registered vaults into one unified graph. Enable this from `Settings` -> `Vaults` -> `Use multiple vaults at the same time`. + +After the option is enabled, open the bottom-left vault menu to include or exclude vaults from the graph. Included vaults appear together in note lists, search, quick open, backlinks, and wikilink navigation. Each note keeps a compact vault badge when Tolaria needs to disambiguate where it lives. + +The selected vault still matters. Git status, commits, sync, folder navigation, saved views, and vault repair actions stay scoped to the current repository. Use `Manage vaults` from the vault menu or the Vaults settings section to rename vaults, choose colors, and set the default destination for new notes. + +Cross-vault wikilinks use the target vault's stable alias when needed, for example `[[team/projects/alpha]]`. Links inside the same vault stay normal vault-relative links. + +## App State Versus Vault State + +Vault-level information should travel with the vault. Machine-specific preferences stay with the app installation. + +| Vault state | App state | +| --- | --- | +| Type icons and colors | Editor zoom | +| Saved views | Window size | +| Pinned properties | Recent vault list | +| Relationship conventions | Local cache | +| Vault AI guidance files | AI target selection | + +--- + +# Build Custom Views + +Source: guides/build-custom-views.md +URL: /guides/build-custom-views + +# Build Custom Views + +Custom views are saved filters for recurring questions. + +## Good View Candidates + +- Active projects. +- People without a recent follow-up. +- Drafts ready for review. +- Notes changed this week. +- Events in a date range. + +## View Definition + +Saved views live as files in the vault. They describe filters, sorting, and visible columns using structured data. + +## Filters + +Custom views can use nested conditions, similar to Notion or Airtable filter groups. Combine `all` and `any` logic when a view needs to answer a more precise question than a single field filter can express. + +Date filters support dynamic natural-language values such as `today`, `yesterday`, or `one week ago`. Use these for views that should keep moving over time, such as recent work, stale follow-ups, or upcoming events. + +## Design The Question First + +Before creating a view, write the question it answers. A good view is not "all fields with all filters"; it is a focused lens. + +--- + +# Capture A Note + +Source: guides/capture-a-note.md +URL: /guides/capture-a-note + +# Capture A Note + +Use capture when you need to get an idea into the vault before you know where it belongs. + +## Steps + +1. Press `Cmd+N` on macOS or `Ctrl+N` on Windows and Linux. +2. Write a clear H1. +3. Add the rough content. +4. Leave structure for later if you are still thinking. + +## Capture Well + +Prefer a useful title over a perfect taxonomy. You can add type, status, and relationships during inbox review. + +## When To Add Structure Immediately + +Add structure while capturing when the note's type or relationships are already obvious. Otherwise, capture the idea first and organize it later. + +--- + +# Manage Git Manually Or With AutoGit + +Source: guides/commit-and-push.md +URL: /guides/commit-and-push + +# Manage Git Manually Or With AutoGit + +Tolaria can act as a lightweight Git client for a Git-enabled vault. You can manage commits and pushes yourself, or enable AutoGit to create conservative checkpoints after editing pauses or when the app is no longer active. + +## Manual Git + +1. Open the Git or changes surface. +2. Review changed files. +3. Write a short commit message. +4. Commit locally. +5. Push when a remote is configured. + +If the remote has changed, pull first and resolve any conflicts. If the vault has no remote, manual commits still give you local history, diffs, and rollback. + +## AutoGit + +AutoGit is available in Settings for Git-enabled vaults. When enabled, Tolaria automatically commits and pushes saved local changes after an idle pause or after the app becomes inactive. + +Use AutoGit when you want the safety of regular checkpoints without interrupting capture or editing. You can still inspect each note's current diff, review note history, and browse the whole-vault history before making larger manual commits. + +## Use Small Commits + +Small commits make it easier to understand what changed, roll back safely, and review AI-generated edits. + +--- + +# Configure AI Models + +Source: guides/configure-ai-models.md +URL: /guides/configure-ai-models + +# Configure AI Models + +Use model providers when you want chat over note context without giving an agent vault-write tools. + +## Local Models + +Local model targets are for tools such as Ollama and LM Studio. They usually need a base URL and model ID, and they usually do not need an API key. + +## API Models + +API model targets are for hosted providers such as OpenAI, Anthropic, Gemini, OpenRouter, or another OpenAI-compatible endpoint. + +Tolaria does not store provider API keys in vault settings. Choose one of the supported key paths: + +- Save the key locally on this device. +- Read the key from an environment variable. +- Use no key for local providers that do not require one. + +## Test The Connection + +After adding a provider, use the test action in Settings. A successful test means Tolaria reached the endpoint and the model replied. + +## Select The Target + +Once configured, choose the model from the AI target selector or set it as the default AI target in Settings. + +--- + +# Connect A Git Remote + +Source: guides/connect-a-git-remote.md +URL: /guides/connect-a-git-remote + +# Connect A Git Remote + +Connect a remote when you want backup or sync beyond the current machine. + +## Before You Start + +Make sure the remote repository exists and your system Git can authenticate to it. Tolaria uses system Git rather than storing provider-specific credentials. + +## Steps + +1. Open the bottom status bar remote chip, or run `Add Remote` from the command palette. +2. Paste the remote URL. +3. Confirm the remote name. +4. Fetch or push according to the app prompt. + +## Recommended Auth + +- SSH keys. +- GitHub CLI authentication. +- Existing Git credential helpers. +- macOS Keychain credentials for HTTPS remotes on macOS. + +If authentication fails, see [Git Authentication](/troubleshooting/git-auth). + +--- + +# Create Types + +Source: guides/create-types.md +URL: /guides/create-types + +# Create Types + +Create a type when several notes share the same role in your system. + +## Steps + +1. Run `New Type` from the command palette, or click `+` in the Types header in the sidebar. +2. Give the type a clear name. +3. Add optional icon, color, sidebar order, sidebar label, pinned properties, suggested fields, default values, or a new-note template. + +You can also right-click a type in the sidebar to change its icon and color. + +```yaml +--- +type: Type +_icon: briefcase +_color: blue +_sidebar_label: Projects +_order: 10 +--- + +# Project +``` + +## Use Types Sparingly + +A type should represent a recurring category, not a one-off label. If you only need a temporary grouping, use a saved view or property instead. + +## Templates + +Type documents can include a Markdown template for new notes of that type. Keep templates small and useful: a heading, a few expected fields, and the first checklist are usually enough. + +You can store the template in the Type document's `template` frontmatter field. When hand-editing the Type document body, content after the Type note's own `# TypeName` heading is also used as the new-note template if it looks like template structure such as field labels, secondary headings, or checklist starters. Plain descriptive body text is ignored. + +Type documents can also define fields for new notes. Empty properties and relationships become placeholders in new notes of that type. Properties with values become defaults for new notes of that type. + +--- + +# Manage Display Preferences + +Source: guides/manage-display-preferences.md +URL: /guides/manage-display-preferences + +# Manage Display Preferences + +Display preferences live in local app settings unless a setting is intentionally stored in the note or vault. + +## Theme + +Choose Light, Dark, or System in Settings. System follows the operating system appearance at runtime. + +You can also switch theme mode from the command palette. + +## Note Width + +Set the default rich-editor width in Settings: + +- **Normal** for focused writing. +- **Wide** for tables, diagrams, dense notes, and generated documents. + +An individual note can override the default width from the editor toolbar. That override is stored as `_width` in the note frontmatter. + +## Sidebar Labels + +Tolaria can pluralize type names in the sidebar. Turn this off in Settings if your type names should be shown exactly as written, or use `_sidebar_label` on a type document for an explicit label. + +## Vault Content + +Settings also control whether Gitignored files and non-Markdown file categories are visible in the app. Use these controls to keep generated or local-only files out of regular note workflows. + +--- + +# Organize The Inbox + +Source: guides/organize-inbox.md +URL: /guides/organize-inbox + +# Organize The Inbox + +Inbox review turns quick captures into usable knowledge. + +## Remove A Note From Inbox + +When a note is organized enough, mark it as organized. Use `Cmd+E` on macOS or `Ctrl+E` on Windows and Linux, or click the organize action in the breadcrumb bar. + +That action is what removes the note from Inbox. If auto-advance is enabled in Settings > Workflow, Tolaria opens the next Inbox item immediately after you mark the current note organized. + +## Review Checklist + +- Rename unclear notes. +- Add or correct the first H1. +- Set `type`. +- Add `status` for actionable notes. +- Add `belongs_to`, `related_to`, or other relationship fields when useful. +- Archive or delete notes that no longer matter. + +## Make Notes Navigable + +A note is organized when you can answer: + +- What kind of thing is this? +- What is it connected to? +- What is this useful for? +- What will I do with it? + +## Avoid Over-Structuring + +Do not add fields just because they exist. Add the structure that will help future navigation, review, or automation. + +--- + +# Use The AI + +Source: guides/use-ai-panel.md +URL: /guides/use-ai-panel + +# Use The AI + +Tolaria gives you two ways to ask for AI help: open the AI panel for an ongoing conversation, or prompt directly from the editor with `Cmd+K` followed by a space. + +## Choose How To Prompt + +- **AI panel** is best for longer conversations, agent work, and requests that need visible back-and-forth. +- **Inline prompt** is best when you are already writing. Press `Cmd+K`, type a space, then write the prompt you want the AI to handle from the current note context. + +## Choose A Target + +Open Settings and choose the default AI target: + +- **Coding agent** for tool-backed vault editing through Claude Code, Codex, OpenCode, Pi, or Antigravity CLI. +- **Local model** for Ollama or LM Studio chat over note context. +- **API model** for OpenAI, Anthropic, Gemini, OpenRouter, or an OpenAI-compatible endpoint. + +If a coding agent is missing, install it and reopen Tolaria or switch to another target. + +## Permission Mode + +Coding agents support per-vault permission modes: + +- **Vault Safe** keeps agents limited to file, search, and edit tools. +- **Power User** can allow shell commands for agents that support them. + +Direct model targets always stay in chat mode. They can use note context, but they cannot edit vault files through tools. + +## Good Requests + +- "Find notes related to this project." +- "Summarize what changed in this note." +- "Draft a weekly review from these linked notes." +- "Update this checklist based on the current project status." + +## Review Changes + +AI edits are file edits. Review them with Tolaria's diff and Git history before committing. + +--- + +# Use The Command Palette + +Source: guides/use-command-palette.md +URL: /guides/use-command-palette + +# Use The Command Palette + +The command palette is the fastest way to move around Tolaria. + +Open it with: + +- `Cmd+K` on macOS. +- `Ctrl+K` on Linux and Windows. + +## Common Commands + +- New Note. +- Search. +- Open Settings. +- Reload Vault. +- Add Remote. +- Open Getting Started Vault. +- Toggle Raw Mode. +- Toggle Table of Contents. +- Toggle AI Panel. +- Use Light, Dark, or System theme. +- Open in New Window. + +## Keyboard-First Workflow + +Use the palette when you know what you want to do but do not want to hunt through panels. It is also the best place to discover commands as the app grows. + +--- + +# Use Media Previews + +Source: guides/use-media-previews.md +URL: /guides/use-media-previews + +# Use Media Previews + +Media previews let you inspect vault files without leaving Tolaria. + +## Open A File + +Select an image, PDF, media file, or unsupported file from a folder or file list. Tolaria opens supported files in the app and offers an external-open action for files that should use the system default app. + +## All Notes Visibility + +Open Settings to choose whether non-Markdown files appear in All Notes: + +- PDFs. +- Images. +- Unsupported files. + +Folder browsing still shows files in their folders even when a category is hidden from All Notes. + +## Attachments + +When you paste or drop an image into a note, Tolaria copies it into the vault and references the copied file from Markdown. + +## Troubleshooting + +If a preview does not render, open the file in the default app to confirm the file is valid, then check whether the file is inside the active vault and not blocked by operating-system permissions. + +--- + +# Use Spreadsheets + +Source: guides/use-spreadsheets.md +URL: /guides/use-spreadsheets + +# Use Spreadsheets + +Tolaria spreadsheets are sheet notes: Markdown files with frontmatter and a CSV-like body that open in a spreadsheet editor when their `Display as` value is `Sheet`. + +Use a sheet note when a model needs rows, columns, calculations, or repeated numeric editing. Use a normal note when the main artifact is prose. + +## Create A Sheet + +Use the command palette action `New Sheet`, or create/open a note and set its `Display as` to `Sheet` from the Properties panel. `Type` remains separate and can still be `Note`, `Project`, `Responsibility`, or any other Tolaria type. + +When a note is a sheet: + +- the YAML frontmatter remains available for type, status, relationships, wikilinks, and custom properties +- `_display: sheet` tells Tolaria to display the note with the spreadsheet editor +- the body is the sheet itself +- there is no rich-text body around the table +- the editor switches from the text editor to the spreadsheet editor + +## Enter Values + +Click a cell and type a value. Non-formula values can be text, numbers, dates, or wikilinks. + +Press `Enter` on a selected cell to edit the cell. Press `Escape` while editing to leave cell editing and keep focus in the sheet. + +Use `Delete` or `Backspace` to clear the selected cell or range. + +## Enter Formulas + +Formulas start with `=`. + +```txt +=B2+B3-B4 +=SUM(B2:D2) +=ROUND(E6, 2) +=IF(E6>0, "Up", "Down") +``` + +Tolaria shows inline formula autocomplete while you type. The autocomplete list is built from the implemented function catalog in the bundled IronCalc engine; formula evaluation is still handled by IronCalc. + +See [Spreadsheet Formulas](/reference/spreadsheet-functions) for syntax, supported examples, and links to the full IronCalc formula reference. + +## Select And Edit Ranges + +The sheet editor follows spreadsheet conventions: + +- arrow keys move the active cell +- `Shift` plus arrow keys extends the selection +- drag to select a range +- copy and paste preserves formulas where possible +- cut and paste moves formulas and shifts relative references +- right-click a selected cell or range to apply formatting + +Right-click actions apply to the current selection. Keep a multi-cell selection active before opening the context menu when you want to format several cells together. + +## Format Cells + +Use the context menu for common formatting: + +- number formats such as plain numbers, currency, and percentages +- decimal precision +- bold and italic text +- alignment and clearing formatting when available + +Formatting is stored as plain YAML under `_sheet`, not in an opaque workbook blob. For example, percentage formatting for `E6` is stored as: + +```yaml +_sheet: + cells: + E6: + num_fmt: "0.00%" +``` + +See [Spreadsheet File Format](/reference/spreadsheet-format) for the full storage model. + +## Add Wikilinks + +Type `[[` in a cell to open note autocomplete. + +```csv +Project,Owner,Status +[[website-redesign]],[[person/alice]],Active +[[sponsorship-pipeline]],[[person/matteo]],Review +``` + +When the cell is not being edited, Tolaria renders the wikilink like other note links. When you edit the cell, the raw `[[wikilink]]` syntax is shown again. + +Command-click a wikilink in a sheet cell to open the linked note. + +## Reference Another Note + +Formulas can read a cell from another sheet note with Tolaria's wikilink cell syntax: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=ROUND([[business-plan]].$E$12, 2) +``` + +The part inside `[[...]]` resolves like a normal Tolaria wikilink. The part after the dot is an A1-style cell reference. + +Use absolute markers when copying formulas: + +| Reference | Copy behavior | +| --- | --- | +| `[[revenue]].B5` | row and column can shift | +| `[[revenue]].$B$5` | row and column stay fixed | +| `[[revenue]].B$5` | row fixed, column can shift | +| `[[revenue]].$B5` | column fixed, row can shift | + +Cross-sheet references currently resolve single cells. Keep range formulas inside one sheet note. + +Formulas can read scalar frontmatter properties from a note with dot notation: + +```txt +=[[device]].power.watts +=[[project-alpha]].status +=[[book-notes/the-design-of-everyday-things.md]].rating +``` + +Numbers, booleans, and text properties can be used in formulas. Missing or ambiguous note targets, missing properties, and non-scalar values such as lists or nested objects show as spreadsheet errors. + +## Work With The Raw File + +A sheet file remains readable text: + +```md +--- +type: Project +_display: sheet +status: Draft +belongs_to: + - "[[business-plan]]" +_sheet: + frozen_rows: 1 + columns: + A: + width: 180 +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Services,800,900,750,=SUM(B3:D3) +Expenses,650,700,760,=SUM(B4:D4) +Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5) +``` + +When editing this file with scripts or AI agents, parse the body as CSV and preserve formulas as formulas. Do not replace formulas with displayed values. + +--- + +# Use The Table Of Contents + +Source: guides/use-table-of-contents.md +URL: /guides/use-table-of-contents + +# Use The Table Of Contents + +The table of contents panel helps you navigate long notes by heading. + +## Open It + +Use the editor toolbar, the command palette, or the shortcut: + +- `Cmd+Shift+T` on macOS. +- `Ctrl+Shift+T` on Windows and Linux. + +## How It Works + +Tolaria builds the outline from the current note's headings. The panel updates as the note changes and can jump to sections in the editor. + +## Good Uses + +- Long procedures. +- Meeting notes with many sections. +- Research notes. +- Generated documents that need review. + +If a note has no useful headings, add clear H2 and H3 sections rather than relying on a long uninterrupted document. + +--- + +# Use Wikilinks + +Source: guides/use-wikilinks.md +URL: /guides/use-wikilinks + +# Use Wikilinks + +Wikilinks connect notes by name. + +```md +This project belongs to [[content-systems]] and is related to [[git-workflows]]. +``` + +## Link From The Body + +Use body links when the connection is part of the sentence you are writing. + +## Link From Frontmatter + +Use frontmatter links when the relationship should become structured metadata. + +```yaml +related_to: + - "[[git-workflows]]" +``` + +## Keep Links Stable + +Prefer clear note titles and filenames. Tolaria's wikilink autocomplete helps you pick the right target while you type. + +--- + +# Portent + +Source: templates/portent.md +URL: /templates/portent + +# Portent + +[Portent](https://portent.md) is an open specification and template for work and personal knowledge bases. + +It gives a Tolaria vault a small set of defaults for organizing information: clear types, generic graph-like relationships, and a simple lifecycle for captured knowledge. The goal is to make a knowledge base useful to humans and AI agents without forcing every person or team to design a private ontology first. + +## Core Questions + +Portent favors convention over configuration. Instead of asking "where should this go?", it asks: + +- What is this? +- What is it useful for? +- Is it captured, organized, or archived? + +Those questions map naturally to Tolaria's type documents, relationship fields, Inbox, organized state, archive behavior, and custom views. + +## Types + +Portent defines eight default types. + +PORT types are actionable: + +- Project +- Operation +- Responsibility +- Task + +ENTP types are non-actionable knowledge records: + +- Event +- Note +- Topic +- Person + +These defaults are meant to cover the common shape of personal and work knowledge with almost no setup. You can add custom types later, but Portent works best when the default vocabulary comes first. + +## Relationships + +Portent models knowledge as a graph. The two default relationships are: + +- `belongs_to`: primary ownership, composition, or context. +- `related_to`: a looser semantic connection. + +In Tolaria, these relationships can live in YAML frontmatter and point to other notes with wikilinks. That keeps the graph portable, searchable, and readable outside the app. + +## Lifecycle + +Portent separates capture from organization: + +1. Capture information quickly so it is not lost. +2. Organize it by assigning a type and useful relationships. +3. Archive it when it has served its purpose. + +Tolaria supports that lifecycle directly: the Inbox holds captured notes, organizing a note marks it ready for normal views, and archiving hides old or obsolete notes from active surfaces while keeping them available. + +## Why Use It + +A blank vault is flexible, but it also asks you to make structural decisions before you have momentum. Portent gives you enough structure to start capturing, organizing, and retrieving notes immediately. + +Because Portent is file-friendly and portable, the same model can work across local Markdown vaults, note apps, docs tools, and agent-readable knowledge bases. Tolaria is the first intended implementation, but the spec is not tied to Tolaria internals. + +## Start From The Template + +The fastest starting point is the Portent template vault: + +- [refactoringhq/portent-vault-template](https://github.com/refactoringhq/portent-vault-template) + +Use it as-is, rename pieces to match your language, or treat it as a reference model for your own Tolaria setup. + +## Learn More + +Visit [portent.md](https://portent.md) for the full spec, examples, and implementation notes. + +--- + +# Contribute + +Source: reference/contribute.md +URL: /reference/contribute + +# Contribute + +Tolaria is free and open source, and any kind of help is useful. Pick the path that matches what you want to do. + +## Newsletter + +[Refactoring](https://refactoring.fm/) is Luca's newsletter and community for engineers building better teams and software with AI. Subscribing is the best way to support Tolaria. + +## Sponsors + +Tolaria is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: + +- [Codacy](https://www.codacy.com/) +- [CodeScene](https://codescene.com/) +- [CircleCI](https://circleci.com/) +- [Unblocked](https://getunblocked.com/) + +## Feature Requests + +Use the [product board](https://tolaria.canny.io/) for feature ideas. Search first, upvote existing ideas, and create a new post when the request is genuinely new. + +## Discussions + +Use [GitHub Discussions](https://github.com/refactoringhq/tolaria/discussions) for questions, conversations, show and tell, and broader community context. + +## Contribute Code + +Small, focused pull requests are welcome. Check the product board first so you build the right thing, then open a PR on [GitHub](https://github.com/refactoringhq/tolaria/pulls). The [contributing guide](https://github.com/refactoringhq/tolaria/blob/main/CONTRIBUTING.md) explains the local workflow. + +## Report A Bug + +Use [GitHub Issues](https://github.com/refactoringhq/tolaria/issues) for bugs. Include what happened, what you expected, and clear reproduction steps. If you are reporting from inside Tolaria, use the Contribute panel to copy sanitized diagnostics and attach them to the issue. + +--- + +# Docs Maintenance + +Source: reference/docs-maintenance.md +URL: /reference/docs-maintenance + +# Docs Maintenance + +The public docs live in the app repo so documentation changes can ship with behavior changes. + +## Update Docs When You Change + +- A Tauri command. +- A new component or hook that changes user behavior. +- A data model or frontmatter convention. +- Git, AI, onboarding, or release behavior. +- Public release pages, download metadata, or updater channels. +- Platform support. +- Keyboard shortcuts. + +## Suggested Workflow + +1. Make the code change. +2. Update the matching concept, guide, or reference page. +3. Add a troubleshooting page if the change creates a new failure mode. +4. Run `pnpm docs:build`. +5. Check the home page, search, release/download links, and changed docs pages in a browser. + +## Page Types + +| Type | Purpose | +| --- | --- | +| Start | Helps a new user get into the app. | +| Concepts | Explains mental models. | +| Guides | Teaches workflows. | +| Reference | Gives stable facts and tables. | +| Troubleshooting | Starts from a symptom and ends with recovery. | + +## Review Checklist + +- Does the page describe current behavior? +- Does it mention macOS primary and Windows/Linux supported-early status when platform support matters? +- Are links relative and VitePress-compatible? +- Can a user discover the page with local search? + +--- + +# File Layout + +Source: reference/file-layout.md +URL: /reference/file-layout + +# File Layout + +Tolaria is not opinionated about folder structure. It finds notes recursively across the whole vault, stores new notes in the root by default, and uses types and relationships for real organization. + +```txt +my-vault/ + project-alpha.md + weekly-review.md + research/ + source-notes.md + attachments/ + diagram.png + source.pdf + project.md + person.md + views/ + active-projects.yml +``` + +## Root Notes + +Tolaria works well with a flat vault. Folders are optional and can be useful for compatibility with other tools, but they are not required for people, projects, topics, or any other note category. + +Type is not inferred from folder location. It comes from frontmatter, and relationships are expressed with wikilinks in fields. That is what Tolaria uses for the sidebar, Properties panel, search, custom views, and neighborhood navigation. + +## Special Folders + +| Folder | Purpose | +| --- | --- | +| `views/` | Saved custom views. | +| `attachments/` | Images and other attached files. | + +PDFs, images, and other non-Markdown files stay as normal files. Folder browsing can show them in place, and Settings controls whether PDFs, images, and unsupported files appear in All Notes. + +Whiteboards are Markdown files with durable tldraw data, so they belong with notes rather than in `attachments/`. + +Spreadsheets are also Markdown files. A note with `_display: sheet` stores ordinary frontmatter plus a CSV-like body and opens in the sheet editor. + +Type definitions are Markdown notes with `type: Type` in frontmatter. New type documents are normal notes, and existing type documents in older folders still work. + +## Git Files + +If the vault is a Git repository, `.git/` belongs to Git. Tolaria reads Git state but does not treat `.git/` as notes. + +--- + +# Frontmatter Fields + +Source: reference/frontmatter-fields.md +URL: /reference/frontmatter-fields + +# Frontmatter Fields + +Tolaria uses conventions instead of a required schema. + +| Field | Meaning | +| --- | --- | +| `type` | The note's entity type. | +| `status` | Lifecycle state. | +| `icon` | Per-note icon. | +| `url` | External URL. | +| `date` | Single date. | +| `belongs_to` | Parent relationship. | +| `related_to` | Lateral relationship. | +| `has` | Contained relationship. | +| `_width` | Per-note editor width override. | +| `_display` | Display mode. Omit for text notes; use `sheet` for spreadsheet notes. | +| `_icon`, `_color` | Type or note appearance metadata. | +| `_sidebar_label`, `_order` | Type sidebar label and order. | +| `_pinned_properties` | Properties pinned for a type. | +| `_sheet` | Sheet-note presentation metadata such as grid settings, column widths, row heights, and cell formatting. | + +## Custom Fields + +You can add your own fields. If a field contains wikilinks, Tolaria can treat it as a relationship. + +## System Fields + +Fields starting with `_` are reserved for system behavior and hidden from standard property editing. They remain plain YAML, so they can still be inspected or changed in raw mode when needed. + +Nested keys under a system field are also system-owned. For example, `_sheet.cells.B6.num_fmt` belongs to the sheet editor and should not appear as a normal user property. + +--- + +# Keyboard Shortcuts + +Source: reference/keyboard-shortcuts.md +URL: /reference/keyboard-shortcuts + +# Keyboard Shortcuts + +| Shortcut | Action | +| --- | --- | +| `Cmd+K` / `Ctrl+K` | Open command palette. | +| `Cmd+P` / `Ctrl+P` | Quick open notes and files. | +| `Cmd+N` / `Ctrl+N` | Create a new note. | +| `Cmd+S` / `Ctrl+S` | Save current note. | +| `Cmd+F` / `Ctrl+F` | Find in the current note. | +| `Cmd+Shift+F` / `Ctrl+Shift+F` | Search the vault. | +| `Cmd+Shift+V` / `Ctrl+Shift+V` | Paste without formatting. | +| `Cmd+\` / `Ctrl+\` | Toggle raw Markdown mode. | +| `Cmd+Shift+T` / `Ctrl+Shift+T` | Toggle table of contents. | +| `Cmd+Shift+I` / `Ctrl+Shift+I` | Toggle Properties panel. | +| `Cmd+Shift+L` / `Ctrl+Shift+L` | Toggle AI panel. | +| `Cmd+[` / `Alt+Left` | Navigate back when available. | +| `Cmd+]` / `Alt+Right` | Navigate forward when available. | +| `Cmd+Shift+O` / `Ctrl+Shift+O` | Open current note in a new window. | +| `Cmd+D` / `Ctrl+D` | Toggle favorite for the current note. | +| `Cmd+E` / `Ctrl+E` | Mark the current Inbox note organized. | + +Some shortcuts vary by platform because macOS, Linux, and Windows reserve different key combinations. + +Use the command palette to discover the current command set. + +--- + +# Release Channels + +Source: reference/release-channels.md +URL: /reference/release-channels + +# Release Channels + +Tolaria publishes Stable and Alpha release metadata to GitHub Pages. + +## Stable + +Stable follows manually promoted releases. This is the right channel for normal use. + +The stable updater metadata lives at: + +```txt +/stable/latest.json +``` + +The public download page points at the latest stable release. + +## Alpha + +Alpha follows pushes to `main`. It receives fixes and features earlier, but it can be rougher than Stable. + +The alpha updater metadata lives at: + +```txt +/alpha/latest.json +``` + +Compatibility endpoints also point to the alpha metadata: + +```txt +/latest.json +/latest-canary.json +``` + +## Before Switching + +Commit or push important vault changes before changing release channel or installing an update. Your notes are local files, but a clean Git state makes recovery simpler. + +--- + +# Spreadsheet File Format + +Source: reference/spreadsheet-format.md +URL: /reference/spreadsheet-format + +# Spreadsheet File Format + +Sheet notes are Markdown files with YAML frontmatter and a CSV-like body. The note uses `_display: sheet` when it should open in the spreadsheet editor. The `type` field remains ordinary semantic metadata. + +For editing workflows, see [Use Spreadsheets](/guides/use-spreadsheets). For formula syntax and function references, see [Spreadsheet Formulas](/reference/spreadsheet-functions). + +## Structure + +```md +--- +type: Project +_display: sheet +tags: + - planning +_sheet: + show_grid_lines: true + frozen_rows: 1 + frozen_columns: 1 + columns: + A: + width: 180 + rows: + "1": + height: 32 + cells: + E6: + num_fmt: "0.00%" + bold: true +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Expenses,650,700,760,=SUM(B3:D3) +Net,=B2-B3,=C2-C3,=D2-D3,=SUM(B4:D4) +Growth,,=(C4-B4)/B4,=(D4-C4)/C4,=(E4-B4)/B4 +``` + +The frontmatter stores note metadata. The body stores rows and cells. There is no Markdown table wrapper, fenced code block, or embedded workbook blob. + +## Frontmatter + +All ordinary Tolaria fields remain available: + +- `type` +- `status` +- `date` +- `tags` +- `url` +- relationship fields such as `belongs_to`, `related_to`, and custom wikilink properties + +The `_display: sheet` field is the display-as marker. Omit it for ordinary text notes. + +The `_sheet` key is reserved for spreadsheet presentation metadata. It follows the same system-field convention as other underscore-prefixed Tolaria fields: hidden from normal property editing, but visible and editable in raw source. + +## Body + +The body is CSV-like text: + +- rows are separated by line breaks +- cells are separated by commas +- cells containing commas, quotes, or line breaks are quoted +- quotes inside quoted cells are escaped by doubling them +- empty trailing rows and columns may be omitted on save + +Any cell whose input starts with `=` is treated as a formula. Other cells are treated as literal values. + +## `_sheet` Metadata + +Tolaria stores spreadsheet presentation state in `_sheet` as plain YAML. + +| Field | Meaning | +| --- | --- | +| `show_grid_lines` | Whether grid lines are shown. | +| `frozen_rows` | Number of frozen rows from the top. | +| `frozen_columns` | Number of frozen columns from the left. | +| `columns..width` | Custom column width, keyed by column letter such as `A` or `BC`. | +| `rows."".height` | Custom row height, keyed by one-based row number. | +| `cells..num_fmt` | Number format code for a cell. | +| `cells..bold` | Bold text style. | +| `cells..italic` | Italic text style. | +| `cells..underline` | Underline text style. | +| `cells..strike` | Strikethrough text style. | +| `cells..font_size` | Font size. | +| `cells..font_color` | Text color. | +| `cells..fill_color` | Cell fill color. | +| `cells..horizontal_align` | Horizontal alignment. | +| `cells..vertical_align` | Vertical alignment. | +| `cells..wrap_text` | Text wrapping. | +| `cells..border_top` | Top border style. | +| `cells..border_right` | Right border style. | +| `cells..border_bottom` | Bottom border style. | +| `cells..border_left` | Left border style. | + +Cell metadata is keyed by A1-style cell addresses such as `A1`, `B12`, or `AA30`. + +Border values are stored as a style name with an optional color, for example: + +```yaml +border_bottom: "thin #d0d7de" +``` + +## Number Formats + +Number formats are stored in `num_fmt` using spreadsheet-style format codes. Common examples: + +| Format | Example output | +| --- | --- | +| `#,##0` | `1,250` | +| `#,##0.00` | `1,250.50` | +| `0.00%` | `12.35%` | +| `$#,##0.00` | `$1,250.50` | +| `yyyy-mm-dd` | `2026-06-15` | + +These formats affect presentation, not the underlying cell input in the CSV body. + +## Markdown Style Import + +When Tolaria imports a non-formula CSV cell, simple Markdown wrappers can seed initial styles: + +| Cell text | Stored value | Style | +| --- | --- | --- | +| `**Revenue**` | `Revenue` | bold | +| `_Estimate_` | `Estimate` | italic | +| `***Total***` | `Total` | bold and italic | +| `~~Removed~~` | `Removed` | strike | + +After save, the style belongs in `_sheet` metadata and the body keeps the unwrapped text. + +## Wikilinks + +Non-formula cells can store normal Tolaria wikilinks: + +```csv +Account,Source +Newsletter,[[newsletter-revenue]] +Sponsors,[[sponsorship-pipeline]] +``` + +Formula cells can reference another sheet note with Tolaria's cross-sheet syntax: + +```txt +=[[newsletter-revenue]].B5 +=ROUND([[business-plan]].$E$12, 2) +=[[device]].power.watts +``` + +Cross-sheet cell references resolve another sheet note by wikilink target, then read a single A1-style cell. Frontmatter references resolve one note by wikilink target, then read a scalar property path after the dot. Missing, ambiguous, circular, very deep, or non-scalar references are treated as unresolved and surface as spreadsheet errors. + +## Guidance For Agents And Scripts + +When editing a sheet note programmatically: + +- preserve the YAML frontmatter delimiter and ordinary Tolaria fields +- keep `_display: sheet` when the file should display as a spreadsheet +- keep spreadsheet presentation state under `_sheet` +- parse and serialize the body as CSV, not by splitting on every comma manually +- preserve formulas as formulas, including `[[sheet]].A1` and `[[note]].property.path` references +- avoid converting formulas to their displayed values +- quote CSV cells when they contain commas, quotes, or line breaks +- do not add workbook tabs inside one note; create another note with `_display: sheet` instead +- do not store opaque binary workbook state in the Markdown file + +If a script cannot safely preserve `_sheet`, it should leave that block untouched and edit only the CSV body cells it understands. + +--- + +# Spreadsheet Formulas + +Source: reference/spreadsheet-functions.md +URL: /reference/spreadsheet-functions + +# Spreadsheet Formulas + +Formula cells start with `=` and are evaluated by IronCalc through Tolaria's sheet editor. + +Tolaria adds vault-aware sheet references on top of the normal spreadsheet formula model. Everything else should be treated as IronCalc formula behavior. IronCalc aims for Excel-compatible formulas, but the upstream project is still evolving, so verify advanced formulas against the IronCalc docs when precision matters. + +## Basic Syntax + +| Syntax | Meaning | +| --- | --- | +| `=B2+B3-B4` | Arithmetic over cells. | +| `=SUM(B2:D2)` | Function call over a range. | +| `=ROUND(E6, 2)` | Function call with arguments. | +| `=IF(E6>0, "Up", "Down")` | Conditional expression. | +| `=$B$2` | Absolute column and row reference. | +| `=B$2` | Relative column, absolute row. | +| `=$B2` | Absolute column, relative row. | +| `=B2:D10` | A range in the current sheet note. | +| `="Q" & 1` | Text concatenation. | + +Use parentheses when a model depends on precedence: + +```txt +=(B2+B3-B4)/B5 +``` + +## Tolaria Note References + +Tolaria supports wikilink cell references for values that live in another sheet note: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=ROUND([[business-plan]].$E$12, 2) +``` + +The target inside `[[...]]` resolves like a normal Tolaria wikilink. The cell address after the dot uses A1 notation. + +Absolute markers follow spreadsheet copy behavior: + +| Reference | Copy behavior | +| --- | --- | +| `[[revenue]].B5` | row and column can shift | +| `[[revenue]].$B$5` | row and column stay fixed | +| `[[revenue]].B$5` | row fixed, column can shift | +| `[[revenue]].$B5` | column fixed, row can shift | + +Cross-sheet cell references currently resolve single cells. Keep range formulas inside one sheet note until cross-note ranges are explicitly supported. + +Formulas can also read scalar frontmatter properties from a specific note: + +```txt +=[[device.md]].power.watts +=[[project-alpha]].status +=[[book-notes/the-design-of-everyday-things.md]].rating +``` + +The target resolves like a wikilink, and the dot path reads nested frontmatter keys. Numbers, booleans, and strings become formula literals. Missing notes, ambiguous note targets, missing properties, arrays, maps, and other non-scalar values resolve to `#N/A`. + +A first segment that looks like an A1 cell address, such as `B2`, is treated as a cross-sheet cell reference. Use property names that do not collide with A1 notation for frontmatter formulas. + +## Autocomplete Functions + +Tolaria's formula autocomplete exposes the implemented function catalog from the bundled IronCalc engine. The current catalog has 195 functions. + +The dropdown shows a small ranked set of matches while you type. Keep typing to narrow the result list. Function names with digits and dots, such as `BIN2DEC` and `ERFC.PRECISE`, are supported. + +### Logical + +`AND`, `FALSE`, `IF`, `IFERROR`, `IFNA`, `IFS`, `NOT`, `OR`, `SWITCH`, `TRUE`, `XOR` + +### Math and trigonometry + +`ABS`, `ACOS`, `ACOSH`, `ASIN`, `ASINH`, `ATAN`, `ATAN2`, `ATANH`, `COS`, `COSH`, `PI`, `POWER`, `PRODUCT`, `RAND`, `RANDBETWEEN`, `ROUND`, `ROUNDDOWN`, `ROUNDUP`, `SIN`, `SINH`, `SQRT`, `SQRTPI`, `SUM`, `SUMIF`, `SUMIFS`, `TAN`, `TANH`, `SUBTOTAL` + +### Lookup and reference + +`CHOOSE`, `COLUMN`, `COLUMNS`, `HLOOKUP`, `INDEX`, `INDIRECT`, `LOOKUP`, `MATCH`, `OFFSET`, `ROW`, `ROWS`, `VLOOKUP`, `XLOOKUP` + +### Text + +`CONCAT`, `CONCATENATE`, `EXACT`, `FIND`, `LEFT`, `LEN`, `LOWER`, `MID`, `REPT`, `RIGHT`, `SEARCH`, `SUBSTITUTE`, `T`, `TEXT`, `TEXTAFTER`, `TEXTBEFORE`, `TEXTJOIN`, `TRIM`, `UNICODE`, `UPPER`, `VALUE`, `VALUETOTEXT` + +### Information + +`ERROR.TYPE`, `FORMULATEXT`, `ISBLANK`, `ISERR`, `ISERROR`, `ISEVEN`, `ISFORMULA`, `ISLOGICAL`, `ISNA`, `ISNONTEXT`, `ISNUMBER`, `ISODD`, `ISREF`, `ISTEXT`, `NA`, `SHEET`, `TYPE` + +### Statistical + +`AVERAGE`, `AVERAGEA`, `AVERAGEIF`, `AVERAGEIFS`, `COUNT`, `COUNTA`, `COUNTBLANK`, `COUNTIF`, `COUNTIFS`, `GEOMEAN`, `MAX`, `MAXIFS`, `MIN`, `MINIFS` + +### Date and time + +`DATE`, `DAY`, `EDATE`, `EOMONTH`, `MONTH`, `NOW`, `TODAY`, `YEAR` + +### Financial + +`CUMIPMT`, `CUMPRINC`, `DB`, `DDB`, `DOLLARDE`, `DOLLARFR`, `EFFECT`, `FV`, `IPMT`, `IRR`, `ISPMT`, `MIRR`, `NOMINAL`, `NPER`, `NPV`, `PDURATION`, `PMT`, `PPMT`, `PV`, `RATE`, `RRI`, `SLN`, `SYD`, `TBILLEQ`, `TBILLPRICE`, `TBILLYIELD`, `XIRR`, `XNPV` + +### Engineering + +`BESSELI`, `BESSELJ`, `BESSELK`, `BESSELY`, `BIN2DEC`, `BIN2HEX`, `BIN2OCT`, `BITAND`, `BITLSHIFT`, `BITOR`, `BITRSHIFT`, `BITXOR`, `COMPLEX`, `CONVERT`, `DEC2BIN`, `DEC2HEX`, `DEC2OCT`, `DELTA`, `ERF`, `ERF.PRECISE`, `ERFC`, `ERFC.PRECISE`, `GESTEP`, `HEX2BIN`, `HEX2DEC`, `HEX2OCT`, `IMABS`, `IMAGINARY`, `IMARGUMENT`, `IMCONJUGATE`, `IMCOS`, `IMCOSH`, `IMCOT`, `IMCSC`, `IMCSCH`, `IMDIV`, `IMEXP`, `IMLN`, `IMLOG10`, `IMLOG2`, `IMPOWER`, `IMPRODUCT`, `IMREAL`, `IMSEC`, `IMSECH`, `IMSIN`, `IMSINH`, `IMSQRT`, `IMSUB`, `IMSUM`, `IMTAN`, `OCT2BIN`, `OCT2DEC`, `OCT2HEX` + +## Examples + +### Totals + +```txt +=SUM(B2:D2) +=B2+B3-B4 +=SUM(B2:D2)-SUM(B4:D4) +``` + +### Growth And Percentages + +```txt +=(C5-B5)/B5 +=IF(B5=0, 0, (C5-B5)/B5) +=ROUND((C5-B5)/B5, 4) +``` + +Format the result as a percentage with a cell `num_fmt` such as `0.00%`. + +### Conditional Logic + +```txt +=IF(E5>10000, "On track", "Review") +=IFS(E5>10000, "High", E5>5000, "Medium", TRUE, "Low") +=IFERROR(B5/B4, 0) +``` + +### Dates + +```txt +=TODAY() +=DATE(2026, 6, 15) +=YEAR(TODAY()) +=MONTH(TODAY()) +``` + +### Text + +```txt +=CONCAT(A2, " - ", B2) +=UPPER(A2) +=TRIM(A2) +=TEXT(B2, "$#,##0.00") +``` + +### Lookup + +```txt +=INDEX(B2:E10, 3, 2) +=MATCH("Revenue", A2:A20, 0) +=VLOOKUP("Revenue", A2:E20, 5, FALSE) +=XLOOKUP("Revenue", A2:A20, E2:E20) +``` + +### Cross-Sheet Model + +```txt +=[[newsletter-revenue]].E5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=IF([[business-plan]].$E$12>0, [[business-plan]].$E$12, 0) +``` + +## IronCalc Function Families + +IronCalc documents formulas by category. Use these upstream pages for detailed syntax and examples. The upstream documentation may include newer functions that are not yet present in Tolaria's bundled IronCalc version. + +| Family | Link | +| --- | --- | +| Lookup and reference | [IronCalc lookup and reference](https://docs.ironcalc.com/functions/lookup-and-reference.html) | +| Financial | [IronCalc financial](https://docs.ironcalc.com/functions/financial.html) | +| Engineering | [IronCalc engineering](https://docs.ironcalc.com/functions/engineering.html) | +| Database | [IronCalc database](https://docs.ironcalc.com/functions/database.html) | +| Statistical | [IronCalc statistical](https://docs.ironcalc.com/functions/statistical.html) | +| Text | [IronCalc text](https://docs.ironcalc.com/functions/text.html) | +| Math and trigonometry | [IronCalc math and trigonometry](https://docs.ironcalc.com/functions/math-and-trigonometry.html) | +| Logical | [IronCalc logical](https://docs.ironcalc.com/functions/logical.html) | +| Date and time | [IronCalc date and time](https://docs.ironcalc.com/functions/date-and-time.html) | +| Information | [IronCalc information](https://docs.ironcalc.com/functions/information.html) | + +IronCalc also documents [value types](https://docs.ironcalc.com/features/value-types.html), [error types](https://docs.ironcalc.com/features/error-types.html), [optional arguments](https://docs.ironcalc.com/features/optional-arguments.html), and [formatting values](https://docs.ironcalc.com/features/formatting-values.html). + +For current upstream gaps, see IronCalc's [unsupported features](https://docs.ironcalc.com/features/unsupported-features.html). + +--- + +# Supported Platforms + +Source: reference/supported-platforms.md +URL: /reference/supported-platforms + +# Supported Platforms + +Tolaria is a desktop app built with Tauri. Releases currently target macOS, Windows, and Linux. + +| Platform | Current support | Notes | +| --- | --- | --- | +| macOS | Primary | Main development and QA target. Apple Silicon and Intel artifacts are published. | +| Windows | Supported, early | NSIS installers and signed updater bundles are published. Menu, shell-path, and credential-helper behavior receive platform-specific fixes as they appear. | +| Linux | Supported, early | AppImage, deb, and RPM artifacts are published. Behavior can depend on distro WebKitGTK packages, Wayland/X11 details, and input-method setup. | + +## Support Policy + +Primary support means the platform is part of normal development and release validation. Supported, early means release artifacts exist and the app is expected to work, but platform-specific bugs can take longer to diagnose than macOS issues. + +## Reporting Platform Bugs + +Include: + +- Tolaria version. +- Operating system and version. +- CPU architecture. +- Whether the vault is local-only or connected to a remote. +- Steps to reproduce. + +--- + +# View Filters + +Source: reference/view-filters.md +URL: /reference/view-filters + +# View Filters + +View filters define saved lists of notes. + +## Common Filter Ideas + +| Goal | Filter direction | +| --- | --- | +| Active projects | `type` is Project and `status` is Active | +| Drafts | `type` is Article and `status` is Draft | +| People follow-up | `type` is Person and date is before today | +| Recent work | modified date is within a recent range | + +## Sorting + +Useful sorts include: + +- Recently modified first. +- Title ascending. +- Status ascending. +- A custom property ascending or descending. + +## Operators + +Saved views can combine filters for text, dates, relationship fields, and frontmatter values. Relative date expressions are useful for views such as notes changed this week or people that need follow-up. + +Regex filters are available for power-user cases. Keep them narrow and test them on a small view first. + +## Keep Views Focused + +A view should answer one recurring question. If it becomes too broad, split it into two views. + +You can also customize view appearance with the same kind of icon and color controls used by types. + +--- + +# AI Agent Not Found + +Source: troubleshooting/ai-agent-not-found.md +URL: /troubleshooting/ai-agent-not-found + +# AI Agent Not Found + +Tolaria can only launch local CLI agents that are installed and discoverable. + +## Symptoms + +- The AI panel says no supported agent is available. +- Claude Code or another agent works in one shell but not in Tolaria. + +## Checks + +Open a terminal and run the agent command directly. For Claude Code: + +```bash +claude --version +``` + +If the command fails, install or repair the agent first. + +## Path Issues + +Desktop apps can inherit a different `PATH` from your interactive shell. Tolaria checks common install locations, but shell setup can still vary. Prefer installing CLI tools in standard locations or making them available from your login shell. + +--- + +# Git Authentication + +Source: troubleshooting/git-auth.md +URL: /troubleshooting/git-auth + +# Git Authentication + +Tolaria uses system Git authentication. It does not manage provider passwords directly. + +## Symptoms + +- Push fails. +- Pull asks for credentials repeatedly. +- Remote fetch works in one terminal but not in Tolaria. + +## Checks + +1. Open a terminal. +2. `cd` into the vault. +3. Run `git remote -v`. +4. Run `git fetch`. + +If `git fetch` fails in the terminal, fix system Git auth first. + +## Common Fixes + +- Sign in with GitHub CLI. +- Configure SSH keys. +- Update the remote URL. +- Check your credential helper. + +--- + +# Model Provider Connection + +Source: troubleshooting/model-provider-connection.md +URL: /troubleshooting/model-provider-connection + +# Model Provider Connection + +Use this checklist when a local or API model provider does not connect. + +## Local Providers + +For Ollama or LM Studio: + +1. Start the local model server. +2. Confirm the base URL in Tolaria matches the server. +3. Confirm the model ID is installed and loaded by the provider. +4. Use the Settings test action again. + +## API Providers + +For hosted providers: + +1. Confirm the provider kind and endpoint. +2. Confirm the model ID exists for your account. +3. Confirm the API key is saved locally or available in the configured environment variable. +4. Avoid storing secrets in the vault. + +## Chat Mode Boundary + +Direct model targets run in chat mode. If you need file-editing tools, use a coding agent target such as Claude Code, Codex, OpenCode, Pi, or Antigravity CLI. + +--- + +# Sync Conflicts + +Source: troubleshooting/sync-conflicts.md +URL: /troubleshooting/sync-conflicts + +# Sync Conflicts + +Sync conflicts happen when local and remote changes touch the same content. + +## What To Do + +1. Stop editing the conflicted note. +2. Open the conflict resolver if Tolaria presents it. +3. Review both sides. +4. Choose the correct content or merge manually. +5. Commit the resolved file. +6. Push again. + +## Prevent Conflicts + +- Pull before starting work on another device. +- Push after meaningful sessions. +- Keep AI-generated edits in small commits. +- Avoid editing the same note on multiple devices at the same time. + +--- + +# Vault Not Loading + +Source: troubleshooting/vault-not-loading.md +URL: /troubleshooting/vault-not-loading + +# Vault Not Loading + +Use this checklist when Tolaria cannot open or refresh a vault. + +## Check The Folder + +- Confirm the folder exists. +- Confirm the folder contains readable files. +- Confirm Tolaria has permission to access the folder. +- Try opening a smaller test vault to isolate the issue. + +## Check Git + +If the vault is a Git repository, verify it is not in a broken state: + +```bash +git status +``` + +Resolve interrupted merges or corrupted repository state before retrying. + +## Reload + +Run `Reload Vault` from the command palette. This clears derived cache and rescans the filesystem. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/build-custom-views.md b/src-tauri/gen/apple/assets/agent-docs/build-custom-views.md new file mode 100644 index 0000000..487adb3 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/build-custom-views.md @@ -0,0 +1,30 @@ +# Build Custom Views + +Source: guides/build-custom-views.md +URL: /guides/build-custom-views + +# Build Custom Views + +Custom views are saved filters for recurring questions. + +## Good View Candidates + +- Active projects. +- People without a recent follow-up. +- Drafts ready for review. +- Notes changed this week. +- Events in a date range. + +## View Definition + +Saved views live as files in the vault. They describe filters, sorting, and visible columns using structured data. + +## Filters + +Custom views can use nested conditions, similar to Notion or Airtable filter groups. Combine `all` and `any` logic when a view needs to answer a more precise question than a single field filter can express. + +Date filters support dynamic natural-language values such as `today`, `yesterday`, or `one week ago`. Use these for views that should keep moving over time, such as recent work, stale follow-ups, or upcoming events. + +## Design The Question First + +Before creating a view, write the question it answers. A good view is not "all fields with all filters"; it is a focused lens. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/capture-a-note.md b/src-tauri/gen/apple/assets/agent-docs/capture-a-note.md new file mode 100644 index 0000000..f858218 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/capture-a-note.md @@ -0,0 +1,23 @@ +# Capture A Note + +Source: guides/capture-a-note.md +URL: /guides/capture-a-note + +# Capture A Note + +Use capture when you need to get an idea into the vault before you know where it belongs. + +## Steps + +1. Press `Cmd+N` on macOS or `Ctrl+N` on Windows and Linux. +2. Write a clear H1. +3. Add the rough content. +4. Leave structure for later if you are still thinking. + +## Capture Well + +Prefer a useful title over a perfect taxonomy. You can add type, status, and relationships during inbox review. + +## When To Add Structure Immediately + +Add structure while capturing when the note's type or relationships are already obvious. Otherwise, capture the idea first and organize it later. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/commit-and-push.md b/src-tauri/gen/apple/assets/agent-docs/commit-and-push.md new file mode 100644 index 0000000..8778dc0 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/commit-and-push.md @@ -0,0 +1,28 @@ +# Manage Git Manually Or With AutoGit + +Source: guides/commit-and-push.md +URL: /guides/commit-and-push + +# Manage Git Manually Or With AutoGit + +Tolaria can act as a lightweight Git client for a Git-enabled vault. You can manage commits and pushes yourself, or enable AutoGit to create conservative checkpoints after editing pauses or when the app is no longer active. + +## Manual Git + +1. Open the Git or changes surface. +2. Review changed files. +3. Write a short commit message. +4. Commit locally. +5. Push when a remote is configured. + +If the remote has changed, pull first and resolve any conflicts. If the vault has no remote, manual commits still give you local history, diffs, and rollback. + +## AutoGit + +AutoGit is available in Settings for Git-enabled vaults. When enabled, Tolaria automatically commits and pushes saved local changes after an idle pause or after the app becomes inactive. + +Use AutoGit when you want the safety of regular checkpoints without interrupting capture or editing. You can still inspect each note's current diff, review note history, and browse the whole-vault history before making larger manual commits. + +## Use Small Commits + +Small commits make it easier to understand what changed, roll back safely, and review AI-generated edits. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/concepts.md b/src-tauri/gen/apple/assets/agent-docs/concepts.md new file mode 100644 index 0000000..34ba557 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/concepts.md @@ -0,0 +1,554 @@ +# AI + +Source: concepts/ai.md +URL: /concepts/ai + +# AI + +Tolaria has two AI paths: coding agents that can use tools to inspect and edit a vault, and direct model targets that answer in chat mode from note context. + +## Coding Agents + +The AI panel can stream supported local CLI agents through Tolaria's normalized event layer. Current targets include Claude Code, Codex, OpenCode, Pi, and Antigravity CLI when they are installed on the machine. + +Coding agents can run in: + +- **Vault Safe** mode, limited to file, search, and edit tools. +- **Power User** mode, which can allow local shell commands scoped to the active vault for agents that support shell access. + +## Direct Models + +Direct model targets run in chat mode. They receive the active note, linked context, and conversation history, but they do not receive vault-write tools or shell access. + +Supported provider shapes include: + +- Local models through Ollama or LM Studio. +- Hosted providers such as OpenAI, Anthropic, Gemini, and OpenRouter. +- Custom OpenAI-compatible endpoints. + +## External MCP Setup + +Tolaria exposes an MCP server for external tools. The setup flow can write Tolaria's MCP entry into Claude Code, Antigravity CLI, Cursor, and a generic MCP config path, and it can also copy the exact JSON snippet for manual setup. + +MCP setup is explicit. Closing the dialog leaves third-party config files untouched. + +## Why Git Matters For AI + +AI-generated changes should be inspectable. Git gives you diffs, history, rollback, and a clear boundary between suggestions and committed work. + +--- + +# Editor + +Source: concepts/editor.md +URL: /concepts/editor + +# Editor + +Tolaria offers a rich editor for daily writing and a raw Markdown mode for exact file control. Both modes write back to the same Markdown file. + +## Rich Editing + +The rich editor supports blocks, slash commands, wikilinks, tables, code blocks, images, Mermaid diagrams, LaTeX-style math, and markdown-backed whiteboards. + +Use it when you want to write and reorganize quickly without thinking about Markdown syntax. + +## Raw Mode + +Raw mode shows the Markdown source directly. Use it when you need to edit YAML frontmatter, repair unusual Markdown, or make an exact text change. + +Toggle raw mode with `Cmd+\` on macOS or `Ctrl+\` on Windows and Linux. + +## Table Of Contents + +The table of contents panel builds an outline from headings in the current note. It is useful for long notes, procedures, research files, and generated documents. Toggle it with `Cmd+Shift+T` on macOS or `Ctrl+Shift+T` on Windows and Linux. + +## Width + +Notes can use normal or wide editor width. Set the default in Settings, or override an individual note from the editor toolbar. + +--- + +# Files And Media + +Source: concepts/files-and-media.md +URL: /concepts/files-and-media + +# Files And Media + +Tolaria starts with Markdown notes, but a vault can also contain images, PDFs, media files, whiteboards, and other local files. + +## Mermaid Diagrams + +Use Mermaid code blocks when a note needs a diagram that should stay plain text and versionable. + +````md +```mermaid +flowchart LR + Idea --> Draft --> Review --> Publish +``` +```` + +Tolaria renders Mermaid diagrams in the editor while keeping the source in Markdown. + +## Attachments + +Images pasted into the editor are saved into the vault as normal files. They remain portable and can be opened by other tools. + +## Previews + +Tolaria can preview common image files, PDFs, and supported media files in the app. Files without an in-app preview can still be opened in the default system app. + +Settings control whether PDFs, images, and unsupported files appear in All Notes. Folder browsing still shows files in their folders. + +## Whiteboards + +Whiteboards use tldraw in the editor, but their durable representation stays in Markdown. That keeps them inside the vault and versioned by Git with the rest of your notes. + +## Git Boundary + +If generated or local-only files are ignored by Git, Tolaria can hide them from notes, search, quick open, and folders. Use this when build artifacts or private local files should not behave like vault content. + +--- + +# Git + +Source: concepts/git.md +URL: /concepts/git + +# Git + +Git is Tolaria's recommended history and sync layer. Tolaria can work with plain Markdown folders, and Git unlocks local history, recovery, remote backup, and multi-device workflows when you want them. + +Tolaria acts as a lightweight Git client for your vault. You can review changes, commit, pull, push, and inspect history without leaving the app. + +## What Tolaria Uses Git For + +- Whole-vault commit history. +- Current diff for the vault. +- Per-note history. +- Current diff for an individual note. +- Pull and push. +- Conflict detection and resolution. +- Remote connection for local-only vaults. + +## History And Diffs + +Each note can show its own history and current diff, so you can understand how that file changed over time or what is unsaved relative to Git. + +Tolaria also shows a history of the whole vault. Use it when you want to review broader changes across multiple notes before committing or syncing. + +## Local Commits + +You can commit changes inside Tolaria without leaving the app. This gives you useful restore points even before a remote is configured. + +## Remotes + +Connect a compatible Git remote when you want sync or backup. Tolaria relies on your system Git authentication, so GitHub CLI, SSH keys, credential helpers, and existing Git configuration can continue to work. + +--- + +# Inbox + +Source: concepts/inbox.md +URL: /concepts/inbox + +# Inbox + +The Inbox is for notes that have been captured but not yet organized. + +## Why It Exists + +Fast capture should not require perfect structure. The Inbox gives you a place to put incomplete notes, then process them later. + +The Inbox workflow is optional. Turn it off in Settings > Workflow if you prefer every note to appear organized by default. + +## Organizing Inbox Notes + +When reviewing the Inbox: + +1. Give the note a clear H1. +2. Set its `type`. +3. Add status, dates, or URL if useful. +4. Add relationships with wikilinks or frontmatter fields. +5. Move it into a folder only if the folder adds value. + +## Healthy Inbox Habit + +Keep the Inbox small enough that it can be reviewed in one focused pass. Tolaria works best when capture is fast and organization is deliberate. + +--- + +# Notes + +Source: concepts/notes.md +URL: /concepts/notes + +# Notes + +A note is a Markdown file with optional YAML frontmatter. Tolaria reads the first H1 as the primary title and keeps the file on disk as the durable representation. + +## Anatomy + +```md +--- +type: Project +status: Active +belongs_to: + - "[[workspace]]" +--- + +# Launch Documentation + +Draft the public Tolaria docs and keep them close to code changes. +``` + +## Titles + +The first H1 is the note title. Tolaria uses that title wherever the note is displayed: note lists, search results, wikilink suggestions, relationship pickers, tabs, and window titles. + +The title is separate from the filename. The filename stays visible in the breadcrumb so you can see the file on disk, and you can rename it independently when needed. + +Use the breadcrumb action to rename the file to match the title. New untitled notes can also auto-rename from the first H1 the first time they get a real title. Turn this behavior off in Settings > Vault Content > Titles & Filenames if you prefer filenames to stay unchanged until you rename them manually. + +## Body Links + +Use `[[wikilinks]]` to connect notes from the body. Tolaria shows autocomplete suggestions while you type, and links can resolve by filename or title. + +## Frontmatter + +Use frontmatter for structured fields such as type, status, date, URL, and relationships. Keep free-form thinking in the body. + +Some notes can be displayed with specialized editors while keeping the same file-first model. A note with `_display: sheet` opens as a spreadsheet and stores its cells in a CSV-like body, while `type` remains available for organization. See [Spreadsheets](/concepts/spreadsheets). + +--- + +# Properties + +Source: concepts/properties.md +URL: /concepts/properties + +# Properties + +Properties are frontmatter fields that Tolaria can display, filter, and edit. + +## Suggested Properties + +Suggested properties are the fields Tolaria knows how to create quickly from the Properties panel. When a suggested property is missing, the panel shows a shortcut to add it with the right editor. + +| Field | Purpose | +| --- | --- | +| `type` | Groups the note into a type such as Project, Person, or Topic. | +| `status` | Tracks lifecycle state such as Active, Done, or Blocked. | +| `url` | Stores a canonical external link. | +| `date` | Represents a single date. | + +## System Properties + +Fields that start with `_` are system properties. They remain in plain text but are hidden from normal property editing. + +Examples include `_icon`, `_color`, `_order`, `_sidebar_label`, `_width`, and `_pinned_properties` on type documents or notes. + +## Property Editing + +The Properties panel is the safest place to edit structured properties. Toggle it with `Cmd+Shift+I` on macOS or `Ctrl+Shift+I` on Windows and Linux. + +Date fields use Tolaria's picker, relationship fields can use wikilinks, and raw Markdown mode is available when you need direct control over YAML. + +--- + +# Relationships + +Source: concepts/relationships.md +URL: /concepts/relationships + +# Relationships + +Relationships make a vault feel like a graph instead of a pile of documents. + +## Relationship Fields + +Any frontmatter field containing wikilinks can become a relationship. Relationship fields can point to one note or to an array of notes. + +```yaml +belongs_to: + - "[[product-work]]" +related_to: + - "[[documentation]]" + - "[[editor-research]]" +blocked_by: + - "[[release-process]]" + - "[[sync-conflicts]]" +``` + +Tolaria supports default relationship fields out of the box: `belongs_to`, `has`, and `related_to`. It also detects custom relationship fields dynamically when they contain wikilinks. + +Default relationships have automatically computed inverses. If a note says it `belongs_to` a project, the project can show that note under its inverse `has` relationship without you writing the reverse link by hand. `related_to` works as a lateral relationship in both directions. + +These outgoing and inverse relationships appear in the Properties panel and in Neighborhood mode, where the note list becomes a graph view around the selected note. + +## Body Links Versus Relationship Fields + +Use body links when the relationship appears naturally in writing. Use frontmatter relationships when the connection is important enough to show in navigation, filters, Neighborhood mode, or the Properties panel. + +## Backlinks + +Tolaria can show incoming links and inverse relationships, making it easier to navigate from a note to the rest of its context. + +--- + +# Spreadsheets + +Source: concepts/spreadsheets.md +URL: /concepts/spreadsheets + +# Spreadsheets + +Tolaria sheets are spreadsheet notes. They keep the same file-first model as other notes, but a note with `_display: sheet` opens in a spreadsheet editor instead of the rich text editor. The note's `type` remains available for organization. + +The durable file is still Markdown with YAML frontmatter. The body is CSV-like text containing cell inputs and formulas, and spreadsheet presentation state is stored as plain YAML under `_sheet`. + +## Read Next + +- [Use Spreadsheets](/guides/use-spreadsheets) for the editing workflow. +- [Spreadsheet File Format](/reference/spreadsheet-format) for the plain-text storage contract. +- [Spreadsheet Formulas](/reference/spreadsheet-functions) for formula syntax, autocomplete, and IronCalc function families. + +## Why Sheet Notes + +Sheets are useful when information is better modeled as rows, columns, and formulas than as prose. Examples include budgets, revenue models, inventories, editorial calendars, lightweight trackers, and analytical scratchpads. + +Tolaria does not store sheets as opaque workbook binaries. A sheet should remain: + +- readable in a text editor +- diffable in Git +- editable by humans and AI agents +- available offline +- connected to the rest of the vault through types, properties, relationships, and wikilinks + +## One Note, One Sheet + +A sheet note is a single sheet. Tolaria does not expose multiple tabs inside one note. + +When a model needs more than one table, create more than one sheet note and connect them with wikilinks or cross-sheet formulas. This keeps each file small, legible, and aligned with Tolaria's graph model. + +For example: + +- `newsletter-revenue.md` +- `sponsorship-pipeline.md` +- `refactoring-business-plan.md` + +Each can be a normal `_display: sheet` note, and formulas can reference cells in another sheet note with Tolaria's wikilink cell syntax. + +## Editing + +The interactive sheet editor is backed by IronCalc. Tolaria uses IronCalc for spreadsheet behavior and formula evaluation, then adapts the workbook back to Tolaria's plain-text note format. + +In the sheet editor: + +- cell inputs that start with `=` are formulas +- non-formula cells can contain normal text, numbers, dates, and `[[wikilinks]]` +- typing `[[` in a cell opens the same note autocomplete concept used elsewhere in Tolaria +- typing a formula function name opens inline formula autocomplete for the bundled IronCalc function catalog +- right-clicking a selection exposes core formatting controls such as number formats, decimal precision, bold, italic, and clear formatting + +Keyboard basics follow spreadsheet conventions: + +- arrow keys move the active cell +- `Shift` with arrows extends the selection +- `Enter` starts editing the active cell +- `Escape` exits cell editing while keeping focus in the sheet +- `Delete` or `Backspace` clears the selected range +- copy and paste should preserve formulas, including Tolaria cross-sheet references + +## Wikilinks In Cells + +Wikilinks in non-formula cells are stored as normal Tolaria wikilinks: + +```csv +Project,Owner,Status +[[website-redesign]],[[person/alice]],Active +[[sponsorship-pipeline]],[[person/matteo]],Review +``` + +The cell still behaves like a spreadsheet cell, but the value remains a vault link that Tolaria can understand. + +## Note Reference Formulas + +Tolaria adds a sheet-note reference syntax on top of IronCalc formulas: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=[[refactoring-business-plan]].$C$18 +``` + +The target before the dot is a normal Tolaria wikilink target. For another sheet note, the part after the dot is an A1-style cell address. + +Relative and absolute references work like spreadsheet references when copied: + +- `[[revenue]].B5` can shift when pasted to another cell +- `[[revenue]].$B$5` stays fixed +- `[[revenue]].B$5` fixes the row +- `[[revenue]].$B5` fixes the column + +This is not the same as an IronCalc workbook tab reference. It is Tolaria-specific syntax for referencing another sheet note in the vault. + +Current cross-sheet formulas resolve single cells. Ranges across sheet notes are not a stable file-format feature yet, so prefer composing them from explicit cell references or keeping range formulas inside the same sheet note. + +Sheet formulas can also read scalar frontmatter properties from a note: + +```txt +=[[device]].power.watts +=[[project-alpha]].status +``` + +This keeps sheet models connected to ordinary Tolaria metadata without requiring a saved view or query. Unresolved, ambiguous, or non-scalar property references show spreadsheet errors. + +## Storage + +A minimal sheet note looks like this: + +```md +--- +type: Project +_display: sheet +status: Draft +belongs_to: + - "[[business-plan]]" +_sheet: + frozen_rows: 1 + columns: + A: + width: 180 + cells: + E6: + num_fmt: "0.00%" +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Services,800,900,750,=SUM(B3:D3) +Expenses,650,700,760,=SUM(B4:D4) +Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5) +Growth,,=(C5-B5)/B5,=(D5-C5)/C5,=(E5-B5)/B5 +``` + +Normal frontmatter stays normal Tolaria metadata. `_sheet` is system metadata for the spreadsheet editor and is hidden from normal property editing. + +For the full storage contract, see [Spreadsheet File Format](/reference/spreadsheet-format). + +## Formulas + +Tolaria delegates formula calculation to IronCalc. IronCalc aims for Excel-compatible formulas, while its project documentation still describes it as work in progress. For Tolaria-specific formula behavior and the autocomplete function catalog, see [Spreadsheet Formulas](/reference/spreadsheet-functions). + +--- + +# Types + +Source: concepts/types.md +URL: /concepts/types + +# Types + +Types describe what kind of thing a note represents: Project, Person, Topic, Procedure, Event, or any category you create. + +## Type Field + +The `type:` field assigns a note to a type. + +```yaml +type: Project +``` + +Tolaria does not infer type from folder location. Moving a file into another folder does not change its type. + +## Prefer Types Over Folders + +Types are the preferred way to group notes in Tolaria. Folders are supported for existing vaults and fallback organization, but Tolaria is built around types and relationships because they carry stronger meaning than file paths. + +Use types for semantic groups such as Projects, People, Topics, Procedures, Events, and Essays. Use relationships to connect notes across those groups. This gives Tolaria better structure for navigation, filtering, properties, templates, and future automation than folder location alone. + +## Type Documents + +Type documents are Markdown notes with `type: Type` in frontmatter. They describe how a type should appear and what new notes of that type should start with. + +```yaml +--- +type: Type +_icon: folder +_color: blue +_sidebar_label: Projects +_order: 10 +--- + +# Project +``` + +Type templates can live in the Type document's `template` frontmatter field. When a hand-edited Type body contains template-like structure after its own `# TypeName` heading, Tolaria also uses that body content as the new-note template. Plain descriptive body text stays documentation-only. + +## What Types Control + +- Sidebar grouping. +- Type icon and color. +- Sidebar order and label. +- Pinned properties. +- New-note templates. + +## New Note Defaults + +Type documents can define empty properties and relationships. When you create a new note of that type, Tolaria shows placeholders for those fields so you can fill them in from the Properties panel. + +If a type document gives a property a value, that value becomes the default for new notes of that type. For example, a Project type can define `status: Active` so every new project starts active until you change it. + +--- + +# Vaults + +Source: concepts/vaults.md +URL: /concepts/vaults + +# Vaults + +A vault is the folder Tolaria reads and writes. The filesystem is the source of truth; the app state and cache are derived from files. + +## Core Rules + +- Notes are Markdown files. +- YAML frontmatter provides structure. +- Attachments are normal files inside the vault. +- Type definitions and saved views are also files. +- Git can track history and support remote sync. + +## Why Local Files Matter + +Local files keep your notes inspectable. You can open them in another editor, search with command-line tools, back them up with your own system, and version them with Git. + +Tolaria should never become the only way to read your data. + +## Git Is A Capability + +A plain folder of Markdown files can open as a vault. Git-backed vaults unlock history, changes, commits, pull, push, conflict handling, and remote setup. + +If a folder is not a Git repository, Tolaria can initialize Git when you explicitly ask it to. It avoids initializing broad personal folders such as Desktop, Documents, or Downloads unless they are clearly dedicated vault folders. + +## Multiple Vaults At The Same Time + +Tolaria can load multiple registered vaults into one unified graph. Enable this from `Settings` -> `Vaults` -> `Use multiple vaults at the same time`. + +After the option is enabled, open the bottom-left vault menu to include or exclude vaults from the graph. Included vaults appear together in note lists, search, quick open, backlinks, and wikilink navigation. Each note keeps a compact vault badge when Tolaria needs to disambiguate where it lives. + +The selected vault still matters. Git status, commits, sync, folder navigation, saved views, and vault repair actions stay scoped to the current repository. Use `Manage vaults` from the vault menu or the Vaults settings section to rename vaults, choose colors, and set the default destination for new notes. + +Cross-vault wikilinks use the target vault's stable alias when needed, for example `[[team/projects/alpha]]`. Links inside the same vault stay normal vault-relative links. + +## App State Versus Vault State + +Vault-level information should travel with the vault. Machine-specific preferences stay with the app installation. + +| Vault state | App state | +| --- | --- | +| Type icons and colors | Editor zoom | +| Saved views | Window size | +| Pinned properties | Recent vault list | +| Relationship conventions | Local cache | +| Vault AI guidance files | AI target selection | \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/configure-ai-models.md b/src-tauri/gen/apple/assets/agent-docs/configure-ai-models.md new file mode 100644 index 0000000..c6a87e5 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/configure-ai-models.md @@ -0,0 +1,30 @@ +# Configure AI Models + +Source: guides/configure-ai-models.md +URL: /guides/configure-ai-models + +# Configure AI Models + +Use model providers when you want chat over note context without giving an agent vault-write tools. + +## Local Models + +Local model targets are for tools such as Ollama and LM Studio. They usually need a base URL and model ID, and they usually do not need an API key. + +## API Models + +API model targets are for hosted providers such as OpenAI, Anthropic, Gemini, OpenRouter, or another OpenAI-compatible endpoint. + +Tolaria does not store provider API keys in vault settings. Choose one of the supported key paths: + +- Save the key locally on this device. +- Read the key from an environment variable. +- Use no key for local providers that do not require one. + +## Test The Connection + +After adding a provider, use the test action in Settings. A successful test means Tolaria reached the endpoint and the model replied. + +## Select The Target + +Once configured, choose the model from the AI target selector or set it as the default AI target in Settings. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/connect-a-git-remote.md b/src-tauri/gen/apple/assets/agent-docs/connect-a-git-remote.md new file mode 100644 index 0000000..84ec167 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/connect-a-git-remote.md @@ -0,0 +1,28 @@ +# Connect A Git Remote + +Source: guides/connect-a-git-remote.md +URL: /guides/connect-a-git-remote + +# Connect A Git Remote + +Connect a remote when you want backup or sync beyond the current machine. + +## Before You Start + +Make sure the remote repository exists and your system Git can authenticate to it. Tolaria uses system Git rather than storing provider-specific credentials. + +## Steps + +1. Open the bottom status bar remote chip, or run `Add Remote` from the command palette. +2. Paste the remote URL. +3. Confirm the remote name. +4. Fetch or push according to the app prompt. + +## Recommended Auth + +- SSH keys. +- GitHub CLI authentication. +- Existing Git credential helpers. +- macOS Keychain credentials for HTTPS remotes on macOS. + +If authentication fails, see [Git Authentication](/troubleshooting/git-auth). \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/contribute.md b/src-tauri/gen/apple/assets/agent-docs/contribute.md new file mode 100644 index 0000000..4fe2672 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/contribute.md @@ -0,0 +1,37 @@ +# Contribute + +Source: reference/contribute.md +URL: /reference/contribute + +# Contribute + +Tolaria is free and open source, and any kind of help is useful. Pick the path that matches what you want to do. + +## Newsletter + +[Refactoring](https://refactoring.fm/) is Luca's newsletter and community for engineers building better teams and software with AI. Subscribing is the best way to support Tolaria. + +## Sponsors + +Tolaria is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: + +- [Codacy](https://www.codacy.com/) +- [CodeScene](https://codescene.com/) +- [CircleCI](https://circleci.com/) +- [Unblocked](https://getunblocked.com/) + +## Feature Requests + +Use the [product board](https://tolaria.canny.io/) for feature ideas. Search first, upvote existing ideas, and create a new post when the request is genuinely new. + +## Discussions + +Use [GitHub Discussions](https://github.com/refactoringhq/tolaria/discussions) for questions, conversations, show and tell, and broader community context. + +## Contribute Code + +Small, focused pull requests are welcome. Check the product board first so you build the right thing, then open a PR on [GitHub](https://github.com/refactoringhq/tolaria/pulls). The [contributing guide](https://github.com/refactoringhq/tolaria/blob/main/CONTRIBUTING.md) explains the local workflow. + +## Report A Bug + +Use [GitHub Issues](https://github.com/refactoringhq/tolaria/issues) for bugs. Include what happened, what you expected, and clear reproduction steps. If you are reporting from inside Tolaria, use the Contribute panel to copy sanitized diagnostics and attach them to the issue. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/create-types.md b/src-tauri/gen/apple/assets/agent-docs/create-types.md new file mode 100644 index 0000000..22c93af --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/create-types.md @@ -0,0 +1,40 @@ +# Create Types + +Source: guides/create-types.md +URL: /guides/create-types + +# Create Types + +Create a type when several notes share the same role in your system. + +## Steps + +1. Run `New Type` from the command palette, or click `+` in the Types header in the sidebar. +2. Give the type a clear name. +3. Add optional icon, color, sidebar order, sidebar label, pinned properties, suggested fields, default values, or a new-note template. + +You can also right-click a type in the sidebar to change its icon and color. + +```yaml +--- +type: Type +_icon: briefcase +_color: blue +_sidebar_label: Projects +_order: 10 +--- + +# Project +``` + +## Use Types Sparingly + +A type should represent a recurring category, not a one-off label. If you only need a temporary grouping, use a saved view or property instead. + +## Templates + +Type documents can include a Markdown template for new notes of that type. Keep templates small and useful: a heading, a few expected fields, and the first checklist are usually enough. + +You can store the template in the Type document's `template` frontmatter field. When hand-editing the Type document body, content after the Type note's own `# TypeName` heading is also used as the new-note template if it looks like template structure such as field labels, secondary headings, or checklist starters. Plain descriptive body text is ignored. + +Type documents can also define fields for new notes. Empty properties and relationships become placeholders in new notes of that type. Properties with values become defaults for new notes of that type. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/docs-maintenance.md b/src-tauri/gen/apple/assets/agent-docs/docs-maintenance.md new file mode 100644 index 0000000..b15cc24 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/docs-maintenance.md @@ -0,0 +1,43 @@ +# Docs Maintenance + +Source: reference/docs-maintenance.md +URL: /reference/docs-maintenance + +# Docs Maintenance + +The public docs live in the app repo so documentation changes can ship with behavior changes. + +## Update Docs When You Change + +- A Tauri command. +- A new component or hook that changes user behavior. +- A data model or frontmatter convention. +- Git, AI, onboarding, or release behavior. +- Public release pages, download metadata, or updater channels. +- Platform support. +- Keyboard shortcuts. + +## Suggested Workflow + +1. Make the code change. +2. Update the matching concept, guide, or reference page. +3. Add a troubleshooting page if the change creates a new failure mode. +4. Run `pnpm docs:build`. +5. Check the home page, search, release/download links, and changed docs pages in a browser. + +## Page Types + +| Type | Purpose | +| --- | --- | +| Start | Helps a new user get into the app. | +| Concepts | Explains mental models. | +| Guides | Teaches workflows. | +| Reference | Gives stable facts and tables. | +| Troubleshooting | Starts from a symptom and ends with recovery. | + +## Review Checklist + +- Does the page describe current behavior? +- Does it mention macOS primary and Windows/Linux supported-early status when platform support matters? +- Are links relative and VitePress-compatible? +- Can a user discover the page with local search? \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/editor.md b/src-tauri/gen/apple/assets/agent-docs/editor.md new file mode 100644 index 0000000..e3e1b5a --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/editor.md @@ -0,0 +1,28 @@ +# Editor + +Source: concepts/editor.md +URL: /concepts/editor + +# Editor + +Tolaria offers a rich editor for daily writing and a raw Markdown mode for exact file control. Both modes write back to the same Markdown file. + +## Rich Editing + +The rich editor supports blocks, slash commands, wikilinks, tables, code blocks, images, Mermaid diagrams, LaTeX-style math, and markdown-backed whiteboards. + +Use it when you want to write and reorganize quickly without thinking about Markdown syntax. + +## Raw Mode + +Raw mode shows the Markdown source directly. Use it when you need to edit YAML frontmatter, repair unusual Markdown, or make an exact text change. + +Toggle raw mode with `Cmd+\` on macOS or `Ctrl+\` on Windows and Linux. + +## Table Of Contents + +The table of contents panel builds an outline from headings in the current note. It is useful for long notes, procedures, research files, and generated documents. Toggle it with `Cmd+Shift+T` on macOS or `Ctrl+Shift+T` on Windows and Linux. + +## Width + +Notes can use normal or wide editor width. Set the default in Settings, or override an individual note from the editor toolbar. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/file-layout.md b/src-tauri/gen/apple/assets/agent-docs/file-layout.md new file mode 100644 index 0000000..51d84c7 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/file-layout.md @@ -0,0 +1,48 @@ +# File Layout + +Source: reference/file-layout.md +URL: /reference/file-layout + +# File Layout + +Tolaria is not opinionated about folder structure. It finds notes recursively across the whole vault, stores new notes in the root by default, and uses types and relationships for real organization. + +```txt +my-vault/ + project-alpha.md + weekly-review.md + research/ + source-notes.md + attachments/ + diagram.png + source.pdf + project.md + person.md + views/ + active-projects.yml +``` + +## Root Notes + +Tolaria works well with a flat vault. Folders are optional and can be useful for compatibility with other tools, but they are not required for people, projects, topics, or any other note category. + +Type is not inferred from folder location. It comes from frontmatter, and relationships are expressed with wikilinks in fields. That is what Tolaria uses for the sidebar, Properties panel, search, custom views, and neighborhood navigation. + +## Special Folders + +| Folder | Purpose | +| --- | --- | +| `views/` | Saved custom views. | +| `attachments/` | Images and other attached files. | + +PDFs, images, and other non-Markdown files stay as normal files. Folder browsing can show them in place, and Settings controls whether PDFs, images, and unsupported files appear in All Notes. + +Whiteboards are Markdown files with durable tldraw data, so they belong with notes rather than in `attachments/`. + +Spreadsheets are also Markdown files. A note with `_display: sheet` stores ordinary frontmatter plus a CSV-like body and opens in the sheet editor. + +Type definitions are Markdown notes with `type: Type` in frontmatter. New type documents are normal notes, and existing type documents in older folders still work. + +## Git Files + +If the vault is a Git repository, `.git/` belongs to Git. Tolaria reads Git state but does not treat `.git/` as notes. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/files-and-media.md b/src-tauri/gen/apple/assets/agent-docs/files-and-media.md new file mode 100644 index 0000000..67e5491 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/files-and-media.md @@ -0,0 +1,39 @@ +# Files And Media + +Source: concepts/files-and-media.md +URL: /concepts/files-and-media + +# Files And Media + +Tolaria starts with Markdown notes, but a vault can also contain images, PDFs, media files, whiteboards, and other local files. + +## Mermaid Diagrams + +Use Mermaid code blocks when a note needs a diagram that should stay plain text and versionable. + +````md +```mermaid +flowchart LR + Idea --> Draft --> Review --> Publish +``` +```` + +Tolaria renders Mermaid diagrams in the editor while keeping the source in Markdown. + +## Attachments + +Images pasted into the editor are saved into the vault as normal files. They remain portable and can be opened by other tools. + +## Previews + +Tolaria can preview common image files, PDFs, and supported media files in the app. Files without an in-app preview can still be opened in the default system app. + +Settings control whether PDFs, images, and unsupported files appear in All Notes. Folder browsing still shows files in their folders. + +## Whiteboards + +Whiteboards use tldraw in the editor, but their durable representation stays in Markdown. That keeps them inside the vault and versioned by Git with the rest of your notes. + +## Git Boundary + +If generated or local-only files are ignored by Git, Tolaria can hide them from notes, search, quick open, and folders. Use this when build artifacts or private local files should not behave like vault content. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/first-launch.md b/src-tauri/gen/apple/assets/agent-docs/first-launch.md new file mode 100644 index 0000000..dde0c9c --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/first-launch.md @@ -0,0 +1,40 @@ +# First Launch + +Source: start/first-launch.md +URL: /start/first-launch + +# First Launch + +The first launch flow is designed to get you into a real vault quickly without hiding the local-first model. + +## What You Choose + +Tolaria asks whether you want to: + +- Create or clone the Getting Started vault. +- Open an existing local vault. +- Create a new empty vault. + +The Getting Started vault is cloned locally and then disconnected from its remote. That keeps the sample safe to edit without accidentally pushing tutorial changes. + +## What Tolaria Creates + +Tolaria stores app-level settings on the local machine. Your notes stay in the vault folder you choose. + +| Data | Stored in | +| --- | --- | +| Notes and attachments | Your vault folder | +| Type definitions and saved views | Your vault folder | +| Window size, zoom, recent vaults | Local app settings | +| Cache data | Rebuildable local cache | + +## First Commands To Try + +- `Cmd+K` / `Ctrl+K`: open the command palette. +- `New Note`: create a note in the current vault. +- `Open Getting Started Vault`: clone the public sample vault. +- `Reload Vault`: rescan files after external edits. + +## AI Setup Prompt + +Tolaria can show an optional AI agents prompt after a vault is open. It checks common local install locations for supported coding agents and gives you setup paths, but you can dismiss it and use Tolaria without AI. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/frontmatter-fields.md b/src-tauri/gen/apple/assets/agent-docs/frontmatter-fields.md new file mode 100644 index 0000000..6977e34 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/frontmatter-fields.md @@ -0,0 +1,35 @@ +# Frontmatter Fields + +Source: reference/frontmatter-fields.md +URL: /reference/frontmatter-fields + +# Frontmatter Fields + +Tolaria uses conventions instead of a required schema. + +| Field | Meaning | +| --- | --- | +| `type` | The note's entity type. | +| `status` | Lifecycle state. | +| `icon` | Per-note icon. | +| `url` | External URL. | +| `date` | Single date. | +| `belongs_to` | Parent relationship. | +| `related_to` | Lateral relationship. | +| `has` | Contained relationship. | +| `_width` | Per-note editor width override. | +| `_display` | Display mode. Omit for text notes; use `sheet` for spreadsheet notes. | +| `_icon`, `_color` | Type or note appearance metadata. | +| `_sidebar_label`, `_order` | Type sidebar label and order. | +| `_pinned_properties` | Properties pinned for a type. | +| `_sheet` | Sheet-note presentation metadata such as grid settings, column widths, row heights, and cell formatting. | + +## Custom Fields + +You can add your own fields. If a field contains wikilinks, Tolaria can treat it as a relationship. + +## System Fields + +Fields starting with `_` are reserved for system behavior and hidden from standard property editing. They remain plain YAML, so they can still be inspected or changed in raw mode when needed. + +Nested keys under a system field are also system-owned. For example, `_sheet.cells.B6.num_fmt` belongs to the sheet editor and should not appear as a normal user property. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/getting-started-vault.md b/src-tauri/gen/apple/assets/agent-docs/getting-started-vault.md new file mode 100644 index 0000000..314c1b2 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/getting-started-vault.md @@ -0,0 +1,37 @@ +# Getting Started Vault + +Source: start/getting-started-vault.md +URL: /start/getting-started-vault + +# Getting Started Vault + +The Getting Started vault is a small public sample vault hosted at [refactoringhq/tolaria-getting-started](https://github.com/refactoringhq/tolaria-getting-started). + +It exists to show Tolaria's conventions without requiring you to restructure your own notes first. + +## What It Demonstrates + +- Markdown notes with YAML frontmatter. +- Types such as Project, Person, Topic, and Procedure. +- Wikilinks in note bodies. +- Relationship fields in frontmatter. +- A local Git repository that can be connected to a remote later. +- Vault guidance files for AI agents. + +## Local-Only By Default + +When Tolaria clones the sample, it removes the remote from the local copy. This makes the sample vault disposable. You can edit it freely, commit locally, and delete it later. + +To connect a vault to your own remote, use the bottom status bar remote chip or run `Add Remote` from the command palette. + +Tolaria also repairs starter-vault guidance files when needed. `AGENTS.md` is the canonical guidance file, `CLAUDE.md` is kept as a compatibility shim, and `GEMINI.md` is only created when you explicitly restore Antigravity/Gemini guidance. + +## Use It Alongside Your Own Vaults + +You can keep the Getting Started vault open while working in your own notes. Enable `Settings` -> `Vaults` -> `Use multiple vaults at the same time`, then use the bottom-left vault menu to include both the sample vault and your real vault in the unified graph. + +This lets search, quick open, note lists, backlinks, and wikilink navigation span both vaults. Git actions still stay scoped to each vault's own repository, and new notes go to the default vault you choose in `Manage vaults`. + +## When To Move On + +After you understand the sample, open your own vault. Tolaria does not require a special folder structure: a folder of Markdown files is enough to start. You can remove the sample from Tolaria's vault list later without deleting its files from disk. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/git-auth.md b/src-tauri/gen/apple/assets/agent-docs/git-auth.md new file mode 100644 index 0000000..f4cd498 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/git-auth.md @@ -0,0 +1,30 @@ +# Git Authentication + +Source: troubleshooting/git-auth.md +URL: /troubleshooting/git-auth + +# Git Authentication + +Tolaria uses system Git authentication. It does not manage provider passwords directly. + +## Symptoms + +- Push fails. +- Pull asks for credentials repeatedly. +- Remote fetch works in one terminal but not in Tolaria. + +## Checks + +1. Open a terminal. +2. `cd` into the vault. +3. Run `git remote -v`. +4. Run `git fetch`. + +If `git fetch` fails in the terminal, fix system Git auth first. + +## Common Fixes + +- Sign in with GitHub CLI. +- Configure SSH keys. +- Update the remote URL. +- Check your credential helper. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/git.md b/src-tauri/gen/apple/assets/agent-docs/git.md new file mode 100644 index 0000000..f31d363 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/git.md @@ -0,0 +1,34 @@ +# Git + +Source: concepts/git.md +URL: /concepts/git + +# Git + +Git is Tolaria's recommended history and sync layer. Tolaria can work with plain Markdown folders, and Git unlocks local history, recovery, remote backup, and multi-device workflows when you want them. + +Tolaria acts as a lightweight Git client for your vault. You can review changes, commit, pull, push, and inspect history without leaving the app. + +## What Tolaria Uses Git For + +- Whole-vault commit history. +- Current diff for the vault. +- Per-note history. +- Current diff for an individual note. +- Pull and push. +- Conflict detection and resolution. +- Remote connection for local-only vaults. + +## History And Diffs + +Each note can show its own history and current diff, so you can understand how that file changed over time or what is unsaved relative to Git. + +Tolaria also shows a history of the whole vault. Use it when you want to review broader changes across multiple notes before committing or syncing. + +## Local Commits + +You can commit changes inside Tolaria without leaving the app. This gives you useful restore points even before a remote is configured. + +## Remotes + +Connect a compatible Git remote when you want sync or backup. Tolaria relies on your system Git authentication, so GitHub CLI, SSH keys, credential helpers, and existing Git configuration can continue to work. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/guides.md b/src-tauri/gen/apple/assets/agent-docs/guides.md new file mode 100644 index 0000000..d041041 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/guides.md @@ -0,0 +1,597 @@ +# Build Custom Views + +Source: guides/build-custom-views.md +URL: /guides/build-custom-views + +# Build Custom Views + +Custom views are saved filters for recurring questions. + +## Good View Candidates + +- Active projects. +- People without a recent follow-up. +- Drafts ready for review. +- Notes changed this week. +- Events in a date range. + +## View Definition + +Saved views live as files in the vault. They describe filters, sorting, and visible columns using structured data. + +## Filters + +Custom views can use nested conditions, similar to Notion or Airtable filter groups. Combine `all` and `any` logic when a view needs to answer a more precise question than a single field filter can express. + +Date filters support dynamic natural-language values such as `today`, `yesterday`, or `one week ago`. Use these for views that should keep moving over time, such as recent work, stale follow-ups, or upcoming events. + +## Design The Question First + +Before creating a view, write the question it answers. A good view is not "all fields with all filters"; it is a focused lens. + +--- + +# Capture A Note + +Source: guides/capture-a-note.md +URL: /guides/capture-a-note + +# Capture A Note + +Use capture when you need to get an idea into the vault before you know where it belongs. + +## Steps + +1. Press `Cmd+N` on macOS or `Ctrl+N` on Windows and Linux. +2. Write a clear H1. +3. Add the rough content. +4. Leave structure for later if you are still thinking. + +## Capture Well + +Prefer a useful title over a perfect taxonomy. You can add type, status, and relationships during inbox review. + +## When To Add Structure Immediately + +Add structure while capturing when the note's type or relationships are already obvious. Otherwise, capture the idea first and organize it later. + +--- + +# Manage Git Manually Or With AutoGit + +Source: guides/commit-and-push.md +URL: /guides/commit-and-push + +# Manage Git Manually Or With AutoGit + +Tolaria can act as a lightweight Git client for a Git-enabled vault. You can manage commits and pushes yourself, or enable AutoGit to create conservative checkpoints after editing pauses or when the app is no longer active. + +## Manual Git + +1. Open the Git or changes surface. +2. Review changed files. +3. Write a short commit message. +4. Commit locally. +5. Push when a remote is configured. + +If the remote has changed, pull first and resolve any conflicts. If the vault has no remote, manual commits still give you local history, diffs, and rollback. + +## AutoGit + +AutoGit is available in Settings for Git-enabled vaults. When enabled, Tolaria automatically commits and pushes saved local changes after an idle pause or after the app becomes inactive. + +Use AutoGit when you want the safety of regular checkpoints without interrupting capture or editing. You can still inspect each note's current diff, review note history, and browse the whole-vault history before making larger manual commits. + +## Use Small Commits + +Small commits make it easier to understand what changed, roll back safely, and review AI-generated edits. + +--- + +# Configure AI Models + +Source: guides/configure-ai-models.md +URL: /guides/configure-ai-models + +# Configure AI Models + +Use model providers when you want chat over note context without giving an agent vault-write tools. + +## Local Models + +Local model targets are for tools such as Ollama and LM Studio. They usually need a base URL and model ID, and they usually do not need an API key. + +## API Models + +API model targets are for hosted providers such as OpenAI, Anthropic, Gemini, OpenRouter, or another OpenAI-compatible endpoint. + +Tolaria does not store provider API keys in vault settings. Choose one of the supported key paths: + +- Save the key locally on this device. +- Read the key from an environment variable. +- Use no key for local providers that do not require one. + +## Test The Connection + +After adding a provider, use the test action in Settings. A successful test means Tolaria reached the endpoint and the model replied. + +## Select The Target + +Once configured, choose the model from the AI target selector or set it as the default AI target in Settings. + +--- + +# Connect A Git Remote + +Source: guides/connect-a-git-remote.md +URL: /guides/connect-a-git-remote + +# Connect A Git Remote + +Connect a remote when you want backup or sync beyond the current machine. + +## Before You Start + +Make sure the remote repository exists and your system Git can authenticate to it. Tolaria uses system Git rather than storing provider-specific credentials. + +## Steps + +1. Open the bottom status bar remote chip, or run `Add Remote` from the command palette. +2. Paste the remote URL. +3. Confirm the remote name. +4. Fetch or push according to the app prompt. + +## Recommended Auth + +- SSH keys. +- GitHub CLI authentication. +- Existing Git credential helpers. +- macOS Keychain credentials for HTTPS remotes on macOS. + +If authentication fails, see [Git Authentication](/troubleshooting/git-auth). + +--- + +# Create Types + +Source: guides/create-types.md +URL: /guides/create-types + +# Create Types + +Create a type when several notes share the same role in your system. + +## Steps + +1. Run `New Type` from the command palette, or click `+` in the Types header in the sidebar. +2. Give the type a clear name. +3. Add optional icon, color, sidebar order, sidebar label, pinned properties, suggested fields, default values, or a new-note template. + +You can also right-click a type in the sidebar to change its icon and color. + +```yaml +--- +type: Type +_icon: briefcase +_color: blue +_sidebar_label: Projects +_order: 10 +--- + +# Project +``` + +## Use Types Sparingly + +A type should represent a recurring category, not a one-off label. If you only need a temporary grouping, use a saved view or property instead. + +## Templates + +Type documents can include a Markdown template for new notes of that type. Keep templates small and useful: a heading, a few expected fields, and the first checklist are usually enough. + +You can store the template in the Type document's `template` frontmatter field. When hand-editing the Type document body, content after the Type note's own `# TypeName` heading is also used as the new-note template if it looks like template structure such as field labels, secondary headings, or checklist starters. Plain descriptive body text is ignored. + +Type documents can also define fields for new notes. Empty properties and relationships become placeholders in new notes of that type. Properties with values become defaults for new notes of that type. + +--- + +# Manage Display Preferences + +Source: guides/manage-display-preferences.md +URL: /guides/manage-display-preferences + +# Manage Display Preferences + +Display preferences live in local app settings unless a setting is intentionally stored in the note or vault. + +## Theme + +Choose Light, Dark, or System in Settings. System follows the operating system appearance at runtime. + +You can also switch theme mode from the command palette. + +## Note Width + +Set the default rich-editor width in Settings: + +- **Normal** for focused writing. +- **Wide** for tables, diagrams, dense notes, and generated documents. + +An individual note can override the default width from the editor toolbar. That override is stored as `_width` in the note frontmatter. + +## Sidebar Labels + +Tolaria can pluralize type names in the sidebar. Turn this off in Settings if your type names should be shown exactly as written, or use `_sidebar_label` on a type document for an explicit label. + +## Vault Content + +Settings also control whether Gitignored files and non-Markdown file categories are visible in the app. Use these controls to keep generated or local-only files out of regular note workflows. + +--- + +# Organize The Inbox + +Source: guides/organize-inbox.md +URL: /guides/organize-inbox + +# Organize The Inbox + +Inbox review turns quick captures into usable knowledge. + +## Remove A Note From Inbox + +When a note is organized enough, mark it as organized. Use `Cmd+E` on macOS or `Ctrl+E` on Windows and Linux, or click the organize action in the breadcrumb bar. + +That action is what removes the note from Inbox. If auto-advance is enabled in Settings > Workflow, Tolaria opens the next Inbox item immediately after you mark the current note organized. + +## Review Checklist + +- Rename unclear notes. +- Add or correct the first H1. +- Set `type`. +- Add `status` for actionable notes. +- Add `belongs_to`, `related_to`, or other relationship fields when useful. +- Archive or delete notes that no longer matter. + +## Make Notes Navigable + +A note is organized when you can answer: + +- What kind of thing is this? +- What is it connected to? +- What is this useful for? +- What will I do with it? + +## Avoid Over-Structuring + +Do not add fields just because they exist. Add the structure that will help future navigation, review, or automation. + +--- + +# Use The AI + +Source: guides/use-ai-panel.md +URL: /guides/use-ai-panel + +# Use The AI + +Tolaria gives you two ways to ask for AI help: open the AI panel for an ongoing conversation, or prompt directly from the editor with `Cmd+K` followed by a space. + +## Choose How To Prompt + +- **AI panel** is best for longer conversations, agent work, and requests that need visible back-and-forth. +- **Inline prompt** is best when you are already writing. Press `Cmd+K`, type a space, then write the prompt you want the AI to handle from the current note context. + +## Choose A Target + +Open Settings and choose the default AI target: + +- **Coding agent** for tool-backed vault editing through Claude Code, Codex, OpenCode, Pi, or Antigravity CLI. +- **Local model** for Ollama or LM Studio chat over note context. +- **API model** for OpenAI, Anthropic, Gemini, OpenRouter, or an OpenAI-compatible endpoint. + +If a coding agent is missing, install it and reopen Tolaria or switch to another target. + +## Permission Mode + +Coding agents support per-vault permission modes: + +- **Vault Safe** keeps agents limited to file, search, and edit tools. +- **Power User** can allow shell commands for agents that support them. + +Direct model targets always stay in chat mode. They can use note context, but they cannot edit vault files through tools. + +## Good Requests + +- "Find notes related to this project." +- "Summarize what changed in this note." +- "Draft a weekly review from these linked notes." +- "Update this checklist based on the current project status." + +## Review Changes + +AI edits are file edits. Review them with Tolaria's diff and Git history before committing. + +--- + +# Use The Command Palette + +Source: guides/use-command-palette.md +URL: /guides/use-command-palette + +# Use The Command Palette + +The command palette is the fastest way to move around Tolaria. + +Open it with: + +- `Cmd+K` on macOS. +- `Ctrl+K` on Linux and Windows. + +## Common Commands + +- New Note. +- Search. +- Open Settings. +- Reload Vault. +- Add Remote. +- Open Getting Started Vault. +- Toggle Raw Mode. +- Toggle Table of Contents. +- Toggle AI Panel. +- Use Light, Dark, or System theme. +- Open in New Window. + +## Keyboard-First Workflow + +Use the palette when you know what you want to do but do not want to hunt through panels. It is also the best place to discover commands as the app grows. + +--- + +# Use Media Previews + +Source: guides/use-media-previews.md +URL: /guides/use-media-previews + +# Use Media Previews + +Media previews let you inspect vault files without leaving Tolaria. + +## Open A File + +Select an image, PDF, media file, or unsupported file from a folder or file list. Tolaria opens supported files in the app and offers an external-open action for files that should use the system default app. + +## All Notes Visibility + +Open Settings to choose whether non-Markdown files appear in All Notes: + +- PDFs. +- Images. +- Unsupported files. + +Folder browsing still shows files in their folders even when a category is hidden from All Notes. + +## Attachments + +When you paste or drop an image into a note, Tolaria copies it into the vault and references the copied file from Markdown. + +## Troubleshooting + +If a preview does not render, open the file in the default app to confirm the file is valid, then check whether the file is inside the active vault and not blocked by operating-system permissions. + +--- + +# Use Spreadsheets + +Source: guides/use-spreadsheets.md +URL: /guides/use-spreadsheets + +# Use Spreadsheets + +Tolaria spreadsheets are sheet notes: Markdown files with frontmatter and a CSV-like body that open in a spreadsheet editor when their `Display as` value is `Sheet`. + +Use a sheet note when a model needs rows, columns, calculations, or repeated numeric editing. Use a normal note when the main artifact is prose. + +## Create A Sheet + +Use the command palette action `New Sheet`, or create/open a note and set its `Display as` to `Sheet` from the Properties panel. `Type` remains separate and can still be `Note`, `Project`, `Responsibility`, or any other Tolaria type. + +When a note is a sheet: + +- the YAML frontmatter remains available for type, status, relationships, wikilinks, and custom properties +- `_display: sheet` tells Tolaria to display the note with the spreadsheet editor +- the body is the sheet itself +- there is no rich-text body around the table +- the editor switches from the text editor to the spreadsheet editor + +## Enter Values + +Click a cell and type a value. Non-formula values can be text, numbers, dates, or wikilinks. + +Press `Enter` on a selected cell to edit the cell. Press `Escape` while editing to leave cell editing and keep focus in the sheet. + +Use `Delete` or `Backspace` to clear the selected cell or range. + +## Enter Formulas + +Formulas start with `=`. + +```txt +=B2+B3-B4 +=SUM(B2:D2) +=ROUND(E6, 2) +=IF(E6>0, "Up", "Down") +``` + +Tolaria shows inline formula autocomplete while you type. The autocomplete list is built from the implemented function catalog in the bundled IronCalc engine; formula evaluation is still handled by IronCalc. + +See [Spreadsheet Formulas](/reference/spreadsheet-functions) for syntax, supported examples, and links to the full IronCalc formula reference. + +## Select And Edit Ranges + +The sheet editor follows spreadsheet conventions: + +- arrow keys move the active cell +- `Shift` plus arrow keys extends the selection +- drag to select a range +- copy and paste preserves formulas where possible +- cut and paste moves formulas and shifts relative references +- right-click a selected cell or range to apply formatting + +Right-click actions apply to the current selection. Keep a multi-cell selection active before opening the context menu when you want to format several cells together. + +## Format Cells + +Use the context menu for common formatting: + +- number formats such as plain numbers, currency, and percentages +- decimal precision +- bold and italic text +- alignment and clearing formatting when available + +Formatting is stored as plain YAML under `_sheet`, not in an opaque workbook blob. For example, percentage formatting for `E6` is stored as: + +```yaml +_sheet: + cells: + E6: + num_fmt: "0.00%" +``` + +See [Spreadsheet File Format](/reference/spreadsheet-format) for the full storage model. + +## Add Wikilinks + +Type `[[` in a cell to open note autocomplete. + +```csv +Project,Owner,Status +[[website-redesign]],[[person/alice]],Active +[[sponsorship-pipeline]],[[person/matteo]],Review +``` + +When the cell is not being edited, Tolaria renders the wikilink like other note links. When you edit the cell, the raw `[[wikilink]]` syntax is shown again. + +Command-click a wikilink in a sheet cell to open the linked note. + +## Reference Another Note + +Formulas can read a cell from another sheet note with Tolaria's wikilink cell syntax: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=ROUND([[business-plan]].$E$12, 2) +``` + +The part inside `[[...]]` resolves like a normal Tolaria wikilink. The part after the dot is an A1-style cell reference. + +Use absolute markers when copying formulas: + +| Reference | Copy behavior | +| --- | --- | +| `[[revenue]].B5` | row and column can shift | +| `[[revenue]].$B$5` | row and column stay fixed | +| `[[revenue]].B$5` | row fixed, column can shift | +| `[[revenue]].$B5` | column fixed, row can shift | + +Cross-sheet references currently resolve single cells. Keep range formulas inside one sheet note. + +Formulas can read scalar frontmatter properties from a note with dot notation: + +```txt +=[[device]].power.watts +=[[project-alpha]].status +=[[book-notes/the-design-of-everyday-things.md]].rating +``` + +Numbers, booleans, and text properties can be used in formulas. Missing or ambiguous note targets, missing properties, and non-scalar values such as lists or nested objects show as spreadsheet errors. + +## Work With The Raw File + +A sheet file remains readable text: + +```md +--- +type: Project +_display: sheet +status: Draft +belongs_to: + - "[[business-plan]]" +_sheet: + frozen_rows: 1 + columns: + A: + width: 180 +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Services,800,900,750,=SUM(B3:D3) +Expenses,650,700,760,=SUM(B4:D4) +Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5) +``` + +When editing this file with scripts or AI agents, parse the body as CSV and preserve formulas as formulas. Do not replace formulas with displayed values. + +--- + +# Use The Table Of Contents + +Source: guides/use-table-of-contents.md +URL: /guides/use-table-of-contents + +# Use The Table Of Contents + +The table of contents panel helps you navigate long notes by heading. + +## Open It + +Use the editor toolbar, the command palette, or the shortcut: + +- `Cmd+Shift+T` on macOS. +- `Ctrl+Shift+T` on Windows and Linux. + +## How It Works + +Tolaria builds the outline from the current note's headings. The panel updates as the note changes and can jump to sections in the editor. + +## Good Uses + +- Long procedures. +- Meeting notes with many sections. +- Research notes. +- Generated documents that need review. + +If a note has no useful headings, add clear H2 and H3 sections rather than relying on a long uninterrupted document. + +--- + +# Use Wikilinks + +Source: guides/use-wikilinks.md +URL: /guides/use-wikilinks + +# Use Wikilinks + +Wikilinks connect notes by name. + +```md +This project belongs to [[content-systems]] and is related to [[git-workflows]]. +``` + +## Link From The Body + +Use body links when the connection is part of the sentence you are writing. + +## Link From Frontmatter + +Use frontmatter links when the relationship should become structured metadata. + +```yaml +related_to: + - "[[git-workflows]]" +``` + +## Keep Links Stable + +Prefer clear note titles and filenames. Tolaria's wikilink autocomplete helps you pick the right target while you type. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/home.md b/src-tauri/gen/apple/assets/agent-docs/home.md new file mode 100644 index 0000000..a326166 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/home.md @@ -0,0 +1,6 @@ +# Index + +Source: index.md +URL: / + + \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/inbox.md b/src-tauri/gen/apple/assets/agent-docs/inbox.md new file mode 100644 index 0000000..d6e69a9 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/inbox.md @@ -0,0 +1,28 @@ +# Inbox + +Source: concepts/inbox.md +URL: /concepts/inbox + +# Inbox + +The Inbox is for notes that have been captured but not yet organized. + +## Why It Exists + +Fast capture should not require perfect structure. The Inbox gives you a place to put incomplete notes, then process them later. + +The Inbox workflow is optional. Turn it off in Settings > Workflow if you prefer every note to appear organized by default. + +## Organizing Inbox Notes + +When reviewing the Inbox: + +1. Give the note a clear H1. +2. Set its `type`. +3. Add status, dates, or URL if useful. +4. Add relationships with wikilinks or frontmatter fields. +5. Move it into a folder only if the folder adds value. + +## Healthy Inbox Habit + +Keep the Inbox small enough that it can be reviewed in one focused pass. Tolaria works best when capture is fast and organization is deliberate. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/index.md b/src-tauri/gen/apple/assets/agent-docs/index.md new file mode 100644 index 0000000..a326166 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/index.md @@ -0,0 +1,6 @@ +# Index + +Source: index.md +URL: / + + \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/install.md b/src-tauri/gen/apple/assets/agent-docs/install.md new file mode 100644 index 0000000..1932c14 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/install.md @@ -0,0 +1,45 @@ +# Install Tolaria + +Source: start/install.md +URL: /start/install + +# Install Tolaria + +Tolaria publishes desktop builds for macOS, Windows, and Linux. macOS is the primary day-to-day development target, with Windows and Linux builds supported through the release pipeline and fixed as platform issues are found. + +## Download + +Use the latest stable release unless you are intentionally testing pre-release builds: + +- Download the latest stable build +- [Browse all GitHub releases](https://github.com/refactoringhq/tolaria/releases) +- Read the release notes + +## Homebrew + +On macOS you can install the cask: + +```bash +brew install --cask tolaria +``` + +## Platform Status + +| Platform | Status | Notes | +| --- | --- | --- | +| macOS | Primary | Apple Silicon and Intel builds are published. Homebrew is available. | +| Windows | Supported, early | NSIS installers and updater bundles are Tauri-signed. Authenticode publisher signing will be added after Windows certificate provisioning; company-managed SmartScreen, Defender, or WDAC policies can still require IT approval before install. | +| Linux | Supported, early | AppImage, deb, and RPM artifacts are published. Desktop behavior depends on distribution WebKitGTK and input-method integration. | + +See [Supported Platforms](/reference/supported-platforms) for the current support policy. + +## Managed Windows Devices + +Do not disable SmartScreen or Windows Security to install Tolaria. On a managed Windows device, install Tolaria through your normal software approval path if policy blocks unsigned or unknown-publisher installers. After Authenticode provisioning is complete, validate that the downloaded installer has a valid Tolaria publisher signature before installing. + +## After Installing + +1. Open Tolaria. +2. Choose the Getting Started vault if you want a guided sample. +3. Or open an existing folder of Markdown files as a vault. +4. Use the command palette with `Cmd+K` on macOS or `Ctrl+K` on Linux and Windows. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/keyboard-shortcuts.md b/src-tauri/gen/apple/assets/agent-docs/keyboard-shortcuts.md new file mode 100644 index 0000000..0ea896e --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/keyboard-shortcuts.md @@ -0,0 +1,29 @@ +# Keyboard Shortcuts + +Source: reference/keyboard-shortcuts.md +URL: /reference/keyboard-shortcuts + +# Keyboard Shortcuts + +| Shortcut | Action | +| --- | --- | +| `Cmd+K` / `Ctrl+K` | Open command palette. | +| `Cmd+P` / `Ctrl+P` | Quick open notes and files. | +| `Cmd+N` / `Ctrl+N` | Create a new note. | +| `Cmd+S` / `Ctrl+S` | Save current note. | +| `Cmd+F` / `Ctrl+F` | Find in the current note. | +| `Cmd+Shift+F` / `Ctrl+Shift+F` | Search the vault. | +| `Cmd+Shift+V` / `Ctrl+Shift+V` | Paste without formatting. | +| `Cmd+\` / `Ctrl+\` | Toggle raw Markdown mode. | +| `Cmd+Shift+T` / `Ctrl+Shift+T` | Toggle table of contents. | +| `Cmd+Shift+I` / `Ctrl+Shift+I` | Toggle Properties panel. | +| `Cmd+Shift+L` / `Ctrl+Shift+L` | Toggle AI panel. | +| `Cmd+[` / `Alt+Left` | Navigate back when available. | +| `Cmd+]` / `Alt+Right` | Navigate forward when available. | +| `Cmd+Shift+O` / `Ctrl+Shift+O` | Open current note in a new window. | +| `Cmd+D` / `Ctrl+D` | Toggle favorite for the current note. | +| `Cmd+E` / `Ctrl+E` | Mark the current Inbox note organized. | + +Some shortcuts vary by platform because macOS, Linux, and Windows reserve different key combinations. + +Use the command palette to discover the current command set. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/manage-display-preferences.md b/src-tauri/gen/apple/assets/agent-docs/manage-display-preferences.md new file mode 100644 index 0000000..6423910 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/manage-display-preferences.md @@ -0,0 +1,31 @@ +# Manage Display Preferences + +Source: guides/manage-display-preferences.md +URL: /guides/manage-display-preferences + +# Manage Display Preferences + +Display preferences live in local app settings unless a setting is intentionally stored in the note or vault. + +## Theme + +Choose Light, Dark, or System in Settings. System follows the operating system appearance at runtime. + +You can also switch theme mode from the command palette. + +## Note Width + +Set the default rich-editor width in Settings: + +- **Normal** for focused writing. +- **Wide** for tables, diagrams, dense notes, and generated documents. + +An individual note can override the default width from the editor toolbar. That override is stored as `_width` in the note frontmatter. + +## Sidebar Labels + +Tolaria can pluralize type names in the sidebar. Turn this off in Settings if your type names should be shown exactly as written, or use `_sidebar_label` on a type document for an explicit label. + +## Vault Content + +Settings also control whether Gitignored files and non-Markdown file categories are visible in the app. Use these controls to keep generated or local-only files out of regular note workflows. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/model-provider-connection.md b/src-tauri/gen/apple/assets/agent-docs/model-provider-connection.md new file mode 100644 index 0000000..50c0840 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/model-provider-connection.md @@ -0,0 +1,30 @@ +# Model Provider Connection + +Source: troubleshooting/model-provider-connection.md +URL: /troubleshooting/model-provider-connection + +# Model Provider Connection + +Use this checklist when a local or API model provider does not connect. + +## Local Providers + +For Ollama or LM Studio: + +1. Start the local model server. +2. Confirm the base URL in Tolaria matches the server. +3. Confirm the model ID is installed and loaded by the provider. +4. Use the Settings test action again. + +## API Providers + +For hosted providers: + +1. Confirm the provider kind and endpoint. +2. Confirm the model ID exists for your account. +3. Confirm the API key is saved locally or available in the configured environment variable. +4. Avoid storing secrets in the vault. + +## Chat Mode Boundary + +Direct model targets run in chat mode. If you need file-editing tools, use a coding agent target such as Claude Code, Codex, OpenCode, Pi, or Antigravity CLI. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/notes.md b/src-tauri/gen/apple/assets/agent-docs/notes.md new file mode 100644 index 0000000..f0e9295 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/notes.md @@ -0,0 +1,41 @@ +# Notes + +Source: concepts/notes.md +URL: /concepts/notes + +# Notes + +A note is a Markdown file with optional YAML frontmatter. Tolaria reads the first H1 as the primary title and keeps the file on disk as the durable representation. + +## Anatomy + +```md +--- +type: Project +status: Active +belongs_to: + - "[[workspace]]" +--- + +# Launch Documentation + +Draft the public Tolaria docs and keep them close to code changes. +``` + +## Titles + +The first H1 is the note title. Tolaria uses that title wherever the note is displayed: note lists, search results, wikilink suggestions, relationship pickers, tabs, and window titles. + +The title is separate from the filename. The filename stays visible in the breadcrumb so you can see the file on disk, and you can rename it independently when needed. + +Use the breadcrumb action to rename the file to match the title. New untitled notes can also auto-rename from the first H1 the first time they get a real title. Turn this behavior off in Settings > Vault Content > Titles & Filenames if you prefer filenames to stay unchanged until you rename them manually. + +## Body Links + +Use `[[wikilinks]]` to connect notes from the body. Tolaria shows autocomplete suggestions while you type, and links can resolve by filename or title. + +## Frontmatter + +Use frontmatter for structured fields such as type, status, date, URL, and relationships. Keep free-form thinking in the body. + +Some notes can be displayed with specialized editors while keeping the same file-first model. A note with `_display: sheet` opens as a spreadsheet and stores its cells in a CSV-like body, while `type` remains available for organization. See [Spreadsheets](/concepts/spreadsheets). \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/open-or-create-vault.md b/src-tauri/gen/apple/assets/agent-docs/open-or-create-vault.md new file mode 100644 index 0000000..3c51751 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/open-or-create-vault.md @@ -0,0 +1,35 @@ +# Open Or Create A Vault + +Source: start/open-or-create-vault.md +URL: /start/open-or-create-vault + +# Open Or Create A Vault + +A Tolaria vault is a folder on disk. The folder can contain Markdown notes, attachments, type definitions, saved views, and Git metadata. + +## Open An Existing Folder + +Choose an existing folder if you already have Markdown notes. Tolaria scans `.md` files and uses frontmatter when it exists. + +Good starting points: + +- A folder of plain Markdown files. +- An Obsidian-style vault. +- A Git repository containing notes. +- A copy of the Getting Started vault. + +## Create A New Vault + +Choose a new empty folder if you want Tolaria conventions from the start. New notes and optional type definitions are created as Markdown files. + +## Use More Than One Vault + +You do not have to merge everything into one folder. Register each local folder as its own vault, then turn on `Use multiple vaults at the same time` in `Settings` -> `Vaults`. + +Once enabled, the bottom-left vault menu lets you include vaults in the unified graph. Search, quick open, wikilinks, and note lists can span the included vaults, while Git sync and commits remain tied to each vault's own repository. + +## Git Is Recommended, Not Required + +Tolaria works well with a plain folder of Markdown files. You can open, edit, organize, and search notes without making the vault a Git repository. + +Git is recommended when you want local history, diff views, recovery, pull, push, and remote sync without a proprietary backend. If a vault is not already a repository, Tolaria can initialize one when you explicitly ask it to. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/organize-inbox.md b/src-tauri/gen/apple/assets/agent-docs/organize-inbox.md new file mode 100644 index 0000000..8cabe51 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/organize-inbox.md @@ -0,0 +1,36 @@ +# Organize The Inbox + +Source: guides/organize-inbox.md +URL: /guides/organize-inbox + +# Organize The Inbox + +Inbox review turns quick captures into usable knowledge. + +## Remove A Note From Inbox + +When a note is organized enough, mark it as organized. Use `Cmd+E` on macOS or `Ctrl+E` on Windows and Linux, or click the organize action in the breadcrumb bar. + +That action is what removes the note from Inbox. If auto-advance is enabled in Settings > Workflow, Tolaria opens the next Inbox item immediately after you mark the current note organized. + +## Review Checklist + +- Rename unclear notes. +- Add or correct the first H1. +- Set `type`. +- Add `status` for actionable notes. +- Add `belongs_to`, `related_to`, or other relationship fields when useful. +- Archive or delete notes that no longer matter. + +## Make Notes Navigable + +A note is organized when you can answer: + +- What kind of thing is this? +- What is it connected to? +- What is this useful for? +- What will I do with it? + +## Avoid Over-Structuring + +Do not add fields just because they exist. Add the structure that will help future navigation, review, or automation. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/portent.md b/src-tauri/gen/apple/assets/agent-docs/portent.md new file mode 100644 index 0000000..e07e136 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/portent.md @@ -0,0 +1,77 @@ +# Portent + +Source: templates/portent.md +URL: /templates/portent + +# Portent + +[Portent](https://portent.md) is an open specification and template for work and personal knowledge bases. + +It gives a Tolaria vault a small set of defaults for organizing information: clear types, generic graph-like relationships, and a simple lifecycle for captured knowledge. The goal is to make a knowledge base useful to humans and AI agents without forcing every person or team to design a private ontology first. + +## Core Questions + +Portent favors convention over configuration. Instead of asking "where should this go?", it asks: + +- What is this? +- What is it useful for? +- Is it captured, organized, or archived? + +Those questions map naturally to Tolaria's type documents, relationship fields, Inbox, organized state, archive behavior, and custom views. + +## Types + +Portent defines eight default types. + +PORT types are actionable: + +- Project +- Operation +- Responsibility +- Task + +ENTP types are non-actionable knowledge records: + +- Event +- Note +- Topic +- Person + +These defaults are meant to cover the common shape of personal and work knowledge with almost no setup. You can add custom types later, but Portent works best when the default vocabulary comes first. + +## Relationships + +Portent models knowledge as a graph. The two default relationships are: + +- `belongs_to`: primary ownership, composition, or context. +- `related_to`: a looser semantic connection. + +In Tolaria, these relationships can live in YAML frontmatter and point to other notes with wikilinks. That keeps the graph portable, searchable, and readable outside the app. + +## Lifecycle + +Portent separates capture from organization: + +1. Capture information quickly so it is not lost. +2. Organize it by assigning a type and useful relationships. +3. Archive it when it has served its purpose. + +Tolaria supports that lifecycle directly: the Inbox holds captured notes, organizing a note marks it ready for normal views, and archiving hides old or obsolete notes from active surfaces while keeping them available. + +## Why Use It + +A blank vault is flexible, but it also asks you to make structural decisions before you have momentum. Portent gives you enough structure to start capturing, organizing, and retrieving notes immediately. + +Because Portent is file-friendly and portable, the same model can work across local Markdown vaults, note apps, docs tools, and agent-readable knowledge bases. Tolaria is the first intended implementation, but the spec is not tied to Tolaria internals. + +## Start From The Template + +The fastest starting point is the Portent template vault: + +- [refactoringhq/portent-vault-template](https://github.com/refactoringhq/portent-vault-template) + +Use it as-is, rename pieces to match your language, or treat it as a reference model for your own Tolaria setup. + +## Learn More + +Visit [portent.md](https://portent.md) for the full spec, examples, and implementation notes. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/properties.md b/src-tauri/gen/apple/assets/agent-docs/properties.md new file mode 100644 index 0000000..3805a36 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/properties.md @@ -0,0 +1,31 @@ +# Properties + +Source: concepts/properties.md +URL: /concepts/properties + +# Properties + +Properties are frontmatter fields that Tolaria can display, filter, and edit. + +## Suggested Properties + +Suggested properties are the fields Tolaria knows how to create quickly from the Properties panel. When a suggested property is missing, the panel shows a shortcut to add it with the right editor. + +| Field | Purpose | +| --- | --- | +| `type` | Groups the note into a type such as Project, Person, or Topic. | +| `status` | Tracks lifecycle state such as Active, Done, or Blocked. | +| `url` | Stores a canonical external link. | +| `date` | Represents a single date. | + +## System Properties + +Fields that start with `_` are system properties. They remain in plain text but are hidden from normal property editing. + +Examples include `_icon`, `_color`, `_order`, `_sidebar_label`, `_width`, and `_pinned_properties` on type documents or notes. + +## Property Editing + +The Properties panel is the safest place to edit structured properties. Toggle it with `Cmd+Shift+I` on macOS or `Ctrl+Shift+I` on Windows and Linux. + +Date fields use Tolaria's picker, relationship fields can use wikilinks, and raw Markdown mode is available when you need direct control over YAML. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/reference.md b/src-tauri/gen/apple/assets/agent-docs/reference.md new file mode 100644 index 0000000..b604115 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/reference.md @@ -0,0 +1,684 @@ +# Contribute + +Source: reference/contribute.md +URL: /reference/contribute + +# Contribute + +Tolaria is free and open source, and any kind of help is useful. Pick the path that matches what you want to do. + +## Newsletter + +[Refactoring](https://refactoring.fm/) is Luca's newsletter and community for engineers building better teams and software with AI. Subscribing is the best way to support Tolaria. + +## Sponsors + +Tolaria is supported by a panel of tools Luca uses every day to keep the project healthy, tested, and ready for AI-assisted development: + +- [Codacy](https://www.codacy.com/) +- [CodeScene](https://codescene.com/) +- [CircleCI](https://circleci.com/) +- [Unblocked](https://getunblocked.com/) + +## Feature Requests + +Use the [product board](https://tolaria.canny.io/) for feature ideas. Search first, upvote existing ideas, and create a new post when the request is genuinely new. + +## Discussions + +Use [GitHub Discussions](https://github.com/refactoringhq/tolaria/discussions) for questions, conversations, show and tell, and broader community context. + +## Contribute Code + +Small, focused pull requests are welcome. Check the product board first so you build the right thing, then open a PR on [GitHub](https://github.com/refactoringhq/tolaria/pulls). The [contributing guide](https://github.com/refactoringhq/tolaria/blob/main/CONTRIBUTING.md) explains the local workflow. + +## Report A Bug + +Use [GitHub Issues](https://github.com/refactoringhq/tolaria/issues) for bugs. Include what happened, what you expected, and clear reproduction steps. If you are reporting from inside Tolaria, use the Contribute panel to copy sanitized diagnostics and attach them to the issue. + +--- + +# Docs Maintenance + +Source: reference/docs-maintenance.md +URL: /reference/docs-maintenance + +# Docs Maintenance + +The public docs live in the app repo so documentation changes can ship with behavior changes. + +## Update Docs When You Change + +- A Tauri command. +- A new component or hook that changes user behavior. +- A data model or frontmatter convention. +- Git, AI, onboarding, or release behavior. +- Public release pages, download metadata, or updater channels. +- Platform support. +- Keyboard shortcuts. + +## Suggested Workflow + +1. Make the code change. +2. Update the matching concept, guide, or reference page. +3. Add a troubleshooting page if the change creates a new failure mode. +4. Run `pnpm docs:build`. +5. Check the home page, search, release/download links, and changed docs pages in a browser. + +## Page Types + +| Type | Purpose | +| --- | --- | +| Start | Helps a new user get into the app. | +| Concepts | Explains mental models. | +| Guides | Teaches workflows. | +| Reference | Gives stable facts and tables. | +| Troubleshooting | Starts from a symptom and ends with recovery. | + +## Review Checklist + +- Does the page describe current behavior? +- Does it mention macOS primary and Windows/Linux supported-early status when platform support matters? +- Are links relative and VitePress-compatible? +- Can a user discover the page with local search? + +--- + +# File Layout + +Source: reference/file-layout.md +URL: /reference/file-layout + +# File Layout + +Tolaria is not opinionated about folder structure. It finds notes recursively across the whole vault, stores new notes in the root by default, and uses types and relationships for real organization. + +```txt +my-vault/ + project-alpha.md + weekly-review.md + research/ + source-notes.md + attachments/ + diagram.png + source.pdf + project.md + person.md + views/ + active-projects.yml +``` + +## Root Notes + +Tolaria works well with a flat vault. Folders are optional and can be useful for compatibility with other tools, but they are not required for people, projects, topics, or any other note category. + +Type is not inferred from folder location. It comes from frontmatter, and relationships are expressed with wikilinks in fields. That is what Tolaria uses for the sidebar, Properties panel, search, custom views, and neighborhood navigation. + +## Special Folders + +| Folder | Purpose | +| --- | --- | +| `views/` | Saved custom views. | +| `attachments/` | Images and other attached files. | + +PDFs, images, and other non-Markdown files stay as normal files. Folder browsing can show them in place, and Settings controls whether PDFs, images, and unsupported files appear in All Notes. + +Whiteboards are Markdown files with durable tldraw data, so they belong with notes rather than in `attachments/`. + +Spreadsheets are also Markdown files. A note with `_display: sheet` stores ordinary frontmatter plus a CSV-like body and opens in the sheet editor. + +Type definitions are Markdown notes with `type: Type` in frontmatter. New type documents are normal notes, and existing type documents in older folders still work. + +## Git Files + +If the vault is a Git repository, `.git/` belongs to Git. Tolaria reads Git state but does not treat `.git/` as notes. + +--- + +# Frontmatter Fields + +Source: reference/frontmatter-fields.md +URL: /reference/frontmatter-fields + +# Frontmatter Fields + +Tolaria uses conventions instead of a required schema. + +| Field | Meaning | +| --- | --- | +| `type` | The note's entity type. | +| `status` | Lifecycle state. | +| `icon` | Per-note icon. | +| `url` | External URL. | +| `date` | Single date. | +| `belongs_to` | Parent relationship. | +| `related_to` | Lateral relationship. | +| `has` | Contained relationship. | +| `_width` | Per-note editor width override. | +| `_display` | Display mode. Omit for text notes; use `sheet` for spreadsheet notes. | +| `_icon`, `_color` | Type or note appearance metadata. | +| `_sidebar_label`, `_order` | Type sidebar label and order. | +| `_pinned_properties` | Properties pinned for a type. | +| `_sheet` | Sheet-note presentation metadata such as grid settings, column widths, row heights, and cell formatting. | + +## Custom Fields + +You can add your own fields. If a field contains wikilinks, Tolaria can treat it as a relationship. + +## System Fields + +Fields starting with `_` are reserved for system behavior and hidden from standard property editing. They remain plain YAML, so they can still be inspected or changed in raw mode when needed. + +Nested keys under a system field are also system-owned. For example, `_sheet.cells.B6.num_fmt` belongs to the sheet editor and should not appear as a normal user property. + +--- + +# Keyboard Shortcuts + +Source: reference/keyboard-shortcuts.md +URL: /reference/keyboard-shortcuts + +# Keyboard Shortcuts + +| Shortcut | Action | +| --- | --- | +| `Cmd+K` / `Ctrl+K` | Open command palette. | +| `Cmd+P` / `Ctrl+P` | Quick open notes and files. | +| `Cmd+N` / `Ctrl+N` | Create a new note. | +| `Cmd+S` / `Ctrl+S` | Save current note. | +| `Cmd+F` / `Ctrl+F` | Find in the current note. | +| `Cmd+Shift+F` / `Ctrl+Shift+F` | Search the vault. | +| `Cmd+Shift+V` / `Ctrl+Shift+V` | Paste without formatting. | +| `Cmd+\` / `Ctrl+\` | Toggle raw Markdown mode. | +| `Cmd+Shift+T` / `Ctrl+Shift+T` | Toggle table of contents. | +| `Cmd+Shift+I` / `Ctrl+Shift+I` | Toggle Properties panel. | +| `Cmd+Shift+L` / `Ctrl+Shift+L` | Toggle AI panel. | +| `Cmd+[` / `Alt+Left` | Navigate back when available. | +| `Cmd+]` / `Alt+Right` | Navigate forward when available. | +| `Cmd+Shift+O` / `Ctrl+Shift+O` | Open current note in a new window. | +| `Cmd+D` / `Ctrl+D` | Toggle favorite for the current note. | +| `Cmd+E` / `Ctrl+E` | Mark the current Inbox note organized. | + +Some shortcuts vary by platform because macOS, Linux, and Windows reserve different key combinations. + +Use the command palette to discover the current command set. + +--- + +# Release Channels + +Source: reference/release-channels.md +URL: /reference/release-channels + +# Release Channels + +Tolaria publishes Stable and Alpha release metadata to GitHub Pages. + +## Stable + +Stable follows manually promoted releases. This is the right channel for normal use. + +The stable updater metadata lives at: + +```txt +/stable/latest.json +``` + +The public download page points at the latest stable release. + +## Alpha + +Alpha follows pushes to `main`. It receives fixes and features earlier, but it can be rougher than Stable. + +The alpha updater metadata lives at: + +```txt +/alpha/latest.json +``` + +Compatibility endpoints also point to the alpha metadata: + +```txt +/latest.json +/latest-canary.json +``` + +## Before Switching + +Commit or push important vault changes before changing release channel or installing an update. Your notes are local files, but a clean Git state makes recovery simpler. + +--- + +# Spreadsheet File Format + +Source: reference/spreadsheet-format.md +URL: /reference/spreadsheet-format + +# Spreadsheet File Format + +Sheet notes are Markdown files with YAML frontmatter and a CSV-like body. The note uses `_display: sheet` when it should open in the spreadsheet editor. The `type` field remains ordinary semantic metadata. + +For editing workflows, see [Use Spreadsheets](/guides/use-spreadsheets). For formula syntax and function references, see [Spreadsheet Formulas](/reference/spreadsheet-functions). + +## Structure + +```md +--- +type: Project +_display: sheet +tags: + - planning +_sheet: + show_grid_lines: true + frozen_rows: 1 + frozen_columns: 1 + columns: + A: + width: 180 + rows: + "1": + height: 32 + cells: + E6: + num_fmt: "0.00%" + bold: true +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Expenses,650,700,760,=SUM(B3:D3) +Net,=B2-B3,=C2-C3,=D2-D3,=SUM(B4:D4) +Growth,,=(C4-B4)/B4,=(D4-C4)/C4,=(E4-B4)/B4 +``` + +The frontmatter stores note metadata. The body stores rows and cells. There is no Markdown table wrapper, fenced code block, or embedded workbook blob. + +## Frontmatter + +All ordinary Tolaria fields remain available: + +- `type` +- `status` +- `date` +- `tags` +- `url` +- relationship fields such as `belongs_to`, `related_to`, and custom wikilink properties + +The `_display: sheet` field is the display-as marker. Omit it for ordinary text notes. + +The `_sheet` key is reserved for spreadsheet presentation metadata. It follows the same system-field convention as other underscore-prefixed Tolaria fields: hidden from normal property editing, but visible and editable in raw source. + +## Body + +The body is CSV-like text: + +- rows are separated by line breaks +- cells are separated by commas +- cells containing commas, quotes, or line breaks are quoted +- quotes inside quoted cells are escaped by doubling them +- empty trailing rows and columns may be omitted on save + +Any cell whose input starts with `=` is treated as a formula. Other cells are treated as literal values. + +## `_sheet` Metadata + +Tolaria stores spreadsheet presentation state in `_sheet` as plain YAML. + +| Field | Meaning | +| --- | --- | +| `show_grid_lines` | Whether grid lines are shown. | +| `frozen_rows` | Number of frozen rows from the top. | +| `frozen_columns` | Number of frozen columns from the left. | +| `columns..width` | Custom column width, keyed by column letter such as `A` or `BC`. | +| `rows."".height` | Custom row height, keyed by one-based row number. | +| `cells..num_fmt` | Number format code for a cell. | +| `cells..bold` | Bold text style. | +| `cells..italic` | Italic text style. | +| `cells..underline` | Underline text style. | +| `cells..strike` | Strikethrough text style. | +| `cells..font_size` | Font size. | +| `cells..font_color` | Text color. | +| `cells..fill_color` | Cell fill color. | +| `cells..horizontal_align` | Horizontal alignment. | +| `cells..vertical_align` | Vertical alignment. | +| `cells..wrap_text` | Text wrapping. | +| `cells..border_top` | Top border style. | +| `cells..border_right` | Right border style. | +| `cells..border_bottom` | Bottom border style. | +| `cells..border_left` | Left border style. | + +Cell metadata is keyed by A1-style cell addresses such as `A1`, `B12`, or `AA30`. + +Border values are stored as a style name with an optional color, for example: + +```yaml +border_bottom: "thin #d0d7de" +``` + +## Number Formats + +Number formats are stored in `num_fmt` using spreadsheet-style format codes. Common examples: + +| Format | Example output | +| --- | --- | +| `#,##0` | `1,250` | +| `#,##0.00` | `1,250.50` | +| `0.00%` | `12.35%` | +| `$#,##0.00` | `$1,250.50` | +| `yyyy-mm-dd` | `2026-06-15` | + +These formats affect presentation, not the underlying cell input in the CSV body. + +## Markdown Style Import + +When Tolaria imports a non-formula CSV cell, simple Markdown wrappers can seed initial styles: + +| Cell text | Stored value | Style | +| --- | --- | --- | +| `**Revenue**` | `Revenue` | bold | +| `_Estimate_` | `Estimate` | italic | +| `***Total***` | `Total` | bold and italic | +| `~~Removed~~` | `Removed` | strike | + +After save, the style belongs in `_sheet` metadata and the body keeps the unwrapped text. + +## Wikilinks + +Non-formula cells can store normal Tolaria wikilinks: + +```csv +Account,Source +Newsletter,[[newsletter-revenue]] +Sponsors,[[sponsorship-pipeline]] +``` + +Formula cells can reference another sheet note with Tolaria's cross-sheet syntax: + +```txt +=[[newsletter-revenue]].B5 +=ROUND([[business-plan]].$E$12, 2) +=[[device]].power.watts +``` + +Cross-sheet cell references resolve another sheet note by wikilink target, then read a single A1-style cell. Frontmatter references resolve one note by wikilink target, then read a scalar property path after the dot. Missing, ambiguous, circular, very deep, or non-scalar references are treated as unresolved and surface as spreadsheet errors. + +## Guidance For Agents And Scripts + +When editing a sheet note programmatically: + +- preserve the YAML frontmatter delimiter and ordinary Tolaria fields +- keep `_display: sheet` when the file should display as a spreadsheet +- keep spreadsheet presentation state under `_sheet` +- parse and serialize the body as CSV, not by splitting on every comma manually +- preserve formulas as formulas, including `[[sheet]].A1` and `[[note]].property.path` references +- avoid converting formulas to their displayed values +- quote CSV cells when they contain commas, quotes, or line breaks +- do not add workbook tabs inside one note; create another note with `_display: sheet` instead +- do not store opaque binary workbook state in the Markdown file + +If a script cannot safely preserve `_sheet`, it should leave that block untouched and edit only the CSV body cells it understands. + +--- + +# Spreadsheet Formulas + +Source: reference/spreadsheet-functions.md +URL: /reference/spreadsheet-functions + +# Spreadsheet Formulas + +Formula cells start with `=` and are evaluated by IronCalc through Tolaria's sheet editor. + +Tolaria adds vault-aware sheet references on top of the normal spreadsheet formula model. Everything else should be treated as IronCalc formula behavior. IronCalc aims for Excel-compatible formulas, but the upstream project is still evolving, so verify advanced formulas against the IronCalc docs when precision matters. + +## Basic Syntax + +| Syntax | Meaning | +| --- | --- | +| `=B2+B3-B4` | Arithmetic over cells. | +| `=SUM(B2:D2)` | Function call over a range. | +| `=ROUND(E6, 2)` | Function call with arguments. | +| `=IF(E6>0, "Up", "Down")` | Conditional expression. | +| `=$B$2` | Absolute column and row reference. | +| `=B$2` | Relative column, absolute row. | +| `=$B2` | Absolute column, relative row. | +| `=B2:D10` | A range in the current sheet note. | +| `="Q" & 1` | Text concatenation. | + +Use parentheses when a model depends on precedence: + +```txt +=(B2+B3-B4)/B5 +``` + +## Tolaria Note References + +Tolaria supports wikilink cell references for values that live in another sheet note: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=ROUND([[business-plan]].$E$12, 2) +``` + +The target inside `[[...]]` resolves like a normal Tolaria wikilink. The cell address after the dot uses A1 notation. + +Absolute markers follow spreadsheet copy behavior: + +| Reference | Copy behavior | +| --- | --- | +| `[[revenue]].B5` | row and column can shift | +| `[[revenue]].$B$5` | row and column stay fixed | +| `[[revenue]].B$5` | row fixed, column can shift | +| `[[revenue]].$B5` | column fixed, row can shift | + +Cross-sheet cell references currently resolve single cells. Keep range formulas inside one sheet note until cross-note ranges are explicitly supported. + +Formulas can also read scalar frontmatter properties from a specific note: + +```txt +=[[device.md]].power.watts +=[[project-alpha]].status +=[[book-notes/the-design-of-everyday-things.md]].rating +``` + +The target resolves like a wikilink, and the dot path reads nested frontmatter keys. Numbers, booleans, and strings become formula literals. Missing notes, ambiguous note targets, missing properties, arrays, maps, and other non-scalar values resolve to `#N/A`. + +A first segment that looks like an A1 cell address, such as `B2`, is treated as a cross-sheet cell reference. Use property names that do not collide with A1 notation for frontmatter formulas. + +## Autocomplete Functions + +Tolaria's formula autocomplete exposes the implemented function catalog from the bundled IronCalc engine. The current catalog has 195 functions. + +The dropdown shows a small ranked set of matches while you type. Keep typing to narrow the result list. Function names with digits and dots, such as `BIN2DEC` and `ERFC.PRECISE`, are supported. + +### Logical + +`AND`, `FALSE`, `IF`, `IFERROR`, `IFNA`, `IFS`, `NOT`, `OR`, `SWITCH`, `TRUE`, `XOR` + +### Math and trigonometry + +`ABS`, `ACOS`, `ACOSH`, `ASIN`, `ASINH`, `ATAN`, `ATAN2`, `ATANH`, `COS`, `COSH`, `PI`, `POWER`, `PRODUCT`, `RAND`, `RANDBETWEEN`, `ROUND`, `ROUNDDOWN`, `ROUNDUP`, `SIN`, `SINH`, `SQRT`, `SQRTPI`, `SUM`, `SUMIF`, `SUMIFS`, `TAN`, `TANH`, `SUBTOTAL` + +### Lookup and reference + +`CHOOSE`, `COLUMN`, `COLUMNS`, `HLOOKUP`, `INDEX`, `INDIRECT`, `LOOKUP`, `MATCH`, `OFFSET`, `ROW`, `ROWS`, `VLOOKUP`, `XLOOKUP` + +### Text + +`CONCAT`, `CONCATENATE`, `EXACT`, `FIND`, `LEFT`, `LEN`, `LOWER`, `MID`, `REPT`, `RIGHT`, `SEARCH`, `SUBSTITUTE`, `T`, `TEXT`, `TEXTAFTER`, `TEXTBEFORE`, `TEXTJOIN`, `TRIM`, `UNICODE`, `UPPER`, `VALUE`, `VALUETOTEXT` + +### Information + +`ERROR.TYPE`, `FORMULATEXT`, `ISBLANK`, `ISERR`, `ISERROR`, `ISEVEN`, `ISFORMULA`, `ISLOGICAL`, `ISNA`, `ISNONTEXT`, `ISNUMBER`, `ISODD`, `ISREF`, `ISTEXT`, `NA`, `SHEET`, `TYPE` + +### Statistical + +`AVERAGE`, `AVERAGEA`, `AVERAGEIF`, `AVERAGEIFS`, `COUNT`, `COUNTA`, `COUNTBLANK`, `COUNTIF`, `COUNTIFS`, `GEOMEAN`, `MAX`, `MAXIFS`, `MIN`, `MINIFS` + +### Date and time + +`DATE`, `DAY`, `EDATE`, `EOMONTH`, `MONTH`, `NOW`, `TODAY`, `YEAR` + +### Financial + +`CUMIPMT`, `CUMPRINC`, `DB`, `DDB`, `DOLLARDE`, `DOLLARFR`, `EFFECT`, `FV`, `IPMT`, `IRR`, `ISPMT`, `MIRR`, `NOMINAL`, `NPER`, `NPV`, `PDURATION`, `PMT`, `PPMT`, `PV`, `RATE`, `RRI`, `SLN`, `SYD`, `TBILLEQ`, `TBILLPRICE`, `TBILLYIELD`, `XIRR`, `XNPV` + +### Engineering + +`BESSELI`, `BESSELJ`, `BESSELK`, `BESSELY`, `BIN2DEC`, `BIN2HEX`, `BIN2OCT`, `BITAND`, `BITLSHIFT`, `BITOR`, `BITRSHIFT`, `BITXOR`, `COMPLEX`, `CONVERT`, `DEC2BIN`, `DEC2HEX`, `DEC2OCT`, `DELTA`, `ERF`, `ERF.PRECISE`, `ERFC`, `ERFC.PRECISE`, `GESTEP`, `HEX2BIN`, `HEX2DEC`, `HEX2OCT`, `IMABS`, `IMAGINARY`, `IMARGUMENT`, `IMCONJUGATE`, `IMCOS`, `IMCOSH`, `IMCOT`, `IMCSC`, `IMCSCH`, `IMDIV`, `IMEXP`, `IMLN`, `IMLOG10`, `IMLOG2`, `IMPOWER`, `IMPRODUCT`, `IMREAL`, `IMSEC`, `IMSECH`, `IMSIN`, `IMSINH`, `IMSQRT`, `IMSUB`, `IMSUM`, `IMTAN`, `OCT2BIN`, `OCT2DEC`, `OCT2HEX` + +## Examples + +### Totals + +```txt +=SUM(B2:D2) +=B2+B3-B4 +=SUM(B2:D2)-SUM(B4:D4) +``` + +### Growth And Percentages + +```txt +=(C5-B5)/B5 +=IF(B5=0, 0, (C5-B5)/B5) +=ROUND((C5-B5)/B5, 4) +``` + +Format the result as a percentage with a cell `num_fmt` such as `0.00%`. + +### Conditional Logic + +```txt +=IF(E5>10000, "On track", "Review") +=IFS(E5>10000, "High", E5>5000, "Medium", TRUE, "Low") +=IFERROR(B5/B4, 0) +``` + +### Dates + +```txt +=TODAY() +=DATE(2026, 6, 15) +=YEAR(TODAY()) +=MONTH(TODAY()) +``` + +### Text + +```txt +=CONCAT(A2, " - ", B2) +=UPPER(A2) +=TRIM(A2) +=TEXT(B2, "$#,##0.00") +``` + +### Lookup + +```txt +=INDEX(B2:E10, 3, 2) +=MATCH("Revenue", A2:A20, 0) +=VLOOKUP("Revenue", A2:E20, 5, FALSE) +=XLOOKUP("Revenue", A2:A20, E2:E20) +``` + +### Cross-Sheet Model + +```txt +=[[newsletter-revenue]].E5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=IF([[business-plan]].$E$12>0, [[business-plan]].$E$12, 0) +``` + +## IronCalc Function Families + +IronCalc documents formulas by category. Use these upstream pages for detailed syntax and examples. The upstream documentation may include newer functions that are not yet present in Tolaria's bundled IronCalc version. + +| Family | Link | +| --- | --- | +| Lookup and reference | [IronCalc lookup and reference](https://docs.ironcalc.com/functions/lookup-and-reference.html) | +| Financial | [IronCalc financial](https://docs.ironcalc.com/functions/financial.html) | +| Engineering | [IronCalc engineering](https://docs.ironcalc.com/functions/engineering.html) | +| Database | [IronCalc database](https://docs.ironcalc.com/functions/database.html) | +| Statistical | [IronCalc statistical](https://docs.ironcalc.com/functions/statistical.html) | +| Text | [IronCalc text](https://docs.ironcalc.com/functions/text.html) | +| Math and trigonometry | [IronCalc math and trigonometry](https://docs.ironcalc.com/functions/math-and-trigonometry.html) | +| Logical | [IronCalc logical](https://docs.ironcalc.com/functions/logical.html) | +| Date and time | [IronCalc date and time](https://docs.ironcalc.com/functions/date-and-time.html) | +| Information | [IronCalc information](https://docs.ironcalc.com/functions/information.html) | + +IronCalc also documents [value types](https://docs.ironcalc.com/features/value-types.html), [error types](https://docs.ironcalc.com/features/error-types.html), [optional arguments](https://docs.ironcalc.com/features/optional-arguments.html), and [formatting values](https://docs.ironcalc.com/features/formatting-values.html). + +For current upstream gaps, see IronCalc's [unsupported features](https://docs.ironcalc.com/features/unsupported-features.html). + +--- + +# Supported Platforms + +Source: reference/supported-platforms.md +URL: /reference/supported-platforms + +# Supported Platforms + +Tolaria is a desktop app built with Tauri. Releases currently target macOS, Windows, and Linux. + +| Platform | Current support | Notes | +| --- | --- | --- | +| macOS | Primary | Main development and QA target. Apple Silicon and Intel artifacts are published. | +| Windows | Supported, early | NSIS installers and signed updater bundles are published. Menu, shell-path, and credential-helper behavior receive platform-specific fixes as they appear. | +| Linux | Supported, early | AppImage, deb, and RPM artifacts are published. Behavior can depend on distro WebKitGTK packages, Wayland/X11 details, and input-method setup. | + +## Support Policy + +Primary support means the platform is part of normal development and release validation. Supported, early means release artifacts exist and the app is expected to work, but platform-specific bugs can take longer to diagnose than macOS issues. + +## Reporting Platform Bugs + +Include: + +- Tolaria version. +- Operating system and version. +- CPU architecture. +- Whether the vault is local-only or connected to a remote. +- Steps to reproduce. + +--- + +# View Filters + +Source: reference/view-filters.md +URL: /reference/view-filters + +# View Filters + +View filters define saved lists of notes. + +## Common Filter Ideas + +| Goal | Filter direction | +| --- | --- | +| Active projects | `type` is Project and `status` is Active | +| Drafts | `type` is Article and `status` is Draft | +| People follow-up | `type` is Person and date is before today | +| Recent work | modified date is within a recent range | + +## Sorting + +Useful sorts include: + +- Recently modified first. +- Title ascending. +- Status ascending. +- A custom property ascending or descending. + +## Operators + +Saved views can combine filters for text, dates, relationship fields, and frontmatter values. Relative date expressions are useful for views such as notes changed this week or people that need follow-up. + +Regex filters are available for power-user cases. Keep them narrow and test them on a small view first. + +## Keep Views Focused + +A view should answer one recurring question. If it becomes too broad, split it into two views. + +You can also customize view appearance with the same kind of icon and color controls used by types. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/relationships.md b/src-tauri/gen/apple/assets/agent-docs/relationships.md new file mode 100644 index 0000000..309a0b0 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/relationships.md @@ -0,0 +1,37 @@ +# Relationships + +Source: concepts/relationships.md +URL: /concepts/relationships + +# Relationships + +Relationships make a vault feel like a graph instead of a pile of documents. + +## Relationship Fields + +Any frontmatter field containing wikilinks can become a relationship. Relationship fields can point to one note or to an array of notes. + +```yaml +belongs_to: + - "[[product-work]]" +related_to: + - "[[documentation]]" + - "[[editor-research]]" +blocked_by: + - "[[release-process]]" + - "[[sync-conflicts]]" +``` + +Tolaria supports default relationship fields out of the box: `belongs_to`, `has`, and `related_to`. It also detects custom relationship fields dynamically when they contain wikilinks. + +Default relationships have automatically computed inverses. If a note says it `belongs_to` a project, the project can show that note under its inverse `has` relationship without you writing the reverse link by hand. `related_to` works as a lateral relationship in both directions. + +These outgoing and inverse relationships appear in the Properties panel and in Neighborhood mode, where the note list becomes a graph view around the selected note. + +## Body Links Versus Relationship Fields + +Use body links when the relationship appears naturally in writing. Use frontmatter relationships when the connection is important enough to show in navigation, filters, Neighborhood mode, or the Properties panel. + +## Backlinks + +Tolaria can show incoming links and inverse relationships, making it easier to navigate from a note to the rest of its context. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/release-channels.md b/src-tauri/gen/apple/assets/agent-docs/release-channels.md new file mode 100644 index 0000000..1ae915d --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/release-channels.md @@ -0,0 +1,41 @@ +# Release Channels + +Source: reference/release-channels.md +URL: /reference/release-channels + +# Release Channels + +Tolaria publishes Stable and Alpha release metadata to GitHub Pages. + +## Stable + +Stable follows manually promoted releases. This is the right channel for normal use. + +The stable updater metadata lives at: + +```txt +/stable/latest.json +``` + +The public download page points at the latest stable release. + +## Alpha + +Alpha follows pushes to `main`. It receives fixes and features earlier, but it can be rougher than Stable. + +The alpha updater metadata lives at: + +```txt +/alpha/latest.json +``` + +Compatibility endpoints also point to the alpha metadata: + +```txt +/latest.json +/latest-canary.json +``` + +## Before Switching + +Commit or push important vault changes before changing release channel or installing an update. Your notes are local files, but a clean Git state makes recovery simpler. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/search-index.json b/src-tauri/gen/apple/assets/agent-docs/search-index.json new file mode 100644 index 0000000..9cd4598 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/search-index.json @@ -0,0 +1,560 @@ +[ + { + "title": "Index", + "path": "pages/index.md", + "url": "/", + "section": "home", + "headings": [] + }, + { + "title": "First Launch", + "path": "pages/start/first-launch.md", + "url": "/start/first-launch", + "section": "start", + "headings": [ + "What You Choose", + "What Tolaria Creates", + "First Commands To Try", + "AI Setup Prompt" + ] + }, + { + "title": "Getting Started Vault", + "path": "pages/start/getting-started-vault.md", + "url": "/start/getting-started-vault", + "section": "start", + "headings": [ + "What It Demonstrates", + "Local-Only By Default", + "Use It Alongside Your Own Vaults", + "When To Move On" + ] + }, + { + "title": "Install Tolaria", + "path": "pages/start/install.md", + "url": "/start/install", + "section": "start", + "headings": [ + "Download", + "Homebrew", + "Platform Status", + "Managed Windows Devices", + "After Installing" + ] + }, + { + "title": "Open Or Create A Vault", + "path": "pages/start/open-or-create-vault.md", + "url": "/start/open-or-create-vault", + "section": "start", + "headings": [ + "Open An Existing Folder", + "Create A New Vault", + "Use More Than One Vault", + "Git Is Recommended, Not Required" + ] + }, + { + "title": "AI", + "path": "pages/concepts/ai.md", + "url": "/concepts/ai", + "section": "concepts", + "headings": [ + "Coding Agents", + "Direct Models", + "External MCP Setup", + "Why Git Matters For AI" + ] + }, + { + "title": "Editor", + "path": "pages/concepts/editor.md", + "url": "/concepts/editor", + "section": "concepts", + "headings": [ + "Rich Editing", + "Raw Mode", + "Table Of Contents", + "Width" + ] + }, + { + "title": "Files And Media", + "path": "pages/concepts/files-and-media.md", + "url": "/concepts/files-and-media", + "section": "concepts", + "headings": [ + "Mermaid Diagrams", + "Attachments", + "Previews", + "Whiteboards", + "Git Boundary" + ] + }, + { + "title": "Git", + "path": "pages/concepts/git.md", + "url": "/concepts/git", + "section": "concepts", + "headings": [ + "What Tolaria Uses Git For", + "History And Diffs", + "Local Commits", + "Remotes" + ] + }, + { + "title": "Inbox", + "path": "pages/concepts/inbox.md", + "url": "/concepts/inbox", + "section": "concepts", + "headings": [ + "Why It Exists", + "Organizing Inbox Notes", + "Healthy Inbox Habit" + ] + }, + { + "title": "Notes", + "path": "pages/concepts/notes.md", + "url": "/concepts/notes", + "section": "concepts", + "headings": [ + "Anatomy", + "Titles", + "Body Links", + "Frontmatter" + ] + }, + { + "title": "Properties", + "path": "pages/concepts/properties.md", + "url": "/concepts/properties", + "section": "concepts", + "headings": [ + "Suggested Properties", + "System Properties", + "Property Editing" + ] + }, + { + "title": "Relationships", + "path": "pages/concepts/relationships.md", + "url": "/concepts/relationships", + "section": "concepts", + "headings": [ + "Relationship Fields", + "Body Links Versus Relationship Fields", + "Backlinks" + ] + }, + { + "title": "Spreadsheets", + "path": "pages/concepts/spreadsheets.md", + "url": "/concepts/spreadsheets", + "section": "concepts", + "headings": [ + "Read Next", + "Why Sheet Notes", + "One Note, One Sheet", + "Editing", + "Wikilinks In Cells", + "Note Reference Formulas", + "Storage", + "Formulas" + ] + }, + { + "title": "Types", + "path": "pages/concepts/types.md", + "url": "/concepts/types", + "section": "concepts", + "headings": [ + "Type Field", + "Prefer Types Over Folders", + "Type Documents", + "What Types Control", + "New Note Defaults" + ] + }, + { + "title": "Vaults", + "path": "pages/concepts/vaults.md", + "url": "/concepts/vaults", + "section": "concepts", + "headings": [ + "Core Rules", + "Why Local Files Matter", + "Git Is A Capability", + "Multiple Vaults At The Same Time", + "App State Versus Vault State" + ] + }, + { + "title": "Build Custom Views", + "path": "pages/guides/build-custom-views.md", + "url": "/guides/build-custom-views", + "section": "guides", + "headings": [ + "Good View Candidates", + "View Definition", + "Filters", + "Design The Question First" + ] + }, + { + "title": "Capture A Note", + "path": "pages/guides/capture-a-note.md", + "url": "/guides/capture-a-note", + "section": "guides", + "headings": [ + "Steps", + "Capture Well", + "When To Add Structure Immediately" + ] + }, + { + "title": "Manage Git Manually Or With AutoGit", + "path": "pages/guides/commit-and-push.md", + "url": "/guides/commit-and-push", + "section": "guides", + "headings": [ + "Manual Git", + "AutoGit", + "Use Small Commits" + ] + }, + { + "title": "Configure AI Models", + "path": "pages/guides/configure-ai-models.md", + "url": "/guides/configure-ai-models", + "section": "guides", + "headings": [ + "Local Models", + "API Models", + "Test The Connection", + "Select The Target" + ] + }, + { + "title": "Connect A Git Remote", + "path": "pages/guides/connect-a-git-remote.md", + "url": "/guides/connect-a-git-remote", + "section": "guides", + "headings": [ + "Before You Start", + "Steps", + "Recommended Auth" + ] + }, + { + "title": "Create Types", + "path": "pages/guides/create-types.md", + "url": "/guides/create-types", + "section": "guides", + "headings": [ + "Steps", + "Use Types Sparingly", + "Templates" + ] + }, + { + "title": "Manage Display Preferences", + "path": "pages/guides/manage-display-preferences.md", + "url": "/guides/manage-display-preferences", + "section": "guides", + "headings": [ + "Theme", + "Note Width", + "Sidebar Labels", + "Vault Content" + ] + }, + { + "title": "Organize The Inbox", + "path": "pages/guides/organize-inbox.md", + "url": "/guides/organize-inbox", + "section": "guides", + "headings": [ + "Remove A Note From Inbox", + "Review Checklist", + "Make Notes Navigable", + "Avoid Over-Structuring" + ] + }, + { + "title": "Use The AI", + "path": "pages/guides/use-ai-panel.md", + "url": "/guides/use-ai-panel", + "section": "guides", + "headings": [ + "Choose How To Prompt", + "Choose A Target", + "Permission Mode", + "Good Requests", + "Review Changes" + ] + }, + { + "title": "Use The Command Palette", + "path": "pages/guides/use-command-palette.md", + "url": "/guides/use-command-palette", + "section": "guides", + "headings": [ + "Common Commands", + "Keyboard-First Workflow" + ] + }, + { + "title": "Use Media Previews", + "path": "pages/guides/use-media-previews.md", + "url": "/guides/use-media-previews", + "section": "guides", + "headings": [ + "Open A File", + "All Notes Visibility", + "Attachments", + "Troubleshooting" + ] + }, + { + "title": "Use Spreadsheets", + "path": "pages/guides/use-spreadsheets.md", + "url": "/guides/use-spreadsheets", + "section": "guides", + "headings": [ + "Create A Sheet", + "Enter Values", + "Enter Formulas", + "Select And Edit Ranges", + "Format Cells", + "Add Wikilinks", + "Reference Another Note", + "Work With The Raw File" + ] + }, + { + "title": "Use The Table Of Contents", + "path": "pages/guides/use-table-of-contents.md", + "url": "/guides/use-table-of-contents", + "section": "guides", + "headings": [ + "Open It", + "How It Works", + "Good Uses" + ] + }, + { + "title": "Use Wikilinks", + "path": "pages/guides/use-wikilinks.md", + "url": "/guides/use-wikilinks", + "section": "guides", + "headings": [ + "Link From The Body", + "Link From Frontmatter", + "Keep Links Stable" + ] + }, + { + "title": "Portent", + "path": "pages/templates/portent.md", + "url": "/templates/portent", + "section": "templates", + "headings": [ + "Core Questions", + "Types", + "Relationships", + "Lifecycle", + "Why Use It", + "Start From The Template", + "Learn More" + ] + }, + { + "title": "Contribute", + "path": "pages/reference/contribute.md", + "url": "/reference/contribute", + "section": "reference", + "headings": [ + "Newsletter", + "Sponsors", + "Feature Requests", + "Discussions", + "Contribute Code", + "Report A Bug" + ] + }, + { + "title": "Docs Maintenance", + "path": "pages/reference/docs-maintenance.md", + "url": "/reference/docs-maintenance", + "section": "reference", + "headings": [ + "Update Docs When You Change", + "Suggested Workflow", + "Page Types", + "Review Checklist" + ] + }, + { + "title": "File Layout", + "path": "pages/reference/file-layout.md", + "url": "/reference/file-layout", + "section": "reference", + "headings": [ + "Root Notes", + "Special Folders", + "Git Files" + ] + }, + { + "title": "Frontmatter Fields", + "path": "pages/reference/frontmatter-fields.md", + "url": "/reference/frontmatter-fields", + "section": "reference", + "headings": [ + "Custom Fields", + "System Fields" + ] + }, + { + "title": "Keyboard Shortcuts", + "path": "pages/reference/keyboard-shortcuts.md", + "url": "/reference/keyboard-shortcuts", + "section": "reference", + "headings": [] + }, + { + "title": "Release Channels", + "path": "pages/reference/release-channels.md", + "url": "/reference/release-channels", + "section": "reference", + "headings": [ + "Stable", + "Alpha", + "Before Switching" + ] + }, + { + "title": "Spreadsheet File Format", + "path": "pages/reference/spreadsheet-format.md", + "url": "/reference/spreadsheet-format", + "section": "reference", + "headings": [ + "Structure", + "Frontmatter", + "Body", + "`_sheet` Metadata", + "Number Formats", + "Markdown Style Import", + "Wikilinks", + "Guidance For Agents And Scripts" + ] + }, + { + "title": "Spreadsheet Formulas", + "path": "pages/reference/spreadsheet-functions.md", + "url": "/reference/spreadsheet-functions", + "section": "reference", + "headings": [ + "Basic Syntax", + "Tolaria Note References", + "Autocomplete Functions", + "Logical", + "Math and trigonometry", + "Lookup and reference", + "Text", + "Information", + "Statistical", + "Date and time", + "Financial", + "Engineering", + "Examples", + "Totals", + "Growth And Percentages", + "Conditional Logic", + "Dates", + "Text", + "Lookup", + "Cross-Sheet Model", + "IronCalc Function Families" + ] + }, + { + "title": "Supported Platforms", + "path": "pages/reference/supported-platforms.md", + "url": "/reference/supported-platforms", + "section": "reference", + "headings": [ + "Support Policy", + "Reporting Platform Bugs" + ] + }, + { + "title": "View Filters", + "path": "pages/reference/view-filters.md", + "url": "/reference/view-filters", + "section": "reference", + "headings": [ + "Common Filter Ideas", + "Sorting", + "Operators", + "Keep Views Focused" + ] + }, + { + "title": "AI Agent Not Found", + "path": "pages/troubleshooting/ai-agent-not-found.md", + "url": "/troubleshooting/ai-agent-not-found", + "section": "troubleshooting", + "headings": [ + "Symptoms", + "Checks", + "Path Issues" + ] + }, + { + "title": "Git Authentication", + "path": "pages/troubleshooting/git-auth.md", + "url": "/troubleshooting/git-auth", + "section": "troubleshooting", + "headings": [ + "Symptoms", + "Checks", + "Common Fixes" + ] + }, + { + "title": "Model Provider Connection", + "path": "pages/troubleshooting/model-provider-connection.md", + "url": "/troubleshooting/model-provider-connection", + "section": "troubleshooting", + "headings": [ + "Local Providers", + "API Providers", + "Chat Mode Boundary" + ] + }, + { + "title": "Sync Conflicts", + "path": "pages/troubleshooting/sync-conflicts.md", + "url": "/troubleshooting/sync-conflicts", + "section": "troubleshooting", + "headings": [ + "What To Do", + "Prevent Conflicts" + ] + }, + { + "title": "Vault Not Loading", + "path": "pages/troubleshooting/vault-not-loading.md", + "url": "/troubleshooting/vault-not-loading", + "section": "troubleshooting", + "headings": [ + "Check The Folder", + "Check Git", + "Reload" + ] + } +] diff --git a/src-tauri/gen/apple/assets/agent-docs/spreadsheet-format.md b/src-tauri/gen/apple/assets/agent-docs/spreadsheet-format.md new file mode 100644 index 0000000..8b61593 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/spreadsheet-format.md @@ -0,0 +1,167 @@ +# Spreadsheet File Format + +Source: reference/spreadsheet-format.md +URL: /reference/spreadsheet-format + +# Spreadsheet File Format + +Sheet notes are Markdown files with YAML frontmatter and a CSV-like body. The note uses `_display: sheet` when it should open in the spreadsheet editor. The `type` field remains ordinary semantic metadata. + +For editing workflows, see [Use Spreadsheets](/guides/use-spreadsheets). For formula syntax and function references, see [Spreadsheet Formulas](/reference/spreadsheet-functions). + +## Structure + +```md +--- +type: Project +_display: sheet +tags: + - planning +_sheet: + show_grid_lines: true + frozen_rows: 1 + frozen_columns: 1 + columns: + A: + width: 180 + rows: + "1": + height: 32 + cells: + E6: + num_fmt: "0.00%" + bold: true +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Expenses,650,700,760,=SUM(B3:D3) +Net,=B2-B3,=C2-C3,=D2-D3,=SUM(B4:D4) +Growth,,=(C4-B4)/B4,=(D4-C4)/C4,=(E4-B4)/B4 +``` + +The frontmatter stores note metadata. The body stores rows and cells. There is no Markdown table wrapper, fenced code block, or embedded workbook blob. + +## Frontmatter + +All ordinary Tolaria fields remain available: + +- `type` +- `status` +- `date` +- `tags` +- `url` +- relationship fields such as `belongs_to`, `related_to`, and custom wikilink properties + +The `_display: sheet` field is the display-as marker. Omit it for ordinary text notes. + +The `_sheet` key is reserved for spreadsheet presentation metadata. It follows the same system-field convention as other underscore-prefixed Tolaria fields: hidden from normal property editing, but visible and editable in raw source. + +## Body + +The body is CSV-like text: + +- rows are separated by line breaks +- cells are separated by commas +- cells containing commas, quotes, or line breaks are quoted +- quotes inside quoted cells are escaped by doubling them +- empty trailing rows and columns may be omitted on save + +Any cell whose input starts with `=` is treated as a formula. Other cells are treated as literal values. + +## `_sheet` Metadata + +Tolaria stores spreadsheet presentation state in `_sheet` as plain YAML. + +| Field | Meaning | +| --- | --- | +| `show_grid_lines` | Whether grid lines are shown. | +| `frozen_rows` | Number of frozen rows from the top. | +| `frozen_columns` | Number of frozen columns from the left. | +| `columns..width` | Custom column width, keyed by column letter such as `A` or `BC`. | +| `rows."".height` | Custom row height, keyed by one-based row number. | +| `cells..num_fmt` | Number format code for a cell. | +| `cells..bold` | Bold text style. | +| `cells..italic` | Italic text style. | +| `cells..underline` | Underline text style. | +| `cells..strike` | Strikethrough text style. | +| `cells..font_size` | Font size. | +| `cells..font_color` | Text color. | +| `cells..fill_color` | Cell fill color. | +| `cells..horizontal_align` | Horizontal alignment. | +| `cells..vertical_align` | Vertical alignment. | +| `cells..wrap_text` | Text wrapping. | +| `cells..border_top` | Top border style. | +| `cells..border_right` | Right border style. | +| `cells..border_bottom` | Bottom border style. | +| `cells..border_left` | Left border style. | + +Cell metadata is keyed by A1-style cell addresses such as `A1`, `B12`, or `AA30`. + +Border values are stored as a style name with an optional color, for example: + +```yaml +border_bottom: "thin #d0d7de" +``` + +## Number Formats + +Number formats are stored in `num_fmt` using spreadsheet-style format codes. Common examples: + +| Format | Example output | +| --- | --- | +| `#,##0` | `1,250` | +| `#,##0.00` | `1,250.50` | +| `0.00%` | `12.35%` | +| `$#,##0.00` | `$1,250.50` | +| `yyyy-mm-dd` | `2026-06-15` | + +These formats affect presentation, not the underlying cell input in the CSV body. + +## Markdown Style Import + +When Tolaria imports a non-formula CSV cell, simple Markdown wrappers can seed initial styles: + +| Cell text | Stored value | Style | +| --- | --- | --- | +| `**Revenue**` | `Revenue` | bold | +| `_Estimate_` | `Estimate` | italic | +| `***Total***` | `Total` | bold and italic | +| `~~Removed~~` | `Removed` | strike | + +After save, the style belongs in `_sheet` metadata and the body keeps the unwrapped text. + +## Wikilinks + +Non-formula cells can store normal Tolaria wikilinks: + +```csv +Account,Source +Newsletter,[[newsletter-revenue]] +Sponsors,[[sponsorship-pipeline]] +``` + +Formula cells can reference another sheet note with Tolaria's cross-sheet syntax: + +```txt +=[[newsletter-revenue]].B5 +=ROUND([[business-plan]].$E$12, 2) +=[[device]].power.watts +``` + +Cross-sheet cell references resolve another sheet note by wikilink target, then read a single A1-style cell. Frontmatter references resolve one note by wikilink target, then read a scalar property path after the dot. Missing, ambiguous, circular, very deep, or non-scalar references are treated as unresolved and surface as spreadsheet errors. + +## Guidance For Agents And Scripts + +When editing a sheet note programmatically: + +- preserve the YAML frontmatter delimiter and ordinary Tolaria fields +- keep `_display: sheet` when the file should display as a spreadsheet +- keep spreadsheet presentation state under `_sheet` +- parse and serialize the body as CSV, not by splitting on every comma manually +- preserve formulas as formulas, including `[[sheet]].A1` and `[[note]].property.path` references +- avoid converting formulas to their displayed values +- quote CSV cells when they contain commas, quotes, or line breaks +- do not add workbook tabs inside one note; create another note with `_display: sheet` instead +- do not store opaque binary workbook state in the Markdown file + +If a script cannot safely preserve `_sheet`, it should leave that block untouched and edit only the CSV body cells it understands. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/spreadsheet-functions.md b/src-tauri/gen/apple/assets/agent-docs/spreadsheet-functions.md new file mode 100644 index 0000000..fadd2a9 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/spreadsheet-functions.md @@ -0,0 +1,191 @@ +# Spreadsheet Formulas + +Source: reference/spreadsheet-functions.md +URL: /reference/spreadsheet-functions + +# Spreadsheet Formulas + +Formula cells start with `=` and are evaluated by IronCalc through Tolaria's sheet editor. + +Tolaria adds vault-aware sheet references on top of the normal spreadsheet formula model. Everything else should be treated as IronCalc formula behavior. IronCalc aims for Excel-compatible formulas, but the upstream project is still evolving, so verify advanced formulas against the IronCalc docs when precision matters. + +## Basic Syntax + +| Syntax | Meaning | +| --- | --- | +| `=B2+B3-B4` | Arithmetic over cells. | +| `=SUM(B2:D2)` | Function call over a range. | +| `=ROUND(E6, 2)` | Function call with arguments. | +| `=IF(E6>0, "Up", "Down")` | Conditional expression. | +| `=$B$2` | Absolute column and row reference. | +| `=B$2` | Relative column, absolute row. | +| `=$B2` | Absolute column, relative row. | +| `=B2:D10` | A range in the current sheet note. | +| `="Q" & 1` | Text concatenation. | + +Use parentheses when a model depends on precedence: + +```txt +=(B2+B3-B4)/B5 +``` + +## Tolaria Note References + +Tolaria supports wikilink cell references for values that live in another sheet note: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=ROUND([[business-plan]].$E$12, 2) +``` + +The target inside `[[...]]` resolves like a normal Tolaria wikilink. The cell address after the dot uses A1 notation. + +Absolute markers follow spreadsheet copy behavior: + +| Reference | Copy behavior | +| --- | --- | +| `[[revenue]].B5` | row and column can shift | +| `[[revenue]].$B$5` | row and column stay fixed | +| `[[revenue]].B$5` | row fixed, column can shift | +| `[[revenue]].$B5` | column fixed, row can shift | + +Cross-sheet cell references currently resolve single cells. Keep range formulas inside one sheet note until cross-note ranges are explicitly supported. + +Formulas can also read scalar frontmatter properties from a specific note: + +```txt +=[[device.md]].power.watts +=[[project-alpha]].status +=[[book-notes/the-design-of-everyday-things.md]].rating +``` + +The target resolves like a wikilink, and the dot path reads nested frontmatter keys. Numbers, booleans, and strings become formula literals. Missing notes, ambiguous note targets, missing properties, arrays, maps, and other non-scalar values resolve to `#N/A`. + +A first segment that looks like an A1 cell address, such as `B2`, is treated as a cross-sheet cell reference. Use property names that do not collide with A1 notation for frontmatter formulas. + +## Autocomplete Functions + +Tolaria's formula autocomplete exposes the implemented function catalog from the bundled IronCalc engine. The current catalog has 195 functions. + +The dropdown shows a small ranked set of matches while you type. Keep typing to narrow the result list. Function names with digits and dots, such as `BIN2DEC` and `ERFC.PRECISE`, are supported. + +### Logical + +`AND`, `FALSE`, `IF`, `IFERROR`, `IFNA`, `IFS`, `NOT`, `OR`, `SWITCH`, `TRUE`, `XOR` + +### Math and trigonometry + +`ABS`, `ACOS`, `ACOSH`, `ASIN`, `ASINH`, `ATAN`, `ATAN2`, `ATANH`, `COS`, `COSH`, `PI`, `POWER`, `PRODUCT`, `RAND`, `RANDBETWEEN`, `ROUND`, `ROUNDDOWN`, `ROUNDUP`, `SIN`, `SINH`, `SQRT`, `SQRTPI`, `SUM`, `SUMIF`, `SUMIFS`, `TAN`, `TANH`, `SUBTOTAL` + +### Lookup and reference + +`CHOOSE`, `COLUMN`, `COLUMNS`, `HLOOKUP`, `INDEX`, `INDIRECT`, `LOOKUP`, `MATCH`, `OFFSET`, `ROW`, `ROWS`, `VLOOKUP`, `XLOOKUP` + +### Text + +`CONCAT`, `CONCATENATE`, `EXACT`, `FIND`, `LEFT`, `LEN`, `LOWER`, `MID`, `REPT`, `RIGHT`, `SEARCH`, `SUBSTITUTE`, `T`, `TEXT`, `TEXTAFTER`, `TEXTBEFORE`, `TEXTJOIN`, `TRIM`, `UNICODE`, `UPPER`, `VALUE`, `VALUETOTEXT` + +### Information + +`ERROR.TYPE`, `FORMULATEXT`, `ISBLANK`, `ISERR`, `ISERROR`, `ISEVEN`, `ISFORMULA`, `ISLOGICAL`, `ISNA`, `ISNONTEXT`, `ISNUMBER`, `ISODD`, `ISREF`, `ISTEXT`, `NA`, `SHEET`, `TYPE` + +### Statistical + +`AVERAGE`, `AVERAGEA`, `AVERAGEIF`, `AVERAGEIFS`, `COUNT`, `COUNTA`, `COUNTBLANK`, `COUNTIF`, `COUNTIFS`, `GEOMEAN`, `MAX`, `MAXIFS`, `MIN`, `MINIFS` + +### Date and time + +`DATE`, `DAY`, `EDATE`, `EOMONTH`, `MONTH`, `NOW`, `TODAY`, `YEAR` + +### Financial + +`CUMIPMT`, `CUMPRINC`, `DB`, `DDB`, `DOLLARDE`, `DOLLARFR`, `EFFECT`, `FV`, `IPMT`, `IRR`, `ISPMT`, `MIRR`, `NOMINAL`, `NPER`, `NPV`, `PDURATION`, `PMT`, `PPMT`, `PV`, `RATE`, `RRI`, `SLN`, `SYD`, `TBILLEQ`, `TBILLPRICE`, `TBILLYIELD`, `XIRR`, `XNPV` + +### Engineering + +`BESSELI`, `BESSELJ`, `BESSELK`, `BESSELY`, `BIN2DEC`, `BIN2HEX`, `BIN2OCT`, `BITAND`, `BITLSHIFT`, `BITOR`, `BITRSHIFT`, `BITXOR`, `COMPLEX`, `CONVERT`, `DEC2BIN`, `DEC2HEX`, `DEC2OCT`, `DELTA`, `ERF`, `ERF.PRECISE`, `ERFC`, `ERFC.PRECISE`, `GESTEP`, `HEX2BIN`, `HEX2DEC`, `HEX2OCT`, `IMABS`, `IMAGINARY`, `IMARGUMENT`, `IMCONJUGATE`, `IMCOS`, `IMCOSH`, `IMCOT`, `IMCSC`, `IMCSCH`, `IMDIV`, `IMEXP`, `IMLN`, `IMLOG10`, `IMLOG2`, `IMPOWER`, `IMPRODUCT`, `IMREAL`, `IMSEC`, `IMSECH`, `IMSIN`, `IMSINH`, `IMSQRT`, `IMSUB`, `IMSUM`, `IMTAN`, `OCT2BIN`, `OCT2DEC`, `OCT2HEX` + +## Examples + +### Totals + +```txt +=SUM(B2:D2) +=B2+B3-B4 +=SUM(B2:D2)-SUM(B4:D4) +``` + +### Growth And Percentages + +```txt +=(C5-B5)/B5 +=IF(B5=0, 0, (C5-B5)/B5) +=ROUND((C5-B5)/B5, 4) +``` + +Format the result as a percentage with a cell `num_fmt` such as `0.00%`. + +### Conditional Logic + +```txt +=IF(E5>10000, "On track", "Review") +=IFS(E5>10000, "High", E5>5000, "Medium", TRUE, "Low") +=IFERROR(B5/B4, 0) +``` + +### Dates + +```txt +=TODAY() +=DATE(2026, 6, 15) +=YEAR(TODAY()) +=MONTH(TODAY()) +``` + +### Text + +```txt +=CONCAT(A2, " - ", B2) +=UPPER(A2) +=TRIM(A2) +=TEXT(B2, "$#,##0.00") +``` + +### Lookup + +```txt +=INDEX(B2:E10, 3, 2) +=MATCH("Revenue", A2:A20, 0) +=VLOOKUP("Revenue", A2:E20, 5, FALSE) +=XLOOKUP("Revenue", A2:A20, E2:E20) +``` + +### Cross-Sheet Model + +```txt +=[[newsletter-revenue]].E5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=IF([[business-plan]].$E$12>0, [[business-plan]].$E$12, 0) +``` + +## IronCalc Function Families + +IronCalc documents formulas by category. Use these upstream pages for detailed syntax and examples. The upstream documentation may include newer functions that are not yet present in Tolaria's bundled IronCalc version. + +| Family | Link | +| --- | --- | +| Lookup and reference | [IronCalc lookup and reference](https://docs.ironcalc.com/functions/lookup-and-reference.html) | +| Financial | [IronCalc financial](https://docs.ironcalc.com/functions/financial.html) | +| Engineering | [IronCalc engineering](https://docs.ironcalc.com/functions/engineering.html) | +| Database | [IronCalc database](https://docs.ironcalc.com/functions/database.html) | +| Statistical | [IronCalc statistical](https://docs.ironcalc.com/functions/statistical.html) | +| Text | [IronCalc text](https://docs.ironcalc.com/functions/text.html) | +| Math and trigonometry | [IronCalc math and trigonometry](https://docs.ironcalc.com/functions/math-and-trigonometry.html) | +| Logical | [IronCalc logical](https://docs.ironcalc.com/functions/logical.html) | +| Date and time | [IronCalc date and time](https://docs.ironcalc.com/functions/date-and-time.html) | +| Information | [IronCalc information](https://docs.ironcalc.com/functions/information.html) | + +IronCalc also documents [value types](https://docs.ironcalc.com/features/value-types.html), [error types](https://docs.ironcalc.com/features/error-types.html), [optional arguments](https://docs.ironcalc.com/features/optional-arguments.html), and [formatting values](https://docs.ironcalc.com/features/formatting-values.html). + +For current upstream gaps, see IronCalc's [unsupported features](https://docs.ironcalc.com/features/unsupported-features.html). \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/spreadsheets.md b/src-tauri/gen/apple/assets/agent-docs/spreadsheets.md new file mode 100644 index 0000000..87fcba2 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/spreadsheets.md @@ -0,0 +1,143 @@ +# Spreadsheets + +Source: concepts/spreadsheets.md +URL: /concepts/spreadsheets + +# Spreadsheets + +Tolaria sheets are spreadsheet notes. They keep the same file-first model as other notes, but a note with `_display: sheet` opens in a spreadsheet editor instead of the rich text editor. The note's `type` remains available for organization. + +The durable file is still Markdown with YAML frontmatter. The body is CSV-like text containing cell inputs and formulas, and spreadsheet presentation state is stored as plain YAML under `_sheet`. + +## Read Next + +- [Use Spreadsheets](/guides/use-spreadsheets) for the editing workflow. +- [Spreadsheet File Format](/reference/spreadsheet-format) for the plain-text storage contract. +- [Spreadsheet Formulas](/reference/spreadsheet-functions) for formula syntax, autocomplete, and IronCalc function families. + +## Why Sheet Notes + +Sheets are useful when information is better modeled as rows, columns, and formulas than as prose. Examples include budgets, revenue models, inventories, editorial calendars, lightweight trackers, and analytical scratchpads. + +Tolaria does not store sheets as opaque workbook binaries. A sheet should remain: + +- readable in a text editor +- diffable in Git +- editable by humans and AI agents +- available offline +- connected to the rest of the vault through types, properties, relationships, and wikilinks + +## One Note, One Sheet + +A sheet note is a single sheet. Tolaria does not expose multiple tabs inside one note. + +When a model needs more than one table, create more than one sheet note and connect them with wikilinks or cross-sheet formulas. This keeps each file small, legible, and aligned with Tolaria's graph model. + +For example: + +- `newsletter-revenue.md` +- `sponsorship-pipeline.md` +- `refactoring-business-plan.md` + +Each can be a normal `_display: sheet` note, and formulas can reference cells in another sheet note with Tolaria's wikilink cell syntax. + +## Editing + +The interactive sheet editor is backed by IronCalc. Tolaria uses IronCalc for spreadsheet behavior and formula evaluation, then adapts the workbook back to Tolaria's plain-text note format. + +In the sheet editor: + +- cell inputs that start with `=` are formulas +- non-formula cells can contain normal text, numbers, dates, and `[[wikilinks]]` +- typing `[[` in a cell opens the same note autocomplete concept used elsewhere in Tolaria +- typing a formula function name opens inline formula autocomplete for the bundled IronCalc function catalog +- right-clicking a selection exposes core formatting controls such as number formats, decimal precision, bold, italic, and clear formatting + +Keyboard basics follow spreadsheet conventions: + +- arrow keys move the active cell +- `Shift` with arrows extends the selection +- `Enter` starts editing the active cell +- `Escape` exits cell editing while keeping focus in the sheet +- `Delete` or `Backspace` clears the selected range +- copy and paste should preserve formulas, including Tolaria cross-sheet references + +## Wikilinks In Cells + +Wikilinks in non-formula cells are stored as normal Tolaria wikilinks: + +```csv +Project,Owner,Status +[[website-redesign]],[[person/alice]],Active +[[sponsorship-pipeline]],[[person/matteo]],Review +``` + +The cell still behaves like a spreadsheet cell, but the value remains a vault link that Tolaria can understand. + +## Note Reference Formulas + +Tolaria adds a sheet-note reference syntax on top of IronCalc formulas: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=[[refactoring-business-plan]].$C$18 +``` + +The target before the dot is a normal Tolaria wikilink target. For another sheet note, the part after the dot is an A1-style cell address. + +Relative and absolute references work like spreadsheet references when copied: + +- `[[revenue]].B5` can shift when pasted to another cell +- `[[revenue]].$B$5` stays fixed +- `[[revenue]].B$5` fixes the row +- `[[revenue]].$B5` fixes the column + +This is not the same as an IronCalc workbook tab reference. It is Tolaria-specific syntax for referencing another sheet note in the vault. + +Current cross-sheet formulas resolve single cells. Ranges across sheet notes are not a stable file-format feature yet, so prefer composing them from explicit cell references or keeping range formulas inside the same sheet note. + +Sheet formulas can also read scalar frontmatter properties from a note: + +```txt +=[[device]].power.watts +=[[project-alpha]].status +``` + +This keeps sheet models connected to ordinary Tolaria metadata without requiring a saved view or query. Unresolved, ambiguous, or non-scalar property references show spreadsheet errors. + +## Storage + +A minimal sheet note looks like this: + +```md +--- +type: Project +_display: sheet +status: Draft +belongs_to: + - "[[business-plan]]" +_sheet: + frozen_rows: 1 + columns: + A: + width: 180 + cells: + E6: + num_fmt: "0.00%" +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Services,800,900,750,=SUM(B3:D3) +Expenses,650,700,760,=SUM(B4:D4) +Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5) +Growth,,=(C5-B5)/B5,=(D5-C5)/C5,=(E5-B5)/B5 +``` + +Normal frontmatter stays normal Tolaria metadata. `_sheet` is system metadata for the spreadsheet editor and is hidden from normal property editing. + +For the full storage contract, see [Spreadsheet File Format](/reference/spreadsheet-format). + +## Formulas + +Tolaria delegates formula calculation to IronCalc. IronCalc aims for Excel-compatible formulas, while its project documentation still describes it as work in progress. For Tolaria-specific formula behavior and the autocomplete function catalog, see [Spreadsheet Formulas](/reference/spreadsheet-functions). \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/start.md b/src-tauri/gen/apple/assets/agent-docs/start.md new file mode 100644 index 0000000..4151092 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/start.md @@ -0,0 +1,166 @@ +# First Launch + +Source: start/first-launch.md +URL: /start/first-launch + +# First Launch + +The first launch flow is designed to get you into a real vault quickly without hiding the local-first model. + +## What You Choose + +Tolaria asks whether you want to: + +- Create or clone the Getting Started vault. +- Open an existing local vault. +- Create a new empty vault. + +The Getting Started vault is cloned locally and then disconnected from its remote. That keeps the sample safe to edit without accidentally pushing tutorial changes. + +## What Tolaria Creates + +Tolaria stores app-level settings on the local machine. Your notes stay in the vault folder you choose. + +| Data | Stored in | +| --- | --- | +| Notes and attachments | Your vault folder | +| Type definitions and saved views | Your vault folder | +| Window size, zoom, recent vaults | Local app settings | +| Cache data | Rebuildable local cache | + +## First Commands To Try + +- `Cmd+K` / `Ctrl+K`: open the command palette. +- `New Note`: create a note in the current vault. +- `Open Getting Started Vault`: clone the public sample vault. +- `Reload Vault`: rescan files after external edits. + +## AI Setup Prompt + +Tolaria can show an optional AI agents prompt after a vault is open. It checks common local install locations for supported coding agents and gives you setup paths, but you can dismiss it and use Tolaria without AI. + +--- + +# Getting Started Vault + +Source: start/getting-started-vault.md +URL: /start/getting-started-vault + +# Getting Started Vault + +The Getting Started vault is a small public sample vault hosted at [refactoringhq/tolaria-getting-started](https://github.com/refactoringhq/tolaria-getting-started). + +It exists to show Tolaria's conventions without requiring you to restructure your own notes first. + +## What It Demonstrates + +- Markdown notes with YAML frontmatter. +- Types such as Project, Person, Topic, and Procedure. +- Wikilinks in note bodies. +- Relationship fields in frontmatter. +- A local Git repository that can be connected to a remote later. +- Vault guidance files for AI agents. + +## Local-Only By Default + +When Tolaria clones the sample, it removes the remote from the local copy. This makes the sample vault disposable. You can edit it freely, commit locally, and delete it later. + +To connect a vault to your own remote, use the bottom status bar remote chip or run `Add Remote` from the command palette. + +Tolaria also repairs starter-vault guidance files when needed. `AGENTS.md` is the canonical guidance file, `CLAUDE.md` is kept as a compatibility shim, and `GEMINI.md` is only created when you explicitly restore Antigravity/Gemini guidance. + +## Use It Alongside Your Own Vaults + +You can keep the Getting Started vault open while working in your own notes. Enable `Settings` -> `Vaults` -> `Use multiple vaults at the same time`, then use the bottom-left vault menu to include both the sample vault and your real vault in the unified graph. + +This lets search, quick open, note lists, backlinks, and wikilink navigation span both vaults. Git actions still stay scoped to each vault's own repository, and new notes go to the default vault you choose in `Manage vaults`. + +## When To Move On + +After you understand the sample, open your own vault. Tolaria does not require a special folder structure: a folder of Markdown files is enough to start. You can remove the sample from Tolaria's vault list later without deleting its files from disk. + +--- + +# Install Tolaria + +Source: start/install.md +URL: /start/install + +# Install Tolaria + +Tolaria publishes desktop builds for macOS, Windows, and Linux. macOS is the primary day-to-day development target, with Windows and Linux builds supported through the release pipeline and fixed as platform issues are found. + +## Download + +Use the latest stable release unless you are intentionally testing pre-release builds: + +- Download the latest stable build +- [Browse all GitHub releases](https://github.com/refactoringhq/tolaria/releases) +- Read the release notes + +## Homebrew + +On macOS you can install the cask: + +```bash +brew install --cask tolaria +``` + +## Platform Status + +| Platform | Status | Notes | +| --- | --- | --- | +| macOS | Primary | Apple Silicon and Intel builds are published. Homebrew is available. | +| Windows | Supported, early | NSIS installers and updater bundles are Tauri-signed. Authenticode publisher signing will be added after Windows certificate provisioning; company-managed SmartScreen, Defender, or WDAC policies can still require IT approval before install. | +| Linux | Supported, early | AppImage, deb, and RPM artifacts are published. Desktop behavior depends on distribution WebKitGTK and input-method integration. | + +See [Supported Platforms](/reference/supported-platforms) for the current support policy. + +## Managed Windows Devices + +Do not disable SmartScreen or Windows Security to install Tolaria. On a managed Windows device, install Tolaria through your normal software approval path if policy blocks unsigned or unknown-publisher installers. After Authenticode provisioning is complete, validate that the downloaded installer has a valid Tolaria publisher signature before installing. + +## After Installing + +1. Open Tolaria. +2. Choose the Getting Started vault if you want a guided sample. +3. Or open an existing folder of Markdown files as a vault. +4. Use the command palette with `Cmd+K` on macOS or `Ctrl+K` on Linux and Windows. + +--- + +# Open Or Create A Vault + +Source: start/open-or-create-vault.md +URL: /start/open-or-create-vault + +# Open Or Create A Vault + +A Tolaria vault is a folder on disk. The folder can contain Markdown notes, attachments, type definitions, saved views, and Git metadata. + +## Open An Existing Folder + +Choose an existing folder if you already have Markdown notes. Tolaria scans `.md` files and uses frontmatter when it exists. + +Good starting points: + +- A folder of plain Markdown files. +- An Obsidian-style vault. +- A Git repository containing notes. +- A copy of the Getting Started vault. + +## Create A New Vault + +Choose a new empty folder if you want Tolaria conventions from the start. New notes and optional type definitions are created as Markdown files. + +## Use More Than One Vault + +You do not have to merge everything into one folder. Register each local folder as its own vault, then turn on `Use multiple vaults at the same time` in `Settings` -> `Vaults`. + +Once enabled, the bottom-left vault menu lets you include vaults in the unified graph. Search, quick open, wikilinks, and note lists can span the included vaults, while Git sync and commits remain tied to each vault's own repository. + +## Git Is Recommended, Not Required + +Tolaria works well with a plain folder of Markdown files. You can open, edit, organize, and search notes without making the vault a Git repository. + +Git is recommended when you want local history, diff views, recovery, pull, push, and remote sync without a proprietary backend. If a vault is not already a repository, Tolaria can initialize one when you explicitly ask it to. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/supported-platforms.md b/src-tauri/gen/apple/assets/agent-docs/supported-platforms.md new file mode 100644 index 0000000..9eaf074 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/supported-platforms.md @@ -0,0 +1,28 @@ +# Supported Platforms + +Source: reference/supported-platforms.md +URL: /reference/supported-platforms + +# Supported Platforms + +Tolaria is a desktop app built with Tauri. Releases currently target macOS, Windows, and Linux. + +| Platform | Current support | Notes | +| --- | --- | --- | +| macOS | Primary | Main development and QA target. Apple Silicon and Intel artifacts are published. | +| Windows | Supported, early | NSIS installers and signed updater bundles are published. Menu, shell-path, and credential-helper behavior receive platform-specific fixes as they appear. | +| Linux | Supported, early | AppImage, deb, and RPM artifacts are published. Behavior can depend on distro WebKitGTK packages, Wayland/X11 details, and input-method setup. | + +## Support Policy + +Primary support means the platform is part of normal development and release validation. Supported, early means release artifacts exist and the app is expected to work, but platform-specific bugs can take longer to diagnose than macOS issues. + +## Reporting Platform Bugs + +Include: + +- Tolaria version. +- Operating system and version. +- CPU architecture. +- Whether the vault is local-only or connected to a remote. +- Steps to reproduce. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/sync-conflicts.md b/src-tauri/gen/apple/assets/agent-docs/sync-conflicts.md new file mode 100644 index 0000000..a122c9e --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/sync-conflicts.md @@ -0,0 +1,24 @@ +# Sync Conflicts + +Source: troubleshooting/sync-conflicts.md +URL: /troubleshooting/sync-conflicts + +# Sync Conflicts + +Sync conflicts happen when local and remote changes touch the same content. + +## What To Do + +1. Stop editing the conflicted note. +2. Open the conflict resolver if Tolaria presents it. +3. Review both sides. +4. Choose the correct content or merge manually. +5. Commit the resolved file. +6. Push again. + +## Prevent Conflicts + +- Pull before starting work on another device. +- Push after meaningful sessions. +- Keep AI-generated edits in small commits. +- Avoid editing the same note on multiple devices at the same time. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/templates.md b/src-tauri/gen/apple/assets/agent-docs/templates.md new file mode 100644 index 0000000..e07e136 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/templates.md @@ -0,0 +1,77 @@ +# Portent + +Source: templates/portent.md +URL: /templates/portent + +# Portent + +[Portent](https://portent.md) is an open specification and template for work and personal knowledge bases. + +It gives a Tolaria vault a small set of defaults for organizing information: clear types, generic graph-like relationships, and a simple lifecycle for captured knowledge. The goal is to make a knowledge base useful to humans and AI agents without forcing every person or team to design a private ontology first. + +## Core Questions + +Portent favors convention over configuration. Instead of asking "where should this go?", it asks: + +- What is this? +- What is it useful for? +- Is it captured, organized, or archived? + +Those questions map naturally to Tolaria's type documents, relationship fields, Inbox, organized state, archive behavior, and custom views. + +## Types + +Portent defines eight default types. + +PORT types are actionable: + +- Project +- Operation +- Responsibility +- Task + +ENTP types are non-actionable knowledge records: + +- Event +- Note +- Topic +- Person + +These defaults are meant to cover the common shape of personal and work knowledge with almost no setup. You can add custom types later, but Portent works best when the default vocabulary comes first. + +## Relationships + +Portent models knowledge as a graph. The two default relationships are: + +- `belongs_to`: primary ownership, composition, or context. +- `related_to`: a looser semantic connection. + +In Tolaria, these relationships can live in YAML frontmatter and point to other notes with wikilinks. That keeps the graph portable, searchable, and readable outside the app. + +## Lifecycle + +Portent separates capture from organization: + +1. Capture information quickly so it is not lost. +2. Organize it by assigning a type and useful relationships. +3. Archive it when it has served its purpose. + +Tolaria supports that lifecycle directly: the Inbox holds captured notes, organizing a note marks it ready for normal views, and archiving hides old or obsolete notes from active surfaces while keeping them available. + +## Why Use It + +A blank vault is flexible, but it also asks you to make structural decisions before you have momentum. Portent gives you enough structure to start capturing, organizing, and retrieving notes immediately. + +Because Portent is file-friendly and portable, the same model can work across local Markdown vaults, note apps, docs tools, and agent-readable knowledge bases. Tolaria is the first intended implementation, but the spec is not tied to Tolaria internals. + +## Start From The Template + +The fastest starting point is the Portent template vault: + +- [refactoringhq/portent-vault-template](https://github.com/refactoringhq/portent-vault-template) + +Use it as-is, rename pieces to match your language, or treat it as a reference model for your own Tolaria setup. + +## Learn More + +Visit [portent.md](https://portent.md) for the full spec, examples, and implementation notes. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/troubleshooting.md b/src-tauri/gen/apple/assets/agent-docs/troubleshooting.md new file mode 100644 index 0000000..1e27b9a --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/troubleshooting.md @@ -0,0 +1,152 @@ +# AI Agent Not Found + +Source: troubleshooting/ai-agent-not-found.md +URL: /troubleshooting/ai-agent-not-found + +# AI Agent Not Found + +Tolaria can only launch local CLI agents that are installed and discoverable. + +## Symptoms + +- The AI panel says no supported agent is available. +- Claude Code or another agent works in one shell but not in Tolaria. + +## Checks + +Open a terminal and run the agent command directly. For Claude Code: + +```bash +claude --version +``` + +If the command fails, install or repair the agent first. + +## Path Issues + +Desktop apps can inherit a different `PATH` from your interactive shell. Tolaria checks common install locations, but shell setup can still vary. Prefer installing CLI tools in standard locations or making them available from your login shell. + +--- + +# Git Authentication + +Source: troubleshooting/git-auth.md +URL: /troubleshooting/git-auth + +# Git Authentication + +Tolaria uses system Git authentication. It does not manage provider passwords directly. + +## Symptoms + +- Push fails. +- Pull asks for credentials repeatedly. +- Remote fetch works in one terminal but not in Tolaria. + +## Checks + +1. Open a terminal. +2. `cd` into the vault. +3. Run `git remote -v`. +4. Run `git fetch`. + +If `git fetch` fails in the terminal, fix system Git auth first. + +## Common Fixes + +- Sign in with GitHub CLI. +- Configure SSH keys. +- Update the remote URL. +- Check your credential helper. + +--- + +# Model Provider Connection + +Source: troubleshooting/model-provider-connection.md +URL: /troubleshooting/model-provider-connection + +# Model Provider Connection + +Use this checklist when a local or API model provider does not connect. + +## Local Providers + +For Ollama or LM Studio: + +1. Start the local model server. +2. Confirm the base URL in Tolaria matches the server. +3. Confirm the model ID is installed and loaded by the provider. +4. Use the Settings test action again. + +## API Providers + +For hosted providers: + +1. Confirm the provider kind and endpoint. +2. Confirm the model ID exists for your account. +3. Confirm the API key is saved locally or available in the configured environment variable. +4. Avoid storing secrets in the vault. + +## Chat Mode Boundary + +Direct model targets run in chat mode. If you need file-editing tools, use a coding agent target such as Claude Code, Codex, OpenCode, Pi, or Antigravity CLI. + +--- + +# Sync Conflicts + +Source: troubleshooting/sync-conflicts.md +URL: /troubleshooting/sync-conflicts + +# Sync Conflicts + +Sync conflicts happen when local and remote changes touch the same content. + +## What To Do + +1. Stop editing the conflicted note. +2. Open the conflict resolver if Tolaria presents it. +3. Review both sides. +4. Choose the correct content or merge manually. +5. Commit the resolved file. +6. Push again. + +## Prevent Conflicts + +- Pull before starting work on another device. +- Push after meaningful sessions. +- Keep AI-generated edits in small commits. +- Avoid editing the same note on multiple devices at the same time. + +--- + +# Vault Not Loading + +Source: troubleshooting/vault-not-loading.md +URL: /troubleshooting/vault-not-loading + +# Vault Not Loading + +Use this checklist when Tolaria cannot open or refresh a vault. + +## Check The Folder + +- Confirm the folder exists. +- Confirm the folder contains readable files. +- Confirm Tolaria has permission to access the folder. +- Try opening a smaller test vault to isolate the issue. + +## Check Git + +If the vault is a Git repository, verify it is not in a broken state: + +```bash +git status +``` + +Resolve interrupted merges or corrupted repository state before retrying. + +## Reload + +Run `Reload Vault` from the command palette. This clears derived cache and rescans the filesystem. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/types.md b/src-tauri/gen/apple/assets/agent-docs/types.md new file mode 100644 index 0000000..5ffa716 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/types.md @@ -0,0 +1,56 @@ +# Types + +Source: concepts/types.md +URL: /concepts/types + +# Types + +Types describe what kind of thing a note represents: Project, Person, Topic, Procedure, Event, or any category you create. + +## Type Field + +The `type:` field assigns a note to a type. + +```yaml +type: Project +``` + +Tolaria does not infer type from folder location. Moving a file into another folder does not change its type. + +## Prefer Types Over Folders + +Types are the preferred way to group notes in Tolaria. Folders are supported for existing vaults and fallback organization, but Tolaria is built around types and relationships because they carry stronger meaning than file paths. + +Use types for semantic groups such as Projects, People, Topics, Procedures, Events, and Essays. Use relationships to connect notes across those groups. This gives Tolaria better structure for navigation, filtering, properties, templates, and future automation than folder location alone. + +## Type Documents + +Type documents are Markdown notes with `type: Type` in frontmatter. They describe how a type should appear and what new notes of that type should start with. + +```yaml +--- +type: Type +_icon: folder +_color: blue +_sidebar_label: Projects +_order: 10 +--- + +# Project +``` + +Type templates can live in the Type document's `template` frontmatter field. When a hand-edited Type body contains template-like structure after its own `# TypeName` heading, Tolaria also uses that body content as the new-note template. Plain descriptive body text stays documentation-only. + +## What Types Control + +- Sidebar grouping. +- Type icon and color. +- Sidebar order and label. +- Pinned properties. +- New-note templates. + +## New Note Defaults + +Type documents can define empty properties and relationships. When you create a new note of that type, Tolaria shows placeholders for those fields so you can fill them in from the Properties panel. + +If a type document gives a property a value, that value becomes the default for new notes of that type. For example, a Project type can define `status: Active` so every new project starts active until you change it. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/use-ai-panel.md b/src-tauri/gen/apple/assets/agent-docs/use-ai-panel.md new file mode 100644 index 0000000..e28777c --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/use-ai-panel.md @@ -0,0 +1,43 @@ +# Use The AI + +Source: guides/use-ai-panel.md +URL: /guides/use-ai-panel + +# Use The AI + +Tolaria gives you two ways to ask for AI help: open the AI panel for an ongoing conversation, or prompt directly from the editor with `Cmd+K` followed by a space. + +## Choose How To Prompt + +- **AI panel** is best for longer conversations, agent work, and requests that need visible back-and-forth. +- **Inline prompt** is best when you are already writing. Press `Cmd+K`, type a space, then write the prompt you want the AI to handle from the current note context. + +## Choose A Target + +Open Settings and choose the default AI target: + +- **Coding agent** for tool-backed vault editing through Claude Code, Codex, OpenCode, Pi, or Antigravity CLI. +- **Local model** for Ollama or LM Studio chat over note context. +- **API model** for OpenAI, Anthropic, Gemini, OpenRouter, or an OpenAI-compatible endpoint. + +If a coding agent is missing, install it and reopen Tolaria or switch to another target. + +## Permission Mode + +Coding agents support per-vault permission modes: + +- **Vault Safe** keeps agents limited to file, search, and edit tools. +- **Power User** can allow shell commands for agents that support them. + +Direct model targets always stay in chat mode. They can use note context, but they cannot edit vault files through tools. + +## Good Requests + +- "Find notes related to this project." +- "Summarize what changed in this note." +- "Draft a weekly review from these linked notes." +- "Update this checklist based on the current project status." + +## Review Changes + +AI edits are file edits. Review them with Tolaria's diff and Git history before committing. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/use-command-palette.md b/src-tauri/gen/apple/assets/agent-docs/use-command-palette.md new file mode 100644 index 0000000..3792eef --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/use-command-palette.md @@ -0,0 +1,31 @@ +# Use The Command Palette + +Source: guides/use-command-palette.md +URL: /guides/use-command-palette + +# Use The Command Palette + +The command palette is the fastest way to move around Tolaria. + +Open it with: + +- `Cmd+K` on macOS. +- `Ctrl+K` on Linux and Windows. + +## Common Commands + +- New Note. +- Search. +- Open Settings. +- Reload Vault. +- Add Remote. +- Open Getting Started Vault. +- Toggle Raw Mode. +- Toggle Table of Contents. +- Toggle AI Panel. +- Use Light, Dark, or System theme. +- Open in New Window. + +## Keyboard-First Workflow + +Use the palette when you know what you want to do but do not want to hunt through panels. It is also the best place to discover commands as the app grows. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/use-media-previews.md b/src-tauri/gen/apple/assets/agent-docs/use-media-previews.md new file mode 100644 index 0000000..768ad34 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/use-media-previews.md @@ -0,0 +1,30 @@ +# Use Media Previews + +Source: guides/use-media-previews.md +URL: /guides/use-media-previews + +# Use Media Previews + +Media previews let you inspect vault files without leaving Tolaria. + +## Open A File + +Select an image, PDF, media file, or unsupported file from a folder or file list. Tolaria opens supported files in the app and offers an external-open action for files that should use the system default app. + +## All Notes Visibility + +Open Settings to choose whether non-Markdown files appear in All Notes: + +- PDFs. +- Images. +- Unsupported files. + +Folder browsing still shows files in their folders even when a category is hidden from All Notes. + +## Attachments + +When you paste or drop an image into a note, Tolaria copies it into the vault and references the copied file from Markdown. + +## Troubleshooting + +If a preview does not render, open the file in the default app to confirm the file is valid, then check whether the file is inside the active vault and not blocked by operating-system permissions. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/use-spreadsheets.md b/src-tauri/gen/apple/assets/agent-docs/use-spreadsheets.md new file mode 100644 index 0000000..07d5553 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/use-spreadsheets.md @@ -0,0 +1,151 @@ +# Use Spreadsheets + +Source: guides/use-spreadsheets.md +URL: /guides/use-spreadsheets + +# Use Spreadsheets + +Tolaria spreadsheets are sheet notes: Markdown files with frontmatter and a CSV-like body that open in a spreadsheet editor when their `Display as` value is `Sheet`. + +Use a sheet note when a model needs rows, columns, calculations, or repeated numeric editing. Use a normal note when the main artifact is prose. + +## Create A Sheet + +Use the command palette action `New Sheet`, or create/open a note and set its `Display as` to `Sheet` from the Properties panel. `Type` remains separate and can still be `Note`, `Project`, `Responsibility`, or any other Tolaria type. + +When a note is a sheet: + +- the YAML frontmatter remains available for type, status, relationships, wikilinks, and custom properties +- `_display: sheet` tells Tolaria to display the note with the spreadsheet editor +- the body is the sheet itself +- there is no rich-text body around the table +- the editor switches from the text editor to the spreadsheet editor + +## Enter Values + +Click a cell and type a value. Non-formula values can be text, numbers, dates, or wikilinks. + +Press `Enter` on a selected cell to edit the cell. Press `Escape` while editing to leave cell editing and keep focus in the sheet. + +Use `Delete` or `Backspace` to clear the selected cell or range. + +## Enter Formulas + +Formulas start with `=`. + +```txt +=B2+B3-B4 +=SUM(B2:D2) +=ROUND(E6, 2) +=IF(E6>0, "Up", "Down") +``` + +Tolaria shows inline formula autocomplete while you type. The autocomplete list is built from the implemented function catalog in the bundled IronCalc engine; formula evaluation is still handled by IronCalc. + +See [Spreadsheet Formulas](/reference/spreadsheet-functions) for syntax, supported examples, and links to the full IronCalc formula reference. + +## Select And Edit Ranges + +The sheet editor follows spreadsheet conventions: + +- arrow keys move the active cell +- `Shift` plus arrow keys extends the selection +- drag to select a range +- copy and paste preserves formulas where possible +- cut and paste moves formulas and shifts relative references +- right-click a selected cell or range to apply formatting + +Right-click actions apply to the current selection. Keep a multi-cell selection active before opening the context menu when you want to format several cells together. + +## Format Cells + +Use the context menu for common formatting: + +- number formats such as plain numbers, currency, and percentages +- decimal precision +- bold and italic text +- alignment and clearing formatting when available + +Formatting is stored as plain YAML under `_sheet`, not in an opaque workbook blob. For example, percentage formatting for `E6` is stored as: + +```yaml +_sheet: + cells: + E6: + num_fmt: "0.00%" +``` + +See [Spreadsheet File Format](/reference/spreadsheet-format) for the full storage model. + +## Add Wikilinks + +Type `[[` in a cell to open note autocomplete. + +```csv +Project,Owner,Status +[[website-redesign]],[[person/alice]],Active +[[sponsorship-pipeline]],[[person/matteo]],Review +``` + +When the cell is not being edited, Tolaria renders the wikilink like other note links. When you edit the cell, the raw `[[wikilink]]` syntax is shown again. + +Command-click a wikilink in a sheet cell to open the linked note. + +## Reference Another Note + +Formulas can read a cell from another sheet note with Tolaria's wikilink cell syntax: + +```txt +=[[newsletter-revenue]].B5 +=SUM(B2:D2)+[[sponsorship-pipeline]].E12 +=ROUND([[business-plan]].$E$12, 2) +``` + +The part inside `[[...]]` resolves like a normal Tolaria wikilink. The part after the dot is an A1-style cell reference. + +Use absolute markers when copying formulas: + +| Reference | Copy behavior | +| --- | --- | +| `[[revenue]].B5` | row and column can shift | +| `[[revenue]].$B$5` | row and column stay fixed | +| `[[revenue]].B$5` | row fixed, column can shift | +| `[[revenue]].$B5` | column fixed, row can shift | + +Cross-sheet references currently resolve single cells. Keep range formulas inside one sheet note. + +Formulas can read scalar frontmatter properties from a note with dot notation: + +```txt +=[[device]].power.watts +=[[project-alpha]].status +=[[book-notes/the-design-of-everyday-things.md]].rating +``` + +Numbers, booleans, and text properties can be used in formulas. Missing or ambiguous note targets, missing properties, and non-scalar values such as lists or nested objects show as spreadsheet errors. + +## Work With The Raw File + +A sheet file remains readable text: + +```md +--- +type: Project +_display: sheet +status: Draft +belongs_to: + - "[[business-plan]]" +_sheet: + frozen_rows: 1 + columns: + A: + width: 180 +--- +Metric,January,February,March,Q1 Total +Subscriptions,1200,1350,1500,=SUM(B2:D2) +Services,800,900,750,=SUM(B3:D3) +Expenses,650,700,760,=SUM(B4:D4) +Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5) +``` + +When editing this file with scripts or AI agents, parse the body as CSV and preserve formulas as formulas. Do not replace formulas with displayed values. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/use-table-of-contents.md b/src-tauri/gen/apple/assets/agent-docs/use-table-of-contents.md new file mode 100644 index 0000000..f978c0e --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/use-table-of-contents.md @@ -0,0 +1,28 @@ +# Use The Table Of Contents + +Source: guides/use-table-of-contents.md +URL: /guides/use-table-of-contents + +# Use The Table Of Contents + +The table of contents panel helps you navigate long notes by heading. + +## Open It + +Use the editor toolbar, the command palette, or the shortcut: + +- `Cmd+Shift+T` on macOS. +- `Ctrl+Shift+T` on Windows and Linux. + +## How It Works + +Tolaria builds the outline from the current note's headings. The panel updates as the note changes and can jump to sections in the editor. + +## Good Uses + +- Long procedures. +- Meeting notes with many sections. +- Research notes. +- Generated documents that need review. + +If a note has no useful headings, add clear H2 and H3 sections rather than relying on a long uninterrupted document. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/use-wikilinks.md b/src-tauri/gen/apple/assets/agent-docs/use-wikilinks.md new file mode 100644 index 0000000..b4e00ca --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/use-wikilinks.md @@ -0,0 +1,29 @@ +# Use Wikilinks + +Source: guides/use-wikilinks.md +URL: /guides/use-wikilinks + +# Use Wikilinks + +Wikilinks connect notes by name. + +```md +This project belongs to [[content-systems]] and is related to [[git-workflows]]. +``` + +## Link From The Body + +Use body links when the connection is part of the sentence you are writing. + +## Link From Frontmatter + +Use frontmatter links when the relationship should become structured metadata. + +```yaml +related_to: + - "[[git-workflows]]" +``` + +## Keep Links Stable + +Prefer clear note titles and filenames. Tolaria's wikilink autocomplete helps you pick the right target while you type. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/vault-not-loading.md b/src-tauri/gen/apple/assets/agent-docs/vault-not-loading.md new file mode 100644 index 0000000..de2fa5c --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/vault-not-loading.md @@ -0,0 +1,29 @@ +# Vault Not Loading + +Source: troubleshooting/vault-not-loading.md +URL: /troubleshooting/vault-not-loading + +# Vault Not Loading + +Use this checklist when Tolaria cannot open or refresh a vault. + +## Check The Folder + +- Confirm the folder exists. +- Confirm the folder contains readable files. +- Confirm Tolaria has permission to access the folder. +- Try opening a smaller test vault to isolate the issue. + +## Check Git + +If the vault is a Git repository, verify it is not in a broken state: + +```bash +git status +``` + +Resolve interrupted merges or corrupted repository state before retrying. + +## Reload + +Run `Reload Vault` from the command palette. This clears derived cache and rescans the filesystem. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/vaults.md b/src-tauri/gen/apple/assets/agent-docs/vaults.md new file mode 100644 index 0000000..fb28941 --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/vaults.md @@ -0,0 +1,50 @@ +# Vaults + +Source: concepts/vaults.md +URL: /concepts/vaults + +# Vaults + +A vault is the folder Tolaria reads and writes. The filesystem is the source of truth; the app state and cache are derived from files. + +## Core Rules + +- Notes are Markdown files. +- YAML frontmatter provides structure. +- Attachments are normal files inside the vault. +- Type definitions and saved views are also files. +- Git can track history and support remote sync. + +## Why Local Files Matter + +Local files keep your notes inspectable. You can open them in another editor, search with command-line tools, back them up with your own system, and version them with Git. + +Tolaria should never become the only way to read your data. + +## Git Is A Capability + +A plain folder of Markdown files can open as a vault. Git-backed vaults unlock history, changes, commits, pull, push, conflict handling, and remote setup. + +If a folder is not a Git repository, Tolaria can initialize Git when you explicitly ask it to. It avoids initializing broad personal folders such as Desktop, Documents, or Downloads unless they are clearly dedicated vault folders. + +## Multiple Vaults At The Same Time + +Tolaria can load multiple registered vaults into one unified graph. Enable this from `Settings` -> `Vaults` -> `Use multiple vaults at the same time`. + +After the option is enabled, open the bottom-left vault menu to include or exclude vaults from the graph. Included vaults appear together in note lists, search, quick open, backlinks, and wikilink navigation. Each note keeps a compact vault badge when Tolaria needs to disambiguate where it lives. + +The selected vault still matters. Git status, commits, sync, folder navigation, saved views, and vault repair actions stay scoped to the current repository. Use `Manage vaults` from the vault menu or the Vaults settings section to rename vaults, choose colors, and set the default destination for new notes. + +Cross-vault wikilinks use the target vault's stable alias when needed, for example `[[team/projects/alpha]]`. Links inside the same vault stay normal vault-relative links. + +## App State Versus Vault State + +Vault-level information should travel with the vault. Machine-specific preferences stay with the app installation. + +| Vault state | App state | +| --- | --- | +| Type icons and colors | Editor zoom | +| Saved views | Window size | +| Pinned properties | Recent vault list | +| Relationship conventions | Local cache | +| Vault AI guidance files | AI target selection | \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/agent-docs/view-filters.md b/src-tauri/gen/apple/assets/agent-docs/view-filters.md new file mode 100644 index 0000000..533498e --- /dev/null +++ b/src-tauri/gen/apple/assets/agent-docs/view-filters.md @@ -0,0 +1,38 @@ +# View Filters + +Source: reference/view-filters.md +URL: /reference/view-filters + +# View Filters + +View filters define saved lists of notes. + +## Common Filter Ideas + +| Goal | Filter direction | +| --- | --- | +| Active projects | `type` is Project and `status` is Active | +| Drafts | `type` is Article and `status` is Draft | +| People follow-up | `type` is Person and date is before today | +| Recent work | modified date is within a recent range | + +## Sorting + +Useful sorts include: + +- Recently modified first. +- Title ascending. +- Status ascending. +- A custom property ascending or descending. + +## Operators + +Saved views can combine filters for text, dates, relationship fields, and frontmatter values. Relative date expressions are useful for views such as notes changed this week or people that need follow-up. + +Regex filters are available for power-user cases. Keep them narrow and test them on a small view first. + +## Keep Views Focused + +A view should answer one recurring question. If it becomes too broad, split it into two views. + +You can also customize view appearance with the same kind of icon and color controls used by types. \ No newline at end of file diff --git a/src-tauri/gen/apple/assets/mcp-server/.placeholder b/src-tauri/gen/apple/assets/mcp-server/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/src-tauri/gen/apple/assets/mcp-server/index.js b/src-tauri/gen/apple/assets/mcp-server/index.js index 7bbf091..54dc69a 100755 --- a/src-tauri/gen/apple/assets/mcp-server/index.js +++ b/src-tauri/gen/apple/assets/mcp-server/index.js @@ -3099,12 +3099,15 @@ var require_data = __commonJS({ } }); -// node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/utils.js +// node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js var require_utils = __commonJS({ - "node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/utils.js"(exports2, module2) { + "node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/utils.js"(exports2, module2) { "use strict"; var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu); var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u); + var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu); + var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu); + var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu); function stringArrayToHexStripped(input) { let acc = ""; let code = 0; @@ -3222,8 +3225,8 @@ var require_utils = __commonJS({ } return ind; } - function removeDotSegments(path2) { - let input = path2; + function removeDotSegments(path4) { + let input = path4; const output = []; let nextSlash = -1; let len = 0; @@ -3297,27 +3300,77 @@ var require_utils = __commonJS({ } return output.join(""); } - function normalizeComponentEncoding(component, esc2) { - const func = esc2 !== true ? escape : unescape; - if (component.scheme !== void 0) { - component.scheme = func(component.scheme); + var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" }; + var HOST_DELIM_RE = /[@/?#:]/g; + var HOST_DELIM_NO_COLON_RE = /[@/?#]/g; + function reescapeHostDelimiters(host, isIP) { + const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE; + re.lastIndex = 0; + return host.replace(re, (ch) => HOST_DELIMS[ch]); + } + function normalizePercentEncoding(input, decodeUnreserved = false) { + if (input.indexOf("%") === -1) { + return input; } - if (component.userinfo !== void 0) { - component.userinfo = func(component.userinfo); + let output = ""; + for (let i = 0; i < input.length; i++) { + if (input[i] === "%" && i + 2 < input.length) { + const hex3 = input.slice(i + 1, i + 3); + if (isHexPair(hex3)) { + const normalizedHex = hex3.toUpperCase(); + const decoded = String.fromCharCode(parseInt(normalizedHex, 16)); + if (decodeUnreserved && isUnreserved(decoded)) { + output += decoded; + } else { + output += "%" + normalizedHex; + } + i += 2; + continue; + } + } + output += input[i]; } - if (component.host !== void 0) { - component.host = func(component.host); + return output; + } + function normalizePathEncoding(input) { + let output = ""; + for (let i = 0; i < input.length; i++) { + if (input[i] === "%" && i + 2 < input.length) { + const hex3 = input.slice(i + 1, i + 3); + if (isHexPair(hex3)) { + const normalizedHex = hex3.toUpperCase(); + const decoded = String.fromCharCode(parseInt(normalizedHex, 16)); + if (decoded !== "." && isUnreserved(decoded)) { + output += decoded; + } else { + output += "%" + normalizedHex; + } + i += 2; + continue; + } + } + if (isPathCharacter(input[i])) { + output += input[i]; + } else { + output += escape(input[i]); + } } - if (component.path !== void 0) { - component.path = func(component.path); + return output; + } + function escapePreservingEscapes(input) { + let output = ""; + for (let i = 0; i < input.length; i++) { + if (input[i] === "%" && i + 2 < input.length) { + const hex3 = input.slice(i + 1, i + 3); + if (isHexPair(hex3)) { + output += "%" + hex3.toUpperCase(); + i += 2; + continue; + } + } + output += escape(input[i]); } - if (component.query !== void 0) { - component.query = func(component.query); - } - if (component.fragment !== void 0) { - component.fragment = func(component.fragment); - } - return component; + return output; } function recomposeAuthority(component) { const uriTokens = []; @@ -3332,7 +3385,7 @@ var require_utils = __commonJS({ if (ipV6res.isIPV6 === true) { host = `[${ipV6res.escapedHost}]`; } else { - host = component.host; + host = reescapeHostDelimiters(host, false); } } uriTokens.push(host); @@ -3346,7 +3399,10 @@ var require_utils = __commonJS({ module2.exports = { nonSimpleDomain, recomposeAuthority, - normalizeComponentEncoding, + reescapeHostDelimiters, + normalizePercentEncoding, + normalizePathEncoding, + escapePreservingEscapes, removeDotSegments, isIPv4, isUUID, @@ -3356,9 +3412,9 @@ var require_utils = __commonJS({ } }); -// node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/schemes.js +// node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js var require_schemes = __commonJS({ - "node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/lib/schemes.js"(exports2, module2) { + "node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/lib/schemes.js"(exports2, module2) { "use strict"; var { isUUID } = require_utils(); var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu; @@ -3422,8 +3478,8 @@ var require_schemes = __commonJS({ wsComponent.secure = void 0; } if (wsComponent.resourceName) { - const [path2, query] = wsComponent.resourceName.split("?"); - wsComponent.path = path2 && path2 !== "/" ? path2 : void 0; + const [path4, query] = wsComponent.resourceName.split("?"); + wsComponent.path = path4 && path4 !== "/" ? path4 : void 0; wsComponent.query = query; wsComponent.resourceName = void 0; } @@ -3566,16 +3622,16 @@ var require_schemes = __commonJS({ } }); -// node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/index.js +// node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js var require_fast_uri = __commonJS({ - "node_modules/.pnpm/fast-uri@3.1.0/node_modules/fast-uri/index.js"(exports2, module2) { + "node_modules/.pnpm/fast-uri@3.1.2/node_modules/fast-uri/index.js"(exports2, module2) { "use strict"; - var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils(); + var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils(); var { SCHEMES, getSchemeHandler } = require_schemes(); function normalize(uri, options2) { if (typeof uri === "string") { uri = /** @type {T} */ - serialize(parse4(uri, options2), options2); + normalizeString(uri, options2); } else if (typeof uri === "object") { uri = /** @type {T} */ parse4(serialize(uri, options2), options2); @@ -3642,19 +3698,9 @@ var require_fast_uri = __commonJS({ return target; } function equal(uriA, uriB, options2) { - if (typeof uriA === "string") { - uriA = unescape(uriA); - uriA = serialize(normalizeComponentEncoding(parse4(uriA, options2), true), { ...options2, skipEscape: true }); - } else if (typeof uriA === "object") { - uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options2, skipEscape: true }); - } - if (typeof uriB === "string") { - uriB = unescape(uriB); - uriB = serialize(normalizeComponentEncoding(parse4(uriB, options2), true), { ...options2, skipEscape: true }); - } else if (typeof uriB === "object") { - uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options2, skipEscape: true }); - } - return uriA.toLowerCase() === uriB.toLowerCase(); + const normalizedA = normalizeComparableURI(uriA, options2); + const normalizedB = normalizeComparableURI(uriB, options2); + return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase(); } function serialize(cmpts, opts) { const component = { @@ -3679,12 +3725,12 @@ var require_fast_uri = __commonJS({ if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options2); if (component.path !== void 0) { if (!options2.skipEscape) { - component.path = escape(component.path); + component.path = escapePreservingEscapes(component.path); if (component.scheme !== void 0) { component.path = component.path.split("%3A").join(":"); } } else { - component.path = unescape(component.path); + component.path = normalizePercentEncoding(component.path); } } if (options2.reference !== "suffix" && component.scheme) { @@ -3719,7 +3765,16 @@ var require_fast_uri = __commonJS({ return uriTokens.join(""); } var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u; - function parse4(uri, opts) { + function getParseError(parsed, matches) { + if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") { + return 'URI path must start with "/" when authority is present.'; + } + if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) { + return "URI port is malformed."; + } + return void 0; + } + function parseWithStatus(uri, opts) { const options2 = Object.assign({}, opts); const parsed = { scheme: void 0, @@ -3730,6 +3785,7 @@ var require_fast_uri = __commonJS({ query: void 0, fragment: void 0 }; + let malformedAuthorityOrPort = false; let isIP = false; if (options2.reference === "suffix") { if (options2.scheme) { @@ -3750,6 +3806,11 @@ var require_fast_uri = __commonJS({ if (isNaN(parsed.port)) { parsed.port = matches[5]; } + const parseError = getParseError(parsed, matches); + if (parseError !== void 0) { + parsed.error = parsed.error || parseError; + malformedAuthorityOrPort = true; + } if (parsed.host) { const ipv4result = isIPv4(parsed.host); if (ipv4result === false) { @@ -3788,14 +3849,18 @@ var require_fast_uri = __commonJS({ parsed.scheme = unescape(parsed.scheme); } if (parsed.host !== void 0) { - parsed.host = unescape(parsed.host); + parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP); } } if (parsed.path) { - parsed.path = escape(unescape(parsed.path)); + parsed.path = normalizePathEncoding(parsed.path); } if (parsed.fragment) { - parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment)); + try { + parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment)); + } catch { + parsed.error = parsed.error || "URI malformed"; + } } } if (schemeHandler && schemeHandler.parse) { @@ -3804,7 +3869,29 @@ var require_fast_uri = __commonJS({ } else { parsed.error = parsed.error || "URI can not be parsed."; } - return parsed; + return { parsed, malformedAuthorityOrPort }; + } + function parse4(uri, opts) { + return parseWithStatus(uri, opts).parsed; + } + function normalizeString(uri, opts) { + return normalizeStringWithStatus(uri, opts).normalized; + } + function normalizeStringWithStatus(uri, opts) { + const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts); + return { + normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts), + malformedAuthorityOrPort + }; + } + function normalizeComparableURI(uri, opts) { + if (typeof uri === "string") { + const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts); + return malformedAuthorityOrPort ? void 0 : normalized; + } + if (typeof uri === "object") { + return serialize(uri, opts); + } } var fastUri = { SCHEMES, @@ -6785,12 +6872,12 @@ var require_dist = __commonJS({ throw new Error(`Unknown format "${name}"`); return f; }; - function addFormats(ajv, list, fs2, exportName) { + function addFormats(ajv, list, fs, exportName) { var _a2; var _b; (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`; for (const f of list) - ajv.addFormat(f, fs2[f]); + ajv.addFormat(f, fs[f]); } module2.exports = exports2 = formatsPlugin; Object.defineProperty(exports2, "__esModule", { value: true }); @@ -6798,9 +6885,9 @@ var require_dist = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/constants.js var require_constants = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/constants.js"(exports2, module2) { "use strict"; var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"]; var hasBlob = typeof Blob !== "undefined"; @@ -6821,9 +6908,9 @@ var require_constants = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/buffer-util.js var require_buffer_util = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/buffer-util.js"(exports2, module2) { "use strict"; var { EMPTY_BUFFER } = require_constants(); var FastBuffer = Buffer[Symbol.species]; @@ -6896,9 +6983,9 @@ var require_buffer_util = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/limiter.js var require_limiter = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/limiter.js"(exports2, module2) { "use strict"; var kDone = /* @__PURE__ */ Symbol("kDone"); var kRun = /* @__PURE__ */ Symbol("kRun"); @@ -6946,9 +7033,9 @@ var require_limiter = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/permessage-deflate.js var require_permessage_deflate = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/permessage-deflate.js"(exports2, module2) { "use strict"; var zlib = require("zlib"); var bufferUtil = require_buffer_util(); @@ -6962,7 +7049,7 @@ var require_permessage_deflate = __commonJS({ var kBuffers = /* @__PURE__ */ Symbol("buffers"); var kError = /* @__PURE__ */ Symbol("error"); var zlibLimiter; - var PerMessageDeflate = class { + var PerMessageDeflate2 = class { /** * Creates a PerMessageDeflate instance. * @@ -6973,6 +7060,9 @@ var require_permessage_deflate = __commonJS({ * acknowledge disabling of client context takeover * @param {Number} [options.concurrencyLimit=10] The number of concurrent * calls to zlib + * @param {Boolean} [options.isServer=false] Create the instance in either + * server or client mode + * @param {Number} [options.maxPayload=0] The maximum allowed message length * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the * use of a custom server window size * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept @@ -6983,15 +7073,12 @@ var require_permessage_deflate = __commonJS({ * deflate * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on * inflate - * @param {Boolean} [isServer=false] Create the instance in either server or - * client mode - * @param {Number} [maxPayload=0] The maximum allowed message length */ - constructor(options2, isServer, maxPayload) { - this._maxPayload = maxPayload | 0; + constructor(options2) { this._options = options2 || {}; this._threshold = this._options.threshold !== void 0 ? this._options.threshold : 1024; - this._isServer = !!isServer; + this._maxPayload = this._options.maxPayload | 0; + this._isServer = !!this._options.isServer; this._deflate = null; this._inflate = null; this.params = null; @@ -7300,7 +7387,7 @@ var require_permessage_deflate = __commonJS({ }); } }; - module2.exports = PerMessageDeflate; + module2.exports = PerMessageDeflate2; function deflateOnData(chunk) { this[kBuffers].push(chunk); this[kTotalLength] += chunk.length; @@ -7329,9 +7416,9 @@ var require_permessage_deflate = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/validation.js var require_validation2 = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/validation.js"(exports2, module2) { "use strict"; var { isUtf8 } = require("buffer"); var { hasBlob } = require_constants(); @@ -7530,12 +7617,12 @@ var require_validation2 = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/receiver.js var require_receiver = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/receiver.js"(exports2, module2) { "use strict"; var { Writable } = require("stream"); - var PerMessageDeflate = require_permessage_deflate(); + var PerMessageDeflate2 = require_permessage_deflate(); var { BINARY_TYPES, EMPTY_BUFFER, @@ -7565,6 +7652,10 @@ var require_receiver = __commonJS({ * extensions * @param {Boolean} [options.isServer=false] Specifies whether to operate in * client or server mode + * @param {Number} [options.maxBufferedChunks=0] The maximum number of + * buffered data chunks + * @param {Number} [options.maxFragments=0] The maximum number of message + * fragments * @param {Number} [options.maxPayload=0] The maximum allowed message length * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or * not to skip UTF-8 validation for text and close messages @@ -7575,6 +7666,8 @@ var require_receiver = __commonJS({ this._binaryType = options2.binaryType || BINARY_TYPES[0]; this._extensions = options2.extensions || {}; this._isServer = !!options2.isServer; + this._maxBufferedChunks = options2.maxBufferedChunks | 0; + this._maxFragments = options2.maxFragments | 0; this._maxPayload = options2.maxPayload | 0; this._skipUTF8Validation = !!options2.skipUTF8Validation; this[kWebSocket] = void 0; @@ -7604,6 +7697,18 @@ var require_receiver = __commonJS({ */ _write(chunk, encoding, cb) { if (this._opcode === 8 && this._state == GET_INFO) return cb(); + if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) { + cb( + this.createError( + RangeError, + "Too many buffered chunks", + false, + 1008, + "WS_ERR_TOO_MANY_BUFFERED_PARTS" + ) + ); + return; + } this._bufferedBytes += chunk.length; this._buffers.push(chunk); this.startLoop(cb); @@ -7702,7 +7807,7 @@ var require_receiver = __commonJS({ return; } const compressed = (buf[0] & 64) === 64; - if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { + if (compressed && !this._extensions[PerMessageDeflate2.extensionName]) { const error2 = this.createError( RangeError, "RSV1 must be clear", @@ -7933,6 +8038,17 @@ var require_receiver = __commonJS({ return; } if (data.length) { + if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) { + const error2 = this.createError( + RangeError, + "Too many message fragments", + false, + 1008, + "WS_ERR_TOO_MANY_BUFFERED_PARTS" + ); + cb(error2); + return; + } this._messageLength = this._totalPayloadLength; this._fragments.push(data); } @@ -7946,7 +8062,7 @@ var require_receiver = __commonJS({ * @private */ decompress(data, cb) { - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName]; perMessageDeflate.decompress(data, this._fin, (err, buf) => { if (err) return cb(err); if (buf.length) { @@ -7962,6 +8078,17 @@ var require_receiver = __commonJS({ cb(error2); return; } + if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) { + const error2 = this.createError( + RangeError, + "Too many message fragments", + false, + 1008, + "WS_ERR_TOO_MANY_BUFFERED_PARTS" + ); + cb(error2); + return; + } this._fragments.push(buf); } this.dataMessage(cb); @@ -8122,13 +8249,16 @@ var require_receiver = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/sender.js var require_sender = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/sender.js"(exports2, module2) { "use strict"; var { Duplex } = require("stream"); var { randomFillSync } = require("crypto"); - var PerMessageDeflate = require_permessage_deflate(); + var { + types: { isUint8Array } + } = require("util"); + var PerMessageDeflate2 = require_permessage_deflate(); var { EMPTY_BUFFER, kWebSocket, NOOP } = require_constants(); var { isBlob, isValidStatusCode } = require_validation2(); var { mask: applyMask, toBuffer } = require_buffer_util(); @@ -8281,8 +8411,10 @@ var require_sender = __commonJS({ buf.writeUInt16BE(code, 0); if (typeof data === "string") { buf.write(data, 2); - } else { + } else if (isUint8Array(data)) { buf.set(data, 2); + } else { + throw new TypeError("Second argument must be a string or a Uint8Array"); } } const options2 = { @@ -8412,7 +8544,7 @@ var require_sender = __commonJS({ * @public */ send(data, options2, cb) { - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName]; let opcode = options2.binary ? 2 : 1; let rsv1 = options2.compress; let byteLength; @@ -8536,7 +8668,7 @@ var require_sender = __commonJS({ this.sendFrame(_Sender.frame(data, options2), cb); return; } - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName]; this._bufferedBytes += options2[kByteLength]; this._state = DEFLATING; perMessageDeflate.compress(data, options2.fin, (_, buf) => { @@ -8610,9 +8742,9 @@ var require_sender = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/event-target.js var require_event_target = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/event-target.js"(exports2, module2) { "use strict"; var { kForOnEventAttribute, kListener } = require_constants(); var kCode = /* @__PURE__ */ Symbol("kCode"); @@ -8839,9 +8971,9 @@ var require_event_target = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/extension.js var require_extension = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/extension.js"(exports2, module2) { "use strict"; var { tokenChars } = require_validation2(); function push(dest, name, elem) { @@ -8974,11 +9106,11 @@ var require_extension = __commonJS({ return offers; } function format(extensions) { - return Object.keys(extensions).map((extension) => { - let configurations = extensions[extension]; + return Object.keys(extensions).map((extension2) => { + let configurations = extensions[extension2]; if (!Array.isArray(configurations)) configurations = [configurations]; return configurations.map((params) => { - return [extension].concat( + return [extension2].concat( Object.keys(params).map((k) => { let values = params[k]; if (!Array.isArray(values)) values = [values]; @@ -8992,9 +9124,9 @@ var require_extension = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket.js var require_websocket = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket.js"(exports2, module2) { "use strict"; var EventEmitter = require("events"); var https = require("https"); @@ -9004,7 +9136,7 @@ var require_websocket = __commonJS({ var { randomBytes, createHash } = require("crypto"); var { Duplex, Readable } = require("stream"); var { URL: URL2 } = require("url"); - var PerMessageDeflate = require_permessage_deflate(); + var PerMessageDeflate2 = require_permessage_deflate(); var Receiver2 = require_receiver(); var Sender2 = require_sender(); var { isBlob } = require_validation2(); @@ -9163,6 +9295,10 @@ var require_websocket = __commonJS({ * multiple times in the same tick * @param {Function} [options.generateMask] The function used to generate the * masking key + * @param {Number} [options.maxBufferedChunks=0] The maximum number of + * buffered data chunks + * @param {Number} [options.maxFragments=0] The maximum number of message + * fragments * @param {Number} [options.maxPayload=0] The maximum allowed message size * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or * not to skip UTF-8 validation for text and close messages @@ -9174,6 +9310,8 @@ var require_websocket = __commonJS({ binaryType: this.binaryType, extensions: this._extensions, isServer: this._isServer, + maxBufferedChunks: options2.maxBufferedChunks, + maxFragments: options2.maxFragments, maxPayload: options2.maxPayload, skipUTF8Validation: options2.skipUTF8Validation }); @@ -9212,8 +9350,8 @@ var require_websocket = __commonJS({ this.emit("close", this._closeCode, this._closeMessage); return; } - if (this._extensions[PerMessageDeflate.extensionName]) { - this._extensions[PerMessageDeflate.extensionName].cleanup(); + if (this._extensions[PerMessageDeflate2.extensionName]) { + this._extensions[PerMessageDeflate2.extensionName].cleanup(); } this._receiver.removeAllListeners(); this._readyState = _WebSocket.CLOSED; @@ -9375,7 +9513,7 @@ var require_websocket = __commonJS({ fin: true, ...options2 }; - if (!this._extensions[PerMessageDeflate.extensionName]) { + if (!this._extensions[PerMessageDeflate2.extensionName]) { opts.compress = false; } this._sender.send(data || EMPTY_BUFFER, opts, cb); @@ -9473,6 +9611,8 @@ var require_websocket = __commonJS({ autoPong: true, closeTimeout: CLOSE_TIMEOUT, protocolVersion: protocolVersions[1], + maxBufferedChunks: 1024 * 1024, + maxFragments: 128 * 1024, maxPayload: 100 * 1024 * 1024, skipUTF8Validation: false, perMessageDeflate: true, @@ -9501,7 +9641,7 @@ var require_websocket = __commonJS({ } else { try { parsedUrl = new URL2(address); - } catch (e) { + } catch { throw new SyntaxError(`Invalid URL: ${address}`); } } @@ -9549,13 +9689,13 @@ var require_websocket = __commonJS({ opts.path = parsedUrl.pathname + parsedUrl.search; opts.timeout = opts.handshakeTimeout; if (opts.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate( - opts.perMessageDeflate !== true ? opts.perMessageDeflate : {}, - false, - opts.maxPayload - ); + perMessageDeflate = new PerMessageDeflate2({ + ...opts.perMessageDeflate, + isServer: false, + maxPayload: opts.maxPayload + }); opts.headers["Sec-WebSocket-Extensions"] = format({ - [PerMessageDeflate.extensionName]: perMessageDeflate.offer() + [PerMessageDeflate2.extensionName]: perMessageDeflate.offer() }); } if (protocols.length) { @@ -9698,23 +9838,25 @@ var require_websocket = __commonJS({ return; } const extensionNames = Object.keys(extensions); - if (extensionNames.length !== 1 || extensionNames[0] !== PerMessageDeflate.extensionName) { + if (extensionNames.length !== 1 || extensionNames[0] !== PerMessageDeflate2.extensionName) { const message = "Server indicated an extension that was not requested"; abortHandshake(websocket, socket, message); return; } try { - perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); + perMessageDeflate.accept(extensions[PerMessageDeflate2.extensionName]); } catch (err) { const message = "Invalid Sec-WebSocket-Extensions header"; abortHandshake(websocket, socket, message); return; } - websocket._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + websocket._extensions[PerMessageDeflate2.extensionName] = perMessageDeflate; } websocket.setSocket(socket, head, { allowSynchronousEvents: opts.allowSynchronousEvents, generateMask: opts.generateMask, + maxBufferedChunks: opts.maxBufferedChunks, + maxFragments: opts.maxFragments, maxPayload: opts.maxPayload, skipUTF8Validation: opts.skipUTF8Validation }); @@ -9878,9 +10020,9 @@ var require_websocket = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/stream.js var require_stream = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/stream.js"(exports2, module2) { "use strict"; var WebSocket2 = require_websocket(); var { Duplex } = require("stream"); @@ -9940,7 +10082,7 @@ var require_stream = __commonJS({ }; duplex._final = function(callback) { if (ws.readyState === ws.CONNECTING) { - ws.once("open", function open() { + ws.once("open", function open2() { duplex._final(callback); }); return; @@ -9961,7 +10103,7 @@ var require_stream = __commonJS({ }; duplex._write = function(chunk, encoding, callback) { if (ws.readyState === ws.CONNECTING) { - ws.once("open", function open() { + ws.once("open", function open2() { duplex._write(chunk, encoding, callback); }); return; @@ -9976,9 +10118,9 @@ var require_stream = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/subprotocol.js var require_subprotocol = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/subprotocol.js"(exports2, module2) { "use strict"; var { tokenChars } = require_validation2(); function parse4(header) { @@ -10021,17 +10163,17 @@ var require_subprotocol = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket-server.js var require_websocket_server = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) { "use strict"; var EventEmitter = require("events"); var http = require("http"); var { Duplex } = require("stream"); var { createHash } = require("crypto"); - var extension = require_extension(); - var PerMessageDeflate = require_permessage_deflate(); - var subprotocol = require_subprotocol(); + var extension2 = require_extension(); + var PerMessageDeflate2 = require_permessage_deflate(); + var subprotocol2 = require_subprotocol(); var WebSocket2 = require_websocket(); var { CLOSE_TIMEOUT, GUID, kWebSocket } = require_constants(); var keyRegex = /^[+/0-9A-Za-z]{22}==$/; @@ -10057,6 +10199,10 @@ var require_websocket_server = __commonJS({ * called * @param {Function} [options.handleProtocols] A hook to handle protocols * @param {String} [options.host] The hostname where to bind the server + * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of + * buffered data chunks + * @param {Number} [options.maxFragments=131072] The maximum number of message + * fragments * @param {Number} [options.maxPayload=104857600] The maximum allowed message * size * @param {Boolean} [options.noServer=false] Enable no server mode @@ -10078,6 +10224,8 @@ var require_websocket_server = __commonJS({ options2 = { allowSynchronousEvents: true, autoPong: true, + maxBufferedChunks: 1024 * 1024, + maxFragments: 128 * 1024, maxPayload: 100 * 1024 * 1024, skipUTF8Validation: false, perMessageDeflate: false, @@ -10254,7 +10402,7 @@ var require_websocket_server = __commonJS({ let protocols = /* @__PURE__ */ new Set(); if (secWebSocketProtocol !== void 0) { try { - protocols = subprotocol.parse(secWebSocketProtocol); + protocols = subprotocol2.parse(secWebSocketProtocol); } catch (err) { const message = "Invalid Sec-WebSocket-Protocol header"; abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); @@ -10264,16 +10412,16 @@ var require_websocket_server = __commonJS({ const secWebSocketExtensions = req.headers["sec-websocket-extensions"]; const extensions = {}; if (this.options.perMessageDeflate && secWebSocketExtensions !== void 0) { - const perMessageDeflate = new PerMessageDeflate( - this.options.perMessageDeflate, - true, - this.options.maxPayload - ); + const perMessageDeflate = new PerMessageDeflate2({ + ...this.options.perMessageDeflate, + isServer: true, + maxPayload: this.options.maxPayload + }); try { - const offers = extension.parse(secWebSocketExtensions); - if (offers[PerMessageDeflate.extensionName]) { - perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); - extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + const offers = extension2.parse(secWebSocketExtensions); + if (offers[PerMessageDeflate2.extensionName]) { + perMessageDeflate.accept(offers[PerMessageDeflate2.extensionName]); + extensions[PerMessageDeflate2.extensionName] = perMessageDeflate; } } catch (err) { const message = "Invalid or unacceptable Sec-WebSocket-Extensions header"; @@ -10344,10 +10492,10 @@ var require_websocket_server = __commonJS({ ws._protocol = protocol; } } - if (extensions[PerMessageDeflate.extensionName]) { - const params = extensions[PerMessageDeflate.extensionName].params; - const value = extension.format({ - [PerMessageDeflate.extensionName]: [params] + if (extensions[PerMessageDeflate2.extensionName]) { + const params = extensions[PerMessageDeflate2.extensionName].params; + const value = extension2.format({ + [PerMessageDeflate2.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); ws._extensions = extensions; @@ -10357,6 +10505,8 @@ var require_websocket_server = __commonJS({ socket.removeListener("error", socketOnError); ws.setSocket(socket, head, { allowSynchronousEvents: this.options.allowSynchronousEvents, + maxBufferedChunks: this.options.maxBufferedChunks, + maxFragments: this.options.maxFragments, maxPayload: this.options.maxPayload, skipUTF8Validation: this.options.skipUTF8Validation }); @@ -13683,12 +13833,12 @@ var require_stringify = __commonJS({ throw new TypeError('expected "' + language + '.stringify" to be a function'); } data = Object.assign({}, file2.data, data); - const open = opts.delimiters[0]; + const open2 = opts.delimiters[0]; const close = opts.delimiters[1]; const matter2 = engine.stringify(data, options2).trim(); let buf = ""; if (matter2 !== "{}") { - buf = newline(open) + newline(matter2) + newline(close); + buf = newline(open2) + newline(matter2) + newline(close); } if (typeof file2.excerpt === "string" && file2.excerpt !== "") { if (str2.indexOf(file2.excerpt.trim()) === -1) { @@ -13785,7 +13935,7 @@ var require_parse = __commonJS({ var require_gray_matter = __commonJS({ "node_modules/.pnpm/gray-matter@4.0.3/node_modules/gray-matter/index.js"(exports2, module2) { "use strict"; - var fs2 = require("fs"); + var fs = require("fs"); var sections = require_section_matter(); var defaults = require_defaults2(); var stringify = require_stringify(); @@ -13812,18 +13962,18 @@ var require_gray_matter = __commonJS({ } function parseMatter(file2, options2) { const opts = defaults(options2); - const open = opts.delimiters[0]; + const open2 = opts.delimiters[0]; const close = "\n" + opts.delimiters[1]; let str2 = file2.content; if (opts.language) { file2.language = opts.language; } - const openLen = open.length; - if (!utils.startsWith(str2, open, openLen)) { + const openLen = open2.length; + if (!utils.startsWith(str2, open2, openLen)) { excerpt(file2, opts); return file2; } - if (str2.charAt(openLen) === open.slice(-1)) { + if (str2.charAt(openLen) === open2.slice(-1)) { return file2; } str2 = str2.slice(openLen); @@ -13869,7 +14019,7 @@ var require_gray_matter = __commonJS({ return stringify(file2, data, options2); }; matter2.read = function(filepath, options2) { - const str2 = fs2.readFileSync(filepath, "utf8"); + const str2 = fs.readFileSync(filepath, "utf8"); const file2 = matter2(str2, options2); file2.path = filepath; return file2; @@ -13879,9 +14029,9 @@ var require_gray_matter = __commonJS({ }; matter2.language = function(str2, options2) { const opts = defaults(options2); - const open = opts.delimiters[0]; + const open2 = opts.delimiters[0]; if (matter2.test(str2)) { - str2 = str2.slice(open.length); + str2 = str2.slice(open2.length); } const language = str2.slice(0, str2.search(/\r?\n/)); return { @@ -14256,8 +14406,8 @@ function getErrorMap() { // node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/helpers/parseUtil.js var makeIssue = (params) => { - const { data, path: path2, errorMaps, issueData } = params; - const fullPath = [...path2, ...issueData.path || []]; + const { data, path: path4, errorMaps, issueData } = params; + const fullPath = [...path4, ...issueData.path || []]; const fullIssue = { ...issueData, path: fullPath @@ -14372,11 +14522,11 @@ var errorUtil; // node_modules/.pnpm/zod@4.3.6/node_modules/zod/v3/types.js var ParseInputLazyPath = class { - constructor(parent, value, path2, key) { + constructor(parent, value, path4, key) { this._cachedPath = []; this.parent = parent; this.data = value; - this._path = path2; + this._path = path4; this._key = key; } get path() { @@ -18020,10 +18170,10 @@ function mergeDefs(...defs) { function cloneDef(schema) { return mergeDefs(schema._zod.def); } -function getElementAtPath(obj, path2) { - if (!path2) +function getElementAtPath(obj, path4) { + if (!path4) return obj; - return path2.reduce((acc, key) => acc?.[key], obj); + return path4.reduce((acc, key) => acc?.[key], obj); } function promiseAllObject(promisesObj) { const keys = Object.keys(promisesObj); @@ -18406,11 +18556,11 @@ function aborted(x, startIndex = 0) { } return false; } -function prefixIssues(path2, issues) { +function prefixIssues(path4, issues) { return issues.map((iss) => { var _a2; (_a2 = iss).path ?? (_a2.path = []); - iss.path.unshift(path2); + iss.path.unshift(path4); return iss; }); } @@ -27906,60 +28056,84 @@ var StdioServerTransport = class { } }; -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs var import_stream = __toESM(require_stream(), 1); +var import_extension = __toESM(require_extension(), 1); +var import_permessage_deflate = __toESM(require_permessage_deflate(), 1); var import_receiver = __toESM(require_receiver(), 1); var import_sender = __toESM(require_sender(), 1); +var import_subprotocol = __toESM(require_subprotocol(), 1); var import_websocket = __toESM(require_websocket(), 1); var import_websocket_server = __toESM(require_websocket_server(), 1); var wrapper_default = import_websocket.default; +// mcp-server/tool-service.js +var import_node_path4 = __toESM(require("node:path"), 1); + // mcp-server/vault.js -var import_promises = __toESM(require("node:fs/promises"), 1); +var import_promises = require("node:fs/promises"); var import_node_path = __toESM(require("node:path"), 1); var import_gray_matter = __toESM(require_gray_matter(), 1); +var ACTIVE_VAULT_ERROR = "Note path must stay inside the active vault"; async function findMarkdownFiles(dir) { const results = []; - const items = await import_promises.default.readdir(dir, { withFileTypes: true }); - for (const item of items) { - if (item.name.startsWith(".")) continue; - const full = import_node_path.default.join(dir, item.name); - if (item.isDirectory()) { - results.push(...await findMarkdownFiles(full)); - } else if (item.name.endsWith(".md")) { - results.push(full); - } + const items = await (0, import_promises.opendir)(dir); + for await (const item of items) { + await collectMarkdownFile(results, dir, item); } return results; } -async function getNote(vaultPath, notePath) { - const absPath = import_node_path.default.isAbsolute(notePath) ? notePath : import_node_path.default.join(vaultPath, notePath); - const raw = await import_promises.default.readFile(absPath, "utf-8"); - const parsed = (0, import_gray_matter.default)(raw); +async function resolveVaultNotePath(vaultPath, notePath) { + const vaultRoot = await (0, import_promises.realpath)(vaultPath); + const requestedPath = resolveRequestedNotePath(vaultRoot, notePath); + const noteRealPath = await (0, import_promises.realpath)(requestedPath); + const relativePath = import_node_path.default.relative(vaultRoot, noteRealPath); + if (!isVaultRelativePath(relativePath)) { + throw new Error(ACTIVE_VAULT_ERROR); + } return { - path: import_node_path.default.relative(vaultPath, absPath), + vaultRoot, + noteRealPath, + relativePath + }; +} +async function getNote(vaultPath, notePath) { + const { + noteRealPath, + relativePath + } = await resolveVaultNotePath(vaultPath, notePath); + const raw = await readUtf8File(noteRealPath); + const parsed = parseMarkdownNote(raw); + return { + path: relativePath, frontmatter: parsed.data, content: parsed.content.trim() }; } +async function createNote(vaultPath, notePath, content) { + const { requestedPath, relativePath } = await resolveNewVaultNotePath(vaultPath, notePath); + await writeNewUtf8File(requestedPath, content); + return { + path: relativePath, + absolutePath: requestedPath + }; +} async function searchNotes(vaultPath, query, limit = 10) { const files = await findMarkdownFiles(vaultPath); const q = query.toLowerCase(); const results = []; for (const filePath of files) { if (results.length >= limit) break; - const content = await import_promises.default.readFile(filePath, "utf-8"); + const content = await readUtf8File(filePath); const filename = import_node_path.default.basename(filePath, ".md"); const titleMatch = extractTitle(content, filename); - const matches = titleMatch.toLowerCase().includes(q) || content.toLowerCase().includes(q); - if (matches) { - const snippet = extractSnippet(content, q); - results.push({ - path: import_node_path.default.relative(vaultPath, filePath), - title: titleMatch, - snippet - }); - } + if (!matchesSearchQuery(titleMatch, content, q)) continue; + const snippet = extractSnippet(content, q); + results.push({ + path: import_node_path.default.relative(vaultPath, filePath), + title: titleMatch, + snippet + }); } return results; } @@ -27969,39 +28143,264 @@ async function vaultContext(vaultPath) { const foldersSet = /* @__PURE__ */ new Set(); const notesWithMtime = []; for (const filePath of files) { - const raw = await import_promises.default.readFile(filePath, "utf-8"); - const parsed = (0, import_gray_matter.default)(raw); - const type = parsed.data.type || parsed.data.is_a || null; + const { topFolder, note, type } = await readVaultContextNote(vaultPath, filePath); if (type) typesSet.add(type); - const rel = import_node_path.default.relative(vaultPath, filePath); - const topFolder = rel.split(import_node_path.default.sep)[0]; - if (topFolder !== rel) foldersSet.add(topFolder + "/"); - const stat = await import_promises.default.stat(filePath); - notesWithMtime.push({ - path: rel, - title: parsed.data.title || extractTitle(raw, import_node_path.default.basename(filePath, ".md")), - type, - mtime: stat.mtimeMs - }); + if (topFolder) foldersSet.add(topFolder); + notesWithMtime.push(note); } notesWithMtime.sort((a, b) => b.mtime - a.mtime); - const recentNotes = notesWithMtime.slice(0, 20).map(({ mtime: _mtime, ...rest }) => rest); - const configFiles = {}; - try { - const agentsPath = import_node_path.default.join(vaultPath, "config", "agents.md"); - const agentsContent = await import_promises.default.readFile(agentsPath, "utf-8"); - configFiles.agents = agentsContent; - } catch { - } + const recentNotes = notesWithMtime.slice(0, 20).map(contextNoteWithoutMtime); return { types: [...typesSet].sort(), noteCount: files.length, folders: [...foldersSet].sort(), recentNotes, - configFiles, + configFiles: await readConfigFiles(vaultPath), vaultPath }; } +async function collectMarkdownFile(results, dir, item) { + if (item.name.startsWith(".")) return; + const full = resolveInside(dir, item.name); + if (!full) return; + if (item.isDirectory()) { + results.push(...await findMarkdownFiles(full)); + return; + } + if (item.name.endsWith(".md")) { + results.push(full); + } +} +function resolveRequestedNotePath(vaultRoot, notePath) { + if (import_node_path.default.isAbsolute(notePath)) return notePath; + const resolved = resolveInside(vaultRoot, notePath); + if (!resolved) throw new Error(ACTIVE_VAULT_ERROR); + return resolved; +} +async function resolveNewVaultNotePath(vaultPath, notePath) { + const requestedNotePath = validateNewNotePath(notePath); + const vaultRoot = await (0, import_promises.realpath)(vaultPath); + const requestedPath = resolveRequestedNotePath(vaultRoot, requestedNotePath); + const relativePath = relativeNotePathInsideVault(vaultRoot, requestedPath); + await ensureWritableParentInsideVault(vaultRoot, requestedPath); + return { requestedPath, relativePath }; +} +function validateNewNotePath(notePath) { + const trimmedPath = typeof notePath === "string" ? notePath.trim() : ""; + if (!trimmedPath) { + throw new Error("Note path is required"); + } + if (!trimmedPath.endsWith(".md")) { + throw new Error("New notes must be markdown files ending in .md"); + } + return trimmedPath; +} +async function ensureWritableParentInsideVault(vaultRoot, requestedPath) { + const parentPath = import_node_path.default.dirname(requestedPath); + const existingAncestor = await nearestExistingAncestor(parentPath); + assertInsideVault(vaultRoot, existingAncestor); + await (0, import_promises.mkdir)(parentPath, { recursive: true }); + assertInsideVault(vaultRoot, await (0, import_promises.realpath)(parentPath)); +} +async function nearestExistingAncestor(targetPath) { + let currentPath = targetPath; + while (currentPath && currentPath !== import_node_path.default.dirname(currentPath)) { + try { + return await (0, import_promises.realpath)(currentPath); + } catch (error2) { + if (error2?.code !== "ENOENT") throw error2; + currentPath = import_node_path.default.dirname(currentPath); + } + } + return (0, import_promises.realpath)(currentPath); +} +function assertInsideVault(vaultRoot, targetPath) { + if (!isVaultRelativePath(import_node_path.default.relative(vaultRoot, targetPath))) { + throw new Error(ACTIVE_VAULT_ERROR); + } +} +function relativeNotePathInsideVault(vaultRoot, requestedPath) { + const relativePath = import_node_path.default.relative(vaultRoot, requestedPath); + if (!isVaultRelativePath(relativePath) || !relativePath) { + throw new Error(ACTIVE_VAULT_ERROR); + } + return relativePath; +} +function resolveInside(root, target) { + const resolved = import_node_path.default.resolve(root, target); + const relative = import_node_path.default.relative(root, resolved); + if (isVaultRelativePath(relative)) return resolved; + return null; +} +function isVaultRelativePath(relativePath) { + return !relativePath.startsWith("..") && !import_node_path.default.isAbsolute(relativePath); +} +function matchesSearchQuery(title, content, query) { + return title.toLowerCase().includes(query) || content.toLowerCase().includes(query); +} +function contextNoteWithoutMtime(note) { + return { + path: note.path, + title: note.title, + type: note.type + }; +} +async function readVaultContextNote(vaultPath, filePath) { + const raw = await readUtf8File(filePath); + const parsed = parseMarkdownNote(raw); + const rel = import_node_path.default.relative(vaultPath, filePath); + const topFolder = extractTopFolder(rel); + const stat = await statFile(filePath); + const type = parsed.data.type || parsed.data.is_a || null; + return { + topFolder, + type, + note: { + path: rel, + title: parsed.data.title || extractTitle(raw, import_node_path.default.basename(filePath, ".md")), + type, + mtime: stat.mtimeMs + } + }; +} +function parseMarkdownNote(raw) { + try { + const parsed = (0, import_gray_matter.default)(raw); + const fallback = parseFrontmatterFallback(raw); + return shouldUseFallbackFrontmatter(parsed, fallback) ? fallback : parsed; + } catch { + return parseFrontmatterFallback(raw); + } +} +function shouldUseFallbackFrontmatter(parsed, fallback) { + return Object.keys(parsed.data).length === 0 && Object.keys(fallback.data).length > 0; +} +function parseFrontmatterFallback(raw) { + const split = splitFrontmatter(raw); + if (!split) return { data: {}, content: raw }; + return { + data: parseFrontmatterBlock(split.frontmatter), + content: split.content + }; +} +function splitFrontmatter(raw) { + const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)([\s\S]*)$/); + if (!match) return null; + return { frontmatter: match[1], content: match[2] }; +} +function parseFrontmatterBlock(frontmatter) { + const data = {}; + let listKey = null; + for (const line of frontmatter.split(/\r?\n/)) { + const item = parseYamlListItem(line); + if (listKey && item !== null) { + data[listKey].push(parseYamlScalar(item)); + continue; + } + listKey = null; + const field = parseTopLevelYamlField(line); + if (!field) continue; + data[field.key] = field.value ? parseYamlValue(field.value) : []; + listKey = field.value ? null : field.key; + } + return data; +} +function parseTopLevelYamlField(line) { + if (!line || line.trimStart() !== line || line.trimStart().startsWith("#")) return null; + const separatorIndex = line.indexOf(":"); + if (separatorIndex <= 0) return null; + return { + key: stripMatchingQuotes(line.slice(0, separatorIndex).trim()), + value: line.slice(separatorIndex + 1).trim() + }; +} +function parseYamlValue(value) { + if (value.startsWith("[") && value.endsWith("]")) { + return splitInlineYamlArray(value).map(parseYamlScalar); + } + return parseYamlScalar(value); +} +function splitInlineYamlArray(value) { + const inner = value.slice(1, -1); + const items = []; + let current = ""; + let quote = null; + for (const char of inner) { + if (quote) { + current += char; + if (char === quote) quote = null; + continue; + } + if (char === '"' || char === "'") { + quote = char; + current += char; + continue; + } + if (char === ",") { + items.push(current.trim()); + current = ""; + continue; + } + current += char; + } + if (current.trim()) items.push(current.trim()); + return items; +} +function parseYamlListItem(line) { + const match = line.match(/^\s+-\s*(.*)$/); + return match ? match[1].trim() : null; +} +function parseYamlScalar(value) { + const unquoted = stripMatchingQuotes(value.trim()); + if (unquoted !== value.trim()) return unquoted; + if (/^(true|yes)$/i.test(unquoted)) return true; + if (/^(false|no)$/i.test(unquoted)) return false; + if (/^(null|~)$/i.test(unquoted)) return null; + if (/^-?\d+(\.\d+)?$/.test(unquoted)) return Number(unquoted); + return unquoted; +} +function stripMatchingQuotes(value) { + const first = value[0]; + const last = value[value.length - 1]; + return (first === '"' || first === "'") && first === last ? value.slice(1, -1) : value; +} +function extractTopFolder(relativePath) { + const topFolder = relativePath.split(import_node_path.default.sep)[0]; + return topFolder === relativePath ? null : `${topFolder}/`; +} +async function readConfigFiles(vaultPath) { + const configFiles = {}; + try { + const agentsPath = resolveInside(vaultPath, "config/agents.md"); + if (agentsPath) configFiles.agents = await readUtf8File(agentsPath); + } catch { + } + return configFiles; +} +async function readUtf8File(filePath) { + const handle = await (0, import_promises.open)(filePath, "r"); + try { + return await handle.readFile("utf-8"); + } finally { + await handle.close(); + } +} +async function writeNewUtf8File(filePath, content) { + const handle = await (0, import_promises.open)(filePath, "wx"); + try { + await handle.writeFile(content, "utf-8"); + } finally { + await handle.close(); + } +} +async function statFile(filePath) { + const handle = await (0, import_promises.open)(filePath, "r"); + try { + return await handle.stat(); + } finally { + await handle.close(); + } +} function extractTitle(content, fallback) { const h1Match = content.match(/^#\s+(.+)$/m); if (h1Match) return h1Match[1].trim(); @@ -28018,38 +28417,409 @@ function extractSnippet(content, query) { return (start > 0 ? "..." : "") + body.slice(start, end) + (end < body.length ? "..." : ""); } +// mcp-server/vault-path.js +var import_node_fs = require("node:fs"); +var import_node_os = require("node:os"); +var import_node_path2 = require("node:path"); + +// mcp-server/app-config-policy.json +var app_config_policy_default = { + current_namespace: "com.tolaria.app", + legacy_namespace: "com.laputa.app", + namespace_read_order: ["current", "legacy"], + files: { + settings: "settings.json", + vaults: "vaults.json", + last_vault: "last-vault.txt", + ai_workspace_sessions: "ai-workspace-sessions.json", + window_state: "window-state.json", + ai_provider_secrets: "ai-provider-secrets.json" + }, + read_order: [ + "preferred config root/current namespace", + "preferred config root/legacy namespace", + "platform config root/current namespace when different", + "platform config root/legacy namespace when different" + ], + write_target: "preferred config root/current namespace" +}; + +// mcp-server/vault-path.js +var APP_CONFIG_DIR = app_config_policy_default.current_namespace; +var APP_CONFIG_FILES = Object.freeze(app_config_policy_default.files); +function parseVaultPathList(rawValue) { + if (!rawValue?.trim()) return []; + try { + const parsed = JSON.parse(rawValue); + if (Array.isArray(parsed)) return parsed.filter((value) => typeof value === "string"); + } catch { + } + return []; +} +function uniqueVaultPaths(paths) { + const seen = /* @__PURE__ */ new Set(); + const unique = []; + for (const path4 of paths) { + const trimmed = path4.trim(); + if (!trimmed || seen.has(trimmed)) continue; + seen.add(trimmed); + unique.push(trimmed); + } + return unique; +} +function absolutePath(path4) { + return typeof path4 === "string" && (0, import_node_path2.isAbsolute)(path4) ? path4 : null; +} +function defaultXdgConfigHome(platformName, homeDir) { + if (platformName === "win32") return null; + return absolutePath(homeDir) ? (0, import_node_path2.join)(homeDir, ".config") : null; +} +function platformConfigDir(env, platformName, homeDir) { + if (platformName === "darwin") return (0, import_node_path2.join)(homeDir, "Library", "Application Support"); + if (platformName === "win32") return absolutePath(env.APPDATA) || (0, import_node_path2.join)(homeDir, "AppData", "Roaming"); + return absolutePath(env.XDG_CONFIG_HOME) || defaultXdgConfigHome(platformName, homeDir); +} +function appConfigBaseDirs({ + env = process.env, + homeDir = (0, import_node_os.homedir)(), + platformName = (0, import_node_os.platform)(), + platformDir = platformConfigDir(env, platformName, homeDir) +} = {}) { + const primary = absolutePath(env.XDG_CONFIG_HOME) || defaultXdgConfigHome(platformName, homeDir) || platformDir; + const dirs = primary ? [primary] : []; + if (platformDir && platformDir !== primary) dirs.push(platformDir); + return dirs; +} +function namespaceDir(namespace) { + if (namespace === "current") return APP_CONFIG_DIR; + if (namespace === "legacy") return app_config_policy_default.legacy_namespace; + throw new Error(`Unknown app config namespace: ${namespace}`); +} +function preferredAppConfigPath(configDir, fileName) { + return (0, import_node_path2.join)(configDir, APP_CONFIG_DIR, fileName); +} +function existingOrPreferredAppConfigPath(configDirs, fileName) { + for (const configDir of configDirs) { + for (const namespace of app_config_policy_default.namespace_read_order) { + const candidate = (0, import_node_path2.join)(configDir, namespaceDir(namespace), fileName); + if ((0, import_node_fs.existsSync)(candidate)) return candidate; + } + } + return preferredAppConfigPath(configDirs[0], fileName); +} +function vaultsJsonPath({ + configDir, + configDirs = configDir ? [configDir] : appConfigBaseDirs() +} = {}) { + return existingOrPreferredAppConfigPath(configDirs, APP_CONFIG_FILES.vaults); +} +function pushUniquePath(paths, value) { + const path4 = typeof value === "string" ? value.trim() : ""; + if (!path4 || paths.includes(path4)) return; + paths.push(path4); +} +function activeVaultPathsFromList(list) { + const paths = []; + pushUniquePath(paths, list?.active_vault); + for (const vault of list?.vaults ?? []) { + if (vault?.mounted === false) continue; + pushUniquePath(paths, vault?.path); + } + return paths; +} +function configuredVaultPaths(options2 = {}) { + const filePath = vaultsJsonPath(options2); + if (!(0, import_node_fs.existsSync)(filePath)) return []; + return activeVaultPathsFromList(JSON.parse((0, import_node_fs.readFileSync)(filePath, "utf-8"))); +} +function requireVaultPaths(env = process.env, options2 = {}) { + const vaultPaths = uniqueVaultPaths([ + env.VAULT_PATH?.trim() ?? "", + ...parseVaultPathList(env.VAULT_PATHS) + ]); + if (vaultPaths.length === 0) { + const configuredPaths = configuredVaultPaths(options2); + if (configuredPaths.length > 0) return configuredPaths; + throw new Error("VAULT_PATH is required. Open a vault in Tolaria before starting MCP tools."); + } + return vaultPaths; +} + +// mcp-server/agent-instructions.js +var import_promises2 = require("node:fs/promises"); +var import_node_path3 = __toESM(require("node:path"), 1); +async function readAgentInstructions(vaultPath) { + const instructionsPath = import_node_path3.default.join(vaultPath, "AGENTS.md"); + try { + return { + path: instructionsPath, + content: await (0, import_promises2.readFile)(instructionsPath, "utf8") + }; + } catch (error2) { + if (error2?.code === "ENOENT") return null; + throw error2; + } +} +async function vaultContextWithInstructions(vaultPath) { + return { + ...await vaultContext(vaultPath), + agentInstructions: await readAgentInstructions(vaultPath) + }; +} + +// mcp-server/tool-service.js +function createMcpToolService({ + resolveVaultPaths = () => requireVaultPaths(), + emitUiAction = () => { + } +} = {}) { + function activeVaultPaths() { + return resolveVaultPaths(); + } + function requestedVaultPath(args = {}) { + const requested = typeof args.vaultPath === "string" ? args.vaultPath.trim() : ""; + if (!requested) return null; + if (!activeVaultPaths().includes(requested)) { + throw new Error(`Vault is not active in Tolaria: ${requested}`); + } + return requested; + } + function resolveUiPath(args = {}) { + const notePath = typeof args.path === "string" ? args.path : ""; + if (import_node_path4.default.isAbsolute(notePath)) return notePath; + const roots = activeVaultPaths(); + const vaultPath = requestedVaultPath(args) ?? (roots.length === 1 ? roots[0] : ""); + return vaultPath ? import_node_path4.default.join(vaultPath, notePath) : notePath; + } + async function readNote(args = {}) { + return getNoteFromActiveVaults(notePathArg(args), requestedVaultPath(args)); + } + async function searchNotes2(args = {}) { + const requestedLimit = Number.isFinite(args.limit) && args.limit > 0 ? args.limit : 10; + const results = []; + for (const vaultPath of activeVaultPaths()) { + const vaultResults = await searchNotes(vaultPath, args.query, requestedLimit); + results.push(...vaultResults.map((result) => withVaultMetadata(result, vaultPath))); + if (results.length >= requestedLimit) break; + } + return results.slice(0, requestedLimit); + } + async function vaultContext2(args = {}) { + const targetVaultPath = requestedVaultPath(args); + const roots = activeVaultPaths(); + if (targetVaultPath) return vaultContextWithInstructions(targetVaultPath); + if (roots.length === 1) return vaultContextWithInstructions(roots[0]); + return { + vaults: await Promise.all(roots.map(vaultContextWithInstructions)) + }; + } + async function listVaults() { + const vaults = await Promise.all(activeVaultPaths().map(async (vaultPath) => { + const agentInstructions = await readAgentInstructions(vaultPath); + return { + path: vaultPath, + label: vaultLabel(vaultPath), + agentInstructionsPath: agentInstructions?.path ?? null, + hasAgentInstructions: agentInstructions !== null + }; + })); + return { vaults }; + } + async function createNote2(args = {}) { + const vaultPath = writableVaultPath(args); + const notePath = writableNotePath(args, vaultPath); + const note = await createNote(vaultPath, notePath, createNoteContent(args)); + const targetPath = resolveUiPath({ ...args, path: note.path, vaultPath }); + emitUiAction("vault_changed", { path: targetPath }); + emitUiAction("open_tab", { path: targetPath }); + return { path: note.path, absolutePath: note.absolutePath, vaultPath }; + } + function openNoteAsTab(args = {}) { + const targetPath = resolveUiPath(args); + emitUiAction("vault_changed", { path: targetPath }); + emitUiAction("open_tab", { path: targetPath }); + return { targetPath }; + } + function openNoteInEditor(args = {}) { + const targetPath = resolveUiPath(args); + emitUiAction("vault_changed", { path: targetPath }); + emitUiAction("open_note", { path: targetPath }); + return { targetPath }; + } + function highlightEditor(args = {}) { + emitUiAction("highlight", { element: args.element, path: args.path }); + } + function setFilter(args = {}) { + emitUiAction("set_filter", { filterType: args.type }); + } + function refreshVault(args = {}) { + emitUiAction("vault_changed", { path: resolveUiPath(args) }); + } + async function getNoteFromActiveVaults(notePath, vaultPath = null) { + const candidates = vaultPath ? [vaultPath] : activeVaultPaths(); + const matches = []; + const errors = []; + for (const candidate of candidates) { + try { + matches.push(withVaultMetadata(await getNote(candidate, notePath), candidate)); + } catch (error2) { + errors.push(error2); + } + } + if (matches.length === 1) return matches[0]; + if (matches.length > 1) { + throw new Error(`Note path is ambiguous across active vaults. Pass vaultPath for ${notePath}.`); + } + throw errors[0] ?? new Error(`Note not found: ${notePath}`); + } + function writableVaultPath(args = {}) { + const requested = requestedVaultPath(args); + if (requested) return requested; + const roots = activeVaultPaths(); + const notePath = notePathArg(args); + if (import_node_path4.default.isAbsolute(notePath)) { + const root = roots.find((vaultPath) => isInsideVaultRoot(vaultPath, notePath)); + if (root) return root; + } + if (roots.length === 1) return roots[0]; + throw new Error(`Note path is ambiguous across active vaults. Pass vaultPath for ${notePath}.`); + } + return { + activeVaultPaths, + createNote: createNote2, + highlightEditor, + listVaults, + openNoteAsTab, + openNoteInEditor, + readNote, + refreshVault, + requestedVaultPath, + resolveUiPath, + searchNotes: searchNotes2, + setFilter, + vaultContext: vaultContext2 + }; +} +function writableNotePath(args, vaultPath) { + const notePath = notePathArg(args); + if (!import_node_path4.default.isAbsolute(notePath) || !isInsideVaultRoot(vaultPath, notePath)) return notePath; + return import_node_path4.default.relative(vaultPath, notePath); +} +function withVaultMetadata(note, vaultPath) { + return { + ...note, + vaultPath, + vaultLabel: vaultLabel(vaultPath) + }; +} +function vaultLabel(vaultPath) { + return import_node_path4.default.basename(vaultPath) || vaultPath; +} +function isInsideVaultRoot(vaultPath, notePath) { + const relative = import_node_path4.default.relative(vaultPath, notePath); + return Boolean(relative) && !relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative); +} +function notePathArg(args = {}) { + const notePath = typeof args.path === "string" ? args.path.trim() : ""; + if (!notePath) throw new Error("Note path is required"); + return notePath; +} +function yamlScalar(value) { + return JSON.stringify(value); +} +function fallbackCreateNoteContent(args = {}) { + const title = typeof args.title === "string" && args.title.trim() ? args.title.trim() : import_node_path4.default.basename(notePathArg(args), ".md"); + const type = typeof args.type === "string" && args.type.trim() ? args.type.trim() : typeof args.is_a === "string" && args.is_a.trim() ? args.is_a.trim() : "Note"; + return `--- +type: ${yamlScalar(type)} +--- + +# ${title} +`; +} +function createNoteContent(args = {}) { + return typeof args.content === "string" && args.content.trim() ? args.content : fallbackCreateNoteContent(args); +} + // mcp-server/index.js -var VAULT_PATH = process.env.VAULT_PATH || process.env.HOME + "/Laputa"; var WS_UI_PORT = parseInt(process.env.WS_UI_PORT || "9711", 10); var WS_UI_URL = `ws://localhost:${WS_UI_PORT}`; +var LOCAL_READ_ONLY_TOOL_ANNOTATIONS = Object.freeze({ + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false +}); +var LOCAL_CREATE_TOOL_ANNOTATIONS = Object.freeze({ + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: false +}); var uiSocket = null; +var reconnectTimer = null; +var shutdownStarted = false; var RECONNECT_INTERVAL_MS = 3e3; function connectUiBridge() { + if (shutdownStarted) return; try { const ws = new wrapper_default(WS_UI_URL); + uiSocket = ws; ws.on("open", () => { - uiSocket = ws; + if (shutdownStarted) { + closeUiSocket(); + return; + } console.error(`[mcp] Connected to UI bridge at ${WS_UI_URL}`); }); ws.on("close", () => { - uiSocket = null; - setTimeout(connectUiBridge, RECONNECT_INTERVAL_MS); + if (uiSocket === ws) uiSocket = null; + scheduleUiReconnect(); }); ws.on("error", () => { }); } catch { - setTimeout(connectUiBridge, RECONNECT_INTERVAL_MS); + scheduleUiReconnect(); } } -connectUiBridge(); +function scheduleUiReconnect() { + if (shutdownStarted) return; + clearUiReconnectTimer(); + reconnectTimer = setTimeout(connectUiBridge, RECONNECT_INTERVAL_MS); + reconnectTimer.unref?.(); +} +function clearUiReconnectTimer() { + if (!reconnectTimer) return; + clearTimeout(reconnectTimer); + reconnectTimer = null; +} +function closeUiSocket() { + const socket = uiSocket; + uiSocket = null; + if (!socket) return; + socket.removeAllListeners(); + socket.on("error", () => { + }); + if (socket.readyState === wrapper_default.CONNECTING) { + socket.terminate?.(); + return; + } + try { + socket.close(); + } catch { + } + socket.terminate?.(); +} function broadcastUiAction(action, payload) { if (!uiSocket || uiSocket.readyState !== wrapper_default.OPEN) return; uiSocket.send(JSON.stringify({ type: "ui_action", action, ...payload })); } +var toolService = createMcpToolService({ emitUiAction: broadcastUiAction }); var TOOLS = [ { name: "search_notes", description: "Full-text search across vault notes by title or content. Returns matching paths, titles, and snippets.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, inputSchema: { type: "object", properties: { @@ -28061,16 +28831,50 @@ var TOOLS = [ }, { name: "get_vault_context", - description: "Get vault orientation: entity types, total note count, top-level folders, and 20 most recently modified notes.", - inputSchema: { type: "object", properties: {} } + description: "Get vault orientation for the active Tolaria vaults: entity types, AGENTS.md instructions, note count, folders, and recent notes.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, + inputSchema: { + type: "object", + properties: { + vaultPath: { type: "string", description: "Optional target vault root. Omit to inspect all active vaults." } + } + } + }, + { + name: "list_vaults", + description: "List the current active Tolaria vaults available to MCP tools, including whether each vault has AGENTS.md instructions.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, + inputSchema: { + type: "object", + properties: {} + } }, { name: "get_note", description: "Read a note with parsed YAML frontmatter and markdown content. Returns {path, frontmatter, content}.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, inputSchema: { type: "object", properties: { - path: { type: "string", description: 'Relative path to the note (e.g. "project/my-project.md")' } + path: { type: "string", description: 'Relative path to the note (e.g. "project/my-project.md")' }, + vaultPath: { type: "string", description: "Optional target vault root when multiple vaults are active." } + }, + required: ["path"] + } + }, + { + name: "create_note", + description: "Create a new markdown note inside an active Tolaria vault. Does not overwrite existing files. Use content for the full markdown including YAML frontmatter and H1.", + annotations: LOCAL_CREATE_TOOL_ANNOTATIONS, + inputSchema: { + type: "object", + properties: { + path: { type: "string", description: "Relative path inside the vault, or an absolute path inside an active vault. Must end in .md." }, + content: { type: "string", description: "Full markdown note content, including YAML frontmatter when needed." }, + title: { type: "string", description: "Optional title used only when content is omitted." }, + type: { type: "string", description: "Optional note type used only when content is omitted." }, + is_a: { type: "string", description: "Legacy alias for type, used only when content is omitted." }, + vaultPath: { type: "string", description: "Optional target vault root when multiple vaults are active." } }, required: ["path"] } @@ -28078,10 +28882,12 @@ var TOOLS = [ { name: "open_note", description: "Open a note in the Tolaria UI as a new tab. Use after creating or editing a note so the user can see it.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, inputSchema: { type: "object", properties: { - path: { type: "string", description: "Relative path to the note" } + path: { type: "string", description: "Relative path to the note" }, + vaultPath: { type: "string", description: "Optional target vault root when opening a note outside the default vault." } }, required: ["path"] } @@ -28089,6 +28895,7 @@ var TOOLS = [ { name: "highlight_editor", description: "Visually highlight a UI element in Tolaria (editor, tab, properties panel, or note list). The highlight auto-clears after a short delay.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, inputSchema: { type: "object", properties: { @@ -28101,49 +28908,69 @@ var TOOLS = [ { name: "refresh_vault", description: "Trigger a vault rescan so new or modified files appear immediately in the Tolaria note list.", + annotations: LOCAL_READ_ONLY_TOOL_ANNOTATIONS, inputSchema: { type: "object", properties: { - path: { type: "string", description: "Optional specific note path that changed" } + path: { type: "string", description: "Optional specific note path that changed" }, + vaultPath: { type: "string", description: "Optional target vault root when refreshing a note outside the default vault." } } } } ]; -var TOOL_HANDLERS = { - search_notes: handleSearchNotes, - get_vault_context: handleVaultContext, - get_note: handleGetNote, - open_note: handleOpenNote, - highlight_editor: handleHighlightEditor, - refresh_vault: handleRefreshVault -}; async function handleSearchNotes(args) { - const results = await searchNotes(VAULT_PATH, args.query, args.limit); - const text = results.length === 0 ? "No matching notes found." : results.map((r) => `**${r.title}** (${r.path}) + const results = await toolService.searchNotes(args); + const text = results.length === 0 ? "No matching notes found." : results.map((r) => `**${r.title}** (${r.vaultLabel} / ${r.path}) ${r.snippet}`).join("\n\n"); return { content: [{ type: "text", text }] }; } -async function handleVaultContext() { - const ctx = await vaultContext(VAULT_PATH); +async function handleVaultContext(args = {}) { + const ctx = await toolService.vaultContext(args); return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] }; } +async function handleListVaults() { + return { content: [{ type: "text", text: JSON.stringify(await toolService.listVaults(), null, 2) }] }; +} async function handleGetNote(args) { - const note = await getNote(VAULT_PATH, args.path); + const note = await toolService.readNote(args); return { content: [{ type: "text", text: JSON.stringify(note, null, 2) }] }; } +async function handleCreateNote(args = {}) { + const note = await toolService.createNote(args); + return { + content: [{ + type: "text", + text: JSON.stringify(note, null, 2) + }] + }; +} function handleOpenNote(args) { - broadcastUiAction("vault_changed", { path: args.path }); - broadcastUiAction("open_tab", { path: args.path }); - return { content: [{ type: "text", text: `Opening ${args.path} in Tolaria` }] }; + const { targetPath } = toolService.openNoteAsTab(args); + return { content: [{ type: "text", text: `Opening ${targetPath} in Tolaria` }] }; } function handleHighlightEditor(args) { - broadcastUiAction("highlight", { element: args.element, path: args.path }); + toolService.highlightEditor(args); return { content: [{ type: "text", text: `Highlighting ${args.element}` }] }; } function handleRefreshVault(args) { - broadcastUiAction("vault_changed", { path: args?.path }); + toolService.refreshVault(args); return { content: [{ type: "text", text: "Vault refresh triggered" }] }; } +var TOOL_HANDLERS = /* @__PURE__ */ new Map([ + ["search_notes", handleSearchNotes], + ["get_vault_context", handleVaultContext], + ["list_vaults", handleListVaults], + ["get_note", handleGetNote], + ["create_note", handleCreateNote], + ["open_note", handleOpenNote], + ["highlight_editor", handleHighlightEditor], + ["refresh_vault", handleRefreshVault] +]); +function callToolHandler(name, args) { + const handler = TOOL_HANDLERS.get(name); + if (!handler) throw new Error(`Unknown tool: ${name}`); + return handler(args); +} var server = new Server( { name: "tolaria-mcp-server", version: "0.3.0" }, { capabilities: { tools: {} } } @@ -28153,12 +28980,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const handler = TOOL_HANDLERS[name]; - if (!handler) { - throw new Error(`Unknown tool: ${name}`); - } try { - return await handler(args); + return await callToolHandler(name, args); } catch (error2) { return { content: [{ type: "text", text: `Error: ${error2.message}` }], @@ -28166,12 +28989,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } }); +async function shutdown(exitCode = 0) { + if (shutdownStarted) return; + shutdownStarted = true; + clearUiReconnectTimer(); + closeUiSocket(); + try { + await server.close(); + } catch (error2) { + console.error(`[mcp] Error while closing server: ${error2.message}`); + } + process.exitCode = exitCode; + setImmediate(() => process.exit(exitCode)); +} async function main() { const transport = new StdioServerTransport(); + server.onclose = () => { + void shutdown(0); + }; + process.stdin.once("end", () => { + void shutdown(0); + }); + process.stdin.once("close", () => { + void shutdown(0); + }); + process.once("SIGINT", () => { + void shutdown(0); + }); + process.once("SIGTERM", () => { + void shutdown(0); + }); + connectUiBridge(); await server.connect(transport); - console.error(`Tolaria MCP server running (vault: ${VAULT_PATH})`); + console.error("Tolaria MCP server running (vaults resolved per call)"); } -main().catch(console.error); +main().catch((error2) => { + console.error(error2); + void shutdown(1); +}); /*! Bundled license information: is-extendable/index.js: diff --git a/src-tauri/gen/apple/assets/mcp-server/ws-bridge.js b/src-tauri/gen/apple/assets/mcp-server/ws-bridge.js index ff316ca..ba9dab6 100755 --- a/src-tauri/gen/apple/assets/mcp-server/ws-bridge.js +++ b/src-tauri/gen/apple/assets/mcp-server/ws-bridge.js @@ -30,9 +30,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/constants.js var require_constants = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/constants.js"(exports2, module2) { "use strict"; var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"]; var hasBlob = typeof Blob !== "undefined"; @@ -53,9 +53,9 @@ var require_constants = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/buffer-util.js var require_buffer_util = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/buffer-util.js"(exports2, module2) { "use strict"; var { EMPTY_BUFFER } = require_constants(); var FastBuffer = Buffer[Symbol.species]; @@ -128,9 +128,9 @@ var require_buffer_util = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/limiter.js var require_limiter = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/limiter.js"(exports2, module2) { "use strict"; var kDone = /* @__PURE__ */ Symbol("kDone"); var kRun = /* @__PURE__ */ Symbol("kRun"); @@ -178,9 +178,9 @@ var require_limiter = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/permessage-deflate.js var require_permessage_deflate = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/permessage-deflate.js"(exports2, module2) { "use strict"; var zlib = require("zlib"); var bufferUtil = require_buffer_util(); @@ -194,7 +194,7 @@ var require_permessage_deflate = __commonJS({ var kBuffers = /* @__PURE__ */ Symbol("buffers"); var kError = /* @__PURE__ */ Symbol("error"); var zlibLimiter; - var PerMessageDeflate = class { + var PerMessageDeflate2 = class { /** * Creates a PerMessageDeflate instance. * @@ -205,6 +205,9 @@ var require_permessage_deflate = __commonJS({ * acknowledge disabling of client context takeover * @param {Number} [options.concurrencyLimit=10] The number of concurrent * calls to zlib + * @param {Boolean} [options.isServer=false] Create the instance in either + * server or client mode + * @param {Number} [options.maxPayload=0] The maximum allowed message length * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the * use of a custom server window size * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept @@ -215,15 +218,12 @@ var require_permessage_deflate = __commonJS({ * deflate * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on * inflate - * @param {Boolean} [isServer=false] Create the instance in either server or - * client mode - * @param {Number} [maxPayload=0] The maximum allowed message length */ - constructor(options2, isServer, maxPayload) { - this._maxPayload = maxPayload | 0; + constructor(options2) { this._options = options2 || {}; this._threshold = this._options.threshold !== void 0 ? this._options.threshold : 1024; - this._isServer = !!isServer; + this._maxPayload = this._options.maxPayload | 0; + this._isServer = !!this._options.isServer; this._deflate = null; this._inflate = null; this.params = null; @@ -532,7 +532,7 @@ var require_permessage_deflate = __commonJS({ }); } }; - module2.exports = PerMessageDeflate; + module2.exports = PerMessageDeflate2; function deflateOnData(chunk) { this[kBuffers].push(chunk); this[kTotalLength] += chunk.length; @@ -561,9 +561,9 @@ var require_permessage_deflate = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/validation.js var require_validation = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/validation.js"(exports2, module2) { "use strict"; var { isUtf8 } = require("buffer"); var { hasBlob } = require_constants(); @@ -762,12 +762,12 @@ var require_validation = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/receiver.js var require_receiver = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/receiver.js"(exports2, module2) { "use strict"; var { Writable } = require("stream"); - var PerMessageDeflate = require_permessage_deflate(); + var PerMessageDeflate2 = require_permessage_deflate(); var { BINARY_TYPES, EMPTY_BUFFER, @@ -797,6 +797,10 @@ var require_receiver = __commonJS({ * extensions * @param {Boolean} [options.isServer=false] Specifies whether to operate in * client or server mode + * @param {Number} [options.maxBufferedChunks=0] The maximum number of + * buffered data chunks + * @param {Number} [options.maxFragments=0] The maximum number of message + * fragments * @param {Number} [options.maxPayload=0] The maximum allowed message length * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or * not to skip UTF-8 validation for text and close messages @@ -807,6 +811,8 @@ var require_receiver = __commonJS({ this._binaryType = options2.binaryType || BINARY_TYPES[0]; this._extensions = options2.extensions || {}; this._isServer = !!options2.isServer; + this._maxBufferedChunks = options2.maxBufferedChunks | 0; + this._maxFragments = options2.maxFragments | 0; this._maxPayload = options2.maxPayload | 0; this._skipUTF8Validation = !!options2.skipUTF8Validation; this[kWebSocket] = void 0; @@ -836,6 +842,18 @@ var require_receiver = __commonJS({ */ _write(chunk, encoding, cb) { if (this._opcode === 8 && this._state == GET_INFO) return cb(); + if (this._maxBufferedChunks > 0 && this._buffers.length >= this._maxBufferedChunks) { + cb( + this.createError( + RangeError, + "Too many buffered chunks", + false, + 1008, + "WS_ERR_TOO_MANY_BUFFERED_PARTS" + ) + ); + return; + } this._bufferedBytes += chunk.length; this._buffers.push(chunk); this.startLoop(cb); @@ -934,7 +952,7 @@ var require_receiver = __commonJS({ return; } const compressed = (buf[0] & 64) === 64; - if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { + if (compressed && !this._extensions[PerMessageDeflate2.extensionName]) { const error = this.createError( RangeError, "RSV1 must be clear", @@ -1165,6 +1183,17 @@ var require_receiver = __commonJS({ return; } if (data.length) { + if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) { + const error = this.createError( + RangeError, + "Too many message fragments", + false, + 1008, + "WS_ERR_TOO_MANY_BUFFERED_PARTS" + ); + cb(error); + return; + } this._messageLength = this._totalPayloadLength; this._fragments.push(data); } @@ -1178,7 +1207,7 @@ var require_receiver = __commonJS({ * @private */ decompress(data, cb) { - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName]; perMessageDeflate.decompress(data, this._fin, (err, buf) => { if (err) return cb(err); if (buf.length) { @@ -1194,6 +1223,17 @@ var require_receiver = __commonJS({ cb(error); return; } + if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) { + const error = this.createError( + RangeError, + "Too many message fragments", + false, + 1008, + "WS_ERR_TOO_MANY_BUFFERED_PARTS" + ); + cb(error); + return; + } this._fragments.push(buf); } this.dataMessage(cb); @@ -1354,13 +1394,16 @@ var require_receiver = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/sender.js var require_sender = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/sender.js"(exports2, module2) { "use strict"; var { Duplex } = require("stream"); var { randomFillSync } = require("crypto"); - var PerMessageDeflate = require_permessage_deflate(); + var { + types: { isUint8Array } + } = require("util"); + var PerMessageDeflate2 = require_permessage_deflate(); var { EMPTY_BUFFER, kWebSocket, NOOP } = require_constants(); var { isBlob, isValidStatusCode } = require_validation(); var { mask: applyMask, toBuffer } = require_buffer_util(); @@ -1513,8 +1556,10 @@ var require_sender = __commonJS({ buf.writeUInt16BE(code, 0); if (typeof data === "string") { buf.write(data, 2); - } else { + } else if (isUint8Array(data)) { buf.set(data, 2); + } else { + throw new TypeError("Second argument must be a string or a Uint8Array"); } } const options2 = { @@ -1644,7 +1689,7 @@ var require_sender = __commonJS({ * @public */ send(data, options2, cb) { - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName]; let opcode = options2.binary ? 2 : 1; let rsv1 = options2.compress; let byteLength; @@ -1768,7 +1813,7 @@ var require_sender = __commonJS({ this.sendFrame(_Sender.frame(data, options2), cb); return; } - const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; + const perMessageDeflate = this._extensions[PerMessageDeflate2.extensionName]; this._bufferedBytes += options2[kByteLength]; this._state = DEFLATING; perMessageDeflate.compress(data, options2.fin, (_, buf) => { @@ -1842,9 +1887,9 @@ var require_sender = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/event-target.js var require_event_target = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/event-target.js"(exports2, module2) { "use strict"; var { kForOnEventAttribute, kListener } = require_constants(); var kCode = /* @__PURE__ */ Symbol("kCode"); @@ -2071,9 +2116,9 @@ var require_event_target = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/extension.js var require_extension = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/extension.js"(exports2, module2) { "use strict"; var { tokenChars } = require_validation(); function push(dest, name, elem) { @@ -2206,11 +2251,11 @@ var require_extension = __commonJS({ return offers; } function format(extensions) { - return Object.keys(extensions).map((extension) => { - let configurations = extensions[extension]; + return Object.keys(extensions).map((extension2) => { + let configurations = extensions[extension2]; if (!Array.isArray(configurations)) configurations = [configurations]; return configurations.map((params) => { - return [extension].concat( + return [extension2].concat( Object.keys(params).map((k) => { let values = params[k]; if (!Array.isArray(values)) values = [values]; @@ -2224,9 +2269,9 @@ var require_extension = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket.js var require_websocket = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket.js"(exports2, module2) { "use strict"; var EventEmitter = require("events"); var https = require("https"); @@ -2236,7 +2281,7 @@ var require_websocket = __commonJS({ var { randomBytes, createHash } = require("crypto"); var { Duplex, Readable } = require("stream"); var { URL } = require("url"); - var PerMessageDeflate = require_permessage_deflate(); + var PerMessageDeflate2 = require_permessage_deflate(); var Receiver2 = require_receiver(); var Sender2 = require_sender(); var { isBlob } = require_validation(); @@ -2395,6 +2440,10 @@ var require_websocket = __commonJS({ * multiple times in the same tick * @param {Function} [options.generateMask] The function used to generate the * masking key + * @param {Number} [options.maxBufferedChunks=0] The maximum number of + * buffered data chunks + * @param {Number} [options.maxFragments=0] The maximum number of message + * fragments * @param {Number} [options.maxPayload=0] The maximum allowed message size * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or * not to skip UTF-8 validation for text and close messages @@ -2406,6 +2455,8 @@ var require_websocket = __commonJS({ binaryType: this.binaryType, extensions: this._extensions, isServer: this._isServer, + maxBufferedChunks: options2.maxBufferedChunks, + maxFragments: options2.maxFragments, maxPayload: options2.maxPayload, skipUTF8Validation: options2.skipUTF8Validation }); @@ -2444,8 +2495,8 @@ var require_websocket = __commonJS({ this.emit("close", this._closeCode, this._closeMessage); return; } - if (this._extensions[PerMessageDeflate.extensionName]) { - this._extensions[PerMessageDeflate.extensionName].cleanup(); + if (this._extensions[PerMessageDeflate2.extensionName]) { + this._extensions[PerMessageDeflate2.extensionName].cleanup(); } this._receiver.removeAllListeners(); this._readyState = _WebSocket.CLOSED; @@ -2607,7 +2658,7 @@ var require_websocket = __commonJS({ fin: true, ...options2 }; - if (!this._extensions[PerMessageDeflate.extensionName]) { + if (!this._extensions[PerMessageDeflate2.extensionName]) { opts.compress = false; } this._sender.send(data || EMPTY_BUFFER, opts, cb); @@ -2705,6 +2756,8 @@ var require_websocket = __commonJS({ autoPong: true, closeTimeout: CLOSE_TIMEOUT, protocolVersion: protocolVersions[1], + maxBufferedChunks: 1024 * 1024, + maxFragments: 128 * 1024, maxPayload: 100 * 1024 * 1024, skipUTF8Validation: false, perMessageDeflate: true, @@ -2733,7 +2786,7 @@ var require_websocket = __commonJS({ } else { try { parsedUrl = new URL(address); - } catch (e) { + } catch { throw new SyntaxError(`Invalid URL: ${address}`); } } @@ -2781,13 +2834,13 @@ var require_websocket = __commonJS({ opts.path = parsedUrl.pathname + parsedUrl.search; opts.timeout = opts.handshakeTimeout; if (opts.perMessageDeflate) { - perMessageDeflate = new PerMessageDeflate( - opts.perMessageDeflate !== true ? opts.perMessageDeflate : {}, - false, - opts.maxPayload - ); + perMessageDeflate = new PerMessageDeflate2({ + ...opts.perMessageDeflate, + isServer: false, + maxPayload: opts.maxPayload + }); opts.headers["Sec-WebSocket-Extensions"] = format({ - [PerMessageDeflate.extensionName]: perMessageDeflate.offer() + [PerMessageDeflate2.extensionName]: perMessageDeflate.offer() }); } if (protocols.length) { @@ -2930,23 +2983,25 @@ var require_websocket = __commonJS({ return; } const extensionNames = Object.keys(extensions); - if (extensionNames.length !== 1 || extensionNames[0] !== PerMessageDeflate.extensionName) { + if (extensionNames.length !== 1 || extensionNames[0] !== PerMessageDeflate2.extensionName) { const message = "Server indicated an extension that was not requested"; abortHandshake(websocket, socket, message); return; } try { - perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]); + perMessageDeflate.accept(extensions[PerMessageDeflate2.extensionName]); } catch (err) { const message = "Invalid Sec-WebSocket-Extensions header"; abortHandshake(websocket, socket, message); return; } - websocket._extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + websocket._extensions[PerMessageDeflate2.extensionName] = perMessageDeflate; } websocket.setSocket(socket, head, { allowSynchronousEvents: opts.allowSynchronousEvents, generateMask: opts.generateMask, + maxBufferedChunks: opts.maxBufferedChunks, + maxFragments: opts.maxFragments, maxPayload: opts.maxPayload, skipUTF8Validation: opts.skipUTF8Validation }); @@ -3110,9 +3165,9 @@ var require_websocket = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/stream.js var require_stream = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/stream.js"(exports2, module2) { "use strict"; var WebSocket2 = require_websocket(); var { Duplex } = require("stream"); @@ -3172,7 +3227,7 @@ var require_stream = __commonJS({ }; duplex._final = function(callback) { if (ws.readyState === ws.CONNECTING) { - ws.once("open", function open() { + ws.once("open", function open2() { duplex._final(callback); }); return; @@ -3193,7 +3248,7 @@ var require_stream = __commonJS({ }; duplex._write = function(chunk, encoding, callback) { if (ws.readyState === ws.CONNECTING) { - ws.once("open", function open() { + ws.once("open", function open2() { duplex._write(chunk, encoding, callback); }); return; @@ -3208,9 +3263,9 @@ var require_stream = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/subprotocol.js var require_subprotocol = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/subprotocol.js"(exports2, module2) { "use strict"; var { tokenChars } = require_validation(); function parse2(header) { @@ -3253,17 +3308,17 @@ var require_subprotocol = __commonJS({ } }); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket-server.js var require_websocket_server = __commonJS({ - "node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) { + "node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) { "use strict"; var EventEmitter = require("events"); var http = require("http"); var { Duplex } = require("stream"); var { createHash } = require("crypto"); - var extension = require_extension(); - var PerMessageDeflate = require_permessage_deflate(); - var subprotocol = require_subprotocol(); + var extension2 = require_extension(); + var PerMessageDeflate2 = require_permessage_deflate(); + var subprotocol2 = require_subprotocol(); var WebSocket2 = require_websocket(); var { CLOSE_TIMEOUT, GUID, kWebSocket } = require_constants(); var keyRegex = /^[+/0-9A-Za-z]{22}==$/; @@ -3289,6 +3344,10 @@ var require_websocket_server = __commonJS({ * called * @param {Function} [options.handleProtocols] A hook to handle protocols * @param {String} [options.host] The hostname where to bind the server + * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of + * buffered data chunks + * @param {Number} [options.maxFragments=131072] The maximum number of message + * fragments * @param {Number} [options.maxPayload=104857600] The maximum allowed message * size * @param {Boolean} [options.noServer=false] Enable no server mode @@ -3310,6 +3369,8 @@ var require_websocket_server = __commonJS({ options2 = { allowSynchronousEvents: true, autoPong: true, + maxBufferedChunks: 1024 * 1024, + maxFragments: 128 * 1024, maxPayload: 100 * 1024 * 1024, skipUTF8Validation: false, perMessageDeflate: false, @@ -3486,7 +3547,7 @@ var require_websocket_server = __commonJS({ let protocols = /* @__PURE__ */ new Set(); if (secWebSocketProtocol !== void 0) { try { - protocols = subprotocol.parse(secWebSocketProtocol); + protocols = subprotocol2.parse(secWebSocketProtocol); } catch (err) { const message = "Invalid Sec-WebSocket-Protocol header"; abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); @@ -3496,16 +3557,16 @@ var require_websocket_server = __commonJS({ const secWebSocketExtensions = req.headers["sec-websocket-extensions"]; const extensions = {}; if (this.options.perMessageDeflate && secWebSocketExtensions !== void 0) { - const perMessageDeflate = new PerMessageDeflate( - this.options.perMessageDeflate, - true, - this.options.maxPayload - ); + const perMessageDeflate = new PerMessageDeflate2({ + ...this.options.perMessageDeflate, + isServer: true, + maxPayload: this.options.maxPayload + }); try { - const offers = extension.parse(secWebSocketExtensions); - if (offers[PerMessageDeflate.extensionName]) { - perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); - extensions[PerMessageDeflate.extensionName] = perMessageDeflate; + const offers = extension2.parse(secWebSocketExtensions); + if (offers[PerMessageDeflate2.extensionName]) { + perMessageDeflate.accept(offers[PerMessageDeflate2.extensionName]); + extensions[PerMessageDeflate2.extensionName] = perMessageDeflate; } } catch (err) { const message = "Invalid or unacceptable Sec-WebSocket-Extensions header"; @@ -3576,10 +3637,10 @@ var require_websocket_server = __commonJS({ ws._protocol = protocol; } } - if (extensions[PerMessageDeflate.extensionName]) { - const params = extensions[PerMessageDeflate.extensionName].params; - const value = extension.format({ - [PerMessageDeflate.extensionName]: [params] + if (extensions[PerMessageDeflate2.extensionName]) { + const params = extensions[PerMessageDeflate2.extensionName].params; + const value = extension2.format({ + [PerMessageDeflate2.extensionName]: [params] }); headers.push(`Sec-WebSocket-Extensions: ${value}`); ws._extensions = extensions; @@ -3589,6 +3650,8 @@ var require_websocket_server = __commonJS({ socket.removeListener("error", socketOnError); ws.setSocket(socket, head, { allowSynchronousEvents: this.options.allowSynchronousEvents, + maxBufferedChunks: this.options.maxBufferedChunks, + maxFragments: this.options.maxFragments, maxPayload: this.options.maxPayload, skipUTF8Validation: this.options.skipUTF8Validation }); @@ -6915,12 +6978,12 @@ var require_stringify = __commonJS({ throw new TypeError('expected "' + language + '.stringify" to be a function'); } data = Object.assign({}, file.data, data); - const open = opts.delimiters[0]; + const open2 = opts.delimiters[0]; const close = opts.delimiters[1]; const matter2 = engine.stringify(data, options2).trim(); let buf = ""; if (matter2 !== "{}") { - buf = newline(open) + newline(matter2) + newline(close); + buf = newline(open2) + newline(matter2) + newline(close); } if (typeof file.excerpt === "string" && file.excerpt !== "") { if (str2.indexOf(file.excerpt.trim()) === -1) { @@ -7017,7 +7080,7 @@ var require_parse = __commonJS({ var require_gray_matter = __commonJS({ "node_modules/.pnpm/gray-matter@4.0.3/node_modules/gray-matter/index.js"(exports2, module2) { "use strict"; - var fs2 = require("fs"); + var fs = require("fs"); var sections = require_section_matter(); var defaults = require_defaults(); var stringify = require_stringify(); @@ -7044,18 +7107,18 @@ var require_gray_matter = __commonJS({ } function parseMatter(file, options2) { const opts = defaults(options2); - const open = opts.delimiters[0]; + const open2 = opts.delimiters[0]; const close = "\n" + opts.delimiters[1]; let str2 = file.content; if (opts.language) { file.language = opts.language; } - const openLen = open.length; - if (!utils.startsWith(str2, open, openLen)) { + const openLen = open2.length; + if (!utils.startsWith(str2, open2, openLen)) { excerpt(file, opts); return file; } - if (str2.charAt(openLen) === open.slice(-1)) { + if (str2.charAt(openLen) === open2.slice(-1)) { return file; } str2 = str2.slice(openLen); @@ -7101,7 +7164,7 @@ var require_gray_matter = __commonJS({ return stringify(file, data, options2); }; matter2.read = function(filepath, options2) { - const str2 = fs2.readFileSync(filepath, "utf8"); + const str2 = fs.readFileSync(filepath, "utf8"); const file = matter2(str2, options2); file.path = filepath; return file; @@ -7111,9 +7174,9 @@ var require_gray_matter = __commonJS({ }; matter2.language = function(str2, options2) { const opts = defaults(options2); - const open = opts.delimiters[0]; + const open2 = opts.delimiters[0]; if (matter2.test(str2)) { - str2 = str2.slice(open.length); + str2 = str2.slice(open2.length); } const language = str2.slice(0, str2.search(/\r?\n/)); return { @@ -7132,65 +7195,92 @@ var require_gray_matter = __commonJS({ // mcp-server/ws-bridge.js var ws_bridge_exports = {}; __export(ws_bridge_exports, { + evaluateBridgeRequest: () => evaluateBridgeRequest, + isLoopbackAddress: () => isLoopbackAddress, + isTrustedUiOrigin: () => isTrustedUiOrigin, startBridge: () => startBridge, startUiBridge: () => startUiBridge }); module.exports = __toCommonJS(ws_bridge_exports); var import_node_http = require("node:http"); -// node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs +// node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs var import_stream = __toESM(require_stream(), 1); +var import_extension = __toESM(require_extension(), 1); +var import_permessage_deflate = __toESM(require_permessage_deflate(), 1); var import_receiver = __toESM(require_receiver(), 1); var import_sender = __toESM(require_sender(), 1); +var import_subprotocol = __toESM(require_subprotocol(), 1); var import_websocket = __toESM(require_websocket(), 1); var import_websocket_server = __toESM(require_websocket_server(), 1); +// mcp-server/tool-service.js +var import_node_path4 = __toESM(require("node:path"), 1); + // mcp-server/vault.js -var import_promises = __toESM(require("node:fs/promises"), 1); +var import_promises = require("node:fs/promises"); var import_node_path = __toESM(require("node:path"), 1); var import_gray_matter = __toESM(require_gray_matter(), 1); +var ACTIVE_VAULT_ERROR = "Note path must stay inside the active vault"; async function findMarkdownFiles(dir) { const results = []; - const items = await import_promises.default.readdir(dir, { withFileTypes: true }); - for (const item of items) { - if (item.name.startsWith(".")) continue; - const full = import_node_path.default.join(dir, item.name); - if (item.isDirectory()) { - results.push(...await findMarkdownFiles(full)); - } else if (item.name.endsWith(".md")) { - results.push(full); - } + const items = await (0, import_promises.opendir)(dir); + for await (const item of items) { + await collectMarkdownFile(results, dir, item); } return results; } -async function getNote(vaultPath, notePath) { - const absPath = import_node_path.default.isAbsolute(notePath) ? notePath : import_node_path.default.join(vaultPath, notePath); - const raw = await import_promises.default.readFile(absPath, "utf-8"); - const parsed = (0, import_gray_matter.default)(raw); +async function resolveVaultNotePath(vaultPath, notePath) { + const vaultRoot = await (0, import_promises.realpath)(vaultPath); + const requestedPath = resolveRequestedNotePath(vaultRoot, notePath); + const noteRealPath = await (0, import_promises.realpath)(requestedPath); + const relativePath = import_node_path.default.relative(vaultRoot, noteRealPath); + if (!isVaultRelativePath(relativePath)) { + throw new Error(ACTIVE_VAULT_ERROR); + } return { - path: import_node_path.default.relative(vaultPath, absPath), + vaultRoot, + noteRealPath, + relativePath + }; +} +async function getNote(vaultPath, notePath) { + const { + noteRealPath, + relativePath + } = await resolveVaultNotePath(vaultPath, notePath); + const raw = await readUtf8File(noteRealPath); + const parsed = parseMarkdownNote(raw); + return { + path: relativePath, frontmatter: parsed.data, content: parsed.content.trim() }; } +async function createNote(vaultPath, notePath, content) { + const { requestedPath, relativePath } = await resolveNewVaultNotePath(vaultPath, notePath); + await writeNewUtf8File(requestedPath, content); + return { + path: relativePath, + absolutePath: requestedPath + }; +} async function searchNotes(vaultPath, query, limit = 10) { const files = await findMarkdownFiles(vaultPath); const q = query.toLowerCase(); const results = []; for (const filePath of files) { if (results.length >= limit) break; - const content = await import_promises.default.readFile(filePath, "utf-8"); + const content = await readUtf8File(filePath); const filename = import_node_path.default.basename(filePath, ".md"); const titleMatch = extractTitle(content, filename); - const matches = titleMatch.toLowerCase().includes(q) || content.toLowerCase().includes(q); - if (matches) { - const snippet = extractSnippet(content, q); - results.push({ - path: import_node_path.default.relative(vaultPath, filePath), - title: titleMatch, - snippet - }); - } + if (!matchesSearchQuery(titleMatch, content, q)) continue; + const snippet = extractSnippet(content, q); + results.push({ + path: import_node_path.default.relative(vaultPath, filePath), + title: titleMatch, + snippet + }); } return results; } @@ -7200,39 +7290,264 @@ async function vaultContext(vaultPath) { const foldersSet = /* @__PURE__ */ new Set(); const notesWithMtime = []; for (const filePath of files) { - const raw = await import_promises.default.readFile(filePath, "utf-8"); - const parsed = (0, import_gray_matter.default)(raw); - const type = parsed.data.type || parsed.data.is_a || null; + const { topFolder, note, type } = await readVaultContextNote(vaultPath, filePath); if (type) typesSet.add(type); - const rel = import_node_path.default.relative(vaultPath, filePath); - const topFolder = rel.split(import_node_path.default.sep)[0]; - if (topFolder !== rel) foldersSet.add(topFolder + "/"); - const stat = await import_promises.default.stat(filePath); - notesWithMtime.push({ - path: rel, - title: parsed.data.title || extractTitle(raw, import_node_path.default.basename(filePath, ".md")), - type, - mtime: stat.mtimeMs - }); + if (topFolder) foldersSet.add(topFolder); + notesWithMtime.push(note); } notesWithMtime.sort((a, b) => b.mtime - a.mtime); - const recentNotes = notesWithMtime.slice(0, 20).map(({ mtime: _mtime, ...rest }) => rest); - const configFiles = {}; - try { - const agentsPath = import_node_path.default.join(vaultPath, "config", "agents.md"); - const agentsContent = await import_promises.default.readFile(agentsPath, "utf-8"); - configFiles.agents = agentsContent; - } catch { - } + const recentNotes = notesWithMtime.slice(0, 20).map(contextNoteWithoutMtime); return { types: [...typesSet].sort(), noteCount: files.length, folders: [...foldersSet].sort(), recentNotes, - configFiles, + configFiles: await readConfigFiles(vaultPath), vaultPath }; } +async function collectMarkdownFile(results, dir, item) { + if (item.name.startsWith(".")) return; + const full = resolveInside(dir, item.name); + if (!full) return; + if (item.isDirectory()) { + results.push(...await findMarkdownFiles(full)); + return; + } + if (item.name.endsWith(".md")) { + results.push(full); + } +} +function resolveRequestedNotePath(vaultRoot, notePath) { + if (import_node_path.default.isAbsolute(notePath)) return notePath; + const resolved = resolveInside(vaultRoot, notePath); + if (!resolved) throw new Error(ACTIVE_VAULT_ERROR); + return resolved; +} +async function resolveNewVaultNotePath(vaultPath, notePath) { + const requestedNotePath = validateNewNotePath(notePath); + const vaultRoot = await (0, import_promises.realpath)(vaultPath); + const requestedPath = resolveRequestedNotePath(vaultRoot, requestedNotePath); + const relativePath = relativeNotePathInsideVault(vaultRoot, requestedPath); + await ensureWritableParentInsideVault(vaultRoot, requestedPath); + return { requestedPath, relativePath }; +} +function validateNewNotePath(notePath) { + const trimmedPath = typeof notePath === "string" ? notePath.trim() : ""; + if (!trimmedPath) { + throw new Error("Note path is required"); + } + if (!trimmedPath.endsWith(".md")) { + throw new Error("New notes must be markdown files ending in .md"); + } + return trimmedPath; +} +async function ensureWritableParentInsideVault(vaultRoot, requestedPath) { + const parentPath = import_node_path.default.dirname(requestedPath); + const existingAncestor = await nearestExistingAncestor(parentPath); + assertInsideVault(vaultRoot, existingAncestor); + await (0, import_promises.mkdir)(parentPath, { recursive: true }); + assertInsideVault(vaultRoot, await (0, import_promises.realpath)(parentPath)); +} +async function nearestExistingAncestor(targetPath) { + let currentPath = targetPath; + while (currentPath && currentPath !== import_node_path.default.dirname(currentPath)) { + try { + return await (0, import_promises.realpath)(currentPath); + } catch (error) { + if (error?.code !== "ENOENT") throw error; + currentPath = import_node_path.default.dirname(currentPath); + } + } + return (0, import_promises.realpath)(currentPath); +} +function assertInsideVault(vaultRoot, targetPath) { + if (!isVaultRelativePath(import_node_path.default.relative(vaultRoot, targetPath))) { + throw new Error(ACTIVE_VAULT_ERROR); + } +} +function relativeNotePathInsideVault(vaultRoot, requestedPath) { + const relativePath = import_node_path.default.relative(vaultRoot, requestedPath); + if (!isVaultRelativePath(relativePath) || !relativePath) { + throw new Error(ACTIVE_VAULT_ERROR); + } + return relativePath; +} +function resolveInside(root, target) { + const resolved = import_node_path.default.resolve(root, target); + const relative = import_node_path.default.relative(root, resolved); + if (isVaultRelativePath(relative)) return resolved; + return null; +} +function isVaultRelativePath(relativePath) { + return !relativePath.startsWith("..") && !import_node_path.default.isAbsolute(relativePath); +} +function matchesSearchQuery(title, content, query) { + return title.toLowerCase().includes(query) || content.toLowerCase().includes(query); +} +function contextNoteWithoutMtime(note) { + return { + path: note.path, + title: note.title, + type: note.type + }; +} +async function readVaultContextNote(vaultPath, filePath) { + const raw = await readUtf8File(filePath); + const parsed = parseMarkdownNote(raw); + const rel = import_node_path.default.relative(vaultPath, filePath); + const topFolder = extractTopFolder(rel); + const stat = await statFile(filePath); + const type = parsed.data.type || parsed.data.is_a || null; + return { + topFolder, + type, + note: { + path: rel, + title: parsed.data.title || extractTitle(raw, import_node_path.default.basename(filePath, ".md")), + type, + mtime: stat.mtimeMs + } + }; +} +function parseMarkdownNote(raw) { + try { + const parsed = (0, import_gray_matter.default)(raw); + const fallback = parseFrontmatterFallback(raw); + return shouldUseFallbackFrontmatter(parsed, fallback) ? fallback : parsed; + } catch { + return parseFrontmatterFallback(raw); + } +} +function shouldUseFallbackFrontmatter(parsed, fallback) { + return Object.keys(parsed.data).length === 0 && Object.keys(fallback.data).length > 0; +} +function parseFrontmatterFallback(raw) { + const split = splitFrontmatter(raw); + if (!split) return { data: {}, content: raw }; + return { + data: parseFrontmatterBlock(split.frontmatter), + content: split.content + }; +} +function splitFrontmatter(raw) { + const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)([\s\S]*)$/); + if (!match) return null; + return { frontmatter: match[1], content: match[2] }; +} +function parseFrontmatterBlock(frontmatter) { + const data = {}; + let listKey = null; + for (const line of frontmatter.split(/\r?\n/)) { + const item = parseYamlListItem(line); + if (listKey && item !== null) { + data[listKey].push(parseYamlScalar(item)); + continue; + } + listKey = null; + const field = parseTopLevelYamlField(line); + if (!field) continue; + data[field.key] = field.value ? parseYamlValue(field.value) : []; + listKey = field.value ? null : field.key; + } + return data; +} +function parseTopLevelYamlField(line) { + if (!line || line.trimStart() !== line || line.trimStart().startsWith("#")) return null; + const separatorIndex = line.indexOf(":"); + if (separatorIndex <= 0) return null; + return { + key: stripMatchingQuotes(line.slice(0, separatorIndex).trim()), + value: line.slice(separatorIndex + 1).trim() + }; +} +function parseYamlValue(value) { + if (value.startsWith("[") && value.endsWith("]")) { + return splitInlineYamlArray(value).map(parseYamlScalar); + } + return parseYamlScalar(value); +} +function splitInlineYamlArray(value) { + const inner = value.slice(1, -1); + const items = []; + let current = ""; + let quote = null; + for (const char of inner) { + if (quote) { + current += char; + if (char === quote) quote = null; + continue; + } + if (char === '"' || char === "'") { + quote = char; + current += char; + continue; + } + if (char === ",") { + items.push(current.trim()); + current = ""; + continue; + } + current += char; + } + if (current.trim()) items.push(current.trim()); + return items; +} +function parseYamlListItem(line) { + const match = line.match(/^\s+-\s*(.*)$/); + return match ? match[1].trim() : null; +} +function parseYamlScalar(value) { + const unquoted = stripMatchingQuotes(value.trim()); + if (unquoted !== value.trim()) return unquoted; + if (/^(true|yes)$/i.test(unquoted)) return true; + if (/^(false|no)$/i.test(unquoted)) return false; + if (/^(null|~)$/i.test(unquoted)) return null; + if (/^-?\d+(\.\d+)?$/.test(unquoted)) return Number(unquoted); + return unquoted; +} +function stripMatchingQuotes(value) { + const first = value[0]; + const last = value[value.length - 1]; + return (first === '"' || first === "'") && first === last ? value.slice(1, -1) : value; +} +function extractTopFolder(relativePath) { + const topFolder = relativePath.split(import_node_path.default.sep)[0]; + return topFolder === relativePath ? null : `${topFolder}/`; +} +async function readConfigFiles(vaultPath) { + const configFiles = {}; + try { + const agentsPath = resolveInside(vaultPath, "config/agents.md"); + if (agentsPath) configFiles.agents = await readUtf8File(agentsPath); + } catch { + } + return configFiles; +} +async function readUtf8File(filePath) { + const handle = await (0, import_promises.open)(filePath, "r"); + try { + return await handle.readFile("utf-8"); + } finally { + await handle.close(); + } +} +async function writeNewUtf8File(filePath, content) { + const handle = await (0, import_promises.open)(filePath, "wx"); + try { + await handle.writeFile(content, "utf-8"); + } finally { + await handle.close(); + } +} +async function statFile(filePath) { + const handle = await (0, import_promises.open)(filePath, "r"); + try { + return await handle.stat(); + } finally { + await handle.close(); + } +} function extractTitle(content, fallback) { const h1Match = content.match(/^#\s+(.+)$/m); if (h1Match) return h1Match[1].trim(); @@ -7249,11 +7564,341 @@ function extractSnippet(content, query) { return (start > 0 ? "..." : "") + body.slice(start, end) + (end < body.length ? "..." : ""); } +// mcp-server/vault-path.js +var import_node_fs = require("node:fs"); +var import_node_os = require("node:os"); +var import_node_path2 = require("node:path"); + +// mcp-server/app-config-policy.json +var app_config_policy_default = { + current_namespace: "com.tolaria.app", + legacy_namespace: "com.laputa.app", + namespace_read_order: ["current", "legacy"], + files: { + settings: "settings.json", + vaults: "vaults.json", + last_vault: "last-vault.txt", + ai_workspace_sessions: "ai-workspace-sessions.json", + window_state: "window-state.json", + ai_provider_secrets: "ai-provider-secrets.json" + }, + read_order: [ + "preferred config root/current namespace", + "preferred config root/legacy namespace", + "platform config root/current namespace when different", + "platform config root/legacy namespace when different" + ], + write_target: "preferred config root/current namespace" +}; + +// mcp-server/vault-path.js +var APP_CONFIG_DIR = app_config_policy_default.current_namespace; +var APP_CONFIG_FILES = Object.freeze(app_config_policy_default.files); +function parseVaultPathList(rawValue) { + if (!rawValue?.trim()) return []; + try { + const parsed = JSON.parse(rawValue); + if (Array.isArray(parsed)) return parsed.filter((value) => typeof value === "string"); + } catch { + } + return []; +} +function uniqueVaultPaths(paths) { + const seen = /* @__PURE__ */ new Set(); + const unique = []; + for (const path4 of paths) { + const trimmed = path4.trim(); + if (!trimmed || seen.has(trimmed)) continue; + seen.add(trimmed); + unique.push(trimmed); + } + return unique; +} +function absolutePath(path4) { + return typeof path4 === "string" && (0, import_node_path2.isAbsolute)(path4) ? path4 : null; +} +function defaultXdgConfigHome(platformName, homeDir) { + if (platformName === "win32") return null; + return absolutePath(homeDir) ? (0, import_node_path2.join)(homeDir, ".config") : null; +} +function platformConfigDir(env, platformName, homeDir) { + if (platformName === "darwin") return (0, import_node_path2.join)(homeDir, "Library", "Application Support"); + if (platformName === "win32") return absolutePath(env.APPDATA) || (0, import_node_path2.join)(homeDir, "AppData", "Roaming"); + return absolutePath(env.XDG_CONFIG_HOME) || defaultXdgConfigHome(platformName, homeDir); +} +function appConfigBaseDirs({ + env = process.env, + homeDir = (0, import_node_os.homedir)(), + platformName = (0, import_node_os.platform)(), + platformDir = platformConfigDir(env, platformName, homeDir) +} = {}) { + const primary = absolutePath(env.XDG_CONFIG_HOME) || defaultXdgConfigHome(platformName, homeDir) || platformDir; + const dirs = primary ? [primary] : []; + if (platformDir && platformDir !== primary) dirs.push(platformDir); + return dirs; +} +function namespaceDir(namespace) { + if (namespace === "current") return APP_CONFIG_DIR; + if (namespace === "legacy") return app_config_policy_default.legacy_namespace; + throw new Error(`Unknown app config namespace: ${namespace}`); +} +function preferredAppConfigPath(configDir, fileName) { + return (0, import_node_path2.join)(configDir, APP_CONFIG_DIR, fileName); +} +function existingOrPreferredAppConfigPath(configDirs, fileName) { + for (const configDir of configDirs) { + for (const namespace of app_config_policy_default.namespace_read_order) { + const candidate = (0, import_node_path2.join)(configDir, namespaceDir(namespace), fileName); + if ((0, import_node_fs.existsSync)(candidate)) return candidate; + } + } + return preferredAppConfigPath(configDirs[0], fileName); +} +function vaultsJsonPath({ + configDir, + configDirs = configDir ? [configDir] : appConfigBaseDirs() +} = {}) { + return existingOrPreferredAppConfigPath(configDirs, APP_CONFIG_FILES.vaults); +} +function pushUniquePath(paths, value) { + const path4 = typeof value === "string" ? value.trim() : ""; + if (!path4 || paths.includes(path4)) return; + paths.push(path4); +} +function activeVaultPathsFromList(list) { + const paths = []; + pushUniquePath(paths, list?.active_vault); + for (const vault of list?.vaults ?? []) { + if (vault?.mounted === false) continue; + pushUniquePath(paths, vault?.path); + } + return paths; +} +function configuredVaultPaths(options2 = {}) { + const filePath = vaultsJsonPath(options2); + if (!(0, import_node_fs.existsSync)(filePath)) return []; + return activeVaultPathsFromList(JSON.parse((0, import_node_fs.readFileSync)(filePath, "utf-8"))); +} +function requireVaultPaths(env = process.env, options2 = {}) { + const vaultPaths = uniqueVaultPaths([ + env.VAULT_PATH?.trim() ?? "", + ...parseVaultPathList(env.VAULT_PATHS) + ]); + if (vaultPaths.length === 0) { + const configuredPaths = configuredVaultPaths(options2); + if (configuredPaths.length > 0) return configuredPaths; + throw new Error("VAULT_PATH is required. Open a vault in Tolaria before starting MCP tools."); + } + return vaultPaths; +} + +// mcp-server/agent-instructions.js +var import_promises2 = require("node:fs/promises"); +var import_node_path3 = __toESM(require("node:path"), 1); +async function readAgentInstructions(vaultPath) { + const instructionsPath = import_node_path3.default.join(vaultPath, "AGENTS.md"); + try { + return { + path: instructionsPath, + content: await (0, import_promises2.readFile)(instructionsPath, "utf8") + }; + } catch (error) { + if (error?.code === "ENOENT") return null; + throw error; + } +} +async function vaultContextWithInstructions(vaultPath) { + return { + ...await vaultContext(vaultPath), + agentInstructions: await readAgentInstructions(vaultPath) + }; +} + +// mcp-server/tool-service.js +function createMcpToolService({ + resolveVaultPaths = () => requireVaultPaths(), + emitUiAction = () => { + } +} = {}) { + function activeVaultPaths() { + return resolveVaultPaths(); + } + function requestedVaultPath(args = {}) { + const requested = typeof args.vaultPath === "string" ? args.vaultPath.trim() : ""; + if (!requested) return null; + if (!activeVaultPaths().includes(requested)) { + throw new Error(`Vault is not active in Tolaria: ${requested}`); + } + return requested; + } + function resolveUiPath(args = {}) { + const notePath = typeof args.path === "string" ? args.path : ""; + if (import_node_path4.default.isAbsolute(notePath)) return notePath; + const roots = activeVaultPaths(); + const vaultPath = requestedVaultPath(args) ?? (roots.length === 1 ? roots[0] : ""); + return vaultPath ? import_node_path4.default.join(vaultPath, notePath) : notePath; + } + async function readNote(args = {}) { + return getNoteFromActiveVaults(notePathArg(args), requestedVaultPath(args)); + } + async function searchNotes2(args = {}) { + const requestedLimit = Number.isFinite(args.limit) && args.limit > 0 ? args.limit : 10; + const results = []; + for (const vaultPath of activeVaultPaths()) { + const vaultResults = await searchNotes(vaultPath, args.query, requestedLimit); + results.push(...vaultResults.map((result) => withVaultMetadata(result, vaultPath))); + if (results.length >= requestedLimit) break; + } + return results.slice(0, requestedLimit); + } + async function vaultContext2(args = {}) { + const targetVaultPath = requestedVaultPath(args); + const roots = activeVaultPaths(); + if (targetVaultPath) return vaultContextWithInstructions(targetVaultPath); + if (roots.length === 1) return vaultContextWithInstructions(roots[0]); + return { + vaults: await Promise.all(roots.map(vaultContextWithInstructions)) + }; + } + async function listVaults() { + const vaults = await Promise.all(activeVaultPaths().map(async (vaultPath) => { + const agentInstructions = await readAgentInstructions(vaultPath); + return { + path: vaultPath, + label: vaultLabel(vaultPath), + agentInstructionsPath: agentInstructions?.path ?? null, + hasAgentInstructions: agentInstructions !== null + }; + })); + return { vaults }; + } + async function createNote2(args = {}) { + const vaultPath = writableVaultPath(args); + const notePath = writableNotePath(args, vaultPath); + const note = await createNote(vaultPath, notePath, createNoteContent(args)); + const targetPath = resolveUiPath({ ...args, path: note.path, vaultPath }); + emitUiAction("vault_changed", { path: targetPath }); + emitUiAction("open_tab", { path: targetPath }); + return { path: note.path, absolutePath: note.absolutePath, vaultPath }; + } + function openNoteAsTab(args = {}) { + const targetPath = resolveUiPath(args); + emitUiAction("vault_changed", { path: targetPath }); + emitUiAction("open_tab", { path: targetPath }); + return { targetPath }; + } + function openNoteInEditor(args = {}) { + const targetPath = resolveUiPath(args); + emitUiAction("vault_changed", { path: targetPath }); + emitUiAction("open_note", { path: targetPath }); + return { targetPath }; + } + function highlightEditor(args = {}) { + emitUiAction("highlight", { element: args.element, path: args.path }); + } + function setFilter(args = {}) { + emitUiAction("set_filter", { filterType: args.type }); + } + function refreshVault(args = {}) { + emitUiAction("vault_changed", { path: resolveUiPath(args) }); + } + async function getNoteFromActiveVaults(notePath, vaultPath = null) { + const candidates = vaultPath ? [vaultPath] : activeVaultPaths(); + const matches = []; + const errors = []; + for (const candidate of candidates) { + try { + matches.push(withVaultMetadata(await getNote(candidate, notePath), candidate)); + } catch (error) { + errors.push(error); + } + } + if (matches.length === 1) return matches[0]; + if (matches.length > 1) { + throw new Error(`Note path is ambiguous across active vaults. Pass vaultPath for ${notePath}.`); + } + throw errors[0] ?? new Error(`Note not found: ${notePath}`); + } + function writableVaultPath(args = {}) { + const requested = requestedVaultPath(args); + if (requested) return requested; + const roots = activeVaultPaths(); + const notePath = notePathArg(args); + if (import_node_path4.default.isAbsolute(notePath)) { + const root = roots.find((vaultPath) => isInsideVaultRoot(vaultPath, notePath)); + if (root) return root; + } + if (roots.length === 1) return roots[0]; + throw new Error(`Note path is ambiguous across active vaults. Pass vaultPath for ${notePath}.`); + } + return { + activeVaultPaths, + createNote: createNote2, + highlightEditor, + listVaults, + openNoteAsTab, + openNoteInEditor, + readNote, + refreshVault, + requestedVaultPath, + resolveUiPath, + searchNotes: searchNotes2, + setFilter, + vaultContext: vaultContext2 + }; +} +function writableNotePath(args, vaultPath) { + const notePath = notePathArg(args); + if (!import_node_path4.default.isAbsolute(notePath) || !isInsideVaultRoot(vaultPath, notePath)) return notePath; + return import_node_path4.default.relative(vaultPath, notePath); +} +function withVaultMetadata(note, vaultPath) { + return { + ...note, + vaultPath, + vaultLabel: vaultLabel(vaultPath) + }; +} +function vaultLabel(vaultPath) { + return import_node_path4.default.basename(vaultPath) || vaultPath; +} +function isInsideVaultRoot(vaultPath, notePath) { + const relative = import_node_path4.default.relative(vaultPath, notePath); + return Boolean(relative) && !relative.startsWith("..") && !import_node_path4.default.isAbsolute(relative); +} +function notePathArg(args = {}) { + const notePath = typeof args.path === "string" ? args.path.trim() : ""; + if (!notePath) throw new Error("Note path is required"); + return notePath; +} +function yamlScalar(value) { + return JSON.stringify(value); +} +function fallbackCreateNoteContent(args = {}) { + const title = typeof args.title === "string" && args.title.trim() ? args.title.trim() : import_node_path4.default.basename(notePathArg(args), ".md"); + const type = typeof args.type === "string" && args.type.trim() ? args.type.trim() : typeof args.is_a === "string" && args.is_a.trim() ? args.is_a.trim() : "Note"; + return `--- +type: ${yamlScalar(type)} +--- + +# ${title} +`; +} +function createNoteContent(args = {}) { + return typeof args.content === "string" && args.content.trim() ? args.content : fallbackCreateNoteContent(args); +} + // mcp-server/ws-bridge.js -var VAULT_PATH = process.env.VAULT_PATH || process.env.HOME + "/Laputa"; var WS_PORT = parseInt(process.env.WS_PORT || "9710", 10); var WS_UI_PORT = parseInt(process.env.WS_UI_PORT || "9711", 10); +var LOOPBACK_HOST = "localhost"; +var TRUSTED_UI_ORIGINS = /* @__PURE__ */ new Set([ + "tauri://localhost", + "http://tauri.localhost", + "https://tauri.localhost" +]); var uiBridge = null; +var UNKNOWN_TOOL = /* @__PURE__ */ Symbol("unknown tool"); function broadcastUiAction(action, payload) { if (!uiBridge) return; const msg = JSON.stringify({ type: "ui_action", action, ...payload }); @@ -7261,52 +7906,100 @@ function broadcastUiAction(action, payload) { if (client.readyState === 1) client.send(msg); } } -var TOOL_HANDLERS = { - open_note: (args) => getNote(VAULT_PATH, args.path).then((note) => ({ content: note.content, frontmatter: note.frontmatter })), - read_note: (args) => getNote(VAULT_PATH, args.path).then((note) => ({ content: note.content, frontmatter: note.frontmatter })), - search_notes: (args) => searchNotes(VAULT_PATH, args.query, args.limit), - vault_context: () => vaultContext(VAULT_PATH), - ui_open_note: (args) => { - broadcastUiAction("vault_changed", { path: args.path }); - broadcastUiAction("open_note", { path: args.path }); - return { ok: true }; - }, - ui_open_tab: (args) => { - broadcastUiAction("vault_changed", { path: args.path }); - broadcastUiAction("open_tab", { path: args.path }); - return { ok: true }; - }, - ui_highlight: (args) => { - broadcastUiAction("highlight", { element: args.element, path: args.path }); - return { ok: true }; - }, - ui_set_filter: (args) => { - broadcastUiAction("set_filter", { filterType: args.type }); - return { ok: true }; - }, - highlight_editor: (args) => { - broadcastUiAction("highlight", { element: args.element, path: args.path }); - return { ok: true }; - }, - refresh_vault: (args) => { - broadcastUiAction("vault_changed", { path: args?.path }); - return { ok: true }; - } -}; +var toolService = createMcpToolService({ emitUiAction: broadcastUiAction }); +async function readNoteTool(args) { + const note = await toolService.readNote(args); + return { content: note.content, frontmatter: note.frontmatter }; +} +function uiOpenNoteTool(args) { + toolService.openNoteInEditor(args); + return { ok: true }; +} +function uiOpenTabTool(args) { + toolService.openNoteAsTab(args); + return { ok: true }; +} +async function createNoteTool(args = {}) { + return { ok: true, ...await toolService.createNote(args) }; +} +function highlightTool(args) { + toolService.highlightEditor(args); + return { ok: true }; +} +function uiSetFilterTool(args) { + toolService.setFilter(args); + return { ok: true }; +} +function refreshVaultTool(args) { + toolService.refreshVault(args); + return { ok: true }; +} +var TOOL_EXECUTORS = [ + ["open_note", readNoteTool], + ["read_note", readNoteTool], + ["create_note", createNoteTool], + ["search_notes", (args) => toolService.searchNotes(args)], + ["vault_context", (args) => toolService.vaultContext(args)], + ["list_vaults", () => toolService.listVaults()], + ["ui_open_note", uiOpenNoteTool], + ["ui_open_tab", uiOpenTabTool], + ["ui_highlight", highlightTool], + ["highlight_editor", highlightTool], + ["ui_set_filter", uiSetFilterTool], + ["refresh_vault", refreshVaultTool] +]; +function callToolHandler(tool, args) { + const executor = TOOL_EXECUTORS.find(([name]) => name === tool)?.[1]; + return executor ? executor(args) : UNKNOWN_TOOL; +} async function handleMessage(data) { const msg = JSON.parse(data); const { id, tool, args } = msg; - const handler = TOOL_HANDLERS[tool]; - if (!handler) { - return { id, error: `Unknown tool: ${tool}` }; - } try { - const result = await handler(args || {}); + const result = await callToolHandler(tool, args || {}); + if (result === UNKNOWN_TOOL) { + return { id, error: `Unknown tool: ${tool}` }; + } return { id, result }; } catch (err) { return { id, error: err.message }; } } +function isLoopbackAddress(remoteAddress) { + return remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1"; +} +function isTrustedUiOrigin(origin) { + if (!origin) return true; + if (TRUSTED_UI_ORIGINS.has(origin)) return true; + return /^http:\/\/(?:localhost|127\.0\.0\.1):\d+$/u.test(origin); +} +function evaluateBridgeRequest({ bridgeType, origin, remoteAddress }) { + if (!isLoopbackAddress(remoteAddress)) { + return { ok: false, reason: "non-local client" }; + } + if (bridgeType === "tool" && origin) { + return { ok: false, reason: "browser origins are not allowed on the tool bridge" }; + } + if (bridgeType === "ui" && !isTrustedUiOrigin(origin)) { + return { ok: false, reason: "untrusted UI origin" }; + } + return { ok: true, reason: null }; +} +function verifyBridgeRequest(bridgeType) { + return (info, done) => { + const verdict = evaluateBridgeRequest({ + bridgeType, + origin: info.origin, + remoteAddress: info.req.socket.remoteAddress + }); + if (!verdict.ok) { + console.error(`[ws-bridge] Rejected ${bridgeType} bridge client: ${verdict.reason}`); + done(false, 403, "Forbidden"); + return; + } + done(true); + }; +} function startUiBridge(port = WS_UI_PORT) { return new Promise((resolve) => { const httpServer = (0, import_node_http.createServer)(); @@ -7318,8 +8011,11 @@ function startUiBridge(port = WS_UI_PORT) { } resolve(null); }); - httpServer.listen(port, () => { - const wss = new import_websocket_server.default({ server: httpServer }); + httpServer.listen(port, LOOPBACK_HOST, () => { + const wss = new import_websocket_server.default({ + server: httpServer, + verifyClient: verifyBridgeRequest("ui") + }); wss.on("connection", (ws) => { console.error(`[ws-bridge] UI client connected on port ${port}`); ws.on("message", (raw) => { @@ -7335,9 +8031,14 @@ function startUiBridge(port = WS_UI_PORT) { }); } function startBridge(port = WS_PORT) { - const wss = new import_websocket_server.default({ port }); + const currentVaultPaths = toolService.activeVaultPaths(); + const wss = new import_websocket_server.default({ + port, + host: LOOPBACK_HOST, + verifyClient: verifyBridgeRequest("tool") + }); wss.on("connection", (ws) => { - console.error(`[ws-bridge] Client connected (vault: ${VAULT_PATH})`); + console.error(`[ws-bridge] Client connected (vaults: ${currentVaultPaths.join(", ")})`); ws.on("message", async (raw) => { try { const response = await handleMessage(raw.toString()); @@ -7348,15 +8049,24 @@ function startBridge(port = WS_PORT) { }); ws.on("close", () => console.error("[ws-bridge] Client disconnected")); }); - console.error(`[ws-bridge] Listening on ws://localhost:${port}`); + console.error(`[ws-bridge] Listening on ws://${LOOPBACK_HOST}:${port}`); return wss; } var isMain = process.argv[1]?.endsWith("ws-bridge.js"); if (isMain) { - startUiBridge().then(() => startBridge()); + try { + toolService.activeVaultPaths(); + startUiBridge().then(() => startBridge()); + } catch (err) { + console.error(`[ws-bridge] ${err.message}`); + process.exit(1); + } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { + evaluateBridgeRequest, + isLoopbackAddress, + isTrustedUiOrigin, startBridge, startUiBridge }); diff --git a/src-tauri/gen/apple/assets/public-architecture/gls-system-architecture.json b/src-tauri/gen/apple/assets/public-architecture/gls-system-architecture.json new file mode 100644 index 0000000..ee7fd4d --- /dev/null +++ b/src-tauri/gen/apple/assets/public-architecture/gls-system-architecture.json @@ -0,0 +1,37 @@ +{ + "schema": "guanghu_public_architecture/v1", + "id": "GLS-SYS-ARCH-001", + "visibility": "public-safe", + "development_repository": { + "id": "REPO-008", + "url": "https://guanghulab.com/fifth-domain/bingshuo/hololake-platform" + }, + "route": [ + { + "id": "GLW-ENTRY-001", + "name_zh": "光湖世界主入口" + }, + { + "id": "FD-LANGUAGE-001", + "name_zh": "第五域" + }, + { + "id": "TCS-ROOT-001", + "name_zh": "TCS 语言人格模型" + }, + { + "id": "GLS-SYS-ARCH-001", + "name_zh": "GLS 光湖语言世界架构" + }, + { + "id": "ELH-LAMP-001", + "name_zh": "永恒湖心与小湖灯" + } + ], + "excludes": [ + "private domains", + "credentials", + "server addresses", + "personal channel identifiers" + ] +} diff --git a/src-tauri/gen/apple/hololake.xcodeproj/project.pbxproj b/src-tauri/gen/apple/hololake.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e394f4c --- /dev/null +++ b/src-tauri/gen/apple/hololake.xcodeproj/project.pbxproj @@ -0,0 +1,798 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 1C424E7EC0536E93EE157AAC /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 117563A1D670435510A187BE /* libapp.a */; }; + 4EB3A2423ABA624C5F6AABC9 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 907B33BB6C97C8B233870648 /* WebKit.framework */; }; + 5B7D0602465AAF6E20917B51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F920B9501682227EB4C5580B /* Assets.xcassets */; }; + 6E8F0F19612EDA6CB9899982 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = AEDF0B8CB7F0B7FCEB9DCAF3 /* assets */; }; + 80C327188314ED778AFE1A52 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9935075CB2F77C7FF5DEC9CD /* Metal.framework */; }; + 861B4CB9523FF4475E91C486 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45C9A6B16B4A4D83DB471F70 /* CoreGraphics.framework */; }; + 901ADE52A49AE72CB0DC74B5 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E8A91F6EDFE1DF22A75BAC5B /* UIKit.framework */; }; + 9548B4F08BAC94137D619B1D /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = FBD91C23BBFFCA204E30DF3C /* main.mm */; }; + C83DAFDDC40D6B13ED3067DD /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 173014947E8ACEAF0A6530C8 /* MetalKit.framework */; }; + CBA8DF76D32875939B11BB7E /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12EF895C7409F66F94087427 /* QuartzCore.framework */; }; + F65F7706A2A012C9C62AF91D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5559CAF2F000248F428011FC /* Security.framework */; }; + FD913972B6AE055A540F0B22 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 318D53F5BA017F1A6F56F5C5 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 00266099C3B2A3641E3EC2A8 /* git.rs */ = {isa = PBXFileReference; path = git.rs; sourceTree = ""; }; + 0CB4FCD517531E320A021759 /* subprocess.rs */ = {isa = PBXFileReference; path = subprocess.rs; sourceTree = ""; }; + 0D8C0BF75A2D8581CAC3168E /* views.rs */ = {isa = PBXFileReference; path = views.rs; sourceTree = ""; }; + 117563A1D670435510A187BE /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; + 12EF895C7409F66F94087427 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 1301493F0688650405F65A99 /* mcp.rs */ = {isa = PBXFileReference; path = mcp.rs; sourceTree = ""; }; + 134B1A0B803EC3636BBD0540 /* git_clone.rs */ = {isa = PBXFileReference; path = git_clone.rs; sourceTree = ""; }; + 13884CA1616DD620C003651A /* lifecycle_cmds.rs */ = {isa = PBXFileReference; path = lifecycle_cmds.rs; sourceTree = ""; }; + 14FA9E51ED6C6378565D3B0B /* telemetry.rs */ = {isa = PBXFileReference; path = telemetry.rs; sourceTree = ""; }; + 173014947E8ACEAF0A6530C8 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 187F125A14E9E017AD23214A /* ops.rs */ = {isa = PBXFileReference; path = ops.rs; sourceTree = ""; }; + 1CEF6C06C1287DD4AE2F9B51 /* kiro_cli.rs */ = {isa = PBXFileReference; path = kiro_cli.rs; sourceTree = ""; }; + 209FB6E2406EAC1084C003C5 /* vault_watcher.rs */ = {isa = PBXFileReference; path = vault_watcher.rs; sourceTree = ""; }; + 20F351BEF92336C8A1DF741F /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; + 211CF0973C74006DC242D0DA /* delete.rs */ = {isa = PBXFileReference; path = delete.rs; sourceTree = ""; }; + 2193E56695BF953A7DE197C0 /* menu.rs */ = {isa = PBXFileReference; path = menu.rs; sourceTree = ""; }; + 2295957108F171E85D7017A3 /* opencode_events_tests.rs */ = {isa = PBXFileReference; path = opencode_events_tests.rs; sourceTree = ""; }; + 240750F08BB20691A0E99759 /* opencode_events.rs */ = {isa = PBXFileReference; path = opencode_events.rs; sourceTree = ""; }; + 276294E0D7AC3E659AF8237C /* hololake_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = hololake_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 282A3A834C97BA5453CC9355 /* copilot_discovery.rs */ = {isa = PBXFileReference; path = copilot_discovery.rs; sourceTree = ""; }; + 28CD86F8A12878BAE5808ACE /* boundary.rs */ = {isa = PBXFileReference; path = boundary.rs; sourceTree = ""; }; + 29D602292E0D2C8E10E9B803 /* sheet.rs */ = {isa = PBXFileReference; path = sheet.rs; sourceTree = ""; }; + 2AFAE7D225D25B4BFC6F3370 /* rename_cmds.rs */ = {isa = PBXFileReference; path = rename_cmds.rs; sourceTree = ""; }; + 2B938E7363F949A622DD8397 /* ignored.rs */ = {isa = PBXFileReference; path = ignored.rs; sourceTree = ""; }; + 2E308CF651801982430AF883 /* search.rs */ = {isa = PBXFileReference; path = search.rs; sourceTree = ""; }; + 2FDC2B69E5C741777BB6F71D /* ai_agents.rs */ = {isa = PBXFileReference; path = ai_agents.rs; sourceTree = ""; }; + 312060FB9C2576AA2210903B /* remote_status.rs */ = {isa = PBXFileReference; path = remote_status.rs; sourceTree = ""; }; + 318D53F5BA017F1A6F56F5C5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 330EBF657FF49A5CEA691365 /* claude_cli.rs */ = {isa = PBXFileReference; path = claude_cli.rs; sourceTree = ""; }; + 3460EF754ED1B8654E45EFAB /* ai_models.rs */ = {isa = PBXFileReference; path = ai_models.rs; sourceTree = ""; }; + 36DED2A29CA856166131006E /* folders.rs */ = {isa = PBXFileReference; path = folders.rs; sourceTree = ""; }; + 38772E620BBC5538B49CEDFD /* file_cmds.rs */ = {isa = PBXFileReference; path = file_cmds.rs; sourceTree = ""; }; + 396792FAF69396D4D7456CE7 /* scan_and_file_access.rs */ = {isa = PBXFileReference; path = scan_and_file_access.rs; sourceTree = ""; }; + 3B2C90A787EF15F5C1DDC835 /* scan_cmds.rs */ = {isa = PBXFileReference; path = scan_cmds.rs; sourceTree = ""; }; + 3B3BB6E2A202EA03E7797343 /* view_relationships.rs */ = {isa = PBXFileReference; path = view_relationships.rs; sourceTree = ""; }; + 3D4D38ACF5A57BA7DDF2CC9B /* complex_frontmatter.rs */ = {isa = PBXFileReference; path = complex_frontmatter.rs; sourceTree = ""; }; + 40AF218FD76116EAF73079C2 /* opencode_cli.rs */ = {isa = PBXFileReference; path = opencode_cli.rs; sourceTree = ""; }; + 40CA5E0BA503491D8E109E03 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; + 42406A4FDE217A3D303E1490 /* opencode.rs */ = {isa = PBXFileReference; path = opencode.rs; sourceTree = ""; }; + 45412043345D5656E7D31DFC /* git_connect.rs */ = {isa = PBXFileReference; path = git_connect.rs; sourceTree = ""; }; + 45C9A6B16B4A4D83DB471F70 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 47E641E8D831982238FCF5D4 /* hermes_cli.rs */ = {isa = PBXFileReference; path = hermes_cli.rs; sourceTree = ""; }; + 47EDAF686EACF168E90E00C9 /* paths.rs */ = {isa = PBXFileReference; path = paths.rs; sourceTree = ""; }; + 497C22F10C21FCB4CF1978A0 /* line_stream.rs */ = {isa = PBXFileReference; path = line_stream.rs; sourceTree = ""; }; + 4E4BBC1CC65B04620FE5ACF7 /* memory.rs */ = {isa = PBXFileReference; path = memory.rs; sourceTree = ""; }; + 515B73736D1F5EA458FF4CBF /* claude_invocation.rs */ = {isa = PBXFileReference; path = claude_invocation.rs; sourceTree = ""; }; + 53E80AB4F48A6E53B2338B4B /* ai_model_tools.rs */ = {isa = PBXFileReference; path = ai_model_tools.rs; sourceTree = ""; }; + 5559CAF2F000248F428011FC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 5650F631903A7BFC6C180561 /* commit.rs */ = {isa = PBXFileReference; path = commit.rs; sourceTree = ""; }; + 5FE785C5A75724E0D528CADA /* pi_discovery.rs */ = {isa = PBXFileReference; path = pi_discovery.rs; sourceTree = ""; }; + 60442802617B8BC8EB982029 /* config_seed.rs */ = {isa = PBXFileReference; path = config_seed.rs; sourceTree = ""; }; + 609266ED8ADC8D1B5325517A /* opencode_discovery.rs */ = {isa = PBXFileReference; path = opencode_discovery.rs; sourceTree = ""; }; + 650FA149AD8459A2C245D0C7 /* pulse.rs */ = {isa = PBXFileReference; path = pulse.rs; sourceTree = ""; }; + 65A73B64D88239A170CF299A /* migration.rs */ = {isa = PBXFileReference; path = migration.rs; sourceTree = ""; }; + 68D09A4D9AEC45D6F8190469 /* version.rs */ = {isa = PBXFileReference; path = version.rs; sourceTree = ""; }; + 6AF0BF4F3FC570BF7E65B741 /* remote_url.rs */ = {isa = PBXFileReference; path = remote_url.rs; sourceTree = ""; }; + 6ECAB30D8912656B5E11959A /* view_migration.rs */ = {isa = PBXFileReference; path = view_migration.rs; sourceTree = ""; }; + 6ED92E21C98368571ED6CA7D /* history.rs */ = {isa = PBXFileReference; path = history.rs; sourceTree = ""; }; + 70A93129B1958D310CC38F63 /* type_templates.rs */ = {isa = PBXFileReference; path = type_templates.rs; sourceTree = ""; }; + 722773963A61C9E3DB99B304 /* type_and_links.rs */ = {isa = PBXFileReference; path = type_and_links.rs; sourceTree = ""; }; + 729AD052E90B9A83246D49A6 /* remote_config.rs */ = {isa = PBXFileReference; path = remote_config.rs; sourceTree = ""; }; + 731A143588CD5D2D412BA799 /* connect.rs */ = {isa = PBXFileReference; path = connect.rs; sourceTree = ""; }; + 7526367BFD671D87C04135E7 /* pi_events_tests.rs */ = {isa = PBXFileReference; path = pi_events_tests.rs; sourceTree = ""; }; + 75445E11B4A11A3605177EF9 /* provider.rs */ = {isa = PBXFileReference; path = provider.rs; sourceTree = ""; }; + 7599B204D83DCC8DC75458B1 /* frontmatter_regression_tests.rs */ = {isa = PBXFileReference; path = frontmatter_regression_tests.rs; sourceTree = ""; }; + 7876AB1696C6A473C0CA60DD /* dates.rs */ = {isa = PBXFileReference; path = dates.rs; sourceTree = ""; }; + 79E99FECA35482A58E9929E8 /* upstream.rs */ = {isa = PBXFileReference; path = upstream.rs; sourceTree = ""; }; + 7FEB8ACB3EFE38A9C2273981 /* remote.rs */ = {isa = PBXFileReference; path = remote.rs; sourceTree = ""; }; + 814BEE45F3957BA070B8A6FE /* keys.rs */ = {isa = PBXFileReference; path = keys.rs; sourceTree = ""; }; + 83CCD3A86C61E070EB9C029B /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; + 889853675108C435C5DB7358 /* real_vault_consistency.rs */ = {isa = PBXFileReference; path = real_vault_consistency.rs; sourceTree = ""; }; + 8969FA0D7729542C8FE8ED93 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; + 8A655DE1999D25A828FAB0A2 /* file.rs */ = {isa = PBXFileReference; path = file.rs; sourceTree = ""; }; + 8B033502734FD8E7F64B8885 /* parsing.rs */ = {isa = PBXFileReference; path = parsing.rs; sourceTree = ""; }; + 8CBB3EB747F6DF8B0D8F6DB7 /* trash.rs */ = {isa = PBXFileReference; path = trash.rs; sourceTree = ""; }; + 8D6486437DAA2C59C1085A64 /* settings.rs */ = {isa = PBXFileReference; path = settings.rs; sourceTree = ""; }; + 8E04F42BA9D33CA9F7DAD3F0 /* antigravity_cli.rs */ = {isa = PBXFileReference; path = antigravity_cli.rs; sourceTree = ""; }; + 8EF52B0E3E02145BEF8B164C /* vault.rs */ = {isa = PBXFileReference; path = vault.rs; sourceTree = ""; }; + 907B33BB6C97C8B233870648 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 93CF3E5A2C399C696CB83C8E /* path_identity.rs */ = {isa = PBXFileReference; path = path_identity.rs; sourceTree = ""; }; + 93D4A6ABB25A10CF045919B9 /* credentials.rs */ = {isa = PBXFileReference; path = credentials.rs; sourceTree = ""; }; + 960E88B1AC470035ADEC0DD8 /* mcp_config.rs */ = {isa = PBXFileReference; path = mcp_config.rs; sourceTree = ""; }; + 975BD8B6821A8FA0356DFE31 /* hololake_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = hololake_iOS.entitlements; sourceTree = ""; }; + 98555F139A6093466C0DD80E /* extraction.rs */ = {isa = PBXFileReference; path = extraction.rs; sourceTree = ""; }; + 9935075CB2F77C7FF5DEC9CD /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 99B3DA704DB835E530007E84 /* antigravity_config.rs */ = {isa = PBXFileReference; path = antigravity_config.rs; sourceTree = ""; }; + 9C5AB398816652B7DA146489 /* windows_cmd_shim.rs */ = {isa = PBXFileReference; path = windows_cmd_shim.rs; sourceTree = ""; }; + 9DF2BE1316C12B537A5E99EF /* title_sync.rs */ = {isa = PBXFileReference; path = title_sync.rs; sourceTree = ""; }; + 9F804D206D6339ADD094B1CB /* window_state.rs */ = {isa = PBXFileReference; path = window_state.rs; sourceTree = ""; }; + A0B9AEB056A7B2713B0EF52A /* hermes_discovery.rs */ = {isa = PBXFileReference; path = hermes_discovery.rs; sourceTree = ""; }; + A3BFA73F44481A097F35045D /* parsing_tests.rs */ = {isa = PBXFileReference; path = parsing_tests.rs; sourceTree = ""; }; + A484D6E506A65D914AFD65C7 /* pdf_export.rs */ = {isa = PBXFileReference; path = pdf_export.rs; sourceTree = ""; }; + A75935B7F652A3077F4E2C07 /* ops_update_tests.rs */ = {isa = PBXFileReference; path = ops_update_tests.rs; sourceTree = ""; }; + A7A456C38BF1BB9A93999A38 /* author.rs */ = {isa = PBXFileReference; path = author.rs; sourceTree = ""; }; + A7D7C3B4DBD0E1E2A07CF92A /* display_metadata.rs */ = {isa = PBXFileReference; path = display_metadata.rs; sourceTree = ""; }; + A8A3F1EFA0B974EE7214ECB3 /* relationship_key_tests.rs */ = {isa = PBXFileReference; path = relationship_key_tests.rs; sourceTree = ""; }; + AEDF0B8CB7F0B7FCEB9DCAF3 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; + AF044E0B28E443DBCFB4BFBF /* view_date_filters.rs */ = {isa = PBXFileReference; path = view_date_filters.rs; sourceTree = ""; }; + B0B4AE2937945F1896E1B205 /* mod_tests.rs */ = {isa = PBXFileReference; path = mod_tests.rs; sourceTree = ""; }; + B37489EAD8111AF691B4E56D /* opencode_config.rs */ = {isa = PBXFileReference; path = opencode_config.rs; sourceTree = ""; }; + B377452F2570C8D916B24737 /* rename.rs */ = {isa = PBXFileReference; path = rename.rs; sourceTree = ""; }; + B7223A7822564745FD4B7CDD /* app_icon.rs */ = {isa = PBXFileReference; path = app_icon.rs; sourceTree = ""; }; + B7436FF982457F75FD04F370 /* ai.rs */ = {isa = PBXFileReference; path = ai.rs; sourceTree = ""; }; + B7EEF25B97EA865DC2F2B16F /* app_icon.rs */ = {isa = PBXFileReference; path = app_icon.rs; sourceTree = ""; }; + BA9780D7C82253E700210AD1 /* pi_cli.rs */ = {isa = PBXFileReference; path = pi_cli.rs; sourceTree = ""; }; + BB2C976547D1CF4F9CEDE8E1 /* antigravity_discovery.rs */ = {isa = PBXFileReference; path = antigravity_discovery.rs; sourceTree = ""; }; + BB68BFCC620A099FBDFE5234 /* folders.rs */ = {isa = PBXFileReference; path = folders.rs; sourceTree = ""; }; + BD404CAA02D081312BABF0B2 /* view_cmds.rs */ = {isa = PBXFileReference; path = view_cmds.rs; sourceTree = ""; }; + BE0762EE42FE319CB09FC6C1 /* archival_metadata.rs */ = {isa = PBXFileReference; path = archival_metadata.rs; sourceTree = ""; }; + BE7B1AB7B47F47F165573044 /* cli_agent_runtime.rs */ = {isa = PBXFileReference; path = cli_agent_runtime.rs; sourceTree = ""; }; + BEE7A0C1F8EE18EDAB31905A /* copilot_cli.rs */ = {isa = PBXFileReference; path = copilot_cli.rs; sourceTree = ""; }; + C0BC9BAFF0BD9AA5E75BDD77 /* system.rs */ = {isa = PBXFileReference; path = system.rs; sourceTree = ""; }; + C2C6C397E5BBD3E4640FF236 /* filename_rules.rs */ = {isa = PBXFileReference; path = filename_rules.rs; sourceTree = ""; }; + C3B314E7CF8AF8A86D1C6997 /* app_updater.rs */ = {isa = PBXFileReference; path = app_updater.rs; sourceTree = ""; }; + C594B406D338F9F56959FDDD /* shell_env.rs */ = {isa = PBXFileReference; path = shell_env.rs; sourceTree = ""; }; + C697AFAFE64F32FEDC51A843 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; + C6BFF4E47F1E8F4F966FC6F3 /* yaml.rs */ = {isa = PBXFileReference; path = yaml.rs; sourceTree = ""; }; + C75EB2059FB3A38D3E884D36 /* status.rs */ = {isa = PBXFileReference; path = status.rs; sourceTree = ""; }; + C80317E70943D0F66AF6B18E /* view_tests.rs */ = {isa = PBXFileReference; path = view_tests.rs; sourceTree = ""; }; + CABC6880374F0998B1C69D95 /* clone.rs */ = {isa = PBXFileReference; path = clone.rs; sourceTree = ""; }; + CAE745BD7E8696D9BB969024 /* runtime.rs */ = {isa = PBXFileReference; path = runtime.rs; sourceTree = ""; }; + CCD024076CA11EAC1A9EE692 /* entry.rs */ = {isa = PBXFileReference; path = entry.rs; sourceTree = ""; }; + CCD92D5F8DB1DF1EB5DCAA2E /* relationships.rs */ = {isa = PBXFileReference; path = relationships.rs; sourceTree = ""; }; + D4CB7D29501EF5F65790FAAC /* ai_agent_processes.rs */ = {isa = PBXFileReference; path = ai_agent_processes.rs; sourceTree = ""; }; + D5217A3CE5BA0EA50F116B1E /* pi_config.rs */ = {isa = PBXFileReference; path = pi_config.rs; sourceTree = ""; }; + D5B2D88CFABF8B79C82390E5 /* getting_started.rs */ = {isa = PBXFileReference; path = getting_started.rs; sourceTree = ""; }; + D5C705C3B8C2B76A98702F77 /* journal_type_visibility.rs */ = {isa = PBXFileReference; path = journal_type_visibility.rs; sourceTree = ""; }; + D5CDF98D6115D2AB430BF773 /* view_value_conversions.rs */ = {isa = PBXFileReference; path = view_value_conversions.rs; sourceTree = ""; }; + D6217DF5DA776022D831FE3B /* remote_branch_tests.rs */ = {isa = PBXFileReference; path = remote_branch_tests.rs; sourceTree = ""; }; + D6B1FE5AF362BAC634875019 /* conflict.rs */ = {isa = PBXFileReference; path = conflict.rs; sourceTree = ""; }; + DC17DBB96089A85760E6D190 /* linux_appimage.rs */ = {isa = PBXFileReference; path = linux_appimage.rs; sourceTree = ""; }; + E0733C95F0183F1919FB705F /* rename_transaction.rs */ = {isa = PBXFileReference; path = rename_transaction.rs; sourceTree = ""; }; + E19D7FEEBC114189FCF0F84A /* image.rs */ = {isa = PBXFileReference; path = image.rs; sourceTree = ""; }; + E19EFD664F7375F6A27495CC /* app_config.rs */ = {isa = PBXFileReference; path = app_config.rs; sourceTree = ""; }; + E201C712C4EF00B2BFAD0455 /* system_metadata_tests.rs */ = {isa = PBXFileReference; path = system_metadata_tests.rs; sourceTree = ""; }; + E2B08AF815595124CA76FE33 /* cache.rs */ = {isa = PBXFileReference; path = cache.rs; sourceTree = ""; }; + E411974CEDAB1E163FFF1704 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; + E502EAEA6030504F91B1B6B8 /* command.rs */ = {isa = PBXFileReference; path = command.rs; sourceTree = ""; }; + E7E43D90B97E284B2F6A717D /* frontmatter.rs */ = {isa = PBXFileReference; path = frontmatter.rs; sourceTree = ""; }; + E8A91F6EDFE1DF22A75BAC5B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + EE36F56C627BC172C7CCA065 /* pi_events.rs */ = {isa = PBXFileReference; path = pi_events.rs; sourceTree = ""; }; + EEA7BC2EB818A793D68CECA1 /* frontmatter_cmds.rs */ = {isa = PBXFileReference; path = frontmatter_cmds.rs; sourceTree = ""; }; + EFA6483B1269D2EC0DBD4D5A /* modified_dates_tests.rs */ = {isa = PBXFileReference; path = modified_dates_tests.rs; sourceTree = ""; }; + F0D5D034E9AE877D8F630233 /* kiro_discovery.rs */ = {isa = PBXFileReference; path = kiro_discovery.rs; sourceTree = ""; }; + F1238B11A81DABC7B28747B9 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; + F7D0E6E67B410BBCCF64F197 /* file_url.rs */ = {isa = PBXFileReference; path = file_url.rs; sourceTree = ""; }; + F86E7BC210B3629F404B302E /* basics.rs */ = {isa = PBXFileReference; path = basics.rs; sourceTree = ""; }; + F920B9501682227EB4C5580B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F95BF811C401A0626DEDA0B9 /* clipboard.rs */ = {isa = PBXFileReference; path = clipboard.rs; sourceTree = ""; }; + FA7C034475B4AB303F8E400D /* folder_and_file_kind.rs */ = {isa = PBXFileReference; path = folder_and_file_kind.rs; sourceTree = ""; }; + FB31ABA594014CA2DEFEBD9C /* runtime.rs */ = {isa = PBXFileReference; path = runtime.rs; sourceTree = ""; }; + FBB7A51D7CCC05D3348D1190 /* vault_list.rs */ = {isa = PBXFileReference; path = vault_list.rs; sourceTree = ""; }; + FBD91C23BBFFCA204E30DF3C /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + FE4F7CE296F76FBE49B288EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + FF3044079CC906291942C508 /* codex_cli.rs */ = {isa = PBXFileReference; path = codex_cli.rs; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 593F74524C732216FFF7CCEE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1C424E7EC0536E93EE157AAC /* libapp.a in Frameworks */, + 861B4CB9523FF4475E91C486 /* CoreGraphics.framework in Frameworks */, + 80C327188314ED778AFE1A52 /* Metal.framework in Frameworks */, + C83DAFDDC40D6B13ED3067DD /* MetalKit.framework in Frameworks */, + CBA8DF76D32875939B11BB7E /* QuartzCore.framework in Frameworks */, + F65F7706A2A012C9C62AF91D /* Security.framework in Frameworks */, + 901ADE52A49AE72CB0DC74B5 /* UIKit.framework in Frameworks */, + 4EB3A2423ABA624C5F6AABC9 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08417072C62A5EFA12FD5F46 /* Sources */ = { + isa = PBXGroup; + children = ( + 58726A1E666110EC0B3F52EB /* hololake */, + ); + path = Sources; + sourceTree = ""; + }; + 12067A245B774568941E3362 /* vault */ = { + isa = PBXGroup; + children = ( + E2B08AF815595124CA76FE33 /* cache.rs */, + 60442802617B8BC8EB982029 /* config_seed.rs */, + CCD024076CA11EAC1A9EE692 /* entry.rs */, + 8A655DE1999D25A828FAB0A2 /* file.rs */, + C2C6C397E5BBD3E4640FF236 /* filename_rules.rs */, + BB68BFCC620A099FBDFE5234 /* folders.rs */, + 7599B204D83DCC8DC75458B1 /* frontmatter_regression_tests.rs */, + E7E43D90B97E284B2F6A717D /* frontmatter.rs */, + D5B2D88CFABF8B79C82390E5 /* getting_started.rs */, + 2B938E7363F949A622DD8397 /* ignored.rs */, + E19D7FEEBC114189FCF0F84A /* image.rs */, + 65A73B64D88239A170CF299A /* migration.rs */, + B0B4AE2937945F1896E1B205 /* mod_tests.rs */, + 83CCD3A86C61E070EB9C029B /* mod.rs */, + EFA6483B1269D2EC0DBD4D5A /* modified_dates_tests.rs */, + A3BFA73F44481A097F35045D /* parsing_tests.rs */, + 8B033502734FD8E7F64B8885 /* parsing.rs */, + 93CF3E5A2C399C696CB83C8E /* path_identity.rs */, + A8A3F1EFA0B974EE7214ECB3 /* relationship_key_tests.rs */, + E0733C95F0183F1919FB705F /* rename_transaction.rs */, + B377452F2570C8D916B24737 /* rename.rs */, + E201C712C4EF00B2BFAD0455 /* system_metadata_tests.rs */, + 9DF2BE1316C12B537A5E99EF /* title_sync.rs */, + 8CBB3EB747F6DF8B0D8F6DB7 /* trash.rs */, + 70A93129B1958D310CC38F63 /* type_templates.rs */, + AF044E0B28E443DBCFB4BFBF /* view_date_filters.rs */, + 6ECAB30D8912656B5E11959A /* view_migration.rs */, + 3B3BB6E2A202EA03E7797343 /* view_relationships.rs */, + C80317E70943D0F66AF6B18E /* view_tests.rs */, + D5CDF98D6115D2AB430BF773 /* view_value_conversions.rs */, + 0D8C0BF75A2D8581CAC3168E /* views.rs */, + 50520A5D121D878EA47CE8BA /* mod_tests */, + ); + path = vault; + sourceTree = ""; + }; + 19CFD7CB8FCEE414B74B2502 /* Products */ = { + isa = PBXGroup; + children = ( + 276294E0D7AC3E659AF8237C /* hololake_iOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 21A05FADFF3FF8F606BDB27A /* commands */ = { + isa = PBXGroup; + children = ( + B7436FF982457F75FD04F370 /* ai.rs */, + B7EEF25B97EA865DC2F2B16F /* app_icon.rs */, + F95BF811C401A0626DEDA0B9 /* clipboard.rs */, + 211CF0973C74006DC242D0DA /* delete.rs */, + 36DED2A29CA856166131006E /* folders.rs */, + 134B1A0B803EC3636BBD0540 /* git_clone.rs */, + 45412043345D5656E7D31DFC /* git_connect.rs */, + 00266099C3B2A3641E3EC2A8 /* git.rs */, + 4E4BBC1CC65B04620FE5ACF7 /* memory.rs */, + 40CA5E0BA503491D8E109E03 /* mod.rs */, + A484D6E506A65D914AFD65C7 /* pdf_export.rs */, + CAE745BD7E8696D9BB969024 /* runtime.rs */, + 29D602292E0D2C8E10E9B803 /* sheet.rs */, + C0BC9BAFF0BD9AA5E75BDD77 /* system.rs */, + 8EF52B0E3E02145BEF8B164C /* vault.rs */, + 68D09A4D9AEC45D6F8190469 /* version.rs */, + B51A68A32590206DA8AA27D5 /* vault */, + ); + path = commands; + sourceTree = ""; + }; + 261971315A9566290DE630F0 = { + isa = PBXGroup; + children = ( + AEDF0B8CB7F0B7FCEB9DCAF3 /* assets */, + F920B9501682227EB4C5580B /* Assets.xcassets */, + 318D53F5BA017F1A6F56F5C5 /* LaunchScreen.storyboard */, + E74D33E97ED632AB99B558BA /* hololake_iOS */, + 08417072C62A5EFA12FD5F46 /* Sources */, + 9DCEF43B595AA2B6525AE44D /* src */, + 773BAFFF06B76986EB0B2276 /* Frameworks */, + 19CFD7CB8FCEE414B74B2502 /* Products */, + ); + sourceTree = ""; + }; + 402658380699CDF599C7F26D /* bindings */ = { + isa = PBXGroup; + children = ( + E411974CEDAB1E163FFF1704 /* bindings.h */, + ); + path = bindings; + sourceTree = ""; + }; + 50520A5D121D878EA47CE8BA /* mod_tests */ = { + isa = PBXGroup; + children = ( + BE0762EE42FE319CB09FC6C1 /* archival_metadata.rs */, + F86E7BC210B3629F404B302E /* basics.rs */, + 3D4D38ACF5A57BA7DDF2CC9B /* complex_frontmatter.rs */, + A7D7C3B4DBD0E1E2A07CF92A /* display_metadata.rs */, + FA7C034475B4AB303F8E400D /* folder_and_file_kind.rs */, + D5C705C3B8C2B76A98702F77 /* journal_type_visibility.rs */, + 889853675108C435C5DB7358 /* real_vault_consistency.rs */, + CCD92D5F8DB1DF1EB5DCAA2E /* relationships.rs */, + 396792FAF69396D4D7456CE7 /* scan_and_file_access.rs */, + 722773963A61C9E3DB99B304 /* type_and_links.rs */, + ); + path = mod_tests; + sourceTree = ""; + }; + 58726A1E666110EC0B3F52EB /* hololake */ = { + isa = PBXGroup; + children = ( + FBD91C23BBFFCA204E30DF3C /* main.mm */, + 402658380699CDF599C7F26D /* bindings */, + ); + path = hololake; + sourceTree = ""; + }; + 60FF32980F43B2EE095972C0 /* cli_agent_runtime */ = { + isa = PBXGroup; + children = ( + 497C22F10C21FCB4CF1978A0 /* line_stream.rs */, + 960E88B1AC470035ADEC0DD8 /* mcp_config.rs */, + C594B406D338F9F56959FDDD /* shell_env.rs */, + 9C5AB398816652B7DA146489 /* windows_cmd_shim.rs */, + ); + path = cli_agent_runtime; + sourceTree = ""; + }; + 773BAFFF06B76986EB0B2276 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 45C9A6B16B4A4D83DB471F70 /* CoreGraphics.framework */, + 117563A1D670435510A187BE /* libapp.a */, + 9935075CB2F77C7FF5DEC9CD /* Metal.framework */, + 173014947E8ACEAF0A6530C8 /* MetalKit.framework */, + 12EF895C7409F66F94087427 /* QuartzCore.framework */, + 5559CAF2F000248F428011FC /* Security.framework */, + E8A91F6EDFE1DF22A75BAC5B /* UIKit.framework */, + 907B33BB6C97C8B233870648 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9DCEF43B595AA2B6525AE44D /* src */ = { + isa = PBXGroup; + children = ( + D4CB7D29501EF5F65790FAAC /* ai_agent_processes.rs */, + 2FDC2B69E5C741777BB6F71D /* ai_agents.rs */, + 53E80AB4F48A6E53B2338B4B /* ai_model_tools.rs */, + 3460EF754ED1B8654E45EFAB /* ai_models.rs */, + 8E04F42BA9D33CA9F7DAD3F0 /* antigravity_cli.rs */, + 99B3DA704DB835E530007E84 /* antigravity_config.rs */, + BB2C976547D1CF4F9CEDE8E1 /* antigravity_discovery.rs */, + E19EFD664F7375F6A27495CC /* app_config.rs */, + B7223A7822564745FD4B7CDD /* app_icon.rs */, + C3B314E7CF8AF8A86D1C6997 /* app_updater.rs */, + 330EBF657FF49A5CEA691365 /* claude_cli.rs */, + 515B73736D1F5EA458FF4CBF /* claude_invocation.rs */, + BE7B1AB7B47F47F165573044 /* cli_agent_runtime.rs */, + FF3044079CC906291942C508 /* codex_cli.rs */, + BEE7A0C1F8EE18EDAB31905A /* copilot_cli.rs */, + 282A3A834C97BA5453CC9355 /* copilot_discovery.rs */, + 47E641E8D831982238FCF5D4 /* hermes_cli.rs */, + A0B9AEB056A7B2713B0EF52A /* hermes_discovery.rs */, + 1CEF6C06C1287DD4AE2F9B51 /* kiro_cli.rs */, + F0D5D034E9AE877D8F630233 /* kiro_discovery.rs */, + 8969FA0D7729542C8FE8ED93 /* lib.rs */, + DC17DBB96089A85760E6D190 /* linux_appimage.rs */, + 20F351BEF92336C8A1DF741F /* main.rs */, + 1301493F0688650405F65A99 /* mcp.rs */, + 2193E56695BF953A7DE197C0 /* menu.rs */, + 40AF218FD76116EAF73079C2 /* opencode_cli.rs */, + B37489EAD8111AF691B4E56D /* opencode_config.rs */, + 609266ED8ADC8D1B5325517A /* opencode_discovery.rs */, + 2295957108F171E85D7017A3 /* opencode_events_tests.rs */, + 240750F08BB20691A0E99759 /* opencode_events.rs */, + BA9780D7C82253E700210AD1 /* pi_cli.rs */, + D5217A3CE5BA0EA50F116B1E /* pi_config.rs */, + 5FE785C5A75724E0D528CADA /* pi_discovery.rs */, + 7526367BFD671D87C04135E7 /* pi_events_tests.rs */, + EE36F56C627BC172C7CCA065 /* pi_events.rs */, + 2E308CF651801982430AF883 /* search.rs */, + 8D6486437DAA2C59C1085A64 /* settings.rs */, + 14FA9E51ED6C6378565D3B0B /* telemetry.rs */, + FBB7A51D7CCC05D3348D1190 /* vault_list.rs */, + 209FB6E2406EAC1084C003C5 /* vault_watcher.rs */, + 9F804D206D6339ADD094B1CB /* window_state.rs */, + 60FF32980F43B2EE095972C0 /* cli_agent_runtime */, + 21A05FADFF3FF8F606BDB27A /* commands */, + BBC3D44F61CCC5727BF19E80 /* frontmatter */, + A6232B4AF7C89DFF2781B6B9 /* git */, + F3FD87814A10D493D431F563 /* mcp */, + 12067A245B774568941E3362 /* vault */, + ); + name = src; + path = ../../src; + sourceTree = ""; + }; + A6232B4AF7C89DFF2781B6B9 /* git */ = { + isa = PBXGroup; + children = ( + A7A456C38BF1BB9A93999A38 /* author.rs */, + CABC6880374F0998B1C69D95 /* clone.rs */, + E502EAEA6030504F91B1B6B8 /* command.rs */, + 5650F631903A7BFC6C180561 /* commit.rs */, + D6B1FE5AF362BAC634875019 /* conflict.rs */, + 731A143588CD5D2D412BA799 /* connect.rs */, + 93D4A6ABB25A10CF045919B9 /* credentials.rs */, + 7876AB1696C6A473C0CA60DD /* dates.rs */, + F7D0E6E67B410BBCCF64F197 /* file_url.rs */, + 6ED92E21C98368571ED6CA7D /* history.rs */, + C697AFAFE64F32FEDC51A843 /* mod.rs */, + 75445E11B4A11A3605177EF9 /* provider.rs */, + 650FA149AD8459A2C245D0C7 /* pulse.rs */, + D6217DF5DA776022D831FE3B /* remote_branch_tests.rs */, + 729AD052E90B9A83246D49A6 /* remote_config.rs */, + 312060FB9C2576AA2210903B /* remote_status.rs */, + 6AF0BF4F3FC570BF7E65B741 /* remote_url.rs */, + 7FEB8ACB3EFE38A9C2273981 /* remote.rs */, + C75EB2059FB3A38D3E884D36 /* status.rs */, + 79E99FECA35482A58E9929E8 /* upstream.rs */, + ); + path = git; + sourceTree = ""; + }; + B51A68A32590206DA8AA27D5 /* vault */ = { + isa = PBXGroup; + children = ( + 28CD86F8A12878BAE5808ACE /* boundary.rs */, + 38772E620BBC5538B49CEDFD /* file_cmds.rs */, + EEA7BC2EB818A793D68CECA1 /* frontmatter_cmds.rs */, + 13884CA1616DD620C003651A /* lifecycle_cmds.rs */, + 2AFAE7D225D25B4BFC6F3370 /* rename_cmds.rs */, + 3B2C90A787EF15F5C1DDC835 /* scan_cmds.rs */, + BD404CAA02D081312BABF0B2 /* view_cmds.rs */, + ); + path = vault; + sourceTree = ""; + }; + BBC3D44F61CCC5727BF19E80 /* frontmatter */ = { + isa = PBXGroup; + children = ( + 814BEE45F3957BA070B8A6FE /* keys.rs */, + F1238B11A81DABC7B28747B9 /* mod.rs */, + A75935B7F652A3077F4E2C07 /* ops_update_tests.rs */, + 187F125A14E9E017AD23214A /* ops.rs */, + C6BFF4E47F1E8F4F966FC6F3 /* yaml.rs */, + ); + path = frontmatter; + sourceTree = ""; + }; + E74D33E97ED632AB99B558BA /* hololake_iOS */ = { + isa = PBXGroup; + children = ( + 975BD8B6821A8FA0356DFE31 /* hololake_iOS.entitlements */, + FE4F7CE296F76FBE49B288EE /* Info.plist */, + ); + path = hololake_iOS; + sourceTree = ""; + }; + F3FD87814A10D493D431F563 /* mcp */ = { + isa = PBXGroup; + children = ( + 98555F139A6093466C0DD80E /* extraction.rs */, + 42406A4FDE217A3D303E1490 /* opencode.rs */, + 47EDAF686EACF168E90E00C9 /* paths.rs */, + FB31ABA594014CA2DEFEBD9C /* runtime.rs */, + 0CB4FCD517531E320A021759 /* subprocess.rs */, + ); + path = mcp; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6C4312178BC9716ADE77034C /* hololake_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = EEC68AC09159031B028A1569 /* Build configuration list for PBXNativeTarget "hololake_iOS" */; + buildPhases = ( + F988B1E05D3A98CE4158F78F /* Build Rust Code */, + 3C1B9767C4B07812F55109AF /* Sources */, + D90E1D8095C4568BAF5D5AA1 /* Resources */, + 593F74524C732216FFF7CCEE /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = hololake_iOS; + packageProductDependencies = ( + ); + productName = hololake_iOS; + productReference = 276294E0D7AC3E659AF8237C /* hololake_iOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + DEA98173D2F51277A646B35A /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 6C4312178BC9716ADE77034C = { + DevelopmentTeam = 825A9L3G7Q; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 2BEE742C341A4A355A6B098D /* Build configuration list for PBXProject "hololake" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 261971315A9566290DE630F0; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 19CFD7CB8FCEE414B74B2502 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6C4312178BC9716ADE77034C /* hololake_iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D90E1D8095C4568BAF5D5AA1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B7D0602465AAF6E20917B51 /* Assets.xcassets in Resources */, + FD913972B6AE055A540F0B22 /* LaunchScreen.storyboard in Resources */, + 6E8F0F19612EDA6CB9899982 /* assets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F988B1E05D3A98CE4158F78F /* Build Rust Code */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Rust Code"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", + "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "npm run -- tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3C1B9767C4B07812F55109AF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9548B4F08BAC94137D619B1D /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 61501B3E5E957A706DE67F4C /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = debug; + }; + 6EAA5FC6E93A3B966028D70B /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = ( + arm64, + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = hololake_iOS/hololake_iOS.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 825A9L3G7Q; + ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = hololake_iOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.guanghulab.hololake; + PRODUCT_NAME = "HoloLake Era"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = release; + }; + 991E2C78C8FC11EC349E63ED /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = release; + }; + A1BFC54BE32FD396178DD6BF /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = ( + arm64, + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = hololake_iOS/hololake_iOS.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 825A9L3G7Q; + ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = hololake_iOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.guanghulab.hololake; + PRODUCT_NAME = "HoloLake Era"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2BEE742C341A4A355A6B098D /* Build configuration list for PBXProject "hololake" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 61501B3E5E957A706DE67F4C /* debug */, + 991E2C78C8FC11EC349E63ED /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; + EEC68AC09159031B028A1569 /* Build configuration list for PBXNativeTarget "hololake_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A1BFC54BE32FD396178DD6BF /* debug */, + 6EAA5FC6E93A3B966028D70B /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = DEA98173D2F51277A646B35A /* Project object */; +} diff --git a/src-tauri/gen/apple/HoloLake.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src-tauri/gen/apple/hololake.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from src-tauri/gen/apple/HoloLake.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to src-tauri/gen/apple/hololake.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/src-tauri/gen/apple/HoloLake.xcodeproj/xcshareddata/xcschemes/HoloLake_iOS.xcscheme b/src-tauri/gen/apple/hololake.xcodeproj/xcshareddata/xcschemes/hololake_iOS.xcscheme similarity index 81% rename from src-tauri/gen/apple/HoloLake.xcodeproj/xcshareddata/xcschemes/HoloLake_iOS.xcscheme rename to src-tauri/gen/apple/hololake.xcodeproj/xcshareddata/xcschemes/hololake_iOS.xcscheme index 525723f..5e05c0c 100644 --- a/src-tauri/gen/apple/HoloLake.xcodeproj/xcshareddata/xcschemes/HoloLake_iOS.xcscheme +++ b/src-tauri/gen/apple/hololake.xcodeproj/xcshareddata/xcschemes/hololake_iOS.xcscheme @@ -15,10 +15,10 @@ buildForAnalyzing = "YES"> + BlueprintIdentifier = "6C4312178BC9716ADE77034C" + BuildableName = "hololake_iOS.app" + BlueprintName = "hololake_iOS" + ReferencedContainer = "container:hololake.xcodeproj"> @@ -32,10 +32,10 @@ + BlueprintIdentifier = "6C4312178BC9716ADE77034C" + BuildableName = "hololake_iOS.app" + BlueprintName = "hololake_iOS" + ReferencedContainer = "container:hololake.xcodeproj"> @@ -69,10 +69,10 @@ runnableDebuggingMode = "0"> + BlueprintIdentifier = "6C4312178BC9716ADE77034C" + BuildableName = "hololake_iOS.app" + BlueprintName = "hololake_iOS" + ReferencedContainer = "container:hololake.xcodeproj"> @@ -100,10 +100,10 @@ runnableDebuggingMode = "0"> + BlueprintIdentifier = "6C4312178BC9716ADE77034C" + BuildableName = "hololake_iOS.app" + BlueprintName = "hololake_iOS" + ReferencedContainer = "container:hololake.xcodeproj"> diff --git a/src-tauri/gen/apple/laputa_iOS/Info.plist b/src-tauri/gen/apple/hololake_iOS/Info.plist similarity index 98% rename from src-tauri/gen/apple/laputa_iOS/Info.plist rename to src-tauri/gen/apple/hololake_iOS/Info.plist index b073bdd..2ca7d10 100644 --- a/src-tauri/gen/apple/laputa_iOS/Info.plist +++ b/src-tauri/gen/apple/hololake_iOS/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.1.7 CFBundleVersion - 17 + 20 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/src-tauri/gen/apple/laputa_iOS/laputa_iOS.entitlements b/src-tauri/gen/apple/hololake_iOS/hololake_iOS.entitlements similarity index 95% rename from src-tauri/gen/apple/laputa_iOS/laputa_iOS.entitlements rename to src-tauri/gen/apple/hololake_iOS/hololake_iOS.entitlements index 0c67376..21e2f8d 100644 --- a/src-tauri/gen/apple/laputa_iOS/laputa_iOS.entitlements +++ b/src-tauri/gen/apple/hololake_iOS/hololake_iOS.entitlements @@ -2,4 +2,4 @@ - + \ No newline at end of file diff --git a/src-tauri/gen/apple/laputa.xcodeproj/project.pbxproj b/src-tauri/gen/apple/laputa.xcodeproj/project.pbxproj deleted file mode 100644 index d236f98..0000000 --- a/src-tauri/gen/apple/laputa.xcodeproj/project.pbxproj +++ /dev/null @@ -1,566 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 1662961784944D479C9E1B0D /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E3FC10F8A144BB158574C774 /* Metal.framework */; }; - 1C55EF711F7ACEAC173C344E /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CF0D094B920E757356FC3AD /* libapp.a */; }; - 4A94802D0295ECD102C21BA1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A34681D9D4CE45CDF83ABA59 /* UIKit.framework */; }; - 534929E6730B15780117F21F /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 05C322F5E5E2ACB0CBC644EE /* MetalKit.framework */; }; - 5AAD7159BB4D20588044B3BC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C29783A8194F596483AD35 /* Security.framework */; }; - 74A9072960AF0CB774BEB749 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AB6F4BD2E768B4B6EDF7EA4 /* QuartzCore.framework */; }; - 799F0FF5DD8053B287C9CA46 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D8616031E45547278A88B60 /* main.mm */; }; - 7EF9AA3C7F58D96F21743FB4 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D33799362DF41B226D7635 /* WebKit.framework */; }; - 9BD440BEBCDBD633C9EE2C26 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A70EB7513BFD74428B2C6ED1 /* CoreGraphics.framework */; }; - A2279B6B796C960A2951E7C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F8E52CF938A81FD9ABC1B23F /* Assets.xcassets */; }; - DACF8A23100E74CEA0BE1ED9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9274F38B9AA0A9A44A3CBEAA /* LaunchScreen.storyboard */; }; - E0FB400669725FC03B8E9B39 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 1BC846C6372AE88FE28EA984 /* assets */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 05C322F5E5E2ACB0CBC644EE /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; - 077AD05C7FB4A784D360399B /* commands.rs */ = {isa = PBXFileReference; path = commands.rs; sourceTree = ""; }; - 0AE733EAA82134418C0AC89F /* api.rs */ = {isa = PBXFileReference; path = api.rs; sourceTree = ""; }; - 0CA7012668D8606FE9709D6D /* yaml.rs */ = {isa = PBXFileReference; path = yaml.rs; sourceTree = ""; }; - 0FB1053AF590466CC8971679 /* parsing.rs */ = {isa = PBXFileReference; path = parsing.rs; sourceTree = ""; }; - 16CCC72C07657AEBC320B099 /* auth.rs */ = {isa = PBXFileReference; path = auth.rs; sourceTree = ""; }; - 1BC846C6372AE88FE28EA984 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; - 1D65CB9525835A85C38D3136 /* migration.rs */ = {isa = PBXFileReference; path = migration.rs; sourceTree = ""; }; - 27D33799362DF41B226D7635 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - 2D31CB45BB367FD6A7FA0B67 /* rename.rs */ = {isa = PBXFileReference; path = rename.rs; sourceTree = ""; }; - 2D38A9417EA6B0A133D0D55F /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - 34F1C40770F83FDC21036505 /* laputa_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = laputa_iOS.entitlements; sourceTree = ""; }; - 3AB6F4BD2E768B4B6EDF7EA4 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - 3CF0D094B920E757356FC3AD /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; - 3D8616031E45547278A88B60 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; - 3E23075B718E12551904D3B6 /* frontmatter.rs */ = {isa = PBXFileReference; path = frontmatter.rs; sourceTree = ""; }; - 43C29783A8194F596483AD35 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - 4AAEE16E1AC0E472E9EAF907 /* conflict.rs */ = {isa = PBXFileReference; path = conflict.rs; sourceTree = ""; }; - 4F4C2FE8D3697D180E1D4304 /* entry.rs */ = {isa = PBXFileReference; path = entry.rs; sourceTree = ""; }; - 50B53E0FE0FC2A5515B48A28 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; - 517A653F5A674050430C1167 /* commit.rs */ = {isa = PBXFileReference; path = commit.rs; sourceTree = ""; }; - 5800BA1A3E5C03124E02E946 /* title_sync.rs */ = {isa = PBXFileReference; path = title_sync.rs; sourceTree = ""; }; - 5F28F393EB20BA7F8F2102EF /* config_seed.rs */ = {isa = PBXFileReference; path = config_seed.rs; sourceTree = ""; }; - 6288FD61B10C36115B12B4A4 /* cache.rs */ = {isa = PBXFileReference; path = cache.rs; sourceTree = ""; }; - 692CE75FC8E670478C4209C8 /* remote.rs */ = {isa = PBXFileReference; path = remote.rs; sourceTree = ""; }; - 6A5C3DA38846B440DD56E98A /* claude_cli.rs */ = {isa = PBXFileReference; path = claude_cli.rs; sourceTree = ""; }; - 6AC39E9370FCCB01E81EC824 /* ai_chat.rs */ = {isa = PBXFileReference; path = ai_chat.rs; sourceTree = ""; }; - 6F67F5CDD6BB137126C5AABB /* menu.rs */ = {isa = PBXFileReference; path = menu.rs; sourceTree = ""; }; - 71EDBD38A067DA56FD3E375F /* getting_started.rs */ = {isa = PBXFileReference; path = getting_started.rs; sourceTree = ""; }; - 7D94F0F22CD503903B63FFE7 /* file.rs */ = {isa = PBXFileReference; path = file.rs; sourceTree = ""; }; - 7FCDD5B9C3D021A45C402A43 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - 81C8EBDD5CAD14B7F366AA54 /* trash.rs */ = {isa = PBXFileReference; path = trash.rs; sourceTree = ""; }; - 84ECD74B7841E52AD6549346 /* image.rs */ = {isa = PBXFileReference; path = image.rs; sourceTree = ""; }; - 87F0375DE367A8192708C2D8 /* pulse.rs */ = {isa = PBXFileReference; path = pulse.rs; sourceTree = ""; }; - 9274F38B9AA0A9A44A3CBEAA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - 94B5B4BBA850E2BCAE3E4CC7 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - A17AA2469F233241B0DF8BE3 /* vault_list.rs */ = {isa = PBXFileReference; path = vault_list.rs; sourceTree = ""; }; - A34681D9D4CE45CDF83ABA59 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - A70EB7513BFD74428B2C6ED1 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - AA64014FA2D16950AD4DF619 /* mod.rs */ = {isa = PBXFileReference; path = mod.rs; sourceTree = ""; }; - ACCD8318FA6543BA013E37E4 /* ops.rs */ = {isa = PBXFileReference; path = ops.rs; sourceTree = ""; }; - BA9FA5D7A7A7574FF74589C8 /* telemetry.rs */ = {isa = PBXFileReference; path = telemetry.rs; sourceTree = ""; }; - BB4E3DD2D3BA34BB86C908B6 /* status.rs */ = {isa = PBXFileReference; path = status.rs; sourceTree = ""; }; - C39B460DE1A3B78A0937BFDD /* settings.rs */ = {isa = PBXFileReference; path = settings.rs; sourceTree = ""; }; - C80E8FDDA822F4F167B601C1 /* mod_tests.rs */ = {isa = PBXFileReference; path = mod_tests.rs; sourceTree = ""; }; - CEDEEAE68B0DB4CD43ED89A8 /* laputa_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = laputa_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; - D0900BA51E68887E5DA395B7 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; - DDC76F9C00A29C8C52345249 /* history.rs */ = {isa = PBXFileReference; path = history.rs; sourceTree = ""; }; - E1428C0A0CC22F741B7A4A0C /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; - E36BAA25D76F212157A06395 /* search.rs */ = {isa = PBXFileReference; path = search.rs; sourceTree = ""; }; - E3FC10F8A144BB158574C774 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - F87684B058BB66A215F98DCF /* mcp.rs */ = {isa = PBXFileReference; path = mcp.rs; sourceTree = ""; }; - F8E52CF938A81FD9ABC1B23F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - F9FF9FD3CEB0EB658CA17305 /* clone.rs */ = {isa = PBXFileReference; path = clone.rs; sourceTree = ""; }; - FBB08655AD50E526ACEAE8A2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 50978AC1976616D3342459ED /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1C55EF711F7ACEAC173C344E /* libapp.a in Frameworks */, - 9BD440BEBCDBD633C9EE2C26 /* CoreGraphics.framework in Frameworks */, - 1662961784944D479C9E1B0D /* Metal.framework in Frameworks */, - 534929E6730B15780117F21F /* MetalKit.framework in Frameworks */, - 74A9072960AF0CB774BEB749 /* QuartzCore.framework in Frameworks */, - 5AAD7159BB4D20588044B3BC /* Security.framework in Frameworks */, - 4A94802D0295ECD102C21BA1 /* UIKit.framework in Frameworks */, - 7EF9AA3C7F58D96F21743FB4 /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 021259EC384B5D897634CFF8 /* Products */ = { - isa = PBXGroup; - children = ( - CEDEEAE68B0DB4CD43ED89A8 /* laputa_iOS.app */, - ); - name = Products; - sourceTree = ""; - }; - 109126B51C7DDFCA7CD8B9EA /* Sources */ = { - isa = PBXGroup; - children = ( - DC6461BBBF0BDFBB28352CF9 /* laputa */, - ); - path = Sources; - sourceTree = ""; - }; - 1189728653A76416E01221D7 /* Frameworks */ = { - isa = PBXGroup; - children = ( - A70EB7513BFD74428B2C6ED1 /* CoreGraphics.framework */, - 3CF0D094B920E757356FC3AD /* libapp.a */, - E3FC10F8A144BB158574C774 /* Metal.framework */, - 05C322F5E5E2ACB0CBC644EE /* MetalKit.framework */, - 3AB6F4BD2E768B4B6EDF7EA4 /* QuartzCore.framework */, - 43C29783A8194F596483AD35 /* Security.framework */, - A34681D9D4CE45CDF83ABA59 /* UIKit.framework */, - 27D33799362DF41B226D7635 /* WebKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 314C2D9104FDC1DE81AC36AD = { - isa = PBXGroup; - children = ( - 1BC846C6372AE88FE28EA984 /* assets */, - F8E52CF938A81FD9ABC1B23F /* Assets.xcassets */, - 9274F38B9AA0A9A44A3CBEAA /* LaunchScreen.storyboard */, - FD3AA61436162714FAEC418D /* Externals */, - 56AC65AB30CBAEB08FD782A6 /* laputa_iOS */, - 109126B51C7DDFCA7CD8B9EA /* Sources */, - FA63A5561E1F084B5F13E265 /* src */, - 1189728653A76416E01221D7 /* Frameworks */, - 021259EC384B5D897634CFF8 /* Products */, - ); - sourceTree = ""; - }; - 56AC65AB30CBAEB08FD782A6 /* laputa_iOS */ = { - isa = PBXGroup; - children = ( - FBB08655AD50E526ACEAE8A2 /* Info.plist */, - 34F1C40770F83FDC21036505 /* laputa_iOS.entitlements */, - ); - path = laputa_iOS; - sourceTree = ""; - }; - 5D80057D94E84FA08C34A8B0 /* frontmatter */ = { - isa = PBXGroup; - children = ( - 94B5B4BBA850E2BCAE3E4CC7 /* mod.rs */, - ACCD8318FA6543BA013E37E4 /* ops.rs */, - 0CA7012668D8606FE9709D6D /* yaml.rs */, - ); - path = frontmatter; - sourceTree = ""; - }; - 6A109CE663DEBC9C99A47D0B /* github */ = { - isa = PBXGroup; - children = ( - 0AE733EAA82134418C0AC89F /* api.rs */, - 16CCC72C07657AEBC320B099 /* auth.rs */, - F9FF9FD3CEB0EB658CA17305 /* clone.rs */, - 2D38A9417EA6B0A133D0D55F /* mod.rs */, - ); - path = github; - sourceTree = ""; - }; - 85465A1346B035D3C3DA6236 /* git */ = { - isa = PBXGroup; - children = ( - 517A653F5A674050430C1167 /* commit.rs */, - 4AAEE16E1AC0E472E9EAF907 /* conflict.rs */, - DDC76F9C00A29C8C52345249 /* history.rs */, - 7FCDD5B9C3D021A45C402A43 /* mod.rs */, - 87F0375DE367A8192708C2D8 /* pulse.rs */, - 692CE75FC8E670478C4209C8 /* remote.rs */, - BB4E3DD2D3BA34BB86C908B6 /* status.rs */, - ); - path = git; - sourceTree = ""; - }; - A44D28635D2D14F8A9E644BC /* bindings */ = { - isa = PBXGroup; - children = ( - D0900BA51E68887E5DA395B7 /* bindings.h */, - ); - path = bindings; - sourceTree = ""; - }; - AE0DF532B1E0DADFEDFE383A /* vault */ = { - isa = PBXGroup; - children = ( - 6288FD61B10C36115B12B4A4 /* cache.rs */, - 5F28F393EB20BA7F8F2102EF /* config_seed.rs */, - 4F4C2FE8D3697D180E1D4304 /* entry.rs */, - 7D94F0F22CD503903B63FFE7 /* file.rs */, - 3E23075B718E12551904D3B6 /* frontmatter.rs */, - 71EDBD38A067DA56FD3E375F /* getting_started.rs */, - 84ECD74B7841E52AD6549346 /* image.rs */, - 1D65CB9525835A85C38D3136 /* migration.rs */, - C80E8FDDA822F4F167B601C1 /* mod_tests.rs */, - AA64014FA2D16950AD4DF619 /* mod.rs */, - 0FB1053AF590466CC8971679 /* parsing.rs */, - 2D31CB45BB367FD6A7FA0B67 /* rename.rs */, - 5800BA1A3E5C03124E02E946 /* title_sync.rs */, - 81C8EBDD5CAD14B7F366AA54 /* trash.rs */, - ); - path = vault; - sourceTree = ""; - }; - DC6461BBBF0BDFBB28352CF9 /* laputa */ = { - isa = PBXGroup; - children = ( - 3D8616031E45547278A88B60 /* main.mm */, - A44D28635D2D14F8A9E644BC /* bindings */, - ); - path = laputa; - sourceTree = ""; - }; - FA63A5561E1F084B5F13E265 /* src */ = { - isa = PBXGroup; - children = ( - 6AC39E9370FCCB01E81EC824 /* ai_chat.rs */, - 6A5C3DA38846B440DD56E98A /* claude_cli.rs */, - 077AD05C7FB4A784D360399B /* commands.rs */, - E1428C0A0CC22F741B7A4A0C /* lib.rs */, - 50B53E0FE0FC2A5515B48A28 /* main.rs */, - F87684B058BB66A215F98DCF /* mcp.rs */, - 6F67F5CDD6BB137126C5AABB /* menu.rs */, - E36BAA25D76F212157A06395 /* search.rs */, - C39B460DE1A3B78A0937BFDD /* settings.rs */, - BA9FA5D7A7A7574FF74589C8 /* telemetry.rs */, - A17AA2469F233241B0DF8BE3 /* vault_list.rs */, - 5D80057D94E84FA08C34A8B0 /* frontmatter */, - 85465A1346B035D3C3DA6236 /* git */, - 6A109CE663DEBC9C99A47D0B /* github */, - AE0DF532B1E0DADFEDFE383A /* vault */, - ); - name = src; - path = ../../src; - sourceTree = ""; - }; - FD3AA61436162714FAEC418D /* Externals */ = { - isa = PBXGroup; - children = ( - ); - path = Externals; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - DAF25BBC5B5E1A3750AF7F8E /* laputa_iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = F8ED295B21C1E1D8BE1CBAB2 /* Build configuration list for PBXNativeTarget "laputa_iOS" */; - buildPhases = ( - 72D053FB9C1C9BF219367CA2 /* Build Rust Code */, - 2FA764A26841E6D98B919C1A /* Sources */, - 46BE17F676877ABAEEFB1ACF /* Resources */, - 50978AC1976616D3342459ED /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = laputa_iOS; - packageProductDependencies = ( - ); - productName = laputa_iOS; - productReference = CEDEEAE68B0DB4CD43ED89A8 /* laputa_iOS.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 2E2C059AB505791AB84204D8 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - TargetAttributes = { - }; - }; - buildConfigurationList = 02446155D1A36B8D10250861 /* Build configuration list for PBXProject "laputa" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = 314C2D9104FDC1DE81AC36AD; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 021259EC384B5D897634CFF8 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - DAF25BBC5B5E1A3750AF7F8E /* laputa_iOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 46BE17F676877ABAEEFB1ACF /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A2279B6B796C960A2951E7C5 /* Assets.xcassets in Resources */, - DACF8A23100E74CEA0BE1ED9 /* LaunchScreen.storyboard in Resources */, - E0FB400669725FC03B8E9B39 /* assets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 72D053FB9C1C9BF219367CA2 /* Build Rust Code */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Build Rust Code"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", - "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "npm run -- tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 2FA764A26841E6D98B919C1A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 799F0FF5DD8053B287C9CA46 /* main.mm in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 3CC8721F138177C2FE029D95 /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = debug; - }; - 44D6538EB5E448ECCC49165C /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - }; - name = release; - }; - 487BF5A97BEC469EBD248159 /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = laputa_iOS/laputa_iOS.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = laputa_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - PRODUCT_BUNDLE_IDENTIFIER = club.refactoring.tolaria; - PRODUCT_NAME = Tolaria; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = arm64; - }; - name = debug; - }; - FE69C38FBEB0C14D7D5AC27E /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = laputa_iOS/laputa_iOS.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = laputa_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; - PRODUCT_BUNDLE_IDENTIFIER = club.refactoring.tolaria; - PRODUCT_NAME = Tolaria; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = arm64; - }; - name = release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 02446155D1A36B8D10250861 /* Build configuration list for PBXProject "laputa" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3CC8721F138177C2FE029D95 /* debug */, - 44D6538EB5E448ECCC49165C /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; - F8ED295B21C1E1D8BE1CBAB2 /* Build configuration list for PBXNativeTarget "laputa_iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 487BF5A97BEC469EBD248159 /* debug */, - FE69C38FBEB0C14D7D5AC27E /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; -/* End XCConfigurationList section */ - }; - rootObject = 2E2C059AB505791AB84204D8 /* Project object */; -} diff --git a/src-tauri/gen/apple/laputa.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src-tauri/gen/apple/laputa.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/src-tauri/gen/apple/laputa.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src-tauri/gen/apple/laputa.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/src-tauri/gen/apple/laputa.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index ac90d5a..0000000 --- a/src-tauri/gen/apple/laputa.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,10 +0,0 @@ - - - - - BuildSystemType - Original - DisableBuildSystemDeprecationDiagnostic - - - diff --git a/src-tauri/gen/apple/laputa.xcodeproj/xcshareddata/xcschemes/laputa_iOS.xcscheme b/src-tauri/gen/apple/laputa.xcodeproj/xcshareddata/xcschemes/laputa_iOS.xcscheme deleted file mode 100644 index 086ea44..0000000 --- a/src-tauri/gen/apple/laputa.xcodeproj/xcshareddata/xcschemes/laputa_iOS.xcscheme +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src-tauri/gen/apple/project.yml b/src-tauri/gen/apple/project.yml index 7666603..e7bb68a 100644 --- a/src-tauri/gen/apple/project.yml +++ b/src-tauri/gen/apple/project.yml @@ -1,4 +1,4 @@ -name: HoloLake +name: hololake options: bundleIdPrefix: com.guanghulab.hololake deploymentTarget: @@ -12,6 +12,8 @@ settingGroups: base: PRODUCT_NAME: HoloLake Era PRODUCT_BUNDLE_IDENTIFIER: com.guanghulab.hololake + DEVELOPMENT_TEAM: 825A9L3G7Q + CODE_SIGN_STYLE: Automatic targetTemplates: app: type: application @@ -24,20 +26,19 @@ targetTemplates: settings: groups: [app] targets: - HoloLake_iOS: + hololake_iOS: type: application platform: iOS sources: - path: Sources - path: Assets.xcassets - - path: Externals - - path: laputa_iOS + - path: hololake_iOS - path: assets buildPhase: resources type: folder - path: LaunchScreen.storyboard info: - path: laputa_iOS/Info.plist + path: hololake_iOS/Info.plist properties: LSRequiresIPhoneOS: true UILaunchStoryboardName: LaunchScreen @@ -51,9 +52,9 @@ targets: - UIInterfaceOrientationLandscapeRight CFBundleDisplayName: HoloLake CFBundleShortVersionString: 0.1.7 - CFBundleVersion: "17" + CFBundleVersion: "20" entitlements: - path: laputa_iOS/laputa_iOS.entitlements + path: hololake_iOS/hololake_iOS.entitlements scheme: environmentVariables: RUST_BACKTRACE: full diff --git a/src-tauri/src/app_icon.rs b/src-tauri/src/app_icon.rs index b1254b5..a5c55fb 100644 --- a/src-tauri/src/app_icon.rs +++ b/src-tauri/src/app_icon.rs @@ -1,3 +1,4 @@ +#[cfg(desktop)] use tauri::Manager; const LIGHT_ICON_BYTES: &[u8] = include_bytes!("../icons/512x512.png"); @@ -31,15 +32,25 @@ pub fn update_app_icon_for_theme( theme_mode: &str, ) -> Result<(), String> { let icon_bytes = AppIconMode::parse(theme_mode)?.png_bytes(); - let image = tauri::image::Image::from_bytes(icon_bytes) - .map_err(|err| format!("Failed to decode app icon: {err}"))?; - - for window in app_handle.webview_windows().into_values() { - window - .set_icon(image.clone()) - .map_err(|err| format!("Failed to update window icon: {err}"))?; + #[cfg(mobile)] + { + let _ = app_handle; + let _ = icon_bytes; + return Ok(()); + } + + #[cfg(desktop)] + { + let image = tauri::image::Image::from_bytes(icon_bytes) + .map_err(|err| format!("Failed to decode app icon: {err}"))?; + + for window in app_handle.webview_windows().into_values() { + window + .set_icon(image.clone()) + .map_err(|err| format!("Failed to update window icon: {err}"))?; + } + Ok(()) } - Ok(()) } #[cfg(test)] diff --git a/src-tauri/src/commands/ai.rs b/src-tauri/src/commands/ai.rs index 32dbbc5..70c629a 100644 --- a/src-tauri/src/commands/ai.rs +++ b/src-tauri/src/commands/ai.rs @@ -1,4 +1,3 @@ -#[cfg(desktop)] use crate::ai_agents::{AiAgentStreamRequest, AiAgentsStatus}; #[cfg(desktop)] use crate::ai_models::{AiModelProviderTestRequest, AiModelStreamRequest}; diff --git a/src-tauri/src/commands/runtime.rs b/src-tauri/src/commands/runtime.rs index 185f5d5..46a0cdf 100644 --- a/src-tauri/src/commands/runtime.rs +++ b/src-tauri/src/commands/runtime.rs @@ -22,10 +22,17 @@ pub fn should_use_external_media_preview() -> bool { } #[tauri::command] +#[cfg(desktop)] pub fn print_current_webview(window: tauri::WebviewWindow) -> Result<(), String> { map_print_result(window.print()) } +#[tauri::command] +#[cfg(mobile)] +pub fn print_current_webview() -> Result<(), String> { + Err("System printing is not available in this mobile build yet.".into()) +} + #[cfg(test)] mod tests { use super::{map_print_result, should_use_external_media_preview_for_appimage}; diff --git a/src-tauri/src/commands/vault/scan_cmds.rs b/src-tauri/src/commands/vault/scan_cmds.rs index e53b91c..8d9db1a 100644 --- a/src-tauri/src/commands/vault/scan_cmds.rs +++ b/src-tauri/src/commands/vault/scan_cmds.rs @@ -120,7 +120,10 @@ pub async fn reload_vault( path: String, ) -> Result, String> { let path = expand_tilde(&path).into_owned(); + #[cfg(desktop)] crate::sync_vault_asset_scope(&app_handle, Path::new(&path))?; + #[cfg(mobile)] + let _ = app_handle; tokio::task::spawn_blocking(move || { let vault_path = Path::new(&path); vault::invalidate_cache(vault_path); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 64a787d..e5634b4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -338,7 +338,7 @@ fn setup_desktop_plugins(app: &mut tauri::App) -> Result<(), Box bool { diff --git a/src-tauri/tauri.ios.conf.json b/src-tauri/tauri.ios.conf.json index 01221b4..9c231e6 100644 --- a/src-tauri/tauri.ios.conf.json +++ b/src-tauri/tauri.ios.conf.json @@ -7,7 +7,7 @@ "beforeBuildCommand": "pnpm build" }, "app": { - "macOSPrivateApi": false, + "macOSPrivateApi": true, "windows": [ { "title": "HoloLake Era",