Flutter iOS Safari Double Selection Issue - Technical Workaround

Problem

Flutter web apps on iOS Safari exhibit a "double selection" bug where text selection creates two overlapping selection layers, causing visual artifacts and interaction issues.

Root Cause

iOS Safari creates both native browser text selection AND Flutter's custom SelectionArea selection simultaneously, resulting in conflicting selection states.

Working Workaround

HTML Solution (web/index.html)

<head>
<style>
  * {
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	/* Disable caret to prevent selection artifacts */
	caret-color: rgba(255, 255, 255, 0) !important;
  }
</style>
</head>
<body oncontextmenu="event.preventDefault();" >
...

Flutter Integration

// In main.dart or app initialization
import 'package:flutter/gestures.dart';

void main() {
    // Disable browser context menu to let Flutter handle selection
    if (kIsWeb) {
        BrowserContextMenu.disableContextMenu();
    }
    runApp(MyApp());
}

How It Works

  1. Disables native selection - user-select: none prevents Safari's text selection
  2. Blocks touch events - Prevents iOS touch selection gestures
  3. Maintains Flutter selection - BrowserContextMenu.disableContextMenu() allows Flutter's SelectionArea to work
  4. Hides caret artifacts - caret-color: transparent eliminates visual glitches

Trade-offs

  • ❌ Breaks native web text selection outside Flutter widgets
  • ❌ May affect accessibility tools
  • ✅ Provides consistent cross-platform selection behavior
  • ✅ Eliminates iOS-specific double selection bug

Status

This workaround is recommended for Flutter web apps requiring text selection on iOS Safari until the official framework fix is released.

Git Aliases for Clean Commits

Today I'd love to introduce three useful Git aliases to maintain clean code and intelligent staging.

Quick Setup

# 1. Remove trailing whitespace from modified files
git config --global alias.cleanup '!git -c color.status=false status -s | grep -v "^D\|^.D" | cut -c4- | xargs -r -I {} sed -i "s/[[:space:]]*$//" {}'

# 2. Stage only substantive changes (ignore whitespace)  
git config --global alias.stagewhitespace '!git reset . && git add -N . && git diff -w -b | git apply --cached'

# 3. Combine both operations
git config --global alias.smartadd '!git cleanup && git stagewhitespace'

Usage

git cleanup         # Clean trailing spaces only
git stagewhitespace # Stage content changes only
git smartadd        # Clean + smart staging

What Each Alias Does

cleanup

  • Removes trailing whitespace from all modified files
  • Skips deleted files to avoid errors
  • Handles ANSI color codes in git status output

stagewhitespace

  • Resets staging area
  • Marks new files as intent-to-add
  • Stages only meaningful content changes
  • Ignores pure whitespace modifications

smartadd

  • Runs cleanup first, then stagewhitespace
  • One command for clean, intelligent staging

Key Features

  • Safe: Filters out deleted files to prevent errors
  • Smart: Handles new files correctly
  • Clean: Maintains consistent code formatting
  • Focused: Stages only substantive changes

Technical Details

The aliases handle several edge cases:

  • ANSI color codes in git output (disabled with -c color.status=false)
  • Deleted files that would cause sed errors (filtered with grep -v "^D\|^.D")
  • New untracked files (handled with git add -N)
  • Empty file lists (handled with xargs -r)

Perfect for teams that want clean commits without manual whitespace management.

网友语录 - 第42期 - 伟大成就需要的是以年为单位的积累,而不是每周都冲刺

这里记录我的一周分享,通常在周六发布。


梨梨原上草 @svuoalnalnis 公司把保洁外包给一间公司,常来的就那么几个面孔。时间长了,你就能体会她们之间的差别。

有时候一走进厨房,你就知道今天来上班的肯定是其中的某一个。而另外一些天,不用具体看脸也知道来摸鱼的是哪几个……

