diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 8ddc9daa..17e8d705 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -72,6 +72,78 @@ jobs: - name: Build macOS binaries (Electron) run: npm run build:mac --workspace @neuralnomads/codenomad-electron-app + - name: Repackage Electron macOS zips (ditto) + shell: bash + run: | + set -euo pipefail + + # Prefer the workflow-provided version; fall back to package.json. + VERSION_TO_USE="${VERSION:-}" + if [ -z "$VERSION_TO_USE" ]; then + VERSION_TO_USE=$(node -p "require('./packages/electron-app/package.json').version") + fi + + release_root="packages/electron-app/release" + shopt -s nullglob globstar + + apps=("$release_root"/**/CodeNomad.app) + if [ "${#apps[@]}" -eq 0 ]; then + echo "No CodeNomad.app found under $release_root" >&2 + exit 1 + fi + + for app in "${apps[@]}"; do + bundle_dir=$(basename "$(dirname "$app")") + arch="x64" + if [[ "$bundle_dir" == *"arm64"* ]]; then + arch="arm64" + fi + + out_zip="$release_root/CodeNomad-${VERSION_TO_USE}-mac-${arch}.zip" + rm -f "$out_zip" + echo "ditto -ck: $app -> $out_zip" + ditto -ck --sequesterRsrc --keepParent "$app" "$out_zip" + done + + - name: Validate Electron macOS codesign (unzipped) + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + + tmp_dir=$(mktemp -d) + trap 'rm -rf "$tmp_dir"' EXIT + + zips=(packages/electron-app/release/CodeNomad-*-mac-*.zip) + if [ "${#zips[@]}" -eq 0 ]; then + echo "No Electron macOS zip artifacts found to validate" >&2 + exit 1 + fi + + for zip in "${zips[@]}"; do + echo "Validating codesign for: $zip" + extract_dir="$tmp_dir/$(basename "$zip" .zip)" + mkdir -p "$extract_dir" + + # Use ditto for extraction as well to preserve bundle metadata. + ditto -x -k "$zip" "$extract_dir" + + app_path="" + for candidate in "$extract_dir"/*.app "$extract_dir"/*/*.app; do + if [ -d "$candidate" ]; then + app_path="$candidate" + break + fi + done + + if [ -z "$app_path" ]; then + echo "No .app found after extracting $zip" >&2 + exit 1 + fi + + codesign --verify --deep --strict --verbose=2 "$app_path" + done + - name: Upload release assets if: ${{ inputs.upload && inputs.tag != '' }} run: | diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index c6694f80..ace89d5f 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -61,6 +61,11 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { // Keep enough gutter space so unified diffs don't overlap `+`/`-` markers. lineNumbersMinChars: 4, lineDecorationsWidth: 12, + // Use legacy diff algorithm for better performance with large files + // See: https://github.com/microsoft/vscode/issues/184037 + diffAlgorithm: "legacy", + // Limit computation time to avoid freezing on large files + maxComputationTime: 10000, }) setReady(true) diff --git a/packages/ui/src/components/message-timeline.tsx b/packages/ui/src/components/message-timeline.tsx index c0e1d9d7..fc4ef7f3 100644 --- a/packages/ui/src/components/message-timeline.tsx +++ b/packages/ui/src/components/message-timeline.tsx @@ -1,4 +1,4 @@ -import { For, Show, createEffect, createMemo, createSignal, onCleanup, type Component } from "solid-js" +import { For, Show, createEffect, createMemo, createSignal, onCleanup, on, untrack, type Component } from "solid-js" import MessagePreview from "./message-preview" import { messageStoreBus } from "../stores/message-v2/bus" import type { ClientPart } from "../types/message" @@ -350,11 +350,9 @@ const MessageTimeline: Component = (props) => { clearCloseTimer() }) - createEffect(() => { - const activeId = props.activeMessageId - + createEffect(on(() => props.activeMessageId, (activeId) => { if (!activeId) return - const targetSegment = props.segments.find((segment) => segment.messageId === activeId) + const targetSegment = untrack(() => props.segments).find((segment) => segment.messageId === activeId) if (!targetSegment) return const element = buttonRefs.get(targetSegment.id) if (!element) return @@ -366,7 +364,7 @@ const MessageTimeline: Component = (props) => { window.clearTimeout(timer) } }) - }) + })) createEffect(() => { const element = tooltipElement()