Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
97c6d25
checkpoint(CR-0001): author CR for GPU rendering pipeline replacement
desek Jun 4, 2026
9944a49
checkpoint(CR-0001): CR reviewed against macOS 26.5 SDK; API referenc…
desek Jun 4, 2026
41ad155
checkpoint(CR-0001): fold interactive/gaming latency concerns into CR
desek Jun 4, 2026
10031dd
Add agent tooling: project instructions, offline Apple docs search, M…
desek Jun 4, 2026
3ff98d3
checkpoint(CR-0002): author CR for AVSampleBufferDisplayLayer opt-in …
desek Jun 4, 2026
26a035c
checkpoint(CR-0001): adopt greenfield path as normative in CR
desek Jun 4, 2026
f063ecf
checkpoint(CR-0002): align baseline assumptions with reworked CR-0001
desek Jun 4, 2026
7fac996
checkpoint(CR-0001): CR reviewed: drift reconciled, contradictions re…
desek Jun 4, 2026
cb37f72
checkpoint(CR-0001): phase 1: logging and observability foundation
desek Jun 4, 2026
cb3b7b6
checkpoint(CR-0001): phase 2: capture subsystem
desek Jun 4, 2026
e9b0a77
checkpoint(CR-0001): phase 3: render subsystem
desek Jun 4, 2026
7266ecc
checkpoint(CR-0001): phase 4: integration, cutover, and legacy deletion
desek Jun 4, 2026
ba41b07
checkpoint(CR-0001): CR finalized
desek Jun 4, 2026
81d2ccb
checkpoint(CR-0001): gaps fixed per validation report
desek Jun 4, 2026
f929c8d
checkpoint(CR-0001): documentation updated for implemented feature
desek Jun 4, 2026
6a4eea3
checkpoint(CR-0001): fix white-window bug: consume CAMetalDisplayLink…
desek Jun 5, 2026
e0f7cf3
checkpoint(CR-0001): fix launch crash: idempotent pacer attach
desek Jun 5, 2026
5806880
checkpoint(CR-0001): fix present crash: plain present for link-vended…
desek Jun 5, 2026
cc6842d
checkpoint(CR-0001): append live runtime verification addendum to val…
desek Jun 5, 2026
8f02eeb
checkpoint(CR-0003): author CR for test hardening and rendering self-…
desek Jun 5, 2026
0b835b9
Add build-deskpad-signed.sh: stable Apple Development signing
desek Jun 5, 2026
d82db7b
checkpoint(CR-0003): CR reviewed: drift reconciled, contradictions re…
desek Jun 5, 2026
9a6cc55
Read signing identity from .env in build-deskpad-signed.sh
desek Jun 5, 2026
e096b5b
checkpoint(CR-0003): phase 1: coverage closure to ~95 to 96 percent
desek Jun 5, 2026
e75c138
Add .env.example documenting local signing configuration
desek Jun 5, 2026
c68d76c
checkpoint(CR-0003): phase 2: Layer 1 present-stall watchdog
desek Jun 5, 2026
c129914
checkpoint(CR-0003): phase 3: Layer 2 drawable read-back and --self-t…
desek Jun 5, 2026
fe7bbd7
checkpoint(CR-0003): phase 4: Layer 3 loopback and CLI script
desek Jun 5, 2026
78a1fea
Add taxonomy entries for present stall and self-test mode
desek Jun 5, 2026
e9d4b65
checkpoint(CR-0003): CR finalized
desek Jun 5, 2026
0601ac7
checkpoint(CR-0003): gaps fixed per validation report
desek Jun 5, 2026
611ad81
checkpoint(CR-0003): document self-test mode, present-stall watchdog,…
desek Jun 5, 2026
553cbc0
checkpoint(CR-0002): CR reviewed: drift reconciled against implemente…
desek Jun 5, 2026
3a29010
checkpoint(CR-0002): phase 1: Protocol Seam and Metal Adapter
desek Jun 5, 2026
65aadaa
checkpoint(CR-0002): phase 2: AVSampleBufferDisplayLayer Backend
desek Jun 5, 2026
5e8f7ed
checkpoint(CR-0002): phase 3: Configuration, Menu, and Live Switching
desek Jun 5, 2026
4dae018
checkpoint(CR-0002): phase 4: Documentation, Taxonomy, and Test Bring-up
desek Jun 5, 2026
7f4da62
checkpoint(CR-0002): CR finalized
desek Jun 5, 2026
089dd10
checkpoint(CR-0002): validation report completed
desek Jun 5, 2026
ee2be96
checkpoint(CR-0002): gaps fixed per validation report
desek Jun 5, 2026
a1b0b39
checkpoint(CR-0002): documentation updated for implemented feature
desek Jun 5, 2026
c6e6ebc
checkpoint(CR-0002): fix live frame hand-off: route captured CMSample…
desek Jun 5, 2026
ac3950a
checkpoint(CR-0002): mandate .env signing identity for all agent buil…
desek Jun 5, 2026
6ea8665
checkpoint(CR-0002): record CPU-time energy proxy in validation repor…
desek Jun 5, 2026
955e489
checkpoint(CR-0002): dirty-gate capture ingest and coalesce backend e…
desek Jun 5, 2026
0c5bed8
checkpoint(CR-0002): A/B confirms no Metal regression from dirty-gate…
desek Jun 5, 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
51 changes: 51 additions & 0 deletions .agents/scripts/apple-docs.search.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
# @agents-index Searches Xcode's offline documentation symbol index (LMDB) for Apple API doc paths.
#
# Purpose: Xcode ships a 1.65M-entry LMDB index mapping record IDs to Apple
# documentation URL paths. This script greps that index so agents and humans
# can discover exact symbol names and canonical doc URLs offline.
# Why LMDB dump: the database is Apple-internal but the container is standard
# LMDB; keys are record IDs, values are hex-encoded UTF-8 doc paths.
#
# Usage: apple-docs.search.sh <grep-pattern> [max-results]
# <grep-pattern> case-insensitive pattern matched against doc paths,
# e.g. "scstreamconfiguration" or "screencapturekit/scstream/"
# [max-results] maximum hits to print (default 25)
#
# Output: one doc path per line, prefixed with the full developer.apple.com URL.
# Requires: lmdb (brew install lmdb), python3.
# Side effects: copies the read-only index to /tmp/xcode-docs-index on first run
# (LMDB needs a writable dir for its lock file).

