chore(release): support manual draft builds
This commit is contained in:
parent
4a8cec9dc2
commit
7742c528ad
6 changed files with 195 additions and 161 deletions
185
.github/workflows/release.yml
vendored
185
.github/workflows/release.yml
vendored
|
|
@ -5,14 +5,29 @@ on:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
description: Release tag to build into a draft, for example v2.0.0
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
publish_release:
|
||||||
|
description: Publish the release after all assets are uploaded
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: release-${{ github.ref }}
|
group: release-${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || inputs.release_tag || github.ref }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
IS_RELEASE_BUILD: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }}
|
||||||
|
RELEASE_TAG: ${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || inputs.release_tag }}
|
||||||
|
PUBLISH_RELEASE: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.publish_release }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -36,14 +51,26 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set version from tag
|
- name: Validate release metadata
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
set -euo pipefail
|
||||||
|
case "${RELEASE_TAG:-}" in
|
||||||
|
v[0-9]*) ;;
|
||||||
|
*)
|
||||||
|
echo "release_tag must start with v and include a numeric version, got '${RELEASE_TAG:-<empty>}'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
- name: Set version from release tag
|
||||||
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
|
run: |
|
||||||
|
VERSION="${RELEASE_TAG#v}"
|
||||||
pnpm pkg set version="$VERSION"
|
pnpm pkg set version="$VERSION"
|
||||||
|
|
||||||
- name: Verify Sentry release env
|
- name: Verify Sentry release env
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -54,14 +81,14 @@ jobs:
|
||||||
- name: Build app
|
- name: Build app
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||||
SENTRY_DSN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_DSN || '' }}
|
SENTRY_DSN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_DSN || '' }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
SENTRY_AUTH_TOKEN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Verify Sentry source map upload
|
- name: Verify Sentry source map upload
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -70,16 +97,28 @@ jobs:
|
||||||
run: node ./scripts/ci/verify-sentry-release.cjs postbuild
|
run: node ./scripts/ci/verify-sentry-release.cjs postbuild
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
TAG="${RELEASE_TAG:-$TAG}"
|
||||||
|
existing_is_draft="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' 2>/dev/null || true)"
|
||||||
|
if [ -n "$existing_is_draft" ]; then
|
||||||
|
if [ "${PUBLISH_RELEASE}" != "true" ] && [ "$existing_is_draft" != "true" ]; then
|
||||||
|
echo "Release $TAG already exists and is not a draft; refusing to overwrite it from draft mode." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Release $TAG already exists, skipping creation"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
gh release create "$TAG" \
|
gh release create "$TAG" \
|
||||||
--repo "$GITHUB_REPOSITORY" \
|
--repo "$GITHUB_REPOSITORY" \
|
||||||
|
--target "$GITHUB_SHA" \
|
||||||
--title "$TAG" \
|
--title "$TAG" \
|
||||||
--generate-notes \
|
--generate-notes \
|
||||||
--draft=true 2>/dev/null || echo "Release $TAG already exists, skipping creation"
|
--draft=true
|
||||||
|
|
||||||
- name: Upload dist artifact
|
- name: Upload dist artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
|
|
@ -98,30 +137,55 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Validate release metadata
|
||||||
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
case "${RELEASE_TAG:-}" in
|
||||||
|
v[0-9]*) ;;
|
||||||
|
*)
|
||||||
|
echo "release_tag must start with v and include a numeric version, got '${RELEASE_TAG:-<empty>}'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
TAG="${RELEASE_TAG:-$TAG}"
|
||||||
|
existing_is_draft="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft' 2>/dev/null || true)"
|
||||||
|
if [ -n "$existing_is_draft" ]; then
|
||||||
|
if [ "${PUBLISH_RELEASE}" != "true" ] && [ "$existing_is_draft" != "true" ]; then
|
||||||
|
echo "Release $TAG already exists and is not a draft; refusing to overwrite it from draft mode." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Release $TAG already exists, skipping creation"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
gh release create "$TAG" \
|
gh release create "$TAG" \
|
||||||
--repo "$GITHUB_REPOSITORY" \
|
--repo "$GITHUB_REPOSITORY" \
|
||||||
|
--target "$GITHUB_SHA" \
|
||||||
--title "$TAG" \
|
--title "$TAG" \
|
||||||
--generate-notes \
|
--generate-notes \
|
||||||
--draft=true 2>/dev/null || echo "Release $TAG already exists, skipping creation"
|
--draft=true
|
||||||
|
|
||||||
- name: Skip runtime asset preparation for manual builds
|
- name: Skip runtime asset preparation for non-release builds
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ env.IS_RELEASE_BUILD != 'true' }}
|
||||||
run: echo "Runtime asset preparation is only needed for tagged releases."
|
run: echo "Runtime asset preparation is only needed for release builds."
|
||||||
|
|
||||||
- name: Check runtime assets
|
- name: Check runtime assets
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
id: runtime-assets
|
id: runtime-assets
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
TAG="${RELEASE_TAG:-$TAG}"
|
||||||
existing="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' 2>/dev/null || true)"
|
existing="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' 2>/dev/null || true)"
|
||||||
missing=0
|
missing=0
|
||||||
while IFS= read -r asset; do
|
while IFS= read -r asset; do
|
||||||
|
|
@ -144,7 +208,7 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TARGET_TAG="${GITHUB_REF#refs/tags/}"
|
TARGET_TAG="${RELEASE_TAG}"
|
||||||
SOURCE_REPO="$(node ./scripts/runtime-lock.mjs source-repository)"
|
SOURCE_REPO="$(node ./scripts/runtime-lock.mjs source-repository)"
|
||||||
SOURCE_REF="$(node ./scripts/runtime-lock.mjs source-ref)"
|
SOURCE_REF="$(node ./scripts/runtime-lock.mjs source-ref)"
|
||||||
RUNTIME_VERSION="$(node ./scripts/runtime-lock.mjs version)"
|
RUNTIME_VERSION="$(node ./scripts/runtime-lock.mjs version)"
|
||||||
|
|
@ -215,6 +279,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${GITHUB_REF#refs/tags/}"
|
||||||
|
TAG="${RELEASE_TAG:-$TAG}"
|
||||||
for attempt in $(seq 1 12); do
|
for attempt in $(seq 1 12); do
|
||||||
existing="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' 2>/dev/null || true)"
|
existing="$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' 2>/dev/null || true)"
|
||||||
all_found=1
|
all_found=1
|
||||||
|
|
@ -281,10 +346,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set version from tag
|
- name: Set version from release tag
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
VERSION="${RELEASE_TAG#v}"
|
||||||
pnpm pkg set version="$VERSION"
|
pnpm pkg set version="$VERSION"
|
||||||
|
|
||||||
- name: Stage bundled runtime (macOS ${{ matrix.arch }})
|
- name: Stage bundled runtime (macOS ${{ matrix.arch }})
|
||||||
|
|
@ -293,15 +358,15 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
|
if [[ "${IS_RELEASE_BUILD}" == "true" ]]; then
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${RELEASE_TAG}"
|
||||||
node ./scripts/stage-runtime.mjs --platform "darwin-${{ matrix.arch }}" --release-tag "$TAG"
|
node ./scripts/stage-runtime.mjs --platform "darwin-${{ matrix.arch }}" --release-tag "$TAG"
|
||||||
else
|
else
|
||||||
node ./scripts/stage-runtime.mjs --platform "darwin-${{ matrix.arch }}"
|
node ./scripts/stage-runtime.mjs --platform "darwin-${{ matrix.arch }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Verify Sentry release env (macOS ${{ matrix.arch }})
|
- name: Verify Sentry release env (macOS ${{ matrix.arch }})
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -312,14 +377,14 @@ jobs:
|
||||||
- name: Build app (macOS ${{ matrix.arch }})
|
- name: Build app (macOS ${{ matrix.arch }})
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||||
SENTRY_DSN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_DSN || '' }}
|
SENTRY_DSN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_DSN || '' }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
SENTRY_AUTH_TOKEN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Verify Sentry source map upload (macOS ${{ matrix.arch }})
|
- name: Verify Sentry source map upload (macOS ${{ matrix.arch }})
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -352,11 +417,11 @@ jobs:
|
||||||
run: node ./scripts/electron-builder/smokePackagedApp.cjs "release/mac-${{ matrix.arch }}/Agent Teams UI.app" darwin
|
run: node ./scripts/electron-builder/smokePackagedApp.cjs "release/mac-${{ matrix.arch }}/Agent Teams UI.app" darwin
|
||||||
|
|
||||||
- name: Upload assets to release
|
- name: Upload assets to release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${RELEASE_TAG}"
|
||||||
for f in release/*.dmg release/*.zip release/*.blockmap; do
|
for f in release/*.dmg release/*.zip release/*.blockmap; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
echo "Uploading: $f"
|
echo "Uploading: $f"
|
||||||
|
|
@ -397,11 +462,11 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set version from tag
|
- name: Set version from release tag
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
VERSION="${RELEASE_TAG#v}"
|
||||||
pnpm pkg set version="$VERSION"
|
pnpm pkg set version="$VERSION"
|
||||||
|
|
||||||
- name: Stage bundled runtime (Windows)
|
- name: Stage bundled runtime (Windows)
|
||||||
|
|
@ -410,15 +475,15 @@ jobs:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
if ($env:GITHUB_REF -like 'refs/tags/v*') {
|
if ($env:IS_RELEASE_BUILD -eq 'true') {
|
||||||
$tag = $env:GITHUB_REF.Replace('refs/tags/', '')
|
$tag = $env:RELEASE_TAG
|
||||||
node ./scripts/stage-runtime.mjs --platform win32-x64 --release-tag $tag
|
node ./scripts/stage-runtime.mjs --platform win32-x64 --release-tag $tag
|
||||||
} else {
|
} else {
|
||||||
node ./scripts/stage-runtime.mjs --platform win32-x64
|
node ./scripts/stage-runtime.mjs --platform win32-x64
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Verify Sentry release env (Windows)
|
- name: Verify Sentry release env (Windows)
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -429,14 +494,14 @@ jobs:
|
||||||
- name: Build app (Windows)
|
- name: Build app (Windows)
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||||
SENTRY_DSN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_DSN || '' }}
|
SENTRY_DSN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_DSN || '' }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
SENTRY_AUTH_TOKEN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Verify Sentry source map upload (Windows)
|
- name: Verify Sentry source map upload (Windows)
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -467,12 +532,12 @@ jobs:
|
||||||
run: node ./scripts/electron-builder/smokePackagedApp.cjs "release/win-unpacked" win32
|
run: node ./scripts/electron-builder/smokePackagedApp.cjs "release/win-unpacked" win32
|
||||||
|
|
||||||
- name: Upload assets to release
|
- name: Upload assets to release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${RELEASE_TAG}"
|
||||||
for f in release/*.exe release/*.blockmap; do
|
for f in release/*.exe release/*.blockmap; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
echo "Uploading: $f"
|
echo "Uploading: $f"
|
||||||
|
|
@ -518,10 +583,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Set version from tag
|
- name: Set version from release tag
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
VERSION="${RELEASE_TAG#v}"
|
||||||
pnpm pkg set version="$VERSION"
|
pnpm pkg set version="$VERSION"
|
||||||
|
|
||||||
- name: Stage bundled runtime (Linux)
|
- name: Stage bundled runtime (Linux)
|
||||||
|
|
@ -530,15 +595,15 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
|
if [[ "${IS_RELEASE_BUILD}" == "true" ]]; then
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${RELEASE_TAG}"
|
||||||
node ./scripts/stage-runtime.mjs --platform linux-x64 --release-tag "$TAG"
|
node ./scripts/stage-runtime.mjs --platform linux-x64 --release-tag "$TAG"
|
||||||
else
|
else
|
||||||
node ./scripts/stage-runtime.mjs --platform linux-x64
|
node ./scripts/stage-runtime.mjs --platform linux-x64
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Verify Sentry release env (Linux)
|
- name: Verify Sentry release env (Linux)
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -549,14 +614,14 @@ jobs:
|
||||||
- name: Build app (Linux)
|
- name: Build app (Linux)
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max-old-space-size=8192'
|
NODE_OPTIONS: '--max-old-space-size=8192'
|
||||||
SENTRY_DSN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_DSN || '' }}
|
SENTRY_DSN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_DSN || '' }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ startsWith(github.ref, 'refs/tags/v') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
SENTRY_AUTH_TOKEN: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && secrets.SENTRY_AUTH_TOKEN || '' }}
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
- name: Verify Sentry source map upload (Linux)
|
- name: Verify Sentry source map upload (Linux)
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
|
@ -584,12 +649,12 @@ jobs:
|
||||||
run: xvfb-run -a node ./scripts/electron-builder/smokePackagedApp.cjs "release/linux-unpacked" linux
|
run: xvfb-run -a node ./scripts/electron-builder/smokePackagedApp.cjs "release/linux-unpacked" linux
|
||||||
|
|
||||||
- name: Upload assets to release
|
- name: Upload assets to release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ env.IS_RELEASE_BUILD == 'true' }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${RELEASE_TAG}"
|
||||||
ls -la release/ || true
|
ls -la release/ || true
|
||||||
for f in release/*.AppImage release/*.deb release/*.rpm release/*.pacman release/*.blockmap; do
|
for f in release/*.AppImage release/*.deb release/*.rpm release/*.pacman release/*.blockmap; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
|
|
@ -602,7 +667,7 @@ jobs:
|
||||||
needs: [release-mac, release-win, release-linux]
|
needs: [release-mac, release-win, release-linux]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Upload stable-named assets for /latest/download links
|
- name: Upload stable-named assets for /latest/download links
|
||||||
|
|
@ -610,7 +675,8 @@ jobs:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
VERSION="${RELEASE_TAG#v}"
|
||||||
|
TAG="${RELEASE_TAG}"
|
||||||
REPO="${GITHUB_REPOSITORY}"
|
REPO="${GITHUB_REPOSITORY}"
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
|
@ -629,13 +695,13 @@ jobs:
|
||||||
for STABLE_NAME in "${!FILES[@]}"; do
|
for STABLE_NAME in "${!FILES[@]}"; do
|
||||||
VERSIONED_NAME="${FILES[$STABLE_NAME]}"
|
VERSIONED_NAME="${FILES[$STABLE_NAME]}"
|
||||||
echo "Downloading ${VERSIONED_NAME} -> ${STABLE_NAME}"
|
echo "Downloading ${VERSIONED_NAME} -> ${STABLE_NAME}"
|
||||||
gh release download "v${VERSION}" \
|
gh release download "${TAG}" \
|
||||||
--repo "$REPO" \
|
--repo "$REPO" \
|
||||||
--pattern "${VERSIONED_NAME}" \
|
--pattern "${VERSIONED_NAME}" \
|
||||||
--dir "$TMP_DIR" \
|
--dir "$TMP_DIR" \
|
||||||
--clobber
|
--clobber
|
||||||
cp "${TMP_DIR}/${VERSIONED_NAME}" "${TMP_DIR}/${STABLE_NAME}"
|
cp "${TMP_DIR}/${VERSIONED_NAME}" "${TMP_DIR}/${STABLE_NAME}"
|
||||||
gh release upload "v${VERSION}" "${TMP_DIR}/${STABLE_NAME}" --repo "$REPO" --clobber
|
gh release upload "${TAG}" "${TMP_DIR}/${STABLE_NAME}" --repo "$REPO" --clobber
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Publish canonical updater metadata
|
- name: Publish canonical updater metadata
|
||||||
|
|
@ -643,8 +709,8 @@ jobs:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
VERSION="${RELEASE_TAG#v}"
|
||||||
TAG="v${VERSION}"
|
TAG="${RELEASE_TAG}"
|
||||||
REPO="${GITHUB_REPOSITORY}"
|
REPO="${GITHUB_REPOSITORY}"
|
||||||
RELEASE_DATE="$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
|
RELEASE_DATE="$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
|
@ -724,9 +790,14 @@ jobs:
|
||||||
gh release upload "${TAG}" latest.yml latest-linux.yml latest-mac.yml --repo "${REPO}" --clobber
|
gh release upload "${TAG}" latest.yml latest-linux.yml latest-mac.yml --repo "${REPO}" --clobber
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.publish_release }}
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TAG="${GITHUB_REF#refs/tags/}"
|
TAG="${RELEASE_TAG}"
|
||||||
gh release edit "${TAG}" --repo "${GITHUB_REPOSITORY}" --draft=false --latest
|
gh release edit "${TAG}" --repo "${GITHUB_REPOSITORY}" --draft=false --latest
|
||||||
|
|
||||||
|
- name: Keep release as draft
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && !inputs.publish_release }}
|
||||||
|
run: echo "Draft release ${RELEASE_TAG} is ready. It was not published because publish_release=false."
|
||||||
|
|
|
||||||
|
|
@ -1397,7 +1397,6 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
});
|
});
|
||||||
const [creatingTask, setCreatingTask] = useState(false);
|
const [creatingTask, setCreatingTask] = useState(false);
|
||||||
const [addMemberDialogOpen, setAddMemberDialogOpen] = useState(false);
|
const [addMemberDialogOpen, setAddMemberDialogOpen] = useState(false);
|
||||||
const [runtimeTelemetryPreviewVisible, setRuntimeTelemetryPreviewVisible] = useState(false);
|
|
||||||
const [addingMemberLoading, setAddingMemberLoading] = useState(false);
|
const [addingMemberLoading, setAddingMemberLoading] = useState(false);
|
||||||
const [removeMemberConfirm, setRemoveMemberConfirm] = useState<string | null>(null);
|
const [removeMemberConfirm, setRemoveMemberConfirm] = useState<string | null>(null);
|
||||||
const [updatingRoleLoading, setUpdatingRoleLoading] = useState(false);
|
const [updatingRoleLoading, setUpdatingRoleLoading] = useState(false);
|
||||||
|
|
@ -2872,68 +2871,63 @@ export const TeamDetailView = memo(function TeamDetailView({
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<CollapsibleTeamSection
|
<div className="runtime-telemetry-hover-scope">
|
||||||
sectionId="team"
|
<CollapsibleTeamSection
|
||||||
title="Team"
|
sectionId="team"
|
||||||
icon={<Users size={14} />}
|
title="Team"
|
||||||
badge={activeTeammateCount === 0 ? 'Solo' : activeTeammateCount}
|
icon={<Users size={14} />}
|
||||||
defaultOpen
|
badge={activeTeammateCount === 0 ? 'Solo' : activeTeammateCount}
|
||||||
afterBadge={
|
defaultOpen
|
||||||
<Button
|
afterBadge={
|
||||||
variant="ghost"
|
<Button
|
||||||
size="sm"
|
variant="ghost"
|
||||||
className="pointer-events-auto h-6 gap-1 px-2 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
|
size="sm"
|
||||||
onClick={(e) => {
|
className="pointer-events-auto h-6 gap-1 px-2 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
setAddMemberDialogOpen(true);
|
e.stopPropagation();
|
||||||
}}
|
setAddMemberDialogOpen(true);
|
||||||
>
|
}}
|
||||||
<UserPlus size={12} />
|
>
|
||||||
Add
|
<UserPlus size={12} />
|
||||||
</Button>
|
Add
|
||||||
}
|
</Button>
|
||||||
action={
|
}
|
||||||
<div
|
action={
|
||||||
className={cn(
|
<div className="runtime-telemetry-legend flex items-center gap-3 pr-3 text-[11px] font-medium leading-none text-[var(--color-text-muted)] opacity-0 transition-opacity duration-150">
|
||||||
'flex items-center gap-3 pr-3 text-[11px] font-medium leading-none text-[var(--color-text-muted)] transition-opacity duration-150',
|
<span className="flex items-center gap-1.5">
|
||||||
runtimeTelemetryPreviewVisible ? 'opacity-100' : 'opacity-0'
|
<span className="size-2 rounded-full bg-emerald-500 shadow-[0_0_6px_rgba(34,197,94,0.3)]" />
|
||||||
)}
|
Memory
|
||||||
>
|
</span>
|
||||||
<span className="flex items-center gap-1.5">
|
<span className="flex items-center gap-1.5">
|
||||||
<span className="size-2 rounded-full bg-emerald-500 shadow-[0_0_6px_rgba(34,197,94,0.3)]" />
|
<span className="size-2 rounded-full bg-blue-500 shadow-[0_0_6px_rgba(59,130,246,0.3)]" />
|
||||||
Memory
|
CPU
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1.5">
|
</div>
|
||||||
<span className="size-2 rounded-full bg-blue-500 shadow-[0_0_6px_rgba(59,130,246,0.3)]" />
|
}
|
||||||
CPU
|
contentWrapperClassName="-mx-[calc(1rem-5px)] w-[calc(100%+2rem-10px)]"
|
||||||
</span>
|
>
|
||||||
|
<div className="px-[calc(1rem-5px)]">
|
||||||
|
<TeamMemberListBridge
|
||||||
|
teamName={teamName}
|
||||||
|
members={membersWithLiveBranches}
|
||||||
|
expectedTeammateCount={activeTeammateCount}
|
||||||
|
memberTaskCounts={memberTaskCounts}
|
||||||
|
taskMap={taskMap}
|
||||||
|
pendingRepliesByMember={pendingRepliesByMember}
|
||||||
|
isRosterLoading={loading}
|
||||||
|
isTeamAlive={data.isAlive}
|
||||||
|
isTeamProvisioning={isTeamProvisioning}
|
||||||
|
launchParams={launchParams}
|
||||||
|
onMemberClick={handleSelectMember}
|
||||||
|
onSendMessage={handleSendMessageToMember}
|
||||||
|
onAssignTask={handleAssignTaskToMember}
|
||||||
|
onOpenTask={handleOpenTaskById}
|
||||||
|
onRestartMember={handleRestartMember}
|
||||||
|
onSkipMemberForLaunch={handleSkipMemberForLaunch}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
</CollapsibleTeamSection>
|
||||||
contentWrapperClassName="-mx-[calc(1rem-5px)] w-[calc(100%+2rem-10px)]"
|
</div>
|
||||||
>
|
|
||||||
<div className="px-[calc(1rem-5px)]">
|
|
||||||
<TeamMemberListBridge
|
|
||||||
teamName={teamName}
|
|
||||||
members={membersWithLiveBranches}
|
|
||||||
expectedTeammateCount={activeTeammateCount}
|
|
||||||
memberTaskCounts={memberTaskCounts}
|
|
||||||
taskMap={taskMap}
|
|
||||||
pendingRepliesByMember={pendingRepliesByMember}
|
|
||||||
isRosterLoading={loading}
|
|
||||||
isTeamAlive={data.isAlive}
|
|
||||||
isTeamProvisioning={isTeamProvisioning}
|
|
||||||
launchParams={launchParams}
|
|
||||||
runtimeTelemetryVisible={runtimeTelemetryPreviewVisible}
|
|
||||||
onRuntimeTelemetryHoverChange={setRuntimeTelemetryPreviewVisible}
|
|
||||||
onMemberClick={handleSelectMember}
|
|
||||||
onSendMessage={handleSendMessageToMember}
|
|
||||||
onAssignTask={handleAssignTaskToMember}
|
|
||||||
onOpenTask={handleOpenTaskById}
|
|
||||||
onRestartMember={handleRestartMember}
|
|
||||||
onSkipMemberForLaunch={handleSkipMemberForLaunch}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CollapsibleTeamSection>
|
|
||||||
|
|
||||||
<CollapsibleTeamSection
|
<CollapsibleTeamSection
|
||||||
sectionId="sessions"
|
sectionId="sessions"
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,6 @@ interface MemberCardProps {
|
||||||
spawnLaunchState?: MemberLaunchState;
|
spawnLaunchState?: MemberLaunchState;
|
||||||
spawnRuntimeAlive?: boolean;
|
spawnRuntimeAlive?: boolean;
|
||||||
isLaunchSettling?: boolean;
|
isLaunchSettling?: boolean;
|
||||||
runtimeTelemetryVisible?: boolean;
|
|
||||||
runtimeTelemetryScale?: RuntimeTelemetryScale;
|
runtimeTelemetryScale?: RuntimeTelemetryScale;
|
||||||
onOpenTask?: () => void;
|
onOpenTask?: () => void;
|
||||||
onOpenReviewTask?: () => void;
|
onOpenReviewTask?: () => void;
|
||||||
|
|
@ -502,11 +501,9 @@ function buildRuntimeTelemetryPaths(
|
||||||
|
|
||||||
const MemberRuntimeTelemetryStrip = memo(function MemberRuntimeTelemetryStrip({
|
const MemberRuntimeTelemetryStrip = memo(function MemberRuntimeTelemetryStrip({
|
||||||
runtimeEntry,
|
runtimeEntry,
|
||||||
visible,
|
|
||||||
scale,
|
scale,
|
||||||
}: {
|
}: {
|
||||||
runtimeEntry?: TeamAgentRuntimeEntry;
|
runtimeEntry?: TeamAgentRuntimeEntry;
|
||||||
visible: boolean;
|
|
||||||
scale?: RuntimeTelemetryScale;
|
scale?: RuntimeTelemetryScale;
|
||||||
}): React.JSX.Element | null {
|
}): React.JSX.Element | null {
|
||||||
const paths = useMemo(
|
const paths = useMemo(
|
||||||
|
|
@ -521,10 +518,7 @@ const MemberRuntimeTelemetryStrip = memo(function MemberRuntimeTelemetryStrip({
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
data-testid="member-runtime-telemetry-strip"
|
data-testid="member-runtime-telemetry-strip"
|
||||||
className={cn(
|
className="runtime-telemetry-strip pointer-events-none absolute inset-x-0 bottom-0 z-0 h-5 overflow-hidden rounded-b opacity-0 transition-opacity duration-150"
|
||||||
'pointer-events-none absolute inset-x-0 bottom-0 z-0 h-5 overflow-hidden rounded-b transition-opacity duration-150',
|
|
||||||
visible ? 'opacity-100' : 'opacity-0'
|
|
||||||
)}
|
|
||||||
style={{
|
style={{
|
||||||
WebkitMaskImage:
|
WebkitMaskImage:
|
||||||
'linear-gradient(to right, transparent 0, black 44px, black calc(100% - 44px), transparent 100%)',
|
'linear-gradient(to right, transparent 0, black 44px, black calc(100% - 44px), transparent 100%)',
|
||||||
|
|
@ -602,7 +596,6 @@ export const MemberCard = memo(function MemberCard({
|
||||||
spawnLaunchState,
|
spawnLaunchState,
|
||||||
spawnRuntimeAlive,
|
spawnRuntimeAlive,
|
||||||
isLaunchSettling,
|
isLaunchSettling,
|
||||||
runtimeTelemetryVisible = false,
|
|
||||||
runtimeTelemetryScale,
|
runtimeTelemetryScale,
|
||||||
onOpenTask,
|
onOpenTask,
|
||||||
onOpenReviewTask,
|
onOpenReviewTask,
|
||||||
|
|
@ -696,7 +689,7 @@ export const MemberCard = memo(function MemberCard({
|
||||||
? `Reviewing task: #${deriveTaskDisplayId(reviewTask.id)}`
|
? `Reviewing task: #${deriveTaskDisplayId(reviewTask.id)}`
|
||||||
: undefined;
|
: undefined;
|
||||||
const runtimeTelemetryTitle = buildRuntimeTelemetryTitle(runtimeEntry);
|
const runtimeTelemetryTitle = buildRuntimeTelemetryTitle(runtimeEntry);
|
||||||
const showRuntimeTelemetryTooltip = runtimeTelemetryVisible && Boolean(runtimeTelemetryTitle);
|
const showRuntimeTelemetryTooltip = Boolean(runtimeTelemetryTitle);
|
||||||
const rowTitle = showRuntimeTelemetryTooltip ? undefined : activityTitle;
|
const rowTitle = showRuntimeTelemetryTooltip ? undefined : activityTitle;
|
||||||
const runtimeTelemetryTooltipTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const runtimeTelemetryTooltipTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const [runtimeTelemetryTooltipOpen, setRuntimeTelemetryTooltipOpen] = useState(false);
|
const [runtimeTelemetryTooltipOpen, setRuntimeTelemetryTooltipOpen] = useState(false);
|
||||||
|
|
@ -920,11 +913,7 @@ export const MemberCard = memo(function MemberCard({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isRemoved ? (
|
{!isRemoved ? (
|
||||||
<MemberRuntimeTelemetryStrip
|
<MemberRuntimeTelemetryStrip runtimeEntry={runtimeEntry} scale={runtimeTelemetryScale} />
|
||||||
runtimeEntry={runtimeEntry}
|
|
||||||
visible={runtimeTelemetryVisible}
|
|
||||||
scale={runtimeTelemetryScale}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
<div className="pointer-events-none absolute inset-0 z-10 rounded transition-colors group-hover:bg-white/5" />
|
<div className="pointer-events-none absolute inset-0 z-10 rounded transition-colors group-hover:bg-white/5" />
|
||||||
<div className="relative z-20 flex items-center gap-2.5">
|
<div className="relative z-20 flex items-center gap-2.5">
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@ interface MemberListProps {
|
||||||
isTeamProvisioning?: boolean;
|
isTeamProvisioning?: boolean;
|
||||||
leadActivity?: LeadActivityState;
|
leadActivity?: LeadActivityState;
|
||||||
launchParams?: TeamLaunchParams;
|
launchParams?: TeamLaunchParams;
|
||||||
runtimeTelemetryVisible?: boolean;
|
|
||||||
onRuntimeTelemetryHoverChange?: (visible: boolean) => void;
|
|
||||||
onMemberClick?: (member: ResolvedTeamMember) => void;
|
onMemberClick?: (member: ResolvedTeamMember) => void;
|
||||||
onSendMessage?: (member: ResolvedTeamMember) => void;
|
onSendMessage?: (member: ResolvedTeamMember) => void;
|
||||||
onAssignTask?: (member: ResolvedTeamMember) => void;
|
onAssignTask?: (member: ResolvedTeamMember) => void;
|
||||||
|
|
@ -464,8 +462,6 @@ function areMemberListPropsEqual(
|
||||||
prev.isTeamAlive === next.isTeamAlive &&
|
prev.isTeamAlive === next.isTeamAlive &&
|
||||||
prev.isTeamProvisioning === next.isTeamProvisioning &&
|
prev.isTeamProvisioning === next.isTeamProvisioning &&
|
||||||
prev.leadActivity === next.leadActivity &&
|
prev.leadActivity === next.leadActivity &&
|
||||||
prev.runtimeTelemetryVisible === next.runtimeTelemetryVisible &&
|
|
||||||
prev.onRuntimeTelemetryHoverChange === next.onRuntimeTelemetryHoverChange &&
|
|
||||||
prev.onRestartMember === next.onRestartMember &&
|
prev.onRestartMember === next.onRestartMember &&
|
||||||
prev.onSkipMemberForLaunch === next.onSkipMemberForLaunch &&
|
prev.onSkipMemberForLaunch === next.onSkipMemberForLaunch &&
|
||||||
areLaunchParamsEquivalent(prev.launchParams, next.launchParams)
|
areLaunchParamsEquivalent(prev.launchParams, next.launchParams)
|
||||||
|
|
@ -502,7 +498,6 @@ interface MemberCardRowProps {
|
||||||
isTeamProvisioning?: boolean;
|
isTeamProvisioning?: boolean;
|
||||||
leadActivity?: LeadActivityState;
|
leadActivity?: LeadActivityState;
|
||||||
isLaunchSettling?: boolean;
|
isLaunchSettling?: boolean;
|
||||||
runtimeTelemetryVisible?: boolean;
|
|
||||||
runtimeTelemetryScale?: RuntimeTelemetryScale;
|
runtimeTelemetryScale?: RuntimeTelemetryScale;
|
||||||
onOpenTask?: (taskId: string) => void;
|
onOpenTask?: (taskId: string) => void;
|
||||||
onMemberClick?: (member: ResolvedTeamMember) => void;
|
onMemberClick?: (member: ResolvedTeamMember) => void;
|
||||||
|
|
@ -538,7 +533,6 @@ const MemberCardRow = memo(function MemberCardRow({
|
||||||
isTeamProvisioning,
|
isTeamProvisioning,
|
||||||
leadActivity,
|
leadActivity,
|
||||||
isLaunchSettling,
|
isLaunchSettling,
|
||||||
runtimeTelemetryVisible,
|
|
||||||
runtimeTelemetryScale,
|
runtimeTelemetryScale,
|
||||||
onOpenTask,
|
onOpenTask,
|
||||||
onMemberClick,
|
onMemberClick,
|
||||||
|
|
@ -589,7 +583,6 @@ const MemberCardRow = memo(function MemberCardRow({
|
||||||
spawnLaunchState={spawnLaunchState}
|
spawnLaunchState={spawnLaunchState}
|
||||||
spawnRuntimeAlive={spawnRuntimeAlive}
|
spawnRuntimeAlive={spawnRuntimeAlive}
|
||||||
isLaunchSettling={isLaunchSettling}
|
isLaunchSettling={isLaunchSettling}
|
||||||
runtimeTelemetryVisible={runtimeTelemetryVisible}
|
|
||||||
runtimeTelemetryScale={runtimeTelemetryScale}
|
runtimeTelemetryScale={runtimeTelemetryScale}
|
||||||
onOpenTask={currentTask ? handleOpenTask : undefined}
|
onOpenTask={currentTask ? handleOpenTask : undefined}
|
||||||
onOpenReviewTask={reviewTask ? handleOpenReviewTask : undefined}
|
onOpenReviewTask={reviewTask ? handleOpenReviewTask : undefined}
|
||||||
|
|
@ -714,8 +707,6 @@ export const MemberList = memo(function MemberList({
|
||||||
isTeamProvisioning,
|
isTeamProvisioning,
|
||||||
leadActivity,
|
leadActivity,
|
||||||
launchParams,
|
launchParams,
|
||||||
runtimeTelemetryVisible = false,
|
|
||||||
onRuntimeTelemetryHoverChange,
|
|
||||||
onMemberClick,
|
onMemberClick,
|
||||||
onSendMessage,
|
onSendMessage,
|
||||||
onAssignTask,
|
onAssignTask,
|
||||||
|
|
@ -742,14 +733,6 @@ export const MemberList = memo(function MemberList({
|
||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [handleResize]);
|
}, [handleResize]);
|
||||||
|
|
||||||
const handleRuntimeTelemetryMouseEnter = useCallback(() => {
|
|
||||||
onRuntimeTelemetryHoverChange?.(true);
|
|
||||||
}, [onRuntimeTelemetryHoverChange]);
|
|
||||||
|
|
||||||
const handleRuntimeTelemetryMouseLeave = useCallback(() => {
|
|
||||||
onRuntimeTelemetryHoverChange?.(false);
|
|
||||||
}, [onRuntimeTelemetryHoverChange]);
|
|
||||||
|
|
||||||
const gridClass = isWide ? 'grid grid-cols-2 gap-1' : 'grid grid-cols-1 gap-1';
|
const gridClass = isWide ? 'grid grid-cols-2 gap-1' : 'grid grid-cols-1 gap-1';
|
||||||
const activeMembers = useMemo(
|
const activeMembers = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -941,12 +924,7 @@ export const MemberList = memo(function MemberList({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={containerRef} className="runtime-telemetry-list flex flex-col gap-1">
|
||||||
ref={containerRef}
|
|
||||||
className="flex flex-col gap-1"
|
|
||||||
onMouseEnter={handleRuntimeTelemetryMouseEnter}
|
|
||||||
onMouseLeave={handleRuntimeTelemetryMouseLeave}
|
|
||||||
>
|
|
||||||
<div className={gridClass}>
|
<div className={gridClass}>
|
||||||
{activeMembers.map((member) => {
|
{activeMembers.map((member) => {
|
||||||
const spawnEntry = memberSpawnStatuses?.get(member.name);
|
const spawnEntry = memberSpawnStatuses?.get(member.name);
|
||||||
|
|
@ -1017,7 +995,6 @@ export const MemberList = memo(function MemberList({
|
||||||
isTeamProvisioning={isTeamProvisioning}
|
isTeamProvisioning={isTeamProvisioning}
|
||||||
leadActivity={leadActivity}
|
leadActivity={leadActivity}
|
||||||
isLaunchSettling={isLaunchSettling}
|
isLaunchSettling={isLaunchSettling}
|
||||||
runtimeTelemetryVisible={runtimeTelemetryVisible}
|
|
||||||
runtimeTelemetryScale={runtimeTelemetryScale}
|
runtimeTelemetryScale={runtimeTelemetryScale}
|
||||||
onOpenTask={onOpenTask}
|
onOpenTask={onOpenTask}
|
||||||
onMemberClick={onMemberClick}
|
onMemberClick={onMemberClick}
|
||||||
|
|
@ -1063,7 +1040,6 @@ export const MemberList = memo(function MemberList({
|
||||||
isTeamProvisioning={isTeamProvisioning}
|
isTeamProvisioning={isTeamProvisioning}
|
||||||
leadActivity={leadActivity}
|
leadActivity={leadActivity}
|
||||||
isLaunchSettling={false}
|
isLaunchSettling={false}
|
||||||
runtimeTelemetryVisible={runtimeTelemetryVisible}
|
|
||||||
runtimeTelemetryScale={runtimeTelemetryScale}
|
runtimeTelemetryScale={runtimeTelemetryScale}
|
||||||
onOpenTask={onOpenTask}
|
onOpenTask={onOpenTask}
|
||||||
onMemberClick={onMemberClick}
|
onMemberClick={onMemberClick}
|
||||||
|
|
|
||||||
|
|
@ -1644,6 +1644,13 @@ a[href],
|
||||||
opacity: 0.96;
|
opacity: 0.96;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.runtime-telemetry-list:hover .runtime-telemetry-strip,
|
||||||
|
.runtime-telemetry-list:focus-within .runtime-telemetry-strip,
|
||||||
|
.runtime-telemetry-hover-scope:has(.runtime-telemetry-list:hover) .runtime-telemetry-legend,
|
||||||
|
.runtime-telemetry-hover-scope:has(.runtime-telemetry-list:focus-within) .runtime-telemetry-legend {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.kanban-comment-badge-pulse,
|
.kanban-comment-badge-pulse,
|
||||||
.message-composer-orbit-path,
|
.message-composer-orbit-path,
|
||||||
|
|
|
||||||
|
|
@ -800,7 +800,6 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
],
|
],
|
||||||
updatedAt: '2026-04-24T12:00:05.000Z',
|
updatedAt: '2026-04-24T12:00:05.000Z',
|
||||||
},
|
},
|
||||||
runtimeTelemetryVisible: true,
|
|
||||||
runtimeTelemetryScale: {
|
runtimeTelemetryScale: {
|
||||||
cpuCapPercent: 100,
|
cpuCapPercent: 100,
|
||||||
memoryCapBytes: 512 * 1024 * 1024,
|
memoryCapBytes: 512 * 1024 * 1024,
|
||||||
|
|
@ -861,7 +860,6 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
resourceHistory: 'not-an-array',
|
resourceHistory: 'not-an-array',
|
||||||
updatedAt: '2026-04-24T12:00:05.000Z',
|
updatedAt: '2026-04-24T12:00:05.000Z',
|
||||||
} as any,
|
} as any,
|
||||||
runtimeTelemetryVisible: true,
|
|
||||||
isTeamAlive: true,
|
isTeamAlive: true,
|
||||||
isTeamProvisioning: false,
|
isTeamProvisioning: false,
|
||||||
})
|
})
|
||||||
|
|
@ -911,7 +909,6 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
],
|
],
|
||||||
updatedAt: '2026-04-24T12:00:05.000Z',
|
updatedAt: '2026-04-24T12:00:05.000Z',
|
||||||
} as any,
|
} as any,
|
||||||
runtimeTelemetryVisible: true,
|
|
||||||
isTeamAlive: true,
|
isTeamAlive: true,
|
||||||
isTeamProvisioning: false,
|
isTeamProvisioning: false,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue