I don't like the attribute dot syntax. On the positive side, it's very readable. and the left-to-right syntax is a very clear and strong organization technique.
That said, it has a problem with 'rootedness' as in 'what is the root of this attribute string'.
Firstly, 'with' keyword allows you to silently slice off the front end when the attribute string originates from a passed-in argument variable.
Secondly, when you arrive at foo in foo.bar, you have no concept of what the ancestor path looks like; someone is going to do an import of this file, likely store it in a named variable in the context it is needed, and then dispose it. Every time we need foo, it's being referenced from some ephemeral external context. So it's impossible to say or know "foo has 5 references" via static analysis unless we are just grepping it.
Thirdly, (related to above), when you declare services.tailscale.enable = true; you are relying on some ephemeral other location to find meaning in services-dot as declared in the current file. Metaphorically, we can't actually turn and face upstream looking up the call stack to easily see how we arrived (nor how many times we expect to arrive) at this site.
'Self' in the context of flakes is another great example of emphemeral-ness and non-discoverability...what is accessible under 'self'? It takes a PHD to describe it.
Allowing strings mixed in (foo.bar."baz".biz) and the expectation that you might be interpolating (foo.bar."${baz}".biz) is a whole nother ball of wax...