diff --git a/.gitignore b/.gitignore index 0aff1fb..42b1079 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Xcode DerivedData/ .DerivedData/ +DerivedData-*/ *.xcresult *.xcuserstate *.xcuserdata/ diff --git a/Neon Vision Editor.xcodeproj/project.pbxproj b/Neon Vision Editor.xcodeproj/project.pbxproj index 168772a..7f4b7d5 100644 --- a/Neon Vision Editor.xcodeproj/project.pbxproj +++ b/Neon Vision Editor.xcodeproj/project.pbxproj @@ -358,7 +358,7 @@ CODE_SIGNING_ALLOWED = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 252; + CURRENT_PROJECT_VERSION = 253; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = CS727NF72U; ENABLE_APP_SANDBOX = YES; @@ -438,7 +438,7 @@ CODE_SIGNING_ALLOWED = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 252; + CURRENT_PROJECT_VERSION = 253; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = CS727NF72U; ENABLE_APP_SANDBOX = YES; diff --git a/Neon Vision Editor/Resources/AppIcon.icon/icon.json b/Neon Vision Editor/Resources/AppIcon.icon/icon.json index 24a39ef..2e3cb88 100644 --- a/Neon Vision Editor/Resources/AppIcon.icon/icon.json +++ b/Neon Vision Editor/Resources/AppIcon.icon/icon.json @@ -22,8 +22,8 @@ "appearance" : "dark", "value" : { "linear-gradient" : [ - "srgb:0.11000,0.11000,0.14000,1.00000", - "srgb:0.02000,0.02000,0.04000,1.00000" + "srgb:0.10980,0.10980,0.11765,1.00000", + "srgb:0.10980,0.10980,0.11765,1.00000" ], "orientation" : { "start" : { diff --git a/Neon Vision Editor/UI/ContentView+Toolbar.swift b/Neon Vision Editor/UI/ContentView+Toolbar.swift index 9dfef93..cc48d5f 100644 --- a/Neon Vision Editor/UI/ContentView+Toolbar.swift +++ b/Neon Vision Editor/UI/ContentView+Toolbar.swift @@ -92,7 +92,7 @@ extension ContentView { } .labelsHidden() .help("Language") - .frame(width: isIPadToolbarLayout ? 160 : 120) + .frame(width: isIPadToolbarLayout ? 160 : 98) } @ViewBuilder @@ -184,6 +184,42 @@ extension ContentView { .keyboardShortcut("f", modifiers: .command) } + @ViewBuilder + private var brainDumpControl: some View { + Button(action: { + viewModel.isBrainDumpMode.toggle() + UserDefaults.standard.set(viewModel.isBrainDumpMode, forKey: "BrainDumpModeEnabled") + }) { + Image(systemName: "note.text") + .symbolVariant(viewModel.isBrainDumpMode ? .fill : .none) + } + .help("Brain Dump Mode") + .accessibilityLabel("Brain Dump Mode") + } + + @ViewBuilder + private var welcomeTourControl: some View { + Button(action: { + showWelcomeTour = true + }) { + Image(systemName: "sparkles.rectangle.stack") + } + .help("Welcome Tour") + } + + @ViewBuilder + private var translucentWindowControl: some View { + Button(action: { + enableTranslucentWindow.toggle() + UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow") + NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow) + }) { + Image(systemName: enableTranslucentWindow ? "rectangle.fill" : "rectangle") + } + .help("Toggle Translucent Window Background") + .accessibilityLabel("Translucent Window Background") + } + @ViewBuilder private var iPadPromotedActions: some View { if iPadPromotedActionsCount >= 1 { openFileControl } @@ -198,6 +234,18 @@ extension ContentView { @ViewBuilder private var moreActionsControl: some View { Menu { + Button(action: { + showSettingsSheet = true + }) { + Label("Settings", systemImage: "gearshape") + } + + Button(action: { + requestClearEditorContent() + }) { + Label("Clear Editor", systemImage: "trash") + } + Button(action: { insertTemplateForCurrentLanguage() }) { Label("Insert Template", systemImage: "doc.badge.plus") } @@ -261,15 +309,9 @@ extension ContentView { @ViewBuilder private var iOSToolbarControls: some View { - languagePickerControl newTabControl openFileControl saveFileControl - findReplaceControl - activeProviderBadgeControl - clearEditorControl - settingsControl - moreActionsControl } @ViewBuilder @@ -288,15 +330,19 @@ extension ContentView { @ToolbarContentBuilder var editorToolbarContent: some ToolbarContent { #if os(iOS) - ToolbarItemGroup(placement: .topBarTrailing) { - HStack(spacing: 14) { - if isIPadToolbarLayout { + if isIPadToolbarLayout { + ToolbarItemGroup(placement: .topBarTrailing) { + HStack(spacing: 14) { iPadDistributedToolbarControls - } else { - iOSToolbarControls } + .frame(maxWidth: iPadToolbarMaxWidth, alignment: .trailing) + } + } else { + ToolbarItemGroup(placement: .topBarTrailing) { + languagePickerControl + iOSToolbarControls + moreActionsControl } - .frame(maxWidth: isIPadToolbarLayout ? iPadToolbarMaxWidth : .infinity, alignment: .trailing) } #else ToolbarItemGroup(placement: .automatic) { diff --git a/Neon Vision Editor/UI/ThemeSettings.swift b/Neon Vision Editor/UI/ThemeSettings.swift index 019e8f4..dacb0d8 100644 --- a/Neon Vision Editor/UI/ThemeSettings.swift +++ b/Neon Vision Editor/UI/ThemeSettings.swift @@ -564,11 +564,11 @@ func currentEditorTheme(colorScheme: ColorScheme) -> EditorTheme { comment: comment, attribute: property, variable: property, - def: keyword, + def: type, property: property, meta: builtin, tag: keyword, - atom: number, + atom: builtin, builtin: builtin, type: type ) diff --git a/scripts/release_all.sh b/scripts/release_all.sh index 64900b9..cad842b 100755 --- a/scripts/release_all.sh +++ b/scripts/release_all.sh @@ -6,7 +6,7 @@ usage() { Run end-to-end release flow in one command. Usage: - scripts/release_all.sh [notarized] [--date YYYY-MM-DD] [--skip-notarized] [--self-hosted] [--github-hosted] [--enterprise-selfhosted] [--dry-run] + scripts/release_all.sh [notarized] [--date YYYY-MM-DD] [--skip-notarized] [--self-hosted] [--github-hosted] [--enterprise-selfhosted] [--autostash] [--dry-run] Examples: scripts/release_all.sh v0.4.9 @@ -15,6 +15,7 @@ Examples: scripts/release_all.sh v0.4.9 --self-hosted scripts/release_all.sh v0.4.9 --enterprise-selfhosted scripts/release_all.sh v0.4.9 --github-hosted + scripts/release_all.sh v0.4.9 --autostash scripts/release_all.sh v0.4.9 --dry-run What it does: @@ -46,6 +47,7 @@ DATE_ARG=() TRIGGER_NOTARIZED=1 USE_SELF_HOSTED=0 ENTERPRISE_SELF_HOSTED=0 +AUTOSTASH=0 DRY_RUN=0 while [[ "${1:-}" != "" ]]; do @@ -74,6 +76,9 @@ while [[ "${1:-}" != "" ]]; do ENTERPRISE_SELF_HOSTED=1 USE_SELF_HOSTED=1 ;; + --autostash) + AUTOSTASH=1 + ;; --dry-run) DRY_RUN=1 ;; @@ -91,6 +96,35 @@ if ! command -v gh >/dev/null 2>&1; then exit 1 fi +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "This command must run inside a git repository." >&2 + exit 1 +fi + +ROOT="$(git rev-parse --show-toplevel)" +cd "$ROOT" + +AUTO_STASHED=0 +cleanup_autostash() { + if [[ "$AUTO_STASHED" -eq 1 ]]; then + if git stash pop --index >/dev/null 2>&1; then + echo "Restored stashed working tree changes." + else + echo "Auto-stash restore had conflicts. Changes remain in stash list; resolve manually." >&2 + fi + fi +} +trap cleanup_autostash EXIT + +if [[ "$AUTOSTASH" -eq 1 ]]; then + if [[ -n "$(git status --porcelain)" ]]; then + STASH_MSG="release_all autostash ${TAG} $(date +%Y-%m-%dT%H:%M:%S)" + git stash push --include-untracked -m "$STASH_MSG" >/dev/null + AUTO_STASHED=1 + echo "Auto-stashed dirty working tree before release." + fi +fi + wait_for_pre_release_ci() { local sha="$1" local timeout_seconds=1800 @@ -133,6 +167,11 @@ wait_for_pre_release_ci() { return 1 } +if [[ "$AUTOSTASH" -eq 0 && -n "$(git status --porcelain)" ]]; then + echo "Working tree is not clean. Commit/stash changes first, or rerun with --autostash." >&2 + exit 1 +fi + echo "Verifying release docs are up to date for ${TAG}..." docs_check_cmd=(scripts/prepare_release_docs.py "$TAG" --check) if [[ ${#DATE_ARG[@]} -gt 0 ]]; then diff --git a/scripts/release_prep.sh b/scripts/release_prep.sh index 9a3623e..b101ad5 100755 --- a/scripts/release_prep.sh +++ b/scripts/release_prep.sh @@ -80,22 +80,23 @@ if [[ ! -f "$PBXPROJ_FILE" ]]; then echo "Missing ${PBXPROJ_FILE}; cannot validate MARKETING_VERSION." >&2 exit 1 fi -CURRENT_VERSION="$( +MARKETING_VERSIONS="$( if command -v rg >/dev/null 2>&1; then rg --no-filename --only-matching 'MARKETING_VERSION = [0-9]+\.[0-9]+\.[0-9]+' "$PBXPROJ_FILE" else grep -Eo 'MARKETING_VERSION = [0-9]+\.[0-9]+\.[0-9]+' "$PBXPROJ_FILE" fi \ | awk '{print $3}' \ - | sort -u \ - | head -n1 + | sort -u )" -if [[ -z "${CURRENT_VERSION}" ]]; then +if [[ -z "${MARKETING_VERSIONS}" ]]; then echo "Could not read MARKETING_VERSION from ${PBXPROJ_FILE}." >&2 exit 1 fi -if [[ "$CURRENT_VERSION" != "$EXPECTED_VERSION" ]]; then - echo "Version mismatch: tag ${TAG} requires MARKETING_VERSION ${EXPECTED_VERSION}, found ${CURRENT_VERSION}." >&2 +if ! printf '%s\n' "$MARKETING_VERSIONS" | grep -Fxq "$EXPECTED_VERSION"; then + echo "Version mismatch: tag ${TAG} requires MARKETING_VERSION ${EXPECTED_VERSION}." >&2 + echo "Found MARKETING_VERSION values:" >&2 + printf ' - %s\n' $MARKETING_VERSIONS >&2 echo "Update MARKETING_VERSION or use a matching tag before running release." >&2 exit 1 fi