set -euo pipefail

if [ $# -lt 1 ]; then
sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'
exit 1
fi

PATTERN="$1"
MAX="${2:-25}"
SRC="/Applications/Xcode.app/Contents/SharedFrameworks/DNTDocumentationSupport.framework/Versions/A/Resources/external/index"
WORK="/tmp/xcode-docs-index"

# LMDB opens need a writable lock file; the Xcode copy is root-owned read-only.
if [ ! -f "$WORK/data.mdb" ]; then
cp -r "$SRC" "$WORK" && chmod -R u+w "$WORK"
fi

mdb_dump -s index "$WORK" 2>/dev/null | python3 -c "
import sys, binascii
pattern = sys.argv[1].lower()
limit = int(sys.argv[2])
shown = 0
lines = [l.strip() for l in sys.stdin if l.startswith(' ')]
# Dump alternates key/value lines; values (odd positions) are the doc paths.
for i in range(1, len(lines), 2):
path = binascii.unhexlify(lines[i]).decode('utf-8', 'replace')
if pattern in path.lower():
print(f'https://developer.apple.com/{path}')
shown += 1
if shown >= limit:
break
" "$PATTERN" "$MAX"
51 changes: 51 additions & 0 deletions .agents/scripts/build-deskpad-signed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
# @agents-index Builds DeskPad Release and signs it with the stable Apple Development certificate so the TCC Screen Recording grant survives rebuilds.
#
# Purpose: ad-hoc signatures change on every build, invalidating the
# Screen Recording permission each time. Signing with the keychain's
# Apple Development identity keeps the signature stable across builds,
# so the grant is one-time. xcodebuild cannot see GUI-added Xcode
# accounts from the CLI, so we build unsigned and codesign manually.
#
# Usage: build-deskpad-signed.sh [--install]
# --install also copy the signed app to /Applications and launch it
#
# Output: signed app at build/Build/Products/Release/DeskPad.app
# Requires: an "Apple Development" identity in the login keychain
# (Xcode -> Settings -> Accounts -> Manage Certificates).

set -euo pipefail
cd "$(dirname "$0")/../.."

# Prefer the pinned identity from .env (DESKPAD_CODESIGN_IDENTITY);
# fall back to keychain discovery when .env is absent.
if [ -f .env ]; then
# shellcheck disable=SC1091
source .env
fi
IDENTITY="${DESKPAD_CODESIGN_IDENTITY:-$(security find-identity -v -p codesigning | grep -o '"Apple Development: [^"]*"' | head -1 | tr -d '"')}"
if [ -z "$IDENTITY" ]; then
echo "ERROR: no valid Apple Development identity in keychain" >&2
echo "Create one in Xcode -> Settings -> Accounts -> Manage Certificates" >&2
exit 1
fi

echo "Building (unsigned)..."
xcodebuild -scheme DeskPad -configuration Release -derivedDataPath build \
CODE_SIGNING_ALLOWED=NO build 2>&1 | tail -2

APP=build/Build/Products/Release/DeskPad.app
echo "Signing with: $IDENTITY"
codesign --force --options runtime \
--entitlements DeskPad/DeskPad.entitlements \
--sign "$IDENTITY" "$APP"
codesign -dv "$APP" 2>&1 | grep -E "TeamIdentifier|flags"

if [ "${1:-}" = "--install" ]; then
pkill -f DeskPad.app || true
sleep 1
rm -rf /Applications/DeskPad.app
cp -R "$APP" /Applications/
open /Applications/DeskPad.app
echo "Installed and launched /Applications/DeskPad.app"
fi
121 changes: 121 additions & 0 deletions .agents/scripts/selftest-deskpad.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# @agents-index CR-0003 Phase 4 / FR-14: builds DeskPad Debug, launches the
# binary with `--self-test`, parses the PASS/FAIL verdict line from stdout
# (with a fallback to the rotating on-disk log), prints the verdict, and
# exits with the same status as the self-test process.
#
# Usage:
# .agents/scripts/selftest-deskpad.sh # build + run + verdict
# .agents/scripts/selftest-deskpad.sh --help # show this help
# .agents/scripts/selftest-deskpad.sh -h # show this help
#
# Exit codes:
# 0 PASS line observed
# 1 FAIL line observed (or no verdict line found)
#
# CR cross-reference: docs/cr/CR-0003-test-hardening-and-rendering-self-test.md.
#
# TCC / signing notes (carry-over from CR-0001):
# Screen Recording permission is bound to the code signature. Ad-hoc
# signatures change on every build, so an ad-hoc build re-prompts for TCC
# on every launch. To keep the grant stable, this script prefers the
# machine-local Apple Development identity recorded in `.env`
# (`DESKPAD_CODESIGN_IDENTITY`, optionally with `DESKPAD_DEVELOPMENT_TEAM`)
# and falls back to ad-hoc `CODE_SIGN_IDENTITY="-"` only when `.env` is
# absent. `.env` is git-ignored and contains a personal identity that must
# not leak into commits; see `.agents/scripts/build-deskpad-signed.sh`.
#
# Fallback note (CR-0003 Open Questions, virtual display addressability):
# The Phase 4 loopback runs entirely on an offscreen Metal texture; the
# captured-IOSurface comparison documented in FR-12 is dropped here,
# matching the CR's authorized fallback when the virtual display is not
# addressable from a headless self-test process.

set -euo pipefail

usage() {
sed -n '2,33p' "$0" | sed 's/^# \{0,1\}//'
}

case "${1:-}" in
-h|--help)
usage
exit 0
;;
"")
;;
*)
echo "Unknown argument: $1" >&2
echo "" >&2
usage >&2
exit 1
;;
esac

REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$REPO_ROOT"

# Prefer the pinned identity from .env so the TCC grant survives rebuilds.
# Fall back to ad-hoc signing only when .env is absent.
if [ -f .env ]; then
# shellcheck disable=SC1091
source .env
fi

BUILD_ARGS=(
-scheme DeskPad
-configuration Debug
-derivedDataPath build
)
if [ -n "${DESKPAD_CODESIGN_IDENTITY:-}" ]; then
echo "Signing with pinned identity from .env" >&2
BUILD_ARGS+=("CODE_SIGN_IDENTITY=${DESKPAD_CODESIGN_IDENTITY}")
if [ -n "${DESKPAD_DEVELOPMENT_TEAM:-}" ]; then
BUILD_ARGS+=("DEVELOPMENT_TEAM=${DESKPAD_DEVELOPMENT_TEAM}")
fi
else
echo "No .env signing identity; falling back to ad-hoc (-)" >&2
BUILD_ARGS+=('CODE_SIGN_IDENTITY=-')
fi

echo "Building DeskPad Debug..." >&2
xcodebuild "${BUILD_ARGS[@]}" build 2>&1 | tail -5

BINARY="build/Build/Products/Debug/DeskPad.app/Contents/MacOS/DeskPad"
if [ ! -x "$BINARY" ]; then
echo "ERROR: built binary not found at $BINARY" >&2
exit 1
fi

