[DRE-commits] [sup-mail] 01/01: Imported Upstream version 0.15.0

Per Andersson avtobiff at moszumanska.debian.org
Wed Oct 1 19:06:14 UTC 2014


This is an automated email from the git hooks/post-receive script.

avtobiff pushed a commit to annotated tag upstream/0.15.0
in repository sup-mail.

commit e8f2ab4bc691f491fae5221a535e0012c6d5b5a5
Author: Per Andersson <avtobiff at gmail.com>
Date:   Sun Nov 24 11:00:36 2013 +0100

    Imported Upstream version 0.15.0
---
 .ditz-plugins                                      |   1 -
 .gitignore                                         |   7 -
 CONTRIBUTORS                                       |  51 ++--
 Gemfile                                            |   3 +
 History.txt                                        |  83 +++++++
 README.md                                          |  75 ++++++
 README.txt                                         | 128 ----------
 Rakefile                                           |  71 ++----
 ReleaseNotes                                       |  71 ++++++
 bin/sup                                            |  52 ++---
 bin/sup-add                                        |   7 +-
 bin/sup-cmd                                        | 138 -----------
 bin/sup-config                                     |  23 +-
 bin/sup-dump                                       |   2 +
 bin/sup-import-dump                                |   4 +-
 bin/sup-psych-ify-config-files                     |  16 ++
 bin/sup-recover-sources                            |   2 +
 bin/sup-server                                     |  44 ----
 bin/sup-sync                                       |   4 +-
 bin/sup-sync-back-maildir                          | 127 ++++++++++
 bin/{sup-sync-back => sup-sync-back-mbox}          |  10 +-
 bin/sup-tweak-labels                               |   4 +-
 ...e-0240b36671ecb019e57ef27e0901bff055385371.yaml |  22 --
 ...e-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml |  27 ---
 ...e-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml |  18 --
 ...e-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml |  18 --
 ...e-182841e15d6909892adf43678bae03597ce10519.yaml |  25 --
 ...e-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml |  30 ---
 ...e-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml |  22 --
 ...e-23658477a445c2e61405fecb4cb641a2298caba6.yaml |  27 ---
 ...e-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml |  19 --
 ...e-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml |  30 ---
 ...e-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml |  22 --
 ...e-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml |  18 --
 ...e-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml |  22 --
 ...e-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml |  18 --
 ...e-38d6f805b0c8bad013ec73f56e6245c890528591.yaml |  29 ---
 ...e-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml |  26 ---
 ...e-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml |  26 ---
 ...e-46df983ccdb75408a37b3911472d4015664a3cf6.yaml |  20 --
 ...e-47aab6443b6c107c3067cdb614186099db570acf.yaml |  37 ---
 ...e-4af242013994ae557e431ba350a92c4f9e1739ef.yaml |  28 ---
 ...e-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml |  23 --
 ...e-4e501973cea5bd1f28739ae4cea98edce8249895.yaml |  32 ---
 ...e-5348fec2b1112250e241afc7467de29e5691d1be.yaml |  21 --
 ...e-57668c69d0190d6e849309834d4ad1d215efa779.yaml |  21 --
 ...e-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml |  20 --
 ...e-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml |  24 --
 ...e-60d86dd32054533a6206f698033ec668af6a7574.yaml |  29 ---
 ...e-61949ec83770b5d46f89eff21799968187012cce.yaml |  22 --
 ...e-65506670167642cc581956bc1b25c26b5bff215b.yaml |  30 ---
 ...e-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml |  26 ---
 ...e-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml |  22 --
 ...e-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml |  20 --
 ...e-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml |  24 --
 ...e-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml |  30 ---
 ...e-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml |  26 ---
 ...e-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml |  26 ---
 ...e-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml |  31 ---
 ...e-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml |  28 ---
 ...e-7c77e757321c2639daea013824ad1a14099815b1.yaml |  26 ---
 ...e-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml |  29 ---
 ...e-829b449c51fca9a39047d00fabc552cc110c69b2.yaml |  24 --
 ...e-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml |  24 --
 ...e-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml |  53 -----
 ...e-8aa7ea95f066fd0668452093b85903bd142905c9.yaml |  26 ---
 ...e-8c0e627c500f679badca28f60ba76998fd65d46a.yaml |  26 ---
 ...e-8e825caee33a6ac144580bf44d0d3060ad162394.yaml |  21 --
 ...e-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml |  22 --
 ...e-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml |  22 --
 ...e-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml |  30 ---
 ...e-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml |  26 ---
 ...e-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml |  26 ---
 ...e-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml |  22 --
 ...e-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml |  27 ---
 ...e-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml |  26 ---
 ...e-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml |  29 ---
 ...e-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml |  24 --
 ...e-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml |  25 --
 ...e-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml |  27 ---
 ...e-bff2527210b3aacae2f74029e5856fed82f1689c.yaml |  33 ---
 ...e-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml |  21 --
 ...e-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml |  22 --
 ...e-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml |  22 --
 ...e-cef3096582de268c050f78223eb6a22ac2599606.yaml |  31 ---
 ...e-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml |  26 ---
 ...e-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml |  22 --
 ...e-d131464e921aefc35571c119aac4d9f1decdebae.yaml |  30 ---
 ...e-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml |  34 ---
 ...e-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml |  33 ---
 ...e-e24df153080c6e7a16335018b04d70d9381258b8.yaml |  20 --
 ...e-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml |  34 ---
 ...e-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml |  24 --
 ...e-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml |  20 --
 ...e-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml |  21 --
 ...e-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml |  30 ---
 ...e-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml |  26 ---
 bugs/project.yaml                                  |  53 -----
 contrib/colorpicker.rb                             |   6 +-
 contrib/completion/_sup.zsh                        |   4 +-
 doc/FAQ.txt                                        |  10 +-
 doc/Hooks.txt                                      |   7 +-
 doc/NewUserGuide.txt                               | 258 ---------------------
 lib/sup.rb                                         | 220 +++++++++++-------
 lib/sup/account.rb                                 |   3 +-
 lib/sup/buffer.rb                                  |  36 ++-
 lib/sup/client.rb                                  |  92 --------
 lib/sup/colormap.rb                                |   1 +
 lib/sup/contact.rb                                 |   9 +-
 lib/sup/crypto.rb                                  |  76 ++++--
 lib/sup/draft.rb                                   |   7 +-
 lib/sup/hook.rb                                    |  10 +
 ...rizontal-selector.rb => horizontal_selector.rb} |  11 +-
 lib/sup/index.rb                                   | 214 ++++++++++-------
 .../{interactive-lock.rb => interactive_lock.rb}   |   0
 lib/sup/label.rb                                   |   8 +-
 lib/sup/logger.rb                                  |   7 +-
 lib/sup/logger/singleton.rb                        |  10 +
 lib/sup/maildir.rb                                 | 167 +++++++++----
 lib/sup/mbox.rb                                    |   2 +-
 lib/sup/message.rb                                 |  85 ++++++-
 lib/sup/{message-chunks.rb => message_chunks.rb}   |  75 ++++--
 .../{buffer-list-mode.rb => buffer_list_mode.rb}   |   0
 .../{completion-mode.rb => completion_mode.rb}     |   6 +-
 lib/sup/modes/{compose-mode.rb => compose_mode.rb} |   2 +
 lib/sup/modes/{console-mode.rb => console_mode.rb} |  17 +-
 .../{contact-list-mode.rb => contact_list_mode.rb} |   0
 ...ge-async-mode.rb => edit_message_async_mode.rb} |   0
 .../{edit-message-mode.rb => edit_message_mode.rb} | 107 ++++++---
 .../{file-browser-mode.rb => file_browser_mode.rb} |   0
 lib/sup/modes/{forward-mode.rb => forward_mode.rb} |  17 +-
 lib/sup/modes/{help-mode.rb => help_mode.rb}       |   0
 lib/sup/modes/{inbox-mode.rb => inbox_mode.rb}     |   2 +-
 .../{label-list-mode.rb => label_list_mode.rb}     |   0
 ...esults-mode.rb => label_search_results_mode.rb} |   0
 .../{line-cursor-mode.rb => line_cursor_mode.rb}   |   4 +-
 lib/sup/modes/{log-mode.rb => log_mode.rb}         |   0
 ...sults-mode.rb => person_search_results_mode.rb} |   0
 lib/sup/modes/{poll-mode.rb => poll_mode.rb}       |   0
 lib/sup/modes/{reply-mode.rb => reply_mode.rb}     |  66 +++---
 lib/sup/modes/{resume-mode.rb => resume_mode.rb}   |   0
 lib/sup/modes/{scroll-mode.rb => scroll_mode.rb}   |   8 +-
 .../{search-list-mode.rb => search_list_mode.rb}   |  18 +-
 ...arch-results-mode.rb => search_results_mode.rb} |   6 +-
 lib/sup/modes/{text-mode.rb => text_mode.rb}       |   0
 .../{thread-index-mode.rb => thread_index_mode.rb} |  50 +++-
 .../{thread-view-mode.rb => thread_view_mode.rb}   |  26 ++-
 lib/sup/poll.rb                                    | 198 +++++++++++-----
 lib/sup/protocol.rb                                | 161 -------------
 lib/sup/rfc2047.rb                                 |   4 +-
 lib/sup/search.rb                                  |  41 +++-
 lib/sup/sent.rb                                    |   9 +-
 lib/sup/server.rb                                  | 116 ---------
 lib/sup/service/label_service.rb                   |  45 ++++
 lib/sup/source.rb                                  |  68 ++++--
 lib/sup/textfield.rb                               |  10 +
 lib/sup/thread.rb                                  |   6 +
 lib/sup/util.rb                                    | 137 +++++++----
 lib/sup/util/path.rb                               |   9 +
 lib/sup/util/query.rb                              |  17 ++
 lib/sup/util/uri.rb                                |  15 ++
 lib/sup/version.rb                                 |   3 +
 protocol.md                                        | 168 --------------
 release-script.txt                                 |  17 --
 sup-files.rb                                       |  11 -
 sup-version.rb                                     |  15 --
 sup.gemspec                                        |  67 ++++++
 test/dummy_source.rb                               |   2 +-
 test/gnupg_test_home/gpg.conf                      |   1 +
 test/gnupg_test_home/pubring.gpg                   | Bin 0 -> 1945 bytes
 test/gnupg_test_home/receiver_pubring.gpg          | Bin 0 -> 718 bytes
 test/gnupg_test_home/receiver_secring.gpg          | Bin 0 -> 1382 bytes
 test/gnupg_test_home/receiver_trustdb.gpg          | Bin 0 -> 1280 bytes
 test/gnupg_test_home/secring.gpg                   | Bin 0 -> 2529 bytes
 test/gnupg_test_home/sup-test-2 at foo.bar.asc        |  20 ++
 test/gnupg_test_home/trustdb.gpg                   | Bin 0 -> 1360 bytes
 test/integration/test_label_service.rb             |  18 ++
 test/test_crypto.rb                                | 109 +++++++++
 test/test_header_parsing.rb                        |  35 ++-
 test/test_helper.rb                                |   7 +
 test/test_message.rb                               |  36 ++-
 test/test_server.rb                                | 106 ---------
 test/test_yaml_migration.rb                        |  80 +++++++
 test/test_yaml_regressions.rb                      |  17 ++
 test/unit/service/test_label_service.rb            |  19 ++
 test/unit/test_horizontal_selector.rb              |  40 ++++
 test/unit/util/test_query.rb                       |  46 ++++
 test/unit/util/test_string.rb                      |  57 +++++
 test/unit/util/test_uri.rb                         |  19 ++
 www/index.html                                     | 224 ------------------
 www/main.css                                       |  36 ---
 www/ss1.png                                        | Bin 93669 -> 0 bytes
 www/ss2.png                                        | Bin 38059 -> 0 bytes
 www/ss3.png                                        | Bin 43946 -> 0 bytes
 www/ss4.png                                        | Bin 43776 -> 0 bytes
 www/ss5.png                                        | Bin 63085 -> 0 bytes
 www/ss6.png                                        | Bin 30276 -> 0 bytes
 197 files changed, 2367 insertions(+), 4168 deletions(-)

diff --git a/.ditz-plugins b/.ditz-plugins
deleted file mode 100644
index 2756e1e..0000000
--- a/.ditz-plugins
+++ /dev/null
@@ -1 +0,0 @@
-- git
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 0820160..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-# i use vi
-*.swp
-.ditz-config
-# i use emacs
-*~
-# i use rake package task
-pkg/
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 04d346b..6060f75 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -1,54 +1,71 @@
-William Morgan <wmorgan-sup at the masanjin dot nets>
+William Morgan <william at the twitter dot coms>
 Rich Lane <rlane at the club.cc.cmu dot edus>
+Gaute Hope <eg at the gaute.vetsj dot coms>
+Whyme Lyu <callme5long at the gmail dot coms>
+Hamish Downer <dmishd at the gmail dot coms>
+Damien Leone <damien.leone at the fensalir dot frs>
+Sascha Silbe <sascha-pgp at the silbe dot orgs>
+Eric Weikl <eric.weikl at the gmx dot nets>
 Ismo Puustinen <ismo at the iki dot fis>
 Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
-Eric Sherman <hyperbolist at the gmail dot coms>
 Michael Stapelberg <michael at the stapelberg dot des>
+Eric Sherman <hyperbolist at the gmail dot coms>
+Tero Tilus <tero at the tilus dot nets>
 Ben Walton <bwalton at the artsci.utoronto dot cas>
 Mike Stipicevic <stipim at the rpi dot edus>
+Clint Byrum <clint at the ubuntu dot coms>
 Marcus Williams <marcus-sup at the bar-coded dot nets>
 Lionel Ott <white.magic at the gmx dot des>
-Tero Tilus <tero at the tilus dot nets>
+Gaudenz Steinlin <gaudenz at the soziologie dot chs>
 Ingmar Vanhassel <ingmar at the exherbo dot orgs>
 Mark Alexander <marka at the pobox dot coms>
-Gaute Hope <eg at the gaute.vetsj dot coms>
+Edward Z. Yang <ezyang at the mit dot edus>
+Matthieu Rakotojaona <matthieu.rakotojaona at the gmail dot coms>
 Christopher Warrington <chrisw at the rice dot edus>
 W. Trevor King <wking at the drexel dot edus>
-Gaudenz Steinlin <gaudenz at the soziologie dot chs>
 Richard Brown <rbrown at the exherbo dot orgs>
+Anthony Martinez <pi+sup at the pihost dot uss>
 Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
-Sascha Silbe <sascha-pgp at the silbe dot orgs>
 Israel Herraiz <israel.herraiz at the gmail dot coms>
-Anthony Martinez <pi+sup at the pihost dot uss>
-Hamish Downer <dmishd at the gmail dot coms>
 Bo Borgerson <gigabo at the gmail dot coms>
-William Erik Baxter <web at the superscript dot coms>
 Michael Hamann <michael at the content-space dot des>
+William Erik Baxter <web at the superscript dot coms>
+Jonathan Lassoff <jof at the thejof dot coms>
 Grant Hollingworth <grant at the antiflux dot orgs>
 Adeodato Simó <dato at the net.com.org dot ess>
+Ico Doornekamp <ico at the pruts dot nls>
+Markus Klinik <markus.klinik at the gmx dot des>
 Daniel Schoepe <daniel.schoepe at the googlemail dot coms>
+James Taylor <james at the jamestaylor dot orgs>
 Jason Petsod <jason at the petsod dot orgs>
 Steve Goldman <sgoldman at the tower-research dot coms>
-Edward Z. Yang <ezyang at the MIT dot EDUs>
+Robin Burchell <viroteck at the viroteck dot nets>
+Peter Harkins <ph at the malaprop dot orgs>
 Decklin Foster <decklin at the red-bean dot coms>
 Cameron Matheson <cam+sup at the cammunism dot orgs>
 Carl Worth <cworth at the cworth dot orgs>
-Jeff Balogh <its.jeff.balogh at the gmail dot coms>
+Alex Vandiver <alex at the chmrr dot nets>
 Andrew Pimlott <andrew at the pimlott dot nets>
-Alex Vandiver <alexmv at the mit dot edus>
-Peter Harkins <ph at the malaprop dot orgs>
+Jeff Balogh <its.jeff.balogh at the gmail dot coms>
+Matías Aguirre <matiasaguirre at the gmail dot coms>
 Kornilios Kourtis <kkourt at the cslab.ece.ntua dot grs>
 Giorgio Lando <patroclo7 at the gmail dot coms>
-Damien Leone <damien.leone at the fensalir dot frs>
+Kevin Riggle <kevinr at the free-dissociation dot coms>
 Benoît PIERRE <benoit.pierre at the gmail dot coms>
 Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
+Steven Lawrance <stl at the koffein dot nets>
 Jonah <Jonah at the GoodCoffee dot cas>
+ian <itaylor at the uark dot edus>
 Adam Lloyd <adam at the alloy-d dot nets>
+Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
+0xACE <0xACE at the users.noreply.github dot coms>
+Per Andersson <avtobiff at the gmail dot coms>
+MichaelRevell <mikearevell at the gmail dot coms>
 Todd Eisenberger <teisenbe at the andrew.cmu dot edus>
-ian <ian at the lorf dot orgs>
 Steven Walter <swalter at the monarch.(none)>
-ian <itaylor at the uark dot edus>
+Matthias Vallentin <vallentin at the icir dot orgs>
+akojo <atte.kojo at the gmail dot coms>
 Jon M. Dugan <jdugan at the es dot nets>
-Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
+Horacio Sanson <horacio at the skillupjapan.co dot jps>
 Stefan Lundström <lundst at the snabb.(none)>
 Kirill Smelkov <kirr at the landau.phys.spbu dot rus>
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..e088013
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org/'
+
+gemspec
diff --git a/History.txt b/History.txt
index 8bfd5eb..434ce63 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,86 @@
+== 0.15.0 / 2013-11-07
+
+* Maildir Syncback has now been merged into main sup! This is a
+  long-time waiting feature initially developed by Damien Leone,
+  then picked up by Edward Z. Yang who continued development. Additionally
+  several others have been contributing.
+
+  Eventually, recently, Eric Weikl has picked up this branch, modernized
+  it to current sup, maintained it and gotten it ready for release.
+
+  Main authors:
+
+  Damien Leone
+  Edward Z. Yang
+  Eric Weikl
+
+  Not all of the features initially proposed have been included. This is
+  to maintain compatibility with more operating systems and wait with
+  the more daring features to make sure sup is stable-ish.
+
+  This is a big change since sup now can modify your mail (!), please
+  back up your mail and your configuration before using the maildir
+  syncback feature. For instructions on how to migrate an existing
+  maildir source or how to set up a new one, refer to the wiki:
+
+  https://github.com/sup-heliotrope/sup/wiki/Using-sup-with-other-clients
+
+  It is possible to both disable maildir syncback globally (default:
+  disabled) and per-source (default: enabled).
+
+* Sup on Ruby 2.0.0 now works - but beware, this has not been very throughly
+  tested. Patches are welcome.
+
+* We are now using our own rmail-sup gem with fixes for Ruby 2.0.0 and
+  various warnings fixed.
+
+* sup-sync-back has been renamed to sup-sync-back-mbox to conform with
+  the other sync-back scripts.
+
+* You can now save attachments to directories without specifying the full
+  filename (default filename is used).
+
+* Various encoding fixes and minor bug fixes
+
+== 0.14.1.1 / 2013-10-29
+
+* SBU1: security release
+* Tempfiles for attachments are persistent through the sup process to
+  ensure that spawned processes have access to them.
+
+== 0.13.2.1 / 2013-10-29
+
+* SBU1: security release
+
+== 0.14.1 / 2013-08-31
+
+* Various bugfixes.
+* Predefined 'All mail' search.
+
+== 0.14.0 / 2013-08-15
+
+* CJK compatability
+* Psych over Syck
+* Ruby 1.8 deprecated
+* Thread safety
+* No more Iconv, but using built in Ruby encodings. Better UTF-8
+  handling.
+* GPGME 2.0 support
+
+== 0.13.2 / 2013-06-26
+
+* FreeBSD 10 comptability
+* More threadsafe polling
+
+== 0.13.1 / 2013-06-21
+
+* Bugfixes
+
+== 0.13.0 / 2013-05-15
+
+* Bugfixes
+* Depend on ncursesw-sup
+
 == 0.12.1 / 2011-01-23
 * Depend on ncursesw rather than ncurses (Ruby 1.9 compatibility)
 * Add sup-import-dump
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e1e6c12
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# Sup
+
+A console-based email client with the best features of GMail, mutt and
+Emacs.
+
+## Installation
+
+[See the wiki][Installation]
+
+## Features / Problems
+
+Features:
+
+* GMail-like thread-centered archiving, tagging and muting
+* [Handling mail from multiple mbox and Maildir sources][sources]
+* Blazing fast full-text search with a [rich query language][search]
+* Multiple accounts - pick the right one when sending mail
+* [Ruby-programmable hooks][hooks]
+* Automatically tracking recent contacts
+
+Current limitations:
+
+* [Ruby 2.0 support][ruby20] is very fresh, consider it experimental. Patches
+  are welcome
+
+* Sup does in general not play nicely with other mail clients, not all
+  changes can be synced back to the mail source. Refer to [Maildir Syncback][maildir-syncback]
+  in the wiki for this recently included feature. Maildir Syncback
+  allows you to sync back flag changes in messages and to write messages
+  to maildir sources.
+
+* Unix-centrism in MIME attachment handling and in sendmail invocation.
+
+## Problems
+
+Please report bugs to the [Github issue tracker](https://github.com/sup-heliotrope/sup/issues).
+
+## Links
+
+* [Homepage](http://supmua.org/)
+* [Code repository](https://github.com/sup-heliotrope/sup)
+* [Wiki](https://github.com/sup-heliotrope/sup/wiki)
+* IRC: [#sup @ freenode.net](http://webchat.freenode.net/?channels=#sup)
+* Mailing list: [sup-talk] and [sup-devel]
+
+## License
+
+```
+Copyright (c) 2013       Sup developers.
+Copyright (c) 2006--2009 William Morgan.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+```
+
+[sources]: https://github.com/sup-heliotrope/sup/wiki/Adding-sources
+[hooks]: https://github.com/sup-heliotrope/sup/wiki/Hooks
+[search]: https://github.com/sup-heliotrope/sup/wiki/Searching-your-mail
+[Installation]: https://github.com/sup-heliotrope/sup/wiki#installation
+[ruby20]: https://github.com/sup-heliotrope/sup/wiki/Development#sup-014
+[sup-talk]: http://rubyforge.org/mailman/listinfo/sup-talk
+[sup-devel]: http://rubyforge.org/mailman/listinfo/sup-devel
+[maildir-syncback]: https://github.com/sup-heliotrope/sup/wiki/Using-sup-with-other-clients
diff --git a/README.txt b/README.txt
deleted file mode 100644
index c2f0768..0000000
--- a/README.txt
+++ /dev/null
@@ -1,128 +0,0 @@
-sup
-    by William Morgan <wmorgan-sup at masanjin.net>
-    http://sup.rubyforge.org
-
-== DESCRIPTION:
-
-Sup is a console-based email client for people with a lot of email.
-It supports tagging, very fast full-text search, automatic contact-
-list management, and more. If you're the type of person who treats
-email as an extension of your long-term memory, Sup is for you.
-
-Sup makes it easy to:
-- Handle massive amounts of email.
-
-- Mix email from different sources: mbox files and Maildirs.
-
-- Instantaneously search over your entire email collection. Search over
-  body text, or use a query language to combine search predicates in any
-  way.
-
-- Handle multiple accounts. Replying to email sent to a particular
-  account will use the correct SMTP server, signature, and from address.
-
-- Add custom code to customize Sup to whatever particular and bizarre
-  needs you may have.
-
-- Organize email with user-defined labels, automatically track recent
-  contacts, and much more!
-
-The goal of Sup is to become the email client of choice for nerds
-everywhere.
-
-== FEATURES/PROBLEMS:
-
-Features:
-
-- Scalability to massive amounts of email. Immediate startup and
-  operability, regardless of how much amount of email you have.
-
-- Immediate full-text search of your entire email archive, using the
-  Xapian query language. Search over message bodies, labels, from: and
-  to: fields, or any combination thereof.
-
-- Thread-centrism. Operations are performed at the thread, not the
-  message level. Entire threads are manipulated and viewed (with
-  redundancies removed) at a time.
-
-- Labels instead of folders. Drop that tired old metaphor and you'll see
-  how much easier it is to organize email.
-
-- GMail-style thread management. Archive a thread, and it will disappear
-  from your inbox until someone replies. Kill a thread, and it will
-  never come back to your inbox (but will still show up in searches.)
-  Mark a thread as spam and you'll never again see it unless explicitly
-  searching for spam.
-
-- Console based interface. No mouse clicking required!
-
-- Programmability. It's in Ruby. The code is good. It has an extensive
-  hook system that makes it easy to extend and customize.
-
-- Multiple buffer support. Why be limited to viewing one thing at a
-  time?
-
-- Tons of other little features, like automatic context-sensitive help,
-  multi-message operations, MIME attachment viewing, recent contact list
-  generation, etc.
-
-Current limitations which will be fixed:
-
-- Sup doesn't play nicely with other mail clients. If you alter a mail
-  source (read, move, delete, etc) with another client Sup will punish
-  you with a lengthy reindexing process.
-
-- Unix-centrism in MIME attachment handling and in sendmail invocation.
-
-== SYNOPSYS:
-
-  0. sup-config
-  1. sup
-
-  Note that Sup never changes the contents of any mailboxes; it only
-  indexes in to them. So it shouldn't ever corrupt your mail. The flip
-  side is that if you change a mailbox (e.g. delete messages, or, in the
-  case of mbox files, read an unread message) then Sup will be unable to
-  load messages from that source and will ask you to run sup-sync
-  --changed.
-
-== REQUIREMENTS:
-
- - xapian-full >= 1.1.3.2
- - ncurses >= 0.9.1
- - rmail >= 0.17
- - highline
- - net-ssh
- - trollop >= 1.12
- - lockfile
- - mime-types
- - gettext
- - fastthread
-
-== INSTALL:
-
-* gem install sup
-
-== PROBLEMS:
-
-See FAQ.txt for some common problems and their solutions.
-
-== LICENSE:
-
-Copyright (c) 2006--2009 William Morgan.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301, USA.
-
diff --git a/Rakefile b/Rakefile
index d88fd8d..b830e9f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,64 +1,19 @@
-## is there really no way to make a rule for this?
-WWW_FILES = %w(www/index.html README.txt doc/Philosophy.txt doc/FAQ.txt doc/NewUserGuide.txt www/main.css)
-
-rule 'ss?.png' => 'ss?-small.png' do |t|
-end
-SCREENSHOTS = FileList["www/ss?.png"]
-SCREENSHOTS_SMALL = []
-SCREENSHOTS.each do |fn|
-  fn =~ /ss(\d+)\.png/
-  sfn = "www/ss#{$1}-small.png"
-  file sfn => [fn] do |t|
-    sh "cat #{fn} | pngtopnm | pnmscale -xysize 320 240 | pnmtopng > #{sfn}"
-  end
-  SCREENSHOTS_SMALL << sfn
-end
-
-task :upload_webpage => WWW_FILES do |t|
-  sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan at rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-task :upload_webpage_images => (SCREENSHOTS + SCREENSHOTS_SMALL) do |t|
-  sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan at rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-# vim: syntax=ruby
-# -*- ruby -*-
-task :upload_report do |t|
-  sh "ditz html ditz"
-  sh "rsync -essh -cavz ditz wmorgan at rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-$:.push "lib"
 require 'rubygems'
-require "sup-files"
-require "sup-version"
-require 'rake/gempackagetask.rb'
-
-spec = Gem::Specification.new do |s|
-  s.name = %q{sup}
-  s.version = SUP_VERSION
-  s.date = Time.now.to_s
-  s.authors = ["William Morgan"]
-  s.email = %q{wmorgan-sup at masanjin.net}
-  s.summary = %q{A console-based email client with the best features of GMail, mutt, and emacs. Features full text search, labels, tagged operations, multiple buffers, recent contacts, and more.}
-  s.homepage = %q{http://sup.rubyforge.org/}
-  s.description = %q{Sup is a console-based email client for people with a lot of email. It supports tagging, very fast full-text search, automatic contact-list management, and more. If you're the type of person who treats email as an extension of your long-term memory, Sup is for you.  Sup makes it easy to: - Handle massive amounts of email.  - Mix email from different sources: mbox files (even across different machines), Maildir directories, POP accounts, and GMail accounts.  - Instant [...]
-  s.files = SUP_FILES
-  s.executables = SUP_EXECUTABLES
+require 'rake/testtask'
 
-  s.add_dependency "xapian-full", ">= 1.2.1"
-  s.add_dependency "ncursesw"
-  s.add_dependency "rmail", ">= 0.17"
-  s.add_dependency "highline"
-  s.add_dependency "trollop", ">= 1.12"
-  s.add_dependency "lockfile"
-  s.add_dependency "mime-types", "~> 1"
-  s.add_dependency "gettext"
+Rake::TestTask.new(:test) do |test|
+  test.libs << 'test'
+  test.test_files = FileList.new('test/**/test_*.rb')
+  test.verbose = true
 end
+task :default => :test
+
+require 'rubygems/package_task'
+# For those who don't have `rubygems-bundler` installed
+load 'sup.gemspec' unless defined? Redwood::Gemspec
 
-Rake::GemPackageTask.new(spec) do |pkg|
-    pkg.need_tar = true
+Gem::PackageTask.new(Redwood::Gemspec) do |pkg|
+  pkg.need_tar = true
 end
 
-task :tarball => ["pkg/sup-#{SUP_VERSION}.tgz"]
+task :travis => [:test, :gem]
diff --git a/ReleaseNotes b/ReleaseNotes
index 69b9286..f644f9c 100644
--- a/ReleaseNotes
+++ b/ReleaseNotes
@@ -1,3 +1,74 @@
+Release 0.15.0:
+
+Maildir Syncback has been included. Refer to the wiki for more information on
+how to set it up.
+
+sup-sync-back has been moved to sup-sync-back-mbox, please make sure
+you make any needed changes.
+
+Release 0.14.1.1:
+
+See 0.13.2.1.
+
+Release 0.13.2.1:
+
+Security advisory (#SBU1) for Sup
+
+We have been notified of an potential exploit in the somewhat careless
+way Sup treats attachment metadata in received e-mails. The issues
+should now be fixed and I have released Sup 0.13.2.1 and 0.14.1.1 which
+incorporates these fixes. Please upgrade immediately and also ensure
+that your mime-decode or mime-view hooks are secure [0], [1].
+
+This is specifically related to using quotes (',") around filename or
+content_type which is already escaped using Ruby Shellwords.escape -
+this means that the string (content_type, filename) is intended to be
+used _without_ any further quotes. Please make sure that if you use
+.mailcap (non OSX systems), you do not quote the string.
+
+Credit goes to: joernchen of Phenoelit (http://phenoelit.de) who
+discovered and suggested fixes for these issues.
+
+[0] https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments
+[1] https://github.com/sup-heliotrope/sup/wiki/Secure-usage-of-Sup
+
+Release 0.14.1:
+
+Service release to 0.14.0 plus a predefined 'All mail' search.
+
+Release 0.14.0:
+
+CJK-compatability, Psych usage, thread safety, GPGME 2.0 support. Sup is now
+Ruby 1.9 based, and apart from RMail - ready for Ruby 2.0.0.
+
+Sup now uses Psych as a YAML parser (default by Ruby) and your previous
+configuration files (~/.sup/*.yaml) may need to be migrated or re-created for
+them to work with the new sup. A migration script is included for this.
+
+Check https://github.com/sup-heliotrope/sup/wiki/Migration-0.13-to-0.14 for
+the latest instructions.
+
+First back up your ~/.sup directory and index, after installing the new sup
+run:
+
+$ sup-psych-ify-config-files
+
+to migrate your files. You should now be all set for buisness.
+
+
+Release 0.13.2:
+
+FreeBSD compatability and more thread safe polling.
+
+Release 0.13.1:
+
+Another ruby 1.8 compatible release, various fixes.
+
+Release 0.13.0:
+
+Collection of bugfixes and stability fixes since 0.12.1. We now depend on our
+own ncursesw-sup fork.
+
 Release 0.12.1:
 
 This release changes the gem dependency on ncurses to ncursesw, which
diff --git a/bin/sup b/bin/sup
index ad7a0d1..1baa518 100755
--- a/bin/sup
+++ b/bin/sup
@@ -1,14 +1,11 @@
 #!/usr/bin/env ruby
+# encoding: utf-8
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
 
 require 'rubygems'
 
-no_ncursesw = false
-begin
-  require 'ncursesw'
-rescue LoadError
-  require 'ncurses'
-  no_ncursesw = true
-end
+require 'ncursesw'
 
 no_gpgme = false
 begin
@@ -19,17 +16,13 @@ end
 
 require 'fileutils'
 require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
 
 if ENV['SUP_PROFILE']
   require 'ruby-prof'
   RubyProf.start
 end
 
-if no_ncursesw
-  info "No 'ncursesw' gem detected. Install it for wide character support."
-end
-
 if no_gpgme
   info "No 'gpgme' gem detected. Install it for email encryption, decryption and signatures."
 end
@@ -112,14 +105,14 @@ end
 ## ncurses.so that's been compiled against libncursesw. (note the w.) why
 ## this works, i have no idea. much like pretty much every aspect of
 ## dealing with curses.  cargo cult programming at its best.
-##
-## BSD users: if libc.so.6 is not found, try installing compat6x.
 require 'dl/import'
+require 'rbconfig'
 module LibC
   extend DL.const_defined?(:Importer) ? DL::Importer : DL::Importable
-  setlocale_lib = case Config::CONFIG['arch']
+  setlocale_lib = case RbConfig::CONFIG['arch']
     when /darwin/; "libc.dylib"
     when /cygwin/; "cygwin1.dll"
+    when /freebsd/; "libc.so.7"
     else; "libc.so.6"
   end
 
@@ -132,9 +125,6 @@ module LibC
   rescue RuntimeError => e
     warn "cannot dlload setlocale(); ncurses wide character support probably broken."
     warn "dlload error was #{e.class}: #{e.message}"
-    if Config::CONFIG['arch'] =~ /bsd/
-      warn "BSD variant detected. You may have to install a compat6x package to acquire libc."
-    end
   end
 end
 
@@ -163,11 +153,16 @@ Index.lock_interactively or exit
 begin
   Redwood::start
   Index.load
+  Redwood::check_syncback_settings
   Index.start_sync_worker unless $opts[:no_threads]
 
   $die = false
   trap("TERM") { |x| $die = true }
-  trap("WINCH") { |x| BufferManager.sigwinch_happened! }
+  trap("WINCH") do |x|
+   ::Thread.new do
+     BufferManager.sigwinch_happened!
+   end
+  end
 
   if(s = Redwood::SourceManager.source_for DraftManager.source_name)
     DraftManager.source = s
@@ -298,7 +293,10 @@ begin
       b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
       b.mode.load_in_background if new
     when :search
-      query = BufferManager.ask :search, "Search all messages (enter for saved searches): "
+      completions = LabelManager.all_labels.map { |l| "label:#{LabelManager.string_for l}" }
+      completions = completions.each { |l| l.fix_encoding! }
+      completions += Index::COMPL_PREFIXES
+      query = BufferManager.ask_many_with_completions :search, "Search all messages (enter for saved searches): ", completions
       unless query.nil?
         if query.empty?
           bm.spawn_unless_exists("Saved searches") { SearchListMode.new }
@@ -310,7 +308,7 @@ begin
       SearchResultsMode.spawn_from_query "is:unread"
     when :list_labels
       labels = LabelManager.all_labels.map { |l| LabelManager.string_for l }
-      labels = labels.each { |l| l.force_encoding 'UTF-8' if l.methods.include?(:encoding) }
+      labels = labels.each { |l| l.fix_encoding! }
 
       user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
       unless user_label.nil?
@@ -378,7 +376,7 @@ ensure
     Index.stop_lock_update_thread
   end
 
-  HookManager.run "shutdown"
+  HookManager.run "shutdown" if HookManager.instantiated?
 
   Index.stop_sync_worker
   Redwood::finish
@@ -415,14 +413,14 @@ unless Redwood::exceptions.empty?
   end
   $stderr.puts <<EOS
 ----------------------------------------------------------------
-I'm very sorry. It seems that an error occurred in Sup. Please
-accept my sincere apologies. Please submit the contents of
+We are very sorry. It seems that an error occurred in Sup. Please
+accept our sincere apologies. Please submit the contents of
 #{BASE_DIR}/exception-log.txt and a brief report of the
-circumstances to http://masanjin.net/sup-bugs/ so that I might
-address this problem. Thank you!
+circumstances to https://github.com/sup-heliotrope/sup/issues so that
+we might address this problem. Thank you!
 
 Sincerely,
-William
+The Sup Developers
 ----------------------------------------------------------------
 EOS
   Redwood::exceptions.each do |e, name|
diff --git a/bin/sup-add b/bin/sup-add
index c77720f..49d1770 100755
--- a/bin/sup-add
+++ b/bin/sup-add
@@ -1,10 +1,12 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'uri'
 require 'rubygems'
 require 'highline/import'
 require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
 
 $opts = Trollop::options do
   version "sup-add (sup #{Redwood::VERSION})"
@@ -28,6 +30,7 @@ Options are:
 EOS
   opt :archive, "Automatically archive all new messages from these sources."
   opt :unusual, "Do not automatically poll these sources for new messages."
+  opt :sync_back, "Synchronize status flags back into messages, defaults to true (Maildir sources only).", :default => true
   opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String
   opt :force_new, "Create a new account for this source, even if one already exists."
   opt :force_account, "Reuse previously defined account user at hostname.", :type => String
@@ -97,7 +100,7 @@ begin
     source =
       case parsed_uri.scheme
       when "maildir"
-        Redwood::Maildir.new uri, !$opts[:unusual], $opts[:archive], nil, labels
+        Redwood::Maildir.new uri, !$opts[:unusual], $opts[:archive], $opts[:sync_back], nil, labels
       when "mbox"
         Redwood::MBox.new uri, !$opts[:unusual], $opts[:archive], nil, labels
       when nil
diff --git a/bin/sup-cmd b/bin/sup-cmd
deleted file mode 100755
index 8ec5c69..0000000
--- a/bin/sup-cmd
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'trollop'
-require 'sup'
-require 'sup/client'
-require 'pp'
-require 'yaml'
-include Redwood
-
-SUB_COMMANDS = %w(query count label add)
-global_opts = Trollop::options do
-  #version = "sup-cmd (sup #{Redwood::VERSION})"
-  banner <<EOS
-Connect to a running sup-server.
-
-Usage:
-  sup-cmd [global options] command [options]
-
-  Valid commands: #{SUB_COMMANDS * ', '}
-
-  Global options:
-EOS
-
-  opt :host, "server address", :type => :string, :default => 'localhost', :short => 'o'
-  opt :port, "server port", :type => :int, :default => 4300
-  opt :socket, "unix domain socket path", :type => :string, :default => nil
-  opt :verbose
-
-  conflicts :host, :socket
-  conflicts :port, :socket
-
-  stop_on SUB_COMMANDS
-end
-
-cmd = ARGV.shift
-cmd_opts = case cmd
-when "query"
-  Trollop.options do
-    opt :offset, "Offset", :default => 0, :type => :int
-    opt :limit, "Limit", :type => :int
-    opt :raw, "Retrieve raw message text", :default => false
-  end
-when "count"
-  Trollop.options do
-  end
-when "label"
-  Trollop.options do
-    opt :add_labels, "Labels to add", :default => ""
-    opt :remove_labels, "Labels to remove", :default => ""
-  end
-when "add"
-  Trollop.options do
-    opt :labels, "Labels separated by commas", :default => ""
-    opt :mbox, "Treat input files as mboxes", :default => false
-  end
-else
-  Trollop::die "unrecognized command #{cmd.inspect}"
-end
-
-class SupCmd < Redwood::Client
-  def initialize cmd, args, opts
-    @cmd = cmd
-    @opts = opts
-    @args = args
-    super()
-  end
-
-  def get_query
-    @args.first or fail "query argument required"
-  end
-
-  def connection_established
-    case @cmd
-    when "query"
-      query get_query, @opts[:offset], @opts[:limit], @opts[:raw] do |result|
-        if result
-          puts YAML.dump(result['summary'])
-          puts YAML.dump(result['raw']) if @opts[:raw]
-        else
-          close_connection
-        end
-      end
-    when "count"
-      count(get_query) do |x|
-        puts x
-        close_connection
-      end
-    when "label"
-      label get_query, @opts[:remove_labels].split(','), @opts[:add_labels].split(',') do
-        close_connection
-      end
-    when "add"
-      ARGF.binmode
-      labels = @opts[:labels].split(',')
-      get_message = lambda do
-        return ARGF.gets(nil) unless @opts[:mbox]
-        str = ""
-        l = ARGF.gets
-        str << l until ARGF.closed? || ARGF.eof? || MBox::is_break_line?(l = ARGF.gets)
-        str.empty? ? nil : str
-      end
-      i_s = i = 0
-      t = Time.now
-      while raw = get_message[]
-        i += 1
-        t_d = Time.now - t
-        if t_d >= 5
-          i_d = i - i_s
-          puts "indexed #{i} messages (#{i_d/t_d} m/s)" if global_opts[:verbose]
-          t = Time.now
-          i_s = i
-        end
-        add raw, labels do
-          close_connection
-        end
-      end
-    else
-      fail "#{@cmd} command unimplemented"
-      close_connection
-    end
-  end
-
-  def unbind
-    EM.stop
-  end
-end
-
-
-EM.run do
-  if global_opts[:socket]
-    EM.connect global_opts[:socket], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
-  else
-    EM.connect global_opts[:host], global_opts[:port], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
-  end
-end
-
-exit 0
-
diff --git a/bin/sup-config b/bin/sup-config
index c4a64a3..02bfdc1 100755
--- a/bin/sup-config
+++ b/bin/sup-config
@@ -1,8 +1,9 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'rubygems'
 require 'highline/import'
-require 'yaml'
 require 'trollop'
 require "sup"
 
@@ -20,12 +21,13 @@ EOS
 end
 
 def axe q, default=nil
-  ans = if default && !default.empty?
-    ask "#{q} (enter for \"#{default}\"): "
-  else
-    ask "#{q}: "
-  end
-  ans.empty? ? default : ans
+  question = if default && !default.empty?
+               "#{q} (enter for \"#{default}\"): "
+             else
+               "#{q}: "
+             end
+  ans = ask question
+  ans.empty? ? default : ans.to_s
 end
 
 def axe_yes q, default="n"
@@ -37,6 +39,8 @@ def build_cmd cmd
 end
 
 def add_source
+  require "sup/util/uri"
+
   type = nil
 
   say "Ok, adding a new source."
@@ -70,7 +74,7 @@ def add_source
     end
 
     uri = begin
-      URI::Generic.build components
+      Redwood::Util::Uri.build components
     rescue URI::Error => e
       say "Whoopsie! I couldn't build a URI from that: #{e.message}"
       if axe_yes("Try again?") then next else return end
@@ -84,6 +88,8 @@ def add_source
     usual = axe_yes "Does this source ever receive new messages?", "y"
     archive = usual ? axe_yes("Should new messages be automatically archived? (I.e. not appear in your inbox, though still be accessible via search.)") : false
 
+    sync_back = (type == :maildir) ? axe_yes("Should the original Maildir messages be modified to reflect changes like read status, starred messages, etc.?", "y") : false
+
     labels_str = axe("Enter any labels to be automatically added to all messages from this source, separated by spaces (or 'none')", default_labels.join(","))
 
     labels = if labels_str =~ /^\s*none\s*$/i
@@ -95,6 +101,7 @@ def add_source
     cmd = build_cmd "sup-add"
     cmd += " --unusual" unless usual
     cmd += " --archive" if archive
+    cmd += " --no-sync-back" unless sync_back
     cmd += " --labels=#{labels.join(',')}" if labels && !labels.empty?
     cmd += " #{uri}"
 
diff --git a/bin/sup-dump b/bin/sup-dump
index 4908b18..271a982 100755
--- a/bin/sup-dump
+++ b/bin/sup-dump
@@ -1,5 +1,7 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'rubygems'
 require 'xapian'
 require 'trollop'
diff --git a/bin/sup-import-dump b/bin/sup-import-dump
index 91a1721..bde1723 100755
--- a/bin/sup-import-dump
+++ b/bin/sup-import-dump
@@ -1,9 +1,11 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'uri'
 require 'rubygems'
 require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
 
 PROGRESS_UPDATE_INTERVAL = 15 # seconds
 
diff --git a/bin/sup-psych-ify-config-files b/bin/sup-psych-ify-config-files
new file mode 100755
index 0000000..c023169
--- /dev/null
+++ b/bin/sup-psych-ify-config-files
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
+require "sup"
+require "fileutils"
+
+Redwood.start
+
+fn = Redwood::SOURCE_FN
+FileUtils.cp fn, "#{fn}.syck_bak"
+
+Redwood::SourceManager.load_sources fn
+Redwood::SourceManager.save_sources fn, true
+
+Redwood.finish
diff --git a/bin/sup-recover-sources b/bin/sup-recover-sources
index 1f69f62..2d62c32 100755
--- a/bin/sup-recover-sources
+++ b/bin/sup-recover-sources
@@ -1,5 +1,7 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'optparse'
 
 $opts = {
diff --git a/bin/sup-server b/bin/sup-server
deleted file mode 100755
index aeec37c..0000000
--- a/bin/sup-server
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'trollop'
-require 'sup'
-require 'sup/server'
-require 'pp'
-require 'yaml'
-include Redwood
-
-global_opts = Trollop::options do
-  #version = "sup-cmd (sup #{Redwood::VERSION})"
-  banner <<EOS
-Interact with a Sup index.
-
-Usage:
-  sup-server [options]
-EOS
-
-  opt :host, "address to listen on", :type => :string, :default => 'localhost', :short => 'o'
-  opt :port, "port to listen on", :type => :int, :default => 4300
-  opt :verbose
-end
-
-Redwood.start
-Index.init
-Index.lock_interactively or exit
-begin
-  if(s = Redwood::SourceManager.source_for SentManager.source_uri)
-    SentManager.source = s
-  else
-    Redwood::SourceManager.add_source SentManager.default_source
-  end
-
-  Index.load
-
-  EM.run do
-    EM.start_server global_opts[:host], global_opts[:port],
-                    Redwood::Server, Index.instance
-    EM.next_tick { puts "ready" }
-  end
-
-ensure
-  Index.unlock
-end
diff --git a/bin/sup-sync b/bin/sup-sync
index b4d5cba..ee6d932 100755
--- a/bin/sup-sync
+++ b/bin/sup-sync
@@ -1,9 +1,11 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'uri'
 require 'rubygems'
 require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
 
 PROGRESS_UPDATE_INTERVAL = 15 # seconds
 
diff --git a/bin/sup-sync-back-maildir b/bin/sup-sync-back-maildir
new file mode 100755
index 0000000..e3fa7b0
--- /dev/null
+++ b/bin/sup-sync-back-maildir
@@ -0,0 +1,127 @@
+#!/usr/bin/env ruby
+# encoding: utf-8
+
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
+require 'rubygems'
+require 'trollop'
+require "sup"
+
+opts = Trollop::options do
+  version "sup-sync-back-maildir (sup #{Redwood::VERSION})"
+  banner <<EOS
+Export Xapian entries to Maildir sources on disk.
+
+This script parses the Xapian entries for a given Maildir source and renames
+(changes maildir flags) e-mail files on disk according to the labels stored in
+the index. It will export all the changes you made in Sup to your
+Maildirs so that they can be propagated to your IMAP server with e.g. offlineimap.
+
+The script also merges some Maildir flags into Sup such
+as R (replied) and P (passed, forwarded), for instance suppose you
+have an e-mail file like this: foo_bar:2,FRS (flags are favorite,
+replied, seen) and its Xapian entry has labels 'starred', the merge
+operation will add the 'replied' label to the Xapian entry.
+
+If you choose not to merge (-m) you will lose information ('replied'), and in
+the previous example the file will be renamed to foo_bar:2,FS.
+
+Running this script is *strongly* recommended when setting the
+"sync_back_to_maildir" option from false to true in config.yaml or changing the
+"sync_back" flag to true for a source in sources.yaml.
+
+Usage:
+  sup-sync-back-maildir [options] <source>*
+
+where <source>* is source URIs. If no source is given, the default behavior is
+to sync back all Maildir sources marked as usual and that have not disabled
+sync back using the configuration parameter sync_back = false in sources.yaml.
+
+Options include:
+EOS
+  opt :no_confirm, "Don't ask for confirmation before synchronizing", :default => false, :short => "n"
+  opt :no_merge, "Don't merge new supported Maildir flags (R and P)", :default => false, :short => "m"
+  opt :list_sources, "List your Maildir sources and exit", :default => false, :short => "l"
+  opt :unusual_sources_too, "Sync unusual sources too if no specific source information is given", :default => false, :short => "u"
+end
+
+def die msg
+  $stderr.puts "Error: #{msg}"
+  exit(-1)
+end
+
+Redwood::start true
+index = Redwood::Index.init
+index.lock_interactively or exit
+index.load
+
+## Force sync_back_to_maildir option otherwise nothing will happen
+$config[:sync_back_to_maildir] = true
+
+begin
+  sync_performed = []
+  sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exists? Redwood::SYNC_OK_FN
+  sources = []
+
+  ## Try to find out sources given in parameters
+  sources = ARGV.map do |uri|
+    s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
+    s.is_a?(Redwood::Maildir) or die "#{uri} is not a Maildir source."
+    s.sync_back_enabled? or die "#{uri} has disabled sync back - check your configuration."
+    s
+  end unless opts[:list_sources]
+
+  ## Otherwise, check all sources in sources.yaml
+  if sources.empty? or opts[:list_sources] == true
+    if opts[:unusual_sources_too]
+      sources = Redwood::SourceManager.sources.select do |s|
+        s.is_a? Redwood::Maildir and s.sync_back_enabled?
+      end
+    else
+      sources = Redwood::SourceManager.usual_sources.select do |s|
+        s.is_a? Redwood::Maildir and s.sync_back_enabled?
+      end
+    end
+  end
+
+  if opts[:list_sources] == true
+    sources.each do |s|
+      puts "id: #{s.id}, uri: #{s.uri}"
+    end
+  else
+    sources.each do |s|
+      if opts[:no_confirm] == false
+        print "Are you sure you want to synchronize '#{s.uri}'? (Y/n) "
+        next if STDIN.gets.chomp.downcase == 'n'
+      end
+
+      infos = Enumerator.new(index, :each_source_info, s.id).to_a
+      counter = 0
+      infos.each do |info|
+        print "\rSynchronizing '#{s.uri}'... #{((counter += 1)/infos.size.to_f*100).to_i}%"
+        index.each_message({:location => [s.id, info]}, false) do |m|
+          if opts[:no_merge] == false
+            m.merge_labels_from_locations [:replied, :forwarded]
+          end
+
+          if Redwood::Index.message_joining_killed? m
+            m.labels += [:killed]
+          end
+
+          index.save_message m
+        end
+      end
+      print "\n"
+      sync_performed << s.uri
+    end
+    ## Write a flag file to tell sup that the synchronization has been performed
+    File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(sync_performed.join("\n")) }
+  end
+rescue Exception => e
+  File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
+  raise
+ensure
+  index.save_index
+  Redwood::finish
+  index.unlock
+end
diff --git a/bin/sup-sync-back b/bin/sup-sync-back-mbox
similarity index 96%
rename from bin/sup-sync-back
rename to bin/sup-sync-back-mbox
index 5d84cba..16df7e9 100755
--- a/bin/sup-sync-back
+++ b/bin/sup-sync-back-mbox
@@ -1,10 +1,12 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'rubygems'
 require 'uri'
 require 'tempfile'
 require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
 
 fail "not working yet"
 
@@ -22,7 +24,7 @@ def has_any_from_source_with_label? index, source, label
 end
 
 opts = Trollop::options do
-  version "sup-sync-back (sup #{Redwood::VERSION})"
+  version "sup-sync-back-mbox (sup #{Redwood::VERSION})"
   banner <<EOS
 Drop or move messages from Sup sources that are marked as deleted or
 spam in the Sup index.
@@ -30,7 +32,7 @@ spam in the Sup index.
 Currently only works with mbox sources.
 
 Usage:
-  sup-sync-back [options] <source>*
+  sup-sync-back-mbox [options] <source>*
 
 where <source>* is zero or more source URIs. If no sources are given,
 sync back all usual sources.
@@ -110,7 +112,7 @@ EOS
     source.reset!
     num_dropped = num_moved = num_scanned = 0
 
-    out_fp = Tempfile.new "sup-sync-back-#{source.id}"
+    out_fp = Tempfile.new "sup-sync-back-mbox-#{source.id}"
     Redwood::PollManager.each_message_from source do |m|
       num_scanned += 1
 
diff --git a/bin/sup-tweak-labels b/bin/sup-tweak-labels
index 076c802..36947d5 100755
--- a/bin/sup-tweak-labels
+++ b/bin/sup-tweak-labels
@@ -1,8 +1,10 @@
 #!/usr/bin/env ruby
 
+$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
+
 require 'rubygems'
 require 'trollop'
-require "sup"; Redwood::check_library_version_against "git"
+require "sup"
 
 class Float
   def to_s; sprintf '%.2f', self; end
diff --git a/bugs/issue-0240b36671ecb019e57ef27e0901bff055385371.yaml b/bugs/issue-0240b36671ecb019e57ef27e0901bff055385371.yaml
deleted file mode 100644
index e5565ce..0000000
--- a/bugs/issue-0240b36671ecb019e57ef27e0901bff055385371.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: messages with unparseable date headers are being discarded entirely
-desc: it's better to forge the date headers and keep the messages
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:57:25.399978 Z
-references: []
-
-id: 0240b36671ecb019e57ef27e0901bff055385371
-log_events: 
-- - 2008-03-07 03:57:25.400014 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:57:29.249827 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml b/bugs/issue-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml
deleted file mode 100644
index 3c4be72..0000000
--- a/bugs/issue-08d6bae05fa885bf6fcae39f864eb923c1e9a79e.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: reply-from hook
-desc: "hook for setting the from: address of a reply programmatically"
-type: :feature
-component: hooks
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-19 17:58:26.142289 Z
-references: []
-
-id: 08d6bae05fa885bf6fcae39f864eb923c1e9a79e
-log_events: 
-- - 2008-06-19 17:58:27.334371 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-06-19 17:58:40.526270 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch reply-from-hook, merged into next
-- - 2008-07-30 23:41:56.898257 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed with disposition fixed
-  - merged into master
-git_branch: reply-from-hook
diff --git a/bugs/issue-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml b/bugs/issue-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml
deleted file mode 100644
index 94aa231..0000000
--- a/bugs/issue-09479a2ada22c2a0d76427e12ef2514d4753d070.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: forwarded messages should be threaded under original
-desc: ""
-type: :feature
-component: threading
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-06-07 23:45:11.606455 Z
-references: []
-
-id: 09479a2ada22c2a0d76427e12ef2514d4753d070
-log_events: 
-- - 2008-06-07 23:45:12.746568 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml b/bugs/issue-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml
deleted file mode 100644
index 3c780b9..0000000
--- a/bugs/issue-15738247f939d20f8f202f80ccb85d9ad92101e0.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: command to reload hooks
-desc: useful for debugging hooks without having to restart sup each time
-type: :feature
-component: hooks
-release: 
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-09 06:09:46.127686 Z
-references: []
-
-id: 15738247f939d20f8f202f80ccb85d9ad92101e0
-log_events: 
-- - 2008-05-09 06:09:46.803222 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-182841e15d6909892adf43678bae03597ce10519.yaml b/bugs/issue-182841e15d6909892adf43678bae03597ce10519.yaml
deleted file mode 100644
index b680b9d..0000000
--- a/bugs/issue-182841e15d6909892adf43678bae03597ce10519.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: external mime viewing logic not quite right
-desc: |-
-  weird things happen depending on the specifics on whether you have
-  certain binaries in your path, or you implement the mime-view hook,
-  you can get very weird behavior
-type: :bugfix
-component: hooks
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:05:53.956188 Z
-references: []
-
-id: 182841e15d6909892adf43678bae03597ce10519
-log_events: 
-- - 2008-03-07 04:05:53.956222 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:06:02.239869 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml b/bugs/issue-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml
deleted file mode 100644
index 87d0472..0000000
--- a/bugs/issue-1a1527438c2d198eae9a264ce9e6b847854d9837.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: attachment name searchability
-desc: ""
-type: :feature
-component: indexing
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 03:53:44.524558 Z
-references: []
-
-id: 1a1527438c2d198eae9a264ce9e6b847854d9837
-log_events: 
-- - 2008-05-25 03:53:45.177580 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-05-25 03:54:05.978412 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch attachments, merged into next
-- - 2008-05-31 17:09:04.254381 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - commented
-  - see {issue 65506670167642cc581956bc1b25c26b5bff215b} and {issue 7a68c1e7120a8540c7c51c6095f4815918d16641}
-- - 2008-06-19 18:20:41.656527 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml b/bugs/issue-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml
deleted file mode 100644
index 38364d9..0000000
--- a/bugs/issue-2312263b6a2b7de6ae1ec4ab315c7829763e61be.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: sup-sync-back "nothing to do" error message not informative
-desc: should tell the user that one of the four magic options are required
-type: :bugfix
-component: sup-sync-back
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:37:41.693484 Z
-references: []
-
-id: 2312263b6a2b7de6ae1ec4ab315c7829763e61be
-log_events: 
-- - 2008-03-07 03:37:41.693520 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:37:46.110644 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-23658477a445c2e61405fecb4cb641a2298caba6.yaml b/bugs/issue-23658477a445c2e61405fecb4cb641a2298caba6.yaml
deleted file mode 100644
index 91e661a..0000000
--- a/bugs/issue-23658477a445c2e61405fecb4cb641a2298caba6.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: wide character ncurses support
-desc: ""
-type: :feature
-component: curses
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :in_progress
-disposition: 
-creation_time: 2008-04-22 22:43:23.153185 Z
-references: []
-
-id: 23658477a445c2e61405fecb4cb641a2298caba6
-log_events: 
-- - 2008-04-22 22:43:24.808717 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-22 22:45:52.511820 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - |-
-    Branch 'ncurses-widechar' has been merged into next.
-    
-    Branch "ncursesw" now has a copy of the ncurses 0.9.2 gem with wide character
-    modifications, and a script "run-this-for-sup.sh" to build and install it
-    (assuming you're running from git, of course.)
diff --git a/bugs/issue-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml b/bugs/issue-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml
deleted file mode 100644
index df14160..0000000
--- a/bugs/issue-2673f091c15dd90222a59621a1842d4ef0a743f7.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: make sup-sync-back work on IMAP folders
-desc: ""
-type: :feature
-component: sup-sync-back
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-10-14 01:14:08.690909 Z
-references: []
-
-id: 2673f091c15dd90222a59621a1842d4ef0a743f7
-log_events: 
-- - 2008-10-14 01:14:09.898338 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-git_branch: 
diff --git a/bugs/issue-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml b/bugs/issue-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml
deleted file mode 100644
index 8aab1df..0000000
--- a/bugs/issue-2a0363cdf9d25edfa2a04b21299a538365e8b319.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: gpg mode hook
-desc: |-
-  need a hook for controlling the default setting of the gpg mode (none,
-  sign, sign & encrypt) in reply-mode, based on the gpg mode of the original
-  message.
-type: :feature
-component: hooks
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 02:42:29.391022 Z
-references: []
-
-id: 2a0363cdf9d25edfa2a04b21299a538365e8b319
-log_events: 
-- - 2008-03-07 02:42:29.391058 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-20 21:18:14.263736 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - assigned to release 0.6 from 0.5
-  - ""
-- - 2008-07-31 00:54:02.960978 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-git_branch: 
diff --git a/bugs/issue-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml b/bugs/issue-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml
deleted file mode 100644
index 87252e4..0000000
--- a/bugs/issue-2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: oldest-first thread ordering
-desc: ""
-type: :feature
-component: indexing
-release: 
-reporter: Matt Liggett <mml at pobox.com>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-14 18:33:01.603318 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001271.html
-id: 2e74aa6843feee4daefe740b6e3f1fc54ff4bfcb
-log_events: 
-- - 2008-03-14 18:33:01.603569 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-14 18:33:31.116057 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
diff --git a/bugs/issue-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml b/bugs/issue-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml
deleted file mode 100644
index 431a522..0000000
--- a/bugs/issue-314f0cdac8d1998c46759a4ebef9077999bcef09.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: new user welcome screen the first time you start up
-desc: ""
-type: :feature
-component: curses
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 02:44:24.399133 Z
-references: []
-
-id: 314f0cdac8d1998c46759a4ebef9077999bcef09
-log_events: 
-- - 2008-03-07 02:44:24.399167 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml b/bugs/issue-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml
deleted file mode 100644
index cde47bd..0000000
--- a/bugs/issue-3408c200a5f47f92d12b5c063a00ce891c2ba4ce.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: "email header parsing: space doesn't need to follow colon"
-desc: ""
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:31:55.733379 Z
-references: []
-
-id: 3408c200a5f47f92d12b5c063a00ce891c2ba4ce
-log_events: 
-- - 2008-03-07 04:31:55.733416 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:31:59.580856 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml b/bugs/issue-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml
deleted file mode 100644
index bd3c0fe..0000000
--- a/bugs/issue-3441fb8b7f955d625633d06fa0bf67a9afab046e.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: saving a message as a draft drops attachments
-desc: ""
-type: :bugfix
-component: sup
-release: 
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-25 02:14:13.362087 Z
-references: []
-
-id: 3441fb8b7f955d625633d06fa0bf67a9afab046e
-log_events: 
-- - 2008-05-25 02:14:14.224040 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-38d6f805b0c8bad013ec73f56e6245c890528591.yaml b/bugs/issue-38d6f805b0c8bad013ec73f56e6245c890528591.yaml
deleted file mode 100644
index 635bd65..0000000
--- a/bugs/issue-38d6f805b0c8bad013ec73f56e6245c890528591.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: "'m' in edit-message-mode should prompt for a to: with a default"
-desc: |-
-  the current behavior is to just go ahead and compose the message, which
-  is irritating if you're just trying to compose a message incidental to
-  having highlighted someone's email address.
-type: :bugfix
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 02:37:38.905689 Z
-references: []
-
-id: 38d6f805b0c8bad013ec73f56e6245c890528591
-log_events: 
-- - 2008-03-07 02:37:38.905723 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-20 21:44:06.473431 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - assigned to release 0.6 from 0.5
-  - ""
-- - 2008-05-25 03:47:42.600153 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - fixed in master
diff --git a/bugs/issue-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml b/bugs/issue-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml
deleted file mode 100644
index 07ca173..0000000
--- a/bugs/issue-3b25f1d56b9be533edaf232b9e60dc24e00cba0b.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: maildir speedups
-desc: caching mtimes, using dir mtimes as an upper bound on file mtimes
-type: :feature
-component: maildir
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 02:30:42.010965 Z
-references: []
-
-id: 3b25f1d56b9be533edaf232b9e60dc24e00cba0b
-log_events: 
-- - 2008-05-25 02:30:42.815974 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-05-25 02:30:59.062438 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch maildir-speedups. merged into next.
-- - 2008-06-19 18:09:27.239553 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml b/bugs/issue-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml
deleted file mode 100644
index ba95c00..0000000
--- a/bugs/issue-42ab0840f9a1924f1c0561e8ddcf7e6988543ba0.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: query normalization breaking disjunctive queries, date modifers, etc
-desc: ""
-type: :bugfix
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 03:39:26.683059 Z
-references: []
-
-id: 42ab0840f9a1924f1c0561e8ddcf7e6988543ba0
-log_events: 
-- - 2008-03-07 03:39:26.683093 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:50:16.796313 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - ""
-- - 2008-04-20 22:10:39.075075 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-46df983ccdb75408a37b3911472d4015664a3cf6.yaml b/bugs/issue-46df983ccdb75408a37b3911472d4015664a3cf6.yaml
deleted file mode 100644
index 5358756..0000000
--- a/bugs/issue-46df983ccdb75408a37b3911472d4015664a3cf6.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: curses interface generally sluggish
-desc: |-
-  moving cursors around, etc is sluggish. should be faster. will require
-  profiling. might require dipping into the C level.
-type: :bugfix
-component: curses
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-19 23:38:35.608104 Z
-references: []
-
-id: 46df983ccdb75408a37b3911472d4015664a3cf6
-log_events: 
-- - 2008-05-19 23:38:36.229747 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-47aab6443b6c107c3067cdb614186099db570acf.yaml b/bugs/issue-47aab6443b6c107c3067cdb614186099db570acf.yaml
deleted file mode 100644
index ced024b..0000000
--- a/bugs/issue-47aab6443b6c107c3067cdb614186099db570acf.yaml
+++ /dev/null
@@ -1,37 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: before-add-hook not applied to sent messages
-desc: |-
-  they're not being polled in the regular way but in a vestigal irregular
-  way
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:54:10.773413 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001259.html
-- http://rubyforge.org/pipermail/sup-talk/2008-February/001203.html
-id: 47aab6443b6c107c3067cdb614186099db570acf
-log_events: 
-- - 2008-03-07 03:54:10.773449 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:54:15.699906 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - ""
-- - 2008-03-08 22:02:45.840451 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from in_progress to fixed
-  - merged down to master.
-- - 2008-03-08 22:16:00.346710 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
-- - 2008-03-08 22:22:02.528552 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 2
-  - ""
diff --git a/bugs/issue-4af242013994ae557e431ba350a92c4f9e1739ef.yaml b/bugs/issue-4af242013994ae557e431ba350a92c4f9e1739ef.yaml
deleted file mode 100644
index db4b825..0000000
--- a/bugs/issue-4af242013994ae557e431ba350a92c4f9e1739ef.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: sup-sync shouldn't save the index and sources if an error occurred
-desc: |-
-  If the error was caused by a particular message, saving the source file
-  will move the pointer past the message, so it will never get added.
-type: :bugfix
-component: sup-sync
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:14:07.913103 Z
-references: []
-
-id: 4af242013994ae557e431ba350a92c4f9e1739ef
-log_events: 
-- - 2008-03-07 04:14:07.913140 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:14:12.599051 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
-- - 2008-03-09 18:29:51.789364 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed description
-  - ""
diff --git a/bugs/issue-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml b/bugs/issue-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml
deleted file mode 100644
index 2761c60..0000000
--- a/bugs/issue-4daa2721dac8dfeb8730ee081f73b6c62702bd3e.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: last message of every source is returned twice during polling
-desc: http://rubyforge.org/pipermail/sup-talk/2008-April/001358.html
-type: :bugfix
-component: sup
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-01 01:11:50.650800 Z
-references: []
-
-id: 4daa2721dac8dfeb8730ee081f73b6c62702bd3e
-log_events: 
-- - 2008-05-01 01:11:51.434589 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-07-31 00:54:39.589377 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-git_branch: 
diff --git a/bugs/issue-4e501973cea5bd1f28739ae4cea98edce8249895.yaml b/bugs/issue-4e501973cea5bd1f28739ae4cea98edce8249895.yaml
deleted file mode 100644
index 1c3d0a4..0000000
--- a/bugs/issue-4e501973cea5bd1f28739ae4cea98edce8249895.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: thread joining is not preserved when reindexing
-desc: |
-  the current thread joining just adds references to the index entries. that's
-  fine but if the messages are reindexed, the references obviously won't be
-  there.
-  i think we need to add some separate blob of information somewhere that
-  maintains these references, which sup-sync is aware of.
-  
-  if we're going down the bdb route for state preservation, that might be an
-  obvious place to put this too, because it's essentially a hashtable keyed on
-  message ids.
-
-type: :bugfix
-component: indexing
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-14 18:39:59.697902 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001270.html
-id: 4e501973cea5bd1f28739ae4cea98edce8249895
-log_events: 
-- - 2008-03-14 18:39:59.698163 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-14 18:40:26.559418 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
diff --git a/bugs/issue-5348fec2b1112250e241afc7467de29e5691d1be.yaml b/bugs/issue-5348fec2b1112250e241afc7467de29e5691d1be.yaml
deleted file mode 100644
index 64d84a2..0000000
--- a/bugs/issue-5348fec2b1112250e241afc7467de29e5691d1be.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: imap header caching
-desc: |-
-  imap headers aren't cached at all. that would speed up the initial
-  connection, at least for servers that didn't set uid_validity to
-  the current time (blearf).
-type: :feature
-component: imap
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 04:26:31.474463 Z
-references: []
-
-id: 5348fec2b1112250e241afc7467de29e5691d1be
-log_events: 
-- - 2008-03-07 04:26:31.474497 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-57668c69d0190d6e849309834d4ad1d215efa779.yaml b/bugs/issue-57668c69d0190d6e849309834d4ad1d215efa779.yaml
deleted file mode 100644
index fcb39c1..0000000
--- a/bugs/issue-57668c69d0190d6e849309834d4ad1d215efa779.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: make sup-sync-back work on Maildir folders
-desc: |-
-  possibly we could abstract things entirely between mbox and maildir, but
-  it might just be easiest to have a sup-sync-back-maildir or a big if
-  statement.
-type: :feature
-component: sup-sync-back
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-06-12 19:24:51.772444 Z
-references: []
-
-id: 57668c69d0190d6e849309834d4ad1d215efa779
-log_events: 
-- - 2008-06-12 19:24:53.668373 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml b/bugs/issue-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml
deleted file mode 100644
index 8f774aa..0000000
--- a/bugs/issue-5795c3c1b47e88f7261f57f31d33fe15ad08465d.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: flat (gmail-style) version of thread-view-mode
-desc: |-
-  make thread-view-mode show a flat, chronological list of messages
-  instead of a tree, based on some configuration variable.
-type: :feature
-component: curses
-release: 
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-25 02:38:06.098950 Z
-references: []
-
-id: 5795c3c1b47e88f7261f57f31d33fe15ad08465d
-log_events: 
-- - 2008-05-25 02:38:06.823848 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml b/bugs/issue-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml
deleted file mode 100644
index 7c3c725..0000000
--- a/bugs/issue-5fab957dcd16f1da8962fe5b1f3a58d970315deb.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: extra-contact-addresses hook for lbdb (etc.) integration
-desc: |-
-  add an extra-contact-addresses hook for inserting addresses into the
-  tab-completion list for To:, Cc:, etc. entries.
-type: :feature
-component: hooks
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-20 20:43:06.972853 Z
-references: []
-
-id: 5fab957dcd16f1da8962fe5b1f3a58d970315deb
-log_events: 
-- - 2008-04-20 20:43:08.667355 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-20 20:43:20.678566 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-60d86dd32054533a6206f698033ec668af6a7574.yaml b/bugs/issue-60d86dd32054533a6206f698033ec668af6a7574.yaml
deleted file mode 100644
index 2830fff..0000000
--- a/bugs/issue-60d86dd32054533a6206f698033ec668af6a7574.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: killed threads showing up in inbox-mode
-desc: this goddamn problem is recurring
-type: :bugfix
-component: indexing
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-25 19:28:51.369257 Z
-references: []
-
-id: 60d86dd32054533a6206f698033ec668af6a7574
-log_events: 
-- - 2008-04-25 19:28:52.476687 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-07-31 00:54:38.916308 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-- - 2008-11-21 14:23:17.566852 Z
-  - Nicolas Pouillard <nicolas.pouillard at gmail.com>
-  - closed with disposition fixed
-  - |-
-    Loading options was not given to load_thread_for_message in
-    ThreadIndexMode.add_or_unhide.
-git_branch: 
diff --git a/bugs/issue-61949ec83770b5d46f89eff21799968187012cce.yaml b/bugs/issue-61949ec83770b5d46f89eff21799968187012cce.yaml
deleted file mode 100644
index fb3df1a..0000000
--- a/bugs/issue-61949ec83770b5d46f89eff21799968187012cce.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: \127 should be handled like backspace (it's the 70's all over again)
-desc: ""
-type: :bugfix
-component: curses
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:29:46.043812 Z
-references: []
-
-id: 61949ec83770b5d46f89eff21799968187012cce
-log_events: 
-- - 2008-03-07 04:29:46.043850 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:29:52.233706 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-65506670167642cc581956bc1b25c26b5bff215b.yaml b/bugs/issue-65506670167642cc581956bc1b25c26b5bff215b.yaml
deleted file mode 100644
index e43d6af..0000000
--- a/bugs/issue-65506670167642cc581956bc1b25c26b5bff215b.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: attachment markers in thread-index-mode
-desc: show a little @ if the message has an attachment
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 03:53:10.005404 Z
-references: []
-
-id: 65506670167642cc581956bc1b25c26b5bff215b
-log_events: 
-- - 2008-05-25 03:53:10.994290 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-05-25 03:53:26.388023 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch attachments, merged into next
-- - 2008-05-31 17:09:17.710631 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - commented
-  - see {issue 1a1527438c2d198eae9a264ce9e6b847854d9837} and {issue 7a68c1e7120a8540c7c51c6095f4815918d16641}
-- - 2008-06-19 18:20:33.912937 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml b/bugs/issue-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml
deleted file mode 100644
index 26ce1e8..0000000
--- a/bugs/issue-658389418b5f0038cc3e6bc20fd3fd1566eb7111.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: add a startup hook
-desc: ""
-type: :feature
-component: hooks
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 04:03:52.999956 Z
-references: []
-
-id: 658389418b5f0038cc3e6bc20fd3fd1566eb7111
-log_events: 
-- - 2008-03-07 04:03:52.999992 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:03:56.880347 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - ""
-- - 2008-04-20 20:45:20.047253 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml b/bugs/issue-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml
deleted file mode 100644
index 6a6f488..0000000
--- a/bugs/issue-69f785cddcc6e09ef0a357151373b3aa923d5e3f.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: escape filenames in call to run-mailcap
-desc: otherwise, filenames with spaces don't work
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:10:43.771843 Z
-references: []
-
-id: 69f785cddcc6e09ef0a357151373b3aa923d5e3f
-log_events: 
-- - 2008-03-07 04:10:43.771878 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:10:48.789189 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml b/bugs/issue-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml
deleted file mode 100644
index bbdf1c6..0000000
--- a/bugs/issue-6c053cca2eb05af486a2d09c6772fd5bd0cca444.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: cache threading
-desc: |-
-  thread information should be cached so that it doesn't have to be
-  recomputed each time.
-type: :feature
-component: threading
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-19 23:39:19.716625 Z
-references: []
-
-id: 6c053cca2eb05af486a2d09c6772fd5bd0cca444
-log_events: 
-- - 2008-05-19 23:39:20.190260 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml b/bugs/issue-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml
deleted file mode 100644
index 564b3b1..0000000
--- a/bugs/issue-6e0d634de74b2eb8297174ecd408b3810ba9351b.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: imap connection sharing
-desc: |
-  current behavior is a separate connection for each folder, which is kinda
-  silly.
-  potentially want to still keep a separate connection for polling, but that
-  might be micro-optimization, especially given that the whole ruby imap
-  library seems quite slow.
-
-type: :feature
-component: imap
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 04:25:12.351934 Z
-references: []
-
-id: 6e0d634de74b2eb8297174ecd408b3810ba9351b
-log_events: 
-- - 2008-03-07 04:25:12.351966 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml b/bugs/issue-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml
deleted file mode 100644
index 9656537..0000000
--- a/bugs/issue-6e7960514f66ee67da083bc7bb5632d5808fc607.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: "'!!' will load all threads in current search"
-desc: |-
-  can be dangerous, but sometimes you know there aren't a million and you
-  just want them all loaded, e.g. to apply some mass tagging operation.
-  with the cancel-search feature, can always be canceled if
-  onerous.
-type: :feature
-component: sup
-release: "0.5"
-reporter: Marcus Williams <marcus-sup at bar-coded.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:17:44.706909 Z
-references: []
-
-id: 6e7960514f66ee67da083bc7bb5632d5808fc607
-log_events: 
-- - 2008-03-07 04:17:44.706948 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:17:48.834972 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
-- - 2008-03-11 06:52:15.604233 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed description
-  - ""
diff --git a/bugs/issue-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml b/bugs/issue-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml
deleted file mode 100644
index 915eb68..0000000
--- a/bugs/issue-7456c2d8fbd5de4dac651f6f4e9756f577497e01.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: edit-as-new doesn't preserve replyto and references headers
-desc: ""
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-31 17:07:05.126884 Z
-references: []
-
-id: 7456c2d8fbd5de4dac651f6f4e9756f577497e01
-log_events: 
-- - 2008-05-31 17:07:07.008637 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
-- - 2008-05-31 17:07:22.611383 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - changed status from unstarted to in_progress
-  - in branch edit-as-new-fix, merged into next
-- - 2008-06-19 18:22:24.036557 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml b/bugs/issue-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml
deleted file mode 100644
index e219937..0000000
--- a/bugs/issue-76802330c4fdd091e8b1dd08dcc29ed432f003d4.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: in-buffer search always shifts the screen, even when unnecessary
-desc: ""
-type: :bugfix
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-01 22:34:01.983057 Z
-references: []
-
-id: 76802330c4fdd091e8b1dd08dcc29ed432f003d4
-log_events: 
-- - 2008-05-01 22:34:02.958758 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-05-01 22:34:24.254778 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch find-in-buffer-fix, merged into next
-- - 2008-05-25 04:19:45.168367 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml b/bugs/issue-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml
deleted file mode 100644
index f1f2397..0000000
--- a/bugs/issue-799771a6a435dcad66dc80e7e051d91d24d005b1.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: IMAP server restart crashes sup
-desc: |-
-  this very bizarre backtrace:
-  
-  --- SystemExit from thread: main
-  closed stream
-  /usr/lib/ruby/1.8/openssl/buffering.rb:237:in `select'
-  ./lib/sup/buffer.rb:31:in `nonblocking_getch'
-  bin/sup:227
-  
-  wtf?
-  
-  There's no reason that
-  nonblocking_getch would be calling the openssl stuff, and openssl's
-  buffering.rb doesn't mention select at all. Weird.
-type: :bugfix
-component: imap
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-19 23:17:32.271870 Z
-references: []
-
-id: 799771a6a435dcad66dc80e7e051d91d24d005b1
-log_events: 
-- - 2008-05-19 23:17:33.615525 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml b/bugs/issue-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml
deleted file mode 100644
index 3892dc7..0000000
--- a/bugs/issue-7a68c1e7120a8540c7c51c6095f4815918d16641.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: attachment markers in thread-view-mode
-desc: |-
-  i'd like to see them in thread-view-mode as well
-  (c.f. {issue 65506670167642cc581956bc1b25c26b5bff215b} and {issue 1a1527438c2d198eae9a264ce9e6b847854d9837})
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-31 17:08:28.081944 Z
-references: []
-
-id: 7a68c1e7120a8540c7c51c6095f4815918d16641
-log_events: 
-- - 2008-05-31 17:08:28.710190 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
-- - 2008-05-31 17:08:50.679595 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - changed status from unstarted to in_progress
-  - on branch attachments as well, remerged into next
-- - 2008-06-19 18:20:56.657318 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-7c77e757321c2639daea013824ad1a14099815b1.yaml b/bugs/issue-7c77e757321c2639daea013824ad1a14099815b1.yaml
deleted file mode 100644
index ecc1ae8..0000000
--- a/bugs/issue-7c77e757321c2639daea013824ad1a14099815b1.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: in-buffer searches should move buffer horizontally when necessary
-desc: ""
-type: :feature
-component: curses
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 03:56:17.653639 Z
-references: []
-
-id: 7c77e757321c2639daea013824ad1a14099815b1
-log_events: 
-- - 2008-03-07 03:56:17.653674 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:56:21.870750 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - ""
-- - 2008-04-20 20:44:07.668281 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml b/bugs/issue-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml
deleted file mode 100644
index a2906d8..0000000
--- a/bugs/issue-7d8474dfeeefaa50151c3ce48bee6b686d36a216.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: reply-to mode hook
-desc: |-
-  need a hook for selecting the default setting of the reply-to
-  horizontal selector
-type: :feature
-component: hooks
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-04 03:04:10.253690 Z
-references: []
-
-id: 7d8474dfeeefaa50151c3ce48bee6b686d36a216
-log_events: 
-- - 2008-06-04 03:04:10.945071 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-06-04 03:04:21.284329 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch 'reply-to-hook', merged into next
-- - 2008-07-30 23:41:50.393799 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed with disposition fixed
-  - merged into master
-git_branch: reply-to-hook
diff --git a/bugs/issue-829b449c51fca9a39047d00fabc552cc110c69b2.yaml b/bugs/issue-829b449c51fca9a39047d00fabc552cc110c69b2.yaml
deleted file mode 100644
index e17e213..0000000
--- a/bugs/issue-829b449c51fca9a39047d00fabc552cc110c69b2.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: don't crash when people.txt is corrupted
-desc: |-
-  This was a debug check, but if Sup crashes when writing out people.txt,
-  this will prevent it from ever starting again!
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:07:56.208521 Z
-references: []
-
-id: 829b449c51fca9a39047d00fabc552cc110c69b2
-log_events: 
-- - 2008-03-07 04:07:56.208558 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:21:04.553157 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml b/bugs/issue-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml
deleted file mode 100644
index 86be9e0..0000000
--- a/bugs/issue-82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: gpg generates invalid signature when :edit_signature is on
-desc: |-
-  when :edit_signature is on and there's a signature, gpg signatures are invalid,
-  ccording to mutt and other clients. sup itself thinks they're fine.
-type: :bugfix
-component: crypto
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-19 21:19:23.267668 Z
-references: []
-
-id: 82c80f6dc2ce7b10b9e8f503d68253ced0ee8a1b
-log_events: 
-- - 2008-05-19 21:19:24.611826 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-05-19 21:19:49.649415 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - turns out it was a newline issue. patch directly applied to master.
diff --git a/bugs/issue-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml b/bugs/issue-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml
deleted file mode 100644
index b91de0d..0000000
--- a/bugs/issue-8a5cf9242ca60fa6c81091e425f734b4fb03e41a.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: index speedup
-desc: |
-  I've just merged in a changeset that makes Sup store message body
-  content in the Ferret index. (They've always been indexed, but now
-  they're stored as well.) This means that changing the labels on a
-  message can be a copy operation of the previous Ferret document, rather
-  than requiring downloading and parsing the original message to create a
-  new Ferret document.
-  So, this should have two effects:
-  
-  1. The Ferret index size will expand by about 50%. Sorry.
-  2. Tweaking message labels should be much, much faster, since the
-  message no longer has to be downloaded from the source in order to
-  change the labels. If you've ever tried to label a large IMAP thread,
-  you no longer have to wait 5 minutes just to save. :)
-  
-  The index size increase is unfortunate, but it's something that has to
-  happen anyways if we want search-results-mode to have matching text in
-  the snippets, which is in the future TODO.
-  
-  The change was made in such a way that it's incrementally applied
-  whenever a message is saved or changed in the Ferret index. So, if you
-  want the above behavior on all messages immediately, you must do
-  sup-sync --all on a source (which will require downloading each
-  message). Otherwise, you will get the slow behavior (message body needs
-  to be downloaded from the source) the first time you save a message
-  after merging this change, and the fast behavior (no downloading
-  required) on all subsequent times.
-
-type: :feature
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 02:48:23.885656 Z
-references: []
-
-id: 8a5cf9242ca60fa6c81091e425f734b4fb03e41a
-log_events: 
-- - 2008-03-07 02:48:23.885693 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 02:48:50.979828 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - ""
-- - 2008-04-20 22:10:33.970635 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-8aa7ea95f066fd0668452093b85903bd142905c9.yaml b/bugs/issue-8aa7ea95f066fd0668452093b85903bd142905c9.yaml
deleted file mode 100644
index 59c41c0..0000000
--- a/bugs/issue-8aa7ea95f066fd0668452093b85903bd142905c9.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: "'q' asks and 'Q' quits without asking"
-desc: ""
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-20 03:25:17.109472 Z
-references: []
-
-id: 8aa7ea95f066fd0668452093b85903bd142905c9
-log_events: 
-- - 2008-05-20 03:25:19.265580 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
-- - 2008-05-25 02:13:32.219668 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - closed issue with disposition fixed
-  - fixed in master
-- - 2008-05-25 02:13:40.344453 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - assigned to release 0.6 from unassigned
-  - ""
diff --git a/bugs/issue-8c0e627c500f679badca28f60ba76998fd65d46a.yaml b/bugs/issue-8c0e627c500f679badca28f60ba76998fd65d46a.yaml
deleted file mode 100644
index f31067e..0000000
--- a/bugs/issue-8c0e627c500f679badca28f60ba76998fd65d46a.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: workaround for rubymail quoting bug in pgp MIME header
-desc: gpg MIME headers are being double-quoted due to a rubymail bug
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: Jan Spakula <teatime at gmx.com>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:35:36.731751 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-February/001222.html
-id: 8c0e627c500f679badca28f60ba76998fd65d46a
-log_events: 
-- - 2008-03-07 03:35:36.731787 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:36:24.938159 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
-- - 2008-03-08 22:22:59.515414 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
diff --git a/bugs/issue-8e825caee33a6ac144580bf44d0d3060ad162394.yaml b/bugs/issue-8e825caee33a6ac144580bf44d0d3060ad162394.yaml
deleted file mode 100644
index 77a3ce8..0000000
--- a/bugs/issue-8e825caee33a6ac144580bf44d0d3060ad162394.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: From lines detected over-aggressively
-desc: |-
-  mbox lines starting with "From " should only be considered new-message
-  delimiters if they have a valid email address, date, etc.
-type: :bugfix
-component: mbox
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-11-10 03:56:07.002467 Z
-references: []
-
-id: 8e825caee33a6ac144580bf44d0d3060ad162394
-log_events: 
-- - 2008-11-10 03:56:07.002940 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-git_branch: 
diff --git a/bugs/issue-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml b/bugs/issue-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml
deleted file mode 100644
index ae6d02a..0000000
--- a/bugs/issue-91e1549102c0bfa2c201476d9618f7d234d1a626.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: gpg should use exact match for email address
-desc: otherwise substring matches can select the wrong key
-type: :bugfix
-component: crypto
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-25 02:15:58.006265 Z
-references: []
-
-id: 91e1549102c0bfa2c201476d9618f7d234d1a626
-log_events: 
-- - 2008-05-25 02:15:59.100203 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
-- - 2008-05-25 02:16:07.730483 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - closed issue with disposition fixed
-  - fixed in master
diff --git a/bugs/issue-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml b/bugs/issue-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml
deleted file mode 100644
index e711eea..0000000
--- a/bugs/issue-9f7e28de46d74f7f1e445ae75ea4e230c7473374.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: threads with unsent draft messages are now shown in red
-desc: ""
-type: :feature
-component: curses
-release: "0.5"
-reporter: Nicolas Pouillard <nicolas.pouillard at gmail.com>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:30:53.909487 Z
-references: []
-
-id: 9f7e28de46d74f7f1e445ae75ea4e230c7473374
-log_events: 
-- - 2008-03-07 04:30:53.909522 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:31:03.704713 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml b/bugs/issue-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml
deleted file mode 100644
index 7c30f15..0000000
--- a/bugs/issue-a1a3427de5e8d4f74c0620f99e97ed92d21e924c.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: sup-config name guessing logic can generate nil and crash
-desc: ""
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: Jean-Hadrien CHABRAN <jh at chabran.fr>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-09 17:45:46.095924 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001260.html
-id: a1a3427de5e8d4f74c0620f99e97ed92d21e924c
-log_events: 
-- - 2008-03-09 17:45:46.095961 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-09 17:46:21.702965 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
-- - 2008-03-09 17:46:45.446763 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed reporter
-  - ""
-- - 2008-03-09 17:47:06.797832 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - fixed in master
diff --git a/bugs/issue-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml b/bugs/issue-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml
deleted file mode 100644
index aa6e31f..0000000
--- a/bugs/issue-a1e622dbae0e1841b4d9a376d419aed1d91460e0.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: non-ascii characters in message id need to be normalized
-desc: apparently this happens. in spam email, of course.
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-ditz at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-11 23:04:01.078305 Z
-references: []
-
-id: a1e622dbae0e1841b4d9a376d419aed1d91460e0
-log_events: 
-- - 2008-05-11 23:04:01.677838 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - created
-  - ""
-- - 2008-05-11 23:21:40.281018 Z
-  - William Morgan <wmorgan-ditz at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch non-ascii-message-id, merged into next
-- - 2008-06-19 18:09:04.143173 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml b/bugs/issue-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml
deleted file mode 100644
index 2021c6f..0000000
--- a/bugs/issue-a533480a30a18c3e823dbe20b759e1dcb32ca2b9.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: forward/reply without saving in the editor drops all newlines
-desc: ""
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-23 01:31:16.072859 Z
-references: []
-
-id: a533480a30a18c3e823dbe20b759e1dcb32ca2b9
-log_events: 
-- - 2008-04-23 01:31:16.640737 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-23 01:39:10.304801 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch unedited-newlines, merged into next
-- - 2008-05-25 04:22:44.178693 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml b/bugs/issue-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml
deleted file mode 100644
index e4ce8bf..0000000
--- a/bugs/issue-a68148169baa3838051f4bdb4c175e11cbf7f143.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: all ferret access needs to be wrapped in a mutex
-desc: |-
-  concurrent access breaks things. at least, that's what I *think* is
-  going on here.
-  
-  http://rubyforge.org/pipermail/sup-talk/2008-April/001333.html
-type: :bugfix
-component: indexing
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-01 01:09:39.706808 Z
-references: []
-
-id: a68148169baa3838051f4bdb4c175e11cbf7f143
-log_events: 
-- - 2008-05-01 01:09:40.747646 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml b/bugs/issue-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml
deleted file mode 100644
index 83cc00a..0000000
--- a/bugs/issue-aae5ae6378afa9bd2a8e1b15d28ba7ccef867791.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: remove email->name mapping
-desc: it doesn't work and wouldn't buy that much even if it did
-type: :bugfix
-component: sup
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-05-19 23:42:25.910550 Z
-references: []
-
-id: aae5ae6378afa9bd2a8e1b15d28ba7ccef867791
-log_events: 
-- - 2008-05-19 23:42:26.490587 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-07-31 00:54:39.921596 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-- - 2008-11-22 16:31:27.450146 Z
-  - Nicolas Pouillard <nicolas.pouillard at gmail.com>
-  - closed with disposition fixed
-  - This mapping and the PersonManager are now removed.
-git_branch: 
diff --git a/bugs/issue-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml b/bugs/issue-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml
deleted file mode 100644
index 1802a8a..0000000
--- a/bugs/issue-ad82aa00f4064fc7e1332cee0dae2c2ae95bb217.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: add more vi keys
-desc: ""
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-26 22:23:43.675951 Z
-references: []
-
-id: ad82aa00f4064fc7e1332cee0dae2c2ae95bb217
-log_events: 
-- - 2008-04-26 22:23:44.484689 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-26 22:24:00.893661 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch more-vi-keys. in next.
-- - 2008-05-25 04:12:57.577438 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml b/bugs/issue-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml
deleted file mode 100644
index 138772c..0000000
--- a/bugs/issue-b1f1579fd8350d8add15c5cb588169acfdc5ea24.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: offer to delete lockfile after trying to kill owner process
-desc: |-
-  often the lockfile points to a dead process, so repeatedly offering to kill
-  it isn't all that useful.
-type: :feature
-component: sup
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-04-14 01:25:59.111165 Z
-references: []
-
-id: b1f1579fd8350d8add15c5cb588169acfdc5ea24
-log_events: 
-- - 2008-04-14 01:26:00.135062 Z
-  - William Morgan <w at adap.tv>
-  - created
-  - ""
-- - 2008-05-19 23:40:28.102694 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - assigned to release 0.6 from unassigned
-  - ""
-- - 2008-07-31 00:54:38.573917 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-git_branch: 
diff --git a/bugs/issue-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml b/bugs/issue-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml
deleted file mode 100644
index f293a90..0000000
--- a/bugs/issue-b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: spurious messages appearing in inbox
-desc: |-
-  this is because ThreadSet is claiming that non-relevant videos are actually
-  relevant
-type: :bugfix
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 03:55:29.353904 Z
-references: []
-
-id: b80aa39ef3b8d33bd57e4988c55d89c7c0df5c96
-log_events: 
-- - 2008-03-07 03:55:29.353940 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:55:34.495965 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml b/bugs/issue-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml
deleted file mode 100644
index 2572ce4..0000000
--- a/bugs/issue-bc03bc702f41e6a9687b52d3e32db29132c0f65a.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: add a mark-as-spam hook
-desc: |-
-  a simple hook that triggers when a message is marked as spam, so that
-  users can trigger additional stuff.
-type: :feature
-component: hooks
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-07-31 00:50:51.282526 Z
-references: []
-
-id: bc03bc702f41e6a9687b52d3e32db29132c0f65a
-log_events: 
-- - 2008-07-31 00:50:52.114135 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-07-31 00:51:10.671706 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed with disposition fixed
-  - branch mark-as-spam-hook, merged into master
-git_branch: 
diff --git a/bugs/issue-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml b/bugs/issue-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml
deleted file mode 100644
index b8fc2ad..0000000
--- a/bugs/issue-bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: configurable colors
-desc: All colors should be user-configurable.
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-04 02:27:15.458560 Z
-references: []
-
-id: bdd4415a9d4c8fd3602500111bf9268aa7c7c6a4
-log_events: 
-- - 2008-06-04 02:27:16.721829 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-06-04 02:27:27.256556 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch 'colors', merged into next.
-- - 2008-07-30 23:41:33.553377 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed with disposition fixed
-  - merged into master
-git_branch: color
diff --git a/bugs/issue-bff2527210b3aacae2f74029e5856fed82f1689c.yaml b/bugs/issue-bff2527210b3aacae2f74029e5856fed82f1689c.yaml
deleted file mode 100644
index 802f1b0..0000000
--- a/bugs/issue-bff2527210b3aacae2f74029e5856fed82f1689c.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: wide characters break screen clearing
-desc: |-
-  if you look at a message with wide characters using the new wide-char-aware
-  ncurses library, there will often be snippets of the previous screen
-  immediately to the right of the end of the lines that have wide characters
-  in them.
-  
-  some kind of line length issue maybe? (because everything is done in terms
-  of bytes still. thanks ruby!)
-type: :bugfix
-component: curses
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :wontfix
-creation_time: 2008-04-26 21:35:31.519359 Z
-references: []
-
-id: bff2527210b3aacae2f74029e5856fed82f1689c
-log_events: 
-- - 2008-04-26 21:35:32.384516 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-26 21:44:04.152193 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - depends on {issue 23658477a445c2e61405fecb4cb641a2298caba6} and that's not necessarily destined for 0.6 yet.
-- - 2008-04-28 02:36:51.698817 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition wontfix
-  - dup of {issue c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33}. you'd think i'd'a remembered.
diff --git a/bugs/issue-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml b/bugs/issue-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml
deleted file mode 100644
index b15f04a..0000000
--- a/bugs/issue-c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: wide characters screw up line cursor display
-desc: |-
-  in a message with wide characters, the screen isn't cleared properly, or
-  something. probably due to the # of characters for something being calculated
-  wrong (bytes instead of chars).
-type: :bugfix
-component: curses
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-04-23 02:05:47.272610 Z
-references: []
-
-id: c48f7fc58bba0b38ff6ae14cca01b08a5a7a6c33
-log_events: 
-- - 2008-04-23 02:05:49.360399 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml b/bugs/issue-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml
deleted file mode 100644
index b5b573f..0000000
--- a/bugs/issue-c52f9762bc24a8f45863eb2e7beefa4201db34e8.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: add a --compose option to spawn a compose-message buffer on startup
-desc: ""
-type: :feature
-component: sup
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:28:32.198492 Z
-references: []
-
-id: c52f9762bc24a8f45863eb2e7beefa4201db34e8
-log_events: 
-- - 2008-03-07 04:28:32.198527 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:28:37.471873 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml b/bugs/issue-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml
deleted file mode 100644
index 56c68c5..0000000
--- a/bugs/issue-c660ddfa9d633501140dd199bdfd7cd9fed5df0b.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: ctrl-g should interrupt thread search operation
-desc: ""
-type: :feature
-component: sup
-release: "0.5"
-reporter: Marcus Williams <marcus-sup at bar-coded.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:15:47.155992 Z
-references: []
-
-id: c660ddfa9d633501140dd199bdfd7cd9fed5df0b
-log_events: 
-- - 2008-03-07 04:15:47.156031 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:15:52.274258 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-cef3096582de268c050f78223eb6a22ac2599606.yaml b/bugs/issue-cef3096582de268c050f78223eb6a22ac2599606.yaml
deleted file mode 100644
index 1881fc4..0000000
--- a/bugs/issue-cef3096582de268c050f78223eb6a22ac2599606.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: ruby 1.8.7 breaks sup in weird ways
-desc: |-
-  --- ArgumentError from thread: main
-  wrong number of arguments (2 for 1)
-  /home/benjamin/projects/sup/lib/sup/index.rb:422:in `respond_to?'
-  /home/benjamin/projects/sup/lib/sup/index.rb:422:in `flatten'
-  /home/benjamin/projects/sup/lib/sup/index.rb:422:in `load_sources'
-  /home/benjamin/projects/sup/lib/sup/index.rb:108:in `load'
-  /home/benjamin/projects/sup/lib/sup/util.rb:497:in `send'
-  /home/benjamin/projects/sup/lib/sup/util.rb:497:in `method_missing'
-  /home/benjamin/projects/sup/bin/sup:122
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-06-04 17:39:39.013305 Z
-references: []
-
-id: cef3096582de268c050f78223eb6a22ac2599606
-log_events: 
-- - 2008-06-04 17:39:39.670176 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-06-19 17:57:46.648682 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - fixed directly in master
diff --git a/bugs/issue-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml b/bugs/issue-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml
deleted file mode 100644
index bbba370..0000000
--- a/bugs/issue-cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: invalid gecos can cause sup-config to crash
-desc: ""
-type: :bugfix
-component: sup
-release: "0.5"
-reporter: Jean-Hadrien CHABRAN <jh at chabran.fr>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-14 18:54:32.560987 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-March/001260.html
-id: cf09ec6ec7c35d7d8c002b0521f97b6e94dc9b3e
-log_events: 
-- - 2008-03-14 18:54:32.561241 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-14 18:54:52.486259 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
-- - 2008-03-14 18:55:10.323790 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml b/bugs/issue-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml
deleted file mode 100644
index 5db69fa..0000000
--- a/bugs/issue-cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: exception thrown when a forwarded attachment is not a known mime type
-desc: a minor typo
-type: :bugfix
-component: sup
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-23 01:23:06.368926 Z
-references: []
-
-id: cfbfc65dc90280fa5ecc63094af01d2a47ff0c6e
-log_events: 
-- - 2008-04-23 01:23:07.968757 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-23 01:23:14.995087 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - fixed in master
diff --git a/bugs/issue-d131464e921aefc35571c119aac4d9f1decdebae.yaml b/bugs/issue-d131464e921aefc35571c119aac4d9f1decdebae.yaml
deleted file mode 100644
index d49dcaf..0000000
--- a/bugs/issue-d131464e921aefc35571c119aac4d9f1decdebae.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: blank message-id headers are treated as valid and crash indexing
-desc: |-
-  specifically, they trigger the "just added message to index" debug exception
-  because the id consists of nothing but spaces.
-  
-  header parsing needs to be fixed to not grab headers that are empty.
-type: :bugfix
-component: mbox
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-26 21:07:33.463910 Z
-references: []
-
-id: d131464e921aefc35571c119aac4d9f1decdebae
-log_events: 
-- - 2008-04-26 21:07:34.221325 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-26 21:41:55.731750 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch header-parsing-fix. merged into next.
-- - 2008-05-25 04:19:15.986573 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/issue-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml b/bugs/issue-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml
deleted file mode 100644
index 1fbb23a..0000000
--- a/bugs/issue-d994a360c9cb2a6e12a734962a39ffbc6486a725.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: flags need a separate backup outside of the ferret index
-desc: |
-  Ferret still occasionally barfs and corrupts its own index. Currently all
-  user state is stored in the index and only in the index, so that means
-  you lose big-time if that happens. You can sup-dump your labels, but really,
-  how often are you going to do that.
-  Sup should maintain a separate backup of all labels in some fast on-disk
-  hashtable (message ids to label sets). Bdb comes to mind.
-
-type: :feature
-component: sup
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 04:22:50.236621 Z
-references: []
-
-id: d994a360c9cb2a6e12a734962a39ffbc6486a725
-log_events: 
-- - 2008-03-07 04:22:50.236657 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-20 21:44:51.655741 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - assigned to release 0.6 from 0.5
-  - ""
-- - 2008-07-31 00:54:37.881077 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-git_branch: 
diff --git a/bugs/issue-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml b/bugs/issue-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml
deleted file mode 100644
index 81bb3a8..0000000
--- a/bugs/issue-d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: temp files disappear on sup crash
-desc: |-
-  because we're using the Tempfile library, Sup crashes mean that tempfiles
-  containing message bodies are lost. Sup needs to manage its own tempfiles.
-type: :bugfix
-component: sup
-release: 
-reporter: "Marko Myllym\xC3\xA4ki <marko.myllymaki at iki.fi>"
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 04:59:51.404664 Z
-references: 
-- http://rubyforge.org/pipermail/sup-talk/2008-February/001174.html
-id: d9e6be1b524c6c0a5c31c9c468bda170c2a8cb58
-log_events: 
-- - 2008-03-07 04:59:51.404701 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-11 06:20:47.540967 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - added reference 1
-  - ""
-- - 2008-04-20 21:45:00.615452 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - assigned to release 0.6 from 0.5
-  - ""
-- - 2008-07-31 00:54:38.222035 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-git_branch: 
diff --git a/bugs/issue-e24df153080c6e7a16335018b04d70d9381258b8.yaml b/bugs/issue-e24df153080c6e7a16335018b04d70d9381258b8.yaml
deleted file mode 100644
index 006e2b1..0000000
--- a/bugs/issue-e24df153080c6e7a16335018b04d70d9381258b8.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: Pulling new threads should not shift the cursor.
-desc: |-
-  In thread-view-index, the selected thread should not change due to additions
-  of new threads.
-type: :bugfix
-component: curses
-release: 
-reporter: Nicolas Pouillard <nicolas.pouillard at gmail.com>
-status: :unstarted
-disposition: 
-creation_time: 2008-04-21 08:26:00.191881 Z
-references: []
-
-id: e24df153080c6e7a16335018b04d70d9381258b8
-log_events: 
-- - 2008-04-21 08:26:03.807376 Z
-  - Nicolas Pouillard <nicolas.pouillard at gmail.com>
-  - created
-  - ""
diff --git a/bugs/issue-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml b/bugs/issue-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml
deleted file mode 100644
index 0216c8b..0000000
--- a/bugs/issue-e43b18777ea3aef3566bd80acd126e9ef8a5883a.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: long message ids (>255 characters) never get matched by ferret
-desc: |
-  this is a ferret bug.
-  Apparently, constructing a TermQuery object with a field value of more than
-  255 characters never successfully matches.
-  
-  This is not a good long-term solution. A good one would be to take the SHA1
-  of every message id instead. That will require an index rebuild, so I will
-  save that patch until later.
-
-type: :bugfix
-component: indexing
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-03-07 03:40:55.475449 Z
-references: []
-
-id: e43b18777ea3aef3566bd80acd126e9ef8a5883a
-log_events: 
-- - 2008-03-07 03:40:55.475485 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 03:50:18.590242 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - ""
-- - 2008-04-20 22:10:44.010446 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - ""
diff --git a/bugs/issue-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml b/bugs/issue-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml
deleted file mode 100644
index 5e471f9..0000000
--- a/bugs/issue-e7739718b4dbf49bbd3dd47133affbf7cb1e2361.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: "maildir speedup: don't scan directory except when polling"
-desc: |-
-  lots of useless scanning. removing it should make things faster for large
-  maildirs.
-type: :feature
-component: maildir
-release: "0.5"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: fixed
-creation_time: 2008-03-07 04:20:32.735159 Z
-references: []
-
-id: e7739718b4dbf49bbd3dd47133affbf7cb1e2361
-log_events: 
-- - 2008-03-07 04:20:32.735194 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-03-07 04:20:37.257919 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to fixed
-  - ""
diff --git a/bugs/issue-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml b/bugs/issue-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml
deleted file mode 100644
index f0ffc9d..0000000
--- a/bugs/issue-e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: force hook reload feature
-desc: |-
-  would be nice for hook debugging. otherwise you have to restart sup
-  each time.
-type: :feature
-component: hooks
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-05-19 23:19:38.202269 Z
-references: []
-
-id: e9c2f66a7ff4fb4525c2719e77ac8eedf3835dfd
-log_events: 
-- - 2008-05-19 23:19:38.757600 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml b/bugs/issue-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml
deleted file mode 100644
index ce4ef23..0000000
--- a/bugs/issue-f767a9d2071da7b0f66698ce74e642bf347be96b.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: mbox file handle closing
-desc: |-
-  currently an open file handle is maintained for every single mbox folder.
-  (well, every one that's accessed by polling or by opening a message therefrom.)
-  that is plum crazy.
-type: :feature
-component: mbox
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-03-07 04:27:35.146273 Z
-references: []
-
-id: f767a9d2071da7b0f66698ce74e642bf347be96b
-log_events: 
-- - 2008-03-07 04:27:35.146307 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
diff --git a/bugs/issue-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml b/bugs/issue-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml
deleted file mode 100644
index f4d38d9..0000000
--- a/bugs/issue-fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: thread joining doesn't always work
-desc: |
-  sometimes it works, and sometimes it doesn't, and i haven't found the
-  pattern yet.
-  
-  also, UpdateManager isn't being called properly (maybe even needs a
-  custom event). e.g. if you join in search-results-mode, the results
-  aren't joined in inbox-mode.
-
-type: :bugfix
-component: indexing
-release: 
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :unstarted
-disposition: 
-creation_time: 2008-04-29 00:09:38.366801 Z
-references: []
-
-id: fd7c7a7d7caf41ff20e7d10ca3f074fc02c14a5b
-log_events: 
-- - 2008-04-29 00:09:38.998592 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-07-31 00:54:39.251862 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - unassigned from release 0.6
-  - ""
-git_branch: 
diff --git a/bugs/issue-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml b/bugs/issue-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml
deleted file mode 100644
index 99cbd3e..0000000
--- a/bugs/issue-fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/issue 
-title: archive-and-mark-read command in inbox-mode
-desc: ""
-type: :feature
-component: curses
-release: "0.6"
-reporter: William Morgan <wmorgan-sup at masanjin.net>
-status: :closed
-disposition: :fixed
-creation_time: 2008-04-26 23:32:53.791207 Z
-references: []
-
-id: fdfc906e8f4f6eb10f1ebdf39c416415d9ab6af9
-log_events: 
-- - 2008-04-26 23:32:57.083084 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - created
-  - ""
-- - 2008-04-26 23:33:12.980220 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - changed status from unstarted to in_progress
-  - branch read-and-archive. in next.
-- - 2008-05-25 04:14:48.307896 Z
-  - William Morgan <wmorgan-sup at masanjin.net>
-  - closed issue with disposition fixed
-  - merged into master
diff --git a/bugs/project.yaml b/bugs/project.yaml
deleted file mode 100644
index f6afd47..0000000
--- a/bugs/project.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
---- !ditz.rubyforge.org,2008-03-06/project 
-name: sup
-version: 0.0.1
-components: 
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: sup
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: threading
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: indexing
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: curses
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: hooks
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: sup-sync
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: sup-sync-back
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: maildir
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: imap
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: mbox
-- !ditz.rubyforge.org,2008-03-06/component 
-  name: crypto
-releases: 
-- !ditz.rubyforge.org,2008-03-06/release 
-  name: "0.5"
-  status: :released
-  release_time: 2008-04-22 15:55:47.323776 Z
-  log_events: 
-  - - 2008-03-07 02:37:54.903172 Z
-    - William Morgan <wmorgan-sup at masanjin.net>
-    - created
-    - ""
-  - - 2008-04-22 15:55:47.323829 Z
-    - William Morgan <wmorgan-sup at masanjin.net>
-    - released
-    - ""
-- !ditz.rubyforge.org,2008-03-06/release 
-  name: "0.6"
-  status: :released
-  release_time: 2008-08-04 02:48:44.154676 Z
-  log_events: 
-  - - 2008-04-20 21:17:04.443432 Z
-    - William Morgan <wmorgan-sup at masanjin.net>
-    - created
-    - ""
-  - - 2008-08-04 02:48:44.154704 Z
-    - William Morgan <wmorgan-sup at masanjin.net>
-    - released
-    - ""
diff --git a/contrib/colorpicker.rb b/contrib/colorpicker.rb
index c981a23..947b3d5 100644
--- a/contrib/colorpicker.rb
+++ b/contrib/colorpicker.rb
@@ -1,10 +1,6 @@
 require 'rubygems'
 
-begin
-  require 'ncursesw'
-rescue LoadError
-  require 'ncurses'
-end
+require 'ncursesw'
 
 Ncurses.initscr
 Ncurses.noecho
diff --git a/contrib/completion/_sup.zsh b/contrib/completion/_sup.zsh
index 76870ca..27d1f43 100644
--- a/contrib/completion/_sup.zsh
+++ b/contrib/completion/_sup.zsh
@@ -1,8 +1,8 @@
-#compdef sup sup-add sup-config sup-dump sup-sync sup-sync-back sup-tweak-labels sup-recover-sources
+#compdef sup sup-add sup-config sup-dump sup-sync sup-sync-back-mbox sup-tweak-labels sup-recover-sources
 # vim: set et sw=2 sts=2 ts=2 ft=zsh :
 
 # TODO: sources completion: maildir://some/dir, mbox://some/file, ...
-#       for sup-add, sup-sync, sup-sync-back, sup-tweak-labels
+#       for sup-add, sup-sync, sup-sync-back-mbox, sup-tweak-labels
 
 (( ${+functions[_sup_cmd]} )) ||
 _sup_cmd()
diff --git a/doc/FAQ.txt b/doc/FAQ.txt
index 9a24d7f..16c9a59 100644
--- a/doc/FAQ.txt
+++ b/doc/FAQ.txt
@@ -21,8 +21,8 @@ A: I hate ads, I hate using a mouse, and I hate non-programmability and
 
 Q: Why the console?
 A: Because a keystroke is worth a hundred mouse clicks, as any Unix
-   user knows. Because you don't need web browser. Because you get
-   instantaneous response and a simple interface.
+   user knows. Because you don't need a web browser. Because you get
+   an instantaneous response and a simple interface.
 
 Q: How does Sup deal with spam?
 A: You can manually mark messages as spam, which prevents them from
@@ -112,4 +112,8 @@ P: When I run Sup remotely and view an HTML attachment, an existing
    file, which it can't find (since it's on the remote machine). How do
    I view HTML attachments in this environment?
 S: Put this in your ~/.mailcap on the machine you run Sup on:
-      text/html; /usr/bin/firefox -a sup '%s'; description=HTML Text; test=test -n "$DISPLAY";  nametemplate=%s.html
+      text/html; /usr/bin/firefox -a sup %s; description=HTML Text; test=test -n "$DISPLAY";  nametemplate=%s.html
+
+   Please read
+   https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments for
+   some security concerns on opening attachments.
diff --git a/doc/Hooks.txt b/doc/Hooks.txt
index 21b1e5e..6c33971 100644
--- a/doc/Hooks.txt
+++ b/doc/Hooks.txt
@@ -48,12 +48,17 @@ before-poll:
 
 
 mime-decode:
+  ## Please read:
+  https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments for
+  some security concerns on opening attachments.
+
   ## turn text/html attachments into plain text, unless they are part
   ## of a multipart/alternative pair
+  require 'shellwords'
   unless sibling_types.member? "text/plain"
     case content_type
     when "text/html"
-      `/usr/bin/w3m -dump -T #{content_type} '#{filename}'`
+      `/usr/bin/w3m -dump -T #{content_type} #{Shellwords.escape filename}`
     end
   end
 
diff --git a/doc/NewUserGuide.txt b/doc/NewUserGuide.txt
deleted file mode 100644
index c071deb..0000000
--- a/doc/NewUserGuide.txt
+++ /dev/null
@@ -1,258 +0,0 @@
-Welcome to Sup! Here's how to get started.
-
-First, try running `sup`. Since this is your first time, you'll be
-confronted with a mostly blank screen, and a notice at the bottom that
-you have no new messages. That's because Sup doesn't hasn't loaded
-anything into its index yet, and has no idea where to look for them
-anyways.
-
-If you want to play around a little at this point, you can press 'b'
-to cycle between buffers, ';' to get a list of the open buffers, and
-'x' to kill a buffer. There's probably not too much interesting there,
-but there's a log buffer with some cryptic messages. You can also
-press '?' at any point to get a list of keyboard commands, but in the
-absence of any email, these will be mostly useless. When you get
-bored, press 'q' to quit.
-
-To use Sup for email, we need to load messages into the index. The
-index is where Sup stores all message state (e.g. read or unread, any
-message labels), and all information necessary for searching and for
-threading messages. Sup only knows about messages in its index.
-
-We can add messages to the index by telling Sup about the "source"
-where the messages reside. Sources are things like mbox folders, and
-maildir directories. Sup doesn't duplicate the actual message content
-in the index; it only stores whatever information is necessary for
-searching, threading and labelling. So when you search for messages or
-view your inbox, Sup talks only to the index (stored locally on
-disk). When you view a thread, Sup requests the full content of all
-the messages from the source.
-
-The easiest way to set up all your sources is to run `sup-config`.
-This will interactively walk you through some basic configuration,
-prompt you for all the sources you need, and optionally import
-messages from them.  Sup-config uses two other tools, sup-add and
-sup-sync, to load messages into the index. In the future you may make
-use of these tools directly (see below).
-
-Once you've run sup-config, you're ready to run `sup`. You should see
-the most recent unarchived messages appear in your inbox.
-Congratulations, you've got Sup working!
-
-If you're coming from the world of traditional MUAs, there are a
-couple differences you should be aware of at this point. First, Sup
-has no folders. Instead, you organize and find messages by a
-combination of search and labels (known as "tags" everywhere else in
-the world). Search and labels are an integral part of Sup because in
-Sup, rather than viewing the contents of a folder, you view the
-results of a search. I mentioned above that your inbox is, by
-definition, the set of all messages that aren't archived. This means
-that your inbox is nothing more than the result of the search for all
-messages with the label "inbox". (It's actually slightly more
-complicated---we also omit messages marked as killed, deleted or
-spam.)
-
-You could replicate the folder paradigm easily under this scheme, by
-giving each message exactly one label and only viewing the results of
-simple searches for those labels. But you'd quickly find out that life
-can be easier than that if you just trust the search engine, and use
-labels judiciously for things that are too hard to find with search.
-The idea is that a labeling system that allows arbitrary, user-defined
-labels, supplemented by a quick and easy-to-access search mechanism
-provides all the functionality that folders does, plus much more, at a
-far lower cost to the user.
-
-Now let's take a look at your inbox. You'll see that Sup groups
-messages together into threads: each line in the inbox is a thread,
-and the number in parentheses is the number of messages in that
-thread. (If there's no number, there's just one message in the
-thread.) In Sup, most operations are on threads, not individual
-messages. The idea is that you rarely want to operate on a message
-independent of its context. You typically want to view, archive, kill,
-or label all the messages in a thread at one time.
-
-Use the up and down arrows to highlight a thread. ('j' and 'k' do the
-same thing, and 'J' and 'K' will scroll the whole window. Even the
-left and right arrow keys work.) By default, Sup only loads as many
-threads as it takes to fill the window; if you'd like to load more,
-press 'M'. You can hit tab to cycle between only threads with new
-messages.
-
-Highlight a thread and press enter to view it. You'll notice that all
-messages in the thread are displayed together, laid out graphically by
-their relationship to each other (replies are nested under parents).
-By default, only the new messages in a thread are expanded, and the
-others are hidden. You can toggle an individual message's state by
-highlighting a green line and pressing enter. You can use 'E' to
-expand or collapse all messages or 'N' to expand only the new
-messages. You'll also notice that Sup hides quoted text and
-signatures. If you highlight a particular hidden chunk, you can press
-enter to expand it, or you can press 'o' to toggle every hidden chunk
-in a particular message.
-
-Other useful keyboard commands when viewing a thread are: 'n' and 'p'
-to jump to the next and previous open messages, 'h' to toggle the
-detailed headers for the current message, and enter to expand or
-collapse the current message (when it's on a text region). Enter and
-'n' in combination are useful for scanning through a thread---press
-enter to close the current message and jump to the next open one, and
-'n' to keep it open and jump. If the buffer is misaligned with a
-message, you can press 'z' to highlight it.
-
-This is a lot to remember, but you can always hit '?' to see the full
-list of keyboard commands at any point. There's a lot of useful stuff
-in there---once you learn some, try out some of the others!
-
-Now press 'x' to kill the thread view buffer. You should see the inbox
-again. If you don't, you can cycle through the buffers by pressing
-'b', or you can press ';' to see a list of all buffers and simply
-select the inbox.
-
-There are many operations you can perform on threads beyond viewing
-them. To archive a thread, press 'a'. The thread will disappear from
-your inbox, but will still appear in search results. If someone
-replies an archived thread, it will reappear in your inbox. To kill a
-thread, press '&'. Killed threads will never come back to your inbox,
-even if people reply, but will still be searchable. (This is useful
-for those interminable threads that you really have no immediate
-interest in, but which seem to pop up on every mailing list.)
-
-If a thread is spam, press 'S'. It will disappear and won't come back.
-It won't even appear in search results, unless you explicitly search
-for spam.
-
-You can star a thread by pressing '*'. Starred threads are displayed
-with a little yellow asterisk next to them, but otherwise have no
-special semantics. But you can also search for them easily---we'll see
-how in a moment.
-
-To edit the labels for (all the messages in) a thread, press 'l'. Type
-in the labels as a sequence of space-separated words. To cancel the
-input, press Ctrl-G.
-
-Many of these operations can be applied to a group of threads. Press
-'t' to tag a thread. Tag a couple, then press '=' to apply the next
-command to the set of threads. '=t', of course, will untag all tagged
-messages.
-
-Ok, let's try using labels and search. Press 'L' to do a quick label
-search. You'll be prompted for a label; simply hit enter to bring up
-scrollable list of all the labels you've ever used, along with some
-special labels (Draft, Starred, Sent, Spam, etc.). Highlight a label
-and press enter to view all the messages with that label.
-
-What you just did was actually a specific search. For a general search,
-press '\' (backslash---forward slash is used for in-buffer search,
-following console conventions). Now type in your query (again, Ctrl-G to
-cancel at any point.) You can just type in arbitrary text, which will be
-matched on a per-word basis against the bodies of all email in the
-index, or you can make use of the full Xapian query syntax
-(http://xapian.org/docs/queryparser.html):
-
-- Phrasal queries using double-quotes, e.g.: "three contiguous words"
-- Queries against a particular field using <field name>:<query>,
-  e.g.: label:ruby-talk, or from:matz at ruby-lang.org. (Fields include:
-  body, from, to, and subject.)
-- Force non-occurrence by -, e.g. -body:"hot soup".
-- If you have the chronic gem installed, date queries like
-  "before:today", "on:today", "after:yesterday", "after:(2 days ago)"
-  (parentheses required for multi-word descriptions).
-
-You can combine those all together. For example:
-
-     label:ruby-talk subject:\[ANN\] -rails on:today
-
-Play around with the search, and see the Xapian documentation for
-details on more sophisticated queries (date ranges, "within n words",
-etc.)
-
-At this point, you're well on your way to figuring out all the cool
-things Sup can do. By repeated application of the '?' key, see if you
-can figure out how to:
-
- - List some recent contacts
- - Easily search for all mail from a recent contact
- - Easily search for all mail from several recent contacts
- - Add someone to your address book
- - Postpone a message (i.e., save a draft)
- - Quickly re-edit a just-saved draft message
- - View the raw header of a message
- - Star an individual message, not just a thread
-
-There's one last thing to be aware of when using Sup: how it interacts
-with other email programs. As I described above, Sup stores data about
-messages in the index, but doesn't duplicate the message contents
-themselves. The messages remain on the source. If the index and the
-source every fall out of sync, e.g. due to another email client
-modifying the source, then Sup will be unable to operate on that
-source. For example, for mbox files, Sup stores a byte offset into the
-file for each message. If a message deleted from that file by another
-client, or even marked as read (yeah, mbox sucks), all succeeding
-offsets will be wrong.
-
-That's the bad news. The good news is that Sup is pretty good at being
-able to detect this type of situation, and fixing it is just a matter
-of running `sup-sync --changed` on the source. Sup will even tell you
-how to invoke sup-sync when it detects a problem. This is a
-complication you will almost certainly run in to if you use both Sup
-and another MUA on the same source, so it's good to be aware of it.
-
-Have fun, and email sup-talk at rubyforge.org if you have any problems!
-
-Appendix A: sup-add and sup-sync
----------------------------------
-
-Instead of using sup-config to add a new source, you can manually run
-`sup-add` with a URI pointing to it. The URI should be of the form:
-
-- mbox://path/to/a/filename, for an mbox file on disk.
-- maildir://path/to/a/filename, for a maildir directory on disk.
-
-Before you add the source, you need make three decisions. The first is
-whether you want Sup to regularly poll this source for new messages.
-By default it will, but if this is a source that will never have new
-messages, you can specify `--unusual`. Sup polls only "usual" sources
-when checking for new mail (unless you manually invoke sup-sync).
-
-The second is whether you want messages from the source to be
-automatically archived. An archived message will not show up in your
-inbox, but will be found when you search. (Your inbox in Sup is, by
-definition, the set of all all non-archived messages). Specify
-`--archive` to automatically archive all messages from the source. This
-is useful for sources that contain, for example, high-traffic mailing
-lists that you don't want polluting your inbox.
-
-The final decision is whether you want any labels automatically
-applied to messages from this source. You can use `--labels` to do this.
-
-Now that you've added the source, let's import all the current
-messages from it, by running sup-sync with the source URI. You can
-specify `--archive` to automatically archive all messages in this
-import; typically you'll want to specify this for every source you
-import except your actual inbox. You can also specify `--read` to mark
-all imported messages as read; the default is to preserve the
-read/unread status from the source.
-
-Sup-sync will now load all the messages from the source into the
-index. Depending on the size of the source, this may take a while.
-Don't panic! It's a one-time process.
-
-Appendix B: Automatically labeling incoming email
--------------------------------------------------
-
-One option is to filter incoming email into different sources with
-something like procmail, and have each of these sources auto-apply
-labels by using `sup-add --labels`.
-
-But the better option is to learn Ruby and write a before-add hook.
-This will allow you to apply labels based on whatever crazy logic you
-can come up with. See http://sup.rubyforge.org/wiki/wiki.pl?Hooks for
-examples.
-
-Appendix C: Reading blogs with Sup
-----------------------------------
-
-Really, blog posts should be read like emails are read---you should be
-able to mark them as unread, flag them, label them, etc. Use rss2email
-to transform RSS feeds into emails, direct them all into a source, and
-add that source to Sup. Voila!
diff --git a/lib/sup.rb b/lib/sup.rb
index 74d5cde..90f4335 100644
--- a/lib/sup.rb
+++ b/lib/sup.rb
@@ -1,10 +1,12 @@
+# encoding: utf-8
+
 require 'rubygems'
-require 'syck'
 require 'yaml'
+YAML::ENGINE.yamler = 'psych'
 require 'zlib'
 require 'thread'
 require 'fileutils'
-require 'gettext'
+require 'locale'
 require 'curses'
 require 'rmail'
 begin
@@ -23,24 +25,27 @@ end
 class Module
   def yaml_properties *props
     props = props.map { |p| p.to_s }
-    vars = props.map { |p| "@#{p}" }
-    klass = self
-    path = klass.name.gsub(/::/, "/")
 
-    klass.instance_eval do
-      define_method(:to_yaml_properties) { vars }
-      define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
+    path = name.gsub(/::/, "/")
+    yaml_tag "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"
+
+    define_method :init_with do |coder|
+      initialize(*coder.map.values_at(*props))
     end
 
-    YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
-      klass.new(*props.map { |p| val[p] })
+    define_method :encode_with do |coder|
+      coder.map = props.inject({}) do |hash, key|
+        hash[key] = instance_variable_get("@#{key}")
+        hash
+      end
     end
+
+    # Legacy
+    Psych.load_tags["!#{Redwood::LEGACY_YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}"] = self
   end
 end
 
 module Redwood
-  VERSION = "git"
-
   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
   COLOR_FN   = File.join(BASE_DIR, "colors.yaml")
@@ -54,9 +59,12 @@ module Redwood
   HOOK_DIR   = File.join(BASE_DIR, "hooks")
   SEARCH_FN  = File.join(BASE_DIR, "searches.txt")
   LOG_FN     = File.join(BASE_DIR, "log")
+  SYNC_OK_FN = File.join(BASE_DIR, "sync-back-ok")
 
-  YAML_DOMAIN = "masanjin.net"
+  YAML_DOMAIN = "supmua.org"
+  LEGACY_YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
+  MAILDIR_SYNC_CHECK_SKIPPED = 'SKIPPED'
 
   ## record exceptions thrown in threads nicely
   @exceptions = []
@@ -151,7 +159,7 @@ module Redwood
     SourceManager SearchManager IdleManager).map { |x| Redwood.const_get x.to_sym }
   end
 
-  def start
+  def start bypass_sync_check = false
     managers.each { |x| fail "#{x} already instantiated" if x.instantiated? }
 
     FileUtils.mkdir_p Redwood::BASE_DIR
@@ -167,6 +175,74 @@ module Redwood
     Redwood::SearchManager.init Redwood::SEARCH_FN
 
     managers.each { |x| x.init unless x.instantiated? }
+
+    return if bypass_sync_check
+
+    if $config[:sync_back_to_maildir]
+      if not File.exists? Redwood::SYNC_OK_FN
+        Redwood.warn_syncback <<EOS
+It appears that the "sync_back_to_maildir" option has been changed
+from false to true since the last execution of sup.
+EOS
+        $stderr.puts <<EOS
+
+Should I complain about this again? (Y/n)
+EOS
+        File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(Redwood::MAILDIR_SYNC_CHECK_SKIPPED) } if STDIN.gets.chomp.downcase == 'n'
+      end
+    elsif not $config[:sync_back_to_maildir] and File.exists? Redwood::SYNC_OK_FN
+      File.delete(Redwood::SYNC_OK_FN)
+    end
+  end
+
+  def check_syncback_settings
+    # don't check if syncback was never performed
+    return unless File.exists? Redwood::SYNC_OK_FN
+    active_sync_sources = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? }
+    return if active_sync_sources.length == 1 and active_sync_sources[0] == Redwood::MAILDIR_SYNC_CHECK_SKIPPED
+    sources = SourceManager.sources
+    newly_synced = sources.select { |s| s.is_a? Maildir and s.sync_back_enabled? and not active_sync_sources.include? s.uri }
+    unless newly_synced.empty?
+
+      details =<<EOS
+It appears that the option "sync_back" of the following source(s)
+has been changed from false to true since the last execution of
+sup:
+
+EOS
+      newly_synced.each do |s|
+        details += "#{s} (usual: #{s.usual})\n"
+      end
+
+      Redwood.warn_syncback details
+    end
+  end
+
+  def self.warn_syncback details
+    $stderr.puts <<EOS
+WARNING
+-------
+
+#{details}
+
+It is *strongly* recommended that you run "sup-sync-back-maildir"
+before continuing, otherwise you might lose changes you have made in sup
+to your Xapian index.
+
+This script should be run each time you change the
+"sync_back_to_maildir" flag in config.yaml from false to true or
+the "sync_back" flag is changed to true for a source in sources.yaml.
+
+Please run "sup-sync-back-maildir -h" for more information and why this
+is needed.
+
+Note that if you have any sources that are not marked as 'ususal' in
+sources.yaml you need to manually specify them when running  the
+sup-sync-back-maildir script.
+
+Are you really sure you want to continue? (y/N)
+EOS
+    abort "Aborted" unless STDIN.gets.chomp.downcase == 'y'
   end
 
   def finish
@@ -231,36 +307,6 @@ EOM
     end
   end
 
-  ## to be called by entry points in bin/, to ensure that
-  ## their versions match up against the library versions.
-  ##
-  ## this is a perennial source of bug reports from people
-  ## who both use git and have a gem version installed.
-  def check_library_version_against v
-    unless Redwood::VERSION == v
-      $stderr.puts <<EOS
-Error: version mismatch!
-The sup executable is at version #{v.inspect}.
-The sup libraries are at version #{Redwood::VERSION.inspect}.
-
-Your development environment may be picking up code from a
-rubygems installation of sup.
-
-If you're running from git with a commandline like
-
-  ruby -Ilib #{$0}
-
-try this instead:
-
-  RUBY_INVOCATION="ruby -Ilib" ruby -Ilib #{$0}
-
-You can also try `gem uninstall sup` and removing all Sup rubygems.
-
-EOS
-#' duh!
-      abort
-    end
-  end
 
   ## set up default configuration file
   def load_config filename
@@ -285,7 +331,9 @@ EOS
       :poll_interval => 300,
       :wrap_width => 0,
       :slip_rows => 0,
-      :col_jump => 2
+      :col_jump => 2,
+      :stem_language => "english",
+      :sync_back_to_maildir => false
     }
     if File.exists? filename
       config = Redwood::load_yaml_obj filename
@@ -294,7 +342,7 @@ EOS
     else
       require 'etc'
       require 'socket'
-      name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first rescue nil
+      name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first.force_encoding($encoding).fix_encoding! rescue nil
       name ||= ENV["USER"]
       email = ENV["USER"] + "@" +
         begin
@@ -306,8 +354,8 @@ EOS
       config = {
         :accounts => {
           :default => {
-            :name => name,
-            :email => email,
+            :name => name.dup.fix_encoding!,
+            :email => email.dup.fix_encoding!,
             :alternates => [],
             :sendmail => "/usr/sbin/sendmail -oem -ti",
             :signature => File.join(ENV["HOME"], ".signature"),
@@ -326,22 +374,22 @@ EOS
   end
 
   module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
-                  :report_broken_sources, :check_library_version_against,
-                  :load_config, :managers
+                  :report_broken_sources, :load_config, :managers,
+                  :check_syncback_settings
 end
 
+require 'sup/version'
 require "sup/util"
 require "sup/hook"
 require "sup/time"
 
 ## everything we need to get logging working
-require "sup/logger"
-Redwood::Logger.init.add_sink $stderr
-include Redwood::LogsStuff
+require "sup/logger/singleton"
 
 ## determine encoding and character set
 $encoding = Locale.current.charset
 $encoding = "UTF-8" if $encoding == "utf8"
+$encoding = "UTF-8" if $encoding == "UTF8"
 if $encoding
   debug "using character set encoding #{$encoding.inspect}"
 else
@@ -349,14 +397,24 @@ else
   $encoding = "UTF-8"
 end
 
+# test encoding
+teststr = "test"
+teststr.encode('UTF-8')
+begin
+  teststr.encode($encoding)
+rescue Encoding::ConverterNotFoundError
+  warn "locale encoding is invalid, defaulting to utf-8"
+  $encoding = "UTF-8"
+end
+
 require "sup/buffer"
 require "sup/keymap"
 require "sup/mode"
-require "sup/modes/scroll-mode"
-require "sup/modes/text-mode"
-require "sup/modes/log-mode"
+require "sup/modes/scroll_mode"
+require "sup/modes/text_mode"
+require "sup/modes/log_mode"
 require "sup/update"
-require "sup/message-chunks"
+require "sup/message_chunks"
 require "sup/message"
 require "sup/source"
 require "sup/mbox"
@@ -364,7 +422,7 @@ require "sup/maildir"
 require "sup/person"
 require "sup/account"
 require "sup/thread"
-require "sup/interactive-lock"
+require "sup/interactive_lock"
 require "sup/index"
 require "sup/textfield"
 require "sup/colormap"
@@ -375,31 +433,31 @@ require "sup/draft"
 require "sup/poll"
 require "sup/crypto"
 require "sup/undo"
-require "sup/horizontal-selector"
-require "sup/modes/line-cursor-mode"
-require "sup/modes/help-mode"
-require "sup/modes/edit-message-mode"
-require "sup/modes/edit-message-async-mode"
-require "sup/modes/compose-mode"
-require "sup/modes/resume-mode"
-require "sup/modes/forward-mode"
-require "sup/modes/reply-mode"
-require "sup/modes/label-list-mode"
-require "sup/modes/contact-list-mode"
-require "sup/modes/thread-view-mode"
-require "sup/modes/thread-index-mode"
-require "sup/modes/label-search-results-mode"
-require "sup/modes/search-results-mode"
-require "sup/modes/person-search-results-mode"
-require "sup/modes/inbox-mode"
-require "sup/modes/buffer-list-mode"
-require "sup/modes/poll-mode"
-require "sup/modes/file-browser-mode"
-require "sup/modes/completion-mode"
-require "sup/modes/console-mode"
+require "sup/horizontal_selector"
+require "sup/modes/line_cursor_mode"
+require "sup/modes/help_mode"
+require "sup/modes/edit_message_mode"
+require "sup/modes/edit_message_async_mode"
+require "sup/modes/compose_mode"
+require "sup/modes/resume_mode"
+require "sup/modes/forward_mode"
+require "sup/modes/reply_mode"
+require "sup/modes/label_list_mode"
+require "sup/modes/contact_list_mode"
+require "sup/modes/thread_view_mode"
+require "sup/modes/thread_index_mode"
+require "sup/modes/label_search_results_mode"
+require "sup/modes/search_results_mode"
+require "sup/modes/person_search_results_mode"
+require "sup/modes/inbox_mode"
+require "sup/modes/buffer_list_mode"
+require "sup/modes/poll_mode"
+require "sup/modes/file_browser_mode"
+require "sup/modes/completion_mode"
+require "sup/modes/console_mode"
 require "sup/sent"
 require "sup/search"
-require "sup/modes/search-list-mode"
+require "sup/modes/search_list_mode"
 require "sup/idle"
 
 $:.each do |base|
diff --git a/lib/sup/account.rb b/lib/sup/account.rb
index 1cbf7d8..7fc1aa8 100644
--- a/lib/sup/account.rb
+++ b/lib/sup/account.rb
@@ -50,8 +50,9 @@ class AccountManager
       [:name, :sendmail, :signature, :gpgkey].each { |k| hash[k] ||= @default_account.send(k) }
     end
     hash[:alternates] ||= []
+    fail "alternative emails are not an array: #{hash[:alternates]}" unless hash[:alternates].kind_of? Array
 
-    [:name, :signature].each { |x| hash[x].force_encoding Encoding::UTF_8 if hash[x].respond_to? :encoding }
+    [:name, :signature].each { |x| hash[x] ? hash[x].fix_encoding! : nil }
 
     a = Account.new hash
     @accounts[a] = true
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
index 444589a..6e5d827 100644
--- a/lib/sup/buffer.rb
+++ b/lib/sup/buffer.rb
@@ -1,11 +1,9 @@
+# encoding: utf-8
+
 require 'etc'
 require 'thread'
 
-begin
-  require 'ncursesw'
-rescue LoadError
-  require 'ncurses'
-end
+require 'ncursesw'
 
 if defined? Ncurses
 module Ncurses
@@ -130,14 +128,11 @@ class Buffer
     @w.attrset Colormap.color_for(opts[:color] || :none, opts[:highlight])
     s ||= ""
     maxl = @width - x # maximum display width width
-    stringl = maxl    # string "length"
 
     # fill up the line with blanks to overwrite old screen contents
     @w.mvaddstr y, x, " " * maxl unless opts[:no_fill]
 
-    ## the next horribleness is thanks to ruby's lack of widechar support
-    stringl += 1 while stringl < s.length && s[0 ... stringl].display_length < maxl
-    @w.mvaddstr y, x, s[0 ... stringl]
+    @w.mvaddstr y, x, s.slice_by_display_length(maxl)
   end
 
   def clear
@@ -271,7 +266,7 @@ EOS
 
   def handle_input c
     if @focus_buf
-      if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY[0]
+      if @focus_buf.mode.in_search? && c != CONTINUE_IN_BUFFER_SEARCH_KEY.ord
         @focus_buf.mode.cancel_search!
         @focus_buf.mark_dirty
       end
@@ -454,7 +449,7 @@ EOS
 
   def ask_with_completions domain, question, completions, default=nil
     ask domain, question, default do |s|
-      s.force_encoding 'UTF-8' if s.methods.include?(:encoding)
+      s.fix_encoding!
       completions.select { |x| x =~ /^#{Regexp::escape s}/iu }.map { |x| [x, x] }
     end
   end
@@ -471,9 +466,9 @@ EOS
           raise "william screwed up completion: #{partial.inspect}"
         end
 
-      prefix.force_encoding 'UTF-8' if prefix.methods.include?(:encoding)
-      target.force_encoding 'UTF-8' if target.methods.include?(:encoding)
-      completions.select { |x| x =~ /^#{Regexp::escape target}/i }.map { |x| [prefix + x, x] }
+      prefix.fix_encoding!
+      target.fix_encoding!
+      completions.select { |x| x =~ /^#{Regexp::escape target}/iu }.map { |x| [prefix + x, x] }
     end
   end
 
@@ -481,12 +476,12 @@ EOS
     ask domain, question, default do |partial|
       prefix, target = partial.split_on_commas_with_remainder
       target ||= prefix.pop || ""
-      target.force_encoding 'UTF-8' if target.methods.include?(:encoding)
+      target.fix_encoding!
 
       prefix = prefix.join(", ") + (prefix.empty? ? "" : ", ")
-      prefix.force_encoding 'UTF-8' if prefix.methods.include?(:encoding)
+      prefix.fix_encoding!
 
-      completions.select { |x| x =~ /^#{Regexp::escape target}/i }.sort_by { |c| [ContactManager.contact_for(c) ? 0 : 1, c] }.map { |x| [prefix + x, x] }
+      completions.select { |x| x =~ /^#{Regexp::escape target}/iu }.sort_by { |c| [ContactManager.contact_for(c) ? 0 : 1, c] }.map { |x| [prefix + x, x] }
     end
   end
 
@@ -499,7 +494,7 @@ EOS
         if dir
           [[s.sub(full, dir), "~#{name}"]]
         else
-          users.select { |u| u =~ /^#{Regexp::escape name}/ }.map do |u|
+          users.select { |u| u =~ /^#{Regexp::escape name}/u }.map do |u|
             [s.sub("~#{name}", "~#{u}"), "~#{u}"]
           end
         end
@@ -558,6 +553,7 @@ EOS
 
     completions = (recent + contacts).flatten.uniq
     completions += HookManager.run("extra-contact-addresses") || []
+
     answer = BufferManager.ask_many_emails_with_completions domain, question, completions, default
 
     if answer
@@ -626,7 +622,7 @@ EOS
       tf.deactivate
       draw_screen :sync => false, :status => status, :title => title
     end
-    tf.value.tap { |x| x.force_encoding Encoding::UTF_8 if x && x.respond_to?(:encoding) }
+    tf.value.tap { |x| x }
   end
 
   def ask_getch question, accept=nil
@@ -713,7 +709,7 @@ EOS
     end
 
     Ncurses.mutex.lock unless opts[:sync] == false
-    Ncurses.attrset Colormap.color_for(:none)
+    Ncurses.attrset Colormap.color_for(:text_color)
     adj = @asking ? 2 : 1
     m.each_with_index do |s, i|
       Ncurses.mvaddstr Ncurses.rows - i - adj, 0, s + (" " * [Ncurses.cols - s.length, 0].max)
diff --git a/lib/sup/client.rb b/lib/sup/client.rb
deleted file mode 100644
index 23da5c6..0000000
--- a/lib/sup/client.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'sup/protocol'
-
-module Redwood
-
-class Client < EM::P::RedwoodClient
-  def initialize *a
-    @next_tag = 1
-    @cbs = {}
-    super *a
-  end
-
-  def mktag &b
-    @next_tag.tap do |x|
-      @cbs[x] = b
-      @next_tag += 1
-    end
-  end
-
-  def rmtag tag
-    @cbs.delete tag
-  end
-
-  def query qstr, offset, limit, raw, &b
-    tag = mktag do |type,tag,args|
-      if type == 'message'
-        b.call args
-      else
-        fail unless type == 'done'
-        b.call nil
-        rmtag tag
-      end
-    end
-    send_message 'query', tag,
-                 'query' => qstr,
-                 'offset' => offset,
-                 'limit' => limit,
-                 'raw' => raw
-  end
-
-  def count qstr, &b
-    tag = mktag do |type,tag,args|
-      b.call args['count']
-      rmtag tag
-    end
-    send_message 'count', tag,
-                 'query' => qstr
-  end
-
-  def label qstr, add, remove, &b
-    tag = mktag do |type,tag,args|
-      b.call
-      rmtag tag
-    end
-    send_message 'label', tag,
-                 'query' => qstr,
-                 'add' => add,
-                 'remove' => remove
-  end
-
-  def add raw, labels, &b
-    tag = mktag do |type,tag,args|
-      b.call
-      rmtag tag
-    end
-    send_message 'add', tag,
-                 'raw' => raw,
-                 'labels' => labels
-  end
-
-  def thread msg_id, raw, &b
-    tag = mktag do |type,tag,args|
-      if type == 'message'
-        b.call args
-      else
-        fail unless type == 'done'
-        b.call nil
-        rmtag tag
-      end
-    end
-
-    send_message 'thread', tag,
-                 'message_id' => msg_id,
-                 'raw' => raw
-  end
-
-  def receive_message type, tag, args
-    cb = @cbs[tag] or fail "invalid tag #{tag.inspect}"
-    cb[type, tag, args]
-  end
-end
-
-end
diff --git a/lib/sup/colormap.rb b/lib/sup/colormap.rb
index 2957de7..c29aaad 100644
--- a/lib/sup/colormap.rb
+++ b/lib/sup/colormap.rb
@@ -26,6 +26,7 @@ class Colormap
   @@instance = nil
 
   DEFAULT_COLORS = {
+    :text => { :fg => "white", :bg => "black" },
     :status => { :fg => "white", :bg => "blue", :attrs => ["bold"] },
     :index_old => { :fg => "white", :bg => "default" },
     :index_new => { :fg => "white", :bg => "default", :attrs => ["bold"] },
diff --git a/lib/sup/contact.rb b/lib/sup/contact.rb
index 382896d..82c059e 100644
--- a/lib/sup/contact.rb
+++ b/lib/sup/contact.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 module Redwood
 
 class ContactManager
@@ -27,9 +29,10 @@ class ContactManager
   def contacts_with_aliases; @a2p.values.uniq end
 
   def update_alias person, aalias=nil
-    if(old_aalias = @p2a[person]) # remove old alias
+    old_aalias = @p2a[person]
+    if(old_aalias != nil and old_aalias != "") # remove old alias
       @a2p.delete old_aalias
-      @e2p.delete old_aalias.email
+      @e2p.delete person.email
     end
     @p2a[person] = aalias
     unless aalias.nil? || aalias.empty?
@@ -53,7 +56,7 @@ class ContactManager
   def is_aliased_contact? person; !@p2a[person].nil? end
 
   def save
-    File.open(@fn, "w") do |f|
+    File.open(@fn, "w:UTF-8") do |f|
       @p2a.sort_by { |(p, a)| [p.full_address, a] }.each do |(p, a)|
         f.puts "#{a || ''}: #{p.full_address}"
       end
diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb
index bc96f88..56cc630 100644
--- a/lib/sup/crypto.rb
+++ b/lib/sup/crypto.rb
@@ -39,6 +39,20 @@ from_key: the key that generated the signature (class is GPGME::Key)
 Return value: an array of lines of output
 EOS
 
+  HookManager.register "gpg-expand-keys", <<EOS
+Runs when the list of encryption recipients is created, allowing you to
+replace a recipient with one or more GPGME recipients. For example, you could
+replace the email address of a mailing list with the key IDs that belong to
+the recipients of that list. This is essentially what GPG groups do, which
+are not supported by GPGME.
+
+Variables:
+recipients: an array of recipients of the current email
+
+Return value: an array of recipients (email address or GPG key ID) to encrypt
+the email for
+EOS
+
   def initialize
     @mutex = Mutex.new
 
@@ -48,28 +62,39 @@ EOS
     @gpgme_present =
       begin
         begin
-          GPGME.check_version({:protocol => GPGME::PROTOCOL_OpenPGP})
+          begin
+            GPGME.check_version({:protocol => GPGME::PROTOCOL_OpenPGP})
+          rescue TypeError
+            GPGME.check_version(nil)
+          end
           true
         rescue GPGME::Error
           false
+        rescue ArgumentError
+          # gpgme 2.0.0 raises this due to the hash->string conversion
+          false
         end
       rescue NameError
         false
       end
 
     unless @gpgme_present
-      @not_working_reason = ['gpgme gem not present', 
+      @not_working_reason = ['gpgme gem not present',
         'Install the gpgme gem in order to use signed and encrypted emails']
       return
     end
 
     # if gpg2 is available, it will start gpg-agent if required
     if (bin = `which gpg2`.chomp) =~ /\S/
-      GPGME.set_engine_info GPGME::PROTOCOL_OpenPGP, bin, nil
+      if GPGME.respond_to?('set_engine_info')
+        GPGME.set_engine_info GPGME::PROTOCOL_OpenPGP, bin, nil
+      else
+        GPGME.gpgme_set_engine_info GPGME::PROTOCOL_OpenPGP, bin, nil
+      end
     else
       # check if the gpg-options hook uses the passphrase_callback
       # if it doesn't then check if gpg agent is present
-      gpg_opts = HookManager.run("gpg-options", 
+      gpg_opts = HookManager.run("gpg-options",
                                {:operation => "sign", :options => {}}) || {}
       if gpg_opts[:passphrase_callback].nil?
         if ENV['GPG_AGENT_INFO'].nil?
@@ -94,22 +119,29 @@ EOS
   end
 
   def have_crypto?; @not_working_reason.nil? end
+  def not_working_reason; @not_working_reason end
 
   def sign from, to, payload
     return unknown_status(@not_working_reason) unless @not_working_reason.nil?
 
     gpg_opts = {:protocol => GPGME::PROTOCOL_OpenPGP, :armor => true, :textmode => true}
     gpg_opts.merge!(gen_sign_user_opts(from))
-    gpg_opts = HookManager.run("gpg-options", 
+    gpg_opts = HookManager.run("gpg-options",
                                {:operation => "sign", :options => gpg_opts}) || gpg_opts
 
     begin
-      sig = GPGME.detach_sign(format_payload(payload), gpg_opts)
+      if GPGME.respond_to?('detach_sign')
+        sig = GPGME.detach_sign(format_payload(payload), gpg_opts)
+      else
+        crypto = GPGME::Crypto.new
+        gpg_opts[:mode] = GPGME::SIG_MODE_DETACH
+        sig = crypto.sign(format_payload(payload), gpg_opts).read
+      end
     rescue GPGME::Error => exc
       raise Error, gpgme_exc_msg(exc.message)
     end
 
-    # if the key (or gpg-agent) is not available GPGME does not complain 
+    # if the key (or gpg-agent) is not available GPGME does not complain
     # but just returns a zero length string. Let's catch that
     if sig.length == 0
       raise Error, gpgme_exc_msg("GPG failed to generate signature: check that gpg-agent is running and your key is available.")
@@ -129,20 +161,26 @@ EOS
 
     gpg_opts = {:protocol => GPGME::PROTOCOL_OpenPGP, :armor => true, :textmode => true}
     if sign
-      gpg_opts.merge!(gen_sign_user_opts(from)) 
+      gpg_opts.merge!(gen_sign_user_opts(from))
       gpg_opts.merge!({:sign => true})
     end
     gpg_opts = HookManager.run("gpg-options",
                                {:operation => "encrypt", :options => gpg_opts}) || gpg_opts
     recipients = to + [from]
-
+    recipients = HookManager.run("gpg-expand-keys", { :recipients => recipients }) || recipients
     begin
-      cipher = GPGME.encrypt(recipients, format_payload(payload), gpg_opts)
+      if GPGME.respond_to?('encrypt')
+        cipher = GPGME.encrypt(recipients, format_payload(payload), gpg_opts)
+      else
+        crypto = GPGME::Crypto.new
+        gpg_opts[:recipients] = recipients
+        cipher = crypto.encrypt(format_payload(payload), gpg_opts).read
+      end
     rescue GPGME::Error => exc
       raise Error, gpgme_exc_msg(exc.message)
     end
 
-    # if the key (or gpg-agent) is not available GPGME does not complain 
+    # if the key (or gpg-agent) is not available GPGME does not complain
     # but just returns a zero length string. Let's catch that
     if cipher.length == 0
       raise Error, gpgme_exc_msg("GPG failed to generate cipher text: check that gpg-agent is running and your key is available.")
@@ -246,7 +284,11 @@ EOS
                                {:operation => "decrypt", :options => gpg_opts}) || gpg_opts
     ctx = GPGME::Ctx.new(gpg_opts)
     cipher_data = GPGME::Data.from_str(format_payload(payload))
-    plain_data = GPGME::Data.empty
+    if GPGME::Data.respond_to?('empty')
+      plain_data = GPGME::Data.empty
+    else
+      plain_data = GPGME::Data.empty!
+    end
     begin
       ctx.decrypt_verify(cipher_data, plain_data)
     rescue GPGME::Error => exc
@@ -259,7 +301,7 @@ EOS
     end
     plain_data.seek(0, IO::SEEK_SET)
     output = plain_data.read
-    output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
+    output.transcode(Encoding::ASCII_8BIT, output.encoding)
 
     ## TODO: test to see if it is still necessary to do a 2nd run if verify
     ## fails.
@@ -274,7 +316,7 @@ EOS
       # Look for Charset, they are put before the base64 crypted part
       charsets = payload.body.split("\n").grep(/^Charset:/)
       if !charsets.empty? and charsets[0] =~ /^Charset: (.+)$/
-        output = Iconv.easy_decode($encoding, $1, output)
+        output.transcode($encoding, $1)
       end
       msg.body = output
     else
@@ -298,7 +340,7 @@ EOS
       msg = RMail::Parser.read output
       if msg.header.content_type =~ %r{^multipart/} && !msg.multipart?
         output = "MIME-Version: 1.0\n" + output
-        output.force_encoding Encoding::ASCII_8BIT if output.respond_to? :force_encoding
+        output.fix_encoding!
         msg = RMail::Parser.read output
       end
     end
@@ -314,7 +356,7 @@ private
 
   def gpgme_exc_msg msg
     err_msg = "Exception in GPGME call: #{msg}"
-    info err_msg
+    #info err_msg
     err_msg
   end
 
@@ -346,7 +388,7 @@ private
       else
         first_sig = "Unknown error or empty signature"
       end
-    rescue EOFError 
+    rescue EOFError
       from_key = nil
       first_sig = "No public key available for #{signature.fingerprint}"
     end
diff --git a/lib/sup/draft.rb b/lib/sup/draft.rb
index 58c45db..27dd556 100644
--- a/lib/sup/draft.rb
+++ b/lib/sup/draft.rb
@@ -32,14 +32,17 @@ class DraftLoader < Source
   attr_accessor :dir
   yaml_properties
 
-  def initialize
-    dir = Redwood::DRAFT_DIR
+  def initialize dir=Redwood::DRAFT_DIR
     Dir.mkdir dir unless File.exists? dir
     super DraftManager.source_name, true, false
     @dir = dir
     @cur_offset = 0
   end
 
+  def properly_initialized?
+    !!(@dir && @cur_offset)
+  end
+
   def id; DraftManager.source_id; end
   def to_s; DraftManager.source_name; end
   def uri; DraftManager.source_name; end
diff --git a/lib/sup/hook.rb b/lib/sup/hook.rb
index 9be1295..31d7921 100644
--- a/lib/sup/hook.rb
+++ b/lib/sup/hook.rb
@@ -1,3 +1,5 @@
+require "sup/util"
+
 module Redwood
 
 class HookManager
@@ -17,6 +19,14 @@ class HookManager
       end
     end
 
+    def flash s
+      if BufferManager.instantiated?
+        BufferManager.flash s
+      else
+        log s
+      end
+    end
+
     def log s
       info "hook[#@__name]: #{s}"
     end
diff --git a/lib/sup/horizontal-selector.rb b/lib/sup/horizontal_selector.rb
similarity index 83%
rename from lib/sup/horizontal-selector.rb
rename to lib/sup/horizontal_selector.rb
index e6ec6dc..4b14410 100644
--- a/lib/sup/horizontal-selector.rb
+++ b/lib/sup/horizontal_selector.rb
@@ -1,6 +1,8 @@
 module Redwood
 
 class HorizontalSelector
+  class UnknownValue < StandardError; end
+
   attr_accessor :label, :changed_by_user
 
   def initialize label, vals, labels, base_color=:horizontal_selector_unselected_color, selected_color=:horizontal_selector_selected_color
@@ -13,7 +15,14 @@ class HorizontalSelector
     @changed_by_user = false
   end
 
-  def set_to val; @selection = @vals.index(val) end
+  def set_to val
+    raise UnknownValue, val.inspect unless can_set_to? val
+    @selection = @vals.index(val)
+  end
+
+  def can_set_to? val
+    @vals.include? val
+  end
 
   def val; @vals[@selection] end
 
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
index 95f104a..6e14b8c 100644
--- a/lib/sup/index.rb
+++ b/lib/sup/index.rb
@@ -1,20 +1,27 @@
 ENV["XAPIAN_FLUSH_THRESHOLD"] = "1000"
+ENV["XAPIAN_CJK_NGRAM"] = "1"
 
 require 'xapian'
 require 'set'
 require 'fileutils'
 require 'monitor'
+require 'chronic'
 
-begin
-  require 'chronic'
-  $have_chronic = true
-rescue LoadError => e
-  debug "No 'chronic' gem detected. Install it for date/time query restrictions."
-  $have_chronic = false
-end
+require "sup/util/query"
+require "sup/interactive_lock"
+require "sup/hook"
+require "sup/logger/singleton"
+
+
+if ([Xapian.major_version, Xapian.minor_version, Xapian.revision] <=> [1,2,15]) < 0
+  fail <<-EOF
+\n
+Xapian version 1.2.15 or higher required.
+If you have xapian-full-alaveteli installed,
+Please remove it by running `gem uninstall xapian-full-alaveteli`
+since it's been replaced by the xapian-ruby gem.
 
-if ([Xapian.major_version, Xapian.minor_version, Xapian.revision] <=> [1,2,1]) < 0
-	fail "Xapian version 1.2.1 or higher required"
+  EOF
 end
 
 module Redwood
@@ -25,7 +32,6 @@ module Redwood
 class Index
   include InteractiveLock
 
-  STEM_LANGUAGE = "english"
   INDEX_VERSION = '4'
 
   ## dates are converted to integers for xapian, and are used for document ids,
@@ -162,6 +168,32 @@ EOS
     matchset.matches_estimated
   end
 
+  ## check if a message is part of a killed thread
+  ## (warning: duplicates code below)
+  ## NOTE: We can be more efficient if we assume every
+  ## killed message that hasn't been initially added
+  ## to the indexi s this way
+  def message_joining_killed? m
+    return false unless doc = find_doc(m.id)
+    queue = doc.value(THREAD_VALUENO).split(',')
+    seen_threads = Set.new
+    seen_messages = Set.new [m.id]
+    while not queue.empty?
+      thread_id = queue.pop
+      next if seen_threads.member? thread_id
+      return true if thread_killed?(thread_id)
+      seen_threads << thread_id
+      docs = term_docids(mkterm(:thread, thread_id)).map { |x| @xapian.document x }
+      docs.each do |doc|
+        msgid = doc.value MSGID_VALUENO
+        next if seen_messages.member? msgid
+        seen_messages << msgid
+        queue.concat doc.value(THREAD_VALUENO).split(',')
+      end
+    end
+    false
+  end
+
   ## yield all messages in the thread containing 'm' by repeatedly
   ## querying the index. yields pairs of message ids and
   ## message-building lambdas, so that building an unwanted message
@@ -242,11 +274,11 @@ EOS
 
   ## Yield each message-id matching query
   EACH_ID_PAGE = 100
-  def each_id query={}
+  def each_id query={}, ignore_neg_terms = true
     offset = 0
     page = EACH_ID_PAGE
 
-    xapian_query = build_xapian_query query
+    xapian_query = build_xapian_query query, ignore_neg_terms
     while true
       ids = run_query_ids xapian_query, offset, (offset+page)
       ids.each { |id| yield id }
@@ -256,12 +288,21 @@ EOS
   end
 
   ## Yield each message matching query
-  def each_message query={}, &b
-    each_id query do |id|
+  ## The ignore_neg_terms parameter is used to display result even if
+  ## it contains "forbidden" labels such as :deleted, it is used in
+  ## Poll#poll_from when we need to get the location of a message that
+  ## may contain these labels
+  def each_message query={}, ignore_neg_terms = true, &b
+    each_id query, ignore_neg_terms do |id|
       yield build_message(id)
     end
   end
 
+  # Search messages. Returns an Enumerator.
+  def find_messages query_expr
+    enum_for :each_message, parse_query(query_expr)
+  end
+
   # wrap all future changes inside a transaction so they're done atomically
   def begin_transaction
     synchronize { @xapian.begin_transaction }
@@ -302,14 +343,56 @@ EOS
   ## Yields (in lexicographical order) the source infos of all locations from
   ## the given source with the given source_info prefix
   def each_source_info source_id, prefix='', &b
-    prefix = mkterm :location, source_id, prefix
-    each_prefixed_term prefix do |x|
-      yield x[prefix.length..-1]
+    p = mkterm :location, source_id, prefix
+    each_prefixed_term p do |x|
+      yield prefix + x[p.length..-1]
     end
   end
 
   class ParseError < StandardError; end
 
+  # Stemmed
+  NORMAL_PREFIX = {
+    'subject' => {:prefix => 'S', :exclusive => false},
+    'body' => {:prefix => 'B', :exclusive => false},
+    'from_name' => {:prefix => 'FN', :exclusive => false},
+    'to_name' => {:prefix => 'TN', :exclusive => false},
+    'name' => {:prefix => %w(FN TN), :exclusive => false},
+    'attachment' => {:prefix => 'A', :exclusive => false},
+    'email_text' => {:prefix => 'E', :exclusive => false},
+    '' => {:prefix => %w(S B FN TN A E), :exclusive => false},
+  }
+
+  # Unstemmed
+  BOOLEAN_PREFIX = {
+    'type' => {:prefix => 'K', :exclusive => true},
+    'from_email' => {:prefix => 'FE', :exclusive => false},
+    'to_email' => {:prefix => 'TE', :exclusive => false},
+    'email' => {:prefix => %w(FE TE), :exclusive => false},
+    'date' => {:prefix => 'D', :exclusive => true},
+    'label' => {:prefix => 'L', :exclusive => false},
+    'source_id' => {:prefix => 'I', :exclusive => true},
+    'attachment_extension' => {:prefix => 'O', :exclusive => false},
+    'msgid' => {:prefix => 'Q', :exclusive => true},
+    'id' => {:prefix => 'Q', :exclusive => true},
+    'thread' => {:prefix => 'H', :exclusive => false},
+    'ref' => {:prefix => 'R', :exclusive => false},
+    'location' => {:prefix => 'J', :exclusive => false},
+  }
+
+  PREFIX = NORMAL_PREFIX.merge BOOLEAN_PREFIX
+
+  COMPL_OPERATORS = %w[AND OR NOT]
+  COMPL_PREFIXES = (
+    %w[
+      from to
+      is has label
+      filename filetypem
+      before on in during after
+      limit
+    ] + NORMAL_PREFIX.keys + BOOLEAN_PREFIX.keys
+  ).map{|p|"#{p}:"} + COMPL_OPERATORS
+
   ## parse a query string from the user. returns a query object
   ## that can be passed to any index method with a 'query'
   ## argument.
@@ -389,27 +472,25 @@ EOS
       end
     end
 
-    if $have_chronic
-      lastdate = 2<<32 - 1
-      firstdate = 0
-      subs = subs.gsub(/\b(before|on|in|during|after):(\((.+?)\)\B|(\S+)\b)/) do
-        field, datestr = $1, ($3 || $4)
-        realdate = Chronic.parse datestr, :guess => false, :context => :past
-        if realdate
-          case field
-          when "after"
-            debug "chronic: translated #{field}:#{datestr} to #{realdate.end}"
-            "date:#{realdate.end.to_i}..#{lastdate}"
-          when "before"
-            debug "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
-            "date:#{firstdate}..#{realdate.end.to_i}"
-          else
-            debug "chronic: translated #{field}:#{datestr} to #{realdate}"
-            "date:#{realdate.begin.to_i}..#{realdate.end.to_i}"
-          end
+    lastdate = 2<<32 - 1
+    firstdate = 0
+    subs = subs.gsub(/\b(before|on|in|during|after):(\((.+?)\)\B|(\S+)\b)/) do
+      field, datestr = $1, ($3 || $4)
+      realdate = Chronic.parse datestr, :guess => false, :context => :past
+      if realdate
+        case field
+        when "after"
+          debug "chronic: translated #{field}:#{datestr} to #{realdate.end}"
+          "date:#{realdate.end.to_i}..#{lastdate}"
+        when "before"
+          debug "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
+          "date:#{firstdate}..#{realdate.end.to_i}"
         else
-          raise ParseError, "can't understand date #{datestr.inspect}"
+          debug "chronic: translated #{field}:#{datestr} to #{realdate}"
+          "date:#{realdate.begin.to_i}..#{realdate.end.to_i}"
         end
+      else
+        raise ParseError, "can't understand date #{datestr.inspect}"
       end
     end
 
@@ -428,7 +509,7 @@ EOS
 
     qp = Xapian::QueryParser.new
     qp.database = @xapian
-    qp.stemmer = Xapian::Stem.new(STEM_LANGUAGE)
+    qp.stemmer = Xapian::Stem.new($config[:stem_language])
     qp.stemming_strategy = Xapian::QueryParser::STEM_SOME
     qp.default_op = Xapian::Query::OP_AND
     qp.add_valuerangeprocessor(Xapian::NumberValueRangeProcessor.new(DATE_VALUENO, 'date:', true))
@@ -441,7 +522,7 @@ EOS
       raise ParseError, "xapian query parser error: #{e}"
     end
 
-    debug "parsed xapian query: #{xapian_query.description}"
+    debug "parsed xapian query: #{Util::Query.describe(xapian_query, subs)}"
 
     raise ParseError if xapian_query.nil? or xapian_query.empty?
     query[:qobj] = xapian_query
@@ -449,14 +530,18 @@ EOS
     query
   end
 
+  def save_message m
+    if @sync_worker
+      @sync_queue << m
+    else
+      update_message_state m
+    end
+    m.clear_dirty
+  end
+
   def save_thread t
     t.each_dirty_message do |m|
-      if @sync_worker
-        @sync_queue << m
-      else
-        update_message_state m
-      end
-      m.clear_dirty
+      save_message m
     end
   end
 
@@ -482,37 +567,6 @@ EOS
 
   private
 
-  # Stemmed
-  NORMAL_PREFIX = {
-    'subject' => {:prefix => 'S', :exclusive => false},
-    'body' => {:prefix => 'B', :exclusive => false},
-    'from_name' => {:prefix => 'FN', :exclusive => false},
-    'to_name' => {:prefix => 'TN', :exclusive => false},
-    'name' => {:prefix => %w(FN TN), :exclusive => false},
-    'attachment' => {:prefix => 'A', :exclusive => false},
-    'email_text' => {:prefix => 'E', :exclusive => false},
-    '' => {:prefix => %w(S B FN TN A E), :exclusive => false},
-  }
-
-  # Unstemmed
-  BOOLEAN_PREFIX = {
-    'type' => {:prefix => 'K', :exclusive => true},
-    'from_email' => {:prefix => 'FE', :exclusive => false},
-    'to_email' => {:prefix => 'TE', :exclusive => false},
-    'email' => {:prefix => %w(FE TE), :exclusive => false},
-    'date' => {:prefix => 'D', :exclusive => true},
-    'label' => {:prefix => 'L', :exclusive => false},
-    'source_id' => {:prefix => 'I', :exclusive => true},
-    'attachment_extension' => {:prefix => 'O', :exclusive => false},
-    'msgid' => {:prefix => 'Q', :exclusive => true},
-    'id' => {:prefix => 'Q', :exclusive => true},
-    'thread' => {:prefix => 'H', :exclusive => false},
-    'ref' => {:prefix => 'R', :exclusive => false},
-    'location' => {:prefix => 'J', :exclusive => false},
-  }
-
-  PREFIX = NORMAL_PREFIX.merge BOOLEAN_PREFIX
-
   MSGID_VALUENO = 0
   THREAD_VALUENO = 1
   DATE_VALUENO = 2
@@ -594,7 +648,7 @@ EOS
   end
 
   Q = Xapian::Query
-  def build_xapian_query opts
+  def build_xapian_query opts, ignore_neg_terms = true
     labels = ([opts[:label]] + (opts[:labels] || [])).compact
     neglabels = [:spam, :deleted, :killed].reject { |l| (labels.include? l) || opts.member?("load_#{l}".intern) }
     pos_terms, neg_terms = [], []
@@ -610,7 +664,7 @@ EOS
       pos_terms << Q.new(Q::OP_OR, participant_terms)
     end
 
-    neg_terms.concat(neglabels.map { |l| mkterm(:label,l) })
+    neg_terms.concat(neglabels.map { |l| mkterm(:label,l) }) if ignore_neg_terms
 
     pos_query = Q.new(Q::OP_AND, pos_terms)
     neg_query = Q.new(Q::OP_OR, neg_terms)
@@ -623,6 +677,10 @@ EOS
   end
 
   def sync_message m, overwrite
+    ## TODO: we should not save the message if the sync_back failed
+    ## since it would overwrite the location field
+    m.sync_back
+
     doc = synchronize { find_doc(m.id) }
     existed = doc != nil
     doc ||= Xapian::Document.new
@@ -804,7 +862,7 @@ class Xapian::Document
 
   def index_text text, prefix, weight=1
     term_generator = Xapian::TermGenerator.new
-    term_generator.stemmer = Xapian::Stem.new(Redwood::Index::STEM_LANGUAGE)
+    term_generator.stemmer = Xapian::Stem.new($config[:stem_language])
     term_generator.document = self
     term_generator.index_text text, weight, prefix
   end
diff --git a/lib/sup/interactive-lock.rb b/lib/sup/interactive_lock.rb
similarity index 100%
rename from lib/sup/interactive-lock.rb
rename to lib/sup/interactive_lock.rb
diff --git a/lib/sup/label.rb b/lib/sup/label.rb
index 1699896..3cdfa14 100644
--- a/lib/sup/label.rb
+++ b/lib/sup/label.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 module Redwood
 
 class LabelManager
@@ -5,10 +7,10 @@ class LabelManager
 
   ## labels that have special semantics. user will be unable to
   ## add/remove these via normal label mechanisms.
-  RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox, :attachment ]
+  RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox, :attachment, :forwarded, :replied ]
 
   ## labels that will typically be hidden from the user
-  HIDDEN_RESERVED_LABELS = [ :starred, :unread, :attachment ]
+  HIDDEN_RESERVED_LABELS = [ :starred, :unread, :attachment, :forwarded, :replied ]
 
   def initialize fn
     @fn = fn
@@ -77,7 +79,7 @@ class LabelManager
 
   def save
     return unless @modified
-    File.open(@fn, "w") { |f| f.puts @labels.keys.sort_by { |l| l.to_s } }
+    File.open(@fn, "w:UTF-8") { |f| f.puts @labels.keys.sort_by { |l| l.to_s } }
     @new_labels = {}
   end
 end
diff --git a/lib/sup/logger.rb b/lib/sup/logger.rb
index 46e8a08..514b78e 100644
--- a/lib/sup/logger.rb
+++ b/lib/sup/logger.rb
@@ -1,4 +1,4 @@
-require "sup"
+require "sup/util"
 require 'stringio'
 require 'thread'
 
@@ -60,7 +60,10 @@ private
   ## actually distribute the message
   def send_message m
     @mutex.synchronize do
-      @sinks.each { |sink| sink << m }
+      @sinks.each do |sink|
+        sink << m
+        sink.flush if sink.respond_to?(:flush) and level == "debug"
+      end
       @buf << m
     end
   end
diff --git a/lib/sup/logger/singleton.rb b/lib/sup/logger/singleton.rb
new file mode 100644
index 0000000..3321785
--- /dev/null
+++ b/lib/sup/logger/singleton.rb
@@ -0,0 +1,10 @@
+# TODO: this is ugly. It's better to have a application singleton passed
+# down to lower level components instead of including logging methods in
+# class `Object'
+#
+# For now this is what we have to do.
+require "sup/logger"
+Redwood::Logger.init.add_sink $stderr
+class Object
+  include Redwood::LogsStuff
+end
diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb
index 0c3061c..c767cf6 100644
--- a/lib/sup/maildir.rb
+++ b/lib/sup/maildir.rb
@@ -1,4 +1,5 @@
 require 'uri'
+require 'set'
 
 module Redwood
 
@@ -7,8 +8,8 @@ class Maildir < Source
   MYHOSTNAME = Socket.gethostname
 
   ## remind me never to use inheritance again.
-  yaml_properties :uri, :usual, :archived, :id, :labels
-  def initialize uri, usual=true, archived=false, id=nil, labels=[]
+  yaml_properties :uri, :usual, :archived, :sync_back, :id, :labels
+  def initialize uri, usual=true, archived=false, sync_back=true, id=nil, labels=[]
     super uri, usual, archived, id
     @expanded_uri = Source.expand_filesystem_uri(uri)
     uri = URI(@expanded_uri)
@@ -17,16 +18,28 @@ class Maildir < Source
     raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
     raise ArgumentError, "maildir URI must have a path component" unless uri.path
 
+    @sync_back = sync_back
+    # sync by default if not specified
+    @sync_back = true if @sync_back.nil?
+
     @dir = uri.path
     @labels = Set.new(labels || [])
     @mutex = Mutex.new
-    @mtimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
+    @ctimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
   end
 
   def file_path; @dir end
   def self.suggest_labels_for path; [] end
   def is_source_for? uri; super || (uri == @expanded_uri); end
 
+  def supported_labels?
+    [:draft, :starred, :forwarded, :replied, :unread, :deleted]
+  end
+
+  def sync_back_enabled?
+    @sync_back
+  end
+
   def store_message date, from_email, &block
     stored = false
     new_fn = new_maildir_basefn + ':2,S'
@@ -44,7 +57,7 @@ class Maildir < Source
             f.fsync
           end
 
-          File.link tmp_path, new_path
+          File.safe_link tmp_path, new_path
           stored = true
         ensure
           File.unlink tmp_path if File.exists? tmp_path
@@ -71,6 +84,14 @@ class Maildir < Source
     with_file_for(id) { |f| RMail::Parser.read f }
   end
 
+  def sync_back id, labels
+    synchronize do
+      debug "syncing back maildir message #{id} with flags #{labels.to_a}"
+      flags = maildir_reconcile_flags id, labels
+      maildir_mark_file id, flags
+    end
+  end
+
   def raw_header id
     ret = ""
     with_file_for(id) do |f|
@@ -87,41 +108,78 @@ class Maildir < Source
 
   ## XXX use less memory
   def poll
-    @mtimes.each do |d,prev_mtime|
+    added = []
+    deleted = []
+    updated = []
+    @ctimes.each do |d,prev_ctime|
       subdir = File.join @dir, d
       debug "polling maildir #{subdir}"
       raise FatalSourceError, "#{subdir} not a directory" unless File.directory? subdir
-      mtime = File.mtime subdir
-      next if prev_mtime >= mtime
-      @mtimes[d] = mtime
+      ctime = File.ctime subdir
+      next if prev_ctime >= ctime
+      @ctimes[d] = ctime
 
       old_ids = benchmark(:maildir_read_index) { Enumerator.new(Index.instance, :each_source_info, self.id, "#{d}/").to_a }
-      new_ids = benchmark(:maildir_read_dir) { Dir.glob("#{subdir}/*").map { |x| File.basename x }.sort }
-      added = new_ids - old_ids
-      deleted = old_ids - new_ids
+      new_ids = benchmark(:maildir_read_dir) { Dir.glob("#{subdir}/*").map { |x| File.join(d,File.basename(x)) }.sort }
+      added += new_ids - old_ids
+      deleted += old_ids - new_ids
       debug "#{old_ids.size} in index, #{new_ids.size} in filesystem"
-      debug "#{added.size} added, #{deleted.size} deleted"
+    end
 
-      added.each_with_index do |id,i|
-        yield :add,
-          :info => File.join(d,id),
-          :labels => @labels + maildir_labels(id) + [:inbox],
-          :progress => i.to_f/(added.size+deleted.size)
-      end
+    ## find updated mails by checking if an id is in both added and
+    ## deleted arrays, meaning that its flags changed or that it has
+    ## been moved, these ids need to be removed from added and deleted
+    add_to_delete = del_to_delete = []
+    map = Hash.new { |hash, key| hash[key] = [] }
+    deleted.each do |id_del|
+        map[maildir_data(id_del)[0]].push id_del
+    end
+    added.each do |id_add|
+        map[maildir_data(id_add)[0]].each do |id_del|
+          updated.push [ id_del, id_add ]
+          add_to_delete.push id_add
+          del_to_delete.push id_del
+        end
+    end
+    added -= add_to_delete
+    deleted -= del_to_delete
+    debug "#{added.size} added, #{deleted.size} deleted, #{updated.size} updated"
+    total_size = added.size+deleted.size+updated.size
 
-      deleted.each_with_index do |id,i|
-        yield :delete,
-          :info => File.join(d,id),
-          :progress => (i.to_f+added.size)/(added.size+deleted.size)
-      end
+    added.each_with_index do |id,i|
+      yield :add,
+      :info => id,
+      :labels => @labels + maildir_labels(id) + [:inbox],
+      :progress => i.to_f/total_size
+    end
+
+    deleted.each_with_index do |id,i|
+      yield :delete,
+      :info => id,
+      :progress => (i.to_f+added.size)/total_size
+    end
+
+    updated.each_with_index do |id,i|
+      yield :update,
+      :old_info => id[0],
+      :new_info => id[1],
+      :labels => @labels + maildir_labels(id[1]),
+      :progress => (i.to_f+added.size+deleted.size)/total_size
     end
     nil
   end
 
+  def labels? id
+    maildir_labels id
+  end
+
   def maildir_labels id
     (seen?(id) ? [] : [:unread]) +
       (trashed?(id) ?  [:deleted] : []) +
-      (flagged?(id) ? [:starred] : [])
+      (flagged?(id) ? [:starred] : []) +
+      (passed?(id) ? [:forwarded] : []) +
+      (replied?(id) ? [:replied] : []) +
+      (draft?(id) ? [:draft] : [])
   end
 
   def draft? id; maildir_data(id)[2].include? "D"; end
@@ -131,13 +189,6 @@ class Maildir < Source
   def seen? id; maildir_data(id)[2].include? "S"; end
   def trashed? id; maildir_data(id)[2].include? "T"; end
 
-  def mark_draft id; maildir_mark_file id, "D" unless draft? id; end
-  def mark_flagged id; maildir_mark_file id, "F" unless flagged? id; end
-  def mark_passed id; maildir_mark_file id, "P" unless passed? id; end
-  def mark_replied id; maildir_mark_file id, "R" unless replied? id; end
-  def mark_seen id; maildir_mark_file id, "S" unless seen? id; end
-  def mark_trashed id; maildir_mark_file id, "T" unless trashed? id; end
-
   def valid? id
     File.exists? File.join(@dir, id)
   end
@@ -159,25 +210,47 @@ private
   end
 
   def maildir_data id
-    id =~ %r{^([^:]+):([12]),([DFPRST]*)$}
+    id = File.basename id
+    # Flags we recognize are DFPRST
+    id =~ %r{^([^:]+):([12]),([A-Za-z]*)$}
     [($1 || id), ($2 || "2"), ($3 || "")]
   end
 
-  ## not thread-safe on msg
-  def maildir_mark_file msg, flag
-    orig_path = @ids_to_fns[msg]
-    orig_base, orig_fn = File.split(orig_path)
-    new_base = orig_base.slice(0..-4) + 'cur'
-    tmp_base = orig_base.slice(0..-4) + 'tmp'
-    md_base, md_ver, md_flags = maildir_data msg
-    md_flags += flag; md_flags = md_flags.split(//).sort.join.squeeze
-    new_path = File.join new_base, "#{md_base}:#{md_ver},#{md_flags}"
-    tmp_path = File.join tmp_base, "#{md_base}:#{md_ver},#{md_flags}"
-    File.link orig_path, tmp_path
-    File.unlink orig_path
-    File.link tmp_path, new_path
-    File.unlink tmp_path
-    @ids_to_fns[msg] = new_path
+  def maildir_reconcile_flags id, labels
+      new_flags = Set.new( maildir_data(id)[2].each_char )
+
+      # Set flags based on labels for the six flags we recognize
+      if labels.member? :draft then new_flags.add?( "D" ) else new_flags.delete?( "D" ) end
+      if labels.member? :starred then new_flags.add?( "F" ) else new_flags.delete?( "F" ) end
+      if labels.member? :forwarded then new_flags.add?( "P" ) else new_flags.delete?( "P" ) end
+      if labels.member? :replied then new_flags.add?( "R" ) else new_flags.delete?( "R" ) end
+      if not labels.member? :unread then new_flags.add?( "S" ) else new_flags.delete?( "S" ) end
+      if labels.member? :deleted or labels.member? :killed then new_flags.add?( "T" ) else new_flags.delete?( "T" ) end
+
+      ## Flags must be stored in ASCII order according to Maildir
+      ## documentation
+      new_flags.to_a.sort.join
+  end
+
+  def maildir_mark_file orig_path, flags
+    @mutex.synchronize do
+      new_base = (flags.include?("S")) ? "cur" : "new"
+      md_base, md_ver, md_flags = maildir_data orig_path
+
+      return if md_flags == flags
+
+      new_loc = File.join new_base, "#{md_base}:#{md_ver},#{flags}"
+      orig_path = File.join @dir, orig_path
+      new_path  = File.join @dir, new_loc
+      tmp_path  = File.join @dir, "tmp", "#{md_base}:#{md_ver},#{flags}"
+
+      File.safe_link orig_path, tmp_path
+      File.unlink orig_path
+      File.safe_link tmp_path, new_path
+      File.unlink tmp_path
+
+      new_loc
+    end
   end
 end
 
diff --git a/lib/sup/mbox.rb b/lib/sup/mbox.rb
index 95753c4..b9187fa 100644
--- a/lib/sup/mbox.rb
+++ b/lib/sup/mbox.rb
@@ -120,7 +120,7 @@ class MBox < Source
   ## into memory with raw_message.
   ##
   ## i hoped never to have to move shit around on disk but
-  ## sup-sync-back has to do it.
+  ## sup-sync-back-mbox has to do it.
   def each_raw_message_line offset
     @mutex.synchronize do
       ensure_open
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
index 66745ca..567f21f 100644
--- a/lib/sup/message.rb
+++ b/lib/sup/message.rb
@@ -69,7 +69,9 @@ class Message
     return unless v
     return v unless v.is_a? String
     return unless v.size < MAX_HEADER_VALUE_SIZE # avoid regex blowup on spam
-    Rfc2047.decode_to $encoding, Iconv.easy_decode($encoding, 'ASCII', v)
+    d = v.dup
+    d = d.transcode($encoding, 'ASCII')
+    Rfc2047.decode_to $encoding, d
   end
 
   def parse_header encoded_header
@@ -109,7 +111,9 @@ class Message
       Time.now
     end
 
-    @subj = header["subject"] ? header["subject"].gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
+    subj = header["subject"]
+    subj = subj ? subj.fix_encoding! : nil
+    @subj = subj ? subj.gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
     @to = Person.from_address_list header["to"]
     @cc = Person.from_address_list header["cc"]
     @bcc = Person.from_address_list header["bcc"]
@@ -260,6 +264,8 @@ class Message
       rescue SourceError, SocketError, RMail::EncodingUnsupportedError => e
         warn "problem reading message #{id}"
         [Chunk::Text.new(error_message.split("\n"))]
+
+        debug "could not load message: #{location.inspect}, exception: #{e.inspect}"
       end
   end
 
@@ -285,6 +291,32 @@ EOS
     location.each_raw_message_line &b
   end
 
+  def sync_back
+    @locations.map { |l| l.sync_back @labels, self }.any? do
+      UpdateManager.relay self, :updated, self
+    end
+  end
+
+  def merge_labels_from_locations merge_labels
+    ## Get all labels from all locations
+    location_labels = Set.new([])
+
+    @locations.each do |l|
+      if l.valid?
+        location_labels = location_labels.union(l.labels?)
+      end
+    end
+
+    ## Add to the message labels the intersection between all location
+    ## labels and those we want to merge
+    location_labels = location_labels.intersection(merge_labels.to_set)
+
+    if not location_labels.empty?
+      @labels = @labels.union(location_labels)
+      @dirty = true
+    end
+  end
+
   ## returns all the content from a message that will be indexed
   def indexable_content
     load_from_source!
@@ -303,7 +335,7 @@ EOS
   end
 
   def indexable_chunks
-    chunks.select { |c| c.is_a? Chunk::Text }
+    chunks.select { |c| c.is_a? Chunk::Text } || []
   end
 
   def indexable_subject
@@ -524,7 +556,7 @@ private
           ## if there's no charset, use the current encoding as the charset.
           ## this ensures that the body is normalized to avoid non-displayable
           ## characters
-          body = Iconv.easy_decode($encoding, m.charset || $encoding, m.decode)
+          body = m.decode.transcode($encoding, m.charset)
         else
           body = ""
         end
@@ -539,12 +571,22 @@ private
   ## (and possible signed) inline GPG messages
   def inline_gpg_to_chunks body, encoding_to, encoding_from
     lines = body.split("\n")
+
+    # First case: Message is enclosed between
+    #
+    # -----BEGIN PGP SIGNED MESSAGE-----
+    # and
+    # -----END PGP SIGNED MESSAGE-----
+    #
+    # In some cases, END PGP SIGNED MESSAGE doesn't appear
     gpg = lines.between(GPG_SIGNED_START, GPG_SIGNED_END)
+    # between does not check if GPG_END actually exists
+    # Reference: http://permalink.gmane.org/gmane.mail.sup.devel/641
     if !gpg.empty?
       msg = RMail::Message.new
       msg.body = gpg.join("\n")
 
-      body = Iconv.easy_decode(encoding_to, encoding_from, body)
+      body = body.transcode(encoding_to, encoding_from)
       lines = body.split("\n")
       sig = lines.between(GPG_SIGNED_START, GPG_SIG_START)
       startidx = lines.index(GPG_SIGNED_START)
@@ -552,14 +594,21 @@ private
       before = startidx != 0 ? lines[0 .. startidx-1] : []
       after = endidx ? lines[endidx+1 .. lines.size] : []
 
+      # sig contains BEGIN PGP SIGNED MESSAGE and END PGP SIGNATURE, so
+      # we ditch them. sig may also contain the hash used by PGP (with a
+      # newline), so we also skip them
+      sig_start = sig[1].match(/^Hash:/) ? 3 : 1
+      sig_end = sig.size-2
       payload = RMail::Message.new
-      payload.body = sig[1, sig.size-2].join("\n")
+      payload.body = sig[sig_start, sig_end].join("\n")
       return [text_to_chunks(before, false),
               CryptoManager.verify(nil, msg, false),
               message_to_chunks(payload),
               text_to_chunks(after, false)].flatten.compact
     end
 
+    # Second case: Message is encrypted
+
     gpg = lines.between(GPG_START, GPG_END)
     # between does not check if GPG_END actually exists
     if !gpg.empty? && !lines.index(GPG_END).nil?
@@ -615,7 +664,7 @@ private
         ## like ":a:a:a:a:a" that occurred in certain emails.
         if line =~ QUOTE_PATTERN || (line =~ /:$/ && line =~ /\w/ && nextline =~ QUOTE_PATTERN)
           newstate = :quote
-        elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
+        elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE && !lines[(i+1)..-1].index { |l| l =~ /^-- $/ }
           newstate = :sig
         elsif line =~ BLOCK_QUOTE_PATTERN
           newstate = :block_quote
@@ -696,6 +745,24 @@ class Location
     source.raw_message info
   end
 
+  def sync_back labels, message
+    synced = false
+    return synced unless sync_back_enabled? and valid?
+    source.synchronize do
+      new_info = source.sync_back(@info, labels)
+      if new_info
+        @info = new_info
+        Index.sync_message message, true
+        synced = true
+      end
+    end
+    synced
+  end
+
+  def sync_back_enabled?
+    source.respond_to? :sync_back and $config[:sync_back_to_maildir] and source.sync_back_enabled?
+  end
+
   ## much faster than raw_message
   def each_raw_message_line &b
     source.each_raw_message_line info, &b
@@ -709,6 +776,10 @@ class Location
     source.valid? info
   end
 
+  def labels?
+    source.labels? info
+  end
+
   def == o
     o.source.id == source.id and o.info == info
   end
diff --git a/lib/sup/message-chunks.rb b/lib/sup/message_chunks.rb
similarity index 76%
rename from lib/sup/message-chunks.rb
rename to lib/sup/message_chunks.rb
index 7a061d9..e091e0b 100644
--- a/lib/sup/message-chunks.rb
+++ b/lib/sup/message_chunks.rb
@@ -1,4 +1,6 @@
 require 'tempfile'
+require 'rbconfig'
+require 'shellwords'
 
 ## Here we define all the "chunks" that a message is parsed
 ## into. Chunks are used by ThreadViewMode to render a message. Chunks
@@ -58,6 +60,8 @@ end
 module Redwood
 module Chunk
   class Attachment
+    ## please see note in write_to_disk on important usage
+    ## of quotes to avoid remote command injection.
     HookManager.register "mime-decode", <<EOS
 Decodes a MIME attachment into text form. The text will be displayed
 directly in Sup. For attachments that you wish to use a separate program
@@ -74,6 +78,9 @@ Return value:
   The decoded text of the attachment, or nil if not decoded.
 EOS
 
+
+    ## please see note in write_to_disk on important usage
+    ## of quotes to avoid remote command injection.
     HookManager.register "mime-view", <<EOS
 Views a non-text MIME attachment. This hook allows you to run
 third-party programs for attachments that require such a thing (e.g.
@@ -99,8 +106,18 @@ EOS
     attr_reader :content_type, :filename, :lines, :raw_content
     bool_reader :quotable
 
+    ## store tempfile objects as class variables so that they
+    ## are not removed when the viewing process returns. they
+    ## should be garbage collected when the class variable is removed.
+    @@view_tempfiles = []
+
     def initialize content_type, filename, encoded_content, sibling_types
       @content_type = content_type.downcase
+      if Shellwords.escape(@content_type) != @content_type
+        warn "content_type #{@content_type} is not safe, changed to application/octet-stream"
+        @content_type = 'application/octet-stream'
+      end
+
       @filename = filename
       @quotable = false # changed to true if we can parse it through the
                         # mime-decode hook, or if it's plain text
@@ -115,7 +132,9 @@ EOS
       when /^text\/plain\b/
         @raw_content
       else
-        HookManager.run "mime-decode", :content_type => content_type,
+        ## please see note in write_to_disk on important usage
+        ## of quotes to avoid remote command injection.
+        HookManager.run "mime-decode", :content_type => @content_type,
                         :filename => lambda { write_to_disk },
                         :charset => encoded_content.charset,
                         :sibling_types => sibling_types
@@ -123,13 +142,19 @@ EOS
 
       @lines = nil
       if text
-        text = text.transcode(encoded_content.charset || $encoding)
-        @lines = text.gsub("\r\n", "\n").gsub(/\t/, "        ").gsub(/\r/, "").split("\n")
+        text = text.transcode(encoded_content.charset || $encoding, text.encoding)
+        begin
+          @lines = text.gsub("\r\n", "\n").gsub(/\t/, "        ").gsub(/\r/, "").split("\n")
+        rescue Encoding::CompatibilityError
+          @lines = text.fix_encoding!.gsub("\r\n", "\n").gsub(/\t/, "        ").gsub(/\r/, "").split("\n")
+          debug "error while decoding message text, falling back to default encoding, expect errors in encoding: #{text.fix_encoding!}"
+        end
+
         @quotable = true
       end
     end
 
-    def color; :none end
+    def color; :text_color end
     def patina_color; :attachment_color end
     def patina_text
       if expandable?
@@ -146,11 +171,13 @@ EOS
     def initial_state; :open end
     def viewable?; @lines.nil? end
     def view_default! path
-      case Config::CONFIG['arch']
+      ## please see note in write_to_disk on important usage
+      ## of quotes to avoid remote command injection.
+      case RbConfig::CONFIG['arch']
         when /darwin/
-          cmd = "open '#{path}'"
+          cmd = "open #{path}"
         else
-          cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'"
+          cmd = "/usr/bin/run-mailcap --action=view #{@content_type}:#{path}"
       end
       debug "running: #{cmd.inspect}"
       BufferManager.shell_out(cmd)
@@ -158,17 +185,31 @@ EOS
     end
 
     def view!
-      path = write_to_disk
-      ret = HookManager.run "mime-view", :content_type => @content_type,
-                                         :filename => path
-      ret || view_default!(path)
+      ## please see note in write_to_disk on important usage
+      ## of quotes to avoid remote command injection.
+      write_to_disk do |file|
+
+        @@view_tempfiles.push file # make sure the tempfile is not garbage collected before sup stops
+
+        ret = HookManager.run "mime-view", :content_type => @content_type,
+                                           :filename => file.path
+        ret || view_default!(file.path)
+      end
     end
 
+    ## note that the path returned from write_to_disk is
+    ## Shellwords.escaped and is intended to be used without single
+    ## or double quotes. the use of either opens sup up for remote
+    ## code injection through the file name.
     def write_to_disk
-      file = Tempfile.new(["sup", @filename.gsub("/", "_") || "sup-attachment"])
-      file.print @raw_content
-      file.close
-      file.path
+      begin
+        file = Tempfile.new(["sup", Shellwords.escape(@filename.gsub("/", "_")) || "sup-attachment"])
+        file.print @raw_content
+        yield file if block_given?
+        return file.path
+      ensure
+        file.close
+      end
     end
 
     ## used when viewing the attachment as text
@@ -190,7 +231,7 @@ EOS
     def quotable?; true end
     def expandable?; false end
     def viewable?; false end
-    def color; :none end
+    def color; :text_color end
   end
 
   class Quote
@@ -228,7 +269,7 @@ EOS
   class EnclosedMessage
     attr_reader :lines
     def initialize from, to, cc, date, subj
-      @from = from ? "unknown sender" : from.full_adress
+      @from = from ? "unknown sender" : from.full_address
       @to = to ? "" : to.map { |p| p.full_address }.join(", ")
       @cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
       if date
diff --git a/lib/sup/modes/buffer-list-mode.rb b/lib/sup/modes/buffer_list_mode.rb
similarity index 100%
rename from lib/sup/modes/buffer-list-mode.rb
rename to lib/sup/modes/buffer_list_mode.rb
diff --git a/lib/sup/modes/completion-mode.rb b/lib/sup/modes/completion_mode.rb
similarity index 82%
rename from lib/sup/modes/completion-mode.rb
rename to lib/sup/modes/completion_mode.rb
index 3cb2fad..4bf06e8 100644
--- a/lib/sup/modes/completion-mode.rb
+++ b/lib/sup/modes/completion_mode.rb
@@ -38,11 +38,11 @@ private
           suffix = s[(@prefix_len + 1) .. -1]
           char = s[@prefix_len].chr
 
-          @lines.last += [[:none, sprintf("%#{max_length - suffix.length - 1}s", prefix)],
+          @lines.last += [[:text_color, sprintf("%#{max_length - suffix.length - 1}s", prefix)],
                           [:completion_character_color, char],
-                          [:none, suffix + INTERSTITIAL]]
+                          [:text_color, suffix + INTERSTITIAL]]
         else
-          @lines.last += [[:none, sprintf("%#{max_length}s#{INTERSTITIAL}", s)]]
+          @lines.last += [[:text_color, sprintf("%#{max_length}s#{INTERSTITIAL}", s)]]
         end
       else
         @lines << "" if i % num_per == 0
diff --git a/lib/sup/modes/compose-mode.rb b/lib/sup/modes/compose_mode.rb
similarity index 98%
rename from lib/sup/modes/compose-mode.rb
rename to lib/sup/modes/compose_mode.rb
index cf7a9de..7d08ac2 100644
--- a/lib/sup/modes/compose-mode.rb
+++ b/lib/sup/modes/compose_mode.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 module Redwood
 
 class ComposeMode < EditMessageMode
diff --git a/lib/sup/modes/console-mode.rb b/lib/sup/modes/console_mode.rb
similarity index 82%
rename from lib/sup/modes/console-mode.rb
rename to lib/sup/modes/console_mode.rb
index 6442b9d..6c602cd 100644
--- a/lib/sup/modes/console-mode.rb
+++ b/lib/sup/modes/console_mode.rb
@@ -1,10 +1,13 @@
 require 'pp'
 
+require "sup/service/label_service"
+
 module Redwood
 
 class Console
   def initialize mode
     @mode = mode
+    @label_service = LabelService.new
   end
 
   def query(query)
@@ -12,19 +15,27 @@ class Console
   end
 
   def add_labels(query, *labels)
-    query(query).each { |m| m.labels += labels; m.save Index }
+    count = @label_service.add_labels(query, *labels)
+    print_buffer_dirty_msg count
   end
 
   def remove_labels(query, *labels)
-    query(query).each { |m| m.labels -= labels; m.save Index }
+    count = @label_service.remove_labels(query, *labels)
+    print_buffer_dirty_msg count
+  end
+
+  def print_buffer_dirty_msg msg_count
+    puts "Scanned #{msg_count} messages."
+    puts "You might want to refresh open buffers with `@` key."
   end
+  private :print_buffer_dirty_msg
 
   def xapian; Index.instance.instance_variable_get :@xapian; end
 
   def loglevel; Redwood::Logger.level; end
   def set_loglevel(level); Redwood::Logger.level = level; end
 
-  def special_methods; methods - Object.methods end
+  def special_methods; public_methods - Object.methods end
 
   def puts x; @mode << "#{x.to_s.rstrip}\n" end
   def p x; puts x.inspect end
diff --git a/lib/sup/modes/contact-list-mode.rb b/lib/sup/modes/contact_list_mode.rb
similarity index 100%
rename from lib/sup/modes/contact-list-mode.rb
rename to lib/sup/modes/contact_list_mode.rb
diff --git a/lib/sup/modes/edit-message-async-mode.rb b/lib/sup/modes/edit_message_async_mode.rb
similarity index 100%
rename from lib/sup/modes/edit-message-async-mode.rb
rename to lib/sup/modes/edit_message_async_mode.rb
diff --git a/lib/sup/modes/edit-message-mode.rb b/lib/sup/modes/edit_message_mode.rb
similarity index 87%
rename from lib/sup/modes/edit-message-mode.rb
rename to lib/sup/modes/edit_message_mode.rb
index 5947ffd..9525ef9 100644
--- a/lib/sup/modes/edit-message-mode.rb
+++ b/lib/sup/modes/edit_message_mode.rb
@@ -93,7 +93,6 @@ EOS
     @header_lines = []
 
     @body = opts.delete(:body) || []
-    @body += sig_lines if $config[:edit_signature] && !opts.delete(:have_signature)
 
     if opts[:attachments]
       @attachments = opts[:attachments].values
@@ -112,10 +111,14 @@ EOS
 
     @message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{hostname}>"
     @edited = false
+    @sig_edited = false
     @selectors = []
     @selector_label_width = 0
     @async_mode = nil
 
+    HookManager.run "before-edit", :header => @header, :body => @body
+
+    @account_selector = nil
     # only show account selector if there is more than one email address
     if $config[:account_selector] && AccountManager.user_emails.length > 1
       ## Duplicate e-mail strings to prevent a "can't modify frozen
@@ -128,13 +131,18 @@ EOS
         HorizontalSelector.new "Account:", AccountManager.user_emails + [nil], user_emails_copy + ["Customized"]
 
       if @header["From"] =~ /<?(\S+@(\S+?))>?$/
-        @account_selector.set_to $1
-        @account_user = ""
+        # TODO: this is ugly. might implement an AccountSelector and handle
+        # special cases more transparently.
+        account_from = @account_selector.can_set_to?($1) ? $1 : nil
+        @account_selector.set_to account_from
       else
         @account_selector.set_to nil
-        @account_user = @header["From"]
       end
 
+      # A single source of truth might better than duplicating this in both
+      # @account_user and @account_selector.
+      @account_user = @header["From"]
+
       add_selector @account_selector
     end
 
@@ -144,7 +152,6 @@ EOS
       end
     add_selector @crypto_selector if @crypto_selector
 
-    HookManager.run "before-edit", :header => @header, :body => @body
     if @crypto_selector
       HookManager.run "crypto-mode", :header => @header, :body => @body, :crypto_selector => @crypto_selector
     end
@@ -171,7 +178,7 @@ EOS
   def handle_new_text header, body; end
 
   def edit_message_or_field
-    lines = DECORATION_LINES + @selectors.size
+    lines = (@selectors.empty? ? 0 : DECORATION_LINES) + @selectors.size
     if lines > curpos
       return
     elsif (curpos - lines) >= @header_lines.length
@@ -185,14 +192,49 @@ EOS
   def edit_cc; edit_field "Cc" end
   def edit_subject; edit_field "Subject" end
 
-  def edit_message
-    old_from = @header["From"] if @account_selector
-
-    @file = Tempfile.new "sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}"
+  def save_message_to_file
+    sig = sig_lines.join("\n")
+    @file = Tempfile.new ["sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}", ".eml"]
     @file.puts format_headers(@header - NON_EDITABLE_HEADERS).first
     @file.puts
-    @file.puts @body.join("\n")
+
+    begin
+      text = @body.join("\n")
+    rescue Encoding::CompatibilityError
+      text = @body.map { |x| x.fix_encoding! }.join("\n")
+      debug "encoding problem while writing message, trying to rescue, but expect errors: #{text}"
+    end
+
+    @file.puts text
+    @file.puts sig if ($config[:edit_signature] and !@sig_edited)
     @file.close
+  end
+
+  def set_sig_edit_flag
+    sig = sig_lines.join("\n")
+    if $config[:edit_signature]
+      pbody = @body.map { |x| x.fix_encoding! }.join("\n").fix_encoding!
+      blen = pbody.length
+      slen = sig.length
+
+      if blen > slen and pbody[blen-slen..blen] == sig
+        @sig_edited = false
+        @body = pbody[0..blen-slen].fix_encoding!.split("\n")
+      else
+        @sig_edited = true
+      end
+    end
+  end
+
+  def edit_message
+    old_from = @header["From"] if @account_selector
+
+    begin
+      save_message_to_file
+    rescue SystemCallError => e
+      BufferManager.flash "Can't save message to file: #{e.message}"
+      return
+    end
 
     editor = $config[:editor] || ENV['EDITOR'] || "/usr/bin/vi"
 
@@ -204,6 +246,7 @@ EOS
 
     header, @body = parse_file @file.path
     @header = header - NON_EDITABLE_HEADERS
+    set_sig_edit_flag
 
     if @account_selector and @header["From"] != old_from
       @account_user = @header["From"]
@@ -218,11 +261,12 @@ EOS
   end
 
   def edit_message_async
-    @file = Tempfile.new ["sup.#{self.class.name.gsub(/.*::/, '').camel_to_hyphy}", ".eml"]
-    @file.puts format_headers(@header - NON_EDITABLE_HEADERS).first
-    @file.puts
-    @file.puts @body.join("\n")
-    @file.close
+    begin
+      save_message_to_file
+    rescue SystemCallError => e
+      BufferManager.flash "Can't save message to file: #{e.message}"
+      return
+    end
 
     @mtime = File.mtime @file.path
 
@@ -245,6 +289,7 @@ EOS
 
     header, @body = parse_file @file.path
     @header = header - NON_EDITABLE_HEADERS
+    set_sig_edit_flag
     handle_new_text @header, @body
     update
 
@@ -282,7 +327,7 @@ EOS
   end
 
   def delete_attachment
-    i = curpos - @attachment_lines_offset - DECORATION_LINES - 2
+    i = curpos - @attachment_lines_offset - (@selectors.empty? ? 0 : DECORATION_LINES) - @selectors.size
     if i >= 0 && i < @attachments.size && BufferManager.ask_yes_or_no("Delete attachment #{@attachment_names[i]}?")
       @attachments.delete_at i
       @attachment_names.delete_at i
@@ -362,7 +407,7 @@ protected
   def regen_text
     header, @header_lines = format_headers(@header - NON_EDITABLE_HEADERS) + [""]
     @text = header + [""] + @body
-    @text += sig_lines unless $config[:edit_signature]
+    @text += sig_lines unless @sig_edited
 
     @attachment_lines_offset = 0
 
@@ -447,12 +492,12 @@ protected
       m = build_message date
 
       if HookManager.enabled? "sendmail"
-    if not HookManager.run "sendmail", :message => m, :account => acct
-          warn "Sendmail hook was not successful"
-          return false
-    end
+        if not HookManager.run "sendmail", :message => m, :account => acct
+              warn "Sendmail hook was not successful"
+              return false
+        end
       else
-        IO.popen(acct.sendmail, "w") { |p| p.puts m }
+        IO.popen(acct.sendmail, "w:UTF-8") { |p| p.puts m }
         raise SendmailCommandFailed, "Couldn't execute #{acct.sendmail}" unless $? == 0
       end
 
@@ -477,9 +522,10 @@ protected
     m = RMail::Message.new
     m.header["Content-Type"] = "text/plain; charset=#{$encoding}"
     m.body = @body.join("\n")
-    m.body += sig_lines.join("\n") unless $config[:edit_signature]
+    m.body += "\n" + sig_lines.join("\n") unless @sig_edited
     ## body must end in a newline or GPG signatures will be WRONG!
     m.body += "\n" unless m.body =~ /\n\Z/
+    m.body = m.body.fix_encoding!
 
     ## there are attachments, so wrap body in an attachment of its own
     unless @attachments.empty?
@@ -488,7 +534,10 @@ protected
       m = RMail::Message.new
 
       m.add_part body_m
-      @attachments.each { |a| m.add_part a }
+      @attachments.each do |a|
+        a.body = a.body.fix_encoding! if a.body.kind_of? String
+        m.add_part a
+      end
     end
 
     ## do whatever crypto transformation is necessary
@@ -510,9 +559,9 @@ protected
       m.header[k] =
         case v
         when String
-          k.match(/subject/i) ? mime_encode_subject(v) : mime_encode_address(v)
+          (k.match(/subject/i) ? mime_encode_subject(v).dup.fix_encoding! : mime_encode_address(v)).dup.fix_encoding!
         when Array
-          v.map { |v| mime_encode_address v }.join ", "
+          (v.map { |v| mime_encode_address v }.join ", ").dup.fix_encoding!
         end
     end
 
@@ -594,12 +643,12 @@ private
     if HookManager.enabled? "mentions-attachments"
       HookManager.run "mentions-attachments", :header => @header, :body => @body
     else
-      @body.any? {  |l| l =~ /^[^>]/ && l =~ /\battach(ment|ed|ing|)\b/i }
+      @body.any? {  |l| l.fix_encoding! =~ /^[^>]/ && l.fix_encoding! =~ /\battach(ment|ed|ing|)\b/i }
     end
   end
 
   def top_posting?
-    @body.join("\n") =~ /(\S+)\s*Excerpts from.*\n(>.*\n)+\s*\Z/
+    @body.map { |x| x.fix_encoding! }.join("\n").fix_encoding! =~ /(\S+)\s*Excerpts from.*\n(>.*\n)+\s*\Z/
   end
 
   def sig_lines
diff --git a/lib/sup/modes/file-browser-mode.rb b/lib/sup/modes/file_browser_mode.rb
similarity index 100%
rename from lib/sup/modes/file-browser-mode.rb
rename to lib/sup/modes/file_browser_mode.rb
diff --git a/lib/sup/modes/forward-mode.rb b/lib/sup/modes/forward_mode.rb
similarity index 89%
rename from lib/sup/modes/forward-mode.rb
rename to lib/sup/modes/forward_mode.rb
index 5d1ec58..c6ed2a1 100644
--- a/lib/sup/modes/forward-mode.rb
+++ b/lib/sup/modes/forward_mode.rb
@@ -7,9 +7,10 @@ class ForwardMode < EditMessageMode
       "From" => AccountManager.default_account.full_address,
     }
 
+    @m = opts[:message]
     header["Subject"] =
-      if opts[:message]
-        "Fwd: " + opts[:message].subj
+      if @m
+        "Fwd: " + @m.subj
       elsif opts[:attachments]
         "Fwd: " + opts[:attachments].keys.join(", ")
       end
@@ -19,8 +20,8 @@ class ForwardMode < EditMessageMode
     header["Bcc"] = opts[:bcc].map { |p| p.full_address }.join(", ") if opts[:bcc]
 
     body =
-      if opts[:message]
-        forward_body_lines(opts[:message])
+      if @m
+        forward_body_lines @m
       elsif opts[:attachments]
         ["Note: #{opts[:attachments].size.pluralize 'attachment'}."]
       end
@@ -68,6 +69,14 @@ protected
       m.quotable_header_lines + [""] + m.quotable_body_lines +
       ["--- End forwarded message ---"]
   end
+
+  def send_message
+    return unless super # super returns true if the mail has been sent
+    if @m
+      @m.add_label :forwarded
+      Index.save_message @m
+    end
+  end
 end
 
 end
diff --git a/lib/sup/modes/help-mode.rb b/lib/sup/modes/help_mode.rb
similarity index 100%
rename from lib/sup/modes/help-mode.rb
rename to lib/sup/modes/help_mode.rb
diff --git a/lib/sup/modes/inbox-mode.rb b/lib/sup/modes/inbox_mode.rb
similarity index 98%
rename from lib/sup/modes/inbox-mode.rb
rename to lib/sup/modes/inbox_mode.rb
index 9e37909..a021d4e 100644
--- a/lib/sup/modes/inbox-mode.rb
+++ b/lib/sup/modes/inbox_mode.rb
@@ -1,4 +1,4 @@
-require 'sup'
+require "sup/modes/thread_index_mode"
 
 module Redwood
 
diff --git a/lib/sup/modes/label-list-mode.rb b/lib/sup/modes/label_list_mode.rb
similarity index 100%
rename from lib/sup/modes/label-list-mode.rb
rename to lib/sup/modes/label_list_mode.rb
diff --git a/lib/sup/modes/label-search-results-mode.rb b/lib/sup/modes/label_search_results_mode.rb
similarity index 100%
rename from lib/sup/modes/label-search-results-mode.rb
rename to lib/sup/modes/label_search_results_mode.rb
diff --git a/lib/sup/modes/line-cursor-mode.rb b/lib/sup/modes/line_cursor_mode.rb
similarity index 97%
rename from lib/sup/modes/line-cursor-mode.rb
rename to lib/sup/modes/line_cursor_mode.rb
index aad6fe1..d762c5b 100644
--- a/lib/sup/modes/line-cursor-mode.rb
+++ b/lib/sup/modes/line_cursor_mode.rb
@@ -47,9 +47,9 @@ protected
 
   def draw_line ln, opts={}
     if ln == @curpos
-      super ln, :highlight => true, :debug => opts[:debug]
+      super ln, :highlight => true, :debug => opts[:debug], :color => :text_color
     else
-      super
+      super ln, :color => :text_color
     end
   end
 
diff --git a/lib/sup/modes/log-mode.rb b/lib/sup/modes/log_mode.rb
similarity index 100%
rename from lib/sup/modes/log-mode.rb
rename to lib/sup/modes/log_mode.rb
diff --git a/lib/sup/modes/person-search-results-mode.rb b/lib/sup/modes/person_search_results_mode.rb
similarity index 100%
rename from lib/sup/modes/person-search-results-mode.rb
rename to lib/sup/modes/person_search_results_mode.rb
diff --git a/lib/sup/modes/poll-mode.rb b/lib/sup/modes/poll_mode.rb
similarity index 100%
rename from lib/sup/modes/poll-mode.rb
rename to lib/sup/modes/poll_mode.rb
diff --git a/lib/sup/modes/reply-mode.rb b/lib/sup/modes/reply_mode.rb
similarity index 81%
rename from lib/sup/modes/reply-mode.rb
rename to lib/sup/modes/reply_mode.rb
index ccdf371..4225d0f 100644
--- a/lib/sup/modes/reply-mode.rb
+++ b/lib/sup/modes/reply_mode.rb
@@ -48,6 +48,7 @@ EOS
     ## the full headers (most importantly the list-post header, if
     ## any)
     body = reply_body_lines message
+    @body_orig = body
 
     ## first, determine the address at which we received this email. this will
     ## become our From: address in the reply.
@@ -97,14 +98,21 @@ EOS
     @headers = {}
     @headers[:recipient] = {
       "To" => cc.map { |p| p.full_address },
+      "Cc" => [],
     } if useful_recipient
 
     ## typically we don't want to have a reply-to-sender option if the sender
     ## is a user account. however, if the cc is empty, it's a message to
     ## ourselves, so for the lack of any other options, we'll add it.
-    @headers[:sender] = { "To" => [to.full_address], } if !AccountManager.is_account?(to) || !useful_recipient
+    @headers[:sender] = {
+      "To" => [to.full_address],
+      "Cc" => [],
+    } if !AccountManager.is_account?(to) || !useful_recipient
 
-    @headers[:user] = {}
+    @headers[:user] = {
+      "To" => [],
+      "Cc" => [],
+    }
 
     not_me_ccs = cc.select { |p| !AccountManager.is_account?(p) }
     @headers[:all] = {
@@ -114,22 +122,11 @@ EOS
 
     @headers[:list] = {
       "To" => [@m.list_address.full_address],
+      "Cc" => [],
     } if @m.is_list_message?
 
     refs = gen_references
 
-    @headers.each do |k, v|
-      @headers[k] = {
-               "From" => from.full_address,
-               "To" => [],
-               "Cc" => [],
-               "Bcc" => [],
-               "In-reply-to" => "<#{@m.id}>",
-               "Subject" => Message.reify_subj(@m.subj),
-               "References" => refs,
-             }.merge v
-    end
-
     types = REPLY_TYPES.select { |t| @headers.member?(t) }
     @type_selector = HorizontalSelector.new "Reply to:", types, types.map { |x| TYPE_DESCRIPTIONS[x] }
 
@@ -148,13 +145,17 @@ EOS
         :recipient
       end)
 
-    @bodies = {}
-    @headers.each do |k, v|
-      @bodies[k] = body
-      HookManager.run "before-edit", :header => v, :body => @bodies[k]
-    end
+    headers_full = {
+      "From" => from.full_address,
+      "Bcc" => [],
+      "In-reply-to" => "<#{@m.id}>",
+      "Subject" => Message.reify_subj(@m.subj),
+      "References" => refs,
+    }.merge @headers[@type_selector.val]
 
-    super :header => @headers[@type_selector.val], :body => @bodies[@type_selector.val], :twiddles => false
+    HookManager.run "before-edit", :header => headers_full, :body => body
+
+    super :header => headers_full, :body => body, :twiddles => false
     add_selector @type_selector
   end
 
@@ -163,8 +164,7 @@ protected
   def move_cursor_right
     super
     if @headers[@type_selector.val] != self.header
-      self.header = @headers[@type_selector.val]
-      self.body = @bodies[@type_selector.val] unless @edited
+      self.header = self.header.merge @headers[@type_selector.val]
       rerun_crypto_selector_hook
       update
     end
@@ -173,8 +173,7 @@ protected
   def move_cursor_left
     super
     if @headers[@type_selector.val] != self.header
-      self.header = @headers[@type_selector.val]
-      self.body = @bodies[@type_selector.val] unless @edited
+      self.header = self.header.merge @headers[@type_selector.val]
       rerun_crypto_selector_hook
       update
     end
@@ -192,14 +191,15 @@ protected
   end
 
   def handle_new_text new_header, new_body
-    if new_body != @bodies[@type_selector.val]
-      @bodies[@type_selector.val] = new_body
+    if new_body != @body_orig
+      @body_orig = new_body
       @edited = true
     end
     old_header = @headers[@type_selector.val]
-    if new_header.size != old_header.size || old_header.any? { |k, v| new_header[k] != v }
+    if old_header.any? { |k, v| new_header[k] != v }
       @type_selector.set_to :user
-      self.header = @headers[:user] = new_header
+      self.header["To"] = @headers[:user]["To"] = new_header["To"]
+      self.header["Cc"] = @headers[:user]["Cc"] = new_header["Cc"]
       update
     end
   end
@@ -210,11 +210,19 @@ protected
 
   def edit_field field
     edited_field = super
-    if edited_field && edited_field != "Subject"
+    if edited_field and (field == "To" or field == "Cc")
       @type_selector.set_to :user
+      @headers[:user]["To"] = self.header["To"]
+      @headers[:user]["Cc"] = self.header["Cc"]
       update
     end
   end
+
+  def send_message
+    return unless super # super returns true if the mail has been sent
+    @m.add_label :replied
+    Index.save_message @m
+  end
 end
 
 end
diff --git a/lib/sup/modes/resume-mode.rb b/lib/sup/modes/resume_mode.rb
similarity index 100%
rename from lib/sup/modes/resume-mode.rb
rename to lib/sup/modes/resume_mode.rb
diff --git a/lib/sup/modes/scroll-mode.rb b/lib/sup/modes/scroll_mode.rb
similarity index 96%
rename from lib/sup/modes/scroll-mode.rb
rename to lib/sup/modes/scroll_mode.rb
index aac1c19..9bee795 100644
--- a/lib/sup/modes/scroll-mode.rb
+++ b/lib/sup/modes/scroll_mode.rb
@@ -43,12 +43,12 @@ class ScrollMode < Mode
 
   def draw
     ensure_mode_validity
-    (@topline ... @botline).each { |ln| draw_line ln }
+    (@topline ... @botline).each { |ln| draw_line ln, :color => :text_color }
     ((@botline - @topline) ... buffer.content_height).each do |ln|
       if @twiddles
         buffer.write ln, 0, "~", :color => :twiddle_color
       else
-        buffer.write ln, 0, ""
+        buffer.write ln, 0, "", :color => :text_color
       end
     end
     @status = "lines #{@topline + 1}:#{@botline}/#{lines}"
@@ -208,7 +208,7 @@ protected
       # return
   end
 
-  def matching_text_array s, regex, oldcolor=:none
+  def matching_text_array s, regex, oldcolor=:text_color
     s.split(regex).map do |text|
       next if text.empty?
       if text =~ regex
@@ -244,7 +244,7 @@ protected
   end
 
   def draw_line_from_string ln, s, opts
-    buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight]
+    buffer.write ln - @topline, 0, s[@leftcol .. -1], :highlight => opts[:highlight], :color => opts[:color]
   end
 end
 
diff --git a/lib/sup/modes/search-list-mode.rb b/lib/sup/modes/search_list_mode.rb
similarity index 92%
rename from lib/sup/modes/search-list-mode.rb
rename to lib/sup/modes/search_list_mode.rb
index 8f73659..741abf4 100644
--- a/lib/sup/modes/search-list-mode.rb
+++ b/lib/sup/modes/search_list_mode.rb
@@ -86,7 +86,11 @@ protected
     counted = searches.map do |name|
       search_string = SearchManager.search_string_for name
       begin
-        query = Index.parse_query search_string
+        if SearchManager.predefined_queries.has_key? search_string
+          query = SearchManager.predefined_queries[search_string]
+        else
+          query = Index.parse_query search_string
+        end
         total = Index.num_results_for :qobj => query[:qobj]
         unread = Index.num_results_for :qobj => query[:qobj], :label => :unread
       rescue Index::ParseError => e
@@ -141,6 +145,12 @@ protected
   def rename_selected_search
     old_name, num_unread = @searches[curpos]
     return unless old_name
+
+    if SearchManager.predefined_searches.has_key? old_name
+      BufferManager.flash "Cannot be edited: predefined search."
+      return
+    end
+
     new_name = BufferManager.ask :save_search, "Rename this saved search: ", old_name
     return unless new_name && new_name !~ /^\s*$/ && new_name != old_name
     new_name.strip!
@@ -159,6 +169,12 @@ protected
   def edit_selected_search
     name, num_unread = @searches[curpos]
     return unless name
+
+    if SearchManager.predefined_searches.has_key? name
+      BufferManager.flash "Cannot be edited: predefined search."
+      return
+    end
+
     old_search_string = SearchManager.search_string_for name
     new_search_string = BufferManager.ask :search, "Edit this saved search: ", (old_search_string + " ")
     return unless new_search_string && new_search_string !~ /^\s*$/ && new_search_string != old_search_string
diff --git a/lib/sup/modes/search-results-mode.rb b/lib/sup/modes/search_results_mode.rb
similarity index 90%
rename from lib/sup/modes/search-results-mode.rb
rename to lib/sup/modes/search_results_mode.rb
index f346e97..7bcb35a 100644
--- a/lib/sup/modes/search-results-mode.rb
+++ b/lib/sup/modes/search_results_mode.rb
@@ -40,7 +40,11 @@ class SearchResultsMode < ThreadIndexMode
 
   def self.spawn_from_query text
     begin
-      query = Index.parse_query(text)
+      if SearchManager.predefined_queries.has_key? text
+        query = SearchManager.predefined_queries[text]
+      else
+        query = Index.parse_query(text)
+      end
       return unless query
       short_text = text.length < 20 ? text : text[0 ... 20] + "..."
       mode = SearchResultsMode.new query
diff --git a/lib/sup/modes/text-mode.rb b/lib/sup/modes/text_mode.rb
similarity index 100%
rename from lib/sup/modes/text-mode.rb
rename to lib/sup/modes/text_mode.rb
diff --git a/lib/sup/modes/thread-index-mode.rb b/lib/sup/modes/thread_index_mode.rb
similarity index 95%
rename from lib/sup/modes/thread-index-mode.rb
rename to lib/sup/modes/thread_index_mode.rb
index f04295b..7308f60 100644
--- a/lib/sup/modes/thread-index-mode.rb
+++ b/lib/sup/modes/thread_index_mode.rb
@@ -110,7 +110,7 @@ EOS
       num = t.size
       message = "Loading #{num.pluralize 'message body'}..."
       BufferManager.say(message) do |sid|
-        t.each_with_index do |(m, *o), i|
+        t.each_with_index do |(m, *_), i|
           next unless m
           BufferManager.say "#{message} (#{i}/#{num})", sid if t.size > 1
           m.load_from_source!
@@ -200,6 +200,26 @@ EOS
     BufferManager.draw_screen
   end
 
+  def handle_updated_update sender, m
+    t = thread_containing(m) or return
+    l = @lines[t] or return
+    @ts_mutex.synchronize do
+      @ts.delete_message m
+      @ts.add_message m
+    end
+    Index.save_thread t
+    update_text_for_line l
+  end
+
+  def handle_location_deleted_update sender, m
+    t = thread_containing(m)
+    delete_thread t if t and t.first.id == m.id
+    @ts_mutex.synchronize do
+      @ts.delete_message m if t
+    end
+    update
+  end
+
   def handle_single_message_deleted_update sender, m
     @ts_mutex.synchronize do
       return unless @ts.contains? m
@@ -247,7 +267,7 @@ EOS
 
   def edit_message
     return unless(t = cursor_thread)
-    message, *crap = t.find { |m, *o| m.has_label? :draft }
+    message, *_ = t.find { |m, *o| m.has_label? :draft }
     if message
       mode = ResumeMode.new message
       BufferManager.spawn "Edit message", mode
@@ -258,7 +278,6 @@ EOS
 
   ## returns an undo lambda
   def actually_toggle_starred t
-    pos = curpos
     if t.has_label? :starred # if ANY message has a star
       t.remove_label :starred # remove from all
       UpdateManager.relay self, :unstarred, t.first
@@ -756,6 +775,16 @@ protected
     update
   end
 
+  def delete_thread t
+    @mutex.synchronize do
+      i = @threads.index(t) or return
+      @threads.delete_at i
+      @size_widgets.delete_at i
+      @date_widgets.delete_at i
+      @tags.drop_tag_for t
+    end
+  end
+
   def hide_thread t
     @mutex.synchronize do
       i = @threads.index(t) or return
@@ -857,14 +886,14 @@ protected
 
       abbrev =
         if cur_width + name.display_length > from_width
-          name[0 ... (from_width - cur_width - 1)] + "."
+          name.slice_by_display_length(from_width - cur_width - 1) + "."
         elsif cur_width + name.display_length == from_width
-          name[0 ... (from_width - cur_width)]
+          name.slice_by_display_length(from_width - cur_width)
         else
           if last
-            name[0 ... (from_width - cur_width)]
+            name.slice_by_display_length(from_width - cur_width)
           else
-            name[0 ... (from_width - cur_width - 1)] + ","
+            name.slice_by_display_length(from_width - cur_width - 1) + ","
           end
         end
 
@@ -877,8 +906,9 @@ protected
       from << [(newness ? :index_new_color : (starred ? :index_starred_color : :index_old_color)), abbrev]
     end
 
-    dp = t.direct_participants.any? { |p| AccountManager.is_account? p }
-    p = dp || t.participants.any? { |p| AccountManager.is_account? p }
+    is_me = AccountManager.method(:is_account?)
+    directly_participated = t.direct_participants.any?(&is_me)
+    participated = directly_participated || t.participants.any?(&is_me)
 
     subj_color =
       if t.has_label?(:draft)
@@ -908,7 +938,7 @@ protected
       [
       [:size_widget_color, size_widget_text],
       [:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
-      [:to_me_color, dp ? ">" : (p ? '+' : " ")],
+      [:to_me_color, directly_participated ? ">" : (participated ? '+' : " ")],
     ] +
       (t.labels - @hidden_labels).sort_by {|x| x.to_s}.map {
             |label| [Colormap.sym_is_defined("label_#{label}_color".to_sym) || :label_color, "#{label} "]
diff --git a/lib/sup/modes/thread-view-mode.rb b/lib/sup/modes/thread_view_mode.rb
similarity index 96%
rename from lib/sup/modes/thread-view-mode.rb
rename to lib/sup/modes/thread_view_mode.rb
index 9fcc45d..6e1bafb 100644
--- a/lib/sup/modes/thread-view-mode.rb
+++ b/lib/sup/modes/thread_view_mode.rb
@@ -1,3 +1,5 @@
+require 'shellwords'
+
 module Redwood
 
 class ThreadViewMode < LineCursorMode
@@ -246,6 +248,8 @@ EOS
           sm.puts m.raw_message
         end
         raise SendmailCommandFailed, "Couldn't execute #{cmd}" unless $? == 0
+        m.add_label :forwarded
+        Index.save_message m
       rescue SystemCallError, SendmailCommandFailed => e
         warn "problem sending mail: #{e.message}"
         BufferManager.flash "Problem sending mail: #{e.message}"
@@ -359,8 +363,14 @@ EOS
     when Chunk::Attachment
       default_dir = $config[:default_attachment_save_dir]
       default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty?
-      default_fn = File.expand_path File.join(default_dir, chunk.filename)
-      fn = BufferManager.ask_for_filename :filename, "Save attachment to file: ", default_fn
+      default_fn = File.expand_path File.join(default_dir, Shellwords.escape(chunk.filename))
+      fn = BufferManager.ask_for_filename :filename, "Save attachment to file or directory: ", default_fn, true
+
+      # if user selects directory use file name from message
+      if fn and File.directory? fn
+        fn = File.join(fn, Shellwords.escape(chunk.filename))
+      end
+
       save_to_file(fn) { |f| f.print chunk.raw_content } if fn
     else
       m = @message_lines[curpos]
@@ -382,7 +392,7 @@ EOS
     num_errors = 0
     m.chunks.each do |chunk|
       next unless chunk.is_a?(Chunk::Attachment)
-      fn = File.join(folder, chunk.filename)
+      fn = File.join(folder, Shellwords.escape(chunk.filename))
       num_errors += 1 unless save_to_file(fn, false) { |f| f.print chunk.raw_content }
       num += 1
     end
@@ -780,13 +790,13 @@ private
       @person_lines[start] = m.from
       [[prefix_widget, open_widget, new_widget, attach_widget, starred_widget,
         [color,
-            "#{m.from ? m.from.mediumname : '?'} to #{m.recipients.map { |l| l.shortname }.join(', ')} #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})"]]]
+            "#{m.from ? m.from.mediumname.fix_encoding! : '?'} to #{m.recipients.map { |l| l.shortname.fix_encoding! }.join(', ')} #{m.date.to_nice_s.fix_encoding!} (#{m.date.to_nice_distance_s.fix_encoding!})"]]]
 
     when :closed
       @person_lines[start] = m.from
       [[prefix_widget, open_widget, new_widget, attach_widget, starred_widget,
         [color,
-        "#{m.from ? m.from.mediumname : '?'}, #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})  #{m.snippet}"]]]
+        "#{m.from ? m.from.mediumname.fix_encoding! : '?'}, #{m.date.to_nice_s.fix_encoding!} (#{m.date.to_nice_distance_s.fix_encoding!})  #{m.snippet ? m.snippet.fix_encoding! : ''}"]]]
 
     when :detailed
       @person_lines[start] = m.from
@@ -846,7 +856,11 @@ private
       else
         width = buffer.content_width
       end
-      lines = lines.map { |l| l.chomp.wrap width }.flatten
+      # lines can apparently be both String and Array, convert to Array for map.
+      if lines.kind_of? String
+        lines = lines.lines.to_a
+      end
+      lines = lines.map { |l| l.chomp.wrap width if l }.flatten
     end
     return lines
   end
diff --git a/lib/sup/poll.rb b/lib/sup/poll.rb
index 7e05292..0ed4496 100644
--- a/lib/sup/poll.rb
+++ b/lib/sup/poll.rb
@@ -22,7 +22,12 @@ Variables:
                    num: the total number of new messages added in this poll
              num_inbox: the number of new messages added in this poll which
                         appear in the inbox (i.e. were not auto-archived).
+             num_total: the total number of messages
+       num_inbox_total: the total number of new messages in the inbox.
 num_inbox_total_unread: the total number of unread messages in the inbox
+           num_updated: the total number of updated messages
+           num_deleted: the total number of deleted messages
+                labels: the labels that were applied
          from_and_subj: an array of (from email address, subject) pairs
    from_and_subj_inbox: an array of (from email address, subject) pairs for
                         only those messages appearing in the inbox
@@ -33,7 +38,7 @@ EOS
     @mutex = Mutex.new
     @thread = nil
     @last_poll = nil
-    @polling = false
+    @polling = Mutex.new
     @poll_sources = nil
     @mode = nil
     @should_clear_running_totals = false
@@ -43,40 +48,68 @@ EOS
 
   def poll_with_sources
     @mode ||= PollMode.new
-    HookManager.run "before-poll"
 
-    BufferManager.flash "Polling for new messages..."
-    num, numi, from_and_subj, from_and_subj_inbox, loaded_labels = @mode.poll
+    if HookManager.enabled? "before-poll"
+      HookManager.run("before-poll")
+    else
+      BufferManager.flash "Polling for new messages..."
+    end
+
+    num, numi, numu, numd, from_and_subj, from_and_subj_inbox, loaded_labels = @mode.poll
     clear_running_totals if @should_clear_running_totals
     @running_totals[:num] += num
     @running_totals[:numi] += numi
+    @running_totals[:numu] += numu
+    @running_totals[:numd] += numd
     @running_totals[:loaded_labels] += loaded_labels || []
-    if @running_totals[:num] > 0
-      BufferManager.flash "Loaded #{@running_totals[:num].pluralize 'new message'}, #{@running_totals[:numi]} to inbox. Labels: #{@running_totals[:loaded_labels].map{|l| l.to_s}.join(', ')}"
+
+
+    if HookManager.enabled? "after-poll"
+      hook_args = { :num => num, :num_inbox => numi,
+                    :num_total => @running_totals[:num], :num_inbox_total => @running_totals[:numi],
+                    :num_updated => @running_totals[:numu],
+                    :num_deleted => @running_totals[:numd],
+                    :labels => @running_totals[:loaded_labels],
+                    :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox,
+                    :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] } }
+
+      HookManager.run("after-poll", hook_args)
     else
-      BufferManager.flash "No new messages."
+      if @running_totals[:num] > 0
+        flash_msg = "Loaded #{@running_totals[:num].pluralize 'new message'}, #{@running_totals[:numi]} to inbox. " if @running_totals[:num] > 0
+        flash_msg += "Updated #{@running_totals[:numu].pluralize 'message'}. " if @running_totals[:numu] > 0
+        flash_msg += "Deleted #{@running_totals[:numd].pluralize 'message'}. " if @running_totals[:numd] > 0
+        flash_msg += "Labels: #{@running_totals[:loaded_labels].map{|l| l.to_s}.join(', ')}." if @running_totals[:loaded_labels].size > 0
+        BufferManager.flash flash_msg
+      else
+        BufferManager.flash "No new messages."
+      end
     end
 
-    HookManager.run "after-poll", :num => num, :num_inbox => numi, :from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox, :num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] }
-
   end
 
   def poll
-    return if @polling
-    @polling = true
-    @poll_sources = SourceManager.usual_sources
-    num, numi = poll_with_sources
-    @polling = false
-    [num, numi]
+    if @polling.try_lock
+      @poll_sources = SourceManager.usual_sources
+      num, numi = poll_with_sources
+      @polling.unlock
+      [num, numi]
+    else
+      debug "poll already in progress."
+      return
+    end
   end
 
   def poll_unusual
-    return if @polling
-    @polling = true
-    @poll_sources = SourceManager.unusual_sources
-    num, numi = poll_with_sources
-    @polling = false
-    [num, numi]
+    if @polling.try_lock
+      @poll_sources = SourceManager.unusual_sources
+      num, numi = poll_with_sources
+      @polling.unlock
+      [num, numi]
+    else
+      debug "poll_unusual already in progress."
+      return
+    end
   end
 
   def start
@@ -94,7 +127,7 @@ EOS
   end
 
   def do_poll
-    total_num = total_numi = 0
+    total_num = total_numi = total_numu = total_numd = 0
     from_and_subj = []
     from_and_subj_inbox = []
     loaded_labels = Set.new
@@ -108,16 +141,23 @@ EOS
           next
         end
 
-        num = 0
-        numi = 0
+        msg = ""
+        num = numi = numu = numd = 0
         poll_from source do |action,m,old_m,progress|
           if action == :delete
             yield "Deleting #{m.id}"
+            loaded_labels.merge m.labels
+            numd += 1
+          elsif action == :update
+            yield "Message at #{m.source_info} is an update of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
+            loaded_labels.merge m.labels
+            numu += 1
           elsif action == :add
             if old_m
               new_locations = (m.locations - old_m.locations)
               if not new_locations.empty?
-                yield "Message at #{new_locations[0].info} is an update of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
+                yield "Message at #{new_locations[0].info} has changed its source location. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
+                numu += 1
               else
                 yield "Skipping already-imported message at #{m.locations[-1].info}"
               end
@@ -134,65 +174,99 @@ EOS
           else fail
           end
         end
-        yield "Found #{num} messages, #{numi} to inbox." unless num == 0
+        msg += "Found #{num} messages, #{numi} to inbox. " unless num == 0
+        msg += "Updated #{numu} messages. " unless numu == 0
+        msg += "Deleted #{numd} messages." unless numd == 0
+        yield msg unless msg == ""
         total_num += num
         total_numi += numi
+        total_numu += numu
+        total_numd += numd
       end
 
       loaded_labels = loaded_labels - LabelManager::HIDDEN_RESERVED_LABELS - [:inbox, :killed]
       yield "Done polling; loaded #{total_num} new messages total"
       @last_poll = Time.now
-      @polling = false
     end
-    [total_num, total_numi, from_and_subj, from_and_subj_inbox, loaded_labels]
+    [total_num, total_numi, total_numu, total_numd, from_and_subj, from_and_subj_inbox, loaded_labels]
   end
 
   ## like Source#poll, but yields successive Message objects, which have their
   ## labels and locations set correctly. The Messages are saved to or removed
   ## from the index after being yielded.
   def poll_from source, opts={}
-    begin
-      source.poll do |sym, args|
-        case sym
-        when :add
-          m = Message.build_from_source source, args[:info]
-          old_m = Index.build_message m.id
-          m.labels += args[:labels]
-          m.labels.delete :inbox  if source.archived?
-          m.labels.delete :unread if source.read?
-          m.labels.delete :unread if m.source_marked_read? # preserve read status if possible
-          m.labels.each { |l| LabelManager << l }
-          m.labels = old_m.labels + (m.labels - [:unread, :inbox]) if old_m
-          m.locations = old_m.locations + m.locations if old_m
-          HookManager.run "before-add-message", :message => m
-          yield :add, m, old_m, args[:progress] if block_given?
-          Index.sync_message m, true
-
-          ## We need to add or unhide the message when it either did not exist
-          ## before at all or when it was updated. We do *not* add/unhide when
-          ## the same message was found at a different location
-          if !old_m or not old_m.locations.member? m.location
-            UpdateManager.relay self, :added, m
-          end
-        when :delete
-          Index.each_message :location => [source.id, args[:info]] do |m|
-            m.locations.delete Location.new(source, args[:info])
-            yield :delete, m, [source,args[:info]], args[:progress] if block_given?
-            Index.sync_message m, false
-            #UpdateManager.relay self, :deleted, m
+    debug "trying to acquire poll lock for: #{source}..."
+    if source.try_lock
+      begin
+        source.poll do |sym, args|
+          case sym
+          when :add
+            m = Message.build_from_source source, args[:info]
+            old_m = Index.build_message m.id
+            m.labels += args[:labels]
+            m.labels.delete :inbox  if source.archived?
+            m.labels.delete :unread if source.read?
+            m.labels.delete :unread if m.source_marked_read? # preserve read status if possible
+            m.labels.each { |l| LabelManager << l }
+            m.labels = old_m.labels + (m.labels - [:unread, :inbox]) if old_m
+            m.locations = old_m.locations + m.locations if old_m
+            HookManager.run "before-add-message", :message => m
+            yield :add, m, old_m, args[:progress] if block_given?
+            Index.sync_message m, true
+
+            if Index.message_joining_killed? m
+              m.labels += [:killed]
+              Index.sync_message m, true
+            end
+
+            ## We need to add or unhide the message when it either did not exist
+            ## before at all or when it was updated. We do *not* add/unhide when
+            ## the same message was found at a different location
+            if old_m
+              UpdateManager.relay self, :updated, m
+            elsif !old_m or not old_m.locations.member? m.location
+              UpdateManager.relay self, :added, m
+            end
+          when :delete
+            Index.each_message({:location => [source.id, args[:info]]}, false) do |m|
+              m.locations.delete Location.new(source, args[:info])
+              Index.sync_message m, false
+              if m.locations.size == 0
+                yield :delete, m, [source,args[:info]], args[:progress] if block_given?
+                Index.delete m.id
+                UpdateManager.relay self, :location_deleted, m
+              end
+            end
+          when :update
+            Index.each_message({:location => [source.id, args[:old_info]]}, false) do |m|
+              old_m = Index.build_message m.id
+              m.locations.delete Location.new(source, args[:old_info])
+              m.locations.push Location.new(source, args[:new_info])
+              ## Update labels that might have been modified remotely
+              m.labels -= source.supported_labels?
+              m.labels += args[:labels]
+              yield :update, m, old_m if block_given?
+              Index.sync_message m, true
+              UpdateManager.relay self, :updated, m
+            end
           end
         end
-      end
 
-      source.go_idle
-    rescue SourceError => e
-      warn "problem getting messages from #{source}: #{e.message}"
+      rescue SourceError => e
+        warn "problem getting messages from #{source}: #{e.message}"
+
+      ensure
+        source.go_idle
+        source.unlock
+      end
+    else
+      debug "source #{source} is already being polled."
     end
   end
 
   def handle_idle_update sender, idle_since; @should_clear_running_totals = false; end
   def handle_unidle_update sender, idle_since; @should_clear_running_totals = true; clear_running_totals; end
-  def clear_running_totals; @running_totals = {:num => 0, :numi => 0, :loaded_labels => Set.new}; end
+  def clear_running_totals; @running_totals = {:num => 0, :numi => 0, :numu => 0, :numd => 0, :loaded_labels => Set.new}; end
 end
 
 end
diff --git a/lib/sup/protocol.rb b/lib/sup/protocol.rb
deleted file mode 100644
index 573138b..0000000
--- a/lib/sup/protocol.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-require 'eventmachine'
-require 'socket'
-require 'stringio'
-require 'yajl'
-
-class EM::P::Redwood < EM::Connection
-  VERSION = 1
-  ENCODINGS = %w(marshal json)
-
-  attr_reader :debug
-
-  def initialize *args
-    @state = :negotiating
-    @version_buf = ""
-    @debug = false
-    super
-  end
-
-  def receive_data data
-    if @state == :negotiating
-      @version_buf << data
-      if i = @version_buf.index("\n")
-        l = @version_buf.slice!(0..i)
-        receive_version *parse_version(l.strip)
-        x = @version_buf
-        @version_buf = nil
-        @state = :established
-        connection_established
-        receive_data x
-      end
-    else
-      @filter.decode(data).each do |msg|
-        puts "#{self.class.name} received: #{msg.inspect}" if @debug
-        validate_message *msg
-        receive_message *msg
-      end
-    end
-  end
-
-  def connection_established
-  end
-
-  def send_version encodings, extensions
-    fail if encodings.empty?
-    send_data "Redwood #{VERSION} #{encodings * ','} #{extensions.empty? ? :none : (extensions * ',')}\n"
-  end
-
-  def send_message type, tag, params={}
-    fail "attempted to send message during negotiation" unless @state == :established
-    puts "#{self.class.name} sent: #{[type, tag, params].inspect}" if @debug
-    validate_message type, tag, params
-    send_data @filter.encode([type,tag,params])
-  end
-
-  def receive_version l
-    fail "unimplemented"
-  end
-
-  def receive_message type, tag, params
-    fail "unimplemented"
-  end
-
-private
-
-  def validate_message type, tag, params
-    fail unless type.is_a? String or type.is_a? Symbol
-    fail unless tag.is_a? String or tag.is_a? Integer
-    fail unless params.is_a? Hash
-  end
-
-  def parse_version l
-    l =~ /^Redwood\s+(\d+)\s+([\w,]+)\s+([\w,]+)$/ or fail "unexpected banner #{l.inspect}"
-    version, encodings, extensions = $1.to_i, $2, $3
-    encodings = encodings.split ','
-    extensions = extensions.split ','
-    extensions = [] if extensions == ['none']
-    fail unless version == VERSION
-    fail if encodings.empty?
-    [encodings, extensions]
-  end
-
-  def create_filter encoding
-    case encoding
-    when 'json' then JSONFilter.new
-    when 'marshal' then MarshalFilter.new
-    else fail "unknown encoding #{encoding.inspect}"
-    end
-  end
-
-  class JSONFilter
-    def initialize
-      @parser = Yajl::Parser.new :check_utf8 => false
-    end
-
-    def decode chunk
-      parsed = []
-      @parser.on_parse_complete = lambda { |o| parsed << o }
-      @parser << chunk
-      parsed
-    end
-
-    def encode *os
-      os.inject('') { |s, o| s << Yajl::Encoder.encode(o) }
-    end
-  end
-
-  class MarshalFilter
-    def initialize
-      @buf = ''
-      @state = :prefix
-      @size = 0
-    end
-
-    def decode chunk
-      received = []
-      @buf << chunk
-
-      begin
-        if @state == :prefix
-          break unless @buf.size >= 4
-          prefix = @buf.slice!(0...4)
-          @size = prefix.unpack('N')[0]
-          @state = :data
-        end
-
-        fail unless @state == :data
-        break if @buf.size < @size
-        received << Marshal.load(@buf.slice!(0... at size))
-        @state = :prefix
-      end until @buf.empty?
-
-      received
-    end
-
-    def encode o
-      data = Marshal.dump o
-      [data.size].pack('N') + data
-    end
-  end
-end
-
-class EM::P::RedwoodServer < EM::P::Redwood
-  def post_init
-    send_version ENCODINGS, []
-  end
-
-  def receive_version encodings, extensions
-    fail unless encodings.size == 1
-    fail unless ENCODINGS.member? encodings.first
-    @filter = create_filter encodings.first
-  end
-end
-
-class EM::P::RedwoodClient < EM::P::Redwood
-  def receive_version encodings, extensions
-    encoding = (ENCODINGS & encodings).first
-    fail unless encoding
-    @filter = create_filter encoding
-    send_version [encoding], []
-  end
-end
diff --git a/lib/sup/rfc2047.rb b/lib/sup/rfc2047.rb
index 8b987db..1b00f23 100644
--- a/lib/sup/rfc2047.rb
+++ b/lib/sup/rfc2047.rb
@@ -16,8 +16,6 @@
 #
 # This file is distributed under the same terms as Ruby.
 
-require 'iconv'
-
 module Rfc2047
   WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~]+)\?=} # :nodoc: 'stupid ruby-mode
   WORDSEQ = %r{(#{WORD.source})\s+(?=#{WORD.source})}
@@ -52,7 +50,7 @@ module Rfc2047
         # WORD.
       end
 
-      Iconv.easy_decode(target, charset, text)
+      text.transcode(target, charset)
     end
   end
 end
diff --git a/lib/sup/search.rb b/lib/sup/search.rb
index 0c63b06..6d06130 100644
--- a/lib/sup/search.rb
+++ b/lib/sup/search.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 module Redwood
 
 class SearchManager
@@ -5,6 +7,8 @@ class SearchManager
 
   class ExpansionError < StandardError; end
 
+  attr_reader :predefined_searches
+
   def initialize fn
     @fn = fn
     @searches = {}
@@ -15,33 +19,66 @@ class SearchManager
       end
     end
     @modified = false
+
+    @predefined_searches = { 'All mail' => 'Search all mail.' }
+    @predefined_queries  = { 'All mail'.to_sym => { :qobj => Xapian::Query.new('Kmail'),
+                                                    :load_spam => false,
+                                                    :load_deleted => false,
+                                                    :load_killed => false,
+                                                    :text => 'Search all mail.'}
+    }
+    @predefined_searches.each do |k,v|
+      @searches[k] = v
+    end
   end
 
+  def predefined_queries; return @predefined_queries; end
   def all_searches; return @searches.keys.sort; end
-  def search_string_for name; return @searches[name]; end
+  def search_string_for name;
+    if @predefined_searches.keys.member? name
+      return name.to_sym
+    end
+    return @searches[name];
+  end
   def valid_name? name; name =~ /^[\w-]+$/; end
   def name_format_hint; "letters, numbers, underscores and dashes only"; end
 
   def add name, search_string
     return unless valid_name? name
+    if @predefined_searches.has_key? name
+      warn "cannot add search: #{name} is already taken by a predefined search"
+      return
+    end
     @searches[name] = search_string
     @modified = true
   end
 
   def rename old, new
     return unless @searches.has_key? old
+    if [old, new].any? { |x| @predefined_searches.has_key? x }
+      warn "cannot rename search: #{old} or #{new} is already taken by a predefined search"
+      return
+    end
     search_string = @searches[old]
     delete old if add new, search_string
   end
 
   def edit name, search_string
     return unless @searches.has_key? name
+    if @predefined_searches.has_key? name
+      warn "cannot edit predefined search: #{name}."
+      return
+    end
     @searches[name] = search_string
     @modified = true
   end
 
   def delete name
     return unless @searches.has_key? name
+    if @predefined_searches.has_key? name
+      warn "cannot delete predefined search: #{name}."
+      return
+    end
     @searches.delete name
     @modified = true
   end
@@ -65,7 +102,7 @@ class SearchManager
 
   def save
     return unless @modified
-    File.open(@fn, "w") { |f| @searches.sort.each { |(n, s)| f.puts "#{n}: #{s}" } }
+    File.open(@fn, "w:UTF-8") { |f| (@searches - @predefined_searches.keys).sort.each { |(n, s)| f.puts "#{n}: #{s}" } }
     @modified = false
   end
 end
diff --git a/lib/sup/sent.rb b/lib/sup/sent.rb
index 0ca1fb1..d289cc3 100644
--- a/lib/sup/sent.rb
+++ b/lib/sup/sent.rb
@@ -25,8 +25,13 @@ class SentManager
   end
 
   def write_sent_message date, from_email, &block
-    @source.store_message date, from_email, &block
-    PollManager.poll_from @source
+    ::Thread.new do
+      debug "store the sent message (locking sent source..)"
+      @source.synchronize do
+        @source.store_message date, from_email, &block
+      end
+      PollManager.poll_from @source
+    end
   end
 end
 
diff --git a/lib/sup/server.rb b/lib/sup/server.rb
deleted file mode 100644
index f2dde6a..0000000
--- a/lib/sup/server.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'sup/protocol'
-
-module Redwood
-
-class Server < EM::P::RedwoodServer
-  def initialize index
-    super
-    @index = index
-  end
-
-  def receive_message type, tag, params
-    if respond_to? :"request_#{type}"
-      send :"request_#{type}", tag, params
-    else
-      send_message 'error', tag, 'description' => "invalid request type #{type.inspect}"
-    end
-  end
-
-  def request_query tag, a
-    q = @index.parse_query a['query']
-    query q, a['offset'], a['limit'], a['raw'] do |r|
-      send_message 'message', tag, r
-    end
-    send_message 'done', tag
-  end
-
-  def request_count tag, a
-    q = @index.parse_query a['query']
-    c = count q
-    send_message 'count', tag, 'count' => c
-  end
-
-  def request_label tag, a
-    q = @index.parse_query a['query']
-    label q, a['add'], a['remove']
-    send_message 'done', tag
-  end
-
-  def request_add tag, a
-    add a['raw'], a['labels']
-    send_message 'done', tag
-  end
-
-  def request_thread tag, a
-    thread a['message_id'], a['raw'] do |r|
-      send_message 'message', tag, r
-    end
-    send_message 'done', tag
-  end
-
-private
-
-  def result_from_message m, raw
-    mkperson = lambda { |p| { :email => p.email, :name => p.name } }
-    {
-      'summary' => {
-        'message_id' => m.id,
-        'date' => m.date,
-        'from' => mkperson[m.from],
-        'to' => m.to.map(&mkperson),
-        'cc' => m.cc.map(&mkperson),
-        'bcc' => m.bcc.map(&mkperson),
-        'subject' => m.subj,
-        'refs' => m.refs,
-        'replytos' => m.replytos,
-        'labels' => m.labels.map(&:to_s),
-      },
-      'raw' => raw ? m.raw_message : nil,
-    }
-  end
-
-  def query query, offset, limit, raw
-    c = 0
-    @index.each_message query do |m|
-      next if c < offset
-      break if c >= offset + limit if limit
-      yield result_from_message(m, raw)
-      c += 1
-    end
-    nil
-  end
-
-  def count query
-    @index.num_results_for query
-  end
-
-  def label query, remove_labels, add_labels
-    @index.each_message query do |m|
-      remove_labels.each { |l| m.remove_label l }
-      add_labels.each { |l| m.add_label l }
-      @index.update_message_state m
-    end
-    nil
-  end
-
-  def add raw, labels
-    SentManager.source.store_message Time.now, "test at example.com" do |io|
-      io.write raw
-    end
-    PollManager.poll_from SentManager.source do |sym,m,old_m,progress|
-      next unless sym == :add
-      m.labels = labels
-    end
-    nil
-  end
-
-  def thread msg_id, raw
-    msg = @index.build_message msg_id
-    @index.each_message_in_thread_for msg do |id, builder|
-      m = builder.call
-      yield result_from_message(m, raw)
-    end
-  end
-end
-
-end
diff --git a/lib/sup/service/label_service.rb b/lib/sup/service/label_service.rb
new file mode 100644
index 0000000..1882c67
--- /dev/null
+++ b/lib/sup/service/label_service.rb
@@ -0,0 +1,45 @@
+require "sup/index"
+
+module Redwood
+  # Provides label tweaking service to the user.
+  # Working as the backend of ConsoleMode.
+  #
+  # Should become the backend of bin/sup-tweak-labels in the future.
+  class LabelService
+    # @param index [Redwood::Index]
+    def initialize index=Index.instance
+      @index = index
+    end
+
+    def add_labels query, *labels
+      run_on_each_message(query) do |m|
+        labels.each {|l| m.add_label l }
+      end
+    end
+
+    def remove_labels query, *labels
+      run_on_each_message(query) do |m|
+        labels.each {|l| m.remove_label l }
+      end
+    end
+
+
+    private
+    def run_on_each_message query, &operation
+      count = 0
+
+      find_messages(query).each do |m|
+        operation.call(m)
+        @index.update_message_state m
+        count += 1
+      end
+
+      @index.save_index
+      count
+    end
+
+    def find_messages query
+      @index.find_messages(query)
+    end
+  end
+end
diff --git a/lib/sup/source.rb b/lib/sup/source.rb
index e0aa90e..4f942cf 100644
--- a/lib/sup/source.rb
+++ b/lib/sup/source.rb
@@ -1,4 +1,5 @@
 require "sup/rfc2047"
+require "monitor"
 
 module Redwood
 
@@ -22,30 +23,23 @@ class Source
   ## read, delete them, or anything else. (Well, it's nice to be able
   ## to delete them, but that is optional.)
   ##
-  ## On the other hand, Sup assumes that you can assign each message a
-  ## unique integer id, such that newer messages have higher ids than
-  ## earlier ones, and that those ids stay constant across sessions
-  ## (in the absence of some other client going in and fucking
-  ## everything up). For example, for mboxes I use the file offset of
-  ## the start of the message. If a source does NOT have that
-  ## capability, e.g. IMAP, then you have to do a little more work to
-  ## simulate it.
+  ## Messages are identified internally based on the message id, and stored
+  ## with an unique document id. Along with the message, source information
+  ## that can contain arbitrary fields (set up by the source) is stored. This
+  ## information will be passed back to the source when a message in the
+  ## index (Sup database) needs to be identified to its source, e.g. when
+  ## re-reading or modifying a unique message.
   ##
   ## To write a new source, subclass this class, and implement:
   ##
-  ## - start_offset
-  ## - end_offset (exclusive!) (or, #done?)
+  ## - initialize
   ## - load_header offset
   ## - load_message offset
   ## - raw_header offset
   ## - raw_message offset
-  ## - check (optional)
+  ## - store_message (optional)
+  ## - poll (loads new messages)
   ## - go_idle (optional)
-  ## - next (or each, if you prefer): should return a message and an
-  ##   array of labels.
-  ##
-  ## ... where "offset" really means unique id. (You can tell I
-  ## started with mbox.)
   ##
   ## All exceptions relating to accessing the source must be caught
   ## and rethrown as FatalSourceErrors or OutOfSyncSourceErrors.
@@ -57,11 +51,10 @@ class Source
   ## Finally, be sure the source is thread-safe, since it WILL be
   ## pummelled from multiple threads at once.
   ##
-  ## Examples for you to look at: mbox/loader.rb, imap.rb, and
-  ## maildir.rb.
+  ## Examples for you to look at: mbox.rb and maildir.rb.
 
   bool_accessor :usual, :archived
-  attr_reader :uri
+  attr_reader :uri, :usual
   attr_accessor :id
 
   def initialize uri, usual=true, archived=false, id=nil
@@ -71,9 +64,11 @@ class Source
     @usual = usual
     @archived = archived
     @id = id
+
+    @poll_lock = Monitor.new
   end
 
-  ## overwrite me if you have a disk incarnation (currently used only for sup-sync-back)
+  ## overwrite me if you have a disk incarnation (currently used only for sup-sync-back-mbox)
   def file_path; nil end
 
   def to_s; @uri.to_s; end
@@ -87,6 +82,14 @@ class Source
   ## leaks (esp. file descriptors).
   def go_idle; end
 
+  ## Returns an array containing all the labels that are natively
+  ## supported by this source
+  def supported_labels?; [] end
+
+  ## Returns an array containing all the labels that are currently in
+  ## the location filename
+  def labels? info; [] end
+
   ## Yields values of the form [Symbol, Hash]
   ## add: info, labels, progress
   ## delete: info, progress
@@ -98,6 +101,25 @@ class Source
     true
   end
 
+  def synchronize &block
+    @poll_lock.synchronize &block
+  end
+
+  def try_lock
+    acquired = @poll_lock.try_enter
+    if acquired
+      debug "lock acquired for: #{self}"
+    else
+      debug "could not acquire lock for: #{self}"
+    end
+    acquired
+  end
+
+  def unlock
+    @poll_lock.exit
+    debug "lock released for: #{self}"
+  end
+
   ## utility method to read a raw email header from an IO stream and turn it
   ## into a hash of key-value pairs. minor special semantics for certain headers.
   ##
@@ -160,7 +182,7 @@ module SerializeLabelsNicely
   end
 
   def after_unmarshal!
-    @labels = Set.new(@labels.map { |s| s.to_sym })
+    @labels = Set.new(@labels.to_a.map { |s| s.to_sym })
   end
 end
 
@@ -209,9 +231,9 @@ class SourceManager
     end
   end
 
-  def save_sources fn=Redwood::SOURCE_FN
+  def save_sources fn=Redwood::SOURCE_FN, force=false
     @source_mutex.synchronize do
-      if @sources_dirty
+      if @sources_dirty || force
         Redwood::save_yaml_obj sources, fn, false, true
       end
       @sources_dirty = false
diff --git a/lib/sup/textfield.rb b/lib/sup/textfield.rb
index 343e450..6d535a0 100644
--- a/lib/sup/textfield.rb
+++ b/lib/sup/textfield.rb
@@ -168,6 +168,16 @@ private
     else # trailing spaces
       v + (" " * (x - @question.length - v.length))
     end
+
+    # ncurses returns a ASCII-8BIT (binary) string, which
+    # bytes presumably are of current charset encoding. we force_encoding
+    # so that the char representation / string is tagged will be the
+    # system locale and also hopefully the terminal/input encoding. an
+    # incorrectly configured terminal encoding (not matching the system
+    # encoding) will produce erronous results, but will also do that for
+    # a log of other programs since it is impossible to detect which is
+    # which and what encoding the inputted byte chars are supposed to have.
+    v.force_encoding($encoding).fix_encoding!
   end
 
   def remove_extra_space
diff --git a/lib/sup/thread.rb b/lib/sup/thread.rb
index f1414ba..b08bae2 100644
--- a/lib/sup/thread.rb
+++ b/lib/sup/thread.rb
@@ -387,6 +387,12 @@ class ThreadSet
     m.refs.any? { |ref_id| @messages.member? ref_id }
   end
 
+  def delete_message message
+    el = @messages[message.id]
+    return unless el.message
+    el.message = nil
+  end
+
   ## the heart of the threading code
   def add_message message
     el = @messages[message.id]
diff --git a/lib/sup/util.rb b/lib/sup/util.rb
index cd68fe0..5cc4a2c 100644
--- a/lib/sup/util.rb
+++ b/lib/sup/util.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 require 'thread'
 require 'lockfile'
 require 'mime/types'
@@ -5,6 +7,8 @@ require 'pathname'
 require 'set'
 require 'enumerator'
 require 'benchmark'
+require 'unicode'
+require 'fileutils'
 
 ## time for some monkeypatching!
 class Symbol
@@ -30,7 +34,7 @@ class Lockfile
   def dump_lock_id lock_id = @lock_id
       "host: %s\npid: %s\nppid: %s\ntime: %s\nuser: %s\npname: %s\n" %
         lock_id.values_at('host','pid','ppid','time','user', 'pname')
-    end
+  end
 
   def lockinfo_on_disk
     h = load_lock_id IO.read(path)
@@ -42,6 +46,17 @@ class Lockfile
   def touch_yourself; touch path end
 end
 
+class File
+  # platform safe file.link which attempts a copy if hard-linking fails
+  def self.safe_link src, dest
+    begin
+      File.link src, dest
+    rescue
+      FileUtils.copy src, dest
+    end
+  end
+end
+
 class Pathname
   def human_size
     s =
@@ -113,6 +128,23 @@ module RMail
   end
 
   class Header
+
+    # Convert to ASCII before trying to match with regexp
+    class Field
+
+      class << self
+        def parse(field)
+          field = field.dup.to_s
+          field = field.fix_encoding!.ascii
+          if field =~ EXTRACT_FIELD_NAME_RE
+            [ $1, $'.chomp ]
+          else
+            [ "", Field.value_strip(field) ]
+          end
+        end
+      end
+    end
+
     ## Be more cautious about invalid content-type headers
     ## the original RMail code calls
     ## value.strip.split(/\s*;\s*/)[0].downcase
@@ -233,14 +265,14 @@ class Object
 end
 
 class String
-  ## nasty multibyte hack for ruby 1.8. if it's utf-8, split into chars using
-  ## the utf8 regex and count those. otherwise, use the byte length.
   def display_length
-    if RUBY_VERSION < '1.9.1' && ($encoding == "UTF-8" || $encoding == "utf8")
-      # scan hack is somewhat slow, worth trying to cache
-      @display_length ||= scan(/./u).size
-    else
-      size
+    @display_length ||= Unicode.width(self.fix_encoding!, false)
+  end
+
+  def slice_by_display_length len
+    each_char.each_with_object "" do |c, buffer|
+      len -= c.display_length
+      buffer << c if len >= 0
     end
   end
 
@@ -327,20 +359,69 @@ class String
   def wrap len
     ret = []
     s = self
-    while s.length > len
-      cut = s[0 ... len].rindex(/\s/)
+    while s.display_length > len
+      cut = s.slice_by_display_length(len).rindex(/\s/)
       if cut
         ret << s[0 ... cut]
         s = s[(cut + 1) .. -1]
       else
-        ret << s[0 ... len]
-        s = s[len .. -1]
+        ret << s.slice_by_display_length(len)
+        s = s[ret.last.length .. -1]
       end
     end
     ret << s
   end
 
+  # Fix the damn string! make sure it is valid utf-8, then convert to
+  # user encoding.
+  #
+  # Not Ruby 1.8 compatible
+  def fix_encoding!
+    # first try to encode to utf-8 from whatever current encoding
+    encode!('UTF-8', :invalid => :replace, :undef => :replace)
+
+    # do this anyway in case string is set to be UTF-8, encoding to
+    # something else (UTF-16 which can fully represent UTF-8) and back
+    # ensures invalid chars are replaced.
+    encode!('UTF-16', 'UTF-8', :invalid => :replace, :undef => :replace)
+    encode!('UTF-8', 'UTF-16', :invalid => :replace, :undef => :replace)
+
+    fail "Could not create valid UTF-8 string out of: '#{self.to_s}'." unless valid_encoding?
+
+    # now convert to $encoding
+    encode!($encoding, :invalid => :replace, :undef => :replace)
+
+    fail "Could not create valid #{$encoding.inspect} string out of: '#{self.to_s}'." unless valid_encoding?
+
+    self
+  end
+
+  # transcode the string if original encoding is know
+  # fix if broken.
+  #
+  # Not Ruby 1.8 compatible
+  def transcode to_encoding, from_encoding
+    begin
+      encode!(to_encoding, from_encoding, :invalid => :replace, :undef => :replace)
+
+      unless valid_encoding?
+        # fix encoding (through UTF-8)
+        encode!('UTF-16', from_encoding, :invalid => :replace, :undef => :replace)
+        encode!(to_encoding, 'UTF-16', :invalid => :replace, :undef => :replace)
+      end
+
+    rescue Encoding::ConverterNotFoundError
+      debug "Encoding converter not found for #{from_encoding.inspect} or #{to_encoding.inspect}, fixing string: '#{self.to_s}', but expect weird characters."
+      fix_encoding!
+    end
+
+    fail "Could not create valid #{to_encoding.inspect} string out of: '#{self.to_s}'." unless valid_encoding?
+
+    self
+  end
+
   def normalize_whitespace
+    fix_encoding!
     gsub(/\t/, "    ").gsub(/\r/, "")
   end
 
@@ -382,12 +463,8 @@ class String
         out << b.chr
       end
     end
-    out.force_encoding Encoding::UTF_8 if out.respond_to? :force_encoding
-    out
-  end
-
-  def transcode src_encoding=$encoding
-    Iconv.easy_decode $encoding, src_encoding, self
+    out = out.fix_encoding! # this should now be an utf-8 string of ascii
+                           # compat chars.
   end
 
   unless method_defined? :ascii_only?
@@ -658,27 +735,3 @@ class FinishLine
   end
 end
 
-class Iconv
-  def self.easy_decode target, orig_charset, text
-    if text.respond_to? :force_encoding
-      text = text.dup
-      text.force_encoding Encoding::BINARY
-    end
-    charset = case orig_charset
-      when /UTF[-_ ]?8/i then "utf-8"
-      when /(iso[-_ ])?latin[-_ ]?1$/i then "ISO-8859-1"
-      when /iso[-_ ]?8859[-_ ]?15/i then 'ISO-8859-15'
-      when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i then "utf-7"
-      when /^euc$/i then 'EUC-JP' # XXX try them all?
-      when /^(x-unknown|unknown[-_ ]?8bit|ascii[-_ ]?7[-_ ]?bit)$/i then 'ASCII'
-      else orig_charset
-    end
-
-    begin
-      returning(Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]) { |str| str.check }
-    rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::InvalidCharacter, Iconv::IllegalSequence, String::CheckError
-      debug "couldn't transcode text from #{orig_charset} (#{charset}) to #{target} (#{text[0 ... 20].inspect}...): got #{$!.class} (#{$!.message})"
-      text.ascii
-    end
-  end
-end
diff --git a/lib/sup/util/path.rb b/lib/sup/util/path.rb
new file mode 100644
index 0000000..3e24ff7
--- /dev/null
+++ b/lib/sup/util/path.rb
@@ -0,0 +1,9 @@
+module Redwood
+  module Util
+    module Path
+      def self.expand(path)
+        ::File.expand_path(path)
+      end
+    end
+  end
+end
diff --git a/lib/sup/util/query.rb b/lib/sup/util/query.rb
new file mode 100644
index 0000000..cb8ab1f
--- /dev/null
+++ b/lib/sup/util/query.rb
@@ -0,0 +1,17 @@
+module Redwood
+  module Util
+    module Query
+      class QueryDescriptionError < ArgumentError; end
+
+      def self.describe(query, fallback = nil)
+        d = query.description.force_encoding("UTF-8")
+
+        unless d.valid_encoding?
+          raise QueryDescriptionError.new(d) unless fallback
+          d = fallback
+        end
+        return d
+      end
+    end
+  end
+end
diff --git a/lib/sup/util/uri.rb b/lib/sup/util/uri.rb
new file mode 100644
index 0000000..dfb27b3
--- /dev/null
+++ b/lib/sup/util/uri.rb
@@ -0,0 +1,15 @@
+require "uri"
+
+require "sup/util/path"
+
+module Redwood
+  module Util
+    module Uri
+      def self.build(components)
+        components = components.dup
+        components[:path] = Path.expand(components[:path])
+        ::URI::Generic.build(components)
+      end
+    end
+  end
+end
diff --git a/lib/sup/version.rb b/lib/sup/version.rb
new file mode 100644
index 0000000..043a9ea
--- /dev/null
+++ b/lib/sup/version.rb
@@ -0,0 +1,3 @@
+module Redwood
+  VERSION = "0.15.0"
+end
diff --git a/protocol.md b/protocol.md
deleted file mode 100644
index 1d070a5..0000000
--- a/protocol.md
+++ /dev/null
@@ -1,168 +0,0 @@
-Redwood Protocol
-================
-
-The server begins by sending a line of the form `Redwood <ver> <encodings>
-<extensions>`, where `ver` is the major protocol version (1), encodings is a
-comma-separated list of supported message encodings (e.g. `json,bert,marshal`),
-and `extensions` is a comma-separated list of protocol extensions. The server
-must advertise at least one encoding. A zero-length list of extensions is
-represented by `none`. The client replies in the same format, with the
-restrictions that the protocol version must match, `encodings` and `extensions`
-must be subsets of what the server advertised, and there must be exactly 1
-encoding specified.
-
-Requests and responses are represented as `[type, params]`, where `type`
-is a lowercase string corresponding to one of the message types specified
-below and `params` is a dictionary with string keys.
-
-Requests
---------
-
-There may be zero or more replies to a request. Multiple requests may be
-issued concurrently. There is an implicit, optional, opaque `tag` parameter to
-every request which will be returned in all replies to the request to
-aid clients in keeping multiple requests in flight. `tag` may be an
-arbitrary datastructure and for the purposes of Cancel defaults to nil.
-
-### Query
-Send a Message response for each hit on `query` starting at `offset`
-and sending a maximum of `limit` Messages responses. `raw` controls
-whether the raw message text is included in the response.
-
-#### Parameters
-*   `query`: Query
-*   `offset`: int
-*   `limit`: int
-*   `raw`: boolean
-
-#### Responses
-*   multiple Message
-*   one Done after all Messages
-
-
-### Count
-Send a count reply with the number of hits for `query`.
-
-#### Parameters
-*   `query`: Query
-
-#### Responses
-*   one Count
-
-
-### Label
-Modify the labels on all messages matching `query`. First removes the
-labels in `remove` then adds those in `add`.
-
-#### Parameters
-*   `query`: Query
-*   `add`: string list
-*   `remove`: string list
-
-#### Responses
-*   one Done
-
-
-### Add
-Add a message to the database. `raw` is the normal RFC 2822 message text.
-
-#### Parameters
-*   `raw`: string
-*   `labels`: string list
-
-#### Responses
-*   one Done
-
-
-### Stream
-Sends a Message response whenever a new message that matches `query` is
-added with the Add request. This request will not terminate until a
-corresponding Cancel request is sent.
-
-#### Parameters
-*   `query`: Query
-
-#### Responses
-multiple Message
-
-
-### Cancel
-Cancels all active requests with tag `target`. This is only required to
-be implemented for the Stream request.
-
-#### Parameters
-*   `target`: string
-
-#### Responses
-one Done
-
-
-
-Responses
----------
-
-### Done
-Signifies that a request has completed successfully.
-
-
-### Message
-Represents a query result. If `raw` is present it is the raw message
-text that was previously a parameter to the Add request.
-
-#### Parameters
-*   `summary`: Summary
-*   `raw`: optional string
-
-
-### Count
-`count` is the number of messages matched.
-
-#### Parameters
-*   `count`: int
-
-
-### Error
-
-#### Parameters
-*   `type`: string
-*   `message`: string
-
-
-
-Datatypes
----------
-
-### Query
-Recursive prefix-notation datastructure describing a boolean condition.
-Where `a` and `b` are Queries and `field` and `value` are strings, a
-Query can be any of the following:
-
-*   `[:and, a, b, ...]`
-*   `[:or, a, b, ...]`
-*   `[:not, a, b]`
-*   `[:term, field, value]`
-
-
-### Summary
-*   `message_id`: string
-*   `date`: time
-*   `from`: Person
-*   `to`, `cc`, `bcc`: Person list
-*   `subject`: string
-*   `refs`: string list
-*   `replytos`: string list
-*   `labels`: string list
-
-
-### Person
-*   `name`: string
-*   `email`: string
-
-
-TODO
-----
-
-*   Protocol negotiation
-   -   Version
-   -   Compression (none, gzip, ...)
-*   Specify string encodings
diff --git a/release-script.txt b/release-script.txt
deleted file mode 100644
index 4063139..0000000
--- a/release-script.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Just a few simple steps to make a new release.
-
-vi History.txt
-vi ReleaseNotes
-vi www/index.html # and bump version number
-git rank-contributors -n -o > CONTRIBUTORS
-vi CONTRIBUTORS   # and merge
-vi www/index.html # and include CONTRIBUTORS
-# ... git add, commit, etc
-git checkout -b release-<releasename>
-vi lib/sup.rb bin/* # and bump version numbers in all files
-# ... git add, commit, etc
-rake gem
-rake tarball
-gem push pkg/<gem name> # now using gemcutter
-git publish-branch
-rake upload_webpage
diff --git a/sup-files.rb b/sup-files.rb
deleted file mode 100644
index 2c7d874..0000000
--- a/sup-files.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-SUP_LIB_DIRS = %w(lib lib/sup lib/sup/modes)
-SUP_EXECUTABLES = %w(sup sup-add sup-cmd sup-config sup-dump sup-import-dump sup-recover-sources sup-server sup-sync sup-sync-back sup-tweak-labels)
-SUP_EXTRA_FILES = %w(CONTRIBUTORS README.txt LICENSE History.txt ReleaseNotes)
-SUP_FILES =
-  SUP_EXTRA_FILES +
-  SUP_EXECUTABLES.map { |f| "bin/#{f}" } +
-  SUP_LIB_DIRS.map { |d| Dir["#{d}/*.rb"] }.flatten
-
-if $0 == __FILE__ # if executed from commandline
-  puts SUP_FILES
-end
diff --git a/sup-version.rb b/sup-version.rb
deleted file mode 100644
index 1a10ed2..0000000
--- a/sup-version.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-## allow people who use development versions by running "rake gem"
-## and installing the resulting gem it to be able to do this. (gem
-## versions must be in dotted-digit notation only and can be passed
-## with the REL environment variable to "rake gem").
-SUP_VERSION = if ENV['REL']
-  ENV['REL']
-else
-  $:.unshift 'lib' # force loading from ./lib/ if it exists
-  require 'sup'
-  if Redwood::VERSION == "git"
-    "999"
-  else
-    Redwood::VERSION
-  end
-end
diff --git a/sup.gemspec b/sup.gemspec
new file mode 100644
index 0000000..0f8a4d6
--- /dev/null
+++ b/sup.gemspec
@@ -0,0 +1,67 @@
+lib = File.expand_path("../lib", __FILE__)
+$:.unshift(lib) unless $:.include?(lib)
+
+require 'sup/version'
+
+# Files
+SUP_EXECUTABLES = %w(sup sup-add sup-config sup-dump sup-import-dump
+  sup-recover-sources sup-sync sup-sync-back-maildir sup-sync-back-mbox
+  sup-tweak-labels sup-psych-ify-config-files)
+SUP_EXTRA_FILES = %w(CONTRIBUTORS README.md LICENSE History.txt ReleaseNotes)
+SUP_FILES =
+  SUP_EXTRA_FILES +
+  SUP_EXECUTABLES.map { |f| "bin/#{f}" } +
+  Dir["lib/**/*.rb"]
+
+
+module Redwood
+  Gemspec = Gem::Specification.new do |s|
+    s.name = "sup"
+    s.version = ENV["REL"] || (::Redwood::VERSION == "git" ? "999" : ::Redwood::VERSION)
+    s.date = Time.now.strftime "%Y-%m-%d"
+    s.authors = ["William Morgan", "Gaute Hope", "Hamish Downer", "Matthieu Rakotojaona"]
+    s.email   = "sup-talk at rubyforge.org"
+    s.summary = "A console-based email client with the best features of GMail, mutt and Emacs"
+    s.homepage = "http://supmua.org"
+    s.description = <<-DESC
+      Sup is a console-based email client for people with a lot of email.
+
+      * GMail-like thread-centered archiving, tagging and muting
+      * Handling mail from multiple mbox and Maildir sources
+      * Blazing fast full-text search with a rich query language
+      * Multiple accounts - pick the right one when sending mail
+      * Ruby-programmable hooks
+      * Automatically tracking recent contacts
+DESC
+    s.license = 'GPL-2'
+    # TODO: might want to add index migrating script here, too
+    s.post_install_message = <<-EOF
+SUP: If you are upgrading Sup from before version 0.14.0: Please
+     run `sup-psych-ify-config-files` to migrate from 0.13.
+
+     Check https://github.com/sup-heliotrope/sup/wiki/Migration-0.13-to-0.14
+     for more detailed and up-to-date instructions.
+    EOF
+    s.files = SUP_FILES
+    s.executables = SUP_EXECUTABLES
+
+    s.required_ruby_version = '>= 1.9.2'
+
+    s.add_runtime_dependency "xapian-ruby", "~> 1.2.15"
+    s.add_runtime_dependency "ncursesw-sup", "~> 1.3.1"
+    s.add_runtime_dependency "rmail-sup", "~> 1.0.1"
+    s.add_runtime_dependency "highline"
+    s.add_runtime_dependency "trollop", ">= 1.12"
+    s.add_runtime_dependency "lockfile"
+    s.add_runtime_dependency "mime-types", "~> 1.0"
+    s.add_runtime_dependency "locale", "~> 2.0"
+    s.add_runtime_dependency "chronic", "~> 0.9.1"
+    s.add_runtime_dependency "unicode", "~> 0.4.4"
+
+    s.add_development_dependency "bundler", "~> 1.3"
+    s.add_development_dependency "rake"
+    s.add_development_dependency "minitest", "~> 4.7"
+    s.add_development_dependency "rr", "~> 1.0.5"
+    s.add_development_dependency "gpgme", ">= 2.0.2"
+  end
+end
diff --git a/test/dummy_source.rb b/test/dummy_source.rb
index da26e44..abedf71 100644
--- a/test/dummy_source.rb
+++ b/test/dummy_source.rb
@@ -12,7 +12,7 @@ class DummySource < Source
   attr_accessor :messages
 
   def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[]
-    super uri, last_date, usual, archived, id
+    super uri, usual, archived, id
     @messages = nil
   end
 
diff --git a/test/gnupg_test_home/gpg.conf b/test/gnupg_test_home/gpg.conf
new file mode 100644
index 0000000..b590914
--- /dev/null
+++ b/test/gnupg_test_home/gpg.conf
@@ -0,0 +1 @@
+default-key 789E7011
diff --git a/test/gnupg_test_home/pubring.gpg b/test/gnupg_test_home/pubring.gpg
new file mode 100644
index 0000000..3f604d7
Binary files /dev/null and b/test/gnupg_test_home/pubring.gpg differ
diff --git a/test/gnupg_test_home/receiver_pubring.gpg b/test/gnupg_test_home/receiver_pubring.gpg
new file mode 100644
index 0000000..84cbdc3
Binary files /dev/null and b/test/gnupg_test_home/receiver_pubring.gpg differ
diff --git a/test/gnupg_test_home/receiver_secring.gpg b/test/gnupg_test_home/receiver_secring.gpg
new file mode 100644
index 0000000..d92f0b8
Binary files /dev/null and b/test/gnupg_test_home/receiver_secring.gpg differ
diff --git a/test/gnupg_test_home/receiver_trustdb.gpg b/test/gnupg_test_home/receiver_trustdb.gpg
new file mode 100644
index 0000000..937117c
Binary files /dev/null and b/test/gnupg_test_home/receiver_trustdb.gpg differ
diff --git a/test/gnupg_test_home/secring.gpg b/test/gnupg_test_home/secring.gpg
new file mode 100644
index 0000000..513777c
Binary files /dev/null and b/test/gnupg_test_home/secring.gpg differ
diff --git a/test/gnupg_test_home/sup-test-2 at foo.bar.asc b/test/gnupg_test_home/sup-test-2 at foo.bar.asc
new file mode 100644
index 0000000..26e24d5
--- /dev/null
+++ b/test/gnupg_test_home/sup-test-2 at foo.bar.asc
@@ -0,0 +1,20 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2.0.20 (GNU/Linux)
+
+mI0EUgi0fAEEAOLAcQW96NEUSB7YE/la8X56jGW5BMX3aAixOF8LvOwMBbUK1T+U
+0H2PGIrXVcYyHcPqWRpRahbsIAldBqzffPlzMa+aqJaB1xKkNruxSoIzwPdidZMe
+l0Dxz2FDsoXD0KPyWnAYhGmQyz2MFpZxu2tlYqvwWVW//XGnk/KHvIXbABEBAAG0
+PlN1cCBUZXN0IFJlY2VpdmVyIChUZXN0IHJlY2VpdmVyIGZvciBTdXApIDxzdXAt
+dGVzdC0yQGZvby5iYXI+iL8EEwECACkFAlIItHwCGwMFCQHhM4AHCwkIBwMCAQYV
+CAIJCgsEFgIDAQIeAQIXgAAKCRAsABl+cWpykMMVBADHkQPgTz0CqKKp3k+z3dbm
+ocmI4tYNn1dOkDQqyfoBTfs6L3g4j5OE2UrguntRYyg5oon+uO5d18CQ5dY0sCw/
+o5IwyzTrxI8IocbtZvBdSb+XjLndynGuIQoqaJq9i6n1V4klFHVOna8Q9JstLfRX
+H1d4xPhnvKcaDDx/NV3X/biNBFIItHwBBADBpb43MpkrUWlg7HWJ1ZfOlxnOxrJ3
+Gz9WFNV06UbcZEuFKA/vHRjM6gWzUn5903FLuCWu3eBrq5xQfWipbp187PmocpoG
+skJ6gosLs1fMYRBjv2VbG9xJVKdKJMjqZw5FUpXKAaHr8P9jN6g2STQrbeQ8CVUK
+h7zOWRXAXSKUgwARAQABiKUEGAECAA8FAlIItHwCGwwFCQHhM4AACgkQLAAZfnFq
+cpDV1QQAzcxFXznEX92DjWxWRC7gRHgIsQk9WJnDzjtnDjSWCp3H85qeTZGZrn9W
+NoneV/S5Y7K3Mkceh4rFaANQ3zx4b05y1LFt5N/lPwIe5VB0vcPumtZum2fSGfpK
+nTXvzelcWcm2aGyUSaWvOkntWKEEt1kB5Oq6EtZoRZLMzAxLd7s=
+=aKsV
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/test/gnupg_test_home/trustdb.gpg b/test/gnupg_test_home/trustdb.gpg
new file mode 100644
index 0000000..8666c17
Binary files /dev/null and b/test/gnupg_test_home/trustdb.gpg differ
diff --git a/test/integration/test_label_service.rb b/test/integration/test_label_service.rb
new file mode 100644
index 0000000..a05b42d
--- /dev/null
+++ b/test/integration/test_label_service.rb
@@ -0,0 +1,18 @@
+require "test_helper"
+
+require "sup/service/label_service"
+
+require "tmpdir"
+
+describe Redwood::LabelService do
+  let(:tmpdir) { Dir.mktmpdir }
+  after do
+    require "fileutils"
+    FileUtils.remove_entry_secure @tmpdir unless @tmpdir.nil?
+  end
+
+  describe "#add_labels" do
+    # Integration tests are hard to write at this moment :(
+    it "add labels to all messages matching the query"
+  end
+end
diff --git a/test/test_crypto.rb b/test/test_crypto.rb
new file mode 100644
index 0000000..ae5b6eb
--- /dev/null
+++ b/test/test_crypto.rb
@@ -0,0 +1,109 @@
+# tests for sup's crypto libs
+#
+# Copyright Clint Byrum <clint at ubuntu.com> 2011. All Rights Reserved.
+# Copyright Sup Developers                 2013.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+require 'test_helper'
+require 'sup'
+require 'stringio'
+require 'tmpdir'
+
+module Redwood
+
+class TestCryptoManager < ::Minitest::Unit::TestCase
+
+    def setup
+        @from_email = 'sup-test-1 at foo.bar'
+        @to_email   = 'sup-test-2 at foo.bar'
+        # Use test gnupg setup
+        @orig_gnupghome = ENV['GNUPGHOME']
+        ENV['GNUPGHOME'] = File.join(File.dirname(__FILE__), 'gnupg_test_home')
+
+        @path = Dir.mktmpdir
+        Redwood::HookManager.init File.join(@path, 'hooks')
+
+        am = {:default=> {:name => "test", :email=> 'sup-test-1 at foo.bar'}}
+        Redwood::AccountManager.init am
+
+        Redwood::CryptoManager.init
+
+        if not CryptoManager.have_crypto?
+          warn "No crypto set up, crypto will not be tested. Reason: #{CryptoManager.not_working_reason}"
+        end
+    end
+
+    def teardown
+      CryptoManager.deinstantiate!
+      AccountManager.deinstantiate!
+      HookManager.deinstantiate!
+      FileUtils.rm_r @path
+
+      ENV['GNUPGHOME'] = @orig_gnupghome
+    end
+
+    def test_sign
+        if CryptoManager.have_crypto? then
+            signed = CryptoManager.sign @from_email, at to_email,"ABCDEFG"
+            assert_instance_of RMail::Message, signed
+            assert_equal "ABCDEFG", signed.body[0]
+            assert signed.body[1].body.length > 0 , "signature length must be > 0"
+            assert (signed.body[1].body.include? "-----BEGIN PGP SIGNATURE-----") , "Expecting PGP armored data"
+        end
+    end
+
+    def test_encrypt
+        if CryptoManager.have_crypto? then
+            encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
+            assert_instance_of RMail::Message, encrypted
+            assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
+        end
+    end
+
+    def test_sign_and_encrypt
+        if CryptoManager.have_crypto? then
+            encrypted = CryptoManager.sign_and_encrypt @from_email, [@to_email], "ABCDEFG"
+            assert_instance_of RMail::Message, encrypted
+            assert (encrypted.body[1].body.include? "-----BEGIN PGP MESSAGE-----") , "Expecting PGP armored data"
+        end
+    end
+
+    def test_decrypt
+        if CryptoManager.have_crypto? then
+            encrypted = CryptoManager.encrypt @from_email, [@to_email], "ABCDEFG"
+            assert_instance_of RMail::Message, encrypted
+            assert_instance_of String, (encrypted.body[1].body)
+            decrypted = CryptoManager.decrypt encrypted.body[1], true
+            assert_instance_of Array, decrypted
+            assert_instance_of Chunk::CryptoNotice, decrypted[0]
+            assert_instance_of Chunk::CryptoNotice, decrypted[1]
+            assert_instance_of RMail::Message, decrypted[2]
+            assert_equal "ABCDEFG" , decrypted[2].body
+        end
+    end
+
+    def test_verify
+        if CryptoManager.have_crypto?
+            signed = CryptoManager.sign @from_email, @to_email, "ABCDEFG"
+            assert_instance_of RMail::Message, signed
+            assert_instance_of String, (signed.body[1].body)
+            CryptoManager.verify signed.body[0], signed.body[1], true
+        end
+    end
+end
+
+end
diff --git a/test/test_header_parsing.rb b/test/test_header_parsing.rb
index 1929e07..a2aa2a2 100644
--- a/test/test_header_parsing.rb
+++ b/test/test_header_parsing.rb
@@ -1,16 +1,20 @@
 #!/usr/bin/ruby
 
-require 'test/unit'
+require 'test_helper'
 require 'sup'
 require 'stringio'
 
 include Redwood
 
-class TestMBoxParsing < Test::Unit::TestCase
+class TestMBoxParsing < Minitest::Unit::TestCase
+
   def setup
+    @path = Dir.mktmpdir
+    @mbox = File.join(@path, 'test_mbox')
   end
 
   def teardown
+    FileUtils.rm_r @path
   end
 
   def test_normal_headers
@@ -106,7 +110,7 @@ EOS
   end
 
   def test_from_line_splitting
-    l = MBox.new StringIO.new(<<EOS)
+    l = MBox.new mbox_for_string(<<EOS)
 From sup-talk-bounces at rubyforge.org Mon Apr 27 12:56:18 2009
 From: Bob <bob at bob.com>
 To: a dear friend
@@ -125,14 +129,14 @@ From bob at bob.com
 
 This is the end of the email.
 EOS
-    offset, labels = l.next
-    assert_equal 0, offset
-    offset, labels = l.next
+    offset = l.next_offset 0
+    assert_equal 61, offset
+    offset = l.next_offset 61
     assert_nil offset
   end
 
   def test_more_from_line_splitting
-    l = MBox.new StringIO.new(<<EOS)
+    l = MBox.new mbox_for_string(<<EOS)
 From sup-talk-bounces at rubyforge.org Mon Apr 27 12:56:18 2009
 From: Bob <bob at bob.com>
 To: a dear friend
@@ -145,13 +149,20 @@ To: a dear friend
 
 Hello again! Would you like to buy my products?
 EOS
-    offset, labels = l.next
-    assert_not_nil offset
+    offset = l.next_offset 0
+    refute_nil offset
 
-    offset, labels = l.next
-    assert_not_nil offset
+    offset = l.next_offset offset
+    refute_nil offset
 
-    offset, labels = l.next
+    offset = l.next_offset offset
     assert_nil offset
   end
+
+  def mbox_for_string content
+    File.open(@mbox, 'w') do |f|
+      f.write content
+    end
+    "mbox://#{@mbox}"
+  end
 end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..adb09b7
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,7 @@
+require "rubygems" rescue nil
+require 'minitest/autorun'
+require "rr"
+
+class Minitest::Unit::TestCase
+  include ::RR::Adapters::MiniTest
+end
diff --git a/test/test_message.rb b/test/test_message.rb
index 94b962a..6283174 100644
--- a/test/test_message.rb
+++ b/test/test_message.rb
@@ -1,6 +1,6 @@
 #!/usr/bin/ruby
 
-require 'test/unit'
+require 'test_helper'
 require 'sup'
 require 'stringio'
 
@@ -26,12 +26,16 @@ end
 
 module Redwood
 
-class TestMessage < Test::Unit::TestCase
+class TestMessage < ::Minitest::Unit::TestCase
 
   def setup
+    @path = Dir.mktmpdir
+    Redwood::HookManager.init File.join(@path, 'hooks')
   end
 
   def teardown
+    Redwood::HookManager.deinstantiate!
+    FileUtils.rm_r @path
   end
 
   def test_simple_message
@@ -72,7 +76,7 @@ EOS
     source.messages = [ message ]
     source_info = 0
 
-    sup_message = Message.new( {:source => source, :source_info => source_info } )
+    sup_message = Message.build_from_source(source, source_info)
     sup_message.load_from_source!
 
     # see how well parsing the header went
@@ -222,7 +226,7 @@ EOS
     source.messages = [ message ]
     source_info = 0
 
-    sup_message = Message.new( {:source => source, :source_info => source_info } )
+    sup_message = Message.build_from_source(source, source_info)
     sup_message.load_from_source!
 
     # read the message body chunks
@@ -272,7 +276,7 @@ EOS
     source.messages = [ message ]
     source_info = 0
 
-    sup_message = Message.new( {:source => source, :source_info => source_info } )
+    sup_message = Message.build_from_source(source, source_info)
     sup_message.load_from_source!
 
     to = sup_message.to
@@ -285,7 +289,7 @@ EOS
     from = sup_message.from
     # very basic email address check
     assert_match(/\w+@\w+\.\w{2,4}/, from.email)
-    assert_not_nil(from.name)
+    refute_nil(from.name)
 
   end
 
@@ -318,16 +322,12 @@ EOS
     source.messages = [ message ]
     source_info = 0
 
-    sup_message = Message.new( {:source => source, :source_info => source_info } )
+    sup_message = Message.build_from_source(source, source_info)
     sup_message.load_from_source!
 
     # read the message body chunks: no errors should reach this level
 
-    chunks = nil
-
-    assert_nothing_raised() do
-      chunks = sup_message.load_from_source!
-    end
+    chunks = sup_message.load_from_source!
 
     # the chunks list should be empty
 
@@ -417,15 +417,12 @@ EOS
     source.messages = [ message ]
     source_info = 0
 
-    sup_message = Message.new( {:source => source, :source_info => source_info } )
+    sup_message = Message.build_from_source(source, source_info)
     sup_message.load_from_source!
 
     # read the message body chunks
 
-    assert_nothing_raised() do
-      chunks = sup_message.load_from_source!
-    end
-
+    sup_message.load_from_source!
   end
 
   def test_blank_header_lines
@@ -508,7 +505,7 @@ EOS
     source.messages = [ message ]
     source_info = 0
 
-    sup_message = Message.new( {:source => source, :source_info => source_info } )
+    sup_message = Message.build_from_source(source, source_info)
     sup_message.load_from_source!
 
     # See how well parsing the message ID went.
@@ -517,7 +514,7 @@ EOS
 
     # Look at another header field whose first line was blank.
     list_unsubscribe = sup_message.list_unsubscribe
-    assert_equal("<http://mailman2.widget.com/mailman/listinfo/monitor-list>, " +
+    assert_equal("<http://mailman2.widget.com/mailman/listinfo/monitor-list>,\n \t" +
                  "<mailto:monitor-list-request at widget.com?subject=unsubscribe>",
                  list_unsubscribe)
 
@@ -533,4 +530,3 @@ end
 end
 
 # vim:noai:ts=2:sw=2:
-
diff --git a/test/test_server.rb b/test/test_server.rb
deleted file mode 100644
index a49d309..0000000
--- a/test/test_server.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/usr/bin/ruby
-# encoding: utf-8
-
-require 'test/unit'
-require 'iconv'
-require 'stringio'
-require 'tmpdir'
-require 'fileutils'
-require 'thread'
-require 'eventmachine'
-require 'sup'
-require 'sup/server'
-
-Thread.abort_on_exception = true
-
-module EM
-  # Run the reactor in a new thread. This is useful for using EventMachine
-  # alongside synchronous code. It is recommended to use EM.error_handler to
-  # detect when an exception terminates the reactor thread.
-  def self.spawn_reactor_thread
-    fail "reactor already started" if EM.reactor_running?
-    q = ::Queue.new
-    Thread.new { EM.run { q << nil } }
-    q.pop
-  end
-
-  # Stop the reactor and wait for it to finish. This is the counterpart to #spawn_reactor_thread.
-  def self.kill_reactor_thread
-    fail "reactor is not running" unless EM.reactor_running?
-    fail "current thread is running the reactor" if EM.reactor_thread?
-    EM.stop
-    EM.reactor_thread.join
-  end
-end
-
-class QueueingClient < EM::P::RedwoodClient
-  def initialize
-    super
-    @q = Queue.new
-    @readyq = Queue.new
-  end
-
-  def receive_message type, tag, params
-    @q << [type, tag, params]
-  end
-
-  def connection_established
-    @readyq << nil
-  end
-
-  def wait_until_ready
-    @readyq.pop
-  end
-
-  def read
-    @q.pop
-  end
-
-  alias write send_message
-end
-
-class TestServer < Test::Unit::TestCase
-  def setup
-    port = rand(1000) + 30000
-    EM.spawn_reactor_thread
-    @path = Dir.mktmpdir
-    socket_path = File.join(@path, 'socket')
-    Redwood::HookManager.init File.join(@path, 'hooks')
-    Redwood::SourceManager.init
-    Redwood::SourceManager.load_sources File.join(@path, 'sources.yaml')
-    Redwood::Index.init @path
-    Redwood::SearchManager.init File.join(@path, 'searches')
-    Redwood::Index.load
-    @server = EM.start_server socket_path,
-              Redwood::Server, Redwood::Index.instance
-    @client = EM.connect socket_path, QueueingClient
-    @client.wait_until_ready
-  end
-
-  def teardown
-    FileUtils.rm_r @path if passed?
-    puts "not cleaning up #{@path}" unless passed?
-    %w(Index SearchManager SourceManager HookManager).each do |x|
-      Redwood.const_get(x.to_sym).deinstantiate!
-    end
-    EM.kill_reactor_thread
-  end
-
-  def test_invalid_request
-    @client.write 'foo', '1'
-    check @client.read, 'error', '1'
-  end
-
-  def test_query
-    @client.write 'query', '1', 'query' => 'type:mail'
-    check @client.read, 'done', '1'
-  end
-
-  def check resp, type, tag, args={}
-    assert_equal type.to_s, resp[0]
-    assert_equal tag.to_s, resp[1]
-    args.each do |k,v|
-      assert_equal v, resp[2][k.to_s]
-    end
-  end
-end
diff --git a/test/test_yaml_migration.rb b/test/test_yaml_migration.rb
new file mode 100644
index 0000000..7f2c048
--- /dev/null
+++ b/test/test_yaml_migration.rb
@@ -0,0 +1,80 @@
+require "test_helper"
+
+require "sup"
+require "psych"
+
+describe "Sup's YAML util" do
+  describe "Module#yaml_properties" do
+    def build_class_with_name name, &b
+      Class.new do
+        meta_cls = class << self; self; end
+        meta_cls.send(:define_method, :name) { name }
+        class_exec(&b) unless b.nil?
+      end
+    end
+
+    after do
+      Psych.load_tags = {}
+      Psych.dump_tags = {}
+    end
+
+    it "defines YAML tag for class" do
+      cls = build_class_with_name 'Cls' do
+        yaml_properties
+      end
+
+      expected_yaml_tag = "!supmua.org,2006-10-01/Cls"
+
+      Psych.load_tags[expected_yaml_tag].must_equal cls
+      Psych.dump_tags[cls].must_equal expected_yaml_tag
+
+    end
+
+    it "Loads legacy YAML format as well" do
+      cls = build_class_with_name 'Cls' do
+        yaml_properties :id
+        attr_accessor :id
+        def initialize id
+          @id = id
+        end
+      end
+
+      Psych.load_tags["!masanjin.net,2006-10-01/Cls"].must_equal cls
+
+      yaml = <<EOF
+--- !masanjin.net,2006-10-01/Cls
+id: ID
+EOF
+      loaded = YAML.load(yaml)
+
+      loaded.id.must_equal 'ID'
+      loaded.must_be_kind_of cls
+    end
+
+    it "Dumps & loads w/ state re-initialized" do
+      cls = build_class_with_name 'Cls' do
+        yaml_properties :id
+        attr_accessor :id
+        attr_reader :flag
+
+        def initialize id
+          @id = id
+          @flag = true
+        end
+      end
+
+      instance = cls.new 'ID'
+
+      dumped = YAML.dump(instance)
+      loaded = YAML.load(dumped)
+
+      dumped.must_equal <<-EOF
+--- !supmua.org,2006-10-01/Cls
+id: ID
+      EOF
+
+      loaded.id.must_equal 'ID'
+      assert loaded.flag
+    end
+  end
+end
diff --git a/test/test_yaml_regressions.rb b/test/test_yaml_regressions.rb
new file mode 100644
index 0000000..1ab978e
--- /dev/null
+++ b/test/test_yaml_regressions.rb
@@ -0,0 +1,17 @@
+require 'test_helper'
+
+# Requiring 'yaml' before 'sup' in 1.9.x would get Psych loaded first
+# and becoming the default yamler.
+require 'yaml'
+require 'sup'
+
+module Redwood
+  class TestYamlRegressions < ::Minitest::Unit::TestCase
+    def test_yamling_hash
+      hsh = {:foo => 42}
+      reloaded = YAML.load(hsh.to_yaml)
+
+      assert_equal reloaded, hsh
+    end
+  end
+end
diff --git a/test/unit/service/test_label_service.rb b/test/unit/service/test_label_service.rb
new file mode 100644
index 0000000..7c46e34
--- /dev/null
+++ b/test/unit/service/test_label_service.rb
@@ -0,0 +1,19 @@
+require "test_helper"
+
+require "sup/service/label_service"
+
+describe Redwood::LabelService do
+  describe "#add_labels" do
+    it "add labels to all messages matching the query" do
+      q = 'is:starred'
+      label = 'superstarred'
+      message = mock!.add_label(label).subject
+      index = mock!.find_messages(q){ [message] }.subject
+      mock(index).update_message_state(message)
+      mock(index).save_index
+
+      service = Redwood::LabelService.new(index)
+      service.add_labels q, label
+    end
+  end
+end
diff --git a/test/unit/test_horizontal_selector.rb b/test/unit/test_horizontal_selector.rb
new file mode 100644
index 0000000..6cc252c
--- /dev/null
+++ b/test/unit/test_horizontal_selector.rb
@@ -0,0 +1,40 @@
+require "test_helper" 
+
+require "sup/horizontal_selector"
+
+describe Redwood::HorizontalSelector do
+  let(:values) { %w[foo at example.com bar at example.com] }
+  let(:strange_value) { "strange at example.com" }
+
+  before do
+    @selector = Redwood::HorizontalSelector.new(
+      'Acc:', values, [])
+  end
+
+  it "init w/ the first value selected" do
+    first_value = values.first
+    @selector.val.must_equal first_value
+  end
+
+  it "stores value for selection" do
+    second_value = values[1]
+    @selector.set_to second_value
+    @selector.val.must_equal second_value
+  end
+
+  describe "for unknown value" do
+    it "cannot select unknown value" do
+      @selector.wont_be :can_set_to?, strange_value
+    end
+
+    it "refuses selecting unknown value" do
+      old_value = @selector.val
+
+      assert_raises Redwood::HorizontalSelector::UnknownValue do
+        @selector.set_to strange_value
+      end
+
+      @selector.val.must_equal old_value
+    end
+  end
+end
diff --git a/test/unit/util/test_query.rb b/test/unit/util/test_query.rb
new file mode 100644
index 0000000..b31a6be
--- /dev/null
+++ b/test/unit/util/test_query.rb
@@ -0,0 +1,46 @@
+# encoding: utf-8
+
+require "test_helper"
+
+require "sup/util/query"
+require "xapian"
+
+describe Redwood::Util::Query do
+  describe ".describe" do
+    it "returns a UTF-8 description of query" do
+      query = Xapian::Query.new "テスト"
+      life = "生活: "
+
+      assert_raises Encoding::CompatibilityError do
+        _ = life + query.description
+      end
+
+      desc = Redwood::Util::Query.describe(query)
+      _ = (life + desc) # No exception thrown
+    end
+
+    it "returns a valid UTF-8 description of bad input" do
+      msg = "asdfa \xc3\x28 åasdf"
+      query = Xapian::Query.new msg
+      life = 'hæi'
+
+      # this is now possibly UTF-8 string with possibly invalid chars
+      assert_raises Redwood::Util::Query::QueryDescriptionError do
+        desc = Redwood::Util::Query.describe (query)
+      end
+
+      assert_raises Encoding::CompatibilityError do
+        _ = life + query.description
+      end
+    end
+
+    it "returns a valid UTF-8 fallback description of bad input" do
+      msg = "asdfa \xc3\x28 åasdf"
+      query = Xapian::Query.new msg
+
+      desc = Redwood::Util::Query.describe(query, "invalid query")
+
+      assert_equal("invalid query", desc)
+    end
+  end
+end
diff --git a/test/unit/util/test_string.rb b/test/unit/util/test_string.rb
new file mode 100644
index 0000000..0d10d08
--- /dev/null
+++ b/test/unit/util/test_string.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require "test_helper"
+
+require "sup/util"
+
+describe "Sup's String extension" do
+  describe "#display_length" do
+    let :data do
+      [
+        ['some words', 10,],
+        ['中文', 4,],
+        ['ä', 1,],
+      ]
+    end
+
+    it "calculates display length of a string" do
+      data.each do |(str, length)|
+        str.display_length.must_equal length
+      end
+    end
+  end
+
+  describe "#slice_by_display_length(len)" do
+    let :data do
+      [
+        ['some words', 6, 'some w'],
+        ['中文', 2, '中'],
+        ['älpha', 3, 'älp'],
+      ]
+    end
+
+    it "slices string by display length" do
+      data.each do |(str, length, sliced)|
+        str.slice_by_display_length(length).must_equal sliced
+      end
+    end
+  end
+
+  describe "#wrap" do
+    let :data do
+      [
+        ['some words', 6, ['some', 'words']],
+        ['some words', 80, ['some words']],
+        ['中文', 2, ['中', '文']],
+        ['中文', 5, ['中文']],
+        ['älpha', 3, ['älp', 'ha']],
+      ]
+    end
+
+    it "wraps string by display length" do
+      data.each do |(str, length, wrapped)|
+        str.wrap(length).must_equal wrapped
+      end
+    end
+  end
+end
diff --git a/test/unit/util/test_uri.rb b/test/unit/util/test_uri.rb
new file mode 100644
index 0000000..137d5af
--- /dev/null
+++ b/test/unit/util/test_uri.rb
@@ -0,0 +1,19 @@
+require "test_helper.rb"
+
+require "sup/util/uri"
+
+describe Redwood::Util::Uri do
+  describe ".build" do
+    it "builds uri from hash" do
+      components = {:path => "/var/mail/foo", :scheme => "mbox"}
+      uri = Redwood::Util::Uri.build(components)
+      uri.to_s.must_equal "mbox:/var/mail/foo"
+    end
+
+    it "expands ~ in path" do
+      components = {:path => "~/foo", :scheme => "maildir"}
+      uri = Redwood::Util::Uri.build(components)
+      uri.to_s.must_equal "maildir:#{ENV["HOME"]}/foo"
+    end
+  end
+end
diff --git a/www/index.html b/www/index.html
deleted file mode 100644
index 19ebb0a..0000000
--- a/www/index.html
+++ /dev/null
@@ -1,224 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-	<head>
-		<title>Sup</title>
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-		<link rel="stylesheet" href="main.css" type="text/css" />
-	</head>
-
-	<body>
-		<h1>Sup</h1>
-
-		<blockquote>
-		“Finally a mail client that does what we want, how we want it.”
-		</blockquote>
-
-		<blockquote>
-		“Every other client we've tried is intolerable.”
-		</blockquote>
-
-		<blockquote>
-		“It's just what I wanted, but I hadn't realized what I wanted until I saw it.”
-		</blockquote>
-
-		<blockquote>
-		“Sup is almost to the point where I could jump ship from mutt.”
-		</blockquote>
-
-		<blockquote>
-		“I was previously intrigued by a gmail-styled
-		mutt-killer written in Ruby, but having actually spent a few
-		hours reading the docs, and trying out all the keys, and
-		reading the docs again, and trying the keys out again, and then
-		actually engaging in a bunch of practice usage runs, I now
-		officially can't fucking wait for the future of this thing; it
-		has my full attention.”
-		</blockquote>
-
-			<p>
-   Sup is a console-based email client for people with a lot of email.
-   It supports tagging, very fast full-text search, automatic contact-
-   list management, custom code insertion via a hook system, and more.
-   If you're the type of person who treats email as an extension of your
-   long-term memory, Sup is for you.
-		</p>
-
-		<p>
-		Sup makes it easy to:
-		</p>
-
-		<ul>
-			<li>
-			Handle massive amounts of email.
-			</li>
-
-			<li>
-			Mix email from different sources: mbox files and maildirs.
-			For remote sources (IMAP, IMAPS, ssh+file), use another
-			tool (offlineimap, fetchmail, rsync) to grab local copies.
-			</li>
-
-			<li>
-			Instantaneously search over your entire email collection.
-			Search over body text, or use a query language to combine
-			search predicates in any way.
-			</li>
-
-			<li>
-			Handle multiple accounts. Replying to email sent to a
-			particular account will use the correct SMTP server, signature,
-			and from address.
-			</li>
-
-			<li>
-			Add custom code to handle certain types of messages or to
-			handle certain types of text within messages.
-			</li>
-
-			<li>
-			Organize email with user-defined labels, automatically track
-			recent contacts, and much more!
-			</li>
-		</ul>
-
-		<p>
-		The goal of Sup is to become the email client of choice for nerds
-		everywhere.
-		</p>
-
-		<h2>Screenshots</h2>
-
-		<ul id="screenshots">
-			<li><a href="ss1.png"><img src="ss1-small.png" alt="Sup screenshot 1" /></a></li>
-			<li><a href="ss2.png"><img src="ss2-small.png" alt="Sup screenshot 2" /></a></li>
-			<li><a href="ss3.png"><img src="ss3-small.png" alt="Sup screenshot 3" /></a></li>
-			<li><a href="ss4.png"><img src="ss4-small.png" alt="Sup screenshot 4" /></a></li>
-			<li><a href="ss5.png"><img src="ss5-small.png" alt="Sup screenshot 5" /></a></li>
-			<li><a href="ss6.png"><img src="ss6-small.png" alt="Sup screenshot 6" /></a></li>
-		</ul>
-
-		<h2>Documentation</h2>
-
-		<p>
-		Please read the <a href="README.txt">README</a>, the <a
-			href="FAQ.txt">FAQ</a>, the <a href="NewUserGuide.txt">new user guide</a>
-		and the <a href="Philosophy.txt">philosophical statement</a>.
-		</p>
-
-                <p> Please also read and contribute to the <a href="http://sup.rubyforge.org/wiki/wiki.pl">Sup wiki</a>. </p>
-
-		<h2>Status</h2>
-
-		<p>
-		The current version of Sup is 0.12.1, released 2011-01-23. This is a
-		beta release. It supports mbox and Maildir mailstores.
-		</p>
-
-        <p>To be notified by email of Sup releases, subscribe to the
-           <a href="http://rubyforge.org/mailman/listinfo/sup-announce">sup-announce mailing list</a>. One email per release.
-        </p>
-
-        <!-- <p>Issue and release status is available on the <a href="ditz/">Sup ditz page</a>.</p> -->
-        <p>Sup news can often be see on <a href="http://all-thing.net/label/sup/">William's blog</a>.</p>
-
-        <h2>Bug reports</h2>
-        <p>Find a problem with Sup? Or have a feature you'd like to see? <a href="http://masanjin.net/sup-bugs/issue?@template=item">Submit
-        it</a> to the <a href="http://masanjin.net/sup-bugs/">official Sup issue
-        tracker</a>.
-
-		<h2>Getting it</h2>
-
-		<p>
-		You can download Sup releases from the <a
-			href="http://rubyforge.org/projects/sup/">Sup RubyForge page</a>.
-		If you have RubyGems installed, simply command your computer to "gem
-		install sup".
-		</p>
-
-		<p>
-		If you're interested in development, you can clone the git repository like so: <code>git clone git://gitorious.org/sup/mainline.git</code>. You can also browse the <a
-			href="http://gitorious.org/projects/sup">Sup Gitorious
-			repository</a>. You may consider subscribing to the sup-devel list (below).
-		</p>
-
-		<h2>Make some new friends</h2>
-
-		<p> If you'd like to meet hot single Sup users in your area, try the <a
-			href="http://rubyforge.org/mailman/listinfo/sup-talk">sup-talk mailing list</a> (<a href="http://rubyforge.org/pipermail/sup-talk/">archives</a>).
-		</p>
-
-        <p> If you'd like to meet some grumpy old programmers to talk about Sup internals with, try the <a
-			href="http://rubyforge.org/mailman/listinfo/sup-devel">sup-devel mailing list</a> (<a href="http://rubyforge.org/pipermail/sup-devel/">archives</a>).
-
-
-		<h2>Credit</h2>
-
-		<p>
-		Sup is brought to you by <a href="http://masanjin.net/">William Morgan</a> and the following honorable contributors:
-        <ul>
-          <li> William Morgan </li>
-          <li> Rich Lane </li>
-          <li> Ismo Puustinen </li>
-          <li> Nicolas Pouillard </li>
-          <li> Eric Sherman </li>
-          <li> Michael Stapelberg </li>
-          <li> Ben Walton </li>
-          <li> Mike Stipicevic </li>
-          <li> Marcus Williams </li>
-          <li> Lionel Ott </li>
-          <li> Tero Tilus </li>
-          <li> Ingmar Vanhassel </li>
-          <li> Mark Alexander </li>
-          <li> Gaute Hope </li>
-          <li> Christopher Warrington </li>
-          <li> W. Trevor King </li>
-          <li> Gaudenz Steinlin </li>
-          <li> Richard Brown </li>
-          <li> Marc Hartstein </li>
-          <li> Sascha Silbe </li>
-          <li> Israel Herraiz </li>
-          <li> Anthony Martinez </li>
-          <li> Hamish Downer </li>
-          <li> Bo Borgerson </li>
-          <li> William Erik Baxter </li>
-          <li> Michael Hamann </li>
-          <li> Grant Hollingworth </li>
-          <li> Adeodato Simó </li>
-          <li> Daniel Schoepe </li>
-          <li> Jason Petsod </li>
-          <li> Steve Goldman </li>
-          <li> Edward Z. Yang </li>
-          <li> Decklin Foster </li>
-          <li> Cameron Matheson </li>
-          <li> Carl Worth </li>
-          <li> Jeff Balogh </li>
-          <li> Andrew Pimlott </li>
-          <li> Alex Vandiver </li>
-          <li> Peter Harkins </li>
-          <li> Kornilios Kourtis </li>
-          <li> Giorgio Lando </li>
-          <li> Damien Leone </li>
-          <li> Benoît PIERRE </li>
-          <li> Alvaro Herrera </li>
-          <li> Jonah </li>
-          <li> Adam Lloyd </li>
-          <li> Todd Eisenberger </li>
-          <li> ian </li>
-          <li> Steven Walter </li>
-          <li> ian </li>
-          <li> Jon M. Dugan </li>
-          <li> Gregor Hoffleit </li>
-          <li> Stefan Lundström </li>
-          <li> Kirill Smelkov </li>
-        </ul>
-        </p>
-
-		<p>
-		Sup is made possible by the <a href="http://xapian.org">Xapian</a> search engine,
-		and by <a href="http://www.lickey.com/">Matt Armstrong</a> and his
-		tragically abandoned <a href="http://www.rfc20.org/rubymail/">RubyMail</a>
-		package.
-		</p>
-	</body>
-</html>
diff --git a/www/main.css b/www/main.css
deleted file mode 100644
index 63246fa..0000000
--- a/www/main.css
+++ /dev/null
@@ -1,36 +0,0 @@
-body
-{
-	background: white;
-	color: #404040;
-	margin-bottom: 2em;
-	margin-left: auto;
-	margin-right: auto;
-	width: 90%;
-}
-
-a, a:visited
-{
-	border-bottom: 1px dotted #03c;
-	background: inherit;
-	color: #03c;
-	text-decoration: none;
-}
-
-#screenshots
-{
-	margin: 0;
-	padding: 0;
-}
-#screenshots li
-{
-	display: inline;
-	list-style: none;
-}
-#screenshots a
-{
-	border-bottom: none;
-}
-#screenshots img
-{
-	border: 0;
-}
diff --git a/www/ss1.png b/www/ss1.png
deleted file mode 100644
index a5c43d4..0000000
Binary files a/www/ss1.png and /dev/null differ
diff --git a/www/ss2.png b/www/ss2.png
deleted file mode 100644
index f9b6df7..0000000
Binary files a/www/ss2.png and /dev/null differ
diff --git a/www/ss3.png b/www/ss3.png
deleted file mode 100644
index 4038cb5..0000000
Binary files a/www/ss3.png and /dev/null differ
diff --git a/www/ss4.png b/www/ss4.png
deleted file mode 100644
index eb79f3e..0000000
Binary files a/www/ss4.png and /dev/null differ
diff --git a/www/ss5.png b/www/ss5.png
deleted file mode 100644
index aa367ae..0000000
Binary files a/www/ss5.png and /dev/null differ
diff --git a/www/ss6.png b/www/ss6.png
deleted file mode 100644
index 68ff5bf..0000000
Binary files a/www/ss6.png and /dev/null differ

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/sup-mail.git



More information about the Pkg-ruby-extras-commits mailing list