其实想对其中几个说,你们可能干啥都会做得很好,不限于保洁。只要时间允许,我的活也能干。


还真是这样。一生要是每本书都一字一句的读完,确实读不了几千本。但真正值得一字一句读完的书完全没有那么多,你需要略读上万本书(甚至可以更多)才能找出为数不多值得读一次又一次的那些书。一本书翻了几页放下,没什么,很多书没有读完,没什么。没有读完的那些书,书自己也有责任。


戚小诺 我问小九怎么表达他的愤怒,他说他的脑子里会有一个白色小人,一个黑色小人,黑色小人想要去干架:冲啊!我要干架!白色小人就在一边劝架:算了算了,不要打架,也许打不赢会受伤,爸爸妈妈还会担心。然后自己就会冷静很多。——头脑特工队没白看哈😂😂


memoryza 人们天生就是高效的改进者,而只有极少数会愿意从零开始


失败不可怕,一蹶不振才可怕

反馈是变得更好的关键。正如蓝迪波什所说:"人之所以会改变,是因为获得了反馈"。"失败"的本质是一种强反馈,它告诉我们这样或者此时不行,我们需要调整。在这个世界上,只要我们的生命还在继续(留得青山在),就没有真正的失败(不怕没柴烧),有的只是不断调整的过程。一个人的成长速度,取决于他得到反馈的频率和质量。因此,我们应该勇于改变并珍视每一次的反馈,正是它们引导着我们一步步实现人生目标。


真正重要的工作都不是一口气完成的,而是反复回到同一主题,慢慢打磨。像居里夫人发现放射性元素那年夏天,也去乡下休假、爬山。伟大成就需要的是以年为单位的积累,而不是每周都冲刺。别被高强度伪忙碌欺骗,真正的生产力,是以自然节奏、长期愿景,持续推进关键事务


生活若总是愿意吃苦,就有吃不完的苦;若总是抱怨生活,就有抱怨不完的生活。总觉得前路还长,却不知不觉就进入四十几五十几。能改善生活质量的钱一定要尽早花,早花早享受


调查期望不是让用户畅想这产品能具有什么功能,而是为了满足现有需求应该具有什么功能


LLM 的注意力其实是个滑动窗口,不持续提醒,很容易跑偏,这一点就跟我们管理一个想法很多的员工一个道理。(哎!人的注意力也是个滑动窗口。)

Cloudflare DNS Update Script

Motivation: though reinstalling OS is rare, but I still want to automate the DNS update process.

Get API Token

  1. Go to https://dash.cloudflare.com/profile/api-tokens
  2. Click "Create Token"
  3. Use "Edit zone DNS" template
  4. Select your domain in "Zone Resources"
  5. Copy the generated token

Setup API Token in bashrc

Add to ~/.bashrc:

export CF_API_TOKEN="your_api_token_here"

Reload: source ~/.bashrc

Enhanced Update Script

Create update-dns.sh:

#!/bin/bash

# Usage function
usage() {
    echo "Usage: $0 <record_name> [ip_address]"
    echo "       $0 <domain> <subdomain> [ip_address]"
    echo "Examples:"
    echo "  $0 home.example.com                    # Uses Tailscale IP"
    echo "  $0 example.com                         # Root domain with Tailscale IP"
    echo "  $0 server.example.com 192.168.1.100   # Uses specified IP"
    echo "  $0 example.com home                    # Legacy format"
    echo "  $0 example.com @ 1.2.3.4              # Legacy root domain"
    exit 1
}

