@@ -69,8 +69,10 @@ export const mergeFormats = (formats: Format[], root?: RawObject, inheritedType?
6969 }
7070
7171 if ( subFormats . length > 0 ) {
72- // Pass along the current node's effective type so children can inherit it.
73- const sub = mergeFormats ( subFormats as Format [ ] , effectiveRoot , getNodeType ( ) ) as unknown as RawObject ;
72+ // Pass the effective type down to group children so they can inherit it.
73+ // Do NOT pass it into $value content — type is DTCG metadata on the token
74+ // node itself, not something that should bleed into value sub-objects.
75+ const sub = mergeFormats ( subFormats as Format [ ] , effectiveRoot , key === "$value" ? undefined : getNodeType ( ) ) as unknown as RawObject ;
7476 // Leaf token: unwrap to the resolved value so callers get the value
7577 // directly (e.g. tokens["box-shadow"].panel → shadow array, not { $type, $value }).
7678 // Group nodes have no $value and pass through as-is.
@@ -125,9 +127,10 @@ const resolveAlias = (alias: string, root: RawObject): unknown => {
125127 if ( ! isPlainObject ( node ) ) return undefined ;
126128 node = node [ seg ] ;
127129 }
128- // Read $value from the target node — this triggers that node's own getter,
129- // so chains of aliases resolve automatically.
130- return isPlainObject ( node ) && "$value" in node ? node . $value : undefined ;
130+ // Token nodes are now auto-unwrapped, so the node after traversal IS the
131+ // resolved value — return it directly. Chains resolve automatically because
132+ // each getter fires in turn as we walk the tree.
133+ return node ;
131134 } finally {
132135 resolvingAliases . delete ( path ) ;
133136 }
@@ -143,6 +146,11 @@ const resolveRef = (ref: string, root: RawObject): unknown => {
143146 let node : unknown = root ;
144147 for ( const seg of ref . slice ( 2 ) . split ( "/" ) ) {
145148 if ( ! isPlainObject ( node ) ) return undefined ;
149+ // $ref paths are often written with /$value/ as a literal segment (DTCG
150+ // document-root pointer style). Since token nodes are auto-unwrapped in
151+ // the merged tree, that key no longer exists — skip the segment so the
152+ // walk continues into the already-resolved value.
153+ if ( seg === "$value" && ! ( seg in ( node as RawObject ) ) ) continue ;
146154 node = ( node as RawObject ) [ seg ] ;
147155 }
148156 return node ;
0 commit comments