@@ -572,8 +572,9 @@ func sanitizeName(name string) string {
572572 return r
573573 }, name )
574574 cleaned = strings .TrimSpace (cleaned )
575- if len (cleaned ) > maxNameLength {
576- cleaned = cleaned [:maxNameLength ]
575+ if utf8 .RuneCountInString (cleaned ) > maxNameLength {
576+ runes := []rune (cleaned )
577+ cleaned = string (runes [:maxNameLength ])
577578 }
578579 if cleaned == "" {
579580 return "Unnamed Layout"
@@ -619,13 +620,18 @@ func isDeadKey(legends KeyLegends, declaredDeadKeys map[rune]bool) bool {
619620func buildCharMap (keys []TransportKey ) map [string ]HIDCombo {
620621 m := make (map [string ]HIDCombo )
621622
622- // Sort keys by position for deterministic first-occurrence behaviour
623- slices .SortStableFunc (keys , func (a , b TransportKey ) int {
623+ // Sort a copy by position for deterministic first-occurrence behaviour.
624+ // We must not mutate the original slice — it preserves KLE parse order,
625+ // which the scancodes metadata override uses (0-based index).
626+ sorted := make ([]TransportKey , len (keys ))
627+ copy (sorted , keys )
628+ slices .SortStableFunc (sorted , func (a , b TransportKey ) int {
624629 if comp := cmp .Compare (a .Y , b .Y ); comp != 0 {
625630 return comp
626631 }
627632 return cmp .Compare (a .X , b .X )
628633 })
634+ keys = sorted
629635
630636 for _ , key := range keys {
631637 if key .Scancode == 0 {
@@ -680,8 +686,9 @@ func addDeadKeyCompositions(keys []TransportKey, charMap map[string]HIDCombo, de
680686 }
681687
682688 type deadKeyInfo struct {
683- combo HIDCombo // scancode + modifiers to press the dead key
684- combining rune // Unicode combining character
689+ combo HIDCombo // scancode + modifiers to press the dead key
690+ combining rune // Unicode combining character
691+ displayKey rune // the legend character as it appears on the keycap
685692 }
686693
687694 // Collect dead key legends that are both declared AND have a known
@@ -713,8 +720,9 @@ func addDeadKeyCompositions(keys []TransportKey, charMap map[string]HIDCombo, de
713720 continue
714721 }
715722 deadKeys = append (deadKeys , deadKeyInfo {
716- combo : HIDCombo {Scancode : key .Scancode , Modifiers : layer .mods },
717- combining : combining ,
723+ combo : HIDCombo {Scancode : key .Scancode , Modifiers : layer .mods },
724+ combining : combining ,
725+ displayKey : r ,
718726 })
719727 }
720728 }
@@ -764,15 +772,11 @@ func addDeadKeyCompositions(keys []TransportKey, charMap map[string]HIDCombo, de
764772 }
765773 }
766774
767- // Standalone dead key: dead key + Space → the dead key character itself
768- deadChar := string ([]rune {dk .combining })
769- // Find the display character (not the combining form)
770- for displayRune , combiningRune := range deadKeyToCombining {
771- if combiningRune == dk .combining {
772- deadChar = string (displayRune )
773- break
774- }
775- }
775+ // Standalone dead key: dead key + Space → the dead key character itself.
776+ // Use the display rune captured from the actual key legend, not a
777+ // reverse lookup from deadKeyToCombining (which has duplicate values
778+ // and non-deterministic map iteration order).
779+ deadChar := string (dk .displayKey )
776780 if _ , exists := charMap [deadChar ]; exists {
777781 // Replace the simple entry with a prefixed one (dead key + space)
778782 charMap [deadChar ] = HIDCombo {
0 commit comments