# Check parameters
if [ $# -lt 1 ]; then
    usage
fi

# Check if API token is set
if [ -z "$CF_API_TOKEN" ]; then
    echo "Error: CF_API_TOKEN not set. Add it to ~/.bashrc"
    exit 1
fi

# Parse arguments - detect format
FIRST_ARG="$1"
SECOND_ARG="$2"
THIRD_ARG="$3"

# Check if first argument contains multiple dots (FQDN format)
if [[ "$FIRST_ARG" == *.*.* ]] || ([[ "$FIRST_ARG" == *.* ]] && [[ "$SECOND_ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]); then
    # FQDN format: script.sh home.example.com [ip]
    RECORD_NAME="$FIRST_ARG"
    CUSTOM_IP="$SECOND_ARG"
    
    # Extract domain by removing first subdomain part
    DOMAIN=$(echo "$RECORD_NAME" | cut -d'.' -f2-)
elif [[ "$FIRST_ARG" == *.* ]] && [ -z "$SECOND_ARG" ]; then
    # Root domain format: script.sh example.com
    RECORD_NAME="$FIRST_ARG"
    DOMAIN="$FIRST_ARG"
    CUSTOM_IP=""
elif [[ "$FIRST_ARG" == *.* ]] && [ -n "$SECOND_ARG" ] && [[ ! "$SECOND_ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    # Legacy format: script.sh example.com home [ip]
    DOMAIN="$FIRST_ARG"
    SUBDOMAIN="$SECOND_ARG"
    CUSTOM_IP="$THIRD_ARG"
    
    if [ "$SUBDOMAIN" = "@" ]; then
        RECORD_NAME="$DOMAIN"
    else
        RECORD_NAME="$SUBDOMAIN.$DOMAIN"
    fi
else
    echo "Error: Invalid arguments format"
    usage
fi

# Get IP address
if [ -n "$CUSTOM_IP" ]; then
    NEW_IP="$CUSTOM_IP"
    echo "Using provided IP: $NEW_IP"
else
    if command -v tailscale >/dev/null 2>&1; then
        NEW_IP=$(tailscale ip -4)
        echo "Using Tailscale IP: $NEW_IP"
    else
        echo "Error: tailscale not found and no IP provided"
        exit 1
    fi
fi

# Validate IP format
if ! echo "$NEW_IP" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' >/dev/null; then
    echo "Error: Invalid IP address format: $NEW_IP"
    exit 1
fi

echo "Updating DNS record: $RECORD_NAME -> $NEW_IP"

# Get Zone ID
echo "Getting Zone ID for $DOMAIN..."
ZONE_RESPONSE=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN")

ZONE_ID=$(echo "$ZONE_RESPONSE" | jq -r '.result[0].id')

if [ "$ZONE_ID" = "null" ] || [ -z "$ZONE_ID" ]; then
    echo "Error: Could not find zone for domain $DOMAIN"
    echo "Response: $ZONE_RESPONSE"
    exit 1
fi

echo "Zone ID: $ZONE_ID"

# Get Record ID
echo "Getting Record ID for $RECORD_NAME..."
RECORD_RESPONSE=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME&type=A")

RECORD_ID=$(echo "$RECORD_RESPONSE" | jq -r '.result[0].id')

if [ "$RECORD_ID" = "null" ] || [ -z "$RECORD_ID" ]; then
    echo "Error: Could not find A record for $RECORD_NAME"
    echo "Available records:"
    echo "$RECORD_RESPONSE" | jq -r '.result[] | "\(.name) (\(.type))"'
    exit 1
fi

echo "Record ID: $RECORD_ID"

# Update DNS record
echo "Updating DNS record..."
UPDATE_RESPONSE=$(curl -s -X PUT \
    "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
    -H "Authorization: Bearer $CF_API_TOKEN" \
    -H "Content-Type: application/json" \
    --data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$NEW_IP\"}")

SUCCESS=$(echo "$UPDATE_RESPONSE" | jq -r '.success')

if [ "$SUCCESS" = "true" ]; then
    echo "✅ DNS record updated successfully!"
    echo "   $RECORD_NAME now points to $NEW_IP"
else
    echo "❌ Failed to update DNS record"
    echo "Response: $UPDATE_RESPONSE"
    exit 1
fi

Make executable: chmod +x update-dns.sh

Usage Examples

# New FQDN format (recommended)
./update-dns.sh home.example.com                    # Uses Tailscale IP
./update-dns.sh server.example.com 192.168.1.100   # Uses custom IP
./update-dns.sh example.com                         # Root domain with Tailscale IP

# Legacy format (still supported)
./update-dns.sh example.com home                    # Subdomain with Tailscale IP
./update-dns.sh example.com @ 1.2.3.4              # Root domain with custom IP

# Multiple subdomains
./update-dns.sh web.example.com
./update-dns.sh api.example.com
./update-dns.sh db.example.com 10.0.0.5

Features

  • ✅ API token stored securely in .bashrc
  • ✅ Auto-discovers Zone ID and Record ID
  • ✅ Supports custom IP or auto-detects Tailscale IP
  • ✅ Supports root domain updates with @
  • ✅ Input validation and error handling
  • ✅ Clear success/failure messages
  • ✅ Usage help and examples

Alternative: Using flarectl

# Install
go install github.com/cloudflare/cloudflare-go/cmd/flarectl@latest

# Update (simpler)
export CF_API_TOKEN="your_token"
flarectl dns update --zone yourdomain.com --name subdomain --content $(tailscale ip -4) --type A

PopOS System Recovery and Partition Migration Notes

Background

Yesterday, my PopOS system broke unexpectedly. The refresh install option failed, and attempting to reinstall while preserving the old /var and /home partitions was unsuccessful.

Solution: Fresh Custom Installation

Performed a clean custom installation with:

  • /boot/efi - formatted
  • / - formatted
  • /recovery - formatted

This approach worked successfully, then migrated data from old partitions.

Partition Migration Process

Tools Used

parted --list    # Get partition information
blkid           # Get partition UUIDs

Note: Couldn't stop GDM or enter single-user mode (system would hang). Performed migration on running system instead.

1. Home Partition Migration (/dev/nvme0n1p8)

sudo mv /home /home.bak          # Backup current home
sudo mkdir /home
sudo mount /dev/nvme0n1p8 /home
ls /home                         # Confirm correct partition
sudo blkid /dev/nvme0n1p8       # Get UUID
sudo vi /etc/fstab

Added to /etc/fstab:

UUID=32e4ed56-6ed9-4f14-9585-ffff54f997b2 /home ext4 defaults 0 2

Rebooted to verify system stability.

2. Backup Partition Setup (/dev/nvme0n1p10)

sudo parted --list
sudo blkid /dev/nvme0n1p10
sudo vi /etc/fstab

Added to /etc/fstab:

UUID=8aba0dd5-7058-4036-8a08-fcd1e0e002bc /backup ext4 defaults 0 2
sudo mount -a                   # Test mount configuration

3. Var Partition Migration (/dev/nvme0n1p7)

# Mount old var partition and backup data
sudo mkdir /mnt/var
sudo mount /dev/nvme0n1p7 /mnt/var
sudo tar -czpvf /backup/old-var-backup.tar.gz -C /mnt var

# Prepare new var partition
sudo umount /mnt/var
sudo mkfs.ext4 /dev/nvme0n1p7   # Format old var partition
sudo mkdir /mnt/new-var
sudo mount /dev/nvme0n1p7 /mnt/new-var

# Copy current var data to new partition
sudo cp -av /var/* /mnt/new-var/

# Update fstab and switch to new partition
sudo blkid /dev/nvme0n1p7
sudo vi /etc/fstab              # Add var partition entry
sudo mv /var /var.old && sudo mkdir /var && sudo mount -a

Key Lessons Learned

  • Fresh installation with custom partitioning was more reliable than trying to preserve old partitions during install
  • System migration on a running system worked fine when rescue mode wasn't accessible
  • Always backup critical data before formatting partitions
  • Test each partition mount configuration before proceeding to the next step

Status: Migration completed successfully. System running normally with all data preserved.