STDOUT_LOG="$(mktemp -t deskpad-selftest.XXXXXX)"
trap 'rm -f "$STDOUT_LOG"' EXIT

echo "Launching $BINARY --self-test" >&2
set +e
"$BINARY" --self-test >"$STDOUT_LOG" 2>&1
PROCESS_STATUS=$?
set -e

VERDICT="$(grep -E '^(PASS|FAIL):' "$STDOUT_LOG" | head -1 || true)"

# Fallback: if stdout did not carry the verdict (e.g. swallowed by AppKit's
# stream redirection), look for it in the rotating on-disk log file. Match
# the two-candidate enumeration used by tail-deskpad-log.sh.
if [ -z "$VERDICT" ]; then
SANDBOX_LOG="$HOME/Library/Containers/com.stengo.DeskPad/Data/Library/Logs/DeskPad/deskpad.log"
USER_LOG="$HOME/Library/Logs/DeskPad/deskpad.log"
for candidate in "$SANDBOX_LOG" "$USER_LOG"; do
if [ -f "$candidate" ]; then
VERDICT="$(grep -E '^(PASS|FAIL):' "$candidate" | tail -1 || true)"
if [ -n "$VERDICT" ]; then break; fi
fi
done
fi

if [ -z "$VERDICT" ]; then
echo "FAIL: no_verdict_line process_status=${PROCESS_STATUS}"
exit 1
fi

echo "$VERDICT"
exit "$PROCESS_STATUS"
80 changes: 80 additions & 0 deletions .agents/scripts/tail-deskpad-log.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# @agents-index Live-tail the DeskPad rotating log file written by the
# in-app LogFileSink. Resolves the sandboxed app container's Logs directory
# first (where a signed sandboxed build actually writes), then falls back to
# the non-sandboxed user Library path. Wraps `tail -F` so file rotation is
# followed across rename boundaries.
#
# Usage:
# .agents/scripts/tail-deskpad-log.sh # tail the active log file
# .agents/scripts/tail-deskpad-log.sh --path # print the resolved path and exit
# .agents/scripts/tail-deskpad-log.sh --help # show this help
#
# Exit codes:
# 0 normal exit (user interrupted tail, or --path/--help requested)
# 1 no log file found at either candidate path
#
# Why this exists:
# The project's CLI-first rule (see CLAUDE.md) says recurring operations
# live as scripts under .agents/scripts/. Inspecting the rotating log file
# is a recurring operation, so it is captured here instead of being
# retyped per session.

set -euo pipefail

usage() {
sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'
}

# Resolve the two candidate paths. The sandboxed path is the canonical home
# of the log file when DeskPad runs as a signed sandboxed build; the
# non-sandboxed path is used by ad-hoc local builds and by tests that bypass
# the sandbox.
SANDBOX_PATH="$HOME/Library/Containers/com.stengo.DeskPad/Data/Library/Logs/DeskPad/deskpad.log"
USER_PATH="$HOME/Library/Logs/DeskPad/deskpad.log"

resolve_path() {
if [ -f "$SANDBOX_PATH" ]; then
echo "$SANDBOX_PATH"
return 0
fi
if [ -f "$USER_PATH" ]; then
echo "$USER_PATH"
return 0
fi
return 1
}

case "${1:-}" in
-h|--help)
usage
exit 0
;;
--path)
if path=$(resolve_path); then
echo "$path"
exit 0
fi
echo "No DeskPad log file found. Checked:" >&2
echo " $SANDBOX_PATH" >&2
echo " $USER_PATH" >&2
exit 1
;;
"")
if ! path=$(resolve_path); then
echo "No DeskPad log file found. Checked:" >&2
echo " $SANDBOX_PATH" >&2
echo " $USER_PATH" >&2
echo "Run the app once so it can create the log file, then retry." >&2
exit 1
fi
echo "Tailing $path (Ctrl-C to stop)" >&2
exec tail -F "$path"
;;
*)
echo "Unknown argument: $1" >&2
echo "" >&2
usage >&2
exit 1
;;
esac
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# DeskPad local signing configuration - template.
#
# Copy to .env (gitignored) and fill in your own values. Never commit
# .env: it contains a personal identity and team ID that must not leak
# into the repository or upstream PRs.
#
# Consumed by .agents/scripts/build-deskpad-signed.sh. A stable signing
# identity keeps the macOS Screen Recording (TCC) grant valid across
# rebuilds; ad-hoc signatures re-prompt on every build. A free Apple ID
# is sufficient: Xcode -> Settings -> Accounts -> add account ->
# Manage Certificates -> + -> Apple Development.

