From 13553eaf9286f503493a38e7a8a87de2bb9b3f02 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Thu, 4 Jun 2026 22:27:28 +0800 Subject: [PATCH] {"schema":"decodex/commit/1","summary":"Align Decodex app pills and startup","authority":"manual"} --- README.md | 2 +- .../.codex/environments/environment.toml | 2 +- apps/decodex-app/README.md | 6 ++-- .../Sources/DecodexApp/AccountPanelView.swift | 30 ++++++++++++++----- .../DecodexApp/DecodexServerBridge.swift | 4 +-- .../DecodexAppTests/AccountModelTests.swift | 26 ++++++++++++++++ .../DecodexServerBridgeTests.swift | 4 +-- apps/decodex-app/script/build_and_run.sh | 4 +-- docs/reference/operator-control-plane.md | 13 ++++---- scripts/macos/test_decodex_app_stage.sh | 8 ++--- 10 files changed, 70 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 0cb4c42..a0b6548 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ Use hidden `decodex serve --dev --listen-address 127.0.0.1:8912` only when developing local account/app snapshot APIs against real runtime state while explicitly avoiding scheduler activity. Dev mode deliberately does not register projects, poll Linear, dispatch work, or accept `--config`. Decodex App's normal -fallback server is ordinary `decodex serve --listen-address 127.0.0.1:8912`; the CLI +fallback server is ordinary `decodex serve --listen-address 127.0.0.1:8192`; the CLI owns the default scheduler cadences. App launch connects to an existing live default listener instead of starting a duplicate server. For dashboard-only UI work, prefer the mock server above. diff --git a/apps/decodex-app/.codex/environments/environment.toml b/apps/decodex-app/.codex/environments/environment.toml index f96f57b..a0f89a1 100644 --- a/apps/decodex-app/.codex/environments/environment.toml +++ b/apps/decodex-app/.codex/environments/environment.toml @@ -1,5 +1,5 @@ # THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY -name = "Decodex App" +name = "Decodex" version = 1 [setup] diff --git a/apps/decodex-app/README.md b/apps/decodex-app/README.md index a7ff0ac..c7cf76e 100644 --- a/apps/decodex-app/README.md +++ b/apps/decodex-app/README.md @@ -14,7 +14,7 @@ bundled Rust app helper so account UI stays on the same CLI-owned files even whe long-running local `decodex serve` is older than the app bundle. On launch the app also connects to an existing `decodex serve` on the default local endpoint when one is available; otherwise it starts the bundled Decodex binary as a normal scheduler with -`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8912`. The override +`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8192`. The override only downgrades unverified Codex app-server identity to a warning; other preflight blockers remain fail-closed. The CLI owns the default scheduler interval, currently 15 seconds. App-started servers load the enabled project registry and own the same @@ -78,12 +78,12 @@ swift run --package-path apps/decodex-app DecodexApp Use hidden `decodex serve --dev` only when manually testing local account APIs, the app snapshot API, or dashboard routes while deliberately avoiding scheduler activity. The normal app fallback is `decodex serve --allow-unverified-codex --listen-address -127.0.0.1:8912` so development Codex app-server builds can still run through the local +127.0.0.1:8192` so development Codex app-server builds can still run through the local operator surface. Do not use `--dev` to validate project registration, Linear polling, queue intake, or retained-lane execution; use ordinary `decodex serve` for those paths. The staging script follows the local Rsnap-style signing path: it writes -`target/decodex-app/Decodex App.app`, signs the bundle with an Apple Development +`target/decodex-app/Decodex.app`, signs the bundle with an Apple Development identity, enables hardened runtime, and verifies the signature before launch. Override the signing identity with `DECODEX_APP_SIGN_IDENTITY`; override the staging directory with `DECODEX_APP_STAGE_DIR`. Override the Rust profile with diff --git a/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift b/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift index 4b07452..6f04376 100644 --- a/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift +++ b/apps/decodex-app/Sources/DecodexApp/AccountPanelView.swift @@ -678,11 +678,7 @@ struct AccountPanelView: View { ) } if let snapshot = displayableOperatorSnapshot { - rows.append( - snapshot.warningSummary == nil - ? AccountPanelLayout.telemetryOperatorHeight - : AccountPanelLayout.telemetryOperatorHeightWithWarning - ) + rows.append(operatorTelemetryHeight(for: snapshot)) } guard rows.isEmpty == false else { @@ -694,6 +690,19 @@ struct AccountPanelView: View { + CGFloat(rows.count - 1) * AccountPanelLayout.telemetryRowSpacing } + private func operatorTelemetryHeight(for snapshot: OperatorSnapshotResponse) -> CGFloat { + var rows: [CGFloat] = [AccountPanelLayout.telemetryOperatorMetricHeight] + if snapshot.activeRuns.isEmpty == false { + rows.append(AccountRunChipLayout.height) + } + if snapshot.warningSummary != nil { + rows.append(AccountPanelLayout.telemetryOperatorWarningHeight) + } + + return rows.reduce(0, +) + + CGFloat(rows.count - 1) * AccountPanelLayout.telemetryOperatorRowSpacing + } + private func displayName(for account: CodexAccount) -> String { if emailsHidden { return AccountDisplay.aliases(for: store.accounts)[account.id] @@ -1462,8 +1471,9 @@ private enum AccountPanelLayout { static let telemetryProfileHeight: CGFloat = 50 static let telemetryPoolHeight: CGFloat = 16 static let telemetryPoolMeasuredHeight: CGFloat = 29 - static let telemetryOperatorHeight: CGFloat = 16 - static let telemetryOperatorHeightWithWarning: CGFloat = 36 + static let telemetryOperatorMetricHeight: CGFloat = 16 + static let telemetryOperatorWarningHeight: CGFloat = 16 + static let telemetryOperatorRowSpacing: CGFloat = 4 static let noticeHeight: CGFloat = 44 static let minimumScrollableListHeight: CGFloat = 312 @@ -2501,7 +2511,7 @@ struct OperatorStatusStripView: View { @Environment(\.colorScheme) private var colorScheme var body: some View { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: AccountPanelLayout.telemetryOperatorRowSpacing) { HStack(spacing: 5) { ForEach(Array(metrics.enumerated()), id: \.element.id) { index, metric in OperatorFlowMetricView(metric: metric) @@ -2513,6 +2523,10 @@ struct OperatorStatusStripView: View { } .frame(height: 16) + if snapshot.activeRuns.isEmpty == false { + AccountRunSummaryView(runs: snapshot.activeRuns) + } + if let warning = snapshot.warningSummary { HStack(alignment: .firstTextBaseline, spacing: 5) { PanelMetricIconView( diff --git a/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift b/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift index f6b714b..f890ab6 100644 --- a/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift +++ b/apps/decodex-app/Sources/DecodexApp/DecodexServerBridge.swift @@ -16,7 +16,7 @@ actor DecodexServerBridge { static let shared = DecodexServerBridge() private let defaultBaseURL = DecodexServerBridge.makeDefaultBaseURL() - private let defaultListenAddress = "127.0.0.1:8912" + private let defaultListenAddress = "127.0.0.1:8192" private let liveCheckFreshness: TimeInterval = 5 private var serverBaseURL: URL? private var liveCheckBaseURL: URL? @@ -282,7 +282,7 @@ actor DecodexServerBridge { } private static func makeDefaultBaseURL() -> URL { - guard let url = URL(string: "http://127.0.0.1:8912") else { + guard let url = URL(string: "http://127.0.0.1:8192") else { preconditionFailure("default Decodex server URL must be valid") } diff --git a/apps/decodex-app/Tests/DecodexAppTests/AccountModelTests.swift b/apps/decodex-app/Tests/DecodexAppTests/AccountModelTests.swift index 3d790bd..050cfa4 100644 --- a/apps/decodex-app/Tests/DecodexAppTests/AccountModelTests.swift +++ b/apps/decodex-app/Tests/DecodexAppTests/AccountModelTests.swift @@ -162,6 +162,32 @@ final class AccountModelTests: XCTestCase { XCTAssertTrue(snapshot.activeRuns(for: poolOnlyAccount).isEmpty) } + func testOperatorSnapshotKeepsUnassignedActiveRunsVisibleGlobally() throws { + let account = makeAccount( + status: "available", + email: "pool@example.com", + accountFingerprint: "...654321" + ) + let payload = """ + { + "active_runs": [ + { + "run_id": "run-unassigned", + "project_id": "pubfi-platform", + "issue_identifier": "PUB-1296", + "status": "running" + } + ] + } + """.data(using: .utf8)! + + let snapshot = try JSONDecoder().decode(OperatorSnapshotResponse.self, from: payload) + + XCTAssertEqual(snapshot.activeRuns.map(\.runID), ["run-unassigned"]) + XCTAssertEqual(snapshot.activeRunCount, 1) + XCTAssertTrue(snapshot.activeRuns(for: account).isEmpty) + } + func testOperatorSnapshotAssignsSelectedAccountWhenPrimaryAccountIsMissing() throws { let assignedAccount = makeAccount( status: "available", diff --git a/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift b/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift index a8d9d35..c2e3db5 100644 --- a/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift +++ b/apps/decodex-app/Tests/DecodexAppTests/DecodexServerBridgeTests.swift @@ -4,12 +4,12 @@ import XCTest final class DecodexServerBridgeTests: XCTestCase { func testBundledServerArgumentsAllowUnverifiedCodex() { XCTAssertEqual( - DecodexServerBridge.bundledServerArguments(listenAddress: "127.0.0.1:8912"), + DecodexServerBridge.bundledServerArguments(listenAddress: "127.0.0.1:8192"), [ "serve", "--allow-unverified-codex", "--listen-address", - "127.0.0.1:8912", + "127.0.0.1:8192", ] ) } diff --git a/apps/decodex-app/script/build_and_run.sh b/apps/decodex-app/script/build_and_run.sh index 5de4e20..aeeef85 100755 --- a/apps/decodex-app/script/build_and_run.sh +++ b/apps/decodex-app/script/build_and_run.sh @@ -2,7 +2,7 @@ set -euo pipefail MODE="${1:-run}" -PRODUCT_NAME="Decodex App" +PRODUCT_NAME="Decodex" EXECUTABLE_NAME="DecodexApp" HELPER_NAME="decodex-app-helper" SERVER_NAME="decodex" @@ -148,7 +148,7 @@ sign_staged_app_bundle() { if ! resolve_signing_identity; then echo "error: no valid macOS codesigning identity matching the configured signing selector was found." >&2 echo "error: import the real signing certificate or set DECODEX_APP_SIGN_IDENTITY to a valid identity." >&2 - echo "error: Decodex App staging requires a stable codesigning identity." >&2 + echo "error: Decodex.app staging requires a stable codesigning identity." >&2 exit 1 fi diff --git a/docs/reference/operator-control-plane.md b/docs/reference/operator-control-plane.md index b2de12f..b105e43 100644 --- a/docs/reference/operator-control-plane.md +++ b/docs/reference/operator-control-plane.md @@ -36,9 +36,10 @@ Decodex currently runs as a local, single-machine control plane: Decodex App is a native shell over the same local runtime and account-pool state. On launch it connects to an existing default local listener when one is reachable; if not, it starts the bundled `decodex` binary as -`decodex serve --listen-address 127.0.0.1:8912`. The app fallback is a normal -control-plane server: it loads the registered project registry, schedules only enabled -projects, keeps active runtime DB-backed runs visible even when a project is disabled +`decodex serve --allow-unverified-codex --listen-address 127.0.0.1:8192`. The app +fallback is a normal control-plane server: it loads the registered project registry, +schedules only enabled projects, keeps active runtime DB-backed runs visible even when +a project is disabled for future dispatch, uses the CLI-owned default cadences, and serves the dashboard, account APIs, `GET /api/operator-snapshot`, `POST /api/linear-scan`, `GET /api/lane/inspect`, and `POST /api/lane/interrupt` from @@ -55,7 +56,7 @@ Agents that just created or relabeled queue issues can avoid waiting for the nex 5-minute Linear poll by sending a targeted local request: ```sh -curl -sS -X POST http://127.0.0.1:8912/api/linear-scan \ +curl -sS -X POST http://127.0.0.1:8192/api/linear-scan \ -H 'Content-Type: application/json' \ -d '{"projectId":"decodex"}' ``` @@ -67,8 +68,8 @@ rate-limit backoff. Lane inspect and interrupt are local control APIs, not dashboard UI actions: ```sh -curl -sS 'http://127.0.0.1:8912/api/lane/inspect?projectId=decodex&issue=XY-703' -curl -sS -X POST http://127.0.0.1:8912/api/lane/interrupt \ +curl -sS 'http://127.0.0.1:8192/api/lane/inspect?projectId=decodex&issue=XY-703' +curl -sS -X POST http://127.0.0.1:8192/api/lane/interrupt \ -H 'Content-Type: application/json' \ -d '{"projectId":"decodex","issue":"XY-703","runId":""}' ``` diff --git a/scripts/macos/test_decodex_app_stage.sh b/scripts/macos/test_decodex_app_stage.sh index 1129760..29da0ed 100755 --- a/scripts/macos/test_decodex_app_stage.sh +++ b/scripts/macos/test_decodex_app_stage.sh @@ -2,7 +2,7 @@ set -euo pipefail if [[ "$(uname -s)" != "Darwin" ]]; then - echo "Decodex App staging is macOS-only; skipping." + echo "Decodex.app staging is macOS-only; skipping." exit 0 fi @@ -10,7 +10,7 @@ fi common_root="$(cd "$(git rev-parse --git-common-dir)/.." && pwd)" stage_dir="${DECODEX_APP_STAGE_DIR:-$common_root/target/decodex-app}" -app_path="$stage_dir/Decodex App.app" +app_path="$stage_dir/Decodex.app" test -d "$app_path" test -x "$app_path/Contents/MacOS/DecodexApp" @@ -27,8 +27,8 @@ codesign_details="$(codesign -dv --verbose=4 "$app_path" 2>&1)" grep -q '^TeamIdentifier=' <<<"$codesign_details" grep -q 'flags=.*runtime' <<<"$codesign_details" -plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex App' -plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex App' +plutil -extract CFBundleName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex' +plutil -extract CFBundleDisplayName raw "$app_path/Contents/Info.plist" | grep -qx 'Decodex' plutil -extract CFBundleIconFile raw "$app_path/Contents/Info.plist" | grep -qx 'AppIcon' plutil -extract CFBundleIdentifier raw "$app_path/Contents/Info.plist" | grep -qx 'space.decodex.app' plutil -extract LSUIElement raw "$app_path/Contents/Info.plist" | grep -qx 'true'