+ $rv .= $pad ."<a\nhref=#r$id>$s_s, $s_c; $ctx->{s_nr}</a>\n";
+}
+
+sub walk_thread {
+ my ($rootset, $ctx, $cb) = @_;
+ my @q = map { (0, $_, -1) } @$rootset;
+ while (@q) {
+ my ($level, $node, $i) = splice(@q, 0, 3);
+ defined $node or next;
+ $cb->($ctx, $level, $node, $i);
+ ++$level;
+ $i = 0;
+ unshift @q, map { ($level, $_, $i++) } @{$node->{children}};
+ }
+}
+
+sub pre_thread {
+ my ($ctx, $level, $node, $idx) = @_;
+ $ctx->{mapping}->{$node->{id}} = [ '', $node, $idx, $level ];
+ skel_dump($ctx, $level, $node);
+}
+
+sub thread_index_entry {
+ my ($ctx, $level, $mime) = @_;
+ my ($beg, $end) = thread_adj_level($ctx, $level);
+ $beg . '<pre>' . index_entry($mime, $ctx, 0) . '</pre>' . $end;
+}
+
+sub stream_thread ($$) {
+ my ($rootset, $ctx) = @_;
+ my $inbox = $ctx->{-inbox};
+ my $mime;
+ my @q = map { (0, $_) } @$rootset;
+ my $level;
+ while (@q) {
+ $level = shift @q;
+ my $node = shift @q or next;
+ my $cl = $level + 1;
+ unshift @q, map { ($cl, $_) } @{$node->{children}};
+ $mime = $inbox->msg_by_smsg($node->{smsg}) and last;
+ }
+ return missing_thread($ctx) unless $mime;
+
+ $ctx->{-obfuscate} = $ctx->{-inbox}->{obfuscate};
+ $mime = PublicInbox::MIME->new($mime);
+ $ctx->{-title_html} = ascii_html($mime->header('Subject'));
+ $ctx->{-html_tip} = thread_index_entry($ctx, $level, $mime);
+ PublicInbox::WwwStream->response($ctx, 200, sub {
+ return unless $ctx;
+ while (@q) {
+ $level = shift @q;
+ my $node = shift @q or next;
+ my $cl = $level + 1;
+ unshift @q, map { ($cl, $_) } @{$node->{children}};
+ my $mid = $node->{id};
+ if ($mime = $inbox->msg_by_smsg($node->{smsg})) {
+ $mime = PublicInbox::MIME->new($mime);
+ return thread_index_entry($ctx, $level, $mime);
+ } else {
+ return ghost_index_entry($ctx, $level, $node);
+ }
+ }
+ my $ret = join('', thread_adj_level($ctx, 0));
+ $ret .= ${$ctx->{dst}}; # skel
+ $ctx = undef;
+ $ret;
+ });