private/Get-WtwWorktreeEmoji.ps1

function Get-WtwWorktreeEmojiPoolCodepoints {
    <#
    .SYNOPSIS
        Codepoints for worktree identity glyphs (Unicode 6–12, one scalar each).
    .DESCRIPTION
        Animals, plants, food, and objects that render as a single emoji on
        Windows, macOS, and Linux. No flags, ZWJ sequences, skin tones, or
        color-circle swatches. Keep this in a function: each private file has
        its own script scope.
    #>

    @(
        # Animals (Unicode 6.0)
        0x1F400, 0x1F401, 0x1F402, 0x1F403, 0x1F404, 0x1F405, 0x1F406, 0x1F407,
        0x1F408, 0x1F409, 0x1F40A, 0x1F40B, 0x1F40C, 0x1F40D, 0x1F40E, 0x1F40F,
        0x1F410, 0x1F411, 0x1F412, 0x1F413, 0x1F414, 0x1F415, 0x1F416, 0x1F417,
        0x1F418, 0x1F419, 0x1F41A, 0x1F41B, 0x1F41C, 0x1F41D, 0x1F41E, 0x1F41F,
        0x1F420, 0x1F421, 0x1F422, 0x1F423, 0x1F424, 0x1F425, 0x1F426, 0x1F427,
        0x1F428, 0x1F429, 0x1F42A, 0x1F42B, 0x1F42C, 0x1F42D, 0x1F42E, 0x1F42F,
        0x1F430, 0x1F431, 0x1F432, 0x1F433, 0x1F434, 0x1F435, 0x1F436, 0x1F437,
        0x1F438, 0x1F439, 0x1F43A, 0x1F43B, 0x1F43C, 0x1F43D, 0x1F43E,
        # Animals (Unicode 8–12)
        0x1F980, 0x1F981, 0x1F982, 0x1F983, 0x1F984, 0x1F985, 0x1F986, 0x1F987,
        0x1F988, 0x1F989, 0x1F98A, 0x1F98B, 0x1F98C, 0x1F98D, 0x1F98E, 0x1F990,
        0x1F991, 0x1F992, 0x1F993, 0x1F994, 0x1F995, 0x1F996, 0x1F997, 0x1F998,
        0x1F999, 0x1F99A, 0x1F99B, 0x1F99C, 0x1F99D, 0x1F99E, 0x1F99F, 0x1F9A5,
        0x1F9A6, 0x1F9A7, 0x1F9A8, 0x1F9A9, 0x1F9AA,
        # Plants / fruit
        0x1F330, 0x1F331, 0x1F332, 0x1F333, 0x1F334, 0x1F335, 0x1F337, 0x1F338,
        0x1F339, 0x1F33A, 0x1F33B, 0x1F33C, 0x1F33D, 0x1F340, 0x1F341, 0x1F342,
        0x1F343, 0x1F344, 0x1F345, 0x1F346, 0x1F347, 0x1F348, 0x1F349, 0x1F34A,
        0x1F34B, 0x1F34C, 0x1F34D, 0x1F34E, 0x1F34F, 0x1F350, 0x1F351, 0x1F352,
        0x1F353, 0x1F490, 0x1F951, 0x1F952, 0x1F954, 0x1F955, 0x1F95D, 0x1F965,
        0x1F966, 0x1F96C, 0x1F96D, 0x1F9C4, 0x1F9C5,
        # Food
        0x2615,  0x1F32D, 0x1F32E, 0x1F32F, 0x1F354, 0x1F355, 0x1F356, 0x1F357,
        0x1F35A, 0x1F35C, 0x1F35D, 0x1F35E, 0x1F35F, 0x1F360, 0x1F363, 0x1F364,
        0x1F366, 0x1F369, 0x1F36A, 0x1F36B, 0x1F36D, 0x1F36F, 0x1F370, 0x1F372,
        0x1F373, 0x1F375, 0x1F377, 0x1F37A, 0x1F37F, 0x1F382, 0x1F950, 0x1F956,
        0x1F957, 0x1F95A, 0x1F95E, 0x1F96F, 0x1F9C0, 0x1F9C1, 0x1F9C2, 0x1F9C6,
        0x1F9C7, 0x1F9C8, 0x1F9C9, 0x1F9CA,
        # Nature / objects / sport / music
        0x26A1,  0x26BD,  0x26BE,  0x26F3,  0x2B50,  0x2728,  0x1F308, 0x1F30A,
        0x1F30B, 0x1F315, 0x1F319, 0x1F31F, 0x1F381, 0x1F388, 0x1F389, 0x1F3A4,
        0x1F3A7, 0x1F3A8, 0x1F3AE, 0x1F3AF, 0x1F3B2, 0x1F3B3, 0x1F3B7, 0x1F3B8,
        0x1F3B9, 0x1F3BA, 0x1F3BB, 0x1F3BE, 0x1F3C0, 0x1F3C6, 0x1F3C8, 0x1F48E,
        0x1F4A1, 0x1F4A5, 0x1F4A7, 0x1F4AB, 0x1F4BB, 0x1F4D6, 0x1F4DA, 0x1F511,
        0x1F512, 0x1F514, 0x1F525, 0x1F527, 0x1F528, 0x1F52E, 0x1F680, 0x1F681,
        0x1F6A2, 0x1F6B2, 0x1F6F9, 0x1F9E9, 0x1F9ED, 0x1F9F1, 0x1F9F8, 0x1FA80,
        0x1FA81, 0x1FA82, 0x1FA90
    )
}

