Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7cf8567
docs: glossary for Releases and Updates, ADR on update-check install IDs
lucasdoell Oct 2, 2026
863e7bf
docs(design): Installer section for the DMG window (ENG-255)
lucasdoell Oct 2, 2026
6a1dbfd
docs(design): record the Installer's light-appearance label trade-off…
lucasdoell Oct 2, 2026
2c33055
docs(design): Updates and Daemon Upgrades (ENG-256)
lucasdoell Oct 2, 2026
8104755
Add release packaging with signed Daemons and notarised app Updates
lucasdoell Oct 2, 2026
19cbf6a
fix(desktop): isolate smoke Daemons from upgrade handoff
lucasdoell Oct 2, 2026
a83489c
docs(design): record owner approval of quiet daemon upgrades and Quit…
lucasdoell Oct 2, 2026
277b780
merge: accept release builds with signing and notarisation (ENG-254)
lucasdoell Oct 2, 2026
4ac8180
merge: accept smoke Daemons ignoring an inherited upgrade hand-off
lucasdoell Oct 2, 2026
4770298
feat(site): marketing site in apps/site (ENG-257)
lucasdoell Oct 2, 2026
61009d9
merge: accept the Update UI design (ENG-256)
lucasdoell Oct 2, 2026
6a4bc06
merge: accept the marketing site (ENG-257)
lucasdoell Oct 2, 2026
bea6a98
Merge commit '4ac8180c' into polaris/924eb159-7b06-4860-ad1f-06733d58…
lucasdoell Oct 2, 2026
0906ce7
merge: accept the DMG installer design (ENG-255)
lucasdoell Oct 2, 2026
35cd8ca
feat(site): serve published Release updates and downloads with privat…
lucasdoell Oct 2, 2026
e045ab7
fix(daemon): contain and validate upgrade handoffs
lucasdoell Oct 2, 2026
f62b661
merge: accept the update feed and download routes (ENG-259)
lucasdoell Oct 2, 2026
9c703d4
merge: accept keeping the upgrade hand-off out of child processes
lucasdoell Oct 2, 2026
32985b8
feat(site): send mobile download email through SES with BotID
lucasdoell Oct 2, 2026
78480e5
merge: accept the SES download email with BotID
lucasdoell Oct 2, 2026
fbec26b
docs: research email validation for SES download route
lucasdoell Oct 2, 2026
9807e60
merge: accept email validation research for SES
lucasdoell Oct 3, 2026
4518ca4
docs(design): move the DMG installer to the dawn scene (ENG-255)
lucasdoell Oct 3, 2026
559d73f
merge: accept the dawn DMG installer design
lucasdoell Oct 3, 2026
f887e03
fix(bench): isolate fixture Daemon handoff and reject empty measurements
lucasdoell Oct 3, 2026
d8d161e
feat(desktop): build headless DMG installers (ENG-258)
lucasdoell Oct 3, 2026
67f2e54
merge: accept stripping the hand-off from benchmark fixtures
lucasdoell Oct 3, 2026
950d997
feat(site): validate download email recipients with SES Insights
lucasdoell Oct 3, 2026
77c6c69
merge: accept the DMG build pipeline (ENG-258)
lucasdoell Oct 3, 2026
48cd434
merge: accept SES Insights validation for the download email
lucasdoell Oct 3, 2026
96e1c8e
feat(desktop): add release updates and quiet daemon upgrade captions
lucasdoell Oct 3, 2026
556bffa
merge: accept Desktop App Updates (ENG-261)
lucasdoell Oct 3, 2026
1beaaf8
fix(design): ship dawn DMG artwork with legible labels
lucasdoell Oct 3, 2026
cb4f68e
merge: accept the dawn DMG background
lucasdoell Oct 3, 2026
95071ab
feat(release): build draft macOS releases from version tags
lucasdoell Oct 3, 2026
85c7092
merge: accept the release workflow (ENG-260)
lucasdoell Oct 3, 2026
acf4346
ci: pin setup-bun to a commit in the release workflow
lucasdoell Oct 3, 2026
b0399d7
merge: bring in main
lucasdoell Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .agents/skills/product-design/decision-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ One line per accepted guidance change: `date | change | evidence`.
2026-10-01 | Plan Limit meters count down ("84% left", meter = share left, "<1% left" for a sliver), drawn as crisp discrete cells (Paper S2's 40 × 4px + 1px gaps), windows wrap to a second line instead of clipping; the picker hint counts down too | user feedback, round 5 (R5-limits)
2026-10-01 | Plan Limit forecast after CodexBar: pace marker cell in text-strong (taller by 1px each way, not a signal colour), reserve/deficit, "Runs out in" / "Lasts until reset" from the window's average rate, weekly "About N full 5-hour windows left · M until reset" from Daemon history; the picker hint gets a short form. Paper S2 has no forecast or marker | user request, round 6 (R6-forecast)
2026-10-01 | Harness picker: the newest four Models per Harness (newest of each family, Harness order, default always shown; the rest under "More models…"; Settings defaults use the same four); effort as a dither bar whose density and pace rise with the level; picks staged and sent once on close, Escape cancels | user request R6 (Codex-style effort bar, "top four" models)
2026-10-01 | The / menu never shows an empty list for a Host whose Daemon predates it: it says "Skills need a newer daemon on <Host>" with one action, Upgrade daemon (the Host's own upgrade), and follows it; any Host's upgrade is a toast ("Daemon upgraded") | user report R6 ("I don't see the skills working"): their Mac ran an older installed Daemon
2026-10-01 | The / menu never shows an empty list for a Host whose Daemon predates it: it says "Skills need a newer daemon on <Host>" with one action, Upgrade daemon (the Host's own upgrade), and follows it; any Host's upgrade is a toast ("Daemon upgraded") [toast superseded 2026-10-02: Daemon Upgrades are quiet, DESIGN.md Updates and daemon upgrades] | user report R6 ("I don't see the skills working"): their Mac ran an older installed Daemon
2026-10-01 | The / menu ranks by match quality, then the user's frecency of picks (ported from Sightline's Find); a bare / leads with "Recent"; local only, per Host, Workspace and Harness, bounded | user request R6
2026-10-01 | M2 Review states proposed in Paper (R3 pull requests, R4/R5 review checkout menu and states, R6 comment composer, R7 submit review, R8 agent session feedback; S5/S6 GitHub accounts and device-flow sign-in) and recorded in DESIGN.md Review and Settings as proposed | user request (ENG-185 resolution, ENG-217 map: PR list, account mapping, checkout states, comment and submit flow)
2026-10-01 | Reviewer settings page proposed (Paper S7): reviewer Harness/Model/Effort with per-host readiness (hosts that can't run it fall back to rules only), when it runs (PR on open, agent session on open/accept, ask first over 2,000 lines), rules and risk memory summary, 7-day usage line | user request after M2 Review mockups
Expand All @@ -43,3 +43,8 @@ One line per accepted guidance change: `date | change | evidence`.
2026-10-02 | Setup-only workers nest under their Lead in the sidebar ("setting up · 1m" / "setup failed", never "Dormant"); the command sits in the hover because a 264px row can't hold it with the id and title | Lead decision after C1-G2-setupui; screenshot evidence
2026-10-02 | M3 explorer built from E1: compact folder chains, deleted files kept struck through, the hand on the nearest visible folder, Changes as one flat list (name then folder), three agents then "N more", Delete to the trash without asking (a dialog only where the Host has no trash), and a Worktree or Review Checkout as the explorer's root with "Back to {workspace}". "Agents in" and ages use `text-subtle` where E1 had `text-faint` (rule/faint-is-not-content) | M3-EXPLORER build; spec §2 and §5
2026-10-02 | M3.1 Editor language tooling planned: lazy per-host managed installs with developer-owned prerequisites and Workspace trust; Settings configures composed providers and format-on-save (on, failure saves with error toast); multi-file refactors preview into drafts with guarded resource operations; familiar language assistance and GFM preview via Shift+Cmd+V, Host-relative media and remembered external-image consent | owner answers Q1-Q25 in grill-with-docs; docs/specs/editor-language-tooling-m3.1.md; no implementation or mockups
2026-10-02 | Updates and Daemon Upgrades proposed (Paper Updates page U1–U6): "Restart to update" only in the app menu (plus "Quit and update" on ⌘Q), Settings → About and the K menu, never a toast, badge or dialog; About lists what each update check sends and drops the install ID when checks are off; automatic Daemon Upgrades are quiet (machine bar caption and tooltip, Hosts row), replacing the 2026-10-01 "Daemon upgraded" toast; copy says update for the app and upgrade for daemons | ENG-256 brief; CONTEXT.md Update / Daemon Upgrade; docs/adr/0017
2026-10-02 | Owner approved: the "Daemon upgraded" toast is superseded by the quiet machine-bar caption, tooltip and Hosts row (the updater task removes the toast from code), and the app menu keeps "Quit and update" while an update is ready | owner answers to update-design questions upgrade-toast and quit-and-update, via the Lead

2026-10-02 | S3 mobile download form sends one email via SES instead of opening a mail draft; inline sending, sent and retryable error states retain the accepted layout, success clears the address, local development explicitly says preview | site-email assignment; DESIGN.md Marketing site; rule/design-reachable-states
2026-10-02 | Installer moves to the dawn scene for everyone (Paper I3, I4 the Finder dark-appearance check; I1/I2 superseded): Finder draws icon labels black on a DMG background in both appearances, so the night art left them unreadable; the arrow becomes neutral ink `#4A4C52`; black labels measure 11.9:1 or better | owner decision 2026-10-02 via the dmg-dawn-design brief; mounted-DMG evidence; DESIGN.md Installer
1 change: 1 addition & 0 deletions .agents/skills/product-design/references/surfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| Review: PR or agent session turns, risk column, verdicts | `../exemplars/review.md` | Review page, R1 and R2 |
| Editor: file tree, git states, inline chat, agent editing a file | `../exemplars/editor.md` | Editor page, E1, E2a, E2, E3 |
| Settings: harnesses, usage and plan limits, appearance, hosts | `../exemplars/settings.md` | Settings page, S1–S4 |
| Updates and daemon upgrades: About, restart to update, upgraded hosts | DESIGN.md, Updates and daemon upgrades | Updates page, U1–U6 (proposed) |
| Brand, wordmark, app icon, marketing, README art | `../exemplars/brand.md` | Brand and Brand deck pages |

A surface not listed has no exemplar yet: check `coverage-gaps.md` and
Expand Down
185 changes: 185 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: Release

on:
push:
tags: ["v*"]

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: write

jobs:
release:
name: macOS arm64 draft
runs-on: macos-15
environment: release
timeout-minutes: 90
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Validate tag, versions and signing policy
id: metadata
env:
RELEASE_TAG: ${{ github.ref_name }}
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
python3 - <<'PY'
import json, os, re
from pathlib import Path

tag = os.environ["RELEASE_TAG"]
number = r"(?:0|[1-9][0-9]*)"
if not re.fullmatch(rf"v{number}\.{number}\.{number}(?:-rc\.{number})?", tag):
raise SystemExit("Expected v<major>.<minor>.<patch> or v<major>.<minor>.<patch>-rc.N")
version = tag[1:]
for package in ("desktop", "daemon"):
actual = json.loads(Path(f"apps/{package}/package.json").read_text())["version"]
if actual != version:
raise SystemExit(f"Tag/version mismatch: {package} is {actual}, tag is {version}")

names = ("MACOS_CERT_P12", "MACOS_CERT_PASSWORD", "APPLE_API_KEY",
"APPLE_API_KEY_ID", "APPLE_API_ISSUER", "APPLE_TEAM_ID")
present = [name for name in names if os.environ.get(name, "").strip()]
if present and len(present) != len(names):
raise SystemExit("Incomplete signing secrets: missing " + ", ".join(n for n in names if n not in present))
prerelease = "-rc." in version
signed = bool(present)
if not prerelease and not signed:
raise SystemExit("Stable tags require all six signing secrets")
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
output.write(f"version={version}\nprerelease={str(prerelease).lower()}\nsigned={str(signed).lower()}\n")
PY

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: package.json
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
python-version: "3.12"
enable-cache: false
- name: Check runner prerequisites
run: |
test "$(uname -m)" = arm64
xcrun --find SetFile
xcrun --find notarytool
uv python install 3.12
- run: bun install --frozen-lockfile

- name: Import signing credentials
id: signing
if: steps.metadata.outputs.signed == 'true'
env:
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
run: |
umask 077
signing_dir="$RUNNER_TEMP/polaris-signing"
mkdir "$signing_dir"
security list-keychains -d user > "$signing_dir/keychains.txt"
printf '%s' "$MACOS_CERT_P12" | base64 --decode > "$signing_dir/certificate.p12"
printf '%s' "$APPLE_API_KEY" > "$signing_dir/AuthKey.p8"
keychain="$signing_dir/release.keychain-db"
keychain_password=$(openssl rand -hex 32)
security create-keychain -p "$keychain_password" "$keychain"
security set-keychain-settings -lut 21600 "$keychain"
security unlock-keychain -p "$keychain_password" "$keychain"
security import "$signing_dir/certificate.p12" -P "$MACOS_CERT_PASSWORD" \
-k "$keychain" -t cert -f pkcs12 -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$keychain_password" "$keychain" > /dev/null
python3 - <<'PY'
import os, shlex, subprocess
from pathlib import Path
directory = Path(os.environ["RUNNER_TEMP"]) / "polaris-signing"
original = shlex.split((directory / "keychains.txt").read_text())
subprocess.run(["security", "list-keychains", "-d", "user", "-s",
str(directory / "release.keychain-db"), *original], check=True)
PY
rm "$signing_dir/certificate.p12"
printf 'keychain=%s\napi_key=%s\n' "$keychain" "$signing_dir/AuthKey.p8" >> "$GITHUB_OUTPUT"

- name: Package release app, Update ZIP and installer DMG
env:
APPLE_API_KEY: ${{ steps.signing.outputs.api_key }}
MACOS_KEYCHAIN: ${{ steps.signing.outputs.keychain }}
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: bun run --cwd apps/desktop package --release

- name: Smoke-test packaged app
run: node apps/desktop/scripts/packageCheck.ts

- name: Assess signed app and validate stapled tickets
if: steps.metadata.outputs.signed == 'true'
env:
VERSION: ${{ steps.metadata.outputs.version }}
run: |
app=apps/desktop/out/dist/Polaris-darwin-arm64/Polaris.app
dmg="apps/desktop/out/dist/Polaris-$VERSION-arm64.dmg"
codesign --verify --deep --strict "$app"
spctl --assess --type execute --verbose=2 "$app"
xcrun stapler validate "$app"
codesign --verify --strict "$dmg"
spctl --assess --type open --context context:primary-signature --verbose=2 "$dmg"
xcrun stapler validate "$dmg"

- name: Delete temporary signing credentials
if: always() && steps.metadata.outputs.signed == 'true'
run: |
signing_dir="$RUNNER_TEMP/polaris-signing"
trap 'rm -rf "$signing_dir"' EXIT
python3 - <<'PY'
import os, shlex, subprocess
from pathlib import Path
directory = Path(os.environ["RUNNER_TEMP"]) / "polaris-signing"
try:
saved = directory / "keychains.txt"
if saved.exists():
subprocess.run(["security", "list-keychains", "-d", "user", "-s",
*shlex.split(saved.read_text())], check=True)
finally:
keychain = directory / "release.keychain-db"
if keychain.exists():
subprocess.run(["security", "delete-keychain", str(keychain)], check=True)
PY

- name: Create draft GitHub Release with exact feed assets
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.ref_name }}
VERSION: ${{ steps.metadata.outputs.version }}
PRERELEASE: ${{ steps.metadata.outputs.prerelease }}
SIGNED: ${{ steps.metadata.outputs.signed }}
run: |
dmg="apps/desktop/out/dist/Polaris-$VERSION-arm64.dmg"
zip="apps/desktop/out/dist/Polaris-$VERSION-arm64-mac.zip"
test -s "$dmg"
test -s "$zip"
flags=(--draft --verify-tag --generate-notes)
if [[ "$PRERELEASE" == true ]]; then
flags+=(--prerelease --latest=false)
fi
if [[ "$SIGNED" == false ]]; then
flags+=(--notes 'Unsigned release candidate for testing. Signing and notarisation were skipped.')
fi
gh release create "$RELEASE_TAG" "$dmg" "$zip" "${flags[@]}"
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Polaris is an IDE and agent orchestrator: a Bun Daemon per Host, an Electron Des
| `packages/bench` | Benchmarks: the real Daemon under scripted load, a process-tree memory/CPU sampler, baselines and comparisons. |
| `packages/spec` | The Quint spec of commits, streams, Client feeds and approvals, and its checks (see Verification). |
| `apps/daemon` | The Daemon (`polaris` binary): event store, Harness drivers, transport, files, git, terminals, user service. |
| `apps/site` | The marketing site (Next.js on Vercel, root directory `apps/site`), built from the Paper "Marketing site" page on `@polaris/ui`'s tokens; the download and update-feed routes live under `app/` (see its README). |
| `apps/desktop` | The Electron Desktop App: the Client runtime in the main process, a typed IPC bridge, a React renderer on `@polaris/ui` (see its README). |
| `packages/ui` | The design system in code (`@polaris/ui`): DESIGN.md's tokens on Tailwind v4, restyled shadcn/ui, the Polaris primitives, and a gallery (`bun run --cwd packages/ui gallery`). |
| `packages/lint-config` | The shared oxlint config every workspace extends, the shared oxfmt style (`oxfmt.json`), and the custom plugins (`polaris/no-long-comment`, vendored anti-slop, the SonarJS cognitive-complexity wrapper). Read its `README.md` before touching lint rules. |
Expand Down
12 changes: 12 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ _Avoid_: Error, disconnected
**Offline**:
The Connection State of a Host the Client has stopped retrying, such as a machine that is shut down.

**Release**:
A published version of Polaris: one version number shared by the Desktop App and the Daemon builds it carries.
_Avoid_: Build (for a published version), drop

**Update**:
The Desktop App replacing itself with a newer Release.
_Avoid_: Upgrade (for the Desktop App)

**Daemon Upgrade**:
A Host's Daemon replaced by the build the Desktop App carries.
_Avoid_: Update (for a Daemon), reinstall

### Agents

**Harness**:
Expand Down
Loading
Loading