From 4825b54876ffb07e0ce1d6e0ba33823cd14fd964 Mon Sep 17 00:00:00 2001 From: Simo Date: Mon, 19 Jan 2026 08:56:04 +0100 Subject: [PATCH 1/4] wip Signed-off-by: Simo --- config/nextConfig.ts | 21 --------- package-lock.json | 85 +++++++++++++++++++------------------ package.json | 6 +-- scripts/worktree/sync.conf | 7 +-- scripts/worktree/wt-sync.sh | 82 +++++++++++++++++++++++++++++++++-- 5 files changed, 128 insertions(+), 73 deletions(-) diff --git a/config/nextConfig.ts b/config/nextConfig.ts index 09841efe30..47fb0f1f6e 100644 --- a/config/nextConfig.ts +++ b/config/nextConfig.ts @@ -1,7 +1,6 @@ import { createSecurityHeaders } from "./securityHeaders"; import { PublicEnv } from "./env.schema"; import { NextConfig } from "next"; -import path from "node:path"; export function sharedConfig( publicEnv: PublicEnv, @@ -14,10 +13,6 @@ export function sharedConfig( compress: true, productionBrowserSourceMaps: true, sassOptions: { quietDeps: true }, - experimental: { - webpackMemoryOptimizations: true, - webpackBuildWorker: true, - }, images: { loader: "default", remotePatterns: [ @@ -48,22 +43,6 @@ export function sharedConfig( }, ]; }, - webpack: ( - config: any, - { dev, isServer }: { dev: boolean; isServer: boolean } - ) => { - config.resolve.alias.canvas = false; - config.resolve.alias.encoding = false; - config.resolve.alias["@react-native-async-storage/async-storage"] = false; - config.resolve.alias["react-native"] = false; - config.resolve.alias["idb-keyval"] = path.resolve( - process.cwd(), - "lib/storage/idb-keyval.ts" - ); - if (!dev && !isServer) config.devtool = "source-map"; - config.optimization.minimize = false; - return config; - }, turbopack: { resolveAlias: { canvas: "./stubs/empty.js", diff --git a/package-lock.json b/package-lock.json index 040bcb4ef2..9e33094288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,17 +76,17 @@ "jwt-decode": "^4.0.0", "lexical": "^0.14.5", "lodash": "^4.17.21", - "next": "16.0.10", + "next": "16.1", "next-redux-wrapper": "^8.1.0", "next-sitemap": "^4.2.3", "open": "^10.2.0", "p-limit": "^3.1.0", "p-retry": "^6.2.1", "qrcode": "^1.5.4", - "react": "19.2.3", + "react": "^19.2.3", "react-bootstrap": "^2.10.3", "react-chartjs-2": "^5.2.0", - "react-dom": "19.2.3", + "react-dom": "^19.2.3", "react-error-boundary": "^6.0.0", "react-markdown": "^9.0.1", "react-redux": "^9.1.2", @@ -4930,9 +4930,9 @@ } }, "node_modules/@next/env": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz", - "integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.3.tgz", + "integrity": "sha512-BLP14oBOvZWXgfdJf9ao+VD8O30uE+x7PaV++QtACLX329WcRSJRO5YJ+Bcvu0Q+c/lei41TjSiFf6pXqnpbQA==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -4946,9 +4946,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.10.tgz", - "integrity": "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.3.tgz", + "integrity": "sha512-CpOD3lmig6VflihVoGxiR/l5Jkjfi4uLaOR4ziriMv0YMDoF6cclI+p5t2nstM8TmaFiY6PCTBgRWB57/+LiBA==", "cpu": [ "arm64" ], @@ -4962,9 +4962,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.10.tgz", - "integrity": "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.3.tgz", + "integrity": "sha512-aF4us2JXh0zn3hNxvL1Bx3BOuh8Lcw3p3Xnurlvca/iptrDH1BrpObwkw9WZra7L7/0qB9kjlREq3hN/4x4x+Q==", "cpu": [ "x64" ], @@ -4978,9 +4978,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.10.tgz", - "integrity": "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.3.tgz", + "integrity": "sha512-8VRkcpcfBtYvhGgXAF7U3MBx6+G1lACM1XCo1JyaUr4KmAkTNP8Dv2wdMq7BI+jqRBw3zQE7c57+lmp7jCFfKA==", "cpu": [ "arm64" ], @@ -4994,9 +4994,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.10.tgz", - "integrity": "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.3.tgz", + "integrity": "sha512-UbFx69E2UP7MhzogJRMFvV9KdEn4sLGPicClwgqnLht2TEi204B71HuVfps3ymGAh0c44QRAF+ZmvZZhLLmhNg==", "cpu": [ "arm64" ], @@ -5010,9 +5010,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.10.tgz", - "integrity": "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.3.tgz", + "integrity": "sha512-SzGTfTjR5e9T+sZh5zXqG/oeRQufExxBF6MssXS7HPeZFE98JDhCRZXpSyCfWrWrYrzmnw/RVhlP2AxQm+wkRQ==", "cpu": [ "x64" ], @@ -5026,9 +5026,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.10.tgz", - "integrity": "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.3.tgz", + "integrity": "sha512-HlrDpj0v+JBIvQex1mXHq93Mht5qQmfyci+ZNwGClnAQldSfxI6h0Vupte1dSR4ueNv4q7qp5kTnmLOBIQnGow==", "cpu": [ "x64" ], @@ -5042,9 +5042,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.10.tgz", - "integrity": "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.3.tgz", + "integrity": "sha512-3gFCp83/LSduZMSIa+lBREP7+5e7FxpdBoc9QrCdmp+dapmTK9I+SLpY60Z39GDmTXSZA4huGg9WwmYbr6+WRw==", "cpu": [ "arm64" ], @@ -5058,9 +5058,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz", - "integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.3.tgz", + "integrity": "sha512-1SZVfFT8zmMB+Oblrh5OKDvUo5mYQOkX2We6VGzpg7JUVZlqe4DYOFGKYZKTweSx1gbMixyO1jnFT4thU+nNHQ==", "cpu": [ "x64" ], @@ -24507,14 +24507,15 @@ } }, "node_modules/next": { - "version": "16.0.10", - "resolved": "https://registry.npmjs.org/next/-/next-16.0.10.tgz", - "integrity": "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==", + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.3.tgz", + "integrity": "sha512-gthG3TRD+E3/mA0uDQb9lqBmx1zVosq5kIwxNN6+MRNd085GzD+9VXMPUs+GGZCbZ+GDZdODUq4Pm7CTXK6ipw==", "license": "MIT", "peer": true, "dependencies": { - "@next/env": "16.0.10", + "@next/env": "16.1.3", "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -24526,14 +24527,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.0.10", - "@next/swc-darwin-x64": "16.0.10", - "@next/swc-linux-arm64-gnu": "16.0.10", - "@next/swc-linux-arm64-musl": "16.0.10", - "@next/swc-linux-x64-gnu": "16.0.10", - "@next/swc-linux-x64-musl": "16.0.10", - "@next/swc-win32-arm64-msvc": "16.0.10", - "@next/swc-win32-x64-msvc": "16.0.10", + "@next/swc-darwin-arm64": "16.1.3", + "@next/swc-darwin-x64": "16.1.3", + "@next/swc-linux-arm64-gnu": "16.1.3", + "@next/swc-linux-arm64-musl": "16.1.3", + "@next/swc-linux-x64-gnu": "16.1.3", + "@next/swc-linux-x64-musl": "16.1.3", + "@next/swc-win32-arm64-msvc": "16.1.3", + "@next/swc-win32-x64-msvc": "16.1.3", "sharp": "^0.34.4" }, "peerDependencies": { diff --git a/package.json b/package.json index 916e55b7eb..ac044a8e1d 100644 --- a/package.json +++ b/package.json @@ -109,17 +109,17 @@ "jwt-decode": "^4.0.0", "lexical": "^0.14.5", "lodash": "^4.17.21", - "next": "16.0.10", + "next": "16.1", "next-redux-wrapper": "^8.1.0", "next-sitemap": "^4.2.3", "open": "^10.2.0", "p-limit": "^3.1.0", "p-retry": "^6.2.1", "qrcode": "^1.5.4", - "react": "19.2.3", + "react": "^19.2.3", "react-bootstrap": "^2.10.3", "react-chartjs-2": "^5.2.0", - "react-dom": "19.2.3", + "react-dom": "^19.2.3", "react-error-boundary": "^6.0.0", "react-markdown": "^9.0.1", "react-redux": "^9.1.2", diff --git a/scripts/worktree/sync.conf b/scripts/worktree/sync.conf index d479abee15..e364927c0a 100644 --- a/scripts/worktree/sync.conf +++ b/scripts/worktree/sync.conf @@ -12,8 +12,9 @@ # notes.txt .coderabbit.yml -.claude +.claude/ CLAUDE.md -.codex +.codex/ AGENTS.md -.gemini +.gemini/ +.agent/ diff --git a/scripts/worktree/wt-sync.sh b/scripts/worktree/wt-sync.sh index 8d1f8304ec..8373d9b747 100755 --- a/scripts/worktree/wt-sync.sh +++ b/scripts/worktree/wt-sync.sh @@ -21,6 +21,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MAIN_REPO="$(cd "$SCRIPT_DIR/../.." && pwd)" MAIN_REPO_NAME="$(basename "$MAIN_REPO")" +MAIN_REPO_REALPATH="$(cd "$MAIN_REPO" && pwd -P)" SYNC_CONF="$SCRIPT_DIR/sync.conf" COPY_CONF="$SCRIPT_DIR/copy.conf" @@ -80,7 +81,9 @@ parse_args() { # --- Get list of worktrees (excluding main) --- get_worktrees() { git -C "$MAIN_REPO" worktree list --porcelain | grep "^worktree " | cut -d' ' -f2- | while read -r wt_path; do - if [[ "$wt_path" != "$MAIN_REPO" ]]; then + local wt_real + wt_real="$(cd "$wt_path" && pwd -P 2>/dev/null || echo "$wt_path")" + if [[ "$wt_real" != "$MAIN_REPO_REALPATH" ]]; then echo "$wt_path" fi done @@ -103,13 +106,50 @@ parse_config() { done < "$config_file" } +# --- Normalize relative path (drop trailing slash) --- +normalize_rel_path() { + local rel_path="$1" + # Remove trailing slash to avoid creating nested directories like ".dir/.dir" + rel_path="${rel_path%/}" + echo "$rel_path" +} + +# --- Ensure source directories exist (and are not self-links) --- +ensure_source_dirs() { + [[ ! -f "$SYNC_CONF" ]] && return + + while IFS= read -r rel_path; do + [[ -z "$rel_path" ]] && continue + # Only operate on entries that explicitly declare a directory (trailing slash) + [[ "$rel_path" != */ ]] && continue + + local rel_norm + rel_norm="$(normalize_rel_path "$rel_path")" + local source_path="$MAIN_REPO/$rel_norm" + + # If it's a symlink, drop it so we can create a real directory + if [[ -L "$source_path" ]]; then + log_warn "Replacing self-link with directory: $rel_norm/" + rm -f "$source_path" + fi + + # Create directory if missing + if [[ ! -d "$source_path" ]]; then + mkdir -p "$source_path" + log_info "Ensured source dir: $rel_norm/" + fi + done < <(parse_config "$SYNC_CONF") +} + # --- Create symlink --- create_symlink() { local rel_path="$1" local worktree_path="$2" - local source="$MAIN_REPO/$rel_path" - local target="$worktree_path/$rel_path" + local rel_norm + rel_norm="$(normalize_rel_path "$rel_path")" + local source="$MAIN_REPO/$rel_norm" + local target="$worktree_path/$rel_norm" # Check if source exists if [[ ! -e "$source" ]]; then @@ -119,7 +159,29 @@ create_symlink() { # Calculate relative path from target's parent to source local target_dir=$(dirname "$target") - local relative_source="../$MAIN_REPO_NAME/$rel_path" + + # Calculate relative path from target directory to source so nested worktrees work + local relative_source + if command -v python3 >/dev/null 2>&1; then + relative_source=$(python3 - "$source" "$target_dir" <<'PY' +import os, sys +source = os.path.abspath(sys.argv[1]) +target_dir = os.path.abspath(sys.argv[2]) +print(os.path.relpath(source, target_dir)) +PY +) + elif command -v python >/dev/null 2>&1; then + relative_source=$(python - "$source" "$target_dir" <<'PY' +import os, sys +source = os.path.abspath(sys.argv[1]) +target_dir = os.path.abspath(sys.argv[2]) +print(os.path.relpath(source, target_dir)) +PY +) + else + log_error "python or python3 required to compute relative paths" + exit 1 + fi # Already a correct symlink? if [[ -L "$target" ]]; then @@ -210,6 +272,12 @@ sync_worktree() { local worktree_path="$1" local worktree_name=$(basename "$worktree_path") + # Avoid syncing the main repo onto itself; that would create self-referential links + if [[ "$worktree_path" == "$MAIN_REPO" ]]; then + log_verbose " Skipping main repo" + return + fi + echo "" log_info "Syncing: $worktree_name" @@ -253,6 +321,9 @@ main() { log_info "Main repo: $MAIN_REPO" [[ $DRY_RUN -eq 1 ]] && log_warn "DRY RUN MODE" + # Make sure directory sources listed in sync.conf actually exist in the main repo + ensure_source_dirs + if [[ -n "$TARGET_WORKTREE" ]]; then # Sync specific worktree local wt_path @@ -287,6 +358,9 @@ main() { done fi + # Re-ensure source directories in main haven't been turned into self-links + ensure_source_dirs + echo "" log_success "Done!" } From 8bb1335ee8c6773b7cebd35e3eaf35bf9e58d9dc Mon Sep 17 00:00:00 2001 From: Simo Date: Mon, 19 Jan 2026 09:34:32 +0100 Subject: [PATCH 2/4] wip Signed-off-by: Simo --- .../pfp/UserPageHeaderEditPfp.tsx | 3 +- services/api/upload-error.ts | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 services/api/upload-error.ts diff --git a/components/user/user-page-header/pfp/UserPageHeaderEditPfp.tsx b/components/user/user-page-header/pfp/UserPageHeaderEditPfp.tsx index a012dc9639..6284e01c17 100644 --- a/components/user/user-page-header/pfp/UserPageHeaderEditPfp.tsx +++ b/components/user/user-page-header/pfp/UserPageHeaderEditPfp.tsx @@ -21,6 +21,7 @@ import { commonApiPost, commonApiPostForm, } from "@/services/api/common-api"; +import { getUploadErrorMessage } from "@/services/api/upload-error"; import { useMutation, useQuery } from "@tanstack/react-query"; import type { FormEvent} from "react"; import { useContext, useEffect, useRef, useState } from "react"; @@ -145,7 +146,7 @@ export default function UserPageHeaderEditPfp({ }, onError: (error: unknown) => { setToast({ - message: error as string, + message: getUploadErrorMessage(error), type: "error", }); }, diff --git a/services/api/upload-error.ts b/services/api/upload-error.ts new file mode 100644 index 0000000000..6f33318025 --- /dev/null +++ b/services/api/upload-error.ts @@ -0,0 +1,52 @@ +import type { AxiosError } from "axios"; + +const NETWORK_HINT = + "Can't reach image upload server. Please try again."; +const BLOCKED_HINT = + "Upload was blocked by the server (403). Please try again later."; +const TOO_LARGE_HINT = "Image is too large; max 2MB."; +const FALLBACK_HINT = "Upload failed. Please try again."; + +function isAxiosError(error: unknown): error is AxiosError { + return Boolean((error as AxiosError | undefined)?.isAxiosError); +} + +function messageFromUnknown(error: unknown): string | null { + if (typeof error === "string") return error; + if (error instanceof Error) return error.message; + return null; +} + +export function getUploadErrorMessage(error: unknown): string { + if (isAxiosError(error)) { + const status = error.response?.status; + + if (!error.response) { + return NETWORK_HINT; + } + + if (status === 403) return BLOCKED_HINT; + if (status === 413) return TOO_LARGE_HINT; + + const serverMsg = + (typeof error.response?.data === "object" && + (error.response?.data as any)?.error) || + messageFromUnknown(error); + + return serverMsg ?? FALLBACK_HINT; + } + + const msg = messageFromUnknown(error); + const normalized = msg?.toLowerCase() ?? ""; + + if ( + normalized.includes("network request failed") || + normalized.includes("failed to fetch") || + normalized.includes("load failed") || + normalized.includes("network error") + ) { + return NETWORK_HINT; + } + + return msg ?? FALLBACK_HINT; +} From 6f838e91ef83e70d6329550923ff1911e1205e1a Mon Sep 17 00:00:00 2001 From: Simo Date: Mon, 19 Jan 2026 09:49:57 +0100 Subject: [PATCH 3/4] wip Signed-off-by: Simo --- scripts/worktree/wt-add.sh | 109 ++++-------------------------- scripts/worktree/wt-common.sh | 120 ++++++++++++++++++++++++++++++++++ scripts/worktree/wt-sync.sh | 25 +++++++ 3 files changed, 157 insertions(+), 97 deletions(-) create mode 100644 scripts/worktree/wt-common.sh diff --git a/scripts/worktree/wt-add.sh b/scripts/worktree/wt-add.sh index 1cce340a57..ce571a7184 100755 --- a/scripts/worktree/wt-add.sh +++ b/scripts/worktree/wt-add.sh @@ -4,6 +4,14 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MAIN_REPO="$(cd "$SCRIPT_DIR/../.." && pwd)" PARENT_DIR="$(dirname "$MAIN_REPO")" +COMMON_SH="$SCRIPT_DIR/wt-common.sh" + +if [[ ! -f "$COMMON_SH" ]]; then + echo "Missing helper script: $COMMON_SH" + exit 1 +fi +# shellcheck source=./wt-common.sh +source "$COMMON_SH" # Usage: ./scripts/worktree/wt-add.sh [base-ref] [branch-name] if [ $# -lt 1 ]; then @@ -30,106 +38,13 @@ echo "✓ Branch '$TARGET_BRANCH' ready in worktree '$WORKTREE_NAME'." echo "Syncing files..." "$SCRIPT_DIR/wt-sync.sh" "$WORKTREE_NAME" -# 3. Set VS Code color randomly -VSCODE_PATH="$WORKTREE_PATH/.vscode" -mkdir -p "$VSCODE_PATH" - -COLORS=("red" "orange" "yellow" "green" "blue" "purple" "pink" "teal") -RAND_COLOR=${COLORS[$RANDOM % ${#COLORS[@]}]} - -cat > "$VSCODE_PATH/settings.json" < "$WORKTREE_PATH/.hooks/pre-commit" <<'HOOK' -#!/bin/sh -if [ "$SKIP_LINT" = "1" ]; then - echo "Skipping lint (SKIP_LINT=1)" - exit 0 -fi -npm run format:uncommitted -git add -u -npm run lint:uncommitted:tight -HOOK -chmod +x "$WORKTREE_PATH/.hooks/pre-commit" -git -C "$WORKTREE_PATH" config extensions.worktreeConfig true -git -C "$WORKTREE_PATH" config --worktree core.hooksPath .hooks +setup_precommit_hook "$WORKTREE_PATH" echo "Pre-commit hook configured." # 5. Run npm install diff --git a/scripts/worktree/wt-common.sh b/scripts/worktree/wt-common.sh new file mode 100644 index 0000000000..6bae8a0944 --- /dev/null +++ b/scripts/worktree/wt-common.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# Shared helpers for worktree management (VS Code + git hooks) +# Sourced by wt-add.sh and wt-sync.sh so behavior stays consistent. + +setup_vscode_settings() { + local worktree_path="$1" + local vscode_path="$worktree_path/.vscode" + local settings_file="$vscode_path/settings.json" + + # Keep existing color if present to avoid changing themes on sync runs. + local existing_color="" + if [[ -f "$settings_file" ]]; then + existing_color=$(grep -m1 '"titleBar.activeBackground"' "$settings_file" 2>/dev/null | sed -E 's/.*"titleBar.activeBackground"\s*:\s*"([^"]+)".*/\1/') || true + fi + + local colors=("red" "orange" "yellow" "green" "blue" "purple" "pink" "teal") + local color=${existing_color:-${colors[$RANDOM % ${#colors[@]}]}} + + mkdir -p "$vscode_path" + cat > "$settings_file" < "$hooks_path/pre-commit" <<'HOOK' +#!/bin/sh +if [ "$SKIP_LINT" = "1" ]; then + echo "Skipping lint (SKIP_LINT=1)" + exit 0 +fi +npm run format:uncommitted +git add -u +npm run lint:uncommitted:tight +HOOK + chmod +x "$hooks_path/pre-commit" + + git -C "$worktree_path" config extensions.worktreeConfig true + git -C "$worktree_path" config --worktree core.hooksPath .hooks +} + diff --git a/scripts/worktree/wt-sync.sh b/scripts/worktree/wt-sync.sh index 8373d9b747..92f689da71 100755 --- a/scripts/worktree/wt-sync.sh +++ b/scripts/worktree/wt-sync.sh @@ -24,6 +24,7 @@ MAIN_REPO_NAME="$(basename "$MAIN_REPO")" MAIN_REPO_REALPATH="$(cd "$MAIN_REPO" && pwd -P)" SYNC_CONF="$SCRIPT_DIR/sync.conf" COPY_CONF="$SCRIPT_DIR/copy.conf" +COMMON_SH="$SCRIPT_DIR/wt-common.sh" # --- Flags --- DRY_RUN=0 @@ -43,6 +44,13 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_verbose() { [[ $VERBOSE -eq 1 ]] && echo -e " $1" || true; } +if [[ ! -f "$COMMON_SH" ]]; then + log_error "Missing helper script: $COMMON_SH" + exit 1 +fi +# shellcheck source=./wt-common.sh +source "$COMMON_SH" + show_help() { cat << EOF Usage: $(basename "$0") [options] [worktree-name] @@ -312,6 +320,23 @@ sync_worktree() { else log_verbose " No copy.conf found" fi + + # VS Code settings + if [[ $DRY_RUN -eq 1 ]]; then + log_info "[DRY RUN] Would ensure VS Code settings" + else + local color + color=$(setup_vscode_settings "$worktree_path") + log_success "VS Code settings ensured (title bar color: $color)" + fi + + # Git hooks + if [[ $DRY_RUN -eq 1 ]]; then + log_info "[DRY RUN] Would install pre-commit hook" + else + setup_precommit_hook "$worktree_path" + log_success "Pre-commit hook installed" + fi } # --- Main --- From fd372394f045e7beca00d3431988688f81e9b2dc Mon Sep 17 00:00:00 2001 From: Simo Date: Mon, 19 Jan 2026 09:54:23 +0100 Subject: [PATCH 4/4] Handle non-string upload error payloads --- services/api/upload-error.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/services/api/upload-error.ts b/services/api/upload-error.ts index 6f33318025..b6a2afd820 100644 --- a/services/api/upload-error.ts +++ b/services/api/upload-error.ts @@ -28,10 +28,21 @@ export function getUploadErrorMessage(error: unknown): string { if (status === 403) return BLOCKED_HINT; if (status === 413) return TOO_LARGE_HINT; - const serverMsg = - (typeof error.response?.data === "object" && - (error.response?.data as any)?.error) || - messageFromUnknown(error); + let serverMsg: string | null = null; + const dataError = (error.response?.data as any)?.error; + if (typeof dataError === "string") { + serverMsg = dataError; + } else if (dataError && typeof dataError === "object") { + try { + serverMsg = JSON.stringify(dataError); + } catch { + // ignore JSON stringify failures + } + } + + if (!serverMsg) { + serverMsg = messageFromUnknown(error); + } return serverMsg ?? FALLBACK_HINT; }