function Get-WtwWorktreeEmojiPool {
    <#
    .SYNOPSIS
        Identity-emoji strings. Walk with UTF-32 — never ``.ToCharArray()``.
    #>

    foreach ($cp in (Get-WtwWorktreeEmojiPoolCodepoints)) {
        [char]::ConvertFromUtf32($cp)
    }
}

function Get-WtwCompactRepoEmoji {
    <#
    .SYNOPSIS
        Repo prefix with whitespace removed so ``🎭 ☸️`` + ``🦔`` is ``🎭☸️🦔``.
    #>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [AllowEmptyString()]
        [string] $Emoji,

        [AllowNull()]
        [object] $RepoEntry
    )

    if ($RepoEntry -and -not $PSBoundParameters.ContainsKey('Emoji')) {
        $Emoji = Get-WtwRepoEmoji -RepoEntry $RepoEntry
    }

    $normalized = ConvertTo-WtwNormalizedRepoEmoji $Emoji
    if (-not $normalized) { return $null }
    return ($normalized -replace '\s+', '')
}

function Get-WtwWorktreeEmojiSeed {
    <#
    .SYNOPSIS
        Canonical hash input: branch-safe task name, NFC, invariant lower-case.
    #>

    [CmdletBinding()]
    param([AllowNull()] [AllowEmptyString()] [string] $Name)

    if ([string]::IsNullOrWhiteSpace($Name)) { return $null }
    $safe = ConvertTo-WtwBranchSafeName -Name $Name
    if ([string]::IsNullOrWhiteSpace($safe)) { return $null }
    return $safe.Normalize([System.Text.NormalizationForm]::FormC).ToLowerInvariant()
}

