Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ secrets.json
_DESIGN_LANGUAGE.md

# macOS app build output
macos/build/

# Tauri desktop app build output
desktop/build/
Expand Down
17 changes: 0 additions & 17 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -223,23 +223,6 @@ smoke:

python3 scripts/smoke_e2e.py "${endpoint}"

# macOS app: build cot.app (SwiftUI shell + frozen collector + dashboard).
# just mac build full build
# just mac app Swift shell only, for iterating on the UI
# just mac run build and launch
# just mac clean drop macos/build
mac action="build":
#!/usr/bin/env sh
set -eu

case "{{action}}" in
build) macos/build.sh ;;
app) macos/build.sh --app-only ;;
run) macos/build.sh --run ;;
clean) rm -rf macos/build && printf '%s\n' "removed macos/build" ;;
*) printf '%s\n' "unknown action: {{action}} (build|app|run|clean)" >&2; exit 2 ;;
esac

# Fresh web install in a sandbox: runs `curl cot.run/install | sh` with HOME in a
# temp dir, so the collector container, ~/.cot and hook files never touch yours.
# just fresh collector built from origin/main
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/test_opencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_manual_plugin_inferred_without_its_own_manifest_entry(fresh_db, monkeyp

def test_desktop_spec_serves_the_plugin(tmp_path, monkeypatch):
repo = Path(__file__).resolve().parents[2]
tree = ast.parse((repo / "macos/packaging/cot-collector.spec").read_text())
tree = ast.parse((repo / "desktop/packaging/cot-collector.spec").read_text())
analysis = next(n for n in ast.walk(tree) if isinstance(n, ast.Call) and isinstance(n.func, ast.Name) and n.func.id == "Analysis")
datas = next(k.value for k in analysis.keywords if k.arg == "datas")
for entry in datas.elts:
Expand Down
5 changes: 1 addition & 4 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@ The React UI and the backend are unchanged. The app bundles:

- `Contents/Resources/cot-collector/`, the collector frozen by PyInstaller
(onedir, so launches don't unpack anything), built from
`macos/packaging/cot-collector.spec`, which it shares with the Swift app
`desktop/packaging/cot-collector.spec`
- `Contents/Resources/static/`, the vite build, passed to the collector as
`COT_STATIC_DIR`

The Swift app in `macos/` uses the same bundle id (`run.cot.app`) and `~/.cot`.
Run one or the other, not both.

## Build

```sh
Expand Down
56 changes: 40 additions & 16 deletions desktop/loading/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,70 @@
<title>cot</title>
<!-- Shown by the desktop shell while the collector starts, or when it can't.
The shell sets ?state=starting|held|failed|stopped (+ port, message). -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@500;600&family=Newsreader:ital,opsz,wght@1,6..72,700&display=swap">
<!-- Signal Forest tokens from src/forest.css; follows the OS light/dark setting
(the dashboard's own theme choice lives on another origin). -->
<style>
:root { --bg: #0b0e11; --fg: 244 240 234; --panel: #101419; --accent: #ff4500; color-scheme: dark; }
:root {
--bg: #f5f6f3; --panel: #ecefe9; --hot: #0f5b3e;
--fg: rgb(12 26 20); --dim: rgb(12 26 20 / 0.6); --line: rgb(12 26 20 / 0.1);
--mute: rgb(12 26 20 / 0.16); --wash: rgb(12 26 20 / 0.05);
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0a1612; --panel: #0e1d18; --hot: #c8f169;
--fg: rgb(233 240 234); --dim: rgb(233 240 234 / 0.6); --line: rgb(233 240 234 / 0.1);
--mute: rgb(233 240 234 / 0.16); --wash: rgb(233 240 234 / 0.05);
color-scheme: dark;
}
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
background: var(--bg); color: rgb(var(--fg));
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif;
background: var(--bg); color: var(--fg);
font: 14px/22px Geist, ui-sans-serif, system-ui, -apple-system, sans-serif;
display: grid; place-items: center; -webkit-user-select: none; user-select: none; cursor: default;
}
::selection { background: color-mix(in srgb, var(--hot) 28%, transparent); }
.drag { position: fixed; inset: 0 0 auto 0; height: 46px; }
main { max-width: 440px; padding: 40px 24px; text-align: center; }
.logo { font: italic 400 34px/1 "Newsreader", Georgia, serif; letter-spacing: -0.03em; margin-bottom: 28px; }
.logo span { color: var(--accent); }
h1 { font-size: 15px; font-weight: 600; margin: 0 0 8px; }
p { margin: 0; color: rgb(var(--fg) / 0.55); }
code, .detail { font: 12px/1.5 "JetBrains Mono", ui-monospace, Menlo, monospace; }
.wordmark { font: italic 700 36px/1 Newsreader, Georgia, serif; letter-spacing: -0.02em; margin-bottom: 28px; }
.wordmark i { color: var(--hot); font-style: inherit; }
h1 { font-size: 16px; line-height: 24px; font-weight: 500; margin: 0 0 6px; }
p { margin: 0; color: var(--dim); }
.detail {
margin-top: 16px; padding: 10px 12px; text-align: left; white-space: pre-wrap; word-break: break-word;
background: var(--panel); border: 1px solid rgb(var(--fg) / 0.1); border-radius: 6px;
color: rgb(var(--fg) / 0.7); -webkit-user-select: text; user-select: text;
font: 500 12px/18px "Geist Mono", ui-monospace, Menlo, monospace;
background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
color: var(--fg); -webkit-user-select: text; user-select: text;
}
button {
margin-top: 22px; padding: 8px 16px; border-radius: 6px; border: 1px solid rgb(var(--fg) / 0.2);
background: transparent; color: rgb(var(--fg)); cursor: pointer;
font: 700 10px/1 "JetBrains Mono", ui-monospace, Menlo, monospace; letter-spacing: 0.12em; text-transform: uppercase;
display: inline-flex; align-items: center; height: 36px; margin-top: 22px; padding: 0 14px;
border-radius: 8px; border: 1px solid var(--line); background: var(--panel); color: var(--fg);
font: 500 14px/1 Geist, ui-sans-serif, system-ui, sans-serif; cursor: pointer;
transition: transform 150ms var(--ease-out), background-color 150ms ease, border-color 150ms ease;
}
button:hover { border-color: rgb(var(--fg) / 0.35); }
button:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { button:hover { border-color: var(--mute); background: var(--wash); } }
button:focus-visible { outline: 2px solid color-mix(in srgb, var(--hot) 75%, transparent); outline-offset: 2px; }
.spinner {
width: 18px; height: 18px; margin: 0 auto 16px; border-radius: 50%;
border: 2px solid rgb(var(--fg) / 0.15); border-top-color: var(--accent);
border: 2px solid var(--mute); border-top-color: var(--hot);
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }
[hidden] { display: none !important; }
</style>
</head>
<body>
<div class="drag" data-tauri-drag-region></div>
<main>
<div class="logo">cot<span>.</span></div>
<div class="wordmark">cot<i>.</i></div>
<div id="spinner" class="spinner"></div>
<h1 id="title">Starting the collector…</h1>
<p id="body"></p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# PyInstaller spec for the collector binary embedded in cot.app.
# Built from macos/build.sh; `pathex` points at backend/ so `app.main` resolves.
# Built by desktop/scripts/prepare-collector.sh; `pathex` points at backend/ so `app.main` resolves.

import os

REPO = os.environ["COT_REPO_ROOT"]

a = Analysis(
[os.path.join(REPO, "macos", "packaging", "collector_entry.py")],
[os.path.join(REPO, "desktop", "packaging", "collector_entry.py")],
pathex=[os.path.join(REPO, "backend")],
binaries=[],
datas=[
Expand Down
6 changes: 3 additions & 3 deletions desktop/scripts/prepare-collector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
# scripts/prepare-collector.sh always rebuild
# scripts/prepare-collector.sh --if-missing skip when resources exist (tauri dev)
#
# Reuses the Swift app's PyInstaller spec and entrypoint in macos/packaging, so
# both apps ship the same collector. Python deps go into desktop/build/venv.
# PyInstaller spec and entrypoint live in desktop/packaging. Python deps go
# into desktop/build/venv.
set -euo pipefail

DESKTOP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
Expand Down Expand Up @@ -40,7 +40,7 @@ COT_REPO_ROOT="${REPO_ROOT}" "${VENV}/bin/pyinstaller" \
--noconfirm --clean --log-level WARN \
--distpath "${BUILD_DIR}/dist" \
--workpath "${BUILD_DIR}/work" \
"${REPO_ROOT}/macos/packaging/cot-collector.spec"
"${DESKTOP_DIR}/packaging/cot-collector.spec"

step "Staging resources"
rm -rf "${RESOURCES}"
Expand Down
3 changes: 1 addition & 2 deletions desktop/src-tauri/src/collector.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Where the collector lives and how its process is started and stopped.
//!
//! Ported from macos/Sources/CollectorController.swift and
//! CollectorEndpoint.swift. The rules are the same with one change: if another
//! Ported from the original Swift app, with one change: if another
//! collector already answers on the port, the app refuses to start rather than
//! attaching to it, so there is never a second writer on ~/.cot/cot.db.

Expand Down
108 changes: 0 additions & 108 deletions macos/README.md

This file was deleted.

37 changes: 0 additions & 37 deletions macos/Resources/Info.plist

This file was deleted.

15 changes: 0 additions & 15 deletions macos/Resources/cot.entitlements

This file was deleted.

33 changes: 0 additions & 33 deletions macos/Sources/BridgeInstaller.swift

This file was deleted.

Loading
Loading