Oh damn. Sample program had way more complex input loops than necessary. Try to look at the newly committed ones.
The reason for this is before introducing input filtering read() function was returning all input events: presses, releases, mouse movement. Especially mouse was a problem. The library uses vsync (by default, may be turned off) which is generally a good thing but limits refresh rate to something around 60 fps. With mouse movement it is rather easy to wave mouse around (and register corresponding events) faster than 60 text cells per second. This means that if you read input and redraw the screen after every read, then output will not keep up with input and there will be a noticeable lag. To work around that previous input loops were reading all available/buffered input every 'frame', hence the loop inside the loop.
You can see the same two-loop flow structure is still used in the mouse-specific samples ('mouse' and 'input filtering'). No real way around it, turning vsync off is not a decent solution because that would mean just meaninglessly redrawing/updating the screen.
Another case is animations. For example 'Extended ...' samples produce an animated output so they cannot hang on read() until some input available. They loop continuously, check for input availability via has_input() and call read() only when there is some.
You do not need all that in a simple case. As long as you have neither mouse nor animation, just a simple do-while loop should do. By default read() function returns only key presses so it pretty safe to update after every read.
I might take that even further. One of the possible future features is animated tiles. If those are implemented, even simple animations (like when monsters/torches/etc have a few frames of animation played continuously and synchronously) would not require any fancy input handling.