Preface: The whole circuit isn't complicated, but quite involved. I will try to use 'normal' language to make it less hard to read, as mentioning all the signals and interaction would end in an unreadable conglomeration of words.
you'd write an ASCII byte to the output port and that would be displayed in the next position on the display,
On the circuit side it waited until the 'cursor bit' in the 7th shift register (C11B) marking the cursor position, came along,. Then the character was inserted and the cursor position delayed by one, so it marked the next character position.
with the display automatically scrolling when writing a CR on the last line or writing the last position on the last line.
CR was a function, not a character written into the display. A CR waited like any other character for the cursor position to come around. But instead of getting inserted, it simply waited for the end of the line (*1) and inserted the cursor into the beginning of the next - which may include a scroll.
Scrolling also wasn't exactly like one imagines from memory-mapped screens. The shift registers held 1024 bits each, thus 1024 character positions. The screen was made up as 24x40=960 characters, leaving 64 'invisible' ones. These positions were shifted through during retrace - and cleared. Thus there were always 64 spaces after the last visible line. Scrolling was now done by advancing the start of the screen by one line (40 characters), effectively moving the top line into the 'invisible area' and pulling an already empty one from there. During the next retrace the 'old' line was eliminated, ready for the next up-scroll operation :)
There appears to have been little or no cursor control beyond this, I'm not even sure if backspace was available.
No, it wasn't. The display understood only one control character, CR. The only other manipulation was by pulling CLR to clear the display.
How did it store the characters for the screen
The six 2504 type 1024x1 shift registers build the screen memory. So each shift moves one character ahead.
and generate the video?
During the first scan line of a character row, the screen memory is shifted 40 times and its content is moved into the 2519 type 40x6 shift register. It will output its content to the 2513 character ROM. Once during loading and 7 times thereafter, for each can further scan line. One of the 74161 (D8) addresses them. From the ROM it gets loaded into the 74166 shift register and shifted out to be displayed.
As well as a general theory of operation,
If the above isn't sufficient, please ask in comment. It's just too broad to describe everything here (I guess it'll take 3-5 A4 pages to do so in full detail).
links to data sheets would also be appreciated
Most chips are standard TTL. The shift registers are:
2504 - 1024x1 shift register. Six are used in parallel to store the characters and a 7th holds the cursor position. They are a bit odd as they need voltages of -12V for clock low and +5V for clock high, as well as two alternating clocks, but essentially they are simple shift registers with 1024 bits, advancing one position per clock. The output is rerouted to input, unless a character is written or cleared.
2519 - 40x6 shift register holding one character line during display. As a special feature, they have a 'recirculate' function. This can be seen as kind of a load. As long as pin 4 is high, each clock just moves the content one position ahead and outputs it. If low, the data at the input pins get loaded and at the same time outputted. As described above, during the first scan line of a text row (detected by gate B2 from the scan line counter) 40 characters get loaded from the 2504s holding the screen data.
Everything else is, AFAICT, basic TTL devices.
*1 - This is a little oddity which is complicated to understand (or at least I can't). For one, when a CR occurs, there can't be any character after that position on the line, so CR does not need to delete anything. Still, the CR detection circuit does pull CLR until the end of the line.
The "can't be any character" results from the fact that the only function that can set the cursor up again is clearing the screen. But when doing so, all lines are cleared, so CR does not need to clear to the end of the line. Similarly, any new line scrolled up is also cleared before (during retrace), so again there is no need to clear the line when CR comes along.
The only way to screw that up is by pulling CLR for less than a whole frame (16.7 ms). But even then the cursor would still be positioned after the last text, with all empty lines below.