Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fb47cb2
Improve llama reuse and typing eval coverage
mc-hamster Sep 17, 2026
b345ced
Add local visual context refresh and phrase evals
mc-hamster Sep 17, 2026
2c6163b
Add isolated McHamster release identity and signed packaging
mc-hamster Sep 17, 2026
76e9fc7
Add contextual phrase eval benchmark
mc-hamster Sep 17, 2026
fcbbcd4
Add Cotabby 3 project and baseline workflow
mc-hamster Sep 17, 2026
342a76d
Add three-worker phrase evaluation and first full baseline
mc-hamster Sep 17, 2026
cd99305
Improve word completion and publish McHamster 0.6.2.2 sources
mc-hamster Sep 17, 2026
585179b
Recognize Codex desktop app in browser detection
mc-hamster Sep 17, 2026
bae841e
added benchmarks
mc-hamster Sep 18, 2026
8ee08b0
Improve typing continuity and add word-boundary preference
mc-hamster Sep 24, 2026
4dc3937
Keep predictions continuous across typing and corrections
mc-hamster Sep 24, 2026
b75871d
Resolve focused web composers in the Codex desktop app
mc-hamster Sep 24, 2026
bdcf8d0
Prepare McHamster 0.6.2.3 release notes
mc-hamster Sep 24, 2026
341350e
Rebrand the app as CoHamster and prepare 0.6.3 release
mc-hamster Sep 24, 2026
b978e68
Finish CoHamster GitHub branding and repository links
mc-hamster Sep 24, 2026
477b5b1
Add Xcode projects and shared schemes
mc-hamster Sep 24, 2026
3112850
Refresh menu bar icon and CI checkout
mc-hamster Sep 24, 2026
50da85e
Keep Apple Intelligence continuation examples focused on prose
mc-hamster Sep 24, 2026
76e4687
Reuse predictions while typing with a default-on settings toggle
mc-hamster Sep 24, 2026
941114f
Separate prediction delivery waiting and satisfy SwiftLint formatting
mc-hamster Sep 24, 2026
cf8e3c2
Unify app identity and isolate conversation state
mc-hamster Sep 24, 2026
738192c
Reduce Apple prediction delay and repair missing word separators
mc-hamster Sep 25, 2026
143ab32
Unify local and release versions for CoHamster 0.6.5
mc-hamster Sep 25, 2026
f780483
Add model qualification and usability evals
mc-hamster Sep 25, 2026
2d072d8
Improve llama token healing
mc-hamster Sep 25, 2026
9bc7531
Prepare CoHamster 0.6.6 with compatible development signing
mc-hamster Sep 25, 2026
1da7a16
Record full 1337-scenario benchmark results
mc-hamster Sep 25, 2026
954bbf3
Merge Cotabby upstream ec466b6 host font and margin improvements
mc-hamster Sep 25, 2026
3626520
Fix CI coordinator fixture teardown and SwiftLint violations
mc-hamster Sep 25, 2026
b3efbb9
Merge pull request #1 from mc-hamster/codex/fix-ci-test-teardown
mc-hamster Sep 25, 2026
e8470e6
Recognize writable Mail web composers as editable fields
mc-hamster Sep 25, 2026
ff7cf71
Adopt Fastlane release pipeline and CI
mc-hamster Sep 25, 2026
c74e8ba
Handle Mail NBSP in marker selection flow
mc-hamster Sep 25, 2026
d8cb85a
Add cross-project collaboration note
mc-hamster Sep 25, 2026
10dc37b
Prepare CoHamster 0.6.7 beta 1 release
mc-hamster Sep 26, 2026
fb100e1
Restore Cotabby branding and remove obsolete project artifacts
mc-hamster Sep 26, 2026
4dc3294
Keep Fastlane personal and restore native contributor workflows
mc-hamster Sep 26, 2026
1aa491b
Remove CoHamster personal Fastlane pipeline from contribution
mc-hamster Sep 26, 2026
ce3db58
Restore upstream lifecycle tooling around integrated app changes
mc-hamster Sep 26, 2026
f46b0fe
Remove remaining fork model path and clarify endpoint privacy
mc-hamster Sep 26, 2026
6cbf853
Specify schemes for workspace dependency resolution in CI
mc-hamster Sep 26, 2026
de574c7
Group screen context below clipboard context in menu
mc-hamster Sep 26, 2026
3351598
Remove duplicate per-app enable control from menu
mc-hamster Sep 26, 2026
df52ae0
Address capture freshness, DMG build isolation, and attribution review
mc-hamster Sep 26, 2026
f31abbd
Stop retained coordinator fixtures during test teardown
mc-hamster Sep 26, 2026
805b0f4
Request a fresh prediction when a prepared continuation loses its target
akramj13 Sep 27, 2026
6e1b244
Repair missing separators before contractions
akramj13 Sep 27, 2026
65695cb
Match the live preview's privacy note to the selected engine
akramj13 Sep 27, 2026
ad7b5bf
Drop a held Tab once the user types, moves, or dismisses
akramj13 Sep 27, 2026
a6bd88f
Re-check rebased predictions before offering them at the new caret
akramj13 Sep 27, 2026
b16ff65
Keep a composer that grows in place as the same field
akramj13 Sep 27, 2026
34469e3
Keep typo fixes to text the user just typed
akramj13 Sep 27, 2026
565f026
Match prepared continuations in long fields
akramj13 Sep 27, 2026
d59330b
Keep mid-word insertions unhealed so the whitespace mask applies
akramj13 Sep 27, 2026
18adc0f
Make the local build scripts safe to rerun
akramj13 Sep 27, 2026
6b2ffa7
Build phrase evals from the prepared workspace by default
akramj13 Sep 27, 2026
aa970eb
Limit shifted-window continuation matches to cut windows
akramj13 Sep 27, 2026
5ef6c33
Speak only for Cotabby's storage in the endpoint preview note
akramj13 Sep 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 11 additions & 5 deletions .claude/skills/ship/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ the required-status-check protection so the owner can merge directly.
3. **Validate before pushing.** Run the narrowest useful checks, broaden if shared
behavior changed:
```bash
swiftlint lint --quiet
xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build
scripts/prepare_cotabby_workspace.sh
xcodebuild build-for-testing -workspace build/cotabby-dependencies/Cotabby.xcworkspace \
-scheme Cotabby -configuration Debug -destination 'platform=macOS' \
-onlyUsePackageVersionsFromResolvedFile -derivedDataPath build/DerivedData CODE_SIGNING_ALLOWED=NO
xcodebuild test-without-building -workspace build/cotabby-dependencies/Cotabby.xcworkspace \
-scheme Cotabby -configuration Debug -destination 'platform=macOS' \
-onlyUsePackageVersionsFromResolvedFile -derivedDataPath build/DerivedData \
-skip-testing:CotabbyTests/FoundationModelDriftEvalTests
python3 -m unittest discover -s scripts/tests
swiftlint --strict
```
For test-affecting changes also run `build-for-testing`. Local `test` execution
often fails on a **Team ID / signing mismatch** — that's an environment issue, not
a code failure; report it and rely on `build-for-testing` succeeding.
Report a failed test launch as a failed verification, even if build-for-testing passed.
- **XcodeGen:** `project.yml` is the source of truth and `Cotabby.xcodeproj` is
generated. New files under `Cotabby/` and `CotabbyTests/` are auto-discovered —
no project edit needed. Only structural changes (targets, build settings,
Expand Down
4 changes: 2 additions & 2 deletions .codex/environments/environment.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ name = "Cotabby"
script = ""

[[actions]]
name = "Run"
name = "Open Xcode"
icon = "run"
command = "./scripts/build_and_run.sh"
command = "scripts/prepare_cotabby_workspace.sh && open build/cotabby-dependencies/Cotabby.xcworkspace"
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee:
buy_me_a_coffee:
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
10 changes: 4 additions & 6 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ The diff already shows what; this section should explain why.
What you actually ran and what you actually saw, not what you intended to run.
Examples:

xcodebuild test -project Cotabby.xcodeproj -scheme Cotabby \
-destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO
# ** TEST SUCCEEDED ** N tests, 0 failures

swiftlint lint --config .swiftlint.yml --quiet
# exit 0
# Xcode build/test commands from CONTRIBUTING.md
# ** TEST EXECUTE SUCCEEDED **; N tests, zero failures
python3 -m unittest discover -s scripts/tests
swiftlint --strict

For UI changes, attach a screenshot or short screen recording.
For changes that can't be verified end-to-end yet, say so explicitly.
Expand Down
16 changes: 13 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ jobs:
with:
xcode-version: latest-stable

- name: Prepare matching inference dependency
run: scripts/prepare_cotabby_workspace.sh

- name: Toolchain info
run: |
xcodebuild -version
Expand All @@ -51,7 +54,9 @@ jobs:
- name: Resolve Swift package dependencies
run: |
xcodebuild \
-project Cotabby.xcodeproj \
-workspace build/cotabby-dependencies/Cotabby.xcworkspace \
-scheme Cotabby \
-derivedDataPath build/DerivedData \
-resolvePackageDependencies

- name: Verify Sparkle version pin
Expand All @@ -72,7 +77,7 @@ jobs:
pbxproj_version="$(echo "${sparkle_block}" | grep -A1 'kind = exactVersion' | grep 'version' | sed 's/.*= //;s/;.*//' | tr -d '[:space:]')"
resolved_version="$(python3 -c "
import json, sys
resolved = json.load(open('Cotabby.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved'))
resolved = json.load(open('build/cotabby-dependencies/Cotabby.xcworkspace/xcshareddata/swiftpm/Package.resolved'))
pins = {p['identity']: p for p in resolved['pins']}
if 'sparkle' not in pins:
print('sparkle not found in Package.resolved. Available: ' + ', '.join(sorted(pins.keys())), file=sys.stderr)
Expand All @@ -99,9 +104,14 @@ jobs:
- name: Build
run: |
xcodebuild \
-project Cotabby.xcodeproj \
-workspace build/cotabby-dependencies/Cotabby.xcworkspace \
-scheme Cotabby \
-configuration Debug \
-destination 'platform=macOS' \
-derivedDataPath build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build

- name: Clean build cache
if: always()
run: rm -rf build/DerivedData
17 changes: 13 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ concurrency:
env:
APP_NAME: Cotabby
DMG_NAME: Cotabby.dmg
PROJECT_PATH: Cotabby.xcodeproj
WORKSPACE_PATH: build/cotabby-dependencies/Cotabby.xcworkspace
SCHEME: Cotabby
CONFIGURATION: Release
PAGES_CUSTOM_DOMAIN: updates.cotabby.app
Expand Down Expand Up @@ -83,6 +83,9 @@ jobs:
with:
python-version: "3.13"

- name: Prepare matching inference dependency
run: scripts/prepare_cotabby_workspace.sh

- name: Toolchain info
run: |
xcodebuild -version
Expand All @@ -93,7 +96,9 @@ jobs:
- name: Resolve Swift package dependencies
run: |
xcodebuild \
-project "${PROJECT_PATH}" \
-workspace "${WORKSPACE_PATH}" \
-scheme "${SCHEME}" \
-derivedDataPath build/DerivedData \
-resolvePackageDependencies

- name: Install DMG packaging dependencies
Expand All @@ -110,7 +115,7 @@ jobs:

sparkle_version="$(python3 -c "
import json, sys
resolved = json.load(open('Cotabby.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved'))
resolved = json.load(open('build/cotabby-dependencies/Cotabby.xcworkspace/xcshareddata/swiftpm/Package.resolved'))
pins = {p['identity']: p for p in resolved['pins']}
if 'sparkle' not in pins:
print('Sparkle not found in Package.resolved', file=sys.stderr)
Expand Down Expand Up @@ -308,7 +313,7 @@ jobs:
# "0.0.1-beta" is kept as-is so users see the full version
# string including the channel tag.
xcodebuild archive \
-project "${PROJECT_PATH}" \
-workspace "${WORKSPACE_PATH}" \
-scheme "${SCHEME}" \
-configuration "${CONFIGURATION}" \
-archivePath "build/${APP_NAME}.xcarchive" \
Expand Down Expand Up @@ -430,6 +435,10 @@ jobs:
path: build/${{ env.APP_NAME }}.xcarchive/
if-no-files-found: ignore

- name: Clean build cache
if: always()
run: rm -rf build/DerivedData

# ---------------------------------------------------------------------------
# 2. NOTARIZE
# Downloads the signed DMG, submits to Apple notarization, polls until
Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
with:
xcode-version: latest-stable

- name: Prepare matching inference dependency
run: scripts/prepare_cotabby_workspace.sh

- name: Toolchain info
run: |
xcodebuild -version
Expand All @@ -53,14 +56,22 @@ jobs:
# and the test bundle's project settings already mirror this. Without
# it, xcodebuild would refuse to embed the test xctest bundle into the
# host app's Contents/PlugIns for loading.
- name: Test Python tooling
run: python3 -m unittest discover -s scripts/tests

- name: Run tests
# FoundationModelDriftEvalTests is excluded here: it drives the real on-device Apple
# Intelligence model (absent on CI runners) and is non-deterministic. It self-skips via
# RUN_FM_EVAL too, but skipping it explicitly keeps the CI intent visible.
run: |
xcodebuild test \
-project Cotabby.xcodeproj \
-workspace build/cotabby-dependencies/Cotabby.xcworkspace \
-scheme Cotabby \
-destination 'platform=macOS' \
-derivedDataPath build/DerivedData \
-skip-testing:CotabbyTests/FoundationModelDriftEvalTests \
CODE_SIGNING_ALLOWED=NO

- name: Clean build cache
if: always()
run: rm -rf build/DerivedData
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Xcode
# Finder/iCloud numbered copies are not generated build inputs. Keep only Cotabby.xcodeproj.
/CoHamster*.xcodeproj/
/Cotabby [0-9]*.xcodeproj/
*.xcuserstate
xcuserdata/
*.xcworkspace/xcuserdata/
Expand Down Expand Up @@ -26,6 +29,8 @@ Signing.local.xcconfig
.venv/
.venv-*/
venv/
__pycache__/
*.py[cod]

# Personal notes
launch.txt
Expand All @@ -43,3 +48,8 @@ posts.txt

.agents
.internal

# Never commit signing material or environment secrets.
*.p12
*.p8
*.keychain-db
34 changes: 15 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ When adding a `struct`, `class`, `enum`, actor, or protocol, explain:
- `CotabbyTests/`: unit and microbench tests that mirror the production subsystem map. Prefer
testing pure `Support/` and `Models/` logic when possible.
- `CotabbyInference`: the llama.cpp wrapper, consumed as a SwiftPM package
(`github.com/FuJacob/cotabbyinference`, pinned to `main`) rather than vendored in-tree.
(`github.com/FuJacob/cotabbyinference`). The build workspace pins a revision and applies
`patches/cotabbyinference-upstream-pending.patch` until those APIs are accepted upstream.

Within a subsystem, child folders describe stable responsibilities rather than Swift namespaces.
Examples include `Services/Runtime/{AppleIntelligence,Llama,OpenAICompatible}` and
Expand Down Expand Up @@ -282,27 +283,22 @@ log stream --predicate 'subsystem == "com.cotabby.app"' --level debug
**Rule of thumb.** When a user reports a bug, first `tail` / `jq` the relevant file with the
symptom → category map. Do not ask the user to re-explain symptoms before checking the logs.

## Validation
## Builds And Validation

Use the narrowest meaningful validation first, then broaden if the change touches shared behavior.
Common commands:
Build, run, and test with Xcode as documented in
[`CONTRIBUTING.md`](CONTRIBUTING.md). Prepare the pinned inference workspace with
`scripts/prepare_cotabby_workspace.sh` first. Local launches use the developer's own signing
team configured through `scripts/dev-setup.sh` and the isolated `Cotabby Dev` scheme; production
uses `Cotabby`, its upstream bundle identity, and Sparkle updates. Shared CI uses unsigned compile checks and
app-hosted tests.

```bash
xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build \
-derivedDataPath build/DerivedData
xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build-for-testing \
-derivedDataPath build/DerivedData
```

Always pass `-derivedDataPath build/DerivedData` so the output lands in the repo-scoped `build/`
directory (already gitignored) instead of accumulating under
`~/Library/Developer/Xcode/DerivedData/Cotabby-*`, where every build leaves a fresh multi-GB module
cache and SwiftPM checkout that nothing trims. When a task is done and the artifacts are no longer
needed, `rm -rf build/DerivedData` before reporting completion.
Use the narrowest meaningful validation first, then broaden if the change touches shared behavior.
Keep DerivedData in `build/DerivedData`, avoid concurrent builds in the same checkout, and clean
it after validation. Verify cleanup before reporting completion.
Never move DerivedData to `~/Library/Developer/Xcode/DerivedData/Cotabby-*`.

Run targeted tests for changed pure logic when available. If `xcodebuild test` fails locally because
of app-hosted test bundle signing or Team ID mismatch, report the exact failure and still provide the
successful build/build-for-testing result.
Run targeted tests for changed pure logic when available. If validation fails, report the exact
failure and distinguish a successful build-for-testing step from actual test execution.

## Git And Worktree Safety

Expand Down
60 changes: 48 additions & 12 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,37 @@ The app and CotabbyInference intentionally expose one live autocomplete sequence
one llama.cpp sequence slot and a changing external ID to reject stale work after a reset; the Swift
generation loop remains the single owner of the output-token budget.

Autocomplete heals the final prompt token when its printable bytes exactly match the text at the
caret. The native sampler replays those bytes under a vocabulary-prefix constraint, allowing a
longer token to finish an unfinished word without forcing every complete word to continue.
[TokenHealingBuffer.swift](Cotabby/Support/Runtime/TokenHealingBuffer.swift) removes replay bytes and
holds incomplete UTF-8 until it can publish lossless cumulative text. The extra replay allowance is
bounded separately from visible generation. Special tokens and oversized pieces take the ordinary
continuation path; no model weights or tokenizer files are modified.

Cache reuse is based on actual native restoration, not a permanent model-family blacklist. Ordinary
attention can trim its suffix; recurrent/hybrid or sliding-window memory uses one bounded partial
state checkpoint near the prompt tail, with at most eight tokens replayed. A miss falls back to a
cold prompt. Checkpoints live only in memory, are size-capped, and are cleared with the sequence.
Sampler history is rebuilt from the current prompt so discarded suggestions do not affect the next
request. Native memory may retain a generated tail between requests; the app tracks only the
validated prompt prefix and restores exactly the shared prefix before the next decode. Deferring
restoration avoids replaying the same tail both after generation and again on the next keystroke.
Cancellation targets a request identity as well as the native sequence, preventing a late
cancel from aborting a later request that reused the same sequence.

[BaseCompletionPromptRenderer.swift](Cotabby/Support/Prompting/BaseCompletionPromptRenderer.swift) renders a
base-model text continuation with optional budgeted context and the caret prefix last. It does not
wrap a base GGUF in an instruction conversation. [FoundationModelPromptRenderer.swift](Cotabby/Support/Prompting/FoundationModelPromptRenderer.swift)
keeps Apple's instruction-shaped prompt separate.

The request window and section allocator preserve the prefix's spaces, line breaks, and indentation.
Multiline output cleanup also preserves the leading space needed to join a new word at the caret.
Bounded right-of-caret text is descriptive reference material before the prefix, not invented
fill-in-the-middle control tokens. This added suffix section is local-only: the endpoint renderer
does not receive it. Optional user-authored notes retain their existing budgets and settings; no
automatic writing-history collection is introduced.

Prewarm is opportunistic and goes only to the selected backend. Context reset reaches every backend.
The local runtime is loaded only for the Open Source engine and is released when switching to Apple
or endpoint mode so mapped weights and Metal buffers do not stay resident unnecessarily.
Expand All @@ -277,27 +303,33 @@ Visual context is one field-scoped session:

~~~text
VisualContextCoordinator
-> WindowScreenshotService (ScreenCaptureKit crop)
-> WindowScreenshotService (local: focused window; endpoint: original field crop)
-> ScreenTextExtractor (Vision OCR + line confidence)
-> OCRTextHygiene
-> VisualContextExcerptSelector (local: proximity, deduplication, reading order)
-> bounded sanitized excerpt
~~~

There is no model summarization step and raw screenshots do not enter prompts. Missing Screen Recording
produces an explicit unavailable state without disabling text-only autocomplete. Debug screenshot/OCR
artifacts exist only under the explicit debug launch mode.

Local engines refresh context three seconds after the previous capture finishes, while generation
continues using the last ready excerpt. Every refresh rechecks focus, permissions and eligibility;
unchanged pixels reuse OCR and unchanged excerpts do not trigger generation. Local selection admits
up to 4,000 characters, additionally constrained by estimated-token budgets and caret-prefix priority.
The endpoint retains its original crop, focus-only lifecycle, 1,500-character excerpt and 500-character
prompt section. Increasing local context does not opt users into broader network transmission.
Local excerpts use confidence/line hygiene without the legacy English token whitelist, preserving
recognized names, ordinary words, dates and amounts instead of silently stripping them.

Secure fields cannot schedule generation, show a suggestion, accept text, or open an inline command,
so their context cannot flow into an engine request. The acquisition boundary is currently broader
than that guarantee: FocusSnapshotResolver still creates a bounded context for a secure field before
marking its capability blocked, and visual-capture eligibility deliberately ignores capability so
screenshot/OCR can warm for that field. The excerpt cannot be consumed by prediction, but explicit
debug mode can persist visual captures. Treat this as privacy debt; current architecture guarantees
no secure-field generation, not no secure-field acquisition.
so their context cannot flow into an engine request. VisualContextCoordinator also rejects secure
fields before starting screenshot/OCR work. FocusSnapshotResolver still creates a bounded AX context
before marking a secure field blocked; that lower-level acquisition remains separate privacy debt.

Endpoint credentials are stored in Keychain. A remote endpoint receives the same bounded request that
would otherwise be used locally, so its privacy scope must remain visible in settings and
documentation.
Endpoint credentials are stored in Keychain. A remote endpoint receives its bounded, legacy-scope
request; its privacy scope must remain visible in settings and documentation.

## Presentation and Sibling Features

Expand Down Expand Up @@ -371,8 +403,12 @@ model failures, and app for permissions/lifecycle.

[project.yml](project.yml) is the Xcode project source of truth. XcodeGen produces the committed
[Cotabby.xcodeproj](Cotabby.xcodeproj); CI regenerates it and fails when the checked-in project differs.
Cotabby and Cotabby Dev build the same sources under distinct bundle/product identities so development
does not overwrite the production app's TCC grants. Swift default actor isolation is MainActor.
Debug and Release build the same Cotabby app identity, preference domain, icon, and model storage.
The Debug configuration exposes General > Development > Show Development Debug Overlays (off by
default); AppDelegate forwards that live preference to the presentation controller and polling
diagnostics. The `-cotabby-debug` launch argument controls local diagnostic logging independently.
Keep the signing identity consistent across builds to preserve macOS permission grants.
Swift default actor isolation is MainActor.

Use the narrowest relevant tests first, then broaden. The standard build boundary is:

Expand Down
Loading