# Keychain code-signing identity. List yours with:
# security find-identity -v -p codesigning
DESKPAD_CODESIGN_IDENTITY="Apple Development: you@example.com (XXXXXXXXXX)"

# Apple Developer team identifier. After signing once, read it with:
# codesign -dv /Applications/DeskPad.app 2>&1 | grep TeamIdentifier
DESKPAD_DEVELOPMENT_TEAM="YYYYYYYYYY"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
BuildTools/.build
BuildTools/.swiftpm
xcuserdata/

# Build artifacts
build/
*.log
.env
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"deepwiki": {
"type": "http",
"url": "https://mcp.deepwiki.com/mcp"
}
}
}
17 changes: 17 additions & 0 deletions .taxonomy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# DeskPad Taxonomy

## Rendering & Presentation

**present stall**: A condition detected by the Layer 1 watchdog when the rendering pipeline has ingested new frames from ScreenCaptureKit but has not advanced the presenter's frame count for three seconds, indicating a drawable-starvation failure class. Logs the literal prefix `present stall: ingested=N presented=M elapsed=S` at warning level through the structured logger so CI and agents can grep the on-disk log for regressions without human eyes.

**PresentationBackend**: The `@MainActor` protocol seam introduced by CR-0002 that the capture-render coordinator talks to instead of a concrete renderer. Defines `hostView`, `presentedFrameCount`, `diagnostics`, `configure(displaySize:scaleFactor:)`, `enqueue(_:)`, and `teardown()`. Two production conformances exist: `MetalBackend` (CR-0001 path adapted to the protocol) and `AVSBDLBackend`. The coordinator never branches on which backend is active; everything backend-specific lives behind the protocol.

**MetalBackend**: The default `PresentationBackend` conformance that wraps the CR-0001 `CAMetalLayer`-plus-`CAMetalDisplayLink` pipeline (`ScreenCaptureKit` capture, `IOSurface` zero-copy hand-off, blit pipeline, frame presenter, dirty-bit gate, newest-frame-wins drop policy). Optimised for lowest latency and supports the adaptive low-latency mode. Used by the rendering self-test unconditionally because Layer 2 and Layer 3 require texture read-back.

**AVSBDLBackend**: The opt-in `PresentationBackend` conformance that drives an `AVSampleBufferDisplayLayer` through its modern `sampleBufferRenderer` (`AVSampleBufferVideoRenderer`). Every enqueued `CMSampleBuffer` carries `kCMSampleAttachmentKey_DisplayImmediately = kCFBooleanTrue`; readiness is gated on `isReadyForMoreMediaData` with rate-limited drop logging; status KVO and the `DidFailToDecode` / `RequiresFlushToResumeDecoding` notifications all trigger a `flushWithRemovalOfDisplayedImage:` recovery. The deprecated layer-level methods (`enqueueSampleBuffer:`, `flush`, `flushAndRemoveImage`, `status`, `error`, `timebase`, `readyForMoreMediaData`, `requiresFlushToResumeDecoding`) are never used; a Compliance grep guard enforces this. The adaptive low-latency mode is not applicable while this backend is active.

**PresentationBackendDiagnostics**: A `Sendable` value type that every `PresentationBackend` exposes via its `diagnostics` property. Carries the backend `identifier` (`"metal"` or `"avsbdl"`), `latencyModeApplicable` (whether the adaptive mode controller's latency-mode request is honoured), `lastErrorDescription`, and `droppedFrameCount`. Lets the coordinator, adaptive mode controller, and present-stall watchdog reason about backend state without knowing which conformance is active.

## Self-Test & Diagnostics

**self-test mode**: A launch routing flag `--self-test` parsed in `main.swift` that bypasses the normal window construction and instead runs a three-layer autonomous rendering diagnostic: Layer 2 reads back the presented drawable's texture and asserts per-channel mean and variance statistics (catching the white-window failure class), and Layer 3 renders a known test pattern on the virtual display and asserts captured and presented pixel values match at sample points. Exit code 0 indicates PASS; non-zero indicates FAIL with a reason string greppable by agents and CI runners.
Loading