function Get-WtwDerivedWorktreeEmoji {
    <#
    .SYNOPSIS
        Deterministic pool pick from a worktree task name (SHA-256, little-endian).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [AllowEmptyString()]
        [string] $Name
    )

    $seed = Get-WtwWorktreeEmojiSeed -Name $Name
    if (-not $seed) { return $null }

    $pool = @(Get-WtwWorktreeEmojiPool)
    if ($pool.Count -eq 0) { return $null }

    $payload = [System.Text.Encoding]::UTF8.GetBytes("wtw-worktree-emoji:`n$seed")
    $sha = [System.Security.Cryptography.SHA256]::Create()
    try {
        $hash = $sha.ComputeHash($payload)
    } finally {
        $sha.Dispose()
    }

    # First 4 bytes as little-endian UInt32 so the index is endian-stable.
    [uint32] $indexBits = [uint32]$hash[0] `
        -bor ([uint32]$hash[1] -shl 8) `
        -bor ([uint32]$hash[2] -shl 16) `
        -bor ([uint32]$hash[3] -shl 24)
    $idx = [int]($indexBits % [uint32]$pool.Count)
    return $pool[$idx]
}

function Get-WtwWorktreeEmoji {
    <#
    .SYNOPSIS
        Stored override, else a glyph derived from the task (or pretty-name fallback).
    #>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [object] $WorktreeEntry,

        [AllowNull()]
        [AllowEmptyString()]
        [string] $TaskName,

        [AllowNull()]
        [AllowEmptyString()]
        [string] $Name
    )

    $override = ConvertTo-WtwNormalizedRepoEmoji (Get-WtwPropertyValue -Object $WorktreeEntry -Name 'emoji')
    if ($override) { return $override }

    if (-not [string]::IsNullOrWhiteSpace($TaskName)) {
        $derived = Get-WtwDerivedWorktreeEmoji -Name $TaskName
        if ($derived) { return $derived }
    }

    $fallback = Get-WtwNameWithoutColorCircle -Name $Name
    if ([string]::IsNullOrWhiteSpace($fallback) -and $WorktreeEntry) {
        $fallback = Get-WtwNameWithoutColorCircle (Get-WtwPropertyValue -Object $WorktreeEntry -Name 'prettyName')
    }
    if ([string]::IsNullOrWhiteSpace($fallback)) { return $null }
    return Get-WtwDerivedWorktreeEmoji -Name $fallback
}

function Set-WtwWorktreeEmojiProperty {
    <#
    .SYNOPSIS
        Write or clear a worktree emoji override. ``none`` / ``auto`` restores derived.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object] $WorktreeEntry,

        [AllowNull()]
        [AllowEmptyString()]
        [object] $Emoji
    )

    $normalized = ConvertTo-WtwNormalizedRepoEmoji $Emoji
    if ($normalized) {
        $WorktreeEntry | Add-Member -NotePropertyName 'emoji' -NotePropertyValue $normalized -Force
    } elseif ((Get-WtwPropertyNames -Object $WorktreeEntry) -contains 'emoji') {
        $WorktreeEntry.PSObject.Properties.Remove('emoji')
    }
    return $normalized
}

function Format-WtwWorktreeDisplayName {
    <#
    .SYNOPSIS
        ``{repoEmojiCompact}{worktreeEmoji} {baseName}`` with no space between emojis.
    .DESCRIPTION
        Strips leading color circles from the stored pretty name so SourceGit /
        cmux / T3 titles are identity emojis, not swatches. Does not strip a
        custom leading pool emoji (``🏀 PF037`` stays). Main checkout is repo-only
        and does not use this helper.
    #>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [AllowEmptyString()]
        [string] $Name,

        [AllowNull()]
        [AllowEmptyString()]
        [string] $TaskName,

        [AllowNull()]
        [object] $WorktreeEntry,

        [AllowNull()]
        [object] $RepoEntry,

        [AllowNull()]
        [AllowEmptyString()]
        [string] $WorktreeEmoji,

        [AllowNull()]
        [AllowEmptyString()]
        [string] $RepoEmoji
    )

    if ($WorktreeEntry -and [string]::IsNullOrWhiteSpace($Name)) {
        $Name = Get-WtwPropertyValue -Object $WorktreeEntry -Name 'prettyName'
    }

    if (-not $PSBoundParameters.ContainsKey('WorktreeEmoji') -or $null -eq $WorktreeEmoji) {
        $WorktreeEmoji = Get-WtwWorktreeEmoji -WorktreeEntry $WorktreeEntry -TaskName $TaskName -Name $Name
    }

    $base = Get-WtwNameWithoutColorCircle -Name $Name
    if ([string]::IsNullOrWhiteSpace($base) -and -not [string]::IsNullOrWhiteSpace($TaskName)) {
        $base = $TaskName
    }

    $repoCompact = if ($PSBoundParameters.ContainsKey('RepoEmoji')) {
        Get-WtwCompactRepoEmoji -Emoji $RepoEmoji
    } else {
        Get-WtwCompactRepoEmoji -RepoEntry $RepoEntry
    }

    $prefix = if ($WorktreeEmoji) {
        if ($repoCompact) { "$repoCompact$WorktreeEmoji " } else { "$WorktreeEmoji " }
    } else {
        $null
    }

    $remainder = $base
    if ($prefix -and -not [string]::IsNullOrWhiteSpace($base) -and $base.StartsWith($prefix, [System.StringComparison]::Ordinal)) {
        $remainder = $base.Substring($prefix.Length)
    } elseif ($WorktreeEmoji -and $repoCompact -and -not [string]::IsNullOrWhiteSpace($base)) {
        $tight = "$repoCompact$WorktreeEmoji"
        if ($base.StartsWith($tight, [System.StringComparison]::Ordinal)) {
            $remainder = $base.Substring($tight.Length).TrimStart()
        }
    }

    $remainder = ConvertTo-WtwHumanizedLabel -Name $remainder -Slug $TaskName

    if (-not $WorktreeEmoji) {
        if (-not [string]::IsNullOrWhiteSpace($remainder)) { return $remainder }
        if (-not [string]::IsNullOrWhiteSpace($Name)) { return $Name }
        return $base
    }

    if ([string]::IsNullOrWhiteSpace($remainder)) { return $prefix.TrimEnd() }
    return "$prefix